Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A special case of Java generics matching

Tags:

java

generics

I'd like to resolve a dubious point that has been affecting some parts of my code for years — it is fixed with a trivial cast, but now I'd like to understand the rationale behind it and possibly a reference in official specifications.

Let me introduce this piece of code:

import java.util.function.Supplier;

class BaseClass<T, R> {
  R getSomething() { return null; }
  static <U> U method1 (final U arg1) { return arg1; }
  static <U> U method2 (final U arg1, final Class<? extends U> arg2) { return arg1; }
  static <U> U method3 (final U arg1, final Supplier<? extends U> arg2) { return arg1; }
}

Now let me have a subclass with a partial generics binding (T is still unbound):

class DerivedClass<T> extends BaseClass<T, String> {
  private String s;

  void test (final DerivedClass<T> arg) {
    final var m1 = method1(arg);
    s = m1.getSomething();

    final DerivedClass<T> m3 = method1(arg);
    s = m3.getSomething();

    final var m2 = method2(arg, DerivedClass.class);
    // 1. ERROR: requires String, found Object
    s = m2.getSomething();

    // 2. WARNING: unchecked assignment DerivedClass to DerivedClass<T>
    final DerivedClass<T> m4 = method2(arg, DerivedClass.class);
    s = m4.getSomething();

    final var m5 = method3(arg, () -> new DerivedClass<>());
    s = m5.getSomething();
    } 
}

With method1 everything is fine and the returned object carries the binding R -> String. In other words, the U type of arg1 is correctly propagated to the result.

With method2 the binding is lost (and "downgraded" to Object): this ends up in a warning if I force things with an explicit type declaration of m4 (of course an explicit cast would work too) and an error if I use var.

With method3, in spite of arg2 being declared in a similar way as in method2, everything is fine again.

So it seems that the culprit is the presence of an argument of Class<> type. Why? In general, why doesn't the compiler use arg1 for a complete match even when there is a Class<> parameter?

As far as I can remember this happens since Java 5 (of course the portions with var refer to Java 17).

like image 845
Fabrizio Giudici Avatar asked Feb 11 '26 12:02

Fabrizio Giudici


1 Answers

Because both BaseClass and DerivedClass are generic classes, but DerivedClass.class is a raw type (namely Class<DerivedClass> and not Class<DerivedClass<T>>).

var is inferred to be the raw type and your statement is identical to:

final DerivedClass m2 = method2(arg, DerivedClass.class);

Once you use raw types, all generic information is "lost"/erased and you have to deal with plain Objects.

this.<DerivedClass<T>>method2(arg, DerivedClass.class) should make m2 generic again (but you probably still have the unchecked cast warning, just for the argument).

And it's exactly this "unchecked assignment" that's causing the issues (unchecked from the raw type to the generic closed type).

like image 63
knittl Avatar answered Feb 15 '26 14:02

knittl



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!