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.class
。 ls.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。这是故意的,稍后我们就会明白为什么。
关于在引用中调用 getClass
和 getSubclass
继@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
}
}
我希望这在某种程度上有助于讨论。
我的问题很理论化...这是 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.class
。 ls.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。这是故意的,稍后我们就会明白为什么。
关于在引用中调用 getClass
和 getSubclass
继@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
}
}
我希望这在某种程度上有助于讨论。