3.1_19 JavaSE入门 P18 【泛型】各类泛型对象、通配符、类型擦除

相关链接



P18 【泛型】各类泛型对象、通配符、类型擦除


1 什么是泛型


背景: Java推出泛型之前,可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要明确指定存储的每个元素的数据类型,否则很容易引发ClassCastException异常。

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

   泛型的本质就是参数化类型,也就是所操作的数据局类型被指定为一个参数,消除了强制类型转换异常的风险。

泛型类: 实例化类的时候指明泛型的具体类型

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

类型擦除: JDK5之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。这是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,称之为–类型擦除


案例代码一泛型产生的背景

package com.groupies.base.day18;

import java.util.ArrayList;

/**
 * @author GroupiesM
 * @date 2021/06/04
 * @introduction 泛型产生的背景
 *
 * 泛型:
 *     1.类型安全
 *     2.消除了强制类型转换异常的风险
 *
 * 形参:E
 *      public class ArrayList<E> extends AbstractList<E>
 * 实参:String
 *     ArrayList<String> strList = new ArrayList<>();
 */
public class Demo1Type {
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();
        list.add("java");
        list.add(100);
        list.add(true);

        for (Object o : list) {
            String s = null;
            try {
                s = (String) o;
            } catch (ClassCastException e) {
                //Exception in thread "main" java.lang.ClassCastException .....
                //e.printStackTrace();
            }
            System.out.println(s);//java null null
        }

        /** 泛型:
         *      1.编译期间检查类型
         *      2.减少了数据类型转换
         */
        ArrayList<String> strList = new ArrayList<>();
        strList.add("a");
        strList.add("b");
        //strList.add(5);
        for (String s : strList) {
            System.out.println(s);//a b
        }

        ArrayList<Integer> intList = new ArrayList<>();
        intList.add(100);//自动装箱
        intList.add(200);
        //intList.add("300");
        for (Integer integer : intList) {
            int num = integer;//自动拆箱
            System.out.println(num);//100 200
        }
    }
}

2 泛型类、接口库


泛型类: 实例化类的时候指明泛型的具体类型


2.1 泛型类定义语法


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

//实例
class Generic<T>{
    private T key;
    ....
}

2.2 常用泛型标识


TEKV :四种标识符都代表泛型,没有任何区别,在使用多个参数时,可以用来区分各个泛型参数。


2.3 使用语法


类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
类名<具体的数据类型> 对象名 = new 类名<>();//Java1.7以后,后面的<>中的具体的数据类型可以省略不写

//实例
Generic<String> 对象名 = new Generic<>();

2.4 泛型类注意事项


  • 泛型类,如果没有指定具体的数据类型,此时,操作类型是Object
  • 泛型的类型参数只能是类类型,不能是基本数据类型
  • 泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同的类型

案例代码二定义一个泛型类 【a.泛型类】

package com.groupies.base.day18;

/**
 * @author GroupiesM
 * @date 2021/06/04
 * @introduction 定义一个泛型类 泛型类
 * @param <T> 泛型标识--类型形参
 *         T 创建对象的时候这里制订具体的数据类型--类型实参
 */
public class Demo2Type<T> {
    //T 是由外部使用本类的时候来指定的
    private T key;

    public Demo2Type() {}

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

    public T getKey() {
        return key;
    }

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

案例代码二定义一个泛型类 【b.测试类】

package com.groupies.base.day18;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 定义一个泛型类-测试类
 *
 *  1.泛型类在创建的时候,指定操作的具体类型
 *  2.泛型类在创建对象的时候,没有指定类型,将按照Object类型来操作
 *  3.泛型类,不支持基本数据类型
 *  4.同一泛型类,根据不同的数据类型,创建的对象,本质上是同一类型
 */
public class Demo2Test {
    public static void main(String[] args) {
        //1.泛型类在创建的时候,指定操作的具体类型
        Demo2Type<String> strType = new Demo2Type<>("abc");
        String key1 = strType.getKey();
        System.out.println("key1:" + key1);

        System.out.println("------------------------------");

        Demo2Type<Integer> integerType = new Demo2Type<>(5);
        Integer key2 = integerType.getKey();
        System.out.println("key2:" + key2);

        System.out.println("------------------------------");

        //2.泛型类在创建对象的时候,没有指定类型,将按照Object类型来操作
        Demo2Type objType = new Demo2Type<>("ABC");
        Object key3 = objType.getKey();
        System.out.println("key3:" + key3);

        //3.泛型类,不支持基本数据类型
        //Demo2Type<int> inteType = new Demo2Type<>(5);

        System.out.println("------------------------------");

        //4.同一泛型类,根据不同的数据类型,创建的对象,本质上是同一类型
        System.out.println(strType.getClass());//class com.groupies.base.day18.Demo2Type
        System.out.println(integerType.getClass());//class com.groupies.base.day18.Demo2Type
        System.out.println(objType.getClass());//class com.groupies.base.day18.Demo2Type
    }
}

2.5 案例:泛型类-使用


