【Java基础整理】Java泛型详解

24 阅读7分钟

Java泛型详解

1. 泛型概述

什么是泛型

泛型(Generic)就是规范数据类型的一种机制,用于解决类型不匹配的安全问题。

泛型的作用

  • 类型安全:让程序安全问题从运行时期体现到了编译时期
  • 强制限制:强制客观的限制类型,避免类型转换异常
  • 编译优化:编译完成后Java系统会把限定规则删除
  • 集合应用:在集合框架中广泛应用

泛型的优势

示例对比:

// 不使用泛型(容易出错)
ArrayList list = new ArrayList();
list.add("Hello");
list.add(123);  // 可以添加不同类型
String str = (String) list.get(1);  // 运行时异常

// 使用泛型(类型安全)
ArrayList<String> list = new ArrayList<String>();
list.add("Hello");
// list.add(123);  // 编译时错误,无法通过编译
String str = list.get(0);  // 无需强制转换

意义: 只能存储String类型的元素,从而避免了不同类型的混合存储。

2. 泛型的定义语法

基本语法

定义一个泛型需要在类、接口或方法名后加 <E>,此类或方法中就可以使用泛型了。

参数说明:

  • E 代表所要规范的类型(Element的缩写)
  • 可以使用其他字母:T(Type)、K(Key)、V(Value)等

3. 泛型定义在类上

历史背景

  • JDK早期版本:当类中要操作的引用数据类型不确定时,使用Object来完成扩展
  • JDK 1.5后:引入泛型来完成类型规范

泛型类定义

最简单的泛型类示例:

class GenericDemo<E> {
    private E t;
    
    public void setT(E t) {
        this.t = t;
    }
    
    public E getT() {
        return t;
    }
}

泛型类使用

创建泛型类实例:

// 定义String类型的泛型类
GenericDemo<String> demo = new GenericDemo<String>();

此时,t 元素的类型为String。

验证泛型类型:

System.out.println(GenericDemo.class.getField("t").getType().toString());
// 输出结果为:String

完整的泛型类示例

public class Box<T> {
    private T content;
    
    public Box(T content) {
        this.content = content;
    }
    
    public T getContent() {
        return content;
    }
    
    public void setContent(T content) {
        this.content = content;
    }
    
    @Override
    public String toString() {
        return "Box{content=" + content + "}";
    }
}

// 使用示例
public class TestGenericClass {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>("Hello Generic");
        Box<Integer> intBox = new Box<>(42);
        
        System.out.println(stringBox.getContent());  // Hello Generic
        System.out.println(intBox.getContent());     // 42
    }
}

4. 泛型定义在接口上

泛型接口定义

泛型接口的定义与泛型类类似:

public interface GenericInterface<T> {
    T process(T input);
    void display(T data);
}

泛型接口实现

方式一:实现类不指定泛型类型

public class GenericImplementation<T> implements GenericInterface<T> {
    @Override
    public T process(T input) {
        // 具体实现
        return input;
    }
    
    @Override
    public void display(T data) {
        System.out.println("Data: " + data);
    }
}

方式二:实现类指定具体类型

public class StringImplementation implements GenericInterface<String> {
    @Override
    public String process(String input) {
        return input.toUpperCase();
    }
    
    @Override
    public void display(String data) {
        System.out.println("String Data: " + data);
    }
}

5. 泛型定义在方法上

使用场景

如果泛型定义在类上,那么类中的方法操作的类型就固定为泛型类型了。为了让每个方法操作的类型不同,我们可以将泛型定义在每个方法上。

泛型方法语法

基本语法:

public <E> void show(E t) {
    System.out.println("Value: " + t);
}

语法要点:

  • 泛型参数 <E> 必须在返回值类型之前
  • 方法体中可以使用泛型参数 E

泛型方法示例

public class GenericMethodDemo {
    
    // 泛型方法:交换数组中两个元素的位置
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    // 泛型方法:打印数组元素
    public static <E> void printArray(E[] array) {
        for (E element : array) {
            System.out.println(element);
        }
    }
    
