Java泛型入门:为什么用泛型?简单理解与使用

为什么要用Java泛型?

在Java中,我们经常需要处理各种数据集合,比如存储学生信息、商品信息等。如果不使用泛型,直接用Object类型存储数据,虽然灵活,但会带来两个主要问题:类型不安全强制类型转换繁琐

举个例子,没有泛型时,我们可能这样写:

// 没有泛型的集合,存储Object类型
List list = new ArrayList();
list.add("张三");
list.add(20); // 错误地添加了整数类型

// 取数据时需要强制转换,容易出错
String name = (String) list.get(0); // 正常
Integer age = (Integer) list.get(1); // 正常
String wrong = (String) list.get(1); // 运行时ClassCastException!

这里的问题在于,list可以存储任意类型的数据,编译器无法提前检查类型是否匹配,运行时一旦类型不匹配就会抛出异常。

泛型是什么?

泛型(Generics)是Java 5引入的特性,允许我们将类型作为参数传递给类、接口或方法,从而实现类型安全代码复用。简单来说,泛型就是“参数化类型”,例如List<String>表示“只能存储String类型的列表”。

如何使用泛型?

1. 泛型类

泛型类是最基础的泛型应用,定义时在类名后用<T>声明类型参数(T是占位符,可自定义名称,如EKV等)。

示例:定义一个通用的“盒子”类

class Box<T> {
    private T content; // 用T作为类型参数

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }
}

使用泛型类

// 存储字符串的盒子
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String str = stringBox.getContent(); // 无需强制转换,直接返回String

// 存储整数的盒子
Box<Integer> intBox = new Box<>();
intBox.setContent(100);
Integer num = intBox.getContent(); // 直接返回Integer

通过泛型,Box可以灵活存储任意类型的数据,且编译器会在编译时检查类型是否匹配,避免运行时错误。

2. 泛型接口

泛型接口与泛型类类似,在接口名后声明类型参数,实现类需指定具体类型。

示例:定义一个“生成器”接口

interface Generator<T> {
    T generate(); // 接口方法返回T类型
}

// 实现泛型接口(指定具体类型)
class StringGenerator implements Generator<String> {
    @Override
    public String generate() {
        return "Generic String";
    }
}

// 使用
Generator<String> generator = new StringGenerator();
String result = generator.generate(); // 返回"Generic String"

3. 泛型方法

泛型方法是指方法本身带有类型参数的方法,可在普通类或泛型类中定义。

示例:定义一个通用的“打印”方法

class Util {
    // 泛型方法:接受任意类型的数组,返回第一个元素
    public static <T> T getFirstElement(T[] array) {
        if (array.length == 0) return null;
        return array[0];
    }
}

// 使用
String[] strArray = {"A", "B", "C"};
String firstStr = Util.getFirstElement(strArray); // 返回"A"

Integer[] intArray = {1, 2, 3};
Integer firstInt = Util.getFirstElement(intArray); // 返回1

4. 泛型集合(最常用)

Java标准库中的集合类(如ArrayListHashMap)都支持泛型,这是泛型的典型应用场景。

示例:使用泛型集合

// ArrayList存储String类型
List<String> names = new ArrayList<>();
names.add("Alice"); // 正确
// names.add(123); // 编译错误:类型不匹配,只能添加String
names.forEach(System.out::println); // 遍历输出:Alice

// HashMap存储键值对(键:String,值:Integer)
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90); // 正确
scores.put("Bob", 85);
// scores.put("Charlie", "95"); // 编译错误:值必须是Integer
Integer aliceScore = scores.get("Alice"); // 无需强制转换,直接返回Integer

通配符:让泛型更灵活

通配符<?>用于处理泛型集合的类型范围,常见的有两种:

  1. 上界通配符<? extends T>
    表示集合中的元素只能是T或其子类(T为上界)。
    示例:允许存储Number及其子类(IntegerDouble等)
   List<? extends Number> numbers = new ArrayList<Integer>();
   numbers.add(new Integer(10)); // 错误!不允许添加元素
   Number num = numbers.get(0); // 正确,返回Number类型
  1. 下界通配符<? super T>
    表示集合中的元素只能是T或其父类(T为下界)。
    示例:允许存储Integer及其父类(NumberObject等)
   List<? super Integer> ints = new ArrayList<Object>();
   ints.add(new Integer(10)); // 正确
   Object obj = ints.get(0); // 正确,返回Object类型

泛型的核心优势

  1. 类型安全:编译时检查类型,避免运行时ClassCastException
  2. 消除强制转换:无需手动强转,代码更简洁。
  3. 代码复用:通过类型参数实现一套代码处理多种类型,无需重复编写逻辑。

注意事项

  1. 不能使用基本类型:泛型类型必须是引用类型(如int需用Integerdouble需用Double)。
   List<int> nums = new ArrayList<>(); // 错误!需用List<Integer>
  1. 泛型不支持继承List<String>List<Object>是平级的,不能互相赋值。
   List<String> strList = new ArrayList<>();
   List<Object> objList = strList; // 错误!泛型类型不可继承
  1. 类型擦除:运行时泛型类型会被擦除(T被替换为Object),因此泛型类中无法直接使用T的实例化(如new T())。

总结

泛型是Java中解决类型安全和代码复用的重要特性,通过参数化类型让集合和类更灵活、更安全。掌握泛型的核心在于理解类型参数泛型类/方法/接口以及通配符的使用场景,多通过简单示例练习,就能快速上手。

通过本文的示例和讲解,希望你能初步理解Java泛型的价值和用法。从简单的集合泛型开始,逐步尝试泛型类和方法,很快就能熟练运用泛型提升代码质量啦!

小夜