泛型

 

1. 介绍

  泛型(Generics):是Java语言中一项重要特性,允许在类、接口和方法中使用参数化类型。目的是将具体类型参数化,使用时需要传入具体类型进行替换。通过泛型,可以编程通用、灵活的代码。提高代码的重用性和类型安全性。

  参数分为实参与形参,而泛型属于形参。

  泛型是Java SE 1.5的新特性,是一种不确定的数据类型,这种不确定的数据类型需要我们在使用这个类的时候它才能够确定出来,早期的Object类型可以接收任意的对象类型,但是在实际的使用过程当中,会出现类型转换的问题,会报出一个异常,使用泛型就可以避免这种问题,因为泛型可以使编译器在编译期间对类型进行检查以次来提高类型安全,减少运行时由于对象类型不匹配引发的异常。

  具体来说,泛型允许我们在编写类、接口和方法时使用类型参数,而不是具体的数据类型。这些类型参数在使用时,可被指定为具体的数据类型,从而使得代码可以适用于不同类型的数据,而不必为每种类型编写单独的代码。如果不确定使用什么类型的数据就使用泛型。从概念而言就是一个预定义数据类型。

 

  优点:

  1) 类型安全性和程序健壮性:类型安全性和程序健壮性是相互关联的。泛型在编译时对类型进行检查,提高了程序的健壮性,避免在运行时由于类型转换错误而导致的异常。如:ClassCastException异常。

  ClassCastException异常是JVM在检测到两个类型转换不兼容时引发的运行时异常。

  2) 代码重用性和简洁性:代码重用性和简洁性是相互关联的。通过泛型可以编译通用的,可复用的代码来提高代码的重用性。同时泛型也使得代码更加简洁、易读;因为它消除了很多显式的类型转换和重复的代码。

  3) 减少类型转换:泛型的主要优点是减少了代码中的类型转换。

 

2. 泛型集合和非泛型集合

  非泛型集合:存储元素用Object,add可接收基本数据类型、引用数据类型、布尔类型。

复制代码
    public static void main(String[] args) {

        // 非泛型集合,存储元素用Object,add接收元素可以是基本数据类型、引用数据类型、布尔类型。
        ArrayList<Object> arrayList = new ArrayList<>();

        // 添加元素
        arrayList.add("学Java的Bei");
        arrayList.add(2024);
        for (int i = 0; i < arrayList.size(); i++) {
//            String o = (String) arrayList.get(i);   // 强制String类型
//            System.out.println(o);  // 结果输出: 学Java的Bei int类型则 ClassCastException-转换异常

            // 修改后:
            Object obj = arrayList.get(i);   // 强制String类型
            System.out.println(obj);  // 结果输出: 学Java的Bei 2024
        }

    }
复制代码

  ClassCastException异常:是JVM在检测到两个类型转换不兼容时引发的运行时异常。

复制代码
    // 非泛型集合
    public static void main(String[] args) {

        // 非泛型集合,存储元素用Object类型,add接收的类型可以是基本数据类型,引用数据类型,布尔类型
        ArrayList<Object> nonGenericCollectionList = new ArrayList<>();

        // 列表内添加不同类型的对象
        nonGenericCollectionList.add("学Java的Bei");  // 字符串
        nonGenericCollectionList.add(20240225); // 整数
        nonGenericCollectionList.add(true); // 布尔

        // 列表内获取元素,并进行元素转换
        String string = (String) nonGenericCollectionList.get(0);
        int num = (int)nonGenericCollectionList.get(1);
        Boolean bool = (boolean)nonGenericCollectionList.get(2);

        // 打印元素
        System.out.println("string = " + string);
        System.out.println("num = " + num);
        System.out.println("bool = " + bool);
        
        //  string = 学Java的Bei
        //  num = 20240225
        //  bool = true
    }