  • 需求:通过泛型实现一个公司年会抽奖功能

案例代码三泛型类-使用 【a.泛型类】

package com.groupies.base.day18;

import java.util.ArrayList;
import java.util.Random;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 抽奖器 泛型类
 */
public class Demo3ProductGetter<T> {
    Random r = new Random();//随机抽奖

    private T product;//奖品

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

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

    //抽奖
    public T getProduct() {
        T remove = list.remove(r.nextInt(list.size()));//随机移除一个奖品
        return remove;
    }

    //显示奖品池
    public String show() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i));
            if (i!=list.size()-1){
                sb.append("、");
            }
        }
        return sb.toString();
    }
}

案例代码三泛型类-使用 【b.测试类】

package com.groupies.base.day18;

import java.util.Scanner;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction
 */
public class Demo3Main {
    //创建抽奖器对象,指定数据类型
    static Demo3ProductGetter<String> stringProductGetter = new Demo3ProductGetter<>();
    static Demo3ProductGetter<Integer> integerProductGetter = new Demo3ProductGetter<>();

    //填充奖池
    static {
        int[] intProdudcts = {1000, 5000, 3000, 300,30000000};
        for (int i = 0; i < intProdudcts.length; i++) {
            integerProductGetter.addProduct(intProdudcts[i]);
        }

        String[] strProducts = {"苹果手机", "华为手机", "扫地机器人", "咖啡机"};
        for (int i = 0; i < strProducts.length; i++) {
            stringProductGetter.addProduct(strProducts[i]);
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (stringProductGetter.list.size() > 0 | integerProductGetter.list.size() > 0) {
            //while (true) {
            System.out.print("剩余【物品】奖品:" + stringProductGetter.show() + "\r\n");
            System.out.print("剩余【现金】奖品:" + integerProductGetter.show() + "\r\n");

            System.out.println("请输入抽奖类型,1:物品 2:现金");
            int i = sc.nextInt();
            if (i == 1 && stringProductGetter.list.size() > 0) {
                getPrize();
            } else if (i == 2 && integerProductGetter.list.size() > 0) {
                getCash();
            } else {
                System.out.println("-------------");
                System.out.println("抽奖失败,请重新输入");
                System.out.println("-------------");
            }
        }

    }

    //抽取奖品
    private static void getPrize() {
        //抽取奖品
        String product = stringProductGetter.getProduct();
        System.out.println("-------------");
        System.out.println("恭喜您,您抽到了" + product);
        System.out.println("-------------");
        if (stringProductGetter.list.size() == 0 && integerProductGetter.list.size() == 0){
            System.out.println("已抽完全部奖品,活动结束");
        }
    }

    //抽取现金
    private static void getCash() {
        //抽取奖品
        Integer product = integerProductGetter.getProduct();
        System.out.println("-------------");
        System.out.println("恭喜您,您抽到了" + product + "元");
        System.out.println("-------------");
        if (stringProductGetter.list.size() == 0 && integerProductGetter.list.size() == 0){
            System.out.println("已抽完全部奖品,活动结束");
        }
    }
}

2.6 泛型类-子类


  • a. 子类泛型类,子类和父类的泛型类型要一致

  • b. 子类不是泛型类,父类要明确泛型的数据类型

//a. 子类也是泛型类,子类和父类的泛型类型要一致
 public class Parent<E>
     
 // class ChildGeneric<E> extends Generic<T> //报错
 // class ChildGeneric<T,E> extends Generic<T,E> //报错
 class ChildGeneric<T> extends Generic<T>
 class ChildGeneric<E> extends Generic<E>
 class ChildGeneric<T,E,K,V> extends Generic<T>

     
//b. 子类不是泛型类,父类要明确泛型的数据类型
 class ChildGeneric extends Generic<String>

案例代码四泛型类-子类 【a.泛型父类】

package com.groupies.base.day18.demo4;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 泛型父类
 */
public class Parent<E> {
    private E value;

