Class.asSubclass 签名

Class.asSubclass signature

我的问题很理论化...这是 Class.asSubclass (Javadoc) 的签名:

public <U> Class<? extends U> asSubclass(Class<U> clazz)

为什么在 return 类型中使用通配符泛型?根据我对泛型的理解,更好的签名可能是:

public <U> Class<U> asSubclass(Class<U> clazz)

因为你肯定可以投

Class<? extends U>

更简单

Class<U>

Bloch 在他的书 "Effective Java" 中推荐(第 137 页,第 28 项):

Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code.

这个选择背后的原因是什么?我缺少什么? 非常感谢您。

编辑: 正如@egelev 所建议的那样,我确实可以用另一种方式来表达我的问题......实际上 returning 输入参数 "as is" 是没有意义的。所以真正的问题是: 与普通转换相比,Class.asSubclass 方法的真正用途是什么?如果出现转换问题,两者都会抛出 ClassCastException。

可能添加它是为了避免在特定情况下未经检查的强制转换:当您将 asSubclass 方法的结果直接传递给另一个要求约束类型参数的方法时,如此处(摘自 Effective Java,第 146 页):

AnnotatedElement element;
...
element.getAnnotation(annotationType.asSubclass(Annotation.class));

上述方法的签名为:

<T extends Annotation> T getAnnotation(Class<T> annotationClass);

在我看来,asSubclass 方法只是一种在没有适当的编译器警告的情况下进行(实际上!)未经检查的转换的方法...

最后又重新提出了我之前的问题:签名

public <U> Class<U> asSubclass(Class<U> clazz)

同样有效(虽然奇怪,我承认)!它将与 getAnnotation 示例完全兼容,并且不会限制客户端代码迫使它使用毫无意义的通配符泛型。

编辑2: 我想我的一般问题已经解决了;非常感谢您。如果有人有其他关于 asSubclass 签名正确性的好例子,请将它们添加到讨论中,我想看一个完整的例子,使用 asSubclass 和我的签名,显然 不起作用。

这个方法背后的想法就是添加这个通配符。据我了解,此方法的目的是将 this class 对象转换为表示参数 clazz 的任何子 class 的 class 对象。这就是为什么结果被声明为 Class<? extends U>(扩展 U 的 class)的原因。否则它就没有意义,因为它的 return 类型将与其唯一参数的类型完全相同,即它除了 return clazz; 什么都不做。它进行运行时类型检查并将参数转换为 Class<? extends U>(当然,如果调用目标 this 不代表 U 的 class,它将抛出 ClassCastException衍生物)。这是比简单的 (Class<? extends U>) 类型转换更好的方法,因为后者会使编译器产生警告。

如果是 return,请输入 Class<? extends U>。让我们首先尝试理解,getClass 签名:

AbstractList<String> ls = new ArrayList<>();
Class<? extends AbstractList> x = ls.getClass();

现在编译器允许我们做:

Class<AbstractList> x = ls.getClass();

这是错误的。因为在运行时,ls.getClass 将是 ArrayList.class 而不是 AbstractList.classls.getClass return Class<ArrayList> 也不能,因为 ls 是 AbstractList<> 类型而不是 Arraylist

所以编译器现在说 - 好的!我不能 return Class<ArrayList> 也不能 return Class<AbstractList>。但是因为我知道 ls 肯定是一个 AbstractList,所以实际的 class 对象只能是 AbstractList 的子类型。所以 Class<? extends AbstractList> 是一个非常安全的选择。由于通配符: 你不能做:

AbstractList<String> ls = new ArrayList<>();
Class<? extends AbstractList> x = ls.getClass();
Class<ArrayList<?>> xx = x;
Class<AbstractList<?>> xxx = x;

同样的逻辑也适用于你的问题。假设它被声明为:

 public <U> Class<U> asSubClass(Class<U> c)

以下将编译:

 List<String> ls = new ArrayList<>();
 Class<? extends List> x = ls.getClass();
 Class<AbstractList> aClass = x.asSubclass(AbstractList.class); //BIG ISSUE

在运行时 aClass 以上是 Class<Arraylist> 而不是 Class<AbstractList>。所以这不应该被允许!! Class<? extends AbstractList> 是最好的选择。



我看到这个问题的第一个想法是,为什么它没有声明为:

 public <U extends T> Class<? extends U> asSubClass(Class<U> c)

对我可以传递的参数进行编译时限制更有意义。但我认为它不是首选的原因是 - 这会破坏 pre-java5 代码的向后兼容性。例如,如果像上面那样声明 asSubClass,则使用 pre-Java5 编译的代码将不再编译。

Class x = List.class.asSubclass(String.class); //pre java5
// this would have not compiled if asSubclass was declared above like

快速检查:

    public static <T, U extends T> Class<? extends U> asSubClaz(Class<T> t, Class<U> c){
        return t.asSubClass(c);
    }
    public static <T, U> Class<? extends U> asSubClazOriginal(Class<T> t, Class<U> c){
        return t.asSubClass(c);
    }
    asSubClazOriginal(List.class, String.class);
    asSubClaz(List.class, String.class); //error. So would have broken legacy code

PS:对于已编辑的问题,关于为什么 asSubClass 而不是投射? - 因为演员是背叛。例如:

List<String> ls = new ArrayList<>();
Class<? extends List> x = ls.getClass();
Class<AbstractList> aClass = (Class<AbstractList>) x;

