JAVA 泛型

JAVA泛型

泛型的好处

  1. 编译器的检查(类型安全)
  2. 减少了数据类型转换(消除了强制类型转换)

泛型类

泛型类如果没有指定类型,就要按照Object类型来操作;

泛型类不支持基本数据类型,只支持包装类;在编译器的时候,我们会将泛型转换成Object,然后再使用成员的时候,在适当的时候将Object 转换为我们传进来的类型;基本数据类型 不是继承自Object,所以在编译的过程中,底层没办法将int转换成Object 类型来处理。

同一泛型类根据不同的数据类型创建的对象,本质上是同一类型。(内存地址一样)

  • 定义泛型类的语法
package com.hrkj.main.common;

/**
 * 泛型类
 * @param <T> 泛型标识 - 类型参数形参
 */
public class Generic<T> {
    // T 是外部使用类的时候来指定
    private T key;

    public T getKey() {
        return key;
    }

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

    @Override
    public String toString() {
        return "Generic{" +
                "key=" + key +
                '}';
    }
}

  • 泛型类的使用
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
注意:在java1.7之后,后面<>中的数据类型可以省略不写;
类名<具体的数据类型> 对象名 = new 类名<>();

泛型类注意事项

泛型类,如果没有指定具体的数据类型,此时操作类型是Object

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

泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型(内存地址是一样的)

泛型类派生子类

  • 子类也是泛型类,子类和父类的泛型类型要一致;(子类泛型可以扩展)

    class GhildGeneric<T> extends Generic<T>
    // 扩展为
    class GhildGeneric<T,K,V> extends Generic<T> // 但是一定要存在T
    
  • 子类不是泛型类,父类要明确泛型的数据类型

    class GhildGeneric extends Generic<String>
    

泛型接口

泛型接口的使用

  • 实现类不是泛型类,接口需要明确数据类型

    // 泛型接口
    public interface Generate<T>{
    	T getKey();
    }
    
    // 接口实现类不是泛型类,需要明确实现泛型接口的数据类型
    public class Apple implements Generate<String>{ 
    	@Override
        public String getKey(){
            return "hello!";
        }
    }
    
    // 接口实现类是泛型类,要保证实现接口的泛型类泛型标识包含泛型接口的泛型标识(可以扩展)
    public class Pair<T,K> implements Generate<T>{
       private T key;
       private K value;
        public Pair(T key,E value){
            this.key = key;
            this.value = value;
        }
        
        @Override
        public T getKey(){
            return key;
        }
        @Override
        public K getValue(){
            return value;
        }
    }
    
    
    
  • 实现类也是泛型类,实现类和接口的泛型类型要一致

泛型方法

语法结构

​ 修饰符 <T,E,...> 返回值类型 方法名(形参列表){

​ 方法体....

​ }

  • public 与返回值中间的<T>非常重要,可以理解为声明此方法为泛型方法
  • 只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
  • <T> 表明该方法将使用泛型类型 T 此时才可以在方法中使用泛型类型T
  • 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如:T、E、K、V等形式的参数常用于表示泛型
// 定义一个泛型方法;list泛型参数;E泛型标识,具体类型由调用方法的时候来指定
public <E> E getProduct(ArrayList<E> list){
    return list.get(1);
}

即使定义的泛型方法的泛型标识和泛型类的泛型标识是一致的,在使用过程中,这两者是没有任何关系的,是独立存在的。

普通的成员方法如果采用了类的泛型,是不能被声明成静态方法的;如果是泛型方法,可以被声明成静态

// 成员方法使用了类的泛型,不能被定义成静态的 会编译报错
public static T getProduct(ArrayList<T> list){
    return list.get(0);
}
// 泛型方法声明成静态方法,是允许的
public static <T> T getProduct(ArrayList<T> list){
    return list.get(0);
}
//静态的泛型方法,采用多个泛型类型
public static <T,E,K> void printType(T t,E e,K k){
    System.out.println(t+"\t" + t.getClass.getSimpleName());
    System.out.println(e+"\t" + e.getClass.getSimpleName());
    System.out.println(k+"\t" + k.getClass.getSimpleName());
}
// 调用 测试
类名.printType(100,"java",true);

//泛型可变参数的定义
public static <E> void print(E... e){
    for(int i=0;i<e.length;i++){
        System.out.println(e[i]);
    }
}
// 调用 测试
类名.print(1,2,3,4,5,6,7,8);