    public E getValue() {
        return value;
    }

    public void setValue(E value) {
        this.value = value;
    }
}

案例代码四泛型类-子类 【b.泛型子类1】

package com.groupies.base.day18.demo4;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction
 *
 * a. 子类是泛型类,子类和父类的泛型类型要一致
 */
//public class ChildFirst<T> extends Parent <E>{//报错
public class ChildFirst<T> extends Parent <T>{
    @Override
    public T getValue() {
        return super.getValue();
    }
}

案例代码四泛型类-子类 【b.泛型子类2】

package com.groupies.base.day18.demo4;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 泛型子类 子类2
 *
 * b.子类不是泛型类,父类要明确泛型的数据类型
 */
public class ChiledSecond extends Parent<Integer> {
    @Override
    public Integer getValue() {
        return super.getValue();
    }

    @Override
    public void setValue(Integer value) {
        super.setValue(value);
    }
}

案例代码四泛型类-子类 【c.测试类】

package com.groupies.base.day18.demo4;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 泛型子类 测试类
 */
public class Test {
    public static void main(String[] args) {
        //a. 子类是泛型类,子类和父类的泛型类型要一致
        ChildFirst<String> childFirst = new ChildFirst<>();
        childFirst.setValue("abc");
        String value1 = childFirst.getValue();
        System.out.println(value1);

        System.out.println("------------------------------");

        //b.子类不是泛型类,父类要明确泛型的数据类型
        ChiledSecond childSecond = new ChiledSecond();
        childSecond.setValue(100);
        Integer value2 = childSecond.getValue();
        System.out.println(value2);
    }
}

2.7 泛型类-接口


  • a. 实现类泛型类,实现类和接口的泛型类型要一致

  • b. 实现类不是泛型类,接口明确数据类型


案例代码五泛型类-接口 【a.泛型接口】

package com.groupies.base.day18.demo5;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 泛型接口
 */
public interface Generator<T> {
    T getKey();
}

案例代码五泛型类-接口 【b.泛型接口实现1】

package com.groupies.base.day18.demo5;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 泛型接口 实现2
 *
 * a. 实现类是泛型类,实现类和接口的泛型类型要一致
 */
public class AppleT<T, E> implements Generator<T> {

    private T key ;
    private E value ;

    public AppleT() {
    }

    public AppleT(T key, E value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public T getKey() {
        return key;
    }

    public E getValue(){
        return value;
    }
}

案例代码五泛型类-接口 【b.泛型接口实现2】

package com.groupies.base.day18.demo5;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 泛型接口 实现1
 *
 * b. 实现类不是泛型类,接口要明确数据类型
 */
public class AppleString implements Generator<String>{
    @Override
    public String getKey() {
        return "hello generic";
    }
}

案例代码五泛型类-接口 【c.泛型测试】

package com.groupies.base.day18.demo5;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 泛型接口 测试
 */
public class Test {
    public static void main(String[] args) {
        AppleString appleString = new AppleString();
        String key1 = appleString.getKey();
        System.out.println(key1);//hello generic

        AppleT<String, Integer> appleT = new AppleT<>("苹果",5);
        String key = appleT.getKey();
        Integer value = appleT.getValue();
        System.out.println(key + "==" + value);//苹果==5
    }
}

3 泛型方法⭐️


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

a.调用泛型方法:public <E> E getProduct(ArrayList<E> arr)

b.调用静态泛型方法:public static <T, E> void printType(T t, E e)

c.泛型方法的可变参数:public static <E> void print(E...e) ⭐️

总结: 泛型方法能使方法独立于类而产生的变化,如果static方法要使用泛型能力,就必须使其成为泛型方法。


  • 语法

    修饰符<T,E,...> 返回值类型 方法名(形参列表){
        方法体...
    }
    
    //a.调用泛型方法
    public <E> E getProduct(ArrayList<E> arr) {
        return arr.get(r.nextInt(arr.size()));
    }
    
    //b.调用静态泛型方法
    public static <T, E> void printType(T t, E e) {
        System.out.println(t + "\t" + t.getClass().getSimpleName());
        System.out.println(e + "\t" + e.getClass().getSimpleName());
    }
    
    //c.泛型方法的可变参数
    public static <E> void print(E...e){
        for (E e1 : e) {
            System.out.println(e1);
        }
    }
    

