Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 505
Soundbytes Avatar asked Feb 27 '26 10:02

Soundbytes


1 Answers

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".

like image 157
xerx593 Avatar answered Mar 01 '26 00:03

xerx593