Java泛型中的类层次结构问题

3

我在一个泛型函数的类层次结构中遇到了问题。 我需要强制规定,在函数中指定的两个类TU中,一个是另一个的子类。 但令人惊讶的是,我发现构造<T extends U>根本不强制要求UT之间存在父子关系。 相反,它还允许TU是同一类型。

这会引起问题,因为如果U extends T的情况下,Java不会指示错误,而是愉快地推导出两个对象都是T类型(毫无疑问),然后编译并运行代码而没有任何抱怨。

下面是一个说明该问题的例子:

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!";
    }
}

以下是输出结果:

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

我需要确保编译器能够观察到通用类之间的父子关系。有没有办法实现这一点?

1
作为一般说明,由于类型擦除的原因,在任何运行时上下文中都不能使用泛型类型变量,包括instanceof检查、new调用、数组分配等。我真的很惊讶(h instanceof G)能够编译通过。 - Silvio Mayolo
有趣的是,在编译时进行了类型检查。条件(g instanceof H)不会编译,因为它与函数定义的继承层次结构相矛盾。 - Soundbytes
1个回答

2

它编译通过,因为IF两个HG的"上界"。

这意味着:泛型并不像我们想象的那么"动态",我们也可以这样写:

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

忽略空值校验,你需要的是这个吗:
  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!

请注意“匿名类”,例如:

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";
      }
    }
  )
);

打印出 "it is the other way round!",所以这里的决策并不是 "二进制"。


2
那么,你可能会想要完全摆脱通用边界,并只将两个IF作为参数,因为它们只会造成混乱,而在这种情况下实际上并不提供任何类型安全性。 - Silvio Mayolo
1
感谢您的解释,更感谢您通过Class.isAssignableFrom()解决了问题。我更喜欢在编译时进行检查,但在运行时进行检查仍然可以接受。 - Soundbytes
我对“下限”解释不是很确定。我有另一个例子,没有涉及接口,只有<G,G extends H>,它的行为也是一样的。这就是为什么我认为是编译器的类型推断引起的,正如我在原始问题中所写的那样。 - Soundbytes
我也尝试过:<G,H extends G> ...但编译器无法“理解”它... System.out.println(justTesting(new Object(),“haha”));System.out.println(justTesting(“haha”,new Object())); 都可以工作 :)) - xerx593
1
<G, H extends G> 对于编译器来说,类似于 <G extends Object, H extends Object> ...这也解释了为什么 System.out.println(justTesting("haha", new Object())); 可以编译(并运行)。 - xerx593
抱歉,实际上应该是“上限”,因为在尝试不可编译的代码时我选择了“下限”。(只是一种观点...我想:) - xerx593

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接