使用通用供应商和lambda进行类型检查

时间:2018-05-03 09:00:34

标签: java generics lambda java-8

我有两个通用方法,旨在强制调用者提供类型匹配的参数:

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的“类型安全”版本?

请注意,提供的方法只包含一个虚拟实现,并不反映我实际代码库中的代码。这里的重点仅仅是方法调用。

3 个答案:

答案 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类型的消费者,它不能工作,因为它只消耗字符串而你想要一个可以消耗所有东西的方法