在将类类型添加到列表期间,我发现(对于我的知识状态)奇怪的行为。
我有一个列表,其中包含Abstract类List<Class<MyAbstractClass>> myImplementations
的所有实现类。我添加了一种非派生类,没有错误。谁能解释为什么我可以做myImplementations.add(SomeOtherClass.class);
这样的事情而没有任何例外?似乎第二种泛型类型(MyAbstractClass)根本没有效果。
---编辑---
public abstract class MyAbstractClass{
public static String getMyIdentification(){ throw new RuntimeException("implement method");}
}
public class MyImplementation extends MyAbstractClass{
public static String getMyIdentification(){ return "SomeUUID";}
}
public class OtherClass{}
// in another class:
List<Class<MyAbstractClass>> myImplementations = new ArrayList<Class<MyAbstractClass>>();
myImplementations.add(MyImplementation.class); // does not cause any error
myImplementations.add(OtherClass.class); // does not cause any error, but should in my opinion??
----编辑结束---
谢谢你, EL
答案 0 :(得分:3)
在编译期间会删除该类型,因此您不会在运行时看到任何异常。编译器应该在你的情况下抱怨或发出警告。
List<Class<String>> list = new ArrayList<Class<String>>();
list.add(Integer.class); // should create a compiletime error
list.add(Class.forName("java.lang.Integer")); // should create a warning
// and run due to type erasure
类型参数Class<String>
在编译期间被擦除 - 它仅由编译器用于检查java源代码是否有效。编译后的字节码不再包含此信息,在字节代码级别上,列表将保持并接受Object
或Object
的任何子类。因为Integer.class
是Object
的子类,所以代码将运行 - 直到运行时在程序员处抛出ClassCastExceptions,因为它期望Class<String>
个实例。
答案 1 :(得分:1)
使用eclipse编译器可以正常运行:
List<Class<? extends CharSequence>> myImplementations =
new ArrayList<Class<? extends CharSequence>>();
myImplementations.add(String.class);
myImplementations.add(Vector.class);
即。编译器只抱怨第二次添加。但是,如果它通过编译,则列表将转换为原始列表,并且在从列表中获取元素之前不会出现异常 - 例如,使用foreach循环。
没有? extends
,即使String
,编译也会失败。那应该是这样的。我很惊讶您没有任何错误,因为java泛型是不变的 - 即您无法将Subclass
实例添加到List<Superclass>
。
答案 2 :(得分:0)
Java的泛型不具体化。 List<String>
和List<Integer>
在编译时是不同的类型,但这两种类型在运行时都被删除为List
。这意味着通过绕过编译时检查,您可以在运行时向Integer
插入List<String>
,这本身可能不会生成ClassCastException
。这是一个例子:
List<String> names = new ArrayList<String>();
List raw = names; // generates compiler warning about raw type!
raw.add((Integer) 42); // does not throw ClassCastException! (yet!)
// but here comes trouble!
for (String s : names) {
// Exception in thread "main" java.lang.ClassCastException:
// java.lang.Integer cannot be cast to java.lang.String
}
请注意,您必须故意绕过编译时检查以违反泛型类型不变量:编译器将尽力确保List<String>
确实只包含{ {1}},并将根据需要生成尽可能多的警告和错误以强制执行此操作。
有时我们希望在运行时强制执行类型安全。对于大多数情况,来自java.util.Collections
的已检查集合包装器可以促进此行为。来自文档:
<E> Collection<E> checkedCollection(Collection<E> c, Class<E> type)
返回指定集合的动态类型安全视图。 任何插入错误类型元素的尝试都会立即导致
String
。假设一个集合在生成动态类型安全视图之前不包含错误输入的元素,并且所有后续访问集合都是通过视图进行的,那么可以保证集合不能包含错误输入的元素。该语言中的泛型机制提供了编译时(静态)类型检查,但可以通过未经检查的强制转换来破坏此机制。通常这不是问题,因为编译器会对所有此类未经检查的操作发出警告。但是,有时单独进行静态类型检查是不够的。例如,假设一个集合被传递给第三方库,那么库代码必须通过插入错误类型的元素来破坏集合。
动态类型安全视图的另一个用途是调试。假设一个程序失败并带有
ClassCastException
,表明错误输入的元素被放入参数化集合中。不幸的是,异常可以在插入错误元素之后的任何时间发生,因此它通常提供关于问题的真实来源的很少或没有信息。如果问题是可重现的,可以通过临时修改程序以使用动态类型安全视图包装集合来快速确定其来源。例如,这个声明:ClassCastException
可以暂时替换为:
Collection<String> c = new HashSet<String>();
再次运行程序会导致它在错误输入元素插入集合时失败,从而清楚地识别问题的根源。
以下是对上一个代码段的修改:
Collection<String> c = Collections.checkedCollection(
new HashSet<String>(), String.class);
请注意,作为“奖励”,List<String> names = Collections.checkedList(
new ArrayList<String>(), String.class
);
List raw = names; // generates compiler warning about raw type!
raw.add((Integer) 42); // throws ClassCastException!
// Attempt to insert class java.lang.Integer element into collection
// with element type class java.lang.String
会在尝试Collections.checkedList
时在运行时抛出NullPointerException
。
add(null)
不幸的是Class.isAssignableFrom
不支持我们在这种情况下所需的行为:您可以使用它来确保在运行时只能添加Collections.checkedList
个实例,但它不能确保给定的java.lang.Class
对象是另一个Class
的子类。
幸运的是,Class.isAssignableFrom(Class)
方法允许您进行此检查,但您必须编写自己的已检查Class
包装来强制执行此操作。这个想法在List
辅助方法而不是完整的static
实现中进行了说明:
List
现在我们有:
static void add(List<Class<?>> list, Class<?> base, Class<?> child) {
if (base.isAssignableFrom(child)) {
list.add(child);
} else {
throw new IllegalArgumentException(
String.format("%s is not assignable from %s",
base.getName(),
child.getName()
)
);
}
}