复制代码

    注意:

    •  在创建集合时指定集合存储的元素类型;例:ArrayList<Integer>表示存储整数类型的元素。
    •  遍历集合时不需行类型转换;因为集合内的元素类型已经确定。
    •  泛型集合在编写通用代码和提高代码安全性方面非常有用。

  使用非泛型集合时,需明确存储每个元素的数据类型,否则会引发 类型转换异常 ClassCastException。

 

  泛型集合:是指能够存储特定类型对象的集合,其中的元素类型在集合被创建时是确定,并且集合只能存储该特定类型的元素。泛型集合通过泛型机制提供了类型安全的存储和检索操作。

  默认为Object类型,接受的类型可以是引用数据类型、Boolean类型。如果指定泛型类型为String类型,存储的类型只能是String类型,可以存储多个元素。

  泛型必须是包装类,只能代表引用数据类型。在程序总有些数据会返回空值,用剧本数据类型int会发生异,因为int内有null值;但基本数据类型对应的Integer包装类型不会,因为对象可以为NULL。

复制代码
    // 泛型集合
    // 需要一个存储字符串的列表,并且希望这个列表能够保证类型安全,那么我们可以使用泛型集合 ArrayList。
    public static void main(String[] args) {

        // 创建一个存储字符串的泛型集合
        ArrayList<String> stringList = new ArrayList<>();

        // 向集合内添加元素
        stringList.add("Hello,");
        stringList.add("Generic.");

        // 遍历打印
        System.out.println("字符串集合内的元素:");
        for (String list : stringList) {
            System.out.println(list);
        }

        // 从集合内获取元素
        String accessElement = stringList.get(0);
        System.out.println("第一个元素为:" + accessElement);

    }
复制代码

 

复制代码
    // 泛型集合
    // 创建一个泛型方法,该方法接受一个泛型列表作为参数,并打印出列表中的所有元素。这样的方法可以接受任何类型的列表,并且具有通用性。
    public static void main(String[] args) {

        ArrayList<Integer> integerList = new ArrayList<>();
        integerList.add(2024);

        ArrayList<String> stringList = new ArrayList<>();
        stringList.add("Hello,2024!");

        System.out.println("整数列表:");
        printlnList(integerList);

        System.out.println("字符串列表:");
        printlnList(stringList);
    }

    // 泛型方法,接受一个泛型列表并打印其中所有元素
    public static <T> void printlnList(ArrayList<T> arrayList) {
        for (T element : arrayList) {
            System.out.println(element);
        }
    }
复制代码

 

3. 泛型类

  泛型类是使用泛型类型参数的类。允许类中某些字段、方法或构造函数接受特定类型的数据,而这些类型在类被实例化时才确定。

  泛型标识(也称为类型参数):在Java中指定泛型类,接口或方法的参数类型。允许在编写代码时使用占位符来表示数据类型,而不需提前确定具体的参数类型;当使用时,再用确定的数据类型替换我们的标识。

 

  在定义泛型类、接口或方法时,使用尖括号<T>、<E>或其他标识符来声明参数类型;这些标识符可以是任何合法的Java标识符,通常用单个大写字母表示,以表它们是参数类型。

  • <T>:通用泛型类型,通常表示任意类型;
  • <E>:集合元素泛型类型,通常表示集合中的元素类型;如List、Set
  • <K,V>:映射键,值:表示键值对中键和值的参数类型;如Map

  泛型类在创建对象时,如果没有指定类型,按照Object类型操作。

复制代码
// 泛型标识
public class GenericIdentifier<T> {

    // <T> 是一个泛型标识,它表示任意类型。
    // 在实例化 Box 类时,指定了具体的数据类型(整数和字符串),这样就替换了泛型标识,使得 Box 类可以存储不同类型的数据。

    // 下面的T仅仅表示的是一种参数类型,这个参数类型是一个变量,可以指代任意一种引用数据类型。
    // T可以换成 A-Z 之间的任何一个字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替,在可读性上可能会弱一些。

    private T date;

    public GenericIdentifier(T date) {
        this.date = date;
    }

    public T getDate() {
        return date;
    }

