Java中的泛型(Generics)
在Java中,泛型是JDK 5引入的一个非常重要的特性,它允许你在定义类、接口和方法时使用类型参数(type parameters)。使用泛型的主要好处是可以提供编译时的类型检查,减少类型转换的代码,并增强代码的可读性和可重用性。
0.通俗理解
想象一下,你有一个大箱子(类),这个箱子可以装很多东西,比如苹果、橙子或者玩具车。但是,每次你只能装一种东西,并且一旦你决定装苹果,那么这个箱子就只能装苹果了,不能装橙子或玩具车。这样虽然可以,但不是很灵活。
现在,有了“泛型”这个神奇的工具,你可以把这个箱子变成一个“万能箱”。这个“万能箱”在制造的时候并没有决定要装什么,而是留了一个“空位”给你来指定。你可以告诉它:“我要装苹果”,于是它就变成了装苹果的箱子;你也可以说:“我要装橙子”,它就变成了装橙子的箱子。甚至你还可以说:“我要装玩具车”,它也能满足你的需求。
这个“空位”就是泛型中的“类型参数”,你可以把它想象成一个可以插入不同类型的小插片。当你插入“苹果”插片时,箱子就变成了苹果箱;插入“橙子”插片时,就变成了橙子箱。
使用泛型的好处是,你可以编写更加通用的代码,而不需要为每种类型都写一遍。比如,你可以写一个通用的“万能箱”类,然后告诉它你要装什么,它就能自动适应。这样,你的代码就更加灵活、可重用,而且减少了出错的可能性。
所以,泛型就像是一个魔法箱子,让你的代码更加灵活、高效和通用。
1. 泛型类
泛型类就是使用类型参数声明的类。类型参数在类声明中定义,然后在整个类体中使用。
public class Box<T> {private T t;public void set(T t) { this.t = t; }public T get() { return t; }public static void main(String[] args) {Box<Integer> integerBox = new Box<>();integerBox.set(new Integer(10));System.out.println(integerBox.get() * 2); // 输出 20Box<String> stringBox = new Box<>();stringBox.set("Hello World");System.out.println(stringBox.get().toUpperCase()); // 输出 HELLO WORLD}
}
在上面的例子中,Box
类是一个泛型类,它使用了一个类型参数T
。然后我们在main
方法中创建了两种类型的Box
对象:Box<Integer>
和Box<String>
。
- 补充
你可以在使用泛型类时不指定泛型参数,但是这样做会失去泛型的一些优点,如类型安全和避免不必要的类型转换。 例如,你可以创建一个没有指定泛型参数的 ArrayList:ArrayList list = new ArrayList();
这个 list 可以添加任何类型的对象。但是,当你从 list 中获取元素时,你需要进行类型转换,因为 list.get(int index) 方法将返回一个 Object 类型的引用。 如果你在创建 ArrayList 时指定了泛型参数,如 ArrayList<String>
,那么这个 ArrayList 只能添加 String 类型的对象,尝试添加其他类型的对象将导致编译错误。同时,list.get(int index) 方法将返回一个 String 类型的引用,无需进行类型转换。 因此,虽然可以在使用泛型类时不指定泛型参数,但是为了获取泛型的优点(如类型安全和避免类型转换),建议在使用泛型类时指定泛型参数。
2. 泛型接口
和泛型类类似,泛型接口也是使用类型参数声明的接口。
public interface List<E> {void add(E element);E get(int index);// ... 其他方法
}
虽然上面的List
接口和Java标准库中的java.util.List
接口很相似,但这里只是为了示例。
3. 泛型方法
泛型方法是指使用类型参数声明的方法。类型参数在方法签名中定义,然后在方法体中使用。
public class GenericMethods {// 泛型方法,使用类型参数Tpublic static <T> void printArray(T[] array) {for (T item : array) {System.out.print(item + " ");}System.out.println();}public static void main(String[] args) {Integer[] intArray = {1, 2, 3, 4, 5};printArray(intArray); // 输出整数数组String[] stringArray = {"Hello", "World"};printArray(stringArray); // 输出字符串数组}
}
在上面的例子中,printArray
是一个泛型方法,它接受一个类型参数T
,并打印出该类型数组的所有元素。
4. 类型通配符(?)
在Java中,?
是一个特殊的类型通配符,它表示未知的类型。类型通配符主要用于泛型方法和泛型类的参数定义中。
List<?> wildcardList = new ArrayList<String>();
// wildcardList.add(new Object()); // 编译错误,因为不知道具体类型if (wildcardList instanceof List<String>) { // 注意:这种instanceof检查是不合法的// ...
}
不过,类型通配符还提供了两种限制:上界(extends)和下界(super)。
- 上界(extends):表示类型参数必须是某种类型的子类型。
List<? extends Number> numberList = new ArrayList<Integer>();
// numberList.add(new Double(3.1415)); // 编译错误
Number n = numberList.get(0); // 没问题
- 下界(super):表示类型参数必须是某种类型的超类型(或相同类型)。
List<? super Integer> intList = new ArrayList<Number>();
intList.add(new Integer(10)); // 没问题
// Number n = intList.get(0); // 编译错误,因为可能是Number的其他子类型
5. 泛型擦除(Type Erasure)
在Java中,泛型是通过类型擦除来实现的。这意味着在运行时,泛型信息会被擦除,所有的泛型类型都会变成原始类型(raw type)。这就是为什么你不能在运行时检查一个对象是否是某个泛型类型的实例(如上面的instanceof
检查)。但是,Java编译器会在编译时检查泛型的使用,确保类型安全。
总结
泛型是Java中一个非常强大的特性,它允许我们编写更加灵活和可重用的代码。通过使用泛型,我们可以减少类型转换的代码。