编译时类型安全检测机制
,这意味着可以在编译期间检测到非法的类型。泛型的使用减少了程序中的强制类型转换和运行时错误的可能性。
class Box
。public void method(T param)。
List[]
来绕过这个限制,但这种做法不安全并会引发警告。public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
// 实例化一个泛型类
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
Box<String> stringBox = new Box<>();
stringBox.set("Hello World");
// 定义一个泛型接口
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
// 实现泛型接口
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
// 使用泛型接口
Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
Pair<String, String> p2 = new OrderedPair<>("hello", "world");
// 定义一个泛型方法
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
// 使用泛型方法
Pair<Integer, String> p1 = new OrderedPair<>(1, "apple");
Pair<Integer, String> p2 = new OrderedPair<>(2, "pear");
boolean same = Util.compare(p1, p2);
public <U extends Number> void inspect(U u){
System.out.println("T: " + u.getClass().getName());
}
// 使用有界类型参数
inspect(123);
inspect(12.34);
public void printBoxContent(Box<?> box){
System.out.println("The box contains: " + box.get());
}
// 使用通配符类型
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
Box<String> stringBox = new Box<>();
stringBox.set("Hello World");
printBoxContent(integerBox);
printBoxContent(stringBox);
在引入泛型之前,Java 集合框架没有类型检查,所以所有对象都被当做 Object 类型存储
以下是一个没有使用泛型的例子:
import java.util.ArrayList;
import java.util.List;
public class NonGenericExample {
public static void main(String[] args) {
List integerList = new ArrayList();
// 添加整数到集合
integerList.add(new Integer(10)); // 自动装箱为 Integer
integerList.add(new Integer(20));
// 尝试添加一个字符串到整数列表
// 编译器不会报错,运行时也不会出错
integerList.add("thirty"); // 这是不安全的操作
// 获取元素并进行类型转换
Integer firstNumber = (Integer) integerList.get(0);
Integer secondNumber = (Integer) integerList.get(1);
// 这里尝试将第三个元素转换为 Integer,但它实际上是一个 String
// 这会引发 ClassCastException
try {
Integer thirdNumber = (Integer) integerList.get(2);
} catch (ClassCastException e) {
System.out.println("Error: " + e.getMessage());
}
// 输出正确的整数值
System.out.println("First number: " + firstNumber);
System.out.println("Second number: " + secondNumber);
}
}
在上面的代码中,我们创建了一个非泛型的 ArrayList,并向其中添加了两个 Integer 对象和一个 String 对象。由于没有类型检查,ArrayList 允许我们添加任何类型的对象,而不会在编译时报错。但是,当我们试图将 String 对象转换为 Integer 时,程序会在运行时抛出 ClassCastException,因为这是一个无效的转换。
引入泛型后,我们可以在编译时期进行类型检查,从而避免这种类型安全问题。下面是使用泛型的代码示例:
import java.util.ArrayList;
import java.util.List;
public class GenericExample {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<Integer>();
// 添加整数到集合
integerList.add(10); // 自动装箱为 Integer
integerList.add(20);
// 下面的代码会在编译时报错,防止了运行时错误
// integerList.add("thirty"); // 编译错误
// 获取元素时不需要进行类型转换
Integer firstNumber = integerList.get(0);
Integer secondNumber = integerList.get(1);
// 输出正确的整数值
System.out.println("First number: " + firstNumber);
System.out.println("Second number: " + secondNumber);
}
}
在这个泛型版本的例子中,如果尝试将一个不是 Integer 类型的对象添加到 integerList 中,编译器将会报错,从而保证了类型安全
。这使得代码更安全,更清晰,也更容易维护。
类型擦除是 Java 编译器应用的过程,它允许泛型代码与不支持泛型的旧有 Java 代码兼容
。在这个过程中,编译器将泛型类型参数替换为它们的限定边界(如果存在),或者其他情况下替换为 Object。这意味着在编译后的 Java 字节码中,所有的泛型类型信息都会丢失。
Java在5.0版本中引入了泛型,这使得开发者能够在编写集合类(如List、Map等)时指定集合中元素的类型,例如 List
。在引入泛型之前,所有的集合中的对象都是 Object 类型,这需要显式的类型转换,并且可能导致运行时错误。
为了保证向后兼容,Java的泛型是通过类型擦除来实现的。这意味着泛型信息只在编译阶段有效,一旦代码被编译,所有泛型类型参数都会被擦除,替换为它们的限定边界类型
(如果有的话)或者Object
。这也意味着在运行时,我们无法获取到泛型的类型参数信息。
public class GenericErasure<T> {
public void doSomething(T t) {
System.out.println(t);
}
}
// 在编译后的class文件中,geString 和 geInteger 类型实际上是相同的
// 它们都被擦除成了 GenericErasure 类型,使用的是 Object 类型,如下:
public class GenericErasure {
public void doSomething(Object t) {
System.out.println(t);
}
}
如上,编译后 泛型 T 变成了 Object,泛型类型信息丢失,转变成了 Object,使得和不支持泛型的java代码一致。
在Java泛型中,“限定边界”(Bounded Type)是指对可以使用的泛型的类型参数进行限制。限定边界可以是一个特定的类,或者是一个满足特定接口的任何类型。使用限定边界可以确保传递给泛型类型的类型参数满足某些基本要求,这样就可以在类或方法内安全地调用定义在边界类型上的方法。
限定边界有两种类型:
这里是针对上界限定的一个例子:
假设你有一个类 Animal 和两个子类 Dog 和 Cat。你想要写一个方法,这个方法可以接受 Animal 的任何子类的列表。你可以通过使用限定边界来实现:
public class Animal {
public void feed() {
// ...
}
}
public class Dog extends Animal {
// ...
}
public class Cat extends Animal {
// ...
}
public class Main {
public static void feedAnimals(List<? extends Animal> animals) {
for (Animal a : animals) {
a.feed();
}
}
public static void main(String[] args) {
List<Dog> dogs = Arrays.asList(new Dog(), new Dog());
List<Cat> cats = Arrays.asList(new Cat(), new Cat());
feedAnimals(dogs); // 正确:dogs 是 Animal 的子类的 List
feedAnimals(cats); // 正确:cats 是 Animal 的子类的 List
}
}
在这个例子中,feedAnimals 方法的参数 animals 使用了限定边界 ? extends Animal,这意味着这个参数可以是 List、List、List 或任何 Animal 子类的列表。
当你没有具体的限定边界时,你可以简单地使用 Object 类型,因为在Java中所有的类都是 Object 的子类。如果你的泛型没有指定上界或下界,它其实隐式的被限定为 Object。例如:
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
在这个例子中,printList 方法接受一个未知类型的列表,并且由于每个对象最终都继承自 Object 类,我们可以安全地将列表中的每个元素作为 Object 输出。
总结:
限定边界用来指定泛型类型参数必须继承自特定的父类,或者实现特定的接口,从而在编译时期就保证了类型安全。
如果没有特定的限定边界,泛型类型默认是 Object。
泛型的作用:增加代码的灵活性和类型安全性。它使得类和方法可以在编译时指定操作的数据类型,从而减少类型转换错误,提高代码的可读性和可维护性。。
泛型擦除:保证了泛型代码与不支持泛型的旧有 Java 代码兼容