【问题标题】:Java 8 currying Function, unable to determine int[] return-typeJava 8 currying 函数,无法确定 int[] 返回类型
【发布时间】:2018-06-21 07:34:00
【问题描述】:

假设我们有一个这样的 lambda 函数:

Function<ArrayList<Integer>, int[]> func1 = a->new int[2];

它的作用并不重要。重要的是:输入是ArrayList&lt;Integer&gt;,输出是int[]

使用一些基本的测试编译和运行没有问题:

int[] func1Result1 = func1.apply(new ArrayList<Integer>()); // Non-currying works with <Integer>
System.out.println(func1Result1);
System.out.println(func1Result1.getClass());
System.out.println(Arrays.toString(func1Result1));
System.out.println();

int[] func1Result2 = func1.apply(new ArrayList<>());        // Non-currying works with <>
System.out.println(func1Result2);
System.out.println(func1Result2.getClass());
System.out.println(Arrays.toString(func1Result2));
System.out.println();

int[] func1Result3 = func1.apply(new ArrayList());          // Non-currying works without <>
System.out.println(func1Result3);
System.out.println(func1Result3.getClass());
System.out.println(Arrays.toString(func1Result3));
System.out.println();

Try it online.


现在假设我们有一个像这样的柯里化 lambda 函数:

Function<Object, Function<Object, int[]>> func2 = a->b->new int[2];

同样,它的作用并不重要。这次柯里化函数接受了两个Object参数,仍然输出一个int[]

使用相同的基本测试编译并再次运行没有问题:

int[] func2Result1 = func2.apply(new ArrayList<Integer>()).apply(null); // Currying works with <Integer>
System.out.println(func2Result1);
System.out.println(func2Result1.getClass());
System.out.println(Arrays.toString(func2Result1));
System.out.println();

int[] func2Result2 = func2.apply(new ArrayList<>()).apply(null);        // Currying works with <>
System.out.println(func2Result2);
System.out.println(func2Result2.getClass());
System.out.println(Arrays.toString(func2Result2));
System.out.println();

int[] func2Result3 = func2.apply(new ArrayList()).apply(null);          // Currying works without <>
System.out.println(func2Result3);
System.out.println(func2Result3.getClass());
System.out.println(Arrays.toString(func2Result3));
System.out.println();

Try it online.


现在是第三个变体,这是我的困惑所在和我的问题所在。假设我们有一个像这样的柯里化 lambda 函数:

Function<ArrayList<Integer>, Function<Object, int[]>> func3 = a->b->new int[2];

这次参数是ArrayList&lt;Integer&gt;Object,返回类型还是int[]

这次使用同样的基本测试编译不通过,报错:

int[] func3Result1 = func3.apply(new ArrayList<Integer>()).apply(null); // Currying works with <Integer>
System.out.println(func3Result1);
System.out.println(func3Result1.getClass());
System.out.println(Arrays.toString(func3Result1));
System.out.println();

int[] func3Result2 = func3.apply(new ArrayList<>()).apply(null);        // Currying works with <>
System.out.println(func3Result2);
System.out.println(func3Result2.getClass());
System.out.println(Arrays.toString(func3Result2));
System.out.println();

int[] func3Result3 = func3.apply(new ArrayList()).apply(null);          // Currying doesn't work without <>
System.out.println(func3Result3);
System.out.println(func3Result3.getClass());
System.out.println(Arrays.toString(func3Result3));
System.out.println();

错误是:

Main.java:23:错误:不兼容的类型:对象无法转换为 int[]
int[] func3Result3 = func3.apply(new ArrayList()).apply(null); // 如果没有
,Currying 不起作用 ^

Try it online.

为什么它认为返回类型是Object 而不是int[]?内部函数的返回类型清楚地表明返回类型是int[]。如果两个 lambda 的参数都是 Objectfunc2 测试),或者集合附有菱形(func3Result1func3Result2),它确实可以正常工作。但是由于某种原因,当从集合中移除钻石时 (func3Result3) 会感到困惑,即使 int[] 返回类型与此 ArrayList&lt;Integer&gt; 输入无关。

编辑:刚刚在我的 jdk 版本 1.8.0_72 上对其进行了本地测试,它确实可以编译和工作。有人可以确认它确实不适用于 jdk 1.9 或 1.10(或最新版本的 jdk 1.8 之一)吗?也许问题是 TIO 在这里做了一些奇怪的事情而不是 JDK 本身.. :S

【问题讨论】:

  • @Holger 感谢您的阅读。我确实知道你永远不应该使用它,尽管现在我也更好地理解了它背后的原因。我仍然有点困惑Java如何以某种方式将ArrayList输入类型与currying lambda中的int[]返回类型联系起来,即使它们彼此无关。我很好奇是否有人可以在这些特定情况下在这些 currying lambdas 中提供编译器中发生的实际 JVM 代码。
  • 在 Java 8 之前,您可以简单地说,使用原始类型会导致表达式根本不使用泛型,因此即使从完全不相关的类型变量中也可能无法推断类型。众所周知的例子,List list; … String[] array = list.toArray(new String[0]); 由于使用了原始类型List 而不起作用,即使toArray 的类型参数与List 的类型参数无关。从 Java 8 及其扩展的目标类型推断开始,规则变得更加复杂,看到编译器之间的差异(错误、修复的错误、规范清理)也就不足为奇了。
  • 从 Java9 及以上,他们已经严格了泛型参数的推断,这可能是为什么

标签: lambda types java-8 currying java-10


【解决方案1】:

简而言之。当您使用原始类型(@Holger 在 cmets 中提到的you should not use them)时,您会删除任何通用信息。所以这行:

int[] func3Result3 = func3.apply(new ArrayList()).apply(null);

可以拆分为多行以进行说明:

Function temp = func3.apply(new ArrayList());

这里只返回Function,因为使用原始类型new ArrayList()擦除了通用信息。

和原始类型函数有点类似,但不等于Function&lt;Object, Object&gt;

这使得现在很容易看到,在将null 应用于该函数时,您并不确切知道返回的是什么(除了它是一个Object),这就是您得到它的原因错误:

int[] func3Result3 = temp.apply(null);

由于类型擦除,编译器只是不知道类型。

所以这句话的寓意是:

永远不要使用原始类型。它们只是一种向后兼容功能,不应在现代生产代码中使用。

【讨论】:

  • 不错的答案,+1!至于主要部分:“这里只返回Function,因为使用原始类型new ArrayList()删除了通用信息。”我仍然有点困惑为什么。为什么当输入为new ArrayList&lt;Integer&gt;()(或new ArrayList&lt;&gt;())时返回Function&lt;Object, int[]&gt;,而当输入为原始类型new ArrayList时只返回Function?我是否正确地说,当原始类型输入用于强类型对象(在这种情况下为Function&lt;..., ...&gt;)时,输出也总是变成原始类型?
  • @KevinCruijssen 这正是正在发生的事情。一旦使用原始类型。使用该原始类型的所有其他内容也将丢失类型信息。
  • 这在包含其他泛型类型对象的泛型类中最为明显。例如。 class Box&lt;T&gt;{List&lt;String&gt; strings; List&lt;String&gt; getStrings(){return strings}} 如果您没有为Box 指定泛型类型参数,那么getStrings() 将只返回一个List,尽管它似乎不会受到影响。它仍然是
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-18
  • 2016-10-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多