    public void setDate(T date) {
        this.date = date;
    }
}

 class Main {
    public static void main(String[] args) {

        // 创建一个存储整数的GenericIdentifier实例
        GenericIdentifier<Integer> integerGenericIdentifier = new GenericIdentifier<>(10);

        // 创建一个存储字符串GenericIdentifier实例
        GenericIdentifier<String> stringGenericIdentifier = new GenericIdentifier<>("学Java的Bei");

        // 获取并打印数据
        System.out.println("整数:" + integerGenericIdentifier.getDate()); // 整数:10
        System.out.println("字符串:" + stringGenericIdentifier.getDate()); // 字符串:学Java的Bei

    }
}
复制代码

  注意:我们 new 出的对象都是 来自 构造方法(有参构造与无参构造)。 

 

4. 从泛型类派生子类

  在Java可以从泛型类派生子类,但要注意一些限制和约束。

  1) 如果子类也是泛型类,并且使用父类的参数类型,那么子类的参数类型标识应与父类的参数类型标识一致。否则,在子类中无法得到具体的数据类型,编译器会报错。 

复制代码
// 从泛型类派生子类
public class NoteOne<T> {

    // SubClass<T> 是 NoteOne<T> 的子类,并且也是一个泛型类。
    // 子类 SubClass 继承了父类 NoteOne 的类型参数 T,使得子类可以使用相同的类型参数。
    // 这样,子类可以保留父类的泛型类型,并且可以使用相同的类型参数来实例化对象。

    private T data;

    public NoteOne(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

// 子类也是泛型类,且继承父类的参数类型
class SubClass<T> extends NoteOne<T>{

    public SubClass(T date) {
        super(date);    // super(data) 表示调用父类的构造方法,并将参数 data 传递给父类的构造方法进行处理。
    }
}

class Main {

    public static void main(String[] args) {

        // 创建一个存储整数的SubClass实例
        SubClass<Integer> integerSubClass = new SubClass<>(2024);
        System.out.println("整数:" + integerSubClass.getData());

        // 创建一个存储字符串的SubClass实例
        SubClass<String> stringSubClass = new SubClass<>("学java的Bei");
        System.out.println("字符串:" + integerSubClass.getData());

    }
}
复制代码
 
  2) 如果子类不是泛型类,而父类是泛型类;那么在子类中实例化父类时,需明确指定泛型类的数据类型。这种行情况下,子类不具有泛型类型参数,而是使用具体的数据类型来实例化父类。
复制代码
public class NoteTwo<T> {

    // 子类 SubBox 继承了父类 NoteTwo<T>,但子类自身不是泛型类。
    // 在子类的构造方法中,通过 super(data) 指定了父类 NoteTwo 的泛型数据类型为 Integer。
    // 这样,子类 SubBox 就可以正确地实例化父类 NoteTwo<Integer>。
    private T data;

    public NoteTwo(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

// 子类不是泛类型
class SubClassTwo extends NoteTwo<Integer> {
    public SubClassTwo(Integer date) {
        super(date);    // 指定父类的泛型数据类型为Integer
    }
}

class MainTwo{

    public static void main(String[] args) {

        // 创建子类实例,需明确指定父类的泛型数据类型为Integer
        SubClassTwo subClassTwo = new SubClassTwo(20240225);

        // 调用父类方法
        System.out.println("参数:" + subClassTwo.getData());  // 参数:20240225

    }
}
复制代码

  在上面super 点的父类 是 父类的 构造方法。   

 

  3) 当父类是泛型类而子类不是泛型类时,子类继承父类后,父类的泛型类型参数会被参数,编译器会视为Object类型。

复制代码
// 父类泛型
public class NoteThree<T> {

    private T data;

    public NoteThree(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

// 子类泛型
class SubClassThree extends NoteThree<String>{

    public SubClassThree(String date) {
        super(date);    // 指定父类的泛型类型为String
    }

}

class MainThree{
    public static void main(String[] args) {
        // 创建子类实例,需明确指定父类泛型数据类型为String
        SubClass subClass = new SubClass("学Java的Bei");

        // 调用父类方法
        System.out.println("结果:" + subClass.getData()); // 结果:学Java的Bei
    }
}
复制代码

 

  4) 子类的泛型标识可以添加多个,但必须有一个和父类的泛型标识一样。子类的泛型标识数量不一样要与父类相同。

复制代码
// 可以正确地继承父类的行为,且还可以添加自己的特定行为。
public class NoteFour<T> {
    private T data;

