文章目录
- [1. 泛型的基本概念](#1. 泛型的基本概念)
- [2. 泛型的优点](#2. 泛型的优点)
- [3. 泛型的使用](#3. 泛型的使用)
-
- [3.1 泛型类](#3.1 泛型类)
- [3.2 泛型方法](#3.2 泛型方法)
- [3.3 泛型接口](#3.3 泛型接口)
- [4. 限制泛型](#4. 限制泛型)
-
- [4.1 上界(Upper Bound)](#4.1 上界(Upper Bound))
- [4.2 下界(Lower Bound)](#4.2 下界(Lower Bound))
- [5. 通配符](#5. 通配符)
-
- [5.1 上界通配符(Upper Bound Wildcard)](#5.1 上界通配符(Upper Bound Wildcard))
- [5.2 下界通配符(Lower Bound Wildcard)](#5.2 下界通配符(Lower Bound Wildcard))
- [6. 特殊用例](#6. 特殊用例)
-
- [6.1 `T extends Comparable<T>`](#6.1
T extends Comparable<T>
) - [6.2 `List<E> extends Collection<E>`](#6.2
List<E> extends Collection<E>
) - [6.3 `List<? extends Shape>` 和 `List<T extends Shape>`的区别](#6.3
List<? extends Shape>
和List<T extends Shape>
的区别)
- [6.1 `T extends Comparable<T>`](#6.1
- [7. 类型擦除](#7. 类型擦除)
-
- [7.1 类型擦除的过程](#7.1 类型擦除的过程)
- [7.2 示例](#7.2 示例)
-
- **替代方案**
-
- [(1) **使用`Object[]`并强制转型(需谨慎)**](#(1) 使用
Object[]
并强制转型(需谨慎)) - [(2) **通过反射创建数组**](#(2) 通过反射创建数组)
- [(3) **优先使用集合类**](#(3) 优先使用集合类)
- [(1) **使用`Object[]`并强制转型(需谨慎)**](#(1) 使用
泛型(Generics)是 Java 中的一种特性,允许在类、接口和方法中定义类型和参数,从而实现参数的类型化。泛型的主要目的是提高代码的重用性和类型安全性。
1. 泛型的基本概念
- 类型参数 :泛型允许你在定义类、接口或方法时使用类型参数(如
T
、E
、K
、V
等),这些参数在使用时会被具体的类型替代。 - 类型安全:使用泛型可以在编译时检查类型,减少运行时错误。例如,使用泛型的集合类可以确保集合中只包含特定类型的对象。
2. 泛型的优点
- 代码重用:通过使用泛型,可以编写通用的算法和数据结构,而不需要为每种数据类型编写重复的代码。
- 类型安全 :编译器会检查类型,减少了类型转换错误和
ClassCastException
的风险。 - 可读性:泛型使得代码更具可读性,明确了方法和类的预期类型。
3. 泛型的使用
3.1 泛型类
定义一个泛型类,可以使用类型参数:
java
class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 使用泛型类
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String item = stringBox.getItem(); // 返回 String 类型
3.2 泛型方法
定义一个泛型方法,可以在方法中使用类型参数:
java
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
// 使用泛型方法
Integer[] intArray = {1, 2, 3};
printArray(intArray); // 可以打印 Integer 数组
3.3 泛型接口
定义一个泛型接口:
java
interface Pair<K, V> {
public K getKey();
public V getValue();
}
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> pair = new OrderedPair<>("One", 1);
4. 限制泛型
4.1 上界(Upper Bound)
使用 extends
关键字指定上界,表示类型参数必须是某个类的子类或实现某个接口。
java
public static <T extends Number> void printNumbers(List<T> numbers) {
for (T number : numbers) {
System.out.println(number);
}
}
在这个例子中,T
必须是 Number
类或其子类(如 Integer
、Double
等)。
4.2 下界(Lower Bound)
使用 super
关键字指定下界,表示类型参数必须是某个类的父类。
java
public static <T> void addNumbers(List<? super T> list, T number) {
list.add(number);
}
在这个例子中,list
可以是 T
的任何父类的列表,例如 Number
或 Object
。
5. 通配符
通配符用于表示未知类型,通常用 ?
表示。通配符分为上界通配符和下界通配符。
可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符?
来处理
5.1 上界通配符(Upper Bound Wildcard)
使用 ? extends Type
表示可以接受 Type
的子类。
java
public class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Message<T> { // 设置泛型
private T message ;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
class TestDemo {
public static void main(String[] args) {
Message<Apple> appleMessage = new Message<>() ;
appleMessage.setMessage(new Apple());
fun(appleMessage);
Message<Banana> bananaMessage = new Message<>() ;
bananaMessage.setMessage(new Banana());
fun(bananaMessage);
}
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<? extends Fruit> temp){
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
System.out.println(temp.getMessage());
}
}
在这个例子中,temp
可以是任何 Fruit
的子类列表,适合只读操作。
5.2 下界通配符(Lower Bound Wildcard)
使用 ? super Type
表示可以接受 Type
的父类。
java
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Plate<T> {
private T plate ;
public T getPlate() {
return plate;
}
public void setPlate(T plate) {
this.plate = plate;
}
}
class TestDemo {
public static void main(String[] args) {
Plate<Fruit> fruitPlate = new Plate<>();
fruitPlate.setPlate(new Fruit());
fun(fruitPlate);
Plate<Food> foodPlate = new Plate<>();
foodPlate.setPlate(new Food());
fun(foodPlate);
}
public static void fun(Plate<? super Fruit> temp){
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
temp.setPlate(new Apple());//这个是Fruit的子类
temp.setPlate(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getPlate(); 不能接收,这里无法确定是哪个父类
System.out.println(temp.getPlate());//只能直接输出
}
}
在这个例子中,temp
可以是 Fruit
的任何父类的列表,适合写入操作。
6. 特殊用例
6.1 T extends Comparable<T>
- 定义 :
T extends Comparable<T>
是 Java 泛型中的一种类型边界,用于指定类型参数T
必须实现Comparable<T>
接口。 - 用途 :确保类型
T
的对象可以进行比较,通常用于排序和比较操作。
java
public class Sorter {
public static <T extends Comparable<T>> void sort(List<T> list) {
// 排序逻辑
}
}
- 比较功能 :通过实现
Comparable<T>
接口,类的对象可以使用compareTo
方法进行比较。 - 灵活性 :可以对任何实现了
Comparable
接口的类型进行排序。
6.2 List<E> extends Collection<E>
- 定义 :
List<E> extends Collection<E>
表示List
接口是Collection
接口的子接口,E
是元素的类型参数。 - 用途:提供一个有序的集合,允许重复元素,并支持通过索引访问。
6.3 List<? extends Shape>
和 List<T extends Shape>
的区别
List<? extends Shape>
:- 只读:适合只读取数据,不能添加元素。
- 灵活性 :可以接受任何
Shape
的子类列表。
List<T extends Shape>
:- 读写:适合读取和写入数据,可以添加元素。
- 类型安全 :在方法内部可以安全地处理具体类型
T
的对象。
7. 类型擦除
类型擦除是 Java 泛型实现中的一个重要概念,它是指在编译时,Java 编译器会将泛型类型替换为它的原始类型,从而使得泛型在运行时不再保留类型信息。
7.1 类型擦除的过程
- 编译时处理 :在编译过程中,所有的泛型类型参数(例如
T
、E
等)会被替换为它们的原始类型。通常情况下,这个原始类型是Object
,但在某些情况下(如有上界限制时),可能会被替换为指定的类型。 - 运行时行为:由于类型信息在运行时被擦除,泛型的类型安全性主要依赖于编译时的检查。这意味着在运行时,无法直接获取泛型的具体类型。
7.2 示例
考虑以下泛型类:
java
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
在编译时,Box<T>
会被转换为 Box<Object>
,因此在运行时,所有的 T
都会被视为 Object
。这意味着:
- 在
Box<Integer>
和Box<String>
中,item
都被视为Object
。 - 当从
Box<Integer>
中取出元素时,必须进行类型转换。
#8. T[] ts = new T[5]
那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]吗?
关键原因 | 说明 |
---|---|
类型擦除 | 泛型类型T 在编译后被擦除,JVM无法确定数组的具体类型。 |
数组的运行时类型 | 数组需要明确的运行时类型信息以进行安全检查。 |
类型安全性 | 禁止泛型数组可避免潜在的ArrayStoreException 和类型污染问题。 |
替代方案
若需要泛型数组,可以通过以下方式实现:
(1) 使用Object[]
并强制转型(需谨慎)
java
T[] arr = (T[]) new Object[5]; // 编译警告:未检查的强制转换
但这种方式存在类型不安全风险,需确保后续操作不会插入错误类型的元素。
(2) 通过反射创建数组
java
T[] arr = (T[]) Array.newInstance(clazz, 5); // clazz是Class<T>类型
通过传递Class<T>
参数保留类型信息,绕开类型擦除的限制。
(3) 优先使用集合类
更推荐使用ArrayList<T>
等集合类,它们天然支持泛型且类型安全。