【问题标题】:Type checking with generic Suppliers and lambdas使用通用供应商和 lambda 进行类型检查
【发布时间】:2018-05-03 09:00:34
【问题描述】:

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

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

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

【问题讨论】:

  • 编译器推断出compareValues() 参数的最常见类型,因此对于第二次和第三次compareValues() 调用,结果是Serializable &amp; Comparable&lt;? extends Serializable &amp; Comparable&lt;?&gt;&gt;
  • 另外,请注意您可能应该使用equals
  • @VladimirVagaytsev 在哪些情况下编译器会这样做?
  • @xagaffar 在每种情况下,编译器都会尝试找到最常见的类型,如果找不到它只会选择Object

标签: java generics lambda java-8


【解决方案1】:

其他人已经提到为什么会发生这种情况,所以这里有一个解决问题的解决方案。

如果你创建一个泛型类,将供应商的传递与参数的传递分开,你就不会给编译器选择交集类型的机会:

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 执行潜在有用的操作,例如缓存Supplier.get() 的结果。

【讨论】:

  • 好主意!碰巧的是,这种方法非常适合我的实际代码。谢谢!
  • 我选择了这个答案,因为它最终会在我的项目中带来更好的代码。其他答案也值得称赞,但很遗憾,我只能接受一个。
【解决方案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<?>

【讨论】:

  • 剧情变厚了!我不知道 Java 类型系统能够推断出公共/交叉类型。您是否碰巧知道如何强制编译器变得不那么聪明和更严格?
  • @PatrickPeer 你可以添加一个真正没用的见证compareValues(Supplier&lt;T&gt; supplier, T value, Class&lt;T&gt; clazz)
  • @Eugene 我知道这种方法,但是对于我尝试创建的 API,它是一个人为参数,这使得使用我的代码比它需要的更笨重。好主意!
【解决方案3】:

这里T 可以是任何东西。它是一种类型的同义词,但基本上可以是任何类型。

因此,当您拥有compareValues(Supplier&lt;T&gt; 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 分配给它,但在更奇怪的情况下不能相反。字符串供应商是&lt;T&gt; 供应商,但字符串消费者不是&lt;T&gt; 消费者。

看这两种方法:

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

【讨论】:

  • 不幸的是,我仍然看不到 compareValues 中的 T 可以是任何类型(毕竟我在调用中提供了特定的供应商)。另外,尽管我喜欢您的“人性化”解释,但这次我正在寻找更深入的技术答案;)
  • 好吧,我认为这已经足够技术了。您不能缩小预期类型。当您期望 Consumer(基本上是消费者可以使用任何类型 的消费者)时,如果您提供 Consumer,您基本上会缩小类型,并且如果在方法中有使用 Consumer 的代码,它可能不适用于消费者
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-19
  • 2020-07-13
  • 2018-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-20
相关资源
最近更新 更多