我只是想知道执行后会发生什么:
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() {
}
//...
}
如果EmptyList(null)
没有带参数的构造函数,如何调用EmptyList
。
请解释一下Collection
方法泛型T
成为EmptyList
的通用类型E
的过程?
答案 0 :(得分:3)
我只是想知道执行
后会发生什么
没有发生,因为有关泛型和类型参数的所有内容都会在编译时继续。
如果
EmptyList(null)
没有带参数的构造函数,如何调用EmptyList
这是不可能的,但是我没有看到JDK代码通过将null
传递给构造函数来创建空列表。不要相信反编译代码。
向我解释一下
Collection
方法泛型类型T
成为EmptyList
的泛型类型E
的过程如何?
T
是一个类型变量,捕获在emptyList()
的每个调用站点推断的类型。这个变量是&#34;传递&#34; (在静态类型分析期间)到emptyList
,以便返回值的类型为List<T>
。 E
是EmptyList
的类型参数的名称,此类型参数假定该特定类型实例化中的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。