加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

java – 运算符”不能应用于’T’,’T’表示有界泛型类型

发布时间:2020-12-15 04:39:39 所属栏目:Java 来源:网络整理
导读:参见英文答案 Generic type extending Number,calculations????????????????????????????????????2个 下面的代码片段告诉我错误,如标题中所示,我没有弄清楚为什么它不起作用,因为T的类型为Number,我希望运算符”没问题. class MathOperationV1T extends Numb
参见英文答案 > Generic type extending Number,calculations????????????????????????????????????2个
下面的代码片段告诉我错误,如标题中所示,我没有弄清楚为什么它不起作用,因为T的类型为Number,我希望运算符”没问题.

class MathOperationV1<T extends Number> {
        public T add(T a,T b) {
            return a + b; // error: Operator '+' cannot be applied to 'T','T' 
        }
    }

如果有人能提供一些线索,那将不胜感激,谢谢!

解决方法

这种泛型算法的实现存在一个基本问题.问题不在于你在数学上如何说这应该起作用的推理,而在于它如何被Java编译器编译成字节码的含义.

在你的例子中你有这个:

class MathOperationV1<T extends Number> {
        public T add(T a,'T' 
        }
}

抛开装箱和拆箱,问题是编译器不知道它应该如何编译你的运算符.编译器应该使用多个重载版本中的哪一个? JVM对于不同的基元类型具有不同的算术运算符(即操作码);因此整数的和运算符是一个完全不同的操作码,而不是双精度的操作码(例如,参见iadd vs dadd)如果你考虑它,这完全有意义,因为毕竟整数运算和浮点运算是完全不同.不同类型也有不同的尺寸等(参见例如ladd).还要考虑扩展Number的BigInteger和BigDecimal,但那些不支持自动装箱,因此没有操作码可以直接处理它们.可能还有许多其他数字库实现,就像其他库中那样.编译器怎么知道如何处理它们?

因此,当编译器推断出T是一个数字时,这不足以确定哪些操作码对操作有效(即装箱,拆箱和算术).

稍后您建议将代码更改为:

class MathOperationV1<T extends Integer> {
        public T add(T a,T b) {
            return a + b;
        }
    }

现在运算符可以用整数和操作码实现,但总和的结果将是一个整数,而不是一个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));

但是,只有当你能确保添加返回总是SmallInt(我们不能因为我原来的答案中描述的操作符问题)时,这才有效.

所以,正如你所看到的,你的类型擦除只是确保,根据子类型的规则,你可以返回任何扩展Integer的东西,但是一旦你的调用站点为T声明了一个类型参数,你应该总是假设相同的类型,无论T出现在原始代码中,以保持类型系统的声音.

实际上,您可以使用Java反编译器(JDK bin目录中名为javap的工具)来证明这些要点.如果你认为你需要它们,我可以提供更好的例子,但你最好自己尝试一下,看看幕后发生了什么:-)

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读