Class hierarchy problem with java generics

I have run into a problem with class hierarchy in a generic function. I need to enforce that, with the two classes T and U specified in the function, one is the child of the other. I have found to much surprise that the construct <T extends U> does not at all enforce a parent child relation of U and T. Instead it also allows T and U to be the same type.

This creates a problem because it looks like in a case where U extends T Java will not indicate the error but instead it will happily deduce both objects to be of type T (which is true no doubt) and then compile and run the code without any complaint.

Here is an example that illustrates the issue:

public class MyClass {
    public static void main(String args[]) {
      Foo foo = new Foo();
      Bar bar = new Bar();

      // This code is written as intended
      System.out.println( justTesting(foo, bar) );

      // This line shouldn't even compile
      System.out.println( justTesting(bar, foo) );
    }
    
    static interface IF {
       String get(); 
    }
    
    static class Foo implements IF {
       public String get(){return "foo";} 
    }
    
    static class Bar extends Foo {
        public String get(){return "bar";}
        
    }
    
    static <G extends IF , H extends G> String justTesting(G g, H h) {
        if (h instanceof G)
            return h.get() + " (" + h.getClass() + ") is instance of " + g.getClass() + ". ";
        else
            return "it is the other way round!";
    }
}

And this is the output:

bar (class MyClass$Bar) is instance of class MyClass$Foo. 
foo (class MyClass$Foo) is instance of class MyClass$Bar. 

I need to ensure that the parent child relation of the generic classes is observed by the compiler. Is there any way to do that?


Solution 1:

It compiles, because IF is a "upper bound" for both H and G.

Means: Generics are not that "dynamic" as we think, and we could also write:

static <G extends IF, H extends IF> ... // just pointing out that G *could* differ from H 

Disregarding null check, is this, what you want:

  static <G extends IF, H extends G> String justTesting(G g, H h) {
    if (g.getClass().isAssignableFrom(h.getClass())) {
      return h.get() + " (" + h.getClass() + ") is instance of " + g.getClass() + ". ";
    } else {
      return "it is the other way round!";
    }
  }

?

Class.isAssignableFrom()


Prints:

bar (class com.example.test.generics.Main$Bar) is instance of class com.example.test.generics.Main$Foo. 
it is the other way round!

And watch out with "anonymous classes", e.g.:

System.out.println(
  justTesting(
    new IF() {
      @Override
      public String get() {
        return "haha";
      }
    }, foo)
);

System.out.println(
  justTesting(
    foo, new IF() {
      @Override
      public String get() {
        return "haha";
      }
    }
  )
);

print both "it is the other way round!", so the decision here is not that "binary".