泛型

1 概述

推出泛型之前,可以构建一个元素类型为Object的集合,可以存储任意数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,后则很容易引发ClassCastException

Java泛型(generics)是JDK5中引入新特性,泛型提供了编译时类型安全监测机制,可以允许我们在编译时监测到非法类型的数据结构

泛型本质就是类型参数化,也就是所操作的数据类型被指定为一个参数,这样类型安全、消除了强制类型的转换

2 泛型类

【格式】

class 类名 <泛型标识1, 泛型标识2, ...> {
    private 泛型标识 变量名;
    ...
}

【示例】

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Generic<T> {
    private T key;
}

常用的泛型标识:T、E、K、V

  • 在创建泛型对象时,不支持基本数据类型,需要使用各自的包装类型

  • 在创建泛型对象时,如果不指定类型,默认使用Object类型

  • 泛型类在逻辑上可以看成是多个不同的类型,实际上是相同类型

    public static void demo1() {
        Generic<String> strGeneric = new Generic<>();
        Generic<Integer> intGeneric = new Generic<>();
        // 打印:true
        System.out.println(strGeneric.getClass() == intGeneric.getClass());
    }
    

2.1 泛型类派生子类

  • 子类也是泛型类,子类和父类的泛型要一致(倘若子类进行泛型扩展,子类也要有一个泛型与父类一致)

    class ChildGeneric<T> extends FatherGeneric<T> {}
    // ok
    class ChildGeneric<T, E, K, V> extends FatherGeneric<T> {}
    // no
    class ChildGeneric<E, K, V> extends FatherGeneric<T> {}
    
  • 子类不是泛型类,父类要明确泛型的数据类型

    class ChildGeneric extends Generic<String> {}
    

3 泛型接口

interface 接口名称 <泛型标识1, 泛型标识2...> {
    泛型标识 方法名();
}
  • 实现类不是泛型类,接口要明确数据类型

  • 实现类也是泛型类型,实现类和接口的泛型类型一致

4 泛型方法

泛型类:在实例化的时候指明泛型的具体类型
泛型方法:在调用方法的时候指明泛型具体类型

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

泛型方法的泛型,与泛型类的泛型,无关系

注意区分泛型方法,和成员方法。如果static方法要使用泛型能力,就必须使其成为泛型方法

public <T> void print(T...list) { // 泛型可变参数
    for(T t : list) {
        System.out.println(t);
    }
}

5 类型通配符

5.1 引子

class Box<T> {
    @Getter
    @Setter
    private T first;
}
public class Test {
    public static void main(String[] args) {
        Box<Number> box1 = new Box<>();
        showBox(box1);
        Box<Integer> box2 = new Box<>();
        showBox(box2);
    }
    public static void showBox(Box<Number> box) {
        Number first = box.getFirst();
        System.out.println(first);
    }
	// 这是重载?????
    public static void showBox(Box<Integer> box) {
        Number first = box.getFirst();
        System.out.println(first);
    }
}

上面的代码是有问题的,出在了showBox(box2)身上,因为参数类型不匹配,需要的是Box<Number>,而box2的类型是Box<Integer>。那如何解决问题?再写一个接收Box<Intger>类型的同名showBox方法,进行重载?
这是不行的。虽然Box<Number>Box<Integer>看似类型不同,实则是同一类型,重载的话,要求参数类型不同,显然不符合重载要求。
那么如何解决???

5.2 类型通配符

// ?即为类型通配符
public static void showBox(Box<?> box) {
    Object first = box.getFirst();
    System.out.println(first);
}
  • 类型通配符一般使用?代替具体的类型实参
  • 类型通配符是类型实参,而不是类型形参

5.3 类型通配符的上限

类/接口 <? extends 实参类型>
// 通过? extends Number 表明类型通配符的上限
public static void showBox(Box<? extends Number> box) {
    Number first = box.getFirst();
    System.out.println(first);
}