以上总是会成功,因为泛型被删除了。所以它的 class 转换为 class。但是 aClass.equals(ArrayList.class) 会给出 false。所以肯定演员是错误的。如果你需要类型安全,你可以使用上面的asSubClaz

我认为要理解这一点,首先必须了解类型和classes之间的细微差别:objects有classes,references有types.

Classes 与类型

我认为一个例子是解释我这里的意思的好方法。假设存在以下 class 层次结构:

class Mammal {}
class Feline extends Mammal {}
class Lion extends Feline {}
class Tiger extends Feline {}

多亏了子类型多态性,我们可以声明对同一对象的多个引用。

Tiger tiger = new Tiger(); //!
Feline feline = tiger;
Mammal mammal = feline;

有趣的是,如果我们向这些参考文献中的每个人询问他们 class 的名字,他们都会回答相同的答案:"Tiger".

System.out.println(tiger.getClass().getName()); //yields Tiger
System.out.println(feline.getClass().getName()); //yields Tiger
System.out.println(mammal.getClass().getName()); //yield Tiger

这意味着对象的实际class是固定的,它的class就是我们在使用"new" 运算符在我们上面的代码中。

另一方面,引用 可能有不同的类型 ,与实际的 class 对象(即本例中的老虎)多态兼容。

因此,对象具有固定的 class 而引用具有兼容的类型。

这容易造成混淆,因为 class 名称与我们用来命名引用类型的名称相同,但从语义上讲存在细微差别,正如我们在上面看到的那样。

也许最令人困惑的部分是认识到 classes 也是对象,因此它们可以有自己的兼容引用。例如:

Class<Tiger> tigerClass = null;
Class<? extends Tiger> someTiger = tigerClass;
Class<? extends Feline> someFeline = tigerClass;
Class<? extends Mammal> someMammal = tigerClass;

在我上面的代码中,被引用的对象是一个class对象(我暂时保留为null),这里使用的这些引用有不同的类型到达那个假设的物体。

所以,你看,这里的单词 "class" 用于命名一个 "type of reference" 指向一个实际的 class 对象,其类型与任何这些引用兼容。

在我上面的示例中,我未能定义这样的 class 对象,因为我将原始变量初始化为 null。这是故意的,稍后我们就会明白为什么。

关于在引用中调用 getClassgetSubclass

继@Jatin 考虑 getClass 方法的示例之后,现在,让我们考虑以下多态代码:

Mammal animal = new Tiger();

现在我们知道,不管我们的 animal 引用的类型是 Mammal,对象的实际 class 是,而且永远是,Tiger(即 Class)。

这样做会得到什么?

? type = mammal.getClass()

type 应该是 Class<Mammal> 还是 Class<Tiger>

嗯,问题是当编译器看到 类型 Mammal 的引用 时,它无法判断实际的 class 是什么此引用指向的对象。那只能在运行时确定,对吗?它实际上可能是哺乳动物,但也可能是它的任何子classes,比如老虎,对吧?

因此,当我们请求它的 class 时,我们没有得到 Class<Mammal>,因为编译器无法确定。相反,我们得到一个 Class<? extends Mammal>,这更有意义,因为毕竟,编译器知道基于子类型多态性的规则,给定的引用可能指向哺乳动物或其任何子类型。

在这一点上,您可能会在这里看到使用 class 这个词的细微差别。看起来我们实际上从 getClass() 方法中得到的是某种类型引用,我们用它来指向我们之前已经解释过的原始对象的实际 class。

嗯,对于 asSubclass 方法也可以这样说。例如:

Mammal tiger = new Tiger();

Class<? extends Mammal> tigerAsMammal = tiger.getClass();
Class<? extends Feline> tigerAsFeline = tigerAsMammal.asSubclass(Feline.class);

当我们调用 asSubclass 时,我们得到的是我们引用所指向的 class 的实际类型的引用,但编译器无法再确定该实际性质应该是什么,因此您会得到一个更宽松的参考,例如 Class<? extends Feline>。这是编译器可以假设的关于对象原始性质的最多信息,这就是我们得到的全部内容的原因。

new Tiger().gerClass()怎么样?

我们可能期望获得 Class<Tiger>(没有 wirldcards)的唯一方法应该是访问原始对象,对吧?喜欢:

Class<Tiger> tigerClass = new Tiger().getClass()

有趣的是,我们总是通过引用类型到达老虎对象,对吧?在 Java 中,我们永远无法直接访问对象。因为我们总是通过它们的引用到达一个对象,所以编译器无法假设引用的实际类型是 returned.

这就是为什么即使这段代码也会产生 Class<? extend Tiger>

Class<? extends Tiger> tigerClass = new Tiger().getClass();

也就是说,编译器不保证 new 运算符在这里可以 return 做什么。对于所有重要的事情,它可能 return 一个与 Tiger 兼容的对象,但不一定 class 是 Tiger 本身。

如果您更改工厂方法的 new 运算符,这将变得更加清晰。

TigerFactory factory = new TigerFactory();
Class<? extends Tiger> tigerClass = tigerFactory.newTiger().getClass();

还有我们的老虎工厂:

class TigerFactory {
   public Tiger newTiger(){
     return new Tiger(){ } //notice this is an anonymous class
   }
}

我希望这在某种程度上有助于讨论。