    // 泛型方法:查找最大值
    public static <T extends Comparable<T>> T findMax(T[] array) {
        T max = array[0];
        for (T element : array) {
            if (element.compareTo(max) > 0) {
                max = element;
            }
        }
        return max;
    }
}

使用示例:

public class TestGenericMethod {
    public static void main(String[] args) {
        String[] strings = {"apple", "banana", "cherry"};
        Integer[] numbers = {3, 1, 4, 1, 5};
        
        // 使用泛型方法
        GenericMethodDemo.printArray(strings);
        GenericMethodDemo.printArray(numbers);
        
        // 交换元素
        GenericMethodDemo.swap(strings, 0, 2);
        GenericMethodDemo.printArray(strings);  // cherry, banana, apple
        
        // 查找最大值
        String maxString = GenericMethodDemo.findMax(strings);
        Integer maxNumber = GenericMethodDemo.findMax(numbers);
        System.out.println("Max string: " + maxString);
        System.out.println("Max number: " + maxNumber);
    }
}

💡 注意: 泛型类和类中泛型方法是不冲突的,可以同时存在。

6. 泛型定义在静态方法上

为什么静态方法需要独立定义泛型

因为泛型类是规范对象的类型,而静态方法属于类而不是对象,所以静态方法不能访问类上定义的泛型。

静态泛型方法语法

如果静态方法要使用泛型,必须将泛型定义在静态方法上。

语法要点:

  • 泛型定义应该在 static 关键字后面
  • 在返回值类型之前
public static <E> void method(E t) {
    // 方法体
}

静态泛型方法示例

public class StaticGenericDemo {
    
    // 静态泛型方法:比较两个对象是否相等
    public static <T> boolean isEqual(T obj1, T obj2) {
        return obj1.equals(obj2);
    }
    
    // 静态泛型方法:创建数组
    @SuppressWarnings("unchecked")
    public static <T> T[] createArray(int length) {
        return (T[]) new Object[length];
    }
    
    // 静态泛型方法:合并两个数组
    public static <T> T[] mergeArrays(T[] array1, T[] array2) {
        @SuppressWarnings("unchecked")
        T[] result = (T[]) new Object[array1.length + array2.length];
        
        System.arraycopy(array1, 0, result, 0, array1.length);
        System.arraycopy(array2, 0, result, array1.length, array2.length);
        
        return result;
    }
}

使用示例:

public class TestStaticGeneric {
    public static void main(String[] args) {
        // 使用静态泛型方法
        boolean result = StaticGenericDemo.isEqual("Hello", "Hello");
        System.out.println("Are equal: " + result);  // true
        
        String[] arr1 = {"A", "B"};
        String[] arr2 = {"C", "D"};
        String[] merged = StaticGenericDemo.mergeArrays(arr1, arr2);
        
        for (String s : merged) {
            System.out.println(s);  // A B C D
        }
    }
}

7. 泛型限定(通配符)

通配符类型

通配符说明用途
<E>类型明确明确指定具体类型
<?>不明确类型占位符、通配符,代表此泛型通用

7.1 无界通配符

语法: <?>

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

7.2 向上限定(上边界)

语法: <? extends E>

含义: 此泛型类型不明确,但是此泛型限定为E或者E的子类。

// 向上限定示例
public class BoundedGeneric {
    
    // 只接受Number及其子类
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number number : list) {
            sum += number.doubleValue();
        }
        return sum;
    }
}

使用示例:

public class TestUpperBound {
    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
        
        // 可以传入Integer和Double,因为它们都是Number的子类
        System.out.println(BoundedGeneric.sumOfList(intList));    // 6.0
        System.out.println(BoundedGeneric.sumOfList(doubleList)); // 6.6
        
        // List<String> stringList = Arrays.asList("a", "b", "c");
        // BoundedGeneric.sumOfList(stringList);  // 编译错误
    }
}

7.3 向下限定(下边界)

语法: <? super E>

含义: 定义不明确泛型,接受E类型或者E的父类型。

// 向下限定示例
public class LowerBoundGeneric {
    
    // 只接受Integer及其父类
    public static void addIntegers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
        list.add(3);
    }
}

使用示例:

