Operator '+' cannot be applied to 'T', 'T' for restricted generic type

The following code snippet gives me an error as shown in the title, I didn't understand why it doesn't work since T is of type Number, I expected the "+" operator to be ok.

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

      

It would be helpful if anyone can provide some hints, thanks!

+3


source to share


3 answers


There is a fundamental problem with implementing this idea of ​​general arithmetic. The problem is not your reasoning about how mathematically it should work, but how the bytecodes should be compiled with the Java compiler.

In your example, you have the following:

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

      

Leaving boxing and unboxing aside, the problem is that the compiler doesn't know how it should compile your statement +

. Which of the several overloads +

should the compiler use? The JVM has different arithmetic operators (i.e. opcodes) for different primitive types; hence the sum operator for integers is a completely different opcode than the one used for doubles (see for example iadd vs dadd ) If you think about it, it makes sense because, after all, integer arithmetic numbers and floating point arithmetic are completely different. Also different types have different sizes, etc. (See for example ladd ). Also think about BigInteger

andBigDecimal

which also extend Number

, but they don't have autoboxing support and thus no opcode to deal with them directly. There are probably dozens of other implementations Number

similar to those in other libraries. How does the compiler know how to deal with them?

So when the compiler tells what T

is Number

, it is not enough to determine which opcodes are valid for the operation (e.g. boxing, unboxing, and arithmetic).

Later, you suggest changing the code a bit:

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

      

And now the operator +

can be implemented with an integer code-behind, but the result of the sum will be Integer

, not T

, which will still invalidate that code, since from the compiler's point of view T

could be something other than Integer

.

I believe there is no way to make your code generic enough that you can forget about these basic implementation details.

- Edit -

To answer your question in the comments section, consider the following scenario based on the last definition MathOperationV1<T extends Integer>

above.

You are correct when you say that the compiler will perform type erasure in the class definition and it will be compiled as if it were

class MathOperationV1 {
        public Integer add(Integer a, Integer b) {
            return a + b; 
        }
}

      



Given this type erasure, it looks like using a subclass Integer

should work here, but it doesn't, because it would make the type system unreasonable. Let me try to demonstrate this.

Not only can the compiler worry about the ad site, but it must also consider what is happening across multiple sites, perhaps using a different type of argument for T

.

For example, imagine (for the sake of my argument) that there is a subclass Integer

that we will call SmallInt

. And suppose our code above compiled perfectly (are you really in doubt: why doesn't it compile?).

What happens if we do the following?

MathOperationV1<SmallInt> op = new MathOperationV1<>();
SmallInt res = op.add(SmallInt.of(1), SmallInt.of(2));

      

And as you can see, the result of the method op.add()

should be SmallInt

, not Integer

. However, the result of our a + b

above, from our erasable class definition, will always return Integer

not a SmallInt

(because + uses integer JVM arithmetic opcodes), and so this result would be unreasonable, correct ?.

Now you may be wondering, but if erasing a type MathOperationV1

always returns Integer

, how in the world at the calling site can it expect something else (like SmallInt

)?

Well, the compiler adds some extra magic here, discarding the result add

by SmallInt

, but only because it has already ensured that the operation cannot return anything other than the expected type (which is why you see a compiler error).

In other words, your calling site will look like this after erasing:

MathOperationV1 op = new MathOperationV1<>(); //with Integer type erasure
SmallInt res = (SmallInt) op.add(SmallInt.of(1), SmallInt.of(2));

      

But this will only work if you can guarantee that it add

always returns a SmallInt

(which we cannot because of the operator issues described in my original answer).

So, as you can see, type erasure just ensures that, according to the subtyping rules, you can return anything that extends Integer

, but once your invocation site declares a type argument for T

, you are "supposed to always assume the same type where T

appeared in the source code to keep the sound system type.

You can actually prove these points using a Java decompiler (a tool in your JDK bin directory called javap). I could give more subtle examples if you think you need them, but you should try it yourself and see what's going on under the hood :-)

+4


source


Auto (un) boxing only works for types that can be converted to their primitive equivalents. Complement is only defined for numeric primitive types plus String. ie: int, long, short, char, double, float, byte. The number has no primitive equivalent, so it cannot be unpacked, so you cannot add them.



+1


source


+

not defined for Number

. You can see this by writing (no generics):

Number a = 1;
Number b = 2;
System.out.println(a + b);

      

It just won't compile.

You cannot share directly: you need a BiFunction

, BinaryOperator

or similar, which can apply the operation to the inputs:

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);
    }
}

      

But again, you can just use BinaryOperator<T>

directly: MathOperationV1

does not add anything over and above the standard class (in fact, it is smaller).

+1


source







All Articles