英文:
How do I choose action depending on type in this case?
问题
可以改进标题并添加标签。我不知道在这里使用什么术语。
我有如下的类:
abstract class A {
void foo(Object obj) {
pre();
if(obj instanceof Map<String, String[]>) {
methodA();
} else if (obj instanceof Map<String, String>) {
methodB();
} else if (obj instanceof JSONObject) {
...
post();
}
abstract protected void methodA();
abstract protected void methodB();
}
class B extends A { /* Stuff */}
注意我有两种不同的映射。但让我们专注于第一个,Map<String, String[]>
。if语句无法编译,所以我尝试了这个:
interface MyMap extends Map<String, String[]> {}
以及
if(obj instanceof MyMap) {
那样是可以编译的。然而,这似乎不起作用。
Map<String, String[]> map = new Hashtable<>();
B b = new B();
b.foo(map);
这会使上面的if语句失败,所以我尝试了这个:
MyMap map = new Hashtable<>();
但然后我得到了这个错误:
incompatible types: cannot infer type arguments for java.util.Hashtable<>
以及使用
MyMap map = new Hashtable<String, String[]>();
我得到了这个错误:
incompatible types: java.util.Hashtable<java.lang.String,java.lang.String[]> cannot be converted to MyMap
我在这里想要实现的是,以前我为每个对象都有一个单独的方法。但我不喜欢代码重复,因为pre()
和post()
对于所有这些方法都是相同的。我应该如何解决这个问题?
英文:
Feel free to improve the title and to add tags. I did not know the terminology to use here.
I have classes like this:
abstract class A {
void foo(Object obj) {
pre();
if(obj instanceof Map<String, String[]>) {
methodA();
} else if (obj instanceof Map<String, String>) {
methodB();
} else if (obj instanceof JSONObject) {
...
post();
}
abstract protected void methodA();
abstract protected void methodB();
}
class B extends A { /* Stuff */}
Note that I have two different maps. But let's focus on the first, Map<String, String[]>
. The if statment did not compile, so I tried this:
interface MyMap extends Map<String, String[]> {}
and
if(obj instanceof MyMap) {
That compiled. However, this does not seem to work.
Map<String, String[]> map = new Hashtable<>();
B b = new B();
b.foo(map);
This makes the above if statement fail, so I tried this:
MyMap map = new Hashtable<>();
But then I get this:
incompatible types: cannot infer type arguments for java.util.Hashtable<>
and with
MyMap map = new Hashtable<String, String[]>();
I get this:
incompatible types: java.util.Hashtable<java.lang.String,java.lang.String[]> cannot be converted to MyMap
What I'm trying to achieve here is that previously, I had a separate method for each object. But I don't like the code duplication, because the pre()
and post()
are the same for all of them. How should I solve this?
答案1
得分: 2
你对Java有一些误解。
你似乎期望,鉴于以下代码:
interface MyMap extends Map<String, String[]> {}
你已经做出了一个本体声明:你似乎认为这意味着:
“我在此宣布,所有是 Map<String, String[]>
的东西也都是 MyMap
。”
或者可能你认为这意味着:
“我创建了一个别名;MyMap
只是 Map<String, String[]>
的简写。”
但这两种都是不正确的。
正确的意思是:
我创建了一个全新的名为 MyMap
的接口。任何 MyMap
必然也是 Map<String, String[]>
,但反过来未必成立。就现在而言,除了 Map
接口要求的方法之外,我没有进一步的要求。唯一的 MyMap
对象是通过编写 new X()
创建的,其中 X 是一个明确在类上带有 implements MyMap
的类,或者是一个做了这个声明的类的子类。
因此,在解决你的问题时,这是完全无用的。
你的第二个误解涉及泛型。
泛型主要是编译器想象出来的。它们是编译器检查的注释。认真看看:
Map<?, ?> = new HashMap<String, String>();
和
Map<?, ?> = new HashMap<Integer, Integer>();
都编译通过,并且对结果进行二进制比较。完全相同的字节。这清楚地证明了 <String, String>
部分在编译后 不会保留。
这被称为擦除。在反射可访问的内容中出现泛型,比如方法的返回类型、方法的参数、扩展子句中的某些类型、字段的类型等,它会存储在类文件中,但作为注释存在(也就是说,JVM 本身甚至不会看它,对此毫不关心)。这个注释供 javac
使用,用于编译引用这些字段/方法/类的其他代码。
但在其他地方呢?javac 用它来生成警告、错误,并且偷偷地插入一些类型转换,然后它就 消失了。
因此,这个代码:
x instanceof Map<String, String>
是不可能的。
你不能检查那个。x
知道它是一个 Map
。但它不知道泛型是什么。
解决方案是退一步。
你遇到了某个未知的问题 X,然后你想:我知道了!我会编写一个方法,接受任何对象,然后我会检查传入的对象是否是 Map<String, String>
,然后我会解决我的未知问题 X!但我不知道怎么检查,所以我会在 Stack Overflow 上问。
但这是一个死胡同。回去重新问问关于 '未知问题 X'。我敢肯定它是可以解决的,而且非常容易解决,但不是用这种方式。
对你可能的解决方案的一次猜测
如果你想消除代码重复,并且你要消除的重复代码是 pre
和 post
,比如:
public void someMethod(SomeSpecificType x) {
pre();
customStuffUniqueToThisType();
post();
}
// 还有 20 多个类似的方法,同样的 pre 和 post
那么或许考虑使用 Lambda 表达式。你可以创建这个方法:
public <T> void apply(T obj, Consumer<T> runner) {
pre();
runner.consume(obj);
post();
}
然后你可以调用它:
Map<String, String> obj = ....;
apply(obj, o -> {
// o 是一个 Map<String, String>。
// 在这里随便做任何你想做的事情。
});
英文:
You have some misunderstandings about java.
You seem to expect that, given:
> interface MyMap extends Map<String, String[]> {}
That you've made an ontological declaration: You seem to think this means:
"I henceforth decree that all things that are a Map<String, String[]>
are also MyMap
s."
Or possibly you think this means:
"I have created an alias; MyMap
is just shorthand for Map<String, String[]>
".
Neither is true.
The correct meaning is:
I created an entirely brand new interface called MyMap
. Any MyMap is necessarily a Map<String, String[]>, but the reverse need not be true. Right now, other than what that Map interface demands you have as methods, I make no further demands. The only objects that are MyMap
s are any objects created by writing new X()
, where X is a class that explicitly has implements MyMap
on it, or is a subclass of a class that did that.
And therefore, it is completely useless in trying to address your problem here.
Your second misunderstanding is generics.
Generics are primarily a figment of the compiler's imagination. They are compiler-checked comments. Seriously. Go check:
Map<?, ?> = new HashMap<String, String>();
and
Map<?, ?> = new HashMap<Integer, Integer>();
compile both, and do a binary diff on the result. Exactly the same bytes. Thus clearly proving that the <String, String>
part does not survive compilation.
This is called erasure. There where generics appears in reflectively accessible stuff, such as in the return type of a method, or the parameter of a method, or some type in an extends clause, or a field's type - it is stored in the class file, but as a comment (as in, the JVM itself doesn't even look at it and cares absolutely not one iota about it). The comment is there for javac
to look at, and use when compiling other code that references these fields/methods/classes.
But everywhere else? javac uses it to generate warnings, errors, and silently inject a few casts, and then it disappears.
Thus, this:
x instanceof Map<String, String>
is impossible.
You just can't check that. x
knows that it is a Map
. It has no idea what the generics are, though.
The solution then is to take a step back.
You had some unknown problem X, and you thought: I know! I'll write a method that takes any object, and then I'll check if the passed-in object is a Map<String, String>
, and then I'll solve my unknown problem X! Except I don't know how to check for that so I'll ask on SO.
But this is a dead end. Go back and re-ask about 'unknown problem X'. I'm positive it can be solved, and quite easily at that, but not like this.
A wild stab at a possible solution for you
If you're looking to eliminate code duplication, and the duplicated code you're trying to deduplicate is the pre
and post
in:
public void someMethod(SomeSpecificType x) {
pre();
customStuffUniqueToThisType();
post();
}
// and 20 more of these, with the same pre and post everywhere
Then perhaps consider lambdas. You could make this method:
public <T> void apply(T obj, Consumer<T> runner) {
pre();
runner.consume(obj);
post();
}
and you can then call it:
Map<String, String> obj = ....;
apply(obj, o -> {
// o is a Map<String, String>.
// do whatever you want here.
});
答案2
得分: 2
这是面向对象世界中已经多次解决的常见用例。
- 在了解了模板模式之后,你应该明白你的代码已经在某种程度上使用了这个模式。(尽管:
foo
应该是final
) - 我会让类
A
接受一个类型参数(即abstract class A<T>
)。我会在class A
中创建一个接受 T 作为输入的构造函数(即A(T t) { this.t = t}
) - 不要再有
methodA
和methodB
这两个方法,我只会有一个叫做doSomething(T t)
的抽象方法;我只会调用它一次,将t
实例变量传递给它。我会移除所有的 if-else 条件。foo
的最终实现将是foo() { pre(); doSomething(t); post() }
- 类
B
然后可以扩展A
并实现doSomething(T t)
。例如:class B extends A<Map<String, String[]>> { doSomething(<Map<String, String[]>> t) {...做些什么..} }
- 类似地,我可以分别为处理
Map<String, String>
和JSONObject
创建一个子类。 - 在这一点上,我有了不同的
A
实现。我需要一个名为Context
的类,它将被传递一个A
,并调用a.foo()
。我需要一个名为Factory
的类,它创建一个Context
类并将合适的A
子类实例根据某些运行时输入参数传递给它。
英文:
This is a common use case already solved multiple times in the object oriented world.
What you are looking for is a combination of Template Pattern, the Strategy Pattern and the Factory pattern.
- After reading up on the Template pattern, it should be clear that your code is somewhat already using this pattern. (Although :
foo
should befinal
) - I would make class
A
take a type parameter (i.eabstract class A<T>
). I would create a constructor inclass A
that takes T as input (i.eA(T t) { this.t = t}
) - Instead of having 2 methods namely
methodA
andmethodB
, I would simply have one abstract method calleddoSomething(T t)
; I would call it only once, passing it thet
instance variable. I would remove all the if-else conditions. The final implementation offoo
would befoo() { pre(); doSomething(t); post() }
- class
B
can then extendA
and implementdoSomething(T t)
. Example :class B extends A<Map<String, String[]>> { doSomething(<Map<String, String[]>> t) {...do something..} }
- Similarly, I can create one subclass each for handling
Map<String, String>
andJSONObject
respecrively. - At this point, I have different implementations of
A
. I need to have aContext
class that will be passed anA
and it will calla.foo()
. I need aFactory
class that creates aContext
class and passes it the right instance of a subclassA
depending on some runtime input parameter.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论