    public NoteFour(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

// 子类拥有两个泛型类型,但必须有一个与父类相同
class SubClassFour<T,V> extends NoteFour<T>{
    private V info;

    public SubClassFour(T data, V info) {
        super(data);
        this.info = info;
    }

    public V getInfo() {
        return info;
    }
}

class MainFour{
    public static void main(String[] args) {

        SubClassFour<Integer, String> subClassFour = new SubClassFour<>(20240224,"学Java的Bei");

        System.out.println("info--" + subClassFour.getInfo());
        System.out.println("data--" + subClassFour.getData());

    }
}
复制代码

 

  5) 若子类拥有多个泛型标识,创建子类对象时需对这些泛型标识制定具体的类型。为了确保类型的一致性,并遵循泛型类的规范。

复制代码
public class NoteFive<T> {

        private T data;

        public NoteFive(T data) {
            this.data = data;
        }

        public T getData() {
            return data;
        }
    }

    // 子类拥有两个泛型类型,但必须有一个与父类相同
    class SubClassFive<T,U> extends NoteFive<T> {
        private U info;

        public SubClassFive(T data, U info) {
            super(data);
            this.info = info;
        }

        public U getInfo() {
            return info;
    }

}

class MainFive{
    public static void main(String[] args) {

        // 创建SubClassFive对象,需指定 T,U的具体类型
        SubClassFive<String, Integer> subClassFive = new SubClassFive<>("学Java的Bei:龙年大吉!", 20240224);

        System.out.println("info--" + subClassFive.getInfo());
        System.out.println("data--" + subClassFive.getData());

    }
}
复制代码

  总结:

  • 泛型是定义的时候用,但真正使用的时候,必须确定类型;
  • 定义的时候是泛型类,使用的时候没有指定,则就是Object;
  • 如果父类是泛型类,子类不是泛型类,继承父类后,父类没有声明,则默认是Object;
  • 子父关系中,定义的时候,子类的泛型类必须要和父类的泛型类一致,否则会报错;
  • 如果子类不是泛型类,父类要明确泛型的数据类型;
  • 子类的泛型标识可以添加多个,但必须有一个和父类的泛型标识一致;

 

5.  泛型接口

  1) 泛型接口的定义语法

  泛型接口的定义语法与普通接口的语法相似。在接口名称除使用尖括号<>声明一个或多个泛型参数。这些泛型参数可在接口中的方法中作为返回值类型或参数类型使用,从而使得接口更具有泛型特特征。

复制代码
// 泛型接口定义语法
// DefineSyntax是一个泛型接口,它有一个类型参数 T。
// 接口中的方法 getValue() 返回的类型和 setValue() 方法的参数类型都是泛型类型参数 T。
// 这样,实现这个接口的类就可以根据需要指定具体的类型参数,或者保留泛型参数。
public interface DefineSyntax<T> {

    T getValue();

    void setValue(T value);

}
复制代码

 

  2) 泛型接口的使用

  •  如果实现了不是泛型类,那么实现的接口的泛型接口必须明确具体的数据类型。

  实现接口类指定泛型接口类型为String,那么接口类内的泛型标识符T就是String类型,返回值也是String类型。

复制代码
public interface DefineSyntax<T> {

    T getValue();

    void setValue(T value);

}

// 指定泛型T的参数类型为String
class ImplClass implements  DefineSyntax<String> {

    private String value;

    @Override
    public String getValue() {
        return value;
    }

    @Override
    public void setValue(String value) {
        this.value = value;
    }
}
复制代码

  

  •  如果实现类是泛型类且实现了泛型接口,实现类和接口的泛型类型必保持一致,否则无法接受具体的数据类型。
复制代码
public interface TypeConsistency<T> {

    T getValue();

