Java泛型

泛型(Generics)是一种允许在类、接口和方法中使用类型参数的机制,从而在编译时保证类型安全,并提高代码的复用性。泛型提供了一种解决类型不安全问题的方法,并使代码更具可读性和可维护性。

一.泛型类

定义格式

  • 一个泛型类的定义包括类型参数。类型参数通常用尖括号(<>)括起来,并放在类名后面。可以使用一个或多个类型参数。
public class 方法名<泛型类型,.....> {} 

注意事项

  • 1.泛型的默认类型是Object,意味着没有加泛型就可以存储任意类型的数据

  • 2.泛型只能编写引用数据类型(包装类在引用数据类型里面),不能用于基本数据类型(int, double, char…)

泛型类型标识符

  • 常见的有:ETKV

  • 它们不是 Java 语言的专有名词,但它们在泛型编程中有一些约定俗成的含义:

    E: Element(元素),通常用于集合类中,例如 List<E>、Set<E>。
    T: Type(类型),一般用于通用的类型参数,例如 Box<T>。
    K: Key(键),用于表示键的类型,通常出现在映射(Map)相关的泛型定义中,例如 Map<K, V>
    V: Value(值),用于表示值的类型,也出现在映射(Map)相关的泛型定义中,例如 Map<K, V>
    
  • 具体的类型什么时候确认:

    • 创建对象的时候确定到具体的类型,例如:

      //创建Student类
      class Student<E>{    // <E> : 给自己写的类加上了泛型
          private E e;
      
          public E getE() {
              return e;
          }
      
          public void setE(E e) {
              this.e = e;
          }
      }
      
    • 当Student 类实例化时没有明确指定其泛型类型时,默认认为所有的数据类型都是Object类型,这时候可以添加任何类型的数据
      泛型类的类型确定_2

    • 当Student 类实例化时指定泛型类型为String时,只能往里面添加String类型的数据
      泛型类的类型确定_1

二.泛型方法

泛型方法可以分为两种:1.非静态的方法 2.静态的方法

1.非静态的方法

public class test<E> {

    public static void main(String[] args) {
        test<Integer> ex1 = new test();  //泛型参数 E 被指定为 Integer
        ex1.printElement(111);    // E 被推断为 Integer

        test<String> ex2 = new test<>();  //泛型参数 E 被指定为 String
        ex2.printElement("张三");  // E 被推断为 String
    }

    // 泛型方法定义
    public void printElement(E e) {
        System.out.println(e);
    }
}
  • 当text类被实例化时,泛型参数 E 被指定为 Integer时,只能使用Integer类型的数据
    泛型方法的类型确定_1

  • 当text类被实例化时,泛型参数 E 被指定为 String时,只能使用String类型的数据
    泛型方法的类型确定_2

    结论:非静态的方法内部的泛型,会根据类的泛型去匹配

2.静态的方法

public class GenericsDemo03{
    public static void main(String[] args) {
        String[] arr1 = {"张三","李四","王五"};
        Integer[] arr2 = {11,22,33};
        Double[] arr3 = {11.1,22.2,33.3};

        printArray(arr1);
        printArray(arr2);
        printArray(arr3);

    }

    //这个类型 T 在调用方法时由传递的数组类型确定
    public static<T> void printArray(T[] arr){  
        System.out.print("[");
        for (int i = 0; i < arr.length-1; i++) {
            System.out.print(arr[i] + ",");
        }
        System.out.println(arr[arr.length-1] + "]");
    }

}
  • 当往泛型方法中传入参数时,会根据传入的参数类确定确定具体的类型。下面arr1,arr2,arr3就分别对应了String类型,Integer类型,Double类型。
    泛型方法的类型确定_3
    结论:静态方法中如果加入了泛型,必须申明出自己独立的泛型。在调用方法,传入实际参数的时候,确定到具体的类型

三.泛型接口

泛型接口有两种方式确定自己的具体类型:

  • 1.实现类实现接口的时候确定到具体的类型

  • 2.实现类实现接口,没有指定具体类型,就让接口的泛型,跟着类的泛型去匹配

