运算符'+'不能应用于'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!

+1

请将相关的语言标签添加到您的问题。 –

+0

这是Java,不是吗? – lilezek

+0

我刚更新了标题,提醒! –

实现这种通用算术思想存在一个基本问题。这个问题并不在于如何在数学上说这应该起作用,而在于它应该如何编译为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具有不同的算术运算符(即操作码);因此整数的和运算符与双精度运算符完全不同(例如,请参阅iadddadd)。如果仔细考虑它,那完全有意义,因为毕竟整数运算和浮点运算是完全不同。不同的类型也有不同的尺寸等(参见例如ladd)。另外请考虑BigIntegerBigDecimal,它们也扩展为Number,但那些不支持自动装箱,因此没有操作码直接处理它们。可能有其他几十个其他类似于其他库中的实现的其他Number实现。编译器如何知道如何处理它们?

因此,当编译器推断TNumber时,这不足以确定哪些操作码对操作有效(即装箱,拆箱和算术)。

后来你建议更改代码位:

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的工具)来证明这些观点。我可以提供更好的例子,如果你认为你需要它们,但你会自己尝试一下,看看底下发生了什么:-)

+0

对于第二个代码片段,从我所了解的情况来看,编译器会在这里进行类型擦除,它将用有界类型替换类型参数T,在我的情况下是Integer,并且它变成类'MathOperationV1 {public Integer add(Integer a,整数b){a}返回a + b; } }'我在这里误解了什么? –

+0

@PhoebeLi这是一个很难回答的简短评论,所以我通过更多细节丰富了我的答案。我希望我能够解释我自己,但除此之外,可以随时提出更多问题,我会尽我所能澄清任何论点。 –

+1

很多很多人在这里感谢!我希望我能给你10个更多的赞扬! :)这对我来说更加清晰了,我想我会使用反编译器做一些实验,正如您在下一步中所建议的那样:-) –

自动(非)拳击只适用于可以转换为其原始等值的类型。加法仅针对数字基元类型和字符串进行定义。即:int,long,short,char,double,float,byte。数字没有原始的等价物,所以不能拆箱,这就是为什么你不能添加它们。

+0

根据您的回答,我将代码更改为,但它仍然不起作用,我不太明白。 –

+0

首先,数字类是最终的,因此您将无法创建任何子类。其次,类型擦除意味着通用类型只在编译时被检查,在运行时丢失信息。因此,JVM将无法阻止您将无法拆箱的“对象”传递给原始类型。换句话说,你不能做你想做的事情@see https://docs.oracle.com/javase/tutorial/java/generics/erasure.html – xburgos

+0

最好你可以创建一个MathOperation接口,然后让类实现每个数字类型的接口 – xburgos

+未定义为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不会在标准类之上添加任何内容(实际上,它提供的更少)。

+0

你能提供一个你的组合lambda的实现吗?我认为你只是在解决问题的时候解决问题。 –

+0

当然:'(a,b) - > a + b'。当你在上下文中使用它时,例如'BinaryOperator plusInt =(a,b) - > a + b;',则推断出整数性。 –