说⼀说你对泛型的理解

        泛型(Generics)是Java 5中引入的一个特性,它提供了编译时类型安全检查的机制,允许在编码阶段指定集合的元素类型或者方法的参数类型。使用泛型可以增强程序的可读性和安全性,避免在运行时出现类型转换异常。

        定义:泛型是通过类型参数来实现的,这些类型参数在类、接口或方法的定义中使用一对尖括号(<>)指定。

一、泛型类

        泛型类是一种能够处理不同数据类型的类。它的定义方式是将类型参数放在类的名称后面,通常使用单个大写字母来表示类型参数(如:TEKV等)。例如:

// 泛型类
public class Box<T> {
    private T value;  // 存储一个类型为T的值

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

// 使用泛型类
public class Main {
    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>();  // 创建一个Integer类型的Box
        intBox.setValue(10);  // 设置值为10
        System.out.println(intBox.getValue());  // 输出10

        Box<String> strBox = new Box<>();  // 创建一个String类型的Box
        strBox.setValue("Hello, Generics!");
        System.out.println(strBox.getValue());  // 输出Hello, Generics!
    }
}

        在上面的例子中,Box<T> 是一个泛型类,它的成员变量和方法都使用了类型参数 T,并且当我们创建 Box 实例时,指定了类型参数(如 IntegerString)。这使得 Box 可以用于不同类型的数据。


二、泛型方法

        泛型不仅可以用于类和接口,还可以用于方法。泛型方法允许在方法的签名中声明类型参数,而这个类型参数是与方法调用时所传递的类型相关的。其基本语法是将类型参数放在方法返回类型之前,泛型方法可以是静态的,也可以是非静态的。例如:

public class GenericMethodExample {
    // 泛型方法
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        String[] strArray = {"Hello", "World", "Generics"};

        printArray(intArray);  // 输出:1 2 3 4 5
        printArray(strArray);  // 输出:Hello World Generics
    }
}

三、泛型接口 

        与泛型类类似,接口也可以使用泛型。接口的泛型常用于设计需要处理不同数据类型的行为。例如,Java的 List 接口就是一个常见的泛型接口。

// 泛型接口
public interface Container<T> {
    void add(T item);
    T get(int index);
}

// 泛型接口的实现
public class StringContainer implements Container<String> {
    private List<String> list = new ArrayList<>();

    @Override
    public void add(String item) {
        list.add(item);
    }

    @Override
    public String get(int index) {
        return list.get(index);
    }
}

public class Main {
    public static void main(String[] args) {
        Container<String> container = new StringContainer();
        container.add("Hello");
        container.add("Generics");

        System.out.println(container.get(0));  // 输出Hello
        System.out.println(container.get(1));  // 输出Generics
    }
}

         在这个例子中,Container<T> 是一个泛型接口,表示一个容器,可以存储任何类型的元素。StringContainer 是这个接口的一个实现,它专门处理 String 类型的数据。


四、泛型的通配符

        泛型通配符用于表示未知类型。主要有三种形式:无限制通配符?、有上限的通配符? extends Type和有下限的通配符? super Type

  • 无限制通配符 ?:表示任何类型。
  • 有上限的通配符 ? extends Type:表示类型是Type或其子类。
  • 有下限的通配符 ? super Type:表示类型是Type或其父类。
public class WildcardExample {
    // 使用通配符?表示类型未知
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.print(obj + " ");
        }
        System.out.println();
    }

    // 使用上界通配符表示类型T及其子类
    public static void printNumberList(List<? extends Number> list) {
        for (Number num : list) {
            System.out.print(num + " ");
        }
        System.out.println();
    }

    // 使用下界通配符表示类型T及其父类
    public static void addIntegerList(List<? super Integer> list) {
        list.add(10);
        list.add(20);
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3, 4, 5);
        List<Double> doubleList = List.of(3.14, 2.71, 1.62);

        printList(intList);  // 输出:1 2 3 4 5
        printList(doubleList);  // 输出:3.14 2.71 1.62

        printNumberList(intList);  // 输出:1 2 3 4 5
        printNumberList(doubleList);  // 输出:3.14 2.71 1.62

        List<Number> numberList = new ArrayList<>();
        addIntegerList(numberList);  // 向numberList添加Integer对象
        System.out.println(numberList);  // 输出:[10, 20]
    }
}

        在上面的例子中,printList 使用了 ? 来表示接受任何类型的列表,printNumberList 使用了 ? extends Number 来限制只能传入 Number 或其子类的列表,而 addIntegerList 使用了 ? super Integer 来接受 Integer 或其父类的列表。


五、泛型擦除

        在Java中,泛型擦除(Generic Type Erasure)是指编译器在编译时将泛型类型信息去除(擦除),并替换为原始类型或通配符。这是因为Java的泛型是编译时的特性,在运行时并不会保留泛型类型的信息,泛型的类型参数会被擦除为其边界类型(如果有的话),或者是 Object 类型。

        这种擦除机制确保了泛型代码可以与没有泛型的代码兼容,也可以与老旧的类库互操作。然而,这也意味着在运行时,泛型信息不可用,无法通过反射等方式获取类型参数的实际类型。

1. 基本概念

        假设我们定义了一个泛型类 Box<T>,它存储类型为 T 的元素,编译后,所有泛型类型都会被擦除,替换为其原始类型或边界类型。例如:

// 泛型类
public class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

编译器会将这个类擦除为:

// 擦除后的类
public class Box {
    private Object value;

    public Box(Object value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }
}

         在编译时,T 被擦除为 Object 类型(因为没有给 T 限定边界),这意味着 Box<T> 的实例在运行时其实是一个 Box 类型的对象,它的 value 可以是任何类型的对象。

细节:

  • 类型参数擦除:所有泛型类型的类型参数都会在编译后被擦除成 Object(如果没有指定边界)或者其边界类型(如果有的话)。
  • 类型边界的处理:如果泛型类型参数指定了上界(例如 T extends Number),则擦除后的类型会被替换为这个边界类型。

例如:

// 泛型类:限定T必须是Number的子类
public class Box<T extends Number> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

在编译后,Box<T extends Number> 会被擦除为:

// 擦除后的类
public class Box {
    private Number value;

    public Box(Number value) {
        this.value = value;
    }

    public Number getValue() {
        return value;
    }
}

 此时,T 被擦除为 Number 类型,而不是 Object,因为我们给 T 指定了上界 extends Number

 总结

        泛型在Java中广泛用于提高代码的复用性、类型安全性和可维护性。它通过类型参数使得类、方法和接口能够处理不同类型的数据。在使用泛型时,通配符和上/下界通配符提供了更多的灵活性,使得代码更加通用和强大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值