深入理解什么Java泛型?

Java泛型

什么是泛型?

  • 泛型是jdk1.5的新特性,泛型提供了编译时数据类型安全监测机制,泛型在编译时检测出非法的类型数据结构

  • 泛型本质时参数化类型,指在操作数据类型时被指定为一个参数

  • 优点:

    • 数据安全

      泛型提供了编译时数据类型安全监测机制,一旦创建对象指定了类型,该对象只能是使用该类型的数据

    • 不用强制类型转化

      在没有使用泛型之前,使用的是Object,可以存放多种数据类型,但是使用时必须强制转化类型,否则会出现ClassCastException异常

泛型类与接口

泛型类

  • 语法:

    public class 类名<泛型标识,泛型标识...>{
        private 泛型标识 变量名;
        ...
    }
    
  • 常用的泛型标识:T E K V

  • 创建泛型

    • jdk1.7之前泛型

      ArrayList<具体的数据类型> 变量名 = new ArrayList<具体的变量名>();
      
    • JDK1.7之后

      ArrayList<具体的数据类型> 变量名 = new ArrayList<>();
      
  • 泛型类注意事项:

    • 1,泛型的类型只能是类类型,不能是基本数据类型

    • 2,在使用泛型时,如果没有指定数据类型,默认使用Object

      List list = new ArrayList();
      list.add("小明");
      Object o = list.get(0);
      
    • 3,泛型类型在逻辑上可以看出是多个不同的类型,但实际上是相同类型

      Animal<Cat> cat = new Animal<>(new Cat("小明"));
      Animal<Dog> dog = new Animal<>(new Dog("夏冬"));
      System.out.println("cat:"+cat.getClass());
      System.out.println("dog:"+dog.getClass());
      System.out.println(cat.getClass() == dog.getClass());
      运行结果:
      cat:class com.example.test.Animal
      dog:class com.example.test.Animal
      true
      

泛型派生子类

  • 子类也是泛型,父类是泛型,子类与父类的泛型一致

    • 例如:子类与父类泛型都是T

      public class Cat<T> extends Animal<T>{
          public Cat(T t) { super(t); }
      }
      
    • 子类进行泛型拓展,但子类泛型中必须含有父类泛型

      public class Cat<T,K,V> extends Animal<T>{
          public Cat(T t) { super(t); }
          Map<K,V> map = new HashMap<>();
      }
      
  • 子类不是泛型,父类要指定具体的数据类型,如果不指定,默认为Object

    public class Cat extends Animal<String>{
        public Cat(String s) { super(s);}
    }
    

泛型接口

  • 语法:

    public interface 接口名称<泛型标识>{
        泛型标识 方法名();
    }
    
  • 与泛型派生子类相似。实现类不是泛型,泛型接口必须指定具体的数据类型;实现类是泛型,泛型接口与实现类的泛型标识一致

泛型方法

  • 泛型方法中,泛型类型是在泛型方法被调用时明确的

  • 泛型类中,泛型类型是在创建泛型类对象时指定具体的数据类型。

    public class Animal<T> {
        private T t;
        //泛型类的成员方法
        public T getT() { return t;}
        //泛型类的泛型方法
        public <T> T getT(T t){ return t;}
        public void setT(T t) { this.t = t;}
        public static void main(String[] args) {
            //测试泛型类的成员方法
            String s = "张三";
            Animal<String> stringAnimal = new Animal<>();
            stringAnimal.setT(s);
            System.out.println(stringAnimal.getT());
            //测试泛型类的泛型方法
            Integer t = stringAnimal.getT(11);
            System.out.println(t);
        }
    }
    运行结果:
        张三
        11
    再次证明了,泛型方法中,泛型类型在方法调用时指定
    
  • 语法:

    修饰符 <泛型标识,泛型标识,...> 返回值类型 方法名称(泛型标识,泛型标识,...){
    	方法体    
    }
    
    • 修饰符与返回值类型之间的,表示此方法为泛型方法。
    • 表示方法中使用了泛型类型T,这时方法中才可以使用泛型类型T
    • 只有使用了的方法才算泛型方法,泛型类中使用的成员方法不是泛型方法
    • 泛型标识可以是T,E,K,V

可变泛型方法

修饰符 <T> 返回值类型 方法名称(T... t){
	方法体    
}

类型通配符

一般采用作为类型通配符,类型通配符只能是实参类型,不能是形参类型

类型通配符上限

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

  • 类型通配符:只能是实参类型或者是实参类型的子类

  • 示例:

    public class Animal {
    
        class Cat extends Animal{ }
    
        class Dog extends Cat{}
    
        public static void main(String[] args) {
            ArrayList<Cat> cats = new ArrayList<>();
            ArrayList<Dog> dogs = new ArrayList<>();
            ArrayList<Animal> animals = new ArrayList<>();
            show(cats);show(dogs);show(animals);×
        }
        public static void show(ArrayList<? extends Cat> list){ 
        	list.add(new Cat());×
        }
    }
    
    • 由于animel是cat的父类,超过了类型通配符的上限,所有报错
    • 类型统配符的实参类型不能直接使用list.add添加,因为通配符并不知道添加的数据的上限

类型通配符下限

  • 语法:类/接口<? super 实参类型>

    • 类型通配符:只能是实参类型或者实参类型的父类
  • 示例:

    public class Animal {
    
        class Cat extends Animal{ }
    
        class Dog extends Cat{}
    
        public static void main(String[] args) {
            ArrayList<Cat> cats = new ArrayList<>();
            ArrayList<Dog> dogs = new ArrayList<>();
            ArrayList<Animal> animals = new ArrayList<>();
            show(cats);//√
            show(dogs);//√
            show(animals);//×
        }
        public static void show(ArrayList<? extends Cat> list){
     		list.add(new Cat());//√编译通过
          list.add(new Dog());//√编译通过
        }
    }
    
    

    **注意:虽然类型通配符下限可以添加元素,但是并不保证数据类型的约束要求**

类型擦除

泛型是JDK1.5之后的特性,但是泛型代码能很好的与之前的代码兼容,主要是因为泛型信息只存在于编译时期,在进入JVM之前,与泛型相关的信息会被擦除。称之为类型擦除

深入理解泛型擦除

直接上代码

/**
 * <h2>简单理解泛型</h2>
 */
private static void easyUse() throws Exception{
    List<String> left = new ArrayList<>();
    List<Integer> right = new ArrayList<>();

    System.out.println(left.getClass());
    System.out.println(left.getClass()==right.getClass());
}

运行结果

class java.util.ArrayList
true

为什么List left 与List right 类型相等?在获取left.getClass()并没有反应到它是一个String类型?

这是因为泛型的一个重要概念:类型擦除

大家都是Java的泛型其实是伪泛型,Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型的概念吗,首先要理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中不包含泛型的类型信息的。使用泛型时,加上类型参数,在编译器编译时,会被去掉。这个过程被称为类型擦除

所以说,虽然我们指定了left是String类型,right是Integer类型,但是实际上在编译器编译时,它们的类型被擦除掉了。也就是在编译后,left与right的类型都会变成List,JVM看到的也仅仅是List。而泛型附加的类型信息JVM时看不到的,所以Java的编译器在编译时尽可能去发现可能出现的错误,但是无法在运行时对类型转换异常这个情况进行捕获,同时类型擦除也是Java泛型与C++模板机制实现方式重要的区别,java的泛型是伪泛型,而C++模板机制是真泛型。

无限制类型擦除

  • 代码示例

    public class Animal<T> {
        private T t;
    
        public T getT() { return t;}
    
        public void setT(T t) { this.t = t; }
    
        public static void main(String[] args) {
            Animal<String> stringAnimal = new Animal<>();
            //获取字节码文件
            Class<? extends Animal> aClass = stringAnimal.getClass();
            // 获取所有Animal<String>的属性
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                System.out.println(declaredField.getName()+":"+declaredField.getType().getSimpleName());
            }
        }
    }
    
    运行结果:
        t:Object
    

有限制类型擦除

  • 代码示例:

    public class Animal<T extends Number> {
        private T t;
    
        public T getT() { return t;}
    
        public void setT(T t) { this.t = t; }
    
        public static void main(String[] args) {
            Animal<Integer> stringAnimal = new Animal<>();
            //获取字节码文件
            Class<? extends Animal> aClass = stringAnimal.getClass();
            // 获取所有Animal<String>的属性
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                System.out.println(declaredField.getName()+":"+declaredField.getType().getSimpleName());
            }
        }
    }
    运行结果:
        t:Number
    

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

  • 代码示例:

    public class Animal<T> {
        private T t;
    
        public T getT() { return t;}
    
        public void setT(T t) { this.t = t; }
    
        public <T extends Number> T getValue(T t){
            return t;
        }
    
        public static void main(String[] args) {
            Animal<Integer> stringAnimal = new Animal<>();
            stringAnimal.getValue(100);
            //获取字节码文件
            Class<? extends Animal> aClass = stringAnimal.getClass();
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                System.out.println(method.getName()+":"+method.getReturnType().getSimpleName());
            }
        }
    }
    运行结果:
        main:void
        getValue:Number//方法中类型定义的参数擦除
        setT:void
        getT:Object
    
    

桥接擦除

  • 代码示例

    public interface Animal<T> {
        public T getT(T t);
        class Cat implements Animal<Integer> {
    
            @Override
            public Integer getT(Integer integer) {
                return integer;
            }
        }
        public static void main(String[] args) {
            Animal<Integer> stringAnimal = new Cat();
            //获取字节码文件
            Class<? extends Animal> aClass = stringAnimal.getClass();
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                System.out.println(method.getName()+":"+method.getReturnType().getSimpleName());
            }
        }
    }
    运行结果:
        getT:Integer
        getT:Object
    

泛型数组

方式一:可以申明带泛型数组的引用,不能直接创建带泛型数组的对象

  • 解决方法:

    创建原生的泛型数组,引用给泛型数组

  • 代码示例:

    ArrayList<String>[] arrayLists = new ArrayList[5];
    ArrayList<String> list  = new ArrayList<>();
    list.add("小明");
    arrayLists[0] = list;
    System.out.println(arrayLists[0].get(0));
    运行结果:
        小明
    

方式二:采用java.lang.reflect.Array.newInstance(Class<?> componentType, int length)

  • 代码示例
public class Cat<T> {
    private T[] t;

    public Cat(Class<T> clz,int length) {
      t = (T[]) Array.newInstance(clz, length);
    }

    /**
     * 获取指定下标的元素
     * @param index
     * @return
     */
    public T getT(int index){
        return t[index];
    }

    /**
     * 向指定下标的存放元素
     * @param t
     * @param index
     */
    public void put(T t,int index){
       this.t[index] = t;
    }

    /**
     * 获取泛型数组
     * @return
     */
    public T[] getArrays(){
        return t;
    }

    public static void main(String[] args) {
        Cat<String> stringCat = new Cat<>(String.class,3);
        stringCat.put("香蕉", 0);
        stringCat.put("苹果",1 );
        stringCat.put("柠檬", 2);
        System.out.println("下标为2的元素:"+stringCat.getT(2));
        System.out.println("------------------------");
        System.out.println(Arrays.toString(stringCat.getArrays()));
    }
}
运行结果:
    下标为2的元素:柠檬
    ------------------------
    [香蕉, 苹果, 柠檬]

泛型与反射

  • 代码示例:

    Class<? extends Dog> aClass = new Dog<String>().getClass();
    Constructor<?>[] constructors = aClass.getConstructors();
    Class<TestAnimal> testAnimalClass = TestAnimal.class;
    Constructor<TestAnimal> constructor = testAnimalClass.getConstructor(testAnimalClass);
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值