? 代表任意类型,但任意类型范围太广,倘若我们知道各式各样的泛型类型的上限,那么可以通过上述的写法加以限定。
比如,我可能创建一堆的Box对象,传递的泛型的类型,可能是Integer、Float、Double、Long或者Number类型,虽然可以统一用通配符?一招解决,但同样会存在String类型的泛型也会被接受。
我现在不想String或者其他非数字类型的Box去执行showBox的方法,这时会发现Integer、Float、Double、Long都有统一的父类Number,那么就可以使用上面的写法进行限定:只有泛型类型是Number类型,及Number子类可以使用这个方法

使用泛型通配符上限,对于集合是无法填充元素的

// MiniCat extends Cat,Cat extends Animal
public static void showAnimal(ArrayList<? extends Cat> list) {
    list.add(new Cat());
    list.add(new MiniCat());
    list.add(new Animal());
}

image
上面的三行代码都不允许。因为该ArrayList的泛型上限是Cat,并没有具体指明是Cat还是MiniCat还是其他Cat的子类,故而无法进行如此确定的添加

5.2 类型通配符的下限

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

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

以TreeSet为例

// 省略toString和构造
class Animal {
    String name;
}
// 省略toString和构造
class Cat extends Animal{
    Integer age;
}
// 省略toString和构造
class MiniCat extends Cat{
    Integer level;
}
class Comparator1 implements Comparator<Animal> {
    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.name.compareTo(o2.name);
    }
}
class Comparator2 implements Comparator<Cat> {
    @Override
    public int compare(Cat o1, Cat o2) {
        return o1.age - o2.age;
    }
}
class Comparator3 implements Comparator<MiniCat> {
    @Override
    public int compare(MiniCat o1, MiniCat o2) {
        return o1.level - o2.level;
    }
}

image

public static void demo1() {
    TreeSet<Cat> treeSet1 = new TreeSet<>(new Comparator1());
    TreeSet<Cat> treeSet2 = new TreeSet<>(new Comparator2());
    TreeSet<Cat> treeSet3 = new TreeSet<>(new Comparator3());
}

从图中可以知道,TreeSet的构造方法传递的Comparator泛型下限是E,在上面代码的示例中E是Cat类,这就意味着treeSet3这一行是有误的。

6 类型擦除

泛型是JDK1.5引进的概念,泛型代码能够很好的和之前版本的代码兼容。那是因为,泛型信息只存在于代码的编译阶段,进入JVM之前,与泛型相关的信息会被擦除,称之为类型擦除

6.1 无限制类型擦除

image

class Box<T> {
    private T first;
}
public static void demo2() {
     Box<Integer> box = new Box<>();
     Class<? extends Box> boxClass = box.getClass();
     Field[] fields = boxClass.getDeclaredFields();
     for (Field field : fields) {
         System.out.println(field.getName() + ":" + field.getType().getSimpleName());
     }
}

image

6.2 有限制的类型擦除

image

class Box<T extends Number> {
    private T first;
}
// 测试代码不变

image

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

image

class Box<T extends Number> {
    private T first;
	
    public E getFirst() { return first; }

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

    public <T extends List> T show(T t) { return t; }
}
public static void demo3() {
    Box<Integer> box = new Box<>();
    Class<? extends Box> clz = box.getClass();
    Method[] methods = clz.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName());
    }
}

image

6.4 桥接方法

image

7 泛型数组

泛型数组的创建:

  • 可以声明带泛型的数组引用,但不能直接创建带泛型的数组对象
// 不可行
ArrayList<String>[] arr = new ArrayList<String>[5];
// 可行
ArrayList<String>[] arr;
// 可行
ArrayList<String>[] arr = new ArrayList[5];
  • 可以通过java.lang.reflect.Array的newInstance(Class<T>, int)创建T[]数组
class Fruit<T> {
    // 不可行
    private T[] arr = new T[5];
}
class Fruit<T> {
    private T[] arr;

    public Fruit(Class<T> clz, int len) {
        // 创建泛型数组
        arr = (T[]) Array.newInstance(clz, len);
    }

}
posted @ 2021-08-26 15:40  silverbeats  阅读(64)  评论(0编辑  收藏  举报