运算符'+'不能应用于'T','T'用于有界泛型
下面的代码片段抛出错误,如头部所示,我没有弄清楚为什么它不起作用,因为T是数字类型,我希望运营商'+'罚款。运算符'+'不能应用于'T','T'用于有界泛型
class MathOperationV1<T extends Number> {
public T add(T a, T b) {
return a + b; // error: Operator '+' cannot be applied to 'T', 'T'
}
}
如果有人能提供一些线索,thx!
实现这种通用算术思想存在一个基本问题。这个问题并不在于如何在数学上说这应该起作用,而在于它应该如何编译为Java编译器的字节码。
在你的榜样,你有这样的:
class MathOperationV1<T extends Number> {
public T add(T a, T b) {
return a + b; // error: Operator '+' cannot be applied to 'T', 'T'
}
}
留装箱和拆箱不谈,问题是,编译器不知道应该如何编译+
运营商。编译器应使用+
的多个重载版本中的哪一个?对于不同的基元类型,JVM具有不同的算术运算符(即操作码);因此整数的和运算符与双精度运算符完全不同(例如,请参阅iadd与dadd)。如果仔细考虑它,那完全有意义,因为毕竟整数运算和浮点运算是完全不同。不同的类型也有不同的尺寸等(参见例如ladd)。另外请考虑BigInteger
和BigDecimal
,它们也扩展为Number
,但那些不支持自动装箱,因此没有操作码直接处理它们。可能有其他几十个其他类似于其他库中的实现的其他Number
实现。编译器如何知道如何处理它们?
因此,当编译器推断T
是Number
时,这不足以确定哪些操作码对操作有效(即装箱,拆箱和算术)。
后来你建议更改代码位:
class MathOperationV1<T extends Integer> {
public T add(T a, T b) {
return a + b;
}
}
而且现在+
运营商可以用一个整数总和码来实现,但总和的结果将是一个Integer
,而不是一个T
,但仍然会使此代码无效,因为从编译器的角度来看,T
可能是Integer
以外的其他内容。
我相信没有办法让代码具有足够的通用性,以至于您可以忘记这些底层实现细节。
- 编辑 -
为了回答您的评论部分的问题考虑基于以上MathOperationV1<T extends Integer>
最后一个定义以下情形。
你是正确的,当你说,编译器会做类型擦除的类定义,并且如果考虑到这种类型擦除,就好像使用它似乎是
class MathOperationV1 {
public Integer add(Integer a, Integer b) {
return a + b;
}
}
它会被编译子类Integer
应该在这里工作,但这不是真的,因为它会使类型系统不健全。让我试着证明这一点。
编译器不仅可以担心的声明网站,它也有考虑在多个调用点会发生什么,可能使用不同类型的论据T
。
例如,想象一下(为了我的论点),有一个Integer
的子类,我们将其称为SmallInt
。假设我们上面的代码编译好了(这实际上是你的问题:为什么它不能编译?)。
如果我们做了以下事情,会发生什么?
MathOperationV1<SmallInt> op = new MathOperationV1<>();
SmallInt res = op.add(SmallInt.of(1), SmallInt.of(2));
正如你可以看到op.add()
方法的结果预计将是一个SmallInt
,而不是一个Integer
。然而,我们上面的a + b
的结果,从我们已删除的类定义中,总是会返回Integer
而不是SmallInt
(因为+使用JVM整数算术操作码),因此这个结果是不合适的,对吗?
您现在可能会想知道,但如果MathOperationV1
的类型删除总是返回Integer
,那么在呼叫站点的世界中它可能会有什么其他的东西(如SmallInt
)呢?
好,编译器在这里增加了一些额外的施法者add
结果强制转换为SmallInt
,但只是因为它已经保证了操作无法返回任何东西比预期的类型等(这就是为什么你看到编译器错误)。
换句话说,您的通话网站看起来像这样擦除之后:
MathOperationV1 op = new MathOperationV1<>(); //with Integer type erasure
SmallInt res = (SmallInt) op.add(SmallInt.of(1), SmallInt.of(2));
但是,如果你能保证add
回报总是SmallInt
(我们不能因操作问题描述了只会工作在我原来的答案)。
所以,你可以看到,你的类型擦除只是确保在于,根据分型的规则,你可以返回任何延伸的Integer
,但一旦您的通话网站声明了T
一种说法,你应该在原始代码中出现T
以保持类型系统声音时始终假设为相同类型。
您实际上可以通过使用Java反编译器(您的JDK bin目录中名为javap的工具)来证明这些观点。我可以提供更好的例子,如果你认为你需要它们,但你会自己尝试一下,看看底下发生了什么:-)
对于第二个代码片段,从我所了解的情况来看,编译器会在这里进行类型擦除,它将用有界类型替换类型参数T,在我的情况下是Integer,并且它变成类'MathOperationV1 {public Integer add(Integer a,整数b){a}返回a + b; } }'我在这里误解了什么? –
@PhoebeLi这是一个很难回答的简短评论,所以我通过更多细节丰富了我的答案。我希望我能够解释我自己,但除此之外,可以随时提出更多问题,我会尽我所能澄清任何论点。 –
很多很多人在这里感谢!我希望我能给你10个更多的赞扬! :)这对我来说更加清晰了,我想我会使用反编译器做一些实验,正如您在下一步中所建议的那样:-) –
自动(非)拳击只适用于可以转换为其原始等值的类型。加法仅针对数字基元类型和字符串进行定义。即:int,long,short,char,double,float,byte。数字没有原始的等价物,所以不能拆箱,这就是为什么你不能添加它们。
+
未定义为Number
。你可以通过编写(没有泛型)看到这个:
Number a = 1;
Number b = 2;
System.out.println(a + b);
这根本就不会编译。
你不能这样做除了一般直接:你需要一个BiFunction
,一个BinaryOperator
,或类似的,这是能够运行适用于输入:再次
class MathOperationV1<T extends Number> {
private final BinaryOperator<T> combiner;
// Initialize combiner in constructor.
public T add(T a, T b) {
return combiner.apply(a, b);
}
}
但是,你不妨直接使用BinaryOperator<T>
:MathOperationV1
不会在标准类之上添加任何内容(实际上,它提供的更少)。
你能提供一个你的组合lambda的实现吗?我认为你只是在解决问题的时候解决问题。 –
当然:'(a,b) - > a + b'。当你在上下文中使用它时,例如'BinaryOperator
请将相关的语言标签添加到您的问题。 –
这是Java,不是吗? – lilezek
我刚更新了标题,提醒! –