假设我们有两种方法,如下所示:
public static <T> T genericReturn() { /*...*/ }
public static String stringReturn() { /*...*/ }
在调用任何方法时,无论是否有任何要求,您都可以提供类型见证:
String s;
s = Internet.<String>genericReturn(); //Type witness used in return type, returns a String
s = Internet.<Integer>stringReturn(); //Type witness ignored, returns a String
然而,我根本没有在Java中看到任何实际用途,除非无法推断出这种类型(这通常表明存在更大的问题)。此外,当它没有被适当使用时被简单地忽略这一事实似乎违反直觉。那么在Java中有什么意义呢?
答案 0 :(得分:20)
- 如果方法调用包含显式类型参数,并且该成员是泛型方法,则类型参数的数量等于方法的类型参数的数量。
此子句意味着非泛型方法可能适用于提供显式类型参数的调用。实际上,它可能会变得适用。在这种情况下,类型参数将被忽略。
接下来是理由
这条规则源于兼容性和可替代性原则的问题。由于接口或超类可以独立于其子类型进行泛化,因此我们可以使用非泛型方法覆盖泛型方法。但是,重写(非泛型)方法必须适用于对泛型方法的调用,包括显式传递类型参数的调用。否则,子类型不能替代其生成的超类型。
沿着这条推理,让我们构建一个例子。假设在Java 1.4中,JDK有一个类
public class Foo
{
/** check obj, and return it */
public Object check(Object obj){ ... }
}
有些用户编写了一个扩展Foo
的专有类,并覆盖了check
方法
public class MyFoo extends Foo
{
public Object check(Object obj){ ... }
}
当Java 1.5引入泛型时,Foo.check
被归为
public <T> T check(T obj)
雄心勃勃的向后可比性目标要求MyFoo
仍然在Java 1.5中编译而不进行修改;而MyFoo.check[Object->Object]
仍然是Foo.check[T->T]
的最重要方法。
现在,根据上述理由,由于这个编译:
MyFoo myFoo = new MyFoo();
((Foo)myFoo).<String>check("");
这也必须编译:
myFoo.<String>check("");
即使MyFoo.check
不是通用的。
听起来像是一段时间。但即使我们购买这个论点,解决方案仍然过于宽泛和过度。 JLS可能会收紧它,因此myFoo.<String,String>check
和obj.<Blah>toString()
是非法的,因为类型参数arity不匹配。他们可能没有时间去解决它,所以他们只是采取了一条简单的路线。
答案 1 :(得分:4)
当类型推断不起作用时,您需要类型见证(钻石中的类型)(请参阅http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html)
给出的例子是菊花链式调用,如:
processStringList(Collections.emptyList());
其中processStringList定义为:
void processStringList(List<String> stringList)
{
// process stringList
}
这会导致错误,因为它无法将List<Object>
转换为List<String>
。因此,证人是必需的。虽然,你可以分多步完成,但这可以更方便。
答案 2 :(得分:2)
想知道为什么&#34; Type Witness&#34;在Java中被扔了? :d
为了理解这一点,我们应该从Type Inference开始讲故事。
类型推断是Java编译器查看每个方法调用和相应声明以确定使调用适用的类型参数(或参数)的能力。 推理算法确定参数的类型,如果可用,还确定分配或返回结果的类型。最后,推理算法试图找到适用于所有参数的最具体类型。
如果上述算法仍然无法确定我们的类型&#34; Type Witness&#34;明确说明我们需要什么类型。例如:
public class TypeWitnessTest {
public static void main(String[] args) {
print(Collections.emptyList());
}
static void print(List<String> list) {
System.out.println(list);
}
}
以上代码无法编译:
TypeWitnessTest.java:11: error: method print in class TypeWitnessTest cannot be applied to given types;
print(Collections.emptyList());
^
required: List<String>
found: List<Object>
reason: actual argument List<Object> cannot be converted to List<String> by method invocation conversion
1 error
所以,你有 Type Witness 来拯救这个:
public class TypeWitnessTest {
public static void main(String[] args) {
print(Collections.<String>emptyList());
}
static void print(List<String> list) {
System.out.println(list);
}
}
这是可编辑的并且工作正常,但Java 8
中的情况有所改善
JEP 101: Generalized Target-Type Inference
PS:我从基础开始,以便其他StackOverflow读者也可以受益。
修改强>:
关于非一般见证的类型见证 !
public class InternetTest {
public static void main(String[] args) {
String s;
s = Internet.<String>genericReturn(); //Witness used in return type, returns a string
s = Internet.<Integer>stringReturn(); //Witness ignored, returns a string
}
}
class Internet {
public static <T> T genericReturn() { return null; }
public static String stringReturn() { return null; }
}
我尝试使用javac 1.6.0_65
模拟@Rogue示例,但编译失败并出现以下错误:
javac InternetTest.java
InternetTest.java:5: stringReturn() in Internet cannot be applied to <java.lang.Integer>()
s = Internet.<Integer>stringReturn(); //Witness ignored, returns a string
^
1 error
@Rogue:如果您使用的是先前版本,请告诉我您的javac版本。如果你当时就不允许了。 :P
答案 3 :(得分:2)
这是因为向后兼容和/或向前兼容性(在源级别)。
想象一下类似于在JDK 7中为Swing中的某些类引入泛型参数。它也可能与方法一起发生(即使有限制)。如果事情证明是个坏主意,你可以删除它们,使用它的源代码仍然可以编译。在我看来,这就是为什么允许这样做的原因,即使它没有被使用。
虽然灵活性有限。如果您使用n
类型引入了类型参数,则以后{。}}或m
无法更改为m != 0
类型(以源兼容的方式)。
(我知道这可能不会回答你的问题,因为我不是Java的设计者,这只是我的想法/意见。)