参数化如何在Collections.EmptyList类中工作?

时间:2015-09-20 12:41:26

标签: java generics

我只是想知道执行后会发生什么: List list = Collections.<String>emptyList();

这是收藏代码:

public static final List EMPTY_LIST = new Collections.EmptyList(null);
public static final <T> List<T> emptyList() {
        return EMPTY_LIST;
    }

private static class EmptyList<E> extends AbstractList<E> 
                                  implements RandomAccess, Serializable {

    private EmptyList() {
    }

    //...
}
  1. 如果EmptyList(null)没有带参数的构造函数,如何调用EmptyList

  2. 请解释一下Collection方法泛型T成为EmptyList的通用类型E的过程?

3 个答案:

答案 0 :(得分:3)

  

我只是想知道执行

后会发生什么

没有发生,因为有关泛型和类型参数的所有内容都会在编译时继续。

  

如果EmptyList(null)没有带参数的构造函数,如何调用EmptyList

这是不可能的,但是我没有看到JDK代码通过将null传递给构造函数来创建空列表。不要相信反编译代码。

  

向我解释一下Collection方法泛型类型T成为EmptyList的泛型类型E的过程如何?

T是一个类型变量,捕获在emptyList()的每个调用站点推断的类型。这个变量是&#34;传递&#34; (在静态类型分析期间)到emptyList,以便返回值的类型为List<T>EEmptyList的类型参数的名称,此类型参数假定该特定类型实例化中的T值。这与ArrayList<E> E String new ArrayList<String>()时的display: inline-block;非常相似。

答案 1 :(得分:3)

除了@MarkoTopolnik答案,我还可以解释为什么反编译器会将null传递给构造函数参数。

当你创建没有显式构造函数的私有类时,它的默认no-arg构造函数在字节码内生成为private:

private static class EmptyList<E> {
    private EmptyList() {
    }
    ...
}

这样就无法从其他类中实例化类(JVM不允许这样做)。但它确实在Collections类中实例化了:

public static final List EMPTY_LIST = new EmptyList<>();

从JVM的角度来看,没有像“嵌套类”这样的东西。 EmptyList类只是同一个包中的另一个类(名为Collections$EmptyList)。如果javac编译器生成对私有构造函数的直接调用,那么JVM将在Collections类初始化期间抛出异常。为了解决这个问题,javac编译器引入了一个带有包私有访问的附加构造函数:

private static class EmptyList<E> {
    private EmptyList() {
    }

    EmptyList(Collections$1 ignore) {
        this();
    }
    ...
}

这个人工构造函数只有一个参数可以区别于现有的构造函数。为了避免可能的参数冲突,还会生成一个额外的人工类Collections$1!这个类的目的只是成为这种人工构造函数的参数。它从未实例化甚至初始化。

所以最后你可以从另一个类(在EmptyList包中)实例化java.util来调用这个新的构造函数:

public static final List EMPTY_LIST = new EmptyList<>((Collections$1)null);

这是它在字节码中的样子。似乎反编译器不够聪明,无法检测到这种情况,并在输出中删除了人工构造函数参数(实际上它应该非常简单)。

答案 2 :(得分:1)

您的反编译代码:

public static final List EMPTY_LIST = new Collections.EmptyList(null);

实际代码

@SuppressWarnings("unchecked")
public static final List EMPTY_LIST = new EmptyList<>();

在实际代码中,构造函数没有参数。

您的反编译代码:

public static final <T> List<T> emptyList() {
    return EMPTY_LIST;
}

实际代码:

@SuppressWarnings("unchecked")
public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}

那么,T类型如何成为E?作弊。 EMPTY_LIST原始类型,他们明确地将其强制转换为List<T>,并在创建原始EMPTY_LIST和此显式广告时抑制警告。< / p>

实际上,这是非常安全的,因为列表是空的并且将保持为空。

故事的道德:不要相信反编译器,特别是在涉及泛型时,因为对于类型擦除,反编译器很难知道原始源代码中实际发生了什么。如果你想查看各种开源Java项目的来源,包括OpenJDK,请使用有用的(虽然很慢)GrepCode website