将 Java 的 Optional.or() 与子类一起使用时,lambda 表达式中的错误 return 类型
Bad return type in lambda expression when using Java's Optional.or() with subclasses
我正在尝试使用 Optional.or
获取子类 A 的对象,或者如果为空,则获取子类 B 的对象:
interface Node {}
class InnerNode implements Node {}
class LeafNode implements Node {}
Optional<InnerNode> createInnerNode() {}
Optional<LeafNode> createLeafNode() {}
Optional<Node> node = createInnerNode().or(() -> createLeafNode());
执行此操作时,出现以下编译器错误:
Bad return type in lambda expression: Optional<LeafNode> cannot be converted to Optional<? extends InnerNode>
如果我改为使用通配符明确告诉编译器 Optionals 包含一个从 Node
:
扩展的对象
Optional<? extends Node> optionalInner = createInnerNode();
Optional<? extends Node> optionalLeaf = createLeafNode();
Optional<Node> node = optionalInner.or(() -> optionalLeaf);
我收到以下编译器错误:
Bad return type in lambda expression: Optional<capture of ? extends Node> cannot be converted to Optional<? extends capture of ? extends Node>
我发现实现此功能的唯一方法是在前面使用 Optional.empty
:
Optional<Node> node = Optional.<Node>empty() // requires generic argument
.or(() -> createInnerNode())
.or(() -> createLeafNode());
但对我来说,这读起来很混乱。是否有可能以某种方式指示编译器允许语句 optionalInner.or(() -> optionalLeaf)
?或者还有其他替代方法可以使这项工作有效吗?
这一定是一个令人困惑的错误。让我先解释一下潜在的问题,以便您真正理解 javac 抱怨的原因,然后我将介绍如何解决它。
根本问题是缺少use-site方差。
你得到的错误对于 Optional 来说没有意义,但是当我们使用产生 和 消耗类型化数据的类型时它是完全有意义的(不像 Optional,因为一个实例,只产生数据,例如当你调用 .get()
- Optional 上没有 .set()
方法;它是不可变的)。
让我们看看列表。想象一下你可以这样做:
Integer i = 5;
Number n = i; // This is legal java.
List<Integer> ints = new ArrayList<Integer>();
List<Number> numbers = ints; // So this should be too... right?
第 4 行不合法。因为泛型默认是不变的(超类型和子类型都不行;在不变类型系统中,如果需要一个数字,那么只有一个数字会做) - 这是正确的。毕竟,如果上面的代码 WAS 合法,那么一切都会崩溃:
Double d = 5.0;
numbers.add(d); // Fine - must be. doubles are numbers!
Integer i = ints.get(0); // um.. wait. uhoh.
因为 numbers
和 ints
都只是引用完全相同的列表,如果我向 numbers
添加一个 double,这意味着我也向 ints
添加了一个,和 - 繁荣。坏了。
现在你知道为什么泛型是不变的了。
这也解释了一些进一步的编译器错误:
List<Integer> ints = new ArrayList<Integer>();
List<Number> numbers = ints; // nope, won't compile.
List<? extends Number> numbers = ints; // but this will!
numbers.add(anything-except-literally-null); // this wont!
使用 ? extends
你告诉 java 你 想要 协方差(协方差 = X 的子类型与 X 本身一样好。Java 的 non-generics 类型是协变的)。但是,泛型中的协变自动意味着所有 方法 'send' 将类型数据 禁用。你不能 add
任何东西(除了字面上的 .add(null)
,因为 null
是所有类型)到 List<?>
或 List<? extends>
。如果您考虑一下,这是有道理的:
A List<? extends Number>
可以是 List<Integer>
或 List<Double>
。那么如何向这个列表中添加任何内容呢?没有什么是 both a Double and an Integer(好吧,除了文字 null
),所以你不可能传递任何东西add
必然是 'safe'。因此,为什么编译器 总是 告诉你这是一个打字违规。
这也意味着这也行不通:
List<? extends Number> numbers = ...;
List<Number> numbers2 = numbers; // nope, this won't compile
返回您的代码
现在,对于可选的,none 这似乎是相关的。 Optional 在其类型参数 中可以完全协变,因为 Optional 不 'consume' 数据 - none 它的方法采用 'E'。但是编译器不是某种能够解决这个问题并让你 'break' typeargs 的变化规则的超级大脑。不是这样 - 可选需要坚持相同的规则列表。因此,如果无法将 List<? extends Number>
的实例分配给 List<Number>
类型的变量,则同样适用于 Optional:
类型 Optional<? extends Node>
的实例不能分配给类型 Optional<Node>
的变量
这就解释了你的错误(对于解决你问题中所述问题的两次尝试)。
即使特别针对可选,这些错误也没有多大意义。现在你知道为什么要得到它们了。
最好的解决方法是不要在这里使用 optional;这并不是它的真正含义,也不是很 java-like(见下文)。但是遥远的第二个最佳选择是修复这些东西。根本的解决办法是所有 接受 可选的地方应该总是被声明为 ? extends
。因此,您试图将其分配给类型为 Optional<Node>
的字段,但该字段为 'wrong'。该字段的类型应为 Optional<? extends NOde>
。然后解决所有问题:
Optional<? extends Node> node = createInnerNode().or(() -> createLeafNode());
工作正常(这是您的第一个片段,但目标的 'type' 已修复)。
啊。但是,我无法更改 API
嗯,那么API就错了。它发生了 - 有时您需要与损坏的代码进行交互。但别搞错了,那就是破码。您对任何其他损坏的代码执行相同的操作:封装并变通。
你不想与破碎的 API 互动。因此,创建一个 'paper over' 错误的包装器类型或辅助方法。在该代码中('bridging' 解决您无法更改的错误代码的代码),您接受需要编写生成警告的不稳定代码。
您可以'fix things'如下:
public Optional<Node> fixIt(Optional<? extends Node> node) {
Optional raw = node;
return raw;
}
之所以可行,是因为泛型最终只是 javac
的想象——JVM 根本不知道泛型是什么。所以我们只需要'fake out'javac停止拒绝编译代码即可。以上将生成警告,并且此类 hackery 中的任何错误都会导致 ClassCastExceptions 出现在对它们进行零转换的行上 - 非常令人困惑。但是,如果您知道自己在做什么,就可以正常工作。您可以使用 @SuppressWarnings
注释消除警告。
或者..不要使用可选的
Java 已 null
融入许多地方。不仅在其核心模型中(未初始化的字段以 null
值开头。non-primitives 的数组用空值初始化),而且在许多最重要的图书馆。最明显和简单的例子是 java.util.Map
:它有一个 get()
方法,return 是一个 V
。不是 Optional<V>
。在 Optional 是正确答案的世界里,get()
当然应该 return Optional<V>
。但它不会,而且永远不会 - java 不会彻底破坏向后兼容性。
因此,'optional everywhere!' 的假设世界糟透了 - 30 年的代码和历史需要先扔进垃圾桶,而社区不太可能这样做。如果您不喜欢 java 中的 null 处理(并且有很多很好的理由这样做!),那么需要一个解决方案来让现有代码 backwards-compatibly 进行调整,并且 Optional 无法交付。 (不过,无效注释可能可以!好多了;或者只是写得更好 APIs。地图有 .getOrDefault
可用于确保您的地图查询调用永远不会 return null
首先。一旦你开始编写你的代码永远不会产生 null
值,除非你真的想要任何 deref 抛出的尝试(即很少),那么 null 就不再是一个问题。这些对各种 java 核心库类型的更现代的插件使得编写此类代码变得更加容易。并且向接口添加方法是向后兼容的,因此,正如 java.util.Map 所示,现有的东西可以 backwards-compatibly 添加这些)。
与 Optional 相关的最佳做法是仅将其用于最初引入它的原因:作为流 API 终端的 return 值。像 intList.stream().max()
return 和 OptionalInt
因为空列表的最大值是多少?它足够好。如果您编写自己的收集器或流终止操作,请使用 Optional
。如果这不是您正在做的,请不要使用它。
您提供的解决方法是一个非常简单的答案,但是如果您想避免占位符 Optional,您可以使用 map 将 Optional<InnerNode>
简单地转换为 Optional<Node>
。
createInnerNode.<Node>map(Function.identity()).or(() -> createLeafNode());
一般来说,这也是您在概念上将 List<Dog>
转换为 List<Animal>
或将任何通用集合转换为 java 中不同通用集合的方式。这有点烦人,但通配符并不能解决问题,因为您希望转换后的集合中的两个 read/write。
or 的签名应该告诉您为什么您的解决方法有效
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
注意它是如何使用 ?在供应商中扩展 T。
由于第一个空的 Optional 的 T 是 Node,or 函数可以接受 returns
的供应商
Optional<? extends Node>
并且由于内部节点和叶节点都是 Optional<? extends Node>
,这有效。
所以问题是你在某些时候需要 Optional<Node>
。
我不认为你可以避免做某事,因为 Optional<InnerNode>
从不输入匹配项 Optional<Node>
出于其他答案中列出的原因(以及堆栈溢出的其他问题)。
阅读其他答案并尝试
Optional<? extends Node> node = createInnerNode();
表示T是一个
? extends Node
这使得 or
似乎想要双重嵌套捕获?我无法停止
Bad return type in lambda expression: Optional<capture of ? extends Node> cannot be converted to Optional<? extends capture of ? extends Node>
基本上尝试使用以下代码进行任何转换时
Optional<? extends Node> node = Node.createInnerNode();
node.or(() -> (Optional<? extends Node>) null);
我正在尝试使用 Optional.or
获取子类 A 的对象,或者如果为空,则获取子类 B 的对象:
interface Node {}
class InnerNode implements Node {}
class LeafNode implements Node {}
Optional<InnerNode> createInnerNode() {}
Optional<LeafNode> createLeafNode() {}
Optional<Node> node = createInnerNode().or(() -> createLeafNode());
执行此操作时,出现以下编译器错误:
Bad return type in lambda expression: Optional<LeafNode> cannot be converted to Optional<? extends InnerNode>
如果我改为使用通配符明确告诉编译器 Optionals 包含一个从 Node
:
Optional<? extends Node> optionalInner = createInnerNode();
Optional<? extends Node> optionalLeaf = createLeafNode();
Optional<Node> node = optionalInner.or(() -> optionalLeaf);
我收到以下编译器错误:
Bad return type in lambda expression: Optional<capture of ? extends Node> cannot be converted to Optional<? extends capture of ? extends Node>
我发现实现此功能的唯一方法是在前面使用 Optional.empty
:
Optional<Node> node = Optional.<Node>empty() // requires generic argument
.or(() -> createInnerNode())
.or(() -> createLeafNode());
但对我来说,这读起来很混乱。是否有可能以某种方式指示编译器允许语句 optionalInner.or(() -> optionalLeaf)
?或者还有其他替代方法可以使这项工作有效吗?
这一定是一个令人困惑的错误。让我先解释一下潜在的问题,以便您真正理解 javac 抱怨的原因,然后我将介绍如何解决它。
根本问题是缺少use-site方差。
你得到的错误对于 Optional 来说没有意义,但是当我们使用产生 和 消耗类型化数据的类型时它是完全有意义的(不像 Optional,因为一个实例,只产生数据,例如当你调用 .get()
- Optional 上没有 .set()
方法;它是不可变的)。
让我们看看列表。想象一下你可以这样做:
Integer i = 5;
Number n = i; // This is legal java.
List<Integer> ints = new ArrayList<Integer>();
List<Number> numbers = ints; // So this should be too... right?
第 4 行不合法。因为泛型默认是不变的(超类型和子类型都不行;在不变类型系统中,如果需要一个数字,那么只有一个数字会做) - 这是正确的。毕竟,如果上面的代码 WAS 合法,那么一切都会崩溃:
Double d = 5.0;
numbers.add(d); // Fine - must be. doubles are numbers!
Integer i = ints.get(0); // um.. wait. uhoh.
因为 numbers
和 ints
都只是引用完全相同的列表,如果我向 numbers
添加一个 double,这意味着我也向 ints
添加了一个,和 - 繁荣。坏了。
现在你知道为什么泛型是不变的了。
这也解释了一些进一步的编译器错误:
List<Integer> ints = new ArrayList<Integer>();
List<Number> numbers = ints; // nope, won't compile.
List<? extends Number> numbers = ints; // but this will!
numbers.add(anything-except-literally-null); // this wont!
使用 ? extends
你告诉 java 你 想要 协方差(协方差 = X 的子类型与 X 本身一样好。Java 的 non-generics 类型是协变的)。但是,泛型中的协变自动意味着所有 方法 'send' 将类型数据 禁用。你不能 add
任何东西(除了字面上的 .add(null)
,因为 null
是所有类型)到 List<?>
或 List<? extends>
。如果您考虑一下,这是有道理的:
A List<? extends Number>
可以是 List<Integer>
或 List<Double>
。那么如何向这个列表中添加任何内容呢?没有什么是 both a Double and an Integer(好吧,除了文字 null
),所以你不可能传递任何东西add
必然是 'safe'。因此,为什么编译器 总是 告诉你这是一个打字违规。
这也意味着这也行不通:
List<? extends Number> numbers = ...;
List<Number> numbers2 = numbers; // nope, this won't compile
返回您的代码
现在,对于可选的,none 这似乎是相关的。 Optional 在其类型参数 中可以完全协变,因为 Optional 不 'consume' 数据 - none 它的方法采用 'E'。但是编译器不是某种能够解决这个问题并让你 'break' typeargs 的变化规则的超级大脑。不是这样 - 可选需要坚持相同的规则列表。因此,如果无法将 List<? extends Number>
的实例分配给 List<Number>
类型的变量,则同样适用于 Optional:
类型 Optional<? extends Node>
的实例不能分配给类型 Optional<Node>
这就解释了你的错误(对于解决你问题中所述问题的两次尝试)。
即使特别针对可选,这些错误也没有多大意义。现在你知道为什么要得到它们了。
最好的解决方法是不要在这里使用 optional;这并不是它的真正含义,也不是很 java-like(见下文)。但是遥远的第二个最佳选择是修复这些东西。根本的解决办法是所有 接受 可选的地方应该总是被声明为 ? extends
。因此,您试图将其分配给类型为 Optional<Node>
的字段,但该字段为 'wrong'。该字段的类型应为 Optional<? extends NOde>
。然后解决所有问题:
Optional<? extends Node> node = createInnerNode().or(() -> createLeafNode());
工作正常(这是您的第一个片段,但目标的 'type' 已修复)。
啊。但是,我无法更改 API
嗯,那么API就错了。它发生了 - 有时您需要与损坏的代码进行交互。但别搞错了,那就是破码。您对任何其他损坏的代码执行相同的操作:封装并变通。
你不想与破碎的 API 互动。因此,创建一个 'paper over' 错误的包装器类型或辅助方法。在该代码中('bridging' 解决您无法更改的错误代码的代码),您接受需要编写生成警告的不稳定代码。
您可以'fix things'如下:
public Optional<Node> fixIt(Optional<? extends Node> node) {
Optional raw = node;
return raw;
}
之所以可行,是因为泛型最终只是 javac
的想象——JVM 根本不知道泛型是什么。所以我们只需要'fake out'javac停止拒绝编译代码即可。以上将生成警告,并且此类 hackery 中的任何错误都会导致 ClassCastExceptions 出现在对它们进行零转换的行上 - 非常令人困惑。但是,如果您知道自己在做什么,就可以正常工作。您可以使用 @SuppressWarnings
注释消除警告。
或者..不要使用可选的
Java 已 null
融入许多地方。不仅在其核心模型中(未初始化的字段以 null
值开头。non-primitives 的数组用空值初始化),而且在许多最重要的图书馆。最明显和简单的例子是 java.util.Map
:它有一个 get()
方法,return 是一个 V
。不是 Optional<V>
。在 Optional 是正确答案的世界里,get()
当然应该 return Optional<V>
。但它不会,而且永远不会 - java 不会彻底破坏向后兼容性。
因此,'optional everywhere!' 的假设世界糟透了 - 30 年的代码和历史需要先扔进垃圾桶,而社区不太可能这样做。如果您不喜欢 java 中的 null 处理(并且有很多很好的理由这样做!),那么需要一个解决方案来让现有代码 backwards-compatibly 进行调整,并且 Optional 无法交付。 (不过,无效注释可能可以!好多了;或者只是写得更好 APIs。地图有 .getOrDefault
可用于确保您的地图查询调用永远不会 return null
首先。一旦你开始编写你的代码永远不会产生 null
值,除非你真的想要任何 deref 抛出的尝试(即很少),那么 null 就不再是一个问题。这些对各种 java 核心库类型的更现代的插件使得编写此类代码变得更加容易。并且向接口添加方法是向后兼容的,因此,正如 java.util.Map 所示,现有的东西可以 backwards-compatibly 添加这些)。
与 Optional 相关的最佳做法是仅将其用于最初引入它的原因:作为流 API 终端的 return 值。像 intList.stream().max()
return 和 OptionalInt
因为空列表的最大值是多少?它足够好。如果您编写自己的收集器或流终止操作,请使用 Optional
。如果这不是您正在做的,请不要使用它。
您提供的解决方法是一个非常简单的答案,但是如果您想避免占位符 Optional,您可以使用 map 将 Optional<InnerNode>
简单地转换为 Optional<Node>
。
createInnerNode.<Node>map(Function.identity()).or(() -> createLeafNode());
一般来说,这也是您在概念上将 List<Dog>
转换为 List<Animal>
或将任何通用集合转换为 java 中不同通用集合的方式。这有点烦人,但通配符并不能解决问题,因为您希望转换后的集合中的两个 read/write。
or 的签名应该告诉您为什么您的解决方法有效
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
注意它是如何使用 ?在供应商中扩展 T。 由于第一个空的 Optional 的 T 是 Node,or 函数可以接受 returns
的供应商Optional<? extends Node>
并且由于内部节点和叶节点都是 Optional<? extends Node>
,这有效。
所以问题是你在某些时候需要 Optional<Node>
。
我不认为你可以避免做某事,因为 Optional<InnerNode>
从不输入匹配项 Optional<Node>
出于其他答案中列出的原因(以及堆栈溢出的其他问题)。
阅读其他答案并尝试
Optional<? extends Node> node = createInnerNode();
表示T是一个
? extends Node
这使得 or
似乎想要双重嵌套捕获?我无法停止
Bad return type in lambda expression: Optional<capture of ? extends Node> cannot be converted to Optional<? extends capture of ? extends Node>
基本上尝试使用以下代码进行任何转换时
Optional<? extends Node> node = Node.createInnerNode();
node.or(() -> (Optional<? extends Node>) null);