Home > Blockchain >  Java Generics -incompatible types: int cannot be converted to T
Java Generics -incompatible types: int cannot be converted to T

Time:05-22

I have simple question about generics , I am curious why is not working, it is about T datatype:

This wont work , why ?

    public class NoGenericClass {
    
    
        private < T extends Number > T GenericMethod(int t) //(Integer t) will throw an error too
        {
            return (t); //even if I do  return (T)(t); dosent work either
        }
    
        public static void main(String[] args) {
            NoGenericClass ng = new NoGenericClass();
            var q = ng.GenericMethod(123);
            System.out.println(q);
    
        } 
}

But if I do: private < T extends Number > T GenericMethod(T t) everything works fine. Is this because T is considered object type ? but everything extends object in java, what am I missing ?

I got the error saying:/NoGenericClass.java:6: error: incompatible types: Integer cannot be converted to T return (t); ^ where T is a type-variable: T extends Number declared in method GenericMethod(Integer) 1 error

Thanks for the explanation.

CodePudding user response:

You'd think this is related to the fact that primitives and generics are incompatible, but, no. It has nothing to do with that.

You trivially proved this by trying Integer as well.

There is no problem here. Of course it doesn't compile; it's entirely logical that it doesn't. Your types make no sense. Presumably then, the problem is your understanding of generics and what those mean; had you fully understood them this question wouldn't have come up. Don't feel bad - generics are quite complicated and the bevy of comments and even another answer are right there with you, evidently.

So let's get into it

private <T extends Number> T GenericMethod(Integer t) {

This declares a new type variable. A type variable is a variable as the name suggests. It's a lot like the x in int x = 5;. Except, where x is a thing whose 'value' is some , well, value, T is a thing that whose value is a type, such as String or Integer, or even List<Map<X, ? extends Y>> - hence, a type variable.

You have given this variable a 'bound'. You have declared that, whatever the T variable's value might be, it must at least be some type that is either Number, or something that is a subtype of Number. So, we know that much T is... something. Number. or Integer. or Double, or perhaps Floobargle, some type you don't even know about that someone else made, declared class Floobargle extends Number. Possibly even ? extends Integer (which guarantees fits the bound of 'Number or some subtype thereof').

Given that it now has this bound, your method should function in a type safe fashion regardless of what the T variable actually ends up being!.

So, it COULD be Double. So let's try that, let's make T double, just for kicks. We get:

private Double GenericMethod(Integer t) {
  return integer;
}

You'd think I left something out: The cast. However, that's is an entirely new can of beans! "Cast" refers specifically to a syntactical construct, looks like (SomeType) someExpr, and that construct is used for 3 completely and utterly unrelated jobs (namely: Type coercion, type assertion, and primitive conversion). (T) in particular is type assertion, whereas Double) would be type coercion - and the only way to make this code typesafe is with type coercion. However, given that generics are erased, it is impossible to do so, and in fact any casting to a type var, such as (T) just emit a compiler warning and nothing else - no bytecode, no type checks, hence, no type safety, hence, it's effectively not there, hence - no can do.

When we slot in Double as the value for T, your method is invalid: You're returning an instance of Integer, which is not type compatible with the type Double. Hence, the compiler refuses to compile your code, correctly: It's nonsense.

This gets us to some crucial clues:

Generics are a figment of the compiler's imagination

The runtime (java.exe) has no idea what they are; most generics do not survive compilation, and the few that do are treated as comments by the JVM. The JVM simply does not know what generics is or means and has no idea what to do. It's like compiler checked documentation: The compiler (javac), does know and will ensure that [A] if type safety is violated, the code either does not compile at all, or at least compiles with a warning, and [B] the code that IS emitted is JVM-level typesafe, which means a lot of invisible real casts are tossed into the bytecode.

Here, give this a try: Compile:

import java.util.*;
class Test {
  void foo() {
    List<String> x = new ArrayList<String>();
    x.add("Hello");
    String y = x.get(0);
    System.out.println(y);
  }
}

Then javap -c it to see the bytecode, and would you look at that:

void foo();
    Code:
       0: new           #7                  // class java/util/ArrayList
       3: dup
       4: invokespecial #9                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #10                 // String Hello
      11: invokeinterface #12,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: aload_1
      18: iconst_0
      19: invokeinterface #18,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      24: checkcast     #22                 // class java/lang/String
      27: astore_2
      28: getstatic     #24                 // Field java/lang/System.out:Ljava/io/PrintStream;
      31: aload_2
      32: invokevirtual #30                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      35: return

Look at bytecode entry at position 24: checkcast. Checkcast??? How? There is no cast in your java code!

Ah, but there is - the compiler inserted one, because generics do not exist at the bytecode level, so that code was merely syntax sugar for:

import java.util.*;
class Test {
  void foo() {
    List x = new ArrayList();
    x.add("Hello");
    String y = (String) x.get(0);
    System.out.println(y);
  }
}

Generics serve to link things

It feels perhaps rather entirely pointless: If T can be Double OR Integer OR Number or even ? extends Integer or even weirder things, then what good is that? It seems kinda stupid, no? You are tasked to write code such that:

private ???? GenericMethod(Integer t) {
  codeHere();
}

the above would have compiled both if you replaced the question marks with Double, as well as if you replaced it with Integer, which seems a bit dumb.

But no so fast - generics link things. You can tell the compiler: Any type is fine, here, but this other place we mention a type? It's the same type. Which could be anything, but, it is the same as the first thing.

Here is a method that prints a value and then returns it:

public Object printAndReturn(Object o) {
  System.out.println(o);
  return o;
}

Unfortunately this method doesn't do what we want. For example, this:

printAndReturn("Hello").toLowerCase();

would not compile. We know it should - printAndReturn returns the argument, which is a string, and we can invoke toLowerCase on this. But your method merely declared that it returns 'some Object'. There is no guarantee that it returns the same type as the parameter, that isn't written anywhere. return 5; would be just as legal an implementation, and implementations can change. Therefore, the compiler won't let you call .toLowerCase() on the expression printAndReturn("Hello").

But what if I want to write that method and sign up to always returning the same type in the future? We CAN do that, but we need to link the two together. We use generics for this:

public <T> T printAndReturn(T o) {
  System.out.println(o);
  return o;
}

Now we can write printAndReturn("Hello").toLowerCase() and it will compile just fine. Even though at the bytecode level, both printAndReturn methods are entirely identical. The compiler will actually insert a TYPECAST bytecode to double check that the printAndReturn method did, in fact, return a String. The generics serve only to tell the compiler to [A] check a few things, and [B] inject the right typecast bytecode to maintain type safety even at the JVM level.

That's what they are for. Another example of 'linking' things is the well known ArrayList class. We want to link things: We want to enforce that only ever some specific, written-out-by-the-user-of-the-class type is only ever added, and we then want to provide the benefit of having e.g. the .get(idx) call on the list to do you a solid and auto-cast it. If the compiler ensures that for, say, a List<String> x = new ArrayList<String>(), only strings are even passed to the x.add() call, then x.get(0) can be 'pre-casted' to a String without ever causing an issue. We linked the return type of the list's get(int idx) method to the parameter type of list's .add(??? param).

Back to your code

So, what is it you are trying to accomplish here?

If the idea is to accept any number, do some operations on this number, and then return the same number type, then you'd want private <T extends Number> T genericMethod(T in) {}: You want to link the parameter type to the return type, telling the compiler that it should assume that these are always identical, by:

  • Automatically treating the return type of any attempt to invoke this method as identical to the param type...
  • But tossing a checkcast bytecode in there to keep the peace with the JVM who doesn't know what generics are,
  • ... and by enforcing it when looking at your method. Actually check that, guaranteed, you are returning the same type.

This can be hard; it is for example not possible to take in any numeric type, add 1 to it, and return it. That seems like something one ought to be able to do, but you simply cannot. Java's Number type isn't complete in this sense. The only way out is hacky: By injecting type assertions (casts to (T), which always generate a warning), checking the actual type of your parameter against every known primitive type (well, wrapped - generics cannot be primitives, at least, not up to and including java 18, maybe java 20 is different, Project Valhalla is trying to change this), and your code will flat out have to throw an exception or otherwise crash out if it's an unknown number type - java.lang.Number is not final, anybody can make a new Number type.

CodePudding user response:

in java, we have primitive types and reference types

int is a primitive type

java has a reference type named Integer that is a wrapper class for int primitive type

generics work with reference types so you need to replace the int parameter with an Integer reference type and cast the result to generic type T

public class NoGenericClass {
    
    private <T extends Number> T GenericMethod(Integer t) {
        return (T) t;
    }

    public static void main(String[] args) {
        NoGenericClass ng = new NoGenericClass();
        var q = ng.GenericMethod(123);
        System.out.println(q);
    }
}
  • Related