1.实现类实现接口的时候确定到具体的类型

  • 具体代码如下:

    public class test {
    
        public static void main(String[] args) {
    
        }
    }
    
    //自定义接口
    interface inter<E> {
        //接口中的抽象方法,方法的形参类型没想好,可以加一个泛型,然后在接口上也加泛型
        void show(E e);   
    }
    
    
    //1:实现类,实现接口的时候确定到具体的类型
    class InterAImpl implements inter<String>{
    
        @Override
        public void show(String s) {
    
        }
    }
    

2.实现类实现接口,没有指定具体类型,就让接口的泛型,跟着类的泛型去匹配

  • 具体代码如下:

    public class test {
        public static void main(String[] args) {
    
        }
    }
    
    interface inter<E> {
    	//接口中的抽象方法,方法的形参类型没想好,可以加一个泛型,然后在接口上也加泛型
        void show(E e);   
    }
    
    //2.实现类实现接口,没有指定具体类型,就让接口的泛型,跟着类的泛型去匹配
    class InterBImpl<E> implements inter<E>{  //实现类实现接口的时候,还没想好类型,还可以继续申明泛型
        @Override
        public void show(E e) {
    
        }
    }
    
    • 当text类被实例化时,泛型参数 E 被指定为 String时,只能使用String类型的数据
      泛型接口的类型确定_1

    • 当text类被实例化时,泛型参数 E 被指定为 Integer时,只能使用Integer类型的数据
      泛型接口的类型确定_2

四.通配符(Wildcard)

  • 无界通配符(?):表示任何类型

    • 例如:
       // 可以是任何类型的 List
      List<?> list = new ArrayList<String>();
      
  • 上界通配符(? extends T):表示匹配 T 类型或其子类型

    • 例如:
      // 可以是 List<Number> 或 List<Integer>
      List<? extends Number> list = new ArrayList<Integer>(); 
      
  • 下界通配符(? super T):表示匹配 T 类型或其父类型

    • 例如:
      // 可以是 List<Number> 或 List<Object>
      List<? super Integer> list = new ArrayList<Number>(); 
      

五.泛型的限制

  • 不能创建泛型数组:由于泛型类型在运行时被擦除,不能直接创建泛型数组。例如,new T[10] 是不允许的。

  • 不能使用基本数据类型:泛型不能用于基本数据类型,如 intchar 等。需要使用对应的包装类,如 IntegerCharacter

  • 不能实例化泛型类型:不能直接实例化泛型类型,例如 new T() 是不允许的。可以通过传递类类型或使用反射来解决这个问题。

  • 类型擦除:泛型类型在编译时会被擦除为其边界类型(通常是 Object),这意味着在运行时泛型类型的信息丢失。

  • 静态上下文限制:泛型类型不能用于静态字段或静态方法中,因为这些静态成员不依赖于具体的泛型类型。

六.类型擦除

基本概念:

Java中的泛型是通过类型擦除实现的。类型擦除是指在编译时,泛型类型信息会被擦除,转化为其边界类型或 Object。这意味着在运行时,泛型信息不可用。这种设计使得泛型与Java的旧代码兼容,但也带来了一些限制,如无法在运行时获取泛型参数的实际类型。

如何工作

1.类型擦除:

  • 泛型类型 T 会被擦除为其上限类型。例如,List<String>List<Integer> 都会被擦除为 List

  • 如果泛型有一个上限(如 T extends Number),T 会被擦除为这个上限(即 Number)。

2.泛型方法和类型参数:

  • 泛型方法的类型参数在方法内部使用,但在编译后会被擦除。例如,public <T> void method(T param) 经过编译后,T 会被替换为 Object(如果没有明确的上限)。

3.实例化泛型类型:

  • 由于类型擦除,不能直接使用泛型类型进行实例化,如 new T()。泛型类型的创建需要通过反射来完成。

4.类型检查:

  • 泛型擦除确保了在运行时泛型类型的类型安全性,通过编译器的检查来避免类型错误。

示例代码:

  • 编译前:

    public class Box<T> {
        private T value;
    
        public void setValue(T value) {
            this.value = value;
        }
    
        public T getValue() {
            return value;
        }
    }
    
  • 编译后:

    public class Box {
        private Object value;  //泛型类型参数 T 被擦除为 Object
    
        public Box() {
        }
    
        public void setValue(Object value) {  //泛型类型参数 T 被擦除为 Object
            this.value = value;
        }
    
        public Object getValue() {     //泛型类型参数 T 被擦除为 Object
            return value;
        }
    }
    
  • 22
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值