  • 注意事项
    • public与返回值中间<T>非常重要,可以理解为声明此方法为泛型方法
    • 只有声明了<T>的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法
    • <T>表名该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T
    • 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型

案例代码六泛型方法 【a.泛型类】

package com.groupies.base.day18.demo06;

import java.util.ArrayList;
import java.util.Random;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 抽奖器 泛型类
 */
public class ProductGetter<T> {
    Random r = new Random();//随机抽奖

    private T product;//奖品

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

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

    //抽奖
    public T getProduct() {
        T remove = list.remove(r.nextInt(list.size()));//随机移除一个奖品
        return remove;
    }

    //显示奖品池
    public String show() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i));
            if (i != list.size() - 1) {
                sb.append("、");
            }
        }
        return sb.toString();
    }

    /**
     * @introduction a.调用泛型方法
     * @param arr
     * @param <E> 泛型标识,表示泛型方法,具体类型在调用方法时指定
     * @return E 泛型类型
     */
    public <E> E getProduct(ArrayList<E> arr) {
        return arr.get(r.nextInt(arr.size()));
    }

    /**
     * @introduction b.调用静态泛型方法
     * @param t
     * @param e
     * @param k
     * @param <T> 泛型标识
     * @param <E> 泛型标识
     * @param <K> 泛型标识
     */
    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());
    }

    /**
     * @introduction c.泛型方法的可变参数
     * @param e
     * @param <E> 泛型标识
     */
    public static <E> void print(E...e){
        for (E e1 : e) {
            System.out.println(e1);
        }
    }
}

案例代码六泛型方法 【b.测试类】

package com.groupies.base.day18.demo06;

import java.util.ArrayList;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 泛型方法的使用
 *
 *  a.调用泛型方法
 *  b.调用静态泛型方法
 *  c.泛型方法的可变参数
 */
public class Test {
    public static void main(String[] args) {
        ProductGetter<Integer> productGetter = new ProductGetter<>();

        ArrayList<String> strList = new ArrayList<>();
        strList.add("笔记本电脑");
        strList.add("苹果手机");
        strList.add("扫地机器人");

        //a.调用泛型方法,类型是通过调用方法的时候来指定的
        String product1 = productGetter.getProduct(strList);
        //XXX String
        System.out.println(product1 + "\t" + product1.getClass().getSimpleName());

        System.out.println("-------------------------------");

        ArrayList<Integer> integerList = new ArrayList<>();
        integerList.add(30);
        integerList.add(50);
        integerList.add(80);
        //a.调用泛型方法,类型是通过调用方法的时候来指定的
        Integer product2 = productGetter.getProduct(integerList);
        //XXX Integer
        System.out.println(product2 + "\t" + product2.getClass().getSimpleName());

        System.out.println("-------------------------------");

        /** b.调用静态泛型方法
         * 100	Integer
         * java	String
         * true	Boolean
         */
        ProductGetter.printType(100, "java", true);

        System.out.println("-------------------------------");

        /** c.泛型方法的可变参数
         * 1
         * a
         * true
         * []
         * [I@63961c42
         */
        ProductGetter.print(1, "a", true, new ArrayList<>(), new int[3]);
    }
}

4 类型通配符 ? ⭐️


  • 类型通配符: 类型通配符一般是使用"?"代替具体的类型实参,所以,类型通配符是类型实参,而不是类型形参

4.1 类型通配符的使用


案例代码七类型通配符 【a.泛型类】

package com.groupies.base.day18.demo07;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 类型通配符 泛型类
 */
public class Box <E>{
    private E first;

    public E getFirst() {
        return first;
    }

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

案例代码七类型通配符 【b.测试类】

package com.groupies.base.day18.demo07;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 类型通配符 测试类
 */
public class Test {
    public static void main(String[] args) {
        Box<Number> box1 = new Box<>();
        box1.setFirst(100);
        showBox(box1);

        //Integer继承自number,在showBox方法传入Number子类对象不能触发多态
        Box<Integer> box2 = new Box<>();
        box2.setFirst(200);
        showBox(box2);//Box<Number>接收Integer时 => java: 不兼容的类型
    }

    /*
    public static void showBox(Box<Number> box){
        Number first = box.getFirst();
        System.out.println(first);
    }
    */

    //both method have same erasure 参数类型都为Box,所以不能触发重载
    /*public static void showBox(Box<Integer> box){}*/

