Java泛型案例怎么基础使用

wen java案例 27

Java泛型案例:从零掌握基础使用的完整指南

目录导读

  1. 什么是Java泛型?为什么要用它?
  2. 泛型基础语法:类、接口、方法
  3. 实战案例:泛型类与泛型方法
  4. 类型擦除与边界限定
  5. 常见问题与避坑指南
  6. 泛型让代码更优雅

什么是Java泛型?为什么要用它?

问:没有泛型之前,Java如何处理不同类型的集合?
答:早期Java使用Object类型来容纳任意对象,但每次取出都需要强制类型转换。

Java泛型案例怎么基础使用

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 手动强转,易出错

这种方式有两个致命缺陷:类型不安全(可混入不同类型)和代码冗余(频繁强转)。

泛型(Generics) 自JDK 5引入,允许在定义类、接口、方法时使用类型参数(如<T>),实现在编译时期检查类型,避免ClassCastException,核心思想是:类型的参数化


泛型基础语法:类、接口、方法

1 泛型类

public class Box<T> {
    private T item;
    public void setItem(T item) {
        this.item = item;
    }
    public T getItem() {
        return item;
    }
}

使用:Box<String> box = new Box<>();(菱形语法<>在JDK 7+可省略类型推断)

2 泛型接口

public interface Processor<T> {
    void process(T data);
}

实现时需指定类型:class StringProcessor implements Processor<String>

3 泛型方法

方法声明中独有类型参数,与类泛型无关:

public static <T> T getMiddle(T... args) {
    return args[args.length / 2];
}

调用:String mid = getMiddle("a", "b", "c");


实战案例:泛型类与泛型方法

案例1:简化集合操作(泛型类)

// 定义一个泛型栈
public class GenericStack<E> {
    private Object[] elements;
    private int size = 0;
    public GenericStack(int capacity) {
        elements = new Object[capacity]; // 泛型数组创建需特殊处理
    }
    public void push(E e) {
        elements[size++] = e;
    }
    @SuppressWarnings("unchecked")
    public E pop() {
        if (size == 0) throw new EmptyStackException();
        E result = (E) elements[--size];
        elements[size] = null; // 避免内存泄露
        return result;
    }
}

问:为什么不能直接new E[capacity]
答:因为类型擦除导致运行时E被擦除为Object,直接创建泛型数组不合法,解决方案:创建Object数组后强转(如上),或使用Array.newInstance()

案例2:泛型方法实现列表反转(泛型方法)

public static <T> void reverse(List<T> list) {
    int size = list.size();
    for (int i = 0; i < size / 2; i++) {
        T temp = list.get(i);
        list.set(i, list.get(size - 1 - i));
        list.set(size - 1 - i, temp);
    }
}

调用时自动推断类型:reverse(stringList);(无需显式传类型参数)

案例3:类型边界的使用(限定上限)

// 限定T必须是Number的子类
public static <T extends Number> double sumOfList(List<T> list) {
    double sum = 0;
    for (T t : list) {
        sum += t.doubleValue();
    }
    return sum;
}

extends关键字表示上界T可以是Number或其子类),调用时限制更严格。


类型擦除与边界限定

问:运行时泛型类型信息还存在吗?
答:Java通过类型擦除实现泛型,编译后,所有类型参数被替换为它们的上界(默认为Object)。

Box<String> box = new Box<>();

编译成:Box box = new Box();(并插入必要的强转)

边界限定的作用

  • <T extends Comparable<T>>:要求T实现Comparable,保证可比较
  • 通配符:List<? extends Number>表示列表元素是Number及其子类(协变),List<? super Integer>表示元素是Integer及其父类(逆变)

问:List和List是同一个类吗?
答:运行时都是List类,泛型参数在运行时不保留,所以List<String>不能作为List<Object>使用(类型擦除后仍是不同类型参数)。


常见问题与避坑指南

问题1:泛型不能用于静态变量/方法?

答:可以,但需要独立声明泛型参数,类的类型参数不能在静态方法中使用,因为静态成员不依赖于具体实例,正确写法:

public class Util {
    public static <T> T getValue(T t) { // 静态方法声明自己的<T>
        return t;
    }
}

问题2:何时使用通配符 vs 类型参数<T>

规则:如果方法中多次使用同一个类型参数(如返回值、参数列表中的多个位置),用<T>;若仅使用一次,用通配符更简洁。

  • void printList(List<?> list)(只读取元素)
  • void copy(List<? super T> dest, List<? extends T> src)(PECS原则:生产者用extends,消费者用super)

问题3:泛型数组创建异常

解决方案:使用ArrayList<T>代替T[],或使用@SuppressWarnings("unchecked")对强转进行压制。

问:为什么不推荐用泛型数组?
答:因为数组是协变且具体化的,而泛型是不可变且擦除的,两者语义冲突。

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

泛型让代码更优雅

掌握Java泛型的基础使用,意味着你能够写出类型安全、代码复用、可读性强的类库,从简单的Box<T>到复杂的<T extends Comparable<? super T>>,泛型是Java进阶的必修课,建议你动手写一个自定义泛型容器类(如Pair<T, U>),并尝试用泛型方法封装常用算法(如排序、查找),从而深入理解其精髓。

推荐练习:将学校里的“学生管理系统”用泛型重写,让一个Repository<T>能同时管理StudentTeacher等不同实体,体会泛型的强大。

抱歉,评论功能暂时关闭!