Java中的泛型详解

Java 的泛型(Generics)是一个非常重要的概念,它使得代码可以更灵活和类型安全。通过使用泛型,程序员可以编写可以处理多种数据类型的类、接口和方法,而不必在运行时进行类型转换。


一、什么是泛型?

泛型允许我们定义类、接口或方法时使用类型参数。换句话说,泛型使得数据类型可以延迟到实例化时再指定。泛型提供了编译时的类型检查,从而避免了在运行时进行不安全的强制类型转换。

为什么需要泛型?

在 Java 1.5 之前,集合类(如 ArrayListHashMap 等)只能存储 Object 类型的对象,这意味着你必须手动进行类型转换,容易引发类型安全问题

例子(没有泛型的情况):
ArrayList list = new ArrayList(); 
list.add("Hello"); 
list.add(123); // 编译没有问题 
String s = (String) list.get(0); // 必须进行类型转换 
int n = (Integer) list.get(1); // 如果出错,会在运行时抛出 ClassCastException 

 

由于 ArrayList 只能存储 Object 类型,程序员在使用时需要进行手动类型转换,如果类型不对,可能会在运行时引发错误。为了提高类型安全性代码的可读性,Java 引入了泛型。

使用泛型:
ArrayList list = new ArrayList<>();
list.add("Hello"); 
// list.add(123); // 编译时会报错 
String s = list.get(0); // 不需要强制类型转换 

 

二、泛型的语法

泛型类

我们可以为一个类定义泛型,以便这个类能够处理不同类型的数据。

示例:定义一个简单的泛型类
public class Box { 
private T item; 
public void set(T item) { this.item = item; } 
public T get() { return this.item; } 
} 

 

在这个类中,T 是一个类型参数,代表类中可以持有任何类型的数据。在使用 Box 类时,可以指定 T 的具体类型。

使用泛型类:
Box stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get()); // 输出: Hello

Box integerBox = new Box<>();
integerBox.set(123);
System.out.println(integerBox.get()); // 输出: 123

 

在使用时,我们通过指定类型参数,确保 Box 只能存储指定类型的数据,比如 StringInteger。这样就可以避免类型转换和运行时异常。

泛型方法

除了泛型类,还可以定义泛型方法,这样的方法可以根据调用时传递的参数类型进行相应的操作。

示例:定义泛型方法
public class GenericMethodTest {
    public static  void printArray(T[] inputArray) {
        for (T element : inputArray) {
            System.out.println(element);
        }
    }
}
使用泛型方法:
GenericMethodTest.printArray(new Integer[]{1, 2, 3});
GenericMethodTest.printArray(new String[]{"A", "B", "C"});

这里的 printArray 方法是一个泛型方法, 代表类型参数,方法可以处理不同类型的数组。

泛型接口

接口也可以使用泛型。泛型接口的定义与泛型类类似。

示例:定义泛型接口
public interface Pair {
    K getKey();
    V getValue();
}

public class OrderedPair implements Pair {
    private K key;
    private V value;

    public OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
}
使用泛型接口:
Pair pair = new OrderedPair<>("One", 1);
System.out.println(pair.getKey() + ": " + pair.getValue()); // 输出: One: 1

三、泛型的边界与通配符

1. 上界通配符

当我们使用泛型时,有时希望限定类型参数必须是某个类或其子类。这时可以使用上界通配符 ,表示参数类型必须是 TT 的子类。

示例:
public void printNumbers(List list) {
    for (Number num : list) {
        System.out.println(num);
    }
}

这个方法允许 List 中的元素是 Number 类或其子类(如 IntegerDouble)。 确保 list 的元素类型是 Number 及其子类。

2. 下界通配符

下界通配符 表示参数类型必须是 TT 的父类。它通常用于写入数据的泛型结构中。

示例:
public void addNumbers(List list) {
    list.add(10);
    list.add(20);
}

这个方法允许 List 中的元素类型是 Integer 或其父类(如 NumberObject)。 确保 list 至少能够存储 Integer 类型的数据。

3. 无界通配符

无界通配符 表示未知的类型。它适用于当我们对泛型类型没有具体要求时的情况。

示例:
public void printList(List list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

这个方法接受任何类型的 List 表示 List 中的元素可以是任何类型。

四、泛型的限制

  1. 泛型类型不能是基本类型:泛型只能用于对象类型,不能用于基本数据类型。

    List list = new ArrayList<>(); // 错误
    List list = new ArrayList<>(); // 正确
    
  2. 泛型类型在运行时会被擦除:Java 的泛型是通过类型擦除实现的,这意味着在编译后,泛型类型信息会被移除,JVM 只知道操作的是 Object 类型。因此,在运行时不能使用 instanceof 检查泛型类型。

    if (obj instanceof List) { // 错误,不能直接检查泛型类型
        ...
    }
    
  3. 不能创建泛型数组:由于类型擦除,Java 不允许创建泛型类型的数组。

    List[] array = new List[10]; // 错误
    

五、实际开发中的注意事项

  1. 类型安全性:泛型的主要目的是保证类型安全,尽量减少强制类型转换的使用。
  2. 避免原始类型的使用:尽量避免使用原始类型(如 List 而不是 List),因为使用原始类型会失去类型检查的好处。
  3. 选择合适的通配符:在开发中,合理使用通配符(如 ),可以使代码更加灵活。
  4. 注意性能:虽然泛型可以提高类型安全性,但在某些性能敏感的场景中,过度使用泛型和对象包装可能导致不必要的性能开销。

你可能感兴趣的:(常用类与基础API,java,开发语言)