我有两个通用方法,旨在强制调用者提供类型匹配的参数:
private <T> void compareValues(Supplier<T> supplier, T value) {
System.out.println(supplier.get() == value);
}
private <T> void setValue(Consumer<T> consumer, T value) {
consumer.accept(value);
}
但是,在调用它们时,编译器对允许作为参数传递的内容有不同的理由:
compareValues(this::getString, "Foo"); // Valid, as expected
compareValues(this::getInt, "Foo"); // Valid, but compiler should raise error
compareValues(this::getString, 1); // Valid, but compiler should raise error
setValue(this::setString, "Foo"); // Valid, as expected
setValue(this::setInt, "Foo"); // Type mismatch, as expected
setValue(this::setString, 1); // Type mismatch, as expected
private String getString() {
return "Foo";
}
private int getInt() {
return 1;
}
private void setString(String string) {
}
private void setInt(int integer) {
}
为什么?编译器是否过于笨拙以至于无法在此处正确推理类型,或者这是类型系统的一个特性?如果是这样,导致这种行为的规则是什么?另外,如果可能的话,如何在不添加人工参数的情况下创建compareValues的“类型安全”版本?
请注意,提供的方法只包含一个虚拟实现,并不反映我实际代码库中的代码。这里的重点仅仅是方法调用。
答案 0 :(得分:3)
其他人已经提到过为什么会这样,所以这是一个解决问题的解决方案。
如果创建泛型类,将供应商的传递与参数的传递分开,则不会给编译器选择交集类型的机会:
pop
通过分离出这种行为,您还允许public class Comparer<T>
{
private final Supplier<T> supplier;
Comparer(final Supplier<T> supplier)
{
this.supplier = supplier;
}
void compare(T value)
{
System.out.println(supplier.get() == value);
}
}
new Comparer<>(this::getString).compare("Foo"); // Valid, as expected
new Comparer<>(this::getInt).compare("Foo"); // Invalid, compiler error
new Comparer<>(this::getString).compare(1); // Invalid, compiler error
执行可能有用的操作,例如缓存Comparer
的结果。
答案 1 :(得分:2)
您可以通过
告诉编译器选择交集类型javac -XDverboseResolution=deferred-inference
其中一个案例的输出是:
instantiated signature: (Supplier<INT#1>,INT#1)void
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>compareValues(Supplier<T>,T)
where INT#1,INT#2 are intersection types:
INT#1 extends Object,Serializable,Comparable<? extends INT#2>
INT#2 extends Object,Serializable,Comparable<?>
答案 2 :(得分:1)
这里T
可以是任何东西。它是一种类型的同义词,但基本上可以是任何类型。
因此,当您拥有compareValues(Supplier<T> supplier, T value)
时,它意味着供应商可以为我提供任何类型的任何类型和价值。因此它不会产生编译错误,甚至可以正常工作。在您的方法中,您可以:
private <T> void compareValues(Supplier<T> supplier, T value) {
value=supplier.get(); //It is still valid even if you give different types
System.out.println((supplier.get() == value) +" - "+ value);
}
至于另一种方法,它是不同的,因为你说“给我一个接受任何类型的消费者”但你给他一个只接受字符串的消费者。
所以这里
private void setString(String s) {
}
无效,但
private <T> void setString(T s) {
}
工作得很好。
就好像你有一个Object类型的变量你可以为它分配String而不是在更奇怪的情况下反过来。 String供应商是<T>
供应商,但String消费者不是<T>
消费者。
请参阅以下两种方法:
private <T> void setString(T a) {
T var=a;
T var2="Asdf"; //This doesn't compile! cannot convert String to T
}
private <String> void setString2(String a) {
String var=a;
String var2="asd";
}
你想要第一种方法是T型消费者。但是你试着给一个String类型的消费者,它不能工作,因为它只消耗字符串而你想要一个可以消耗所有东西的方法