讲解java中的泛型

88 阅读3分钟

🌟 1. 泛型的本质

泛型就是 “在定义时不写死具体类型,而是在用的时候再指定类型”

想象一个可以放任何东西的盒子 Box

  • 以前(没有泛型)Java只知道它能放 Object,取出来还要自己转换类型;
  • 现在(有泛型)Java让你定义 Box<T>,在用的时候给它一个类型:Box<String>Box<Integer>

这样,编译器能检查类型安全,防止把奇怪的东西放进盒子,还能省去繁琐的类型转换。


🌟 2. 泛型的细节

✅ 2.1 泛型类

定义:在类名后面加 <T>,T 是类型形参:

public class Box<T> {
    private T content;
    public void set(T content) {
        this.content = content;
    }
    public T get() {
        return content;
    }
}

使用:

Box<String> box1 = new Box<>();
box1.set("Hello");        // 编译器知道T是String
String str = box1.get();  // 不需要强制类型转换

Box<Integer> box2 = new Box<>();
box2.set(123);            // 编译器知道T是Integer
Integer num = box2.get();

🔑 重点

  • Box<String>Box<Integer>是两种不同的泛型实例。
  • 泛型检查发生在编译期,不会影响运行时性能。

✅ 2.2 泛型方法

在方法返回类型前加 <T> 表示这是个泛型方法:

public class Utils {
    public static <T> T identity(T item) {
        return item;
    }
}

使用:

String s = Utils.identity("abc"); // 编译器推断T=String
Integer i = Utils.identity(100);  // 编译器推断T=Integer

✅ 2.3 泛型接口

可以用在接口上,让实现类指定泛型:

public interface Generator<T> {
    T next();
}

public class StringGenerator implements Generator<String> {
    public String next() {
        return "hello";
    }
}

🌟 3. 泛型的高级用法:通配符

有时候你并不关心具体是什么类型,而是想说:

  • “我能接受任何类型”,就用<?>
  • “我能接受某个类型的子类”,用<? extends T>
  • “我能接受某个类型的父类”,用<? super T>

例子:

public static void printList(List<?> list) {
    for (Object elem : list) {
        System.out.println(elem);
    }
}

public static void processNumbers(List<? extends Number> nums) {
    // 可以读取Number及其子类
    Number n = nums.get(0);
    // nums.add(123); // ❌ 不允许写入,因为可能是 List<Double>,加Integer不安全
}

public static void addIntegers(List<? super Integer> ints) {
    ints.add(42); // ✅ 可以写入Integer及其子类
}

🌟 4. 泛型常见错误

1️⃣ 不能用基本类型做泛型参数

// List<int> list; // ❌ 不允许
List<Integer> list; // ✅ 必须用包装类

2️⃣ 不能直接创建泛型数组

// T[] array = new T[10]; // ❌ 不允许
Object[] array = new Object[10]; // ✅

🌟 5. 实际例子演示:一个泛型工具类

定义一个交换数组中两个位置元素的泛型方法

public class ArrayUtils {
    public static <T> void swap(T[] arr, int i, int j) {
        T tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

使用:

public class Test {
    public static void main(String[] args) {
        String[] words = {"apple", "banana", "cherry"};
        ArrayUtils.swap(words, 0, 2);
        // words现在是 {"cherry", "banana", "apple"}

        Integer[] numbers = {1, 2, 3};
        ArrayUtils.swap(numbers, 1, 2);
        // numbers现在是 {1, 3, 2}
    }
}

🔑 这个例子显示:

  • 不管是String[]还是Integer[]swap()都可以用;
  • 编译器在使用时知道T是具体类型,保证类型安全。

🌟 6. 泛型类型擦除

  • 泛型只存在于编译期,编译后字节码里泛型信息会被擦除

  • 比如:

    List<String> list1 = new ArrayList<>();
    List<Integer> list2 = new ArrayList<>();
    System.out.println(list1.getClass() == list2.getClass()); // true
    
  • 运行时 JVM 看到的都是ArrayList,并不区分StringInteger


✅ 小结一句话

泛型=写代码时不指定具体类型,用的时候告诉编译器是什么类型,让编译器帮你做类型检查,避免强制类型转换出错。