Cpt*_*asp 17 java generics effective-java
我的问题很理论......这是Class.asSubclass(Javadoc)的签名:
public <U> Class<? extends U> asSubclass(Class<U> clazz)
Run Code Online (Sandbox Code Playgroud)
为什么返回类型中使用了通配符泛型?根据我对泛型的理解,更好的签名可能是:
public <U> Class<U> asSubclass(Class<U> clazz)
Run Code Online (Sandbox Code Playgroud)
因为你可以肯定演员
Class<? extends U>
Run Code Online (Sandbox Code Playgroud)
更简单
Class<U>
Run Code Online (Sandbox Code Playgroud)
Bloch在他的书"Effective Java"中建议(第137页,第28项):
不要将通配符类型用作返回类型.它不会为您的用户提供额外的灵活性,而是迫使他们在客户端代码中使用通配符类型.
这个选择背后的原因是什么?我错过了什么?非常感谢你提前.
编辑: 正如@egelev建议的那样,我确实可以用另一种方式表达我的问题......事实上,"按原样"返回输入参数将毫无意义.所以真正的问题是:与普通的强制转换相比,Class.asSubclass方法的真正用处是什么?如果出现强制转换问题,两者都会抛出ClassCastException.
可能已添加它以避免在特定情况下未经检查的转换:当您将asSubclass方法的结果直接传递给另一个请求约束类型参数的方法时,如此处(取自Effective Java,第146页):
AnnotatedElement element;
...
element.getAnnotation(annotationType.asSubclass(Annotation.class));
Run Code Online (Sandbox Code Playgroud)
上述方法的签名是:
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
Run Code Online (Sandbox Code Playgroud)
在我看来,asSubclass方法只是一种方法来做一个(事实上!)未经检查的强制转换没有正确的编译器警告...
这最终重新提出我以前的问题:签名
public <U> Class<U> asSubclass(Class<U> clazz)
Run Code Online (Sandbox Code Playgroud)
同样有效(即使很奇怪,我也承认)!它将与getAnnotation示例完全兼容,并且不会限制客户端代码强制它使用无意义的通配符泛型.
编辑2: 我认为我的一般问题已经解决了; 非常感谢你.如果有人有关于asSubclass签名正确性的其他好例子,请将它们添加到讨论中,我希望看到一个完整的例子,使用asSubclass和我的签名,显然不起作用.
Jat*_*tin 13
如果是返回类型Class<? extends U>.让我们首先尝试理解,getClass签名:
AbstractList<String> ls = new ArrayList<>();
Class<? extends AbstractList> x = ls.getClass();
Run Code Online (Sandbox Code Playgroud)
现在让编译器允许我们这样做:
Class<AbstractList> x = ls.getClass();
Run Code Online (Sandbox Code Playgroud)
这可能是错的.因为在运行时,ls.getClass会 ArrayList.class不会AbstractList.class.两者都不能ls.getClass返回Class<ArrayList>因为ls类型为AbstractList <>而不是Arraylist
所以编译器现在说 - 好吧!我不能回来,Class<ArrayList>也不能回来Class<AbstractList>.但是因为我知道它ls是一个AbstractList,所以实际的类对象只能是AbstractList的子类型.所以这Class<? extends AbstractList>是一个非常安全的赌注.由于外卡:你不能这样做:
AbstractList<String> ls = new ArrayList<>();
Class<? extends AbstractList> x = ls.getClass();
Class<ArrayList<?>> xx = x;
Class<AbstractList<?>> xxx = x;
Run Code Online (Sandbox Code Playgroud)
同样的逻辑适用于您的问题.假设它被宣布为:
public <U> Class<U> asSubClass(Class<U> c)
Run Code Online (Sandbox Code Playgroud)
以下将编译:
List<String> ls = new ArrayList<>();
Class<? extends List> x = ls.getClass();
Class<AbstractList> aClass = x.asSubclass(AbstractList.class); //BIG ISSUE
Run Code Online (Sandbox Code Playgroud)
上述aClass在运行时是Class<Arraylist>不Class<AbstractList>.所以这不应该被允许!! Class<? extends AbstractList>是最好的选择.
我在查看这个问题时的第一个想法是,为什么不将它声明为:
public <U extends T> Class<? extends U> asSubClass(Class<U> c)
Run Code Online (Sandbox Code Playgroud)
对我可以传递的参数有一个编译时间限制更有意义.但我认为它不是首选的原因是 - 这会打破前java5代码的向后兼容性.例如,如果asSubClass声明如上所示,那么使用pre-Java5编译的下面的代码将不再编译.
Class x = List.class.asSubclass(String.class); //pre java5
// this would have not compiled if asSubclass was declared above like
Run Code Online (Sandbox Code Playgroud)
快速检查:
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
Run Code Online (Sandbox Code Playgroud)
PS:对于编辑过的问题,关于为什么asSubClass而不是演员? - 因为演员是背叛.例如:
List<String> ls = new ArrayList<>();
Class<? extends List> x = ls.getClass();
Class<AbstractList> aClass = (Class<AbstractList>) x;
Run Code Online (Sandbox Code Playgroud)
以上将永远成功,因为泛型被删除.所以它的类被投到了一个类.但是aClass.equals(ArrayList.class)会给出错误的.所以肯定演员是错的.如果您需要类型安全,您可以asSubClaz在上面使用
我认为,为了理解这一点,首先必须理解类型和类之间的细微差别:对象有类,引用有类型.
类与类型
我想一个例子是解释我的意思的好方法.假设存在以下类层次结构:
class Mammal {}
class Feline extends Mammal {}
class Lion extends Feline {}
class Tiger extends Feline {}
Run Code Online (Sandbox Code Playgroud)
由于子类型多态性,我们可以声明对同一对象的多个引用.
Tiger tiger = new Tiger(); //!
Feline feline = tiger;
Mammal mammal = feline;
Run Code Online (Sandbox Code Playgroud)
有趣的是,如果我们向这些参考文献的每个人询问他们班级的名字,他们都会回答相同的答案:"老虎".
System.out.println(tiger.getClass().getName()); //yields Tiger
System.out.println(feline.getClass().getName()); //yields Tiger
System.out.println(mammal.getClass().getName()); //yield Tiger
Run Code Online (Sandbox Code Playgroud)
这意味着对象的实际类是固定的,它的类是我们在上面的代码中使用"new"运算符时用来实例化它的类.
另一方面,引用可以具有不同的类型,与实际的对象类(即在这种情况下是老虎)在多态上兼容.
因此,对象具有固定类,而引用具有兼容类型.
这往往令人困惑,因为类名与我们用来命名引用类型的名称相同,但从语义上讲,存在一个微妙的差异,正如我们在上面所看到的.
也许最令人困惑的部分是认识到类也是对象,因此它们可以具有自己的兼容引用.例如:
Class<Tiger> tigerClass = null;
Class<? extends Tiger> someTiger = tigerClass;
Class<? extends Feline> someFeline = tigerClass;
Class<? extends Mammal> someMammal = tigerClass;
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,被引用的对象是一个类对象(我暂时将其保留为null),这里使用的这些引用具有不同的类型来到达该假设对象.
所以,你看,这里的"class"这个词用来命名一个"引用类型",指向一个类型与这些引用中的任何一个兼容的实际类对象.
在上面的例子中,我无法定义这样的类对象,因为我将原始变量初始化为null.这是故意的,我们会在一瞬间看到原因.
关于调用getClass和getSubclass引用
按照@ Jatin的例子来考虑这个getClass方法,现在让我们考虑下面的多态代码:
Mammal animal = new Tiger();
Run Code Online (Sandbox Code Playgroud)
现在我们知道,无论我们的animal引用是类型的事实,Mammal对象的实际类是,并且将始终是Tiger(即Class).
如果我们这样做,我们应该得到什么?
? type = mammal.getClass()
Run Code Online (Sandbox Code Playgroud)
应该type是一个Class<Mammal>还是一个Class<Tiger>?
好吧,问题是当编译器看到类型的引用时Mammal,它无法分辨该引用所指向的对象的实际类.那只能在运行时确定,对吗?它实际上可能是一个哺乳动物,但它也可能是它的任何子类,如老虎,对吗?
所以,当我们要求它的类时,我们不会得到Class<Mammal>因为编译器无法确定.相反,我们得到一个Class<? extends Mammal>,这更有意义,因为,毕竟,编译器知道基于子类型多态的规则,给定的引用可能指向哺乳动物或其任何子类型.
在这一点上,你可以看到在这里使用单词class的微妙细微差别.看起来我们实际上从getClass()方法中获得的是某种类型引用,我们用它来指向原始对象的实际类,正如我们之前已经解释过的那样.
嗯,关于这个asSubclass方法可以说同样的事情.例如:
Mammal tiger = new Tiger();
Class<? extends Mammal> tigerAsMammal = tiger.getClass();
Class<? extends Feline> tigerAsFeline = tigerAsMammal.asSubclass(Feline.class);
Run Code Online (Sandbox Code Playgroud)
当我们调用时,asSubclass我们得到了引用指向的实际类的类型的引用,但是编译器不再能确定实际应该是什么,因此你得到一个更宽松的引用Class<? extends Feline>.这是编译器可以假设的关于对象的原始性质的最多,这就是我们得到的全部原因.
新Tiger().gerClass()怎么样?
我们可能期望获得Class<Tiger>(没有wirldcards)的唯一方法应该是访问原始对象,对吧?喜欢:
Class<Tiger> tigerClass = new Tiger().getClass()
Run Code Online (Sandbox Code Playgroud)
但有趣的是,我们总是通过引用类型到达老虎对象,对吗?在Java中,我们永远不能直接访问对象.由于我们总是通过引用到达对象,因此编译器无法对返回的引用的实际类型进行假设.
这就是为什么即使这个代码也会产生的原因 Class<? extend Tiger>
Class<? extends Tiger> tigerClass = new Tiger().getClass();
Run Code Online (Sandbox Code Playgroud)
也就是说,编译器不保证new运算符可以返回此处.对于所有重要的事情,它可能会返回一个与Tiger兼容的对象,但不一定是其类是Tiger本身的对象.
如果更改new工厂方法的运算符,这将变得更加清晰.
TigerFactory factory = new TigerFactory();
Class<? extends Tiger> tigerClass = tigerFactory.newTiger().getClass();
Run Code Online (Sandbox Code Playgroud)
我们的老虎工厂:
class TigerFactory {
public Tiger newTiger(){
return new Tiger(){ } //notice this is an anonymous class
}
}
Run Code Online (Sandbox Code Playgroud)
我希望这在某种程度上有助于讨论.
| 归档时间: |
|
| 查看次数: |
1393 次 |
| 最近记录: |