    void setValue(T value);

}

// 泛型类型要与实现类的泛型类型一致
class TypeClass<T> implements TypeConsistency<T>{

    private T value;

    @Override
    public T getValue() {
        return value;
    }

    @Override
    public void setValue(T value) {
        this.value = value;
    }
}
复制代码

 

6. 泛型方法

  泛型类和泛型方法的区别:

  泛型类:是在实例化类的时候指明泛型的具体类型;

  泛型方法:是在调用方法的时候指明泛型的具体类型;

 

  1) 定义语法

  •  public与返回之类中间的<T>非常重要,可以声明此方法为泛型方法;
  •  只有在泛型列表中声明了泛型标识的方法才是泛型方法;
  •  <T>表明该方法将使用泛型类型T;
  •  与泛型类的定义一样,此处T可写为任意标识;

  泛型方法:就是将方法参数类型中的泛型,提前在前面声明,必须用<>包起来。

  格式:修饰符 <泛型标识> 返回值类型 方法名(参数列表) { // 方法体 }

复制代码
// 定义语法
public class DefinitionSyntax {

    // isEqual 方法是一个泛型方法,它接受两个参数,并比较它们是否相等。
    public static <T> boolean isEqual(T first, T second) {  // 返回 true/false
        return first.equals(second);
    }
}

// 使用
class Main{
    public static void main(String[] args) {

        // 使用 isEqual方法比较两个整数是否相等
        int num1 = 2024;
        int num2 = 0226;

        boolean result = DefinitionSyntax.isEqual(num1, num2);
        System.out.println("结果:" + result); // 比较:结果:false

        // 使用 isEqual 方法比较两个字符串是否相等
        String str1 = "学Java的Bei";
        String str2 = "学Java的Bei";

        boolean equal = DefinitionSyntax.isEqual(str2, str1);
        System.out.println("结果:" + equal);  // 比较:结果:true

    }
}
复制代码

 

  2) 静态泛型方法

  静态泛型方法:是指在类中声明的静态方法,该方法可以使用泛型类型参数。

  静态泛型方法与普通泛型方法的区别是:静态泛型方法是在静态方法中声明的,而普通方法可以是静态的或非静态的。

复制代码
// 静态泛型方法
public class StaticGenericMethod {

    // 静态类型方法:比较两个元素是否相等
    public static <T> boolean isEqual(T first, T second) {  // 返回 true/false
        return first.equals(second);
    }

    // 静态类型方法:打印数组内所有的元素
    public static <E> void printArray(E[] array) {
        for (E element : array) {
            System.out.println("打印--" + element);
        }
        System.out.println();
    }
}

// 使用这两个静态泛型方法进行比较和打印数组元素。

class StaticMain{
    public static void main(String[] args) {

        // 使用 静态泛型方法 isEqual方法比较两个整数是否相等
        int num1 = 2024;
        int num2 = 0226;
        boolean result = StaticGenericMethod.isEqual(num1, num2);
        System.out.println("结果:" + result); // 比较:结果:false

        // 使用 静态泛型方法 isEqual 方法比较两个字符串是否相等
        String str1 = "学Java的Bei";
        String str2 = "学Java的Bei";
        boolean equal = StaticGenericMethod.isEqual(str2, str1);
        System.out.println("结果:" + equal);  // 比较:结果:true

        // 使用 静态泛型方法 打印 printArray 方法打印元素数组
        Integer[] intArray = {1, 3, 5, 6, 7, 2024};
        StaticGenericMethod.printArray(intArray);   // 打印--1 ...

        String[] strArray = {"学Java的Bei", "在看的你-", "胜过彭于晏"};
        StaticGenericMethod.printArray(strArray);   // 打印--学Java的Bei,打印--在看的你- ...
    }
}
复制代码

 

  3) 泛型可变参数

  泛型可变参数是Java内一种灵活的参数类型,允许方法接受可变数量的参数,且这些参数可以是任意类型的泛型参数。

  可变参数的定义为在形参泛型标识后加 ... ,使得方法可以接受不定数量的参数。... 背后的本质为 T[]数组。

复制代码
// 泛型可变参数
public class GenericVarargs {

