JAVA基础—泛型

本文详细介绍了Java中的泛型,包括泛型的原理、泛型类、泛型接口和泛型方法的应用,以及类型擦除对反射的影响。强调了泛型在提高代码通用性和类型安全性的优势,同时指出使用反射时需注意泛型的限制。

1. 泛型

泛型是Java中的一种强大的特性,它允许在编写代码时使用参数化类型,使得代码更加通用、类型安全(不需要强制类型转换)和可重用。泛型的本质是为了参数化类型,也就是说在泛型的使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别称为泛型类、泛型接口、泛型方法。

2. 泛型的使用范围

2.1泛型类

泛型类是使用了泛型类型参数的类。通过泛型类,可以在类的定义中使用一个或多个类型参数,从而实现类的通用性和复用性。泛型类的定义方式为在类后面使用尖括号<>来声明类型参数,并在类的方法、字段或构造函数中使用这些类型参数。
以下是一个简单的泛型类的示例:

public class Box<T> {
    private T value;

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

    public T getValue() {
        return value;
    }

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

    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>(10);
        Box<String> stringBox = new Box<>("Hello");

        System.out.println("Integer value: " + intBox.getValue());
        System.out.println("String value: " + stringBox.getValue());
    }
}

在这个示例中,Box 类是一个泛型类,使用了类型参数 T。在创建 Box 对象时,可以指定具体的类型,比如 Box 和 Box,从而分别创建一个存储整数和字符串的 Box 对象。通过泛型类,可以实现对不同类型数据的存储和操作,同时保持类型安全性。

2.2泛型接口

泛型接口是使用了泛型类型参数的接口。通过泛型接口,可以在接口中定义一个或多个类型参数,使得接口中的方法或常量可以使用这些类型参数,从而实现接口的通用性和复用性。

以下是一个简单的泛型接口的示例:

interface Pair<K, V> {
    K getKey();
    V getValue();
}

class OrderedPair<K, V> implements Pair<K, V> {
    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;
    }

    public static void main(String[] args) {
        Pair<String, Integer> pair = new OrderedPair<>("One", 1);
        System.out.println("Key: " + pair.getKey());
        System.out.println("Value: " + pair.getValue());
    }
}

在这个示例中,Pair 是一个泛型接口,使用了两个类型参数 K 和 V,代表键和值的类型。OrderedPair 类实现了 Pair 接口,并通过泛型类型参数指定了键和值的类型。通过泛型接口,可以定义具有通用性的接口,并且在实现类中指定具体的类型,从而实现对不同类型数据的处理。

2.3泛型方法

泛型方法是定义泛型类型参数的方法。通过泛型方法,可以在方法的定义中使用一个或多个类型参数,从而实现方法的通用性和复用性。泛型方法可以在静态方法和非静态方法中使用,也可以在泛型类中定义。
以下是一个简单的泛型方法的示例:

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

    public static void main(String[] args) {
        GenericMethodExample example = new GenericMethodExample();
        Integer[] intArray = {1, 2, 3, 4, 5};
        String[] stringArray = {"Hello", "World"};
        example.printArray(intArray);
        example.printArray(stringArray);
    }
}

在这个示例中,printArray 方法是一个泛型方法,使用了类型参数 T。在调用 printArray 方法时,可以传入不同类型的数组,而不需要为每种类型编写不同的方法。这样可以提高代码的复用性和灵活性。

泛型方法还可以使用类型参数的上限和下限进行约束,以限制传入方法的类型。例如, 表示类型参数 T 必须是 Number 类型或其子类。

2. 泛型的上下限

类型参数的上限和下限可以通过使用extends和super关键字来进行约束。

  1. 上限通配符extends:指定类型参数必须是指定的类型或其子类。例如,‘’表示类型参数‘T’必须是‘Number’类型或其子类。
public static <T extends Number> double sumOfList(List<T> list) {
    double sum = 0.0;
    for (T element : list) {
        sum += element.doubleValue();
    }
    return sum;
}

  1. 下限通配符super:指定类型参数必须是指定的类型或其父类。例如‘’表示参数类型‘T’必须是‘Integer’类型或其父类。