泛型方法总结:

​ 1: 泛型方法能使方法独立于类而产生变化

​ 2: 如果static方法要使用泛型能力,就必须使其成为泛型方法

类型通配符

什么是类型通配符?

​ 类型通配符一般是用 “?” 来代替具体的类型实参。

​ 所以 类型通配符是类型实参,而不是类型形参。

​ 类对象中的泛型类型不能用多态和继承的思想去理解;比如 Integer extends Number 申明的泛型是Number时 不能传入Integer。使用泛型通配符来定义就可以传入任意类型。

// 使用类型通配符来 配置,可以传入任意类型
public static void act(Person<?> person){
    // 方法体
}

类型通配符上限

​ 什么是类型通配符上限?

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

​ 要求该泛型的类型只能是该实参类型,或实参类型的子类型。

类型通配符的上限 是不可以填充元素的,就算是符合条件的元素也不可以添加,会报错。

因为:采用的上限通配符 在方法中无法确定类型。

// 泛型上限通配符,传递的集合类型只能是父类及其子类的类型;不能是父类的父类类型。
public static void act(ArrayList<? extends Number> list){
    // 在此处需要注意的是:类型通配符的上限 是不可以填充元素的,就算是符合条件的元素也不可以添加,会报错。
    list.add(new Number());// 此处不允许 会报错
    // 方法体
}

类型通配符的下限

语法:

​ 类/接口<? super 实参类型>

要求该泛型的类型只能是是参类型,或实参类型的父类型。

// 类型通配符的下限,要求集合只能是Person或Person的父类类型
public static void act(ArrayList<? super Person> list){
   
    // 方法体
}

扩展:举例JDKTreeSet的用法

​ 源码截图:

image-20211109133658460

 @Test
    public void test02(){
       // TreeSet<Cat> treeSet = new TreeSet<>(new Comparator2()); 正确的
        //TreeSet<Cat> treeSet = new TreeSet<>(new Comparator1()); 正确的
        TreeSet<Cat> treeSet = new TreeSet<Cat>(new Comparator3()); // 报错
        treeSet.add(new Cat("jerry",15));
        treeSet.add(new Cat("amy",20));
        treeSet.add(new Cat("frank",12));
        treeSet.add(new Cat("jim",189));
        treeSet.add(new Cat("jack",6));
        for (Cat cat : treeSet) {
            System.out.println(cat);
        }
    }

    @Data
    class Animal{
        public String name;
        public Animal(String name){
            this.name =name;
        }
    }

    class Cat extends Animal{
        public Integer age;

        public Cat(String name,Integer age) {
            super(name);
            this.age = age;
        }

        @Override
        public String toString() {
            return "Cat{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    class MiniCat extends Cat{
        public Integer level;
        public MiniCat(String name, Integer age,Integer level) {
            super(name, age);
            this.level = level;
        }
    }

    // 根据Animal的名字进行比较
    class Comparator1 implements Comparator<Animal> {
        @Override
        public int compare(Animal o1, Animal o2) {
            return o1.name.compareTo(o2.name);
        }
    }
    // 根据 Cat的age 来进行比较
    class Comparator2 implements Comparator<Cat>{
        @Override
        public int compare(Cat o1, Cat o2) {
            return o1.age-o2.age;
        }
    }
    // 根据MiniCat 的Level 来进行比较
    class Comparator3 implements  Comparator<MiniCat>{
        @Override
        public int compare(MiniCat o1, MiniCat o2) {
            return o1.level-o2.level;
        }
    }

上方 传入new Comparator3() 报错的原因是:

​ 我们采用下限通配符,下限通配符只能传入该类的父类,而不能传入该类的子类;因为我们在构建子类对象的时候一般先构建父类对象。根据父类对象去比较是可以成功的。但是不会初始化子类对象,所以会导致空引用报错。

泛型擦除

​ 泛型是JAVA1.5版本才引进的概念,在此之前是没有泛型的,但是泛型代码能够很好的兼容之前的代码,是因为泛型信息只存在于代码阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为--类型擦除

image-20211109141712408

image-20211109142010913

image-20211109142039163

泛型与数组

  • 可以声明带泛型的数组引用,但是补鞥呢直接创建带泛型的数组对象

  • 可以通过java.lang .reflect.Arraynew Instance(Class<T>,int) 创建Int数组

posted @ 2021-11-09 12:47  Mr_Kenson  阅读(31)  评论(0编辑  收藏  举报