Skip to content

泛型

泛型基础

什么是泛型?

答案: 泛型(Generics)是JDK5引入的特性,允许在定义类、接口、方法时使用类型参数,提供编译时类型安全检查。

优点

  • 类型安全:编译时检查类型
  • 消除强制转换:不需要手动转型
  • 代码复用:一套代码适用多种类型

示例

java
// 不使用泛型
List list = new ArrayList();
list.add("hello");
list.add(123);
String str = (String) list.get(0);  // 需要强制转换
String str2 = (String) list.get(1);  // 运行时ClassCastException

// 使用泛型
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123);  // 编译错误
String str = list.get(0);  // 不需要转换

泛型的命名规范?

答案:

字母含义
EElement(元素)
TType(类型)
KKey(键)
VValue(值)
NNumber(数字)
?通配符

泛型类

如何定义泛型类?

答案:

java
// 单个类型参数
public class Box<T> {
    private T value;

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

    public T get() {
        return value;
    }
}

// 使用
Box<String> box1 = new Box<>();
box1.set("hello");
String value1 = box1.get();

Box<Integer> box2 = new Box<>();
box2.set(123);
Integer value2 = box2.get();

// 多个类型参数
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;
    }
}

// 使用
Pair<String, Integer> pair = new Pair<>("age", 20);

泛型接口

如何定义泛型接口?

答案:

java
// 泛型接口
public interface Generator<T> {
    T generate();
}

// 实现方式1:指定具体类型
public class StringGenerator implements Generator<String> {
    @Override
    public String generate() {
        return "hello";
    }
}

// 实现方式2:保留泛型
public class GenericGenerator<T> implements Generator<T> {
    @Override
    public T generate() {
        // 实现
        return null;
    }
}

// 使用
Generator<String> gen1 = new StringGenerator();
Generator<Integer> gen2 = new GenericGenerator<>();

泛型方法

如何定义泛型方法?

答案:

java
// 泛型方法
public class GenericMethod {

    // 静态泛型方法
    public static <T> T getMiddle(T... args) {
        return args[args.length / 2];
    }

    // 普通泛型方法
    public <T> void print(T value) {
        System.out.println(value);
    }

    // 多个类型参数
    public <K, V> void put(K key, V value) {
        System.out.println(key + " = " + value);
    }
}

// 使用
String middle = GenericMethod.getMiddle("a", "b", "c");

GenericMethod gm = new GenericMethod();
gm.print("hello");
gm.print(123);
gm.put("name", "Tom");

注意

  • 泛型方法的类型参数在返回值前声明
  • 泛型方法可以在普通类中定义
  • 泛型方法的类型参数与类的类型参数无关
java
public class Box<T> {
    private T value;

    // 这不是泛型方法,T是类的类型参数
    public void set(T value) {
        this.value = value;
    }

    // 这是泛型方法,E是方法的类型参数
    public <E> void print(E element) {
        System.out.println(element);
    }
}

泛型通配符

泛型通配符有哪些?

答案:

1. 无界通配符 <?>

java
public void print(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

// 可以接受任何类型的List
print(new ArrayList<String>());
print(new ArrayList<Integer>());

2. 上界通配符 <? extends T>

java
// 只能读取,不能写入(除了null)
public double sum(List<? extends Number> list) {
    double total = 0;
    for (Number num : list) {
        total += num.doubleValue();
    }
    return total;
}

// 可以接受Number及其子类
sum(new ArrayList<Integer>());
sum(new ArrayList<Double>());
// sum(new ArrayList<String>());  // 编译错误

3. 下界通配符 <? super T>

java
// 只能写入,读取只能用Object接收
public void addNumbers(List<? sger> list) {
    list.add(1);
    list.add(2);
    // Integer num = list.get(0);  // 编译错误
    Object obj = list.get(0);  // 只能用Object接收
}

// 可以接受Integer及其父类
addNumbers(new ArrayList<Integer>());
addNumbers(new ArrayList<Number>());
addNumbers(new ArrayList<Object>());

PECS原则是什么?

答案: PECS:Producer Extends, Consumer Super

  • Producer Extends:如果需要从集合中读取数据(生产者),使用<? extends T>
  • Consumer Super:如果需要向集合中写入数据(消费者),使用<? super T>
java
// 生产者:读取数据
public void copy(List<? extends Number> src, List<? super Number> dest) {
    for (Number num : src) {  // 从src读取
        dest.add(num);        // 向dest写入
    }
}

// 使用
List<Integer> src = Arrays.asList(1, 2, 3);
List<Number> dest = new ArrayList<>();
copy(src, dest);

泛型擦除

什么是泛型擦除?

答案: Java的泛型是伪泛型,在编译时会进行类型擦除,运行时不保留泛型信息。

擦除规则

  • 无界类型<T>擦除为Object
  • 有界类型<T extends Number>擦除为Number
  • 保留第一个边界
java
// 编译前
public class Box<T> {
    private T value;

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

    public T get() {
        return value;
    }
}

// 编译后(类型擦除)
public class Box {
    private Object value;

    public void set(Object value) {
        this.value = value;
    }

    public Object get() {
        return value;
    }
}

验证

java
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();

System.out.println(list1.getClass() == list2.getClass());  // true
// 运行时都是ArrayList,泛型信息被擦除

泛型擦除的影响?

答案:

1. 不能创建泛型数组

java
// 编译错误
List<String>[] array = new ArrayList<String>[10];

// 可以这样
List<?>[] array = new ArrayList<?>[10];
List<String>[] array = new ArrayList[10];

2. 不能使用instanceof

java
// 编译错误
if (obj instanceof List<String>) {
}

// 可以这样
if (obj instanceof List<?>) {
}

3. 不能创建泛型实例

java
public class Box<T> {
    // 编译错误
    public void create() {
        T obj = new T();
    }

    // 可以通过反射
    public T create(Class<T> clazz) throws Exception {
        return clazz.newInstance();
    }
}

4. 静态方法不能使用类的泛型

java
public class Box<T> {
    // 编译错误
    public static void print(T value) {
    }

    // 可以定义泛型方法
    public static <E> void print(E value) {
    }
}

5. 不能重载

java
public class Box {
    // 编译错误:擦除后方法签名相同
    public void print(List<String> list) {
    }

    public void print(List<Integer> list) {
    }
}

泛型最佳实践

泛型使用建议?

答案:

1. 优先使用泛型

java
// 不推荐
List list = new ArrayList();

// 推荐
List<String> list = new ArrayList<>();

2. 使用菱形语法

java
// JDK7之前
List<String> list = new ArrayList<String>();

// JDK7+
List<String> list = new ArrayList<>();

3. 返回值使用具体类型

java
// 不推荐
public List<?> getList() {
    return new ArrayList<String>();
}

// 推荐
public List<String> getList() {
    return new ArrayList<>();
}

4. 合理使用通配符

java
// 只读取
public void print(List<? extends Number> list) {
}

// 只写入
public void add(List<? super Integer> list) {
}

5. 避免原始类型

java
// 不推荐
List list = new ArrayList();

// 推荐
List<Object> list = new ArrayList<>();

练习题

  1. 泛型的类型擦除是什么?有什么影响?
  2. List<?>List<Object> 的区别?
  3. 为什么不能创建泛型数组?
  4. 如何获取泛型的实际类型?

Released under the MIT License.