Java通用方法声明的两种方式 [英] Two ways of java generic method declaration

查看:65
本文介绍了Java通用方法声明的两种方式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

虽然有类似的问题,例如官方教程至于我.

While there are similar questions around like this and this; those don't answer what are the difference between two ways of generic declaration and why one of it compiles but other not in the following example?
The one that doesn't compile was based on official tutorial as for me.

public void processMap1(Map<String, List<?>> map) {
}

public <T> void processMap2(Map<String, List<T>> map) {
}

public void f() {
    Map<String, List<String>> map = new HashMap<>();

    processMap1(map); // ERROR
    processMap2(map); // OK
}

我希望我的方法可以同时使用Map<String, List<String>>Map<String, List<Object>>

I want my methods to work with both Map<String, List<String>> and Map<String, List<Object>>

推荐答案

您有一个期望类型为X的对象的方法.为了将对象传递给此方法,该对象必须具有类型Y,该类型是X子类型.

You have a method that expects an object of type X. In order to pass an object to this method, this object must have a type Y that is a subtype of X.

对于简单情况,这可能是显而易见的:

This may be obvious for simple cases:

void processNumber(Number number) { ... }
void callIt()
{
    Integer integer = null;
    processNumber(integer); // Fine. Integer is a subtype of Number

    String string = null;
    processNumber(string); // Error. String is NOT a subtype of Number
}

到目前为止,太好了.但是现在您进入了参数化类型的令人困惑的子类型关系世界. Angelika Langer在她的泛型常见问题解答中详细介绍了该问题:其中存在哪些超亚型关系泛型类型的实例化?:

So far, so good. But now you're entering the confusing world of subtype-relationships for parameterized types. Angelika Langer goes into the details in her Generics FAQ: Which super-subtype relationships exist among instantiations of generic types?:

这是相当复杂的,并且最好根据设置此FAQ条目中的表来确定类型关系.

This is fairly complicated and the type relationships are best determined setting up tables as explained in this FAQ entry.

因此,我什至不会尝试在此处重现此内容,而只是尝试解释(简化)第二个版本为何有效,而第一个版本无效的原因.

So I won't even try to reproduce this here, but only try to explain (simplified) why the second version works, but the first one doesn't.

第二版本有效的事实可以用编译器所做的类型推断来解释.基本上,它只是查看参数,看到它是Map<String, List<String>>,并推断方法声明的T必须为String才能起作用.您可以想象这是T只是被String取代,然后该方法完美匹配. (这并不是那么容易,但是可以像这样直观地理解)

The fact that the second version works can be explained with the type inference that the compiler does. Basically, it just looks at the argument, sees that it is Map<String, List<String>>, and infers that the T of the method declaration must be String for this to work. You can imagine this as T simply being replaced with String, and then the method matches perfectly. (It's not really so easy, but can intuitively be understood like this)

第一版本无法工作的原因似乎也很简单.回到初始语句:该方法需要一个对象,该对象具有方法签名中声明的类型的 subtype .关键是:

The reason of why the first version does not work is seemingly simple as well. Coming back to the initial statement: The method expects an object that has a subtype of the type declared in the method signature. And the key point is:

Map<String, List<String>>不是Map<String, List<?>>

在这种情况下,甚至可以通过省略方法调用来简化这一事实:

This fact can (in this case) even be simplified by omitting the method invocation:

Map<String, List<String>> map = new HashMap<>();
Map<String, List<?>> otherMap = map; // Does not work

同样,我将类型理论"的细节留在上面链接的FAQ条目之后,但至少要说明为什么不允许这样做:Map<String, List<?>>是一种将字符串映射到列表的映射包含未知/未指定的类型.因此,您的processMap1方法的以下实现将是有效的:

Again, I'll leave the details of the "type theory" behind that to the FAQ entry linked above, but to at least explain why it is not allowed: A Map<String, List<?>> is a map that maps strings to lists that contain an unknown/unspecified type. So the following implementation of your processMap1 method would be valid:

public void processMap1(Map<String, List<?>> map) 
{
    List<Number> numbers = new ArrayList<Number>();
    map.put("x", numbers);
}

想象一下,用Map<String, List<String>>调用此方法是有效的:

And imagine, calling this method with a Map<String, List<String>> was valid:

public void f() {
    Map<String, List<String>> map = new HashMap<>();
    processMap1(map); // Imagine this was possible
    List<String> shouldBeStrings = map.get("x");
}

然后,您迟早会以ClassCastException结尾,因为您将Map<String, List<String>>传递给方法,但是允许该方法在此映射中放置List<Number>.

then you would end up with a ClassCastException sooner or later, because you pass Map<String, List<String>> to the method, but the method is allowed to put a List<Number> into this map.

或者简而言之:不允许使用,因为它不是类型安全的.

Or for short: It is not allowed because it is not type-safe.

根据评论进行编辑(这超出了原始问题,因此,我将尽量简短):

Edit in response to the comments (this goes somewhat beyond the original question, so I'll try to keep it short) :

对于第二种情况,没有T可以捕获任何类型.类型是?,从直觉上讲,它可以代表任何类型.在建议从列表的reverserev调用的情况下,T仍然是?,但可以用于专门表示列表中元素的类型 .还是再次提及子类型关系:

For the second case, there is no T to capture any type. The type is ?, which, intuitively, can stand for any type. In the case of the suggested call from reverse to rev for lists, the T is still ?, but may be used to dedicatedly represent the type of the elements in the list. Or again, referring to the subtype relationships:

List<T>总是 List<?>子类型(与T无关)

List<T> is always a subtype of List<?> (regardless of what T is)

由于上述原因,从processMap1processMap2的建议调用不起作用:即使YX的子类型,类型Map<String, Y>也是不是 Map<String, X>的子类型.在这里,此问题可能是相关的.

The suggested call from processMap1 to processMap2 does not work for the reasons explained above: Even when Y is a subtype of X, the type Map<String, Y> is not a subtype of Map<String, X>. Here, this question may be relevant.

这篇关于Java通用方法声明的两种方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