Skip to content

泛型

泛型的出现使得类、接口、方法可以接受任意类型的参数,提高了代码的复用性,解决类型安全问题,提供了编译时类型检查,不会因为将对象置于某个容器中而失去其类型。在 Java 集合框架中,泛型被广泛应用,如 ArrayList<T>HashMap<K, V> 等。

在没有出现泛型之前,Java 也提供了对 Object 引用进行向上转型及向下转型的操作,使得对 Object 的引用可以“任意化”操作。但某些强制类型转换的错误也许不会被编译器捕捉,而在运行后出现异常,并且类型转换是需要消耗资源的。可见强制类型转换是不安全的,因此在 Java 1.5 中引入泛型机制。

例如在下面示例的写法中存在类型安全问题,容易导致 ClassCastException

java
ArrayList list = new ArrayList();
list.add("Hello");
list.add(123); // 可能导致运行时错误

String str = (String) list.get(0); // 需要手动强制转换

泛型分类

根据泛型的使用范围,泛型可以分为泛型类、泛型接口和泛型方法。

泛型类

Java 泛型可以用来定义类,使其能够适用于不同的数据类型。

语法:

java
public class ClassName<T> {
    // ...
}

定义泛型类时,一般类型名称使用 T 表示,而容器的元素则使用 E 表示。

示例:

java
public class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}
java
Box<String> b1 = new Box<String>();
box.setValue("Hello");
String str = b1.getValue(); // 不需要强制转换

Box<Integer> b2 = new Box<Integer>();
box.setValue(123);
int num = b2.getValue(); // 不需要强制转换

泛型接口

泛型接口的定义与泛型类类似,只是在接口名后面添加泛型声明。

语法:

java
public interface InterfaceName<T> {
    // ...
}

示例:

java
public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}
java
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;
    }
}
java
Pair<String, Integer> pair = new OrderedPair<>("Alice", 25);

泛型方法

泛型方法可以在方法中使用泛型类型,使得方法能够适用于不同的数据类型。

语法:

java
public <T> void methodName(T t) {
    // ...
}

示例:

java
public <T> void printArray(T[] array) {
    for (T element : array) {
        System.out.println(element);
    }
}
java
Integer[] intArray = {1, 2, 3};
String[] strArray = {"Hello", "World"};

printArray(intArray);
printArray(strArray);

泛型的常规用法

声明多个泛型

java
public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }
}
java
Pair<String, Integer> pair = new Pair<>("Alice", 25);

声明数组类型

java
public class Box<T> {
    private T[] value;

    public void setValue(T[] value) {
        this.value = value;
    }

    public T[] getValue() {
        return value;
    }
}
java
Box<String> box = new Box<>();
box.setValue(new String[] {"Alice", "Bob", "Charlie"});

声明容器元素

使用 KV 代表容器中的键值对:

java
public class MutiMap<K, V> {
    private Map<K, List<V>> map = new HashMap<>();

    public void put(K key, V value) {
        List<V> list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }
        list.add(value);
    }

    public List<V> get(K key) {
        return map.get(key);
    }
}
java
MutiMap<String, Integer> map = new MutiMap<>();
map.put("Alice", 25);

List<Integer> list = map.get("Alice");

25021806.png

表 常用的被泛型化的集合类

泛型通配符

未知类型通配符

通配符 ? 表示未知类型,用于增强泛型的灵活性。通配符主要用于泛型类、泛型方法和泛型集合,使代码可以适用于不同类型的泛型对象。

通配符 ? 可以在泛型方法或泛型类的参数中表示任意类型,但不能用于泛型类的声明。例如下面的示例中使用普通泛型和通配符的对比:

  • 使用具体类型的泛型

    java
    List<String> list = new ArrayList<>();
    list.add("Hello");
  • 使用通配符

    List<?> 可以引用任何类型的泛型 List<T>,但不能添加元素(除了 null),因为编译器无法确定 ? 的实际类型。

    java
    List<?> list = new ArrayList<String>();
    list = new ArrayList<Integer>(); // 允许不同泛型类型的对象

上界通配符

上界通配符(? extends UpperBound):? extends UpperBound 限定泛型只能是 UpperBoundUpperBound 的子类,主要用于读取数据。

25022001.png

图 上界通配符的使用示意图

示例:

java
public static void printNumbers(List<? extends Number> list) {
    for (Number num : list) {
        System.out.println(num);
    }
}
java
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);

printNumbers(intList);  // ✔ 允许
printNumbers(doubleList); // ✔ 允许

特点:

  1. 允许 List<Integer>List<Double>Number 子类列表作为参数传递。
  2. 不能向 list 添加新元素(null 除外),因为无法确定具体类型:
    java
    list.add(100);  // ❌ 编译错误
    list.add(null); // ✔ 允许
  3. 适用于只读操作,如遍历、计算等。

下界通配符

下界通配符(? super LowerBound):? super LowerBound 限定泛型只能是 LowerBoundLowerBound 的父类,主要用于写入数据。

25022002.png

图 下界通配符的使用示意图

示例:

java
public static void addNumbers(List<? super Integer> list) {
    list.add(10);
    list.add(20);
}
java
List<Number> numberList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();

addNumbers(numberList);  // ✔ 允许
addNumbers(objectList);  // ✔ 允许

特点:

  1. 允许向 list 添加 Integer 或其子类对象。
  2. 不能读取成 Integer,因为 list 可能是 List<Object>,只能赋值给 Object
    java
    Object obj = list.get(0); // ✔ 允许
    Integer num = list.get(0); // ❌ 编译错误
  3. 适用于写入操作,如添加数据。

泛型擦除

泛型擦除是 Java 泛型实现中的一种机制,旨在确保泛型代码与旧版本的非泛型代码兼容。Java 在编译时使用泛型擦除来处理泛型类型信息,使得泛型代码在运行时与非泛型代码表现一致。

工作原理

  1. 类型参数替换:

    • 无界类型参数(如 <T>)会被替换为 Object
    • 有界类型参数(如 <T extends Number>)会被替换为边界类型(如 Number)。
  2. 类型检查和桥接方法:

    • 编译器在编译时进行类型检查,确保类型安全。
    • 生成桥接方法以保持多态性。

示例

无界类型参数

java
public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

编译后:

java
public class Box {
    private Object item;

    public void setItem(Object item) {
        this.item = item;
    }

    public Object getItem() {
        return item;
    }
}

有界类型参数

java
public class Box<T extends Number> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

编译后:

java
public class Box {
    private Number item;

    public void setItem(Number item) {
        this.item = item;
    }

    public Number getItem() {
        return item;
    }
}

泛型擦除的影响

  1. 运行时类型信息丢失:泛型类型信息在编译后被擦除,运行时无法获取具体类型参数。
  2. 类型转换:编译器自动插入类型转换代码,确保类型安全。
  3. 无法创建泛型类型的实例:由于类型信息在运行时不可用,无法直接实例化泛型类型。
  4. 无法创建泛型数组:数组需要明确的类型信息,而泛型擦除导致无法创建泛型数组。

示例:类型转换

java
Box<Integer> box = new Box<>();
box.setItem(123);
Integer item = box.getItem(); // 编译器插入类型转换

编译后:

java
Box box = new Box();
box.setItem(123);
Integer item = (Integer) box.getItem(); // 自动插入的类型转换

桥接方法

桥接方法用于保持多态性,确保子类重写父类方法时类型安全。

java
public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

public class IntegerBox extends Box<Integer> {
    @Override
    public void setItem(Integer item) {
        super.setItem(item);
    }

    @Override
    public Integer getItem() {
        return super.getItem();
    }
}

编译后,IntegerBox 会生成桥接方法:

java
public class IntegerBox extends Box {
    public void setItem(Integer item) {
        super.setItem(item);
    }

    // 桥接方法
    public void setItem(Object item) {
        setItem((Integer) item);
    }

    public Integer getItem() {
        return (Integer) super.getItem();
    }

    // 桥接方法
    public Object getItem() {
        return getItem();
    }
}
编程洪同学服务平台是一个广泛收集编程相关内容和资源,旨在满足编程爱好者和专业开发人员的需求的网站。无论您是初学者还是经验丰富的开发者,都可以在这里找到有用的信息和资料,我们将助您提升编程技能和知识。
专业开发
高端定制
售后无忧
站内资源均为本站制作或收集于互联网等平台,如有侵权,请第一时间联系本站,敬请谅解!本站资源仅限于学习与参考,严禁用于各种非法活动,否则后果自行负责,本站概不承担!