    // 泛型可变参数方法:打印任意数量的元素
    public static <T> void printElements(T... elements) {

        for (T element : elements) {
            System.out.println("打印:" + element);
        }
        System.out.println();
    }
}

// 使用
class VarargsMain {
    public static void main(String[] args) {
        // 使用泛型可变参数打印不同类型的元素
        GenericVarargs.printElements(2024,0226,20.28);
        GenericVarargs.printElements("学Java的Bei", "在看的你", "胜过彭于晏");
    }
}
复制代码

 

7. 泛型通配符

  泛型通配符是Java内用于表示未知泛型类型的符号,用 ? 表示。作用是增加泛型类型的灵活性,允许在不确定使用泛型类型时使用泛型。

  注意: ? 是代替的具体的类型实参,而非类型形参。此时只能接受数据,不能往该集合中存储数据。不能使用泛型标识A-Z,因为泛型标识代表形参,这里需要实参。

复制代码
public class Test001<E> {

    // 声明成员变量
    private E first;

    public E getFirst() {
        return first;
    }

    public void setFirst(E first){
        this.first = first;
    }

}
复制代码
复制代码
public class Test002 {

    public static void show(Test001<Integer> test) {
        Integer first = test.getFirst();
        System.out.println(first);
    }

    public static void main(String[] args) {

        // 调用
        Test001<Integer> test001 = new Test001<>();

        // 赋值
        test001.setFirst(2024);

        show(test001);

        Test001<String> test0011 = new Test001<>();
        test0011.setFirst("学Java的Bei");
//        show(test0011); // 已经被上面定义为了Integer类型,这里的String类型会报错


//      使用通配符 ? 后

        Test001<String> test0012 = new Test001<>();
        test0012.setFirst("学Java的Bei");
        show1(test0012);    // 结果:学Java的Bei

        Test001<Object> test0013 = new Test001<>();
        test0013.setFirst(2024);
        show1(test0013);    // 结果:2024

    }

    public static void show1(Test001<?>test001) {
        Object first = test001.getFirst();
        System.out.println(first);
    }

}
复制代码

 

  通配符可以在泛型类、泛型方法、泛型接口使用,主要三种形式:

  1) 上限通配符

   ? extends T:表示通配符可以匹配 T类型 及其 T类型的子类;通配符表示的是一个上界,表示类型的上界是 T 类型或 T 的子类;

  简单说,? extends T :这种通配符表示一个范围,即表示匹配的类型为T 或 T的某个子类,在泛型中被称为上届通配符。就是取出某种类型或者某种类型的子类型。

  定义语法:类/接口<? extends 实参类型>

  举例:

复制代码
// 设有一个类层次结构,有一个父类 UpperLimit,以及两个子类 ClassOne 和 ClassTwo。
// 现有一个泛型容器 List<? extends UpperLimit>,它可以匹配 UpperLimit 类型以及 UpperLimit 的子类,比如 ClassOne 和 ClassTwo。
class UpperLimit {}

class ClassOne extends UpperLimit{};

class ClassTwo extends UpperLimit{};

class MainUpper{
    public static void main(String[] args) {

        List<? extends UpperLimit> upperLimits;

        upperLimits = new ArrayList<ClassOne>();    // 合法
        upperLimits = new ArrayList<ClassTwo>();    // 合法
    }
}
复制代码

 

  2) 下限通配符

  ? super T:在Java泛型中表示泛型类型的下界。作用为限制通配符所代表的类的下限T或T的父类。意味着可以使用下限通配符的方法传递类型T或T的父类的对象。

  简单来说:指定这个类型或这个类型的父类或者这个类型的父类的父类。

  在 Java 中,使用下限通配符的集合只能接受指定下限类型或其子类型的对象,而不能接受其父类型的对象。

复制代码
class Floor {}

class ClassThree extends Floor{}

class ClassFour extends Floor{}

class MainFloor{

    public static void main(String[] args) {

        List<? super ClassThree> floors = new ArrayList<>();
        addFloor(floors);   // 合法调用。因为 floors 列表可以接受 ClassThree 类型或者 ClassThree 的父类的对象.
        floors.add(new ClassThree());   // 合法操作
        // floors.add(new Floor());    //  非法操作
        // floors.add(new ClassFour());    //  非法操作

    }
    // 设有一个方法 addFloor,它接受一个 List<? super ClassThree>,表示这个列表可以接受 ClassThree 类型或者 ClassThree 的父类的对象.
    public static void addFloor(List<? super ClassThree> floors){}
}
复制代码

 

8. 泛型擦除

  当我们谈论泛型擦除时,我们实际上在讨论Java泛型的一个重要特性。Java泛型是在Java 5中引入的,它通过类型擦除来实现泛型化。

  泛型擦除是Java泛型的一个重要特性,它指的是在编译时期,泛型类型信息被擦除,并转换为原始类型,使得在运行时期无法获取泛型的具体类型信息。尽管编写泛型代码可以提供类型安全性检查,但在运行时,泛型类型的行为与普通类型类似,无法区分不同类型的泛型实例。

  重点强调:

  • 运行时类型信息的丢失:由于类型擦除,泛型的运行时类型信息被丢失。这意味着你不能在运行时获得泛型的具体类型参数信息。

  • 泛型实例的Class对象:不同泛型实例的Class对象在运行时是相同的。例如,List<String>List<Integer> 的实例在运行时的Class对象都是 List.class

  • 泛型转译(Type Erasure):在编译期间,编译器会插入必要的转型代码来保证类型安全性。这些转型代码确保了泛型代码的类型约束。

  1) 无限制泛型擦除

  无限制擦除是指在Java泛型中,如果没有指定具体的类型参数,例如使用泛型类型而没有指定类型参数,那么在编译时会将泛型类型擦除为原始类型。这样的情况下,编译器无法对泛型的类型进行任何检查,因此会发出警告。

复制代码
// 创建一个没有指定类型参数的泛型 List。这种情况下,在编译时会发出警告,因为缺乏类型参数,编译器无法对泛型进行类型检查.
public class GenericErase {
    public static void main(String[] args) {
        List list = new ArrayList(); // 没有指定类型参数的泛型

        list.add("学Java的Bei");
        list.add(2024);

        for (Object obj : list) {
            // 在使用时,需要进行显示的类型转换,并且容易引发类型转换异常
            String str = (String) obj; // 这里会引发 引发类型转换异常(ClassCastException)
            System.out.println(str);
        }
    }
}
复制代码

 

  2) 指定了上限,上限是Number,泛型标识T在做泛型擦除的时候转换成了上限类型。

   定义的时候是泛型标识E,在使用的时候给泛型标识定为Integer类型,编译结束后,进行泛型擦除,生成了class字节码文件,通过反射,此时成员变量key的数据类型就成了Number类型,包括浮点数、整数等。

复制代码
public class Test01<E extends Number> {
    private E key;
    private Integer num;
    private String str;

    public E getKey(){
        return key;
    }

    public void setKey(E key) {
        this.key = key;
    }

    public Integer getNum() {
        return num;
    }
    public void setNum(Integer num) {
        this.num = num;
    }

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }
}
复制代码
复制代码
    public static void main(String[] args) {

        // 创建类对象
        Test01<Integer> test01 = new Test01<>();
        // 反射获取对象的字节码文件
        Class<? extends Test01> aClass = test01.getClass();
        // 反射获取所有成员变量
        Field[] declaredFields = aClass.getDeclaredFields();
        // 对成员变量进行遍历
        for (Field field : declaredFields) {
            // 打印成员变量的名称和类型
            System.out.println(field.getName() + ":" + field.getType().getSimpleName());    // 结果:key:Number; num:Integer; str:String
        }
    }
复制代码

 

  3) 擦除方法中类型定义的参数

  在泛型列表中指定上限是Number,进行泛型擦除以后转换成了上限类型

  如果不是上限类型,是Object类型,进行泛型擦除以后转换成Object类型

 

 

 

 

posted @   学Java的`Bei  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示