泛型学习笔记

1. 什么是泛型

背景

​ JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,改集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的元素类型,否则很容易引发Class Cast Exception异常【类型转换异常】

 public static void main(String[] args){
        ArrayList list = new ArrayList();
        list.add("java");
        list.add(100);
        list.add(true);

        for (int i = 0; i < list.size(); i++) {
            Object o = list.get(i);
            String str = (String)o;
            //全都当String类型所以报错
            System.out.println(str);
        }
    }

/*报错
	Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')at Test.MainClass.main(MainClass.java:17)
*/

泛型的概念

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

​ 泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-91g4bN2c-1639369072732)(C:\Users\11626\AppData\Roaming\Typora\typora-user-images\image-20211210215836481.png)]

  • 编译期间检查类型
  • 减少数据类型转换

泛型的好处

  • 类型安全
  • 消除了强制类型的转换

2. 泛型类

泛型类的定义语法

class 类名称 <泛型标识,泛型标识,...>{
    private 泛型标识 变量名;
    ......
}
/**
 * 泛型类的定义
 * @param <T> 泛型表示--类型参数
 *           T创建对象的时候指定具体的数据类型
 * */
public class Generic<T>{

    //T,是由外部使用类的时候来指定
    private T key;

    public Generic(T key) {
        this.key = key;
    }

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

    public T getKey() {
        return key;
    }

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

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

使用语法

类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();

Java1.7以后,后面<>中具体的数据类型可以省略不写
类名<具体的数据类型> 对象名 = new 类名<>();
public static void main(String[] args){
    //泛型类在创建对象的时候,来指定操作的具体数据类型
    Generic<String> generic = new Generic<>("abc");
    String key = generic.getKey();
    System.out.println(key);
}

注意点

  • 泛型类在创建对象的时候,没有指定类型,按照Object类操作
  • 不支持基本数据类型(int…)
  • 同一泛型类,根据不同数据类型创建的对象,本质上是同一字节码文件
public static void main(String[] args){
        Generic<String> strGeneric = new Generic<>("abc");
        Generic<Integer> intGeneric = new Generic<>(100);

        System.out.println(strGeneric.getClass() == intGeneric.getClass());
    	//true
 }

例子:年会抽奖器

public class ProductGetter<T> {
    Random random = new Random();

    //奖品
    private T product;

    //奖品池
    private ArrayList<T> list = new ArrayList<>();

    //添加奖品
    public void addProduct(T t){
        list.add(t);
    }

    //抽奖
    public T getProduct(){
        product = list.get(random.nextInt(list.size()));
        return product;
    }
}

从泛型类派生子类

  • 子类也是泛型类,子类和父类的泛型类型要一致
class ChildGeneric<T> extends Generic<T>
  • 子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>

3. 泛型接口

泛型接口的使用

  • 实现类不是泛型类,接口需要明确数据类型
public interface Generator<T>{
    T getKey();
}

public class Generic implements Generator<String >{
    @Override
    public String getKey() {
        return "hello";
    }
}
  • 实现类也是泛型类,实现类和接口的泛型类型要一致
public class Generic<T,E> implements Generator<T>{
    private T key;
    public E value;
    
    @Override
    public T getKey() {
        return key;
    }
    
    public E getValue(){
        return value;
    }
}

4. 泛型方法

​ 在调用方法的时候指明泛型的具体类型

修饰符 <T,E,...> 返回值类型 方法名(形参列表){
    方法体...
}
  • 只有声明了的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法
  • 此处的T可以随便写为任意表示,常见的如T、E、K、V等形式的参数常用语表示泛型

泛型方法调用,类型是通过方法的时候来指定的

public class ProductGetter<T> {    
    Random random = new Random();
    private T product;
    
    //方法的泛型类型和类的泛型类型是不同的
    public <T> T getProduct(ArrayList<T> list){
        return list.get(random.nextInt(list.size()));
    }
}

public static void main(String[] args){
    ProductGetter<Integer> generic = new ProductGetter<>();
    ArrayList<String> list = new ArrayList<>();

    list.add("Phone");
    list.add("PC");
    //返回的是String 而不是Integer
    String product = generic.getProduct(list);
}

可变参数泛型方法

public <T> void print(T... e){
    for (T t : e) {
        System.out.println(t);
    }
}

5. 类型通配符

  • 一般使用 “?” 代替具体的类型实参
  • 是类型实参,而不是类型形参
public static void showKey(Generic<?> generic){
    Object key = generic.getKey();
    System.out.println(key);
}

类型通配符的上限(extends)

语法

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

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

public static void main(String[] args){
    ArrayList<Animal> animals = new ArrayList<>();
    ArrayList<Cat> cats = new ArrayList<>();
    ArrayList<MiniCat> miniCats = new ArrayList<>();

    //animals不可以
    showAnimal(animals);
    //cat类型可以
    showAnimal(cats);
    //cat类型的子类可以
    showAnimal(miniCats);
}

/**\
 * 上限通配符,传递的集合类型,只能是Cat或Cat的子类
 * @param list
 */
public static void showAnimal(ArrayList<? extends Cat> list){
    for (Cat cat : list) {
        System.out.println(cat);
    }
}

注意点:不能填充元素

类型通配符的下限(super)

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

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

public static void main(String[] args){
    ArrayList<Animal> animals = new ArrayList<>();
    ArrayList<Cat> cats = new ArrayList<>();
    ArrayList<MiniCat> miniCats = new ArrayList<>();

    //Cat的父类
    showAnimal(animals);
    //Cat类型可以
    showAnimal(cats);
    //miniCat类型的子类可以
    showAnimal(miniCats);
}

/**\
 * 上限通配符,传递的集合类型,只能是Cat或Cat的子类
 * @param list
 */
public static void showAnimal(ArrayList<? super Cat> list){
    for (Object o : list) {
        System.out.println(o);
    }
}

注意点:能填充元素

6.泛型擦除

概念

​ 泛型是Java1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息值存在与代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,称之为——类型擦除。

无限制类型擦除

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sTn6Bo5D-1639369072733)(C:\Users\11626\AppData\Roaming\Typora\typora-user-images\image-20211211221822835.png)]

有限制类型擦除

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EW5kOsMc-1639369072734)(C:\Users\11626\AppData\Roaming\Typora\typora-user-images\image-20211211222209051.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSR2MVaI-1639369072734)(C:\Users\11626\AppData\Roaming\Typora\typora-user-images\image-20211211222648133.png)]

桥接方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yONTiIrI-1639369072734)(C:\Users\11626\AppData\Roaming\Typora\typora-user-images\image-20211211222708751.png)]

7.泛型与数组(不建议使用,建议使用泛型集合)

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

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

8.泛型与反射

反射常用泛型类

  • Class
  • Constructor
public static void main(String[] args) throws Exception {
    //使用泛型反射创建出相应类型的对象
    Class<Person> personClass = Person.class;
    Constructor<Person> constructor = personClass.getConstructor();
    Person person = constructor.newInstance();

    //不使用泛型反射创建出来的类型是object
    Class personClass1 = Person.class;
    Constructor constructor1 = personClass1.getConstructor();
    Object o = constructor1.newInstance();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值