    //解决方案-> 泛型通配符 ? 代表 任意类型
    public static void showBox(Box<?> box){
        Object first = box.getFirst();
        System.out.println(first);
    }
}

4.2 类型通配符上限


  • 语法

    /接口<? extends 上限实参类型>	//要求该泛型的类型,只能是指定实参类型,或指定实参类型的子类类型
        
    //通配符上限实例
    public static void showAnimal(ArrayList<? extends Cat> list) {
        for (Cat cat : list) {
            System.out.println(cat);
        }
    }  
    

案例代码八类型通配符上限 【a.Animal类】

package com.groupies.base.day18.demo08;

public class Animal { }

案例代码八类型通配符上限 【a.Cat类】

package com.groupies.base.day18.demo08;

public class Cat extends Animal{ }

案例代码八类型通配符上限 【a.MiniCat类】

package com.groupies.base.day18.demo08;

public class MiniCat extends Cat { }

案例代码八类型通配符上限 【b.测试类】

package com.groupies.base.day18.demo08;

import java.util.ArrayList;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 类型通配符 测试类
 */
public class Test {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<>();
        ArrayList<Cat> cats = new ArrayList<>();
        ArrayList<MiniCat> miniCats = new ArrayList<>();

        //showAnimal(animals); //超出类型通配符上限,编译不通过
        showAnimal(cats);
        showAnimal(miniCats);

        //public boolean addAll(Collection<? extends E> c)
        //cats.addAll(animals); //超出类型通配符上限,编译不通过
        cats.addAll(cats);
        cats.addAll(miniCats);
    }

    /**
     * @introduction 指定通配符上限
     *              泛型上线通配符,传递的集合类型,只能是Cat或Cat的子类
     * @param list
     */
    public static void showAnimal(ArrayList<? extends Cat> list) {
        //list.add(new Cat());      //类型通配符上限 =>不能添加集合元素
        //list.add(new MiniCat());  //类型通配符上限 =>不能添加集合元素
        for (Cat cat : list) {
            System.out.println(cat);
        }
    }
}

4.3 类型通配符下限


  • 语法

    /接口<? extends 上限实参类型>	//要求该泛型的类型,只能是指定实参类型,或指定实参类型的父类类型
        
    //通配符下限实例
    public static void showAnimal(ArrayList<? super Cat> list) {
        for (Cat cat : list) {
            System.out.println(cat);
        }
    }  
    
    

案例代码九类型通配符下限 【a.Animal类】

package com.groupies.base.day18.demo09;

public class Animal { }

案例代码九类型通配符下限 【a.Cat类】

package com.groupies.base.day18.demo09;

public class Cat extends Animal{ }

案例代码九类型通配符下限 【a.MiniCat类】

package com.groupies.base.day18.demo09;

public class MiniCat extends Cat { }

案例代码九类型通配符下限 【b.测试类】

package com.groupies.base.day18.demo09;

import java.util.ArrayList;

/**
 * @author GroupiesM
 * @date 2021/06/07
 * @introduction 类型通配符 测试类
 */
public class Test {
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList<>();
        ArrayList<Cat> cats = new ArrayList<>();
        ArrayList<MiniCat> miniCats = new ArrayList<>();

        showAnimal(animals);
        showAnimal(cats);
        //showAnimal(miniCats); //超出类型通配符下限,编译不通过

    }


    /**
     * @introduction 指定通配符下限
     *              泛型下限通配符,传递的集合类型,只能是Cat或Cat的父类
     * @param list
     */
    public static void showAnimal(ArrayList<? super Cat> list) {
        //list.add(new Animal());
        list.add(new Cat());      //类型通配符下限 =>可以添加Cat或Cat子类集合元素
        list.add(new MiniCat());  //类型通配符下限 =>可以添加Cat或Cat子类集合元素
        for (Object o : list) {
            System.out.println(o);
        }
    }
}

4.3.1 TreeSet的通配符下限

案例代码十TreeSet的通配符下限 【a.Animal类】

package com.groupies.base.day18.demo10;

public class Animal {
    public String name;

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

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

案例代码十TreeSet的通配符下限 【a.Cat类】

package com.groupies.base.day18.demo10;

public class Cat extends Animal {
    public int age;

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

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

案例代码十TreeSet的通配符下限 【a.MiniCat类】

package com.groupies.base.day18.demo10;

public class MiniCat extends Cat {
    public int level;

    public MiniCat(String name, int age, int level) {
        super(name, age);
        this.level = level;
    }

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

案例代码十TreeSet的通配符下限 【b.测试类】

package com.groupies.base.day18.demo10;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction
 */
public class Test {
    public static void main(String[] args) {
        TreeSet<Cat> treeSet2 = new TreeSet<>(new Comparator1());
        treeSet2.add(new Cat("jerry", 20));
        treeSet2.add(new Cat("amy", 22));
        treeSet2.add(new Cat("frank", 22));
        treeSet2.add(new Cat("frank", 35));

        for (Cat cat : treeSet2) {
            /** comparator2比较姓名:o1.name.compareTo(o2.name);  按姓名排序,且frank添加失败
             * Cat{name='amy', age=22}
             * Cat{name='frank', age=22}
             * Cat{name='jerry', age=20}
             */
            System.out.println(cat);
        }

        System.out.println("-----------------------------");

        /**
         * public TreeSet(Comparator<? super E> comparator) {
         *    this(new TreeMap<>(comparator));
         * }
         */
        TreeSet<Cat> treeSet1 = new TreeSet<>(new Comparator2());
        treeSet1.add(new Cat("jerry", 20));
        treeSet1.add(new Cat("amy", 22));
        treeSet1.add(new Cat("frank", 35));
        treeSet1.add(new Cat("jim", 35));

        for (Cat cat : treeSet1) {
            /** comparator2比较年龄:o1.age - o2.age;  按年龄排序,且jim添加失败
             * Cat{name='jerry', age=20}
             * Cat{name='amy', age=22}
             * Cat{name='frank', age=35}
             */
            System.out.println(cat);
        }

        /**Comparator<? super E> comparator
         * 下限 E = Cat
         * Comparator3,传入
         */
        //TreeSet<Cat> treeSet3 = new TreeSet<Cat>(new Comparator3());

    }
}

//外部比较器1
class Comparator1 implements Comparator<Animal> {
    @Override
    public int compare(Animal o1, Animal o2) {
        //如果参数字符串等于此字符串,则返回值 0;
        //如果此字符串按字典顺序小于字符串参数,则返回一个小于 0 的值;
        //如果此字符串按字典顺序大于字符串参数,则返回一个大于 0 的值。
        return o1.name.compareTo(o2.name);
    }
}

//外部比较器2
class Comparator2 implements Comparator<Cat> {
    @Override
    public int compare(Cat o1, Cat o2) {
        return o1.age - o2.age;
    }
}

//外部比较器3
class Comparator3 implements Comparator<MiniCat> {
    @Override
    public int compare(MiniCat o1, MiniCat o2) {
        return o1.level - o2.level;
    }
}

5 类型擦除


类型擦除: JDK5之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。这是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,称之为–类型擦除


案例代码十一类型擦除

public class Demo11Test {
    public static void main(String[] args) {
        ArrayList<Integer> intList = new ArrayList<>();
        ArrayList<String> strList = new ArrayList<>();

        //Integer 和 String两种泛型的信息都被擦除,只剩下ArrayList信息
        System.out.println(intList.getClass().getSimpleName());//ArrayList
        System.out.println(strList.getClass().getSimpleName());//ArrayList

        System.out.println(intList.getClass() == strList.getClass());//true
    }
}

5.1 擦除类中定义的参数


1️⃣无限制类型擦除:

在这里插入图片描述

2️⃣有限制类型擦除:

在这里插入图片描述


案例代码十二【类】有限制类型擦除 【a.泛型类】

package com.groupies.base.day18.demo12;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 【类】有限制类型擦除 泛型类
 */
public class Erasure<T extends Number> {
    private T key;

    public T getKey() {
        return key;
    }

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

案例代码十二【类】有限制类型擦除 【b.测试类】

package com.groupies.base.day18.demo12;

import java.lang.reflect.Field;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 【类】有限制类型擦除 测试类
 */
public class Test {
    public static void main(String[] args) {
        //创建实例 -> 泛型使用Integer类
        Erasure<Integer> intErasure = new Erasure<>();
        //利用反射 获取Erasure类的字节码文件的Class类对象
        Class<? extends Erasure> cls = intErasure.getClass();
        //获取字段
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            /** 打印成员变量的 字段名称 + 字段类型  => Integer类型擦除为Number(Erasure通配符上限)
             * key:Number */
            System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
        }
    }
}

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

1️⃣无限制类型擦除:

2️⃣有限制类型擦除:

在这里插入图片描述

3️⃣桥接方法​:类型擦除和多态发生了冲突,为了解决这个问题,编译期会产生一个桥方法,在虚拟机中会由参数和返回值类型不同而产生的两个不同的字节码文件,但虚拟机能够正确处理这种情况,通过桥接方法,保持接口和类的实现关系。

在这里插入图片描述


案例代码十三【方法】有限制类型擦除 【a.泛型类】

package com.groupies.base.day18.demo13;

import java.util.List;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 【方法】类型擦除 泛型类
 */
public class Erasure<T extends Number> {
    private T key;

    public T getKey() {								//无限制类型擦除 类型通配符上限:Number
        return key;
    }

    public void setKey(T key) {						//无返回值 void
        this.key = key;
    }

    public <T extends List> T show1(T t){ return t;}   //无限制类型擦除 类型通配符上限:List
    public <T> T show2(T t){ return t;}                 //无限制类型擦除
}

案例代码十三【方法】有限制类型擦除 【b.测试类】

package com.groupies.base.day18.demo13;

import java.lang.reflect.Method;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 【方法】类型擦除 测试类
 */
public class Test {
    public static void main(String[] args) {
        //利用反射 获取Erasure类的字节码文件的Class类对象
        Class<? extends Erasure> cls = Erasure.class;

        //获取方法
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            /** 打印成员方法的 方法名称 + 方法返回值类型  => Integer类型擦除为Number(Erasure通配符上限)
             * getKey:Number
             * setKey:void
             * show1:List
             * show2:Object */
            System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName());
        }
    }
}

3️⃣桥接方法​:类型擦除和多态发生了冲突,为了解决这个问题,编译期会产生一个桥方法,在虚拟机中会由参数和返回值类型不同而产生的两个不同的字节码文件,但虚拟机能够正确处理这种情况,通过桥接方法,保持接口和类的实现关系。

在这里插入图片描述


案例代码十四验证桥接方法的存在 【a.泛型接口】

package com.groupies.base.day18.demo14;

public interface Info<T> {
    T info(T t);
}

案例代码十四验证桥接方法的存在 【b.实现类】

package com.groupies.base.day18.demo14;

public class InfoImpl implements Info<Integer> {
    @Override
    public Integer info(Integer value) {
        return value;
    }
}

/*
//类型擦除后效果如下
public class InfoImpl implements Info {

    public Integer info(Integer value) {
        return value;
    }
    
    @Override
    public Object info(Object value) {
        return info((Integer) value);
    }
}
*/

案例代码十四验证桥接方法的存在 【c.测试类】

package com.groupies.base.day18.demo14;

import java.lang.reflect.Method;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 桥接方法 实现类
 */
public class Test {
    public static void main(String[] args) {
        //利用反射 获取Erasure类的字节码文件的Class类对象
        Class<InfoImpl> cls = InfoImpl.class;

        //获取类中所有方法
        Method[] declaredMethods = cls.getDeclaredMethods();

        //通过反射获取InfoImpl类的所有方法,查看类型擦除后是否有两个方法(一个@Override重写方法,一个桥接方法)
        for (Method declaredMethod : declaredMethods) {
            /** 打印成员方法的 方法名称 + 方法返回值类型  => Integer为定义的类型,Object为类型擦除生成的桥接方法
             * info:Integer
             * info:Object
             * */
            System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName());
        }
    }
}


6 泛型和数组


创建方式:

  a.可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象

  b.可以通过java.lang.reflect.Array的newInstance(Class<T>,int)创建T[]数组

⚠️建议: 不要使用泛型数组,可以使用泛型集合代替泛型数组的功能


案例代码十五泛型数组的创建A方式

package com.groupies.base.day18.demo15;

import java.util.ArrayList;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 泛型数组的创建A方式
 *
 *  a.可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
 *
 *  关于A不能创建带泛型的数组对象:
 *      泛型通过类型擦除,只保留到编译期;数组的初始化后,从编译器时期开始就一直持有类型;
 *       两者从设计理念上就是冲突的,所以jdk不允许直接创建带泛型的数组对象
 */
public class TestA {
    public static void main(String[] args) {
        //ArrayList<String>[] listArr;

        //a.可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
        //ArrayList<String>[] listArr1 = new ArrayList<>[5];    //报错

        ArrayList[] list = new ArrayList[5];
        ArrayList<String>[] listArr = list;

        ArrayList<Integer> intList = new ArrayList<>();
        intList.add(100);
        
        ArrayList<String> strList = new ArrayList<>();
        strList.add("abc");
        
        list[0] = intList;

        //在泛型ArrayList数组中(listArr)指向了一个原生类型数组对象(list),list中可以放入Integer类型,所以会发生类型转换错误
        try {
            //ClassCastException: java.lang.Integer cannot be cast to java.lang.String
            String s = listArr[0].get(0);
            System.out.println(s);
        } catch (ClassCastException e) {
            e.printStackTrace();
        }
        
        //采用以下方式创建
        ArrayList<String>[] listArr2 = new ArrayList[5];
        
        //listArr2[0] = intList; //泛型检查 编译不通过
        listArr2[0] = strList;
        String s2 = listArr2[0].get(0);
        System.out.println(s2);//abc

    }
}

案例代码十六泛型数组的创建B方式 【a.工具类】

package com.groupies.base.day18.demo16;

import java.lang.reflect.Array;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 泛型数组B 工具类
 *
 * b.可以通过java.lang.reflect.Array的newInstance(Class<T>,int)创建T[]数组
 */
public class Fruit<T> {
    //private T[] array = new T[3];
    private T[] array;

    /**
     *
     * @param cls 类对象
     * @param length 数组长度
     */
    public Fruit(Class<T> cls, int length) {
        //通过Array.newInstance创建泛型数组,并强制转换
        array = (T[]) Array.newInstance(cls, length);
    }

    /**
     * @introduction 泛型数组填充元素
     * @param index 指定数组索引
     * @param item 指定填充元素
     */
    public void put(int index,T item){
        array[index] = item;
    }

    /**
     * @introduction 泛型数组获取数组元素
     * @param index 指定数组索引
     * @return 返回指定元素
     */
    public T get(int index){
        return array[index];
    }

    /**
     * @introduction 泛型数组获取所有元素
     * @return 数组所有元素
     */
    public T[] getArray(){
        return array;
    }
}

案例代码十六泛型数组的创建B方式 【b.测试类】

package com.groupies.base.day18.demo16;

import java.util.Arrays;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 泛型数组B 测试类
 *
 *  b.可以通过java.lang.reflect.Array的newInstance(Class<T>,int)创建T[]数组
 */
public class TestB {
    public static void main(String[] args) {
        Fruit<String> stringFruit = new Fruit<String>(String.class,5);
        Fruit<Integer> intFruit = new Fruit<Integer>(Integer.class,5);
        stringFruit.put(0,"苹果");
        stringFruit.put(1,"西瓜");
        intFruit.put(0,10);
        intFruit.put(5,20);

        String[] strArr = stringFruit.getArray();
        Integer[] intArr = intFruit.getArray();

        String s1 = Arrays.toString(strArr);
        String s2 = Arrays.toString(intArr);

        System.out.println(s1);//[苹果, 西瓜, null, null, null]
        System.out.println("-------------");
        System.out.println(s2);//[10, 20, null, null, null]
    }
}


7 泛型和反射


反射常用的泛型类

  Class<T>

  Constructor<T>


案例代码十七反射常用的泛型类 【b.测试类】

package com.groupies.base.day18.demo17;

import java.lang.reflect.Constructor;

/**
 * @author GroupiesM
 * @date 2021/06/08
 * @introduction 反射常用的泛型类
 *
 *  采用泛型反射类,编写代码时更方便
 */
public class Test {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<Person> personClass1 = Person.class;//默认返回了泛型为Person的Class类对象
        Constructor<Person> constructor1 = personClass1.getConstructor();//默认返回了泛型为Person的Constructor构造器对象
        Person person1 = constructor1.newInstance();

        Class personClass2 = Person.class;
        Constructor constructor2 = personClass2.getConstructor();
        Object o = constructor2.newInstance();//当constructor不指定泛型时,默认返回的是Object类型对象
    }
}


8 可变长参数 …

  一个方法只能有一个可变长参数,并且这个可变长参数必须是该方法的最后一个参数。

public class TestC {
    @Test
    public void fun1() {
        t1(1,"a","b");
        String[] a="a,b,c,d".split(",");
        t1(1,a);
    }
    
    private void t1(int num, String ... var){
        for (String s : var) {
            System.out.println(s);
        }
    }
}

21/06/08

M

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值