public static void addNumbers(List<? super Integer> list) {
    list.add(10);
    list.add(20);
}

4.Java泛型常用类型参数和通配符

常用类型参数

  • T : 表示一个具体的类型。
  • E : 通常表示集合中的元素类型。
  • K和V : 通常表示键值对中的Key和Value。
  • S和U : 用于表示在泛型方法中的第二个和第三个类型

通配符

  • :表示未知类型,通常和extends或extends关键字一起使用,表示类型的上下限。

5. 泛型中的类型擦除

Java中的泛型在编译时会进行类型擦除(Type Erasure)。类型擦除是指在编译过程中,将泛型类型信息擦除,替换为原始类型(raw type)或限定类型(bounded type).这样做是为了兼容Java早期版本的代码,并且使得泛型在运行时不会产生额外开销。
类型擦除会导致以下几个方面受影响:

  1. 泛型类型擦除为原始类型:泛型类、接口或方法中的类型参数如果没有指定上限或下限,那么在类型擦除时会擦除为原始类型。例如,List 中的 T 在类型擦除后会变成 List。

  2. 泛型类型擦除为桥接方法:如果泛型类或接口实现了泛型方法,且泛型方法在子类中被重写,那么编译器会生成桥接方法来保持类型安全。桥接方法会擦除类型参数,并在调用时使用类型转换。

    public interface Pair<T> {
        T getFirst();
        void setFirst(T first);
    }
    
    public class StringPair implements Pair<String> {
        private String first;
    
        public String getFirst() {
            return first;
        }
    
        public void setFirst(String first) {
            this.first = first;
        }
    }
    
    

    在StringPair类中,‘Pair’接口中的‘getFirst’方法被思想为返回‘String’类型的方法。在编译时,编译器会生成一个桥接方法来保持类型的安全,类似于以下的形式:

    public class StringPair implements Pair<String> {
        private String first;
    
        public String getFirst() {
            return first;
        }
    
        public void setFirst(String first) {
            this.first = first;
        }
    
        // 桥接方法
        public Object getFirst() {
            return getFirst(); // 调用实际的泛型方法
        }
    }
    
    

    这样,即使在泛型类型被擦除为 Object 类型时,编译器仍然可以保证在运行时调用正确的方法,从而保持类型安全。

  3. 泛型类型擦除为Object或限定类型:如果泛型类型参数有上限或下限,那么在类型擦除时会擦除为其限定类型。例如, 中的 T 在类型擦除后会被擦除为 Number 类型。

    public class Box<T extends Number> {
        private T value;
    
        public T getValue() {
            return value;
        }
    
        public void setValue(T value) {
            this.value = value;
        }
    }
    
    
  4. 泛型数组创建受限制:由于类型擦除的影响,无法直接创建泛型数组。例如,'new T[]'是不合法的,需要使用 Array.newInstance() 方法来创建泛型数组。

    public ArrayWithTypeToken(Class<T> type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }
    

6.泛型对使用反射的影响

  1. 类型擦除:泛型在编译时会进行类型擦除,因此在运行时无法获取泛型类型的具体信息。例如,对于泛型类‘Box’,在运行时只能获取到‘Box’类的信息,无法获取到‘T’的具体信息
  2. 获取泛型信息: 通过反射可以获取泛型类、接口、方法等的信息,但无法直接获取泛型类型的具体信息。通常需要结合‘Type’接口及其子接口(如‘ParameterizedType’、TypeVariable 等)来获取泛型类型的具体信息。
  3. 泛型数组:由于类型擦除的影响,无法直接创建泛型数组。在使用反射时,如果需要创建泛型数组,通常需要使用
  4. 桥接方法: 当泛型类或接口中的方法使用了泛型类型参数,并且在子类中被重写时,编译器会生成桥接方法来保持类型安全。在使用反射获取方法时,需要注意桥接方法的存在。
  5. 泛型边界: 在使用反射获取泛型方法或泛型字段时,需要考虑泛型类型的边界。例如,对于 ,需要通过反射来确定 T 的具体边界。

简单来说,用泛型就尽量不要用反射了,很多限制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值