public class TestLowerBound {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = new ArrayList<>();
        List<Object> objList = new ArrayList<>();
        
        // 可以传入Integer及其父类
        LowerBoundGeneric.addIntegers(intList);  // OK
        LowerBoundGeneric.addIntegers(numList);  // OK
        LowerBoundGeneric.addIntegers(objList);  // OK
        
        // List<Double> doubleList = new ArrayList<>();
        // LowerBoundGeneric.addIntegers(doubleList);  // 编译错误
    }
}

7.4 PECS原则

PECS (Producer Extends Consumer Super) 原则:

  • Producer Extends:如果你需要从集合中读取数据,使用 ? extends
  • Consumer Super:如果你需要向集合中写入数据,使用 ? super
public class PECSExample {
    
    // Producer: 从集合中读取数据
    public static <T> void copy(List<? extends T> src, List<? super T> dest) {
        for (T item : src) {
            dest.add(item);  // 读取src,写入dest
        }
    }
}

8. 泛型使用注意事项

8.1 类型擦除

Java的泛型是通过类型擦除实现的:

List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();

// 运行时类型相同
System.out.println(stringList.getClass() == intList.getClass());  // true

8.2 不能创建泛型数组

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

// 正确方式
@SuppressWarnings("unchecked")
List<String>[] stringLists = new List[10];

8.3 静态成员不能使用类的泛型

public class GenericClass<T> {
    // private static T staticField;  // 编译错误
    // public static T getStaticField() { return staticField; }  // 编译错误
    
    // 静态方法需要自己定义泛型
    public static <E> E getStaticValue(E value) {
        return value;
    }
}

9. 泛型的实际应用

9.1 自定义泛型集合

public class MyList<E> {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_CAPACITY = 10;
    
    public MyList() {
        elements = new Object[DEFAULT_CAPACITY];
    }
    
    public void add(E element) {
        if (size == elements.length) {
            resize();
        }
        elements[size++] = element;
    }
    
    @SuppressWarnings("unchecked")
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        return (E) elements[index];
    }
    
    public int size() {
        return size;
    }
    
    private void resize() {
        int newCapacity = elements.length * 2;
        elements = Arrays.copyOf(elements, newCapacity);
    }
}

9.2 泛型工具类

public class GenericUtils {
    
    // 泛型方法:安全的类型转换
    @SuppressWarnings("unchecked")
    public static <T> T cast(Object obj, Class<T> type) {
        if (type.isInstance(obj)) {
            return (T) obj;
        }
        throw new ClassCastException("Cannot cast " + obj.getClass() + " to " + type);
    }
    
    // 泛型方法:创建列表
    @SafeVarargs
    public static <T> List<T> listOf(T... elements) {
        return new ArrayList<>(Arrays.asList(elements));
    }
    
    // 泛型方法:查找元素
    public static <T> int indexOf(T[] array, T target) {
        for (int i = 0; i < array.length; i++) {
            if (Objects.equals(array[i], target)) {
                return i;
            }
        }
        return -1;
    }
}

10. 总结

泛型的优势

  1. 编译时类型检查:提前发现类型不匹配问题
  2. 消除类型转换:不需要显式的类型转换
  3. 提高代码重用:一个泛型类可以处理多种类型
  4. 增强可读性:代码更清晰地表达设计意图

泛型的应用场景

  • 集合框架:ArrayList、HashMap等
  • 工具类:提供通用的操作方法
  • API设计:提供类型安全的接口
  • 框架开发:Spring、Hibernate等框架大量使用泛型

使用建议

  1. 优先使用泛型:新代码应该使用泛型而不是原始类型
  2. 合理使用通配符:根据PECS原则选择合适的通配符
  3. 注意类型擦除:理解泛型的实现机制
  4. 避免过度复杂:不要创建过于复杂的泛型声明

泛型是Java中重要的特性,正确使用泛型可以让代码更加安全、清晰和易于维护。掌握泛型的各种用法和限制,是成为优秀Java开发者的必备技能。


本文详细介绍了Java泛型的概念、语法、应用场景和注意事项,希望对Java学习者深入理解和使用泛型有所帮助。