java 泛型语法如何帮助避免类型转换?
How does java generics syntax help avoid type casting?
下面是代码,
import java.util.List;
import java.util.ArrayList;
public class Dummy {
public static void main(String[] args) {
List<String> lst = new ArrayList<String>();
lst.add("a string");
lst.add("another string");
String s = lst.get(0);
} //end main
}
调用构造函数 new ArrayList<String>();
时,会创建 Object
类型的数组。
..
lst
持有 Object[0]
个数组。
因此,如果 Object
类型的数组由构造函数创建,javac
如何在该语句 String s = lst.get(0);
中看不到类型转换问题,尽管在调用时使用了通用语法构造函数?
从 class 更远的地方:
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
@SuppressWarnings
告诉编译器您确信您正在执行未经检查的转换。由于所有其他操作(例如 get(int)
)都使用类型参数 E
,这将不安全类型处理限制在特定位置,实现者可以确保正确处理转换。
这是一些非通用代码:
public class MyList {
List myData = new ArrayList();
public void add(Object ob) {
myData.add(ob);
}
public Object getAtIndex(int ix) {
return myData.get(ix);
}
}
此代码将允许您将任何类型的对象存储到您的 MyList 实例中即使MyList 的契约指定对象必须都是同一类型。此外,如果您使用 MyList 实例存储 String 实例,则必须在检索它们时手动将它们转换为 String。
String myString = (String) myList.get(1);
上面的 MyList class 不是类型安全的。如果将 String 实例以外的对象存储到您的 MyList 实例中,则上述赋值语句很可能会因 ClassCastException 而失败(这可能会在 运行 时发生,没有任何投诉)。
这是一个通用的 MyList class:
public class MyList<T> {
List<T> myData = new ArrayList<>();
public void add(T ob) {
myData.add(ob);
}
public T getAtIndex(int ix) {
return myData.get(ix);
}
}
现在,编译器保证只能从 MyList 实例中添加和检索 T
个实例。因为编译器保证总是返回 T
个实例,所以您可以使用这样的语法而无需任何手动转换:
String myString = myList.get(1);
泛化的 MyList class 现在是类型安全的。编译器不允许您将 T
实例以外的任何东西存储到您的 MyList 实例中,这保证在 运行 时不会发生 ClassCastExceptions。如果您检查字节码,您会发现编译器已经自动进行了转换。
Java 中的泛型是一种仅编译时现象。在字节码中,上面 MyList class 中对 T
的所有引用都被替换为 Object
。这个过程被称为"type erasure"。重要的是要记住 Java 中的类型安全仅由编译器提供。如果您的程序编译时没有任何错误 AND 且没有任何警告,那么您的程序、泛型和所有程序都是类型安全的。然而,编译后的程序几乎没有保留任何关于通用参数的信息。
下面是代码,
import java.util.List;
import java.util.ArrayList;
public class Dummy {
public static void main(String[] args) {
List<String> lst = new ArrayList<String>();
lst.add("a string");
lst.add("another string");
String s = lst.get(0);
} //end main
}
调用构造函数 new ArrayList<String>();
时,会创建 Object
类型的数组。
lst
持有 Object[0]
个数组。
因此,如果 Object
类型的数组由构造函数创建,javac
如何在该语句 String s = lst.get(0);
中看不到类型转换问题,尽管在调用时使用了通用语法构造函数?
从 class 更远的地方:
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
@SuppressWarnings
告诉编译器您确信您正在执行未经检查的转换。由于所有其他操作(例如 get(int)
)都使用类型参数 E
,这将不安全类型处理限制在特定位置,实现者可以确保正确处理转换。
这是一些非通用代码:
public class MyList {
List myData = new ArrayList();
public void add(Object ob) {
myData.add(ob);
}
public Object getAtIndex(int ix) {
return myData.get(ix);
}
}
此代码将允许您将任何类型的对象存储到您的 MyList 实例中即使MyList 的契约指定对象必须都是同一类型。此外,如果您使用 MyList 实例存储 String 实例,则必须在检索它们时手动将它们转换为 String。
String myString = (String) myList.get(1);
上面的 MyList class 不是类型安全的。如果将 String 实例以外的对象存储到您的 MyList 实例中,则上述赋值语句很可能会因 ClassCastException 而失败(这可能会在 运行 时发生,没有任何投诉)。
这是一个通用的 MyList class:
public class MyList<T> {
List<T> myData = new ArrayList<>();
public void add(T ob) {
myData.add(ob);
}
public T getAtIndex(int ix) {
return myData.get(ix);
}
}
现在,编译器保证只能从 MyList 实例中添加和检索 T
个实例。因为编译器保证总是返回 T
个实例,所以您可以使用这样的语法而无需任何手动转换:
String myString = myList.get(1);
泛化的 MyList class 现在是类型安全的。编译器不允许您将 T
实例以外的任何东西存储到您的 MyList 实例中,这保证在 运行 时不会发生 ClassCastExceptions。如果您检查字节码,您会发现编译器已经自动进行了转换。
Java 中的泛型是一种仅编译时现象。在字节码中,上面 MyList class 中对 T
的所有引用都被替换为 Object
。这个过程被称为"type erasure"。重要的是要记住 Java 中的类型安全仅由编译器提供。如果您的程序编译时没有任何错误 AND 且没有任何警告,那么您的程序、泛型和所有程序都是类型安全的。然而,编译后的程序几乎没有保留任何关于通用参数的信息。