我一直在尝试正确理解 Java 泛型。所以在这个探索中,我遇到了一个原则“广告中的真实原则”,我试图用简单的语言来理解这一点。
广告真理原则:数组的具体化类型必须是其静态类型擦除的子类型。
我已经编写了示例代码 .java 和 .class 文件如下。请查看代码并解释哪部分(在代码中)指定/指示上述语句的哪一部分。
我已经写了评论,我认为我不应该在这里写代码描述。
public class ClassA {
//when used this method throws exception
//java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
public static <T> T[] toArray(Collection<T> collection) {
//Array created here is of Object type
T[] array = (T[]) new Object[collection.size()];
int i = 0;
for (T item : collection) {
array[i++] = item;
}
return array;
}
//Working fine , no exception
public static <T> T[] toArray(Collection<T> collection, T[] array) {
if (array.length < collection.size()) {
//Array created here is of correct intended type and not actually Object type
//So I think , it inidicates "the reified type of an array" as type here lets say String[]
// is subtype of Object[](the erasure ), so actually no problem
array = (T[]) Array.newInstance(array.getClass().getComponentType(), collection.size());
}
int i = 0;
for (T item : collection) {
array[i++] = item;
}
return array;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B");
String[] strings = toArray(list);
// String[] strings = toArray(list,new String[]{});
System.out.println(strings);
}
}
Run Code Online (Sandbox Code Playgroud)
请尽量用通俗的语言解释。请指出我错的地方。带有更多注释的更正代码表示赞赏。
谢谢你们
我将 Java 泛型和集合称为本书,本书的作者称为作者。
我会不止一次支持这个问题,因为这本书在解释 IMO 的原则方面做得很差。
广告真理原则: 数组的具体化类型必须是其静态类型擦除的子类型。
进一步称为原则。
原理有何帮助?
什么是静态类型?
应该叫做引用类型。
提供A和B是类型,在下面的代码中
A ref = new B();
A是ref(B是 的动态类型ref)的静态类型。学术术语。
数组的具体化类型是什么?
具体化意味着在运行时可用的类型信息。数组被认为是可具体化的,因为 VM 知道它们的组件类型(在运行时)。
在 中arr2 = new Number[30], 的具体化类型arr2是Number[]具有组件类型的数组类型Number。
什么是类型的擦除?
应该称为运行时类型。
类型参数的虚拟机视图(运行时视图)。提供的T是一个类型参数,下面代码的运行时视图
<T extends Comparable<T>> void stupidMethod(T[] elems) {
T first = elems[0];
}
Run Code Online (Sandbox Code Playgroud)
将会
void stupidMethod(Comparable[] elems) {
Comparable first = elems[0];
}
Run Code Online (Sandbox Code Playgroud)
这使得Comparable运行时类型为T. 为什么Comparable?因为那是 的最左边的界限T。
代码应该暗示分配给数组类型的引用。无论是左值或右值应该包括一个类型参数。
例如提供的T是一个类型参数
T[] a = (T[])new Object[0]; // type parameter T involved in lvalue
Run Code Online (Sandbox Code Playgroud)
或者
String[] a = toArray(s); // type parameter involved in rvalue
// where the signature of toArray is
<T> T[] toArray(Collection<T> c);
Run Code Online (Sandbox Code Playgroud)
如果左值或右值中不涉及类型参数,则该原则不相关。
<T extends Number> void stupidMethod(List<T>elems) {
T[] ts = (T[]) new Number[0];
}
Run Code Online (Sandbox Code Playgroud)
Q1:ts引用的数组的具体化类型是什么?
A1:数组创建提供了答案:Number使用new. Number[].
Q2:什么是静态类型ts?
A2 :T[]
Q3:什么是静态类型的擦除ts?
A3:为此我们需要擦除T. 鉴于这T extends Number是有界的,T的擦除类型是其最左边的边界 - Number。现在我们知道了 forT的擦除类型,那么for的擦除类型ts是Number[]
Q4 : 是否遵循了原则?
A4:重申问题。是A1的亚型A3?即是Number[]子类型Number[]?是 => 这意味着遵循原则。
<T extends Number> void stupidMethod(List<T>elems) {
T[] ts = (T[]) new Object[0];
}
Run Code Online (Sandbox Code Playgroud)
Q1:ts引用的数组的具体化类型是什么?
A1:数组创建使用new,组件类型是Object,因此Object[]。
Q2:什么是静态类型ts?
A2 :T[]
Q3:什么是静态类型的擦除ts?
A3:为此我们需要擦除T. 鉴于这T extends Number是有界的,T的擦除类型是其最左边的边界 - Number。现在我们知道了 forT的擦除类型,那么for的擦除类型ts是Number[]
Q4 : 是否遵循了原则?
A4:重申问题。是A1的亚型A3?即是Object[]子类型Number[]?否 => 这意味着没有遵循原则。
期望在运行时抛出异常。
鉴于提供数组的方法
<T> T[] toArray(Collection<T> c){
return (T[]) new Object[0];
}
Run Code Online (Sandbox Code Playgroud)
客户代码
List<String> s = ...;
String[] arr = toArray(s);
Run Code Online (Sandbox Code Playgroud)
Q1:提供方法返回的数组的具体化类型是什么?
A1:为此,您还需要查看提供方法以了解它是如何初始化的 - new Object[...]。这意味着该方法返回的数组的具体化类型是Object[]。
Q2:什么是静态类型arr?
A2 :String[]
Q3:什么是静态类型的擦除ts?
A3:不涉及类型参数。擦除后的类型与静态类型相同String[]。
Q4 : 是否遵循了原则?
A4:重申问题。是A1的亚型A3?即是Object[]子类型String[]?否 => 这意味着没有遵循原则。
期望在运行时抛出异常。
鉴于提供数组的方法
<T> T[] toArray(Collection<T> initialContent, Class<T> clazz){
T[] result = (T[]) Array.newInstance(clazz, initialContent);
// Copy contents to array. (Don't use this method in production, use Collection.toArray() instead)
return result;
}
Run Code Online (Sandbox Code Playgroud)
客户代码
List<Number> s = ...;
Number[] arr = toArray(s, Number.class);
Run Code Online (Sandbox Code Playgroud)
Q1:提供方法返回的数组的具体化类型是什么?
A1:使用反射创建的数组,组件类型为从客户端收到。答案是Number[]。
Q2:什么是静态类型arr?
A2 :Number[]
Q3:什么是静态类型的擦除ts?
A3:不涉及类型参数。擦除后的类型与静态类型相同Number[]。
Q4 : 是否遵循了原则?
A4:重申问题。是A1的亚型A3?即是Number[]子类型Number[]?是 => 这意味着遵循原则。
在此吐槽。广告中的真相可能意味着销售您所说的销售内容。
在
lvalue = rvalue我们有rvalue作为提供者和lvalue接收者。可能是作者将提供者视为广告商。
参考上述实施例三的提供方法,
<T> T[] toArray(Collection<T> c){
return (T[]) new Object[0];
}
Run Code Online (Sandbox Code Playgroud)
方法签名
<T> T[] toArray(Collection<T> c);
Run Code Online (Sandbox Code Playgroud)
可以理解为广告:给我一个Longs的列表,我会给你一个Longs的数组。
然而查看方法体,实现表明该方法不是真实的,因为它创建和返回的数组是一个Objects数组。
因此toArray,示例 3 中的方法在于其营销活动。
在示例 4 中,提供方法是真实的,因为签名中的语句(给我一个集合及其类型参数作为类文字,我会给你一个具有该组件类型的数组)与正文中发生的事情相匹配。
示例 3 和 4 具有充当广告的方法签名。
示例 1 和 2 没有显式广告(方法签名)。广告和规定是交织在一起的。
尽管如此,我想不出更好的名字来命名这个原则。这是一个地狱般的名字。
由于使用了诸如静态类型和擦除类型之类的术语,我认为该原则的陈述不必要地含糊不清。分别在擦除后使用引用类型和运行时类型/类型将使 Java 外行(就像您真正的那样)更容易掌握。
作者说这本书是关于 Java 泛型的最好的 [0]。我认为这意味着他们针对的受众范围很广,因此他们介绍的原则的更多示例将非常有帮助。
[0] https://youtu.be/GOMovkQCYD4?t=53
可以这样想:
T[] array = (T[]) new Object[collection.size()];创建一个新数组。由于语言设计的原因,运行时 的类型T是未知的。在您的示例中,您知道事实T是String,但从虚拟机的角度来看,事实T是Object。所有转换操作都发生在调用方法中。
这样就创建了toArray一个数组Object[]。类型参数或多或少是语法糖,对创建的字节码没有任何影响。
那么为什么不能将对象数组转换为字符串数组呢?
让我们举个例子:
void methodA(){
Object[] array = new Object[10];
array[0]=Integer.valueOf(10);
array[1]=Object.class;
array[2]=new Object();
array[3]="Hello World";
methodB((String[])array);
}
void methodB(String[] stringArray){
String aString=stringArray[1]; //This is not a String, but Object.class!
}
Run Code Online (Sandbox Code Playgroud)
如果你可以转换一个数组,你会说“我之前添加的所有元素都是有效的子类型”。但由于您的数组的类型为Object,因此虚拟机无法保证该数组在所有情况下始终包含有效的子类型。
methodB认为它处理一个字符串数组,但实际上该数组确实包含非常不同的类型。
相反的方法也不起作用:
void methodA(){
String[] array = new String[10];
array[0]="Hello World";
methodB((Object[])array);
//Method B had controll over the array and could have added any object, especially a non-string!
System.out.println(array[1]);
}
void methodB(Object[] oArray){
oArray[1]=Long.valueOf(2);
}
Run Code Online (Sandbox Code Playgroud)
我希望这能有所帮助。
编辑:再次阅读您的问题后,我认为您正在混合以下内容:
Object可以将任何 java 对象放入数组中,但如果您创建一个数组,则Number值必须是类型Number(Long, Double, ...)。总而言之,这句话相当琐碎。或者我也不明白;)编辑2:事实上,您可以将数组转换为您想要的任何类型。也就是说,您可以将数组转换为任何类型,就像将任何类型转换为 String ( String s=(String)Object.class;) 一样。特别是你可以将 a 转换String[]为 an Object[],反之亦然。正如我在示例中指出的,此操作会引入大量潜在错误,因为读取/写入数组可能会失败。我想不出在任何情况下强制转换数组都是一个好的决定。在某些情况下(例如通用实用程序类),它似乎是一个很好的解决方案,但如果您发现自己处于想要转换数组的情况,我仍然建议您过度考虑设计。感谢 newacct 指出强制转换操作本身是有效的。