有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java在列表中添加false类型时没有异常

在将类类型添加到列表中的过程中,我发现(就我所知)一个奇怪的行为

我有一个列表,其中包含抽象类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??

----编辑结束---

谢谢,, 埃尔


共 (3) 个答案

  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. # 2 楼答案

    关于类型擦除

    Java的泛型是非具体化的。A List<String>和A 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>确实只包含String,并将根据需要生成尽可能多的警告和错误来执行此操作


    在已检查的集合上

    有时我们希望在运行时强制执行类型安全。对于大多数情况,来自^{}的选中集合包装器可以促进这种行为。从文件中:

    <E> Collection<E> checkedCollection(Collection<E> c, Class<E> type)

    Returns a dynamically typesafe view of the specified collection. Any attempt to insert an element of the wrong type will result in an immediate ClassCastException. Assuming a collection contains no incorrectly typed elements prior to the time a dynamically typesafe view is generated, and that all subsequent access to the collection takes place through the view, it is guaranteed that the collection cannot contain an incorrectly typed element.

    The generics mechanism in the language provides compile-time (static) type checking, but it is possible to defeat this mechanism with unchecked casts. Usually this is not a problem, as the compiler issues warnings on all such unchecked operations. There are, however, times when static type checking alone is not sufficient. For example, suppose a collection is passed to a third-party library and it is imperative that the library code not corrupt the collection by inserting an element of the wrong type.

    Another use of dynamically typesafe views is debugging. Suppose a program fails with a ClassCastException, indicating that an incorrectly typed element was put into a parameterized collection. Unfortunately, the exception can occur at any time after the erroneous element is inserted, so it typically provides little or no information as to the real source of the problem. If the problem is reproducible, one can quickly determine its source by temporarily modifying the program to wrap the collection with a dynamically typesafe view. For example, this declaration:

        Collection<String> c = new HashSet<String>();     
    

    may be replaced temporarily by this one:

        Collection<String> c = Collections.checkedCollection(
             new HashSet<String>(), String.class);
    

    Running the program again will cause it to fail at the point where an incorrectly typed element is inserted into the collection, clearly identifying the source of the problem.

    以下是对前一段的修改:

    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的子类

    幸运的是^{}方法允许您进行此检查,但您必须编写自己的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()
                )
            );
        }
    }
    

    现在我们有:

    List<Class<?>> list = new ArrayList<Class<?>>();
    
    add(list, CharSequence.class, String.class);         // OK!
    add(list, CharSequence.class, StringBuffer.class);   // OK!
    add(list, CharSequence.class, StringBuilder.class);  // OK!
    
    add(list, CharSequence.class, Integer.class);        // NOT OK!
    // throws IllegalArgumentException:
    // java.lang.CharSequence is not assignable from java.lang.Integer
    
  3. # 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源代码是否有效。编译后的字节码不再包含这些信息,在字节码级别,列表将保存并接受ObjectObject的任何子类。因为Integer.classObject的子类,所以代码将一直运行,直到运行时向程序员抛出ClassCastException,因为它需要Class<String>实例