简体   繁体   中英

Why the generic method changed the parametrized type in an assignation?

When writing a generic method to process data for a form, I came across with the following (as I see it) unexpedted behavior. Given the following code:

public class Test {
   public <T> void someGenericMethod(Integer a) {
      @SuppressWarnings("unchecked")
      T t = (T) a;
      System.out.println(t);
      System.out.println(t.getClass());
   }

   public static void main(String[] args) {
      Test test = new Test();
      test.<BigDecimal>someGenericMethod(42);
   }  
}

AFAIK, the code above should generate a ClassCastException in the line T t = (T) a because the method call in main is setting the parametrized type to BigDecimal and casting from Integer to BigDecimal is not allowed, conversely to what I expected, the program executed well and printed the following:

42
class java.lang.Integer 

In fact, if I add another parameter to the method signature (like String b ) and make another assigment T t2 = (T) b , the program prints

42
class java.lang.String 

Why the t variable changed it's type to Integer (is, by any chance, making some kind of promotion on the type T to Object)?

Any explanation on this behavior is welcome

(T) a is an unchecked cast : due to type erasure , the runtime has no way of knowing what type T is, so it can't actually check if a belongs to type T .

The compiler issues a warning when you do this; in your case, you've suppressed that warning by writing @SuppressWarnings("unchecked") .


Edited to add (in response to a further question in the comments below):

If you want to check the cast, you can write this:

public class Test {
   public <T> void someGenericMethod(Class<T> clazz, Integer a) {
      T t = clazz.cast(a);
      System.out.println(t);
      System.out.println(t.getClass());
   }

   public static void main(String[] args) {
      Test test = new Test();
      // gives a ClassCastException at runtime:
      test.someGenericMethod(BigDecimal.class, 42);
   }
}

by passing in clazz , you allow the runtime to check the cast; and, what's more, you allow the compiler to infer T from the method arguments, so you don't have to write test.<BigDecimal>someGenericMethod anymore.

Of course, the code that calls the method can still circumvent this by using an unchecked cast:

public static void main(String[] args) {
   Test test = new Test();
   Class clazz = Object.class;
   test.someGenericMethod((Class<BigDecimal>) clazz, 42);
}

but then that's main 's fault, not someGenericMethod 's. :-)

When compiling, your code above basically becomes the following non-generic method:

public void someGenericMethod(Integer a) {
   Object t = a;
   System.out.println(t);
   System.out.println(t.getClass());
}

There is no cast. No exception.

You specify a type parameter in your method signature, but never use it.

I think you want something like this:

public class Test {
   public <T> void someGenericMethod(T someItem) {
      System.out.println(someItem);
      System.out.println(someItem.getClass());
   }
}

public static void main(String[] args) {
    Test test = new Test();
    BigDecimal bd = new BigDecimal(42);

    test.someGenericMethod(42);    // Integer
    test.someGenericMethod("42");  // String
    test.someGenericMethod(42L);   // Long
    test.someGenericMethod(bd);    // BigDecimal
}  

Note that there's no need to cast.

The parameter type is declared in the method signature and inferred from the parameter.

In your code you're parameterizing the method call (which I've never seen) and passing in an int.

It's kinda hard to understand what you're trying to do, since your example code does nothing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM