面向对象之16:一文 彻底搞懂Java中的泛型、泛型的 使用细节 及 注意事项:

目录

what's Generic?

泛型的概念:

泛型的好处:

泛型类、泛型接口、泛型方法中常用的泛型标识符的意义释义:

常用的泛型标识符说明:

自定义泛型类(常用):

自定义泛型接口(常用):

自定义泛型类、泛型接口的继承与实现:

自定义泛型方法(常用):

泛型数组以及泛型对象:

创建泛型数组 方式一:

 创建泛型数组 方式2(这种方式 安全一些 ):

泛型对象:

泛型的使用细节以及注意事项(重点):

自定义泛型类及自定义接口的使用细节以及注意事项:

自定义泛型方法的使用细节以及注意事项:

泛型数组的使用细节以及注意事项:

泛型数组的使用细节:

                      5. 泛型数组的注意事项:

泛型对象注意事项:

泛型的通配符(常用):

泛型的上下限(常用):

泛型的上限通配:

泛型的下限通配:

关于泛型的通配符、上下限的细节说明:

泛型的通配符、上下限的使用场景:

泛型的 泛型擦除(编译时泛型擦除、运行时泛型擦除):

泛型的编译时擦除:

泛型的运行时擦除:

泛型擦除 的无限制 类型擦除:

泛型擦除 的有限制 类型擦除(即使用了 泛型的上限通配 )

泛型方法的 泛型擦除 :

泛型擦除前的桥接方法:

往 泛型指定为Double类型的List集合中添加非 Double 的实例:


what's Generic?

  • 泛型(广泛的类型):
    • 泛型就相当于是一个标签。形象一些就是:在生活中随处可见的类似于泛型的参考。比如:男卫生间门上贴的标签  --》 男 or boy ,女卫生间门上的标签 --》 女 or girl,能表示性别的标签 理解在Java中就叫做泛型。再 比如:垃圾分类的垃圾桶上面贴的标签,比如:厨余辣鸡、有害辣鸡、可回收辣鸡、其他辣鸡,……等等。
  • 泛型的声明格式:<泛型标识符[,泛型标识符2,……]>   这个 <>  因为像,也叫做钻石运算符。
  • Java中 泛型的支持:泛型只支持引用数据类型,不支持基本数据类型!!!!
  • 泛型的概念:

    • 集合(有的叫容器)类在设计阶段/声明阶段,并不能够确定这个容器 实际应该存入什么样类型的对象。所以在 JDK1.5 之前都只能把元素的类型设计为Object。这样在使用集合的时候。如果想使用添加的元素的类型的特有成员时。就难免会使用到 类 的向下转型!所以很有可能引发 ClassCastException 异常!
    • JDK1.5 开始 就使用泛型来解决。因为这个时候除了集合中的元素类型不确定。其他的部分都是确定的。比如关于这个元素应该怎么存储。怎么进行管理这些逻辑是 集合中的API 已经实现了逻辑了的。所以这时把元素的类型 设计成为一个参数(泛型标识符)的话。这个类型 就叫做泛型!在实例化集合类的时候。当开发者指定为 什么 数据类型,那么集合中所有使用到 泛型标识符 的地方都会在 编译时成为 开发者指定的数据类型!从而就可以直接在实例化的地方调用 该类型特有和继承、实现下来的成员!
    • ·Java中泛型(Generic)是在 JDK1.5 引入的新特性。泛型的主要作用就是提供了 编译时类型 的安全监测机制。该机制允许开发者在编译时 检测到非法的类型数据结构,即指定了什么数据类型,就只能操作什么数据类型的成员!!
      • 泛型的本质就是参数化类型。换言之,就是所操作的数据类型被指定为一个参数!当使用泛型类的时候,开发者给定了什么类型,那么泛型类 的 泛型标识符所表示的数据类型就是 该类型!从而就可以直接使用、 该类型特有和继承、实现下来的成员!
  • 泛型的好处:

    • Java泛型可以在编译阶段 保证  数据的安全性。比如在集合类中的体现。 当泛型指定为 List<String> list = new ArrayList<>(); 当调用 list.add() API 的时候,就只能往集合中添加String类型的 实例。
    • 消除了 类 的向下转型 。消除了 有可能引发的 ClassCastException 异常的产生!同时,代码更加简洁、健壮。
    • 在Java的源码中、各种设计模式、框架中 泛型得到广泛的应用!

泛型类、泛型接口、泛型方法中常用的泛型标识符的意义释义:

  • <E> : E ---> Element(元素) 的首字母缩写。表示为类中的元素的泛型(比如表示为:实例变量、常量 的泛型,在Java中的List集合、Set集合中经常会看到这样的泛型)。
  • <T>:T ---> Type(类型)的首字母缩写。表示为类中的泛型 "任意引用数据类型"。(比如表示为:类中的实例成员方法、实例变量、常量的泛型)。
  • <K>:K ---> Key(键)的首字母缩写。表示为类中的 键的数据类型。(在Java中的Map集合中 经常会看到这样的泛型)
  • <V>:V ---> Value(值)的首字母缩写。表示为类中的 值的数据类型。(在Java中的Map集合中 经常会看到这样的泛型)
  • <S>:S --->等同于 T  的意义。可以理解成 Species (种类、物种) 的首字母缩写。表示为类中的泛型 "任意引用数据类型。 表示为类中的泛型 "任意引用数据类型"
  • <U>:U ---> 等同于 T  的意义。不是什么单词首字母的缩写。如果非要解释那就是 U的字母顺序 离 T 比较近。表示为类中的泛型 "任意引用数据类型"
  • <R>:R ---> 等同于 T  的意义。不是什么单词首字母的缩写。如果非要解释那就是 R的字母顺序 离 T 比较近。表示为类中的泛型 "任意引用数据类型"
  • <Q>:Q ---> 等同于 T  的意义。不是什么单词首字母的缩写。如果非要解释那就是 Q的字母顺序 离 T 比较近。表示为类中的泛型 "任意引用数据类型"
  • <A>:   A ---> 等同于 T  的意义。不是什么单词首字母的缩写。可以理解成 a (一、任一、每个、每一) 的冠词的大写。表示为类中的泛型 "任意引用数据类型"
  • <C>:C ---> 等同于 T  的意义。可以理解成为Class(类),Category(种类、分类)的首字母缩写。表示为类中的泛型 "任意引用数据类型"
  • <G>:G ---> 等同于 T  的意义。可以理解成 Genre (类型) 的首字母缩写。表示为类中的泛型 "任意引用数据类型"
  • 常用的泛型标识符说明:

    • 其实泛型的标识符不仅仅局限于上面 我所举例的常用的泛型标识符。泛型标识符可以是 任意的英文字母 大写。甚至是 英文单词 都可以的!
    • 只是为了避免歧义。所以才会有这种约定俗成的 命名规则。
    • 基本上不会使用英文单词来作为泛型标识符。
      • 如果真的拿英文单词来作为泛型表示符的话。如果按照 小驼峰命名法 。和变量名、方法名 就产生了歧义。
      • 如果是 按照 大驼峰命名法。和类名 就产生了歧义。
      • 如果是 全部大写。和常量名 就产生了歧义。
      • 应该避免 产生歧义。而不是制造歧义!

自定义泛型类(常用):

例如:

class Son<T> {
    private T t;
    private T[] tArr;
    public final List<T> list = new ArrayList<>();

    public void setT(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }
    
    public List<T> getList(){
        return list;
    }
}

test:

  1. ,此时会看到在编译阶段(也就是写代码时),set方法的数据类型就行在 写泛型类的时候 开发者指定的数据类型。达到了数据类型的安全性!
  2. ,此时会发现,在getT返回设置的属性的时候,就已经是开发者给定的数据类型。从而就可以直接调用String类中的API,就不用再进行类型的强制转换!
  3. ,而get到 List集合的时候,此时的 add方法中的入参类型已经变成了 在一开始实例化对象的时候。就已经给定了的 数据类型!
  4. 另外在集合中的表现是一回事,比如:

自定义泛型接口(常用):

例如:

interface Person<T,S,R,U,Q>{
    S s();
    Map<T,R> setMap(U u,Q q);

    default void print(T t,S s){
        System.out.println(t.getClass());
        System.out.println(s.getClass());
    }
}

test:和自定义泛型类是一样的使用,只是接口的泛型是 它的实现类在初始化时、定义时 或者是 子接口定义时、或者是 子抽象类定义时指定的!

 


自定义泛型类、泛型接口的继承与实现:

形式1(稍微复杂些的)(不常用):

interface Person<Q> {
    public abstract void personMethod(Q q);
}
// 不给定父接口泛型
interface SubPerson<Q, C> extends Person<Q> {
    default void subMethod(C c) {
        System.out.println(c.getClass());
    }
}
// 给定父接口泛型,同时声明自己类中的泛型
abstract class AbstractPerson<T, U, S> implements SubPerson<List<String>, HashMap<String, String>> {

    @Override
    public void personMethod(List<String> list) {
        list.add("抽象类向集合中添加元素");
    }

    public abstract ArrayList<Object> personMethod(T t, U u);

    protected void method2(S s) {
        System.out.println(s.getClass());
    }
}
// 给定父接口的第三个泛型为 Scanner 类型
// 其他两个泛型在实例化 Coder的时候再指定。
// Coder再声明一个自己的泛型。
class Coder<R, T, U> extends AbstractPerson<T, U, Scanner> {

    private T t;
    private U u;
    public Coder(T t,U u){
       this.t = t;
       this.u = u;
    }
    public T getT(){
        return t;
    }
    public U getU(){
        return u;
    }
    @Override
    public ArrayList<Object> personMethod(T t, U u) {
        ArrayList<Object> list = new ArrayList<>();
        Collections.addAll(list, t, u);
        return list;
    }

    public Double useR(R r) {
        if (r instanceof Integer) {
            return Double.valueOf(calculate((Integer) r, 100));
        } else {
            System.out.println(r.getClass());
        }
        return Math.PI;
    }

    private int calculate(int start, int target) {
        return (start + target) * (target / 2);
    }
}

test:

public static void testGenericExtends() {
        Coder<Integer, String, Double> coder =
                new Coder("第二个泛型为String类型",Math.E);
        coder.method2(new Scanner(System.in));
        // 直接以实例化时给定的泛型为准
        String t = coder.getT();
        System.out.println(t);
        Double u = coder.getU();
        System.out.println(u);

        coder.personMethod(new ArrayList<>());
        ArrayList<Object> objects = coder.personMethod("圆周率:", Math.PI);
        System.out.println(objects);

        Double result = coder.useR(1);
        System.out.println(result);
    }

 

形式2(什么泛型都不在定义泛型类、泛型接口的时候 给定)(常用)

  • 说明:
  • 如果子类、子接口、实现类什么类型都不在定义指定的话。那该类、该接口 就是成为泛型类、泛型接口。并且泛型标识符要和父类、父接口的一模一样!
  • 其实子类、子接口、实现类 可以更改父类、父接口的泛型标识符的,但是更改了之后!在定义自己类、接口的时候,也要是这个标识符!(一般不会这样做!)
  • 如果子类自己有拓展的标识符的话,那么和父类泛型标识符一样的那个标识符 在子类自己定义类 声明泛型标识符的时候,和父类泛型标识符一样的那个标识符可以放在泛型声明的 钻石运算符中的任意位置 。只是这可能会产生不好理解的情况。
  • interface People<T> {
        public abstract Integer peopleMethod(T t);
    }
    
    interface Man<T, S> extends People<T> {
        @Override
        Integer peopleMethod(T t);
    
        default String m1(S s, T t) {
            return s.getClass().toString() + "和" + t.getClass().toString();
        }
    }
    
    abstract class Dad<T, S> implements Man<T, S> {
        protected T t;
    
        protected Dad(T t) {
            this.t = t;
        }
    
        @Override
        public Integer peopleMethod(T t) {
    
            return (Integer) t + 999999;
        }
    }
    
    class Child<T, S, L> extends Dad<T, S> {
        private S s;
    
        public Child(T t, S s) {
            super(t);
            this.s = s;
        }
    
        public S getS() {
            return s;
        }
    
        public List<? extends L> getList() {
            return new LinkedList<>();
        }
    }

    test:

    public static void main(String[] args) {
            Child<Integer, String, Serializable> child = new Child<>(9090, "Created at 2021-06-14 08:59");
            String s = child.getS();
            System.out.println(s);
            Integer integer = child.peopleMethod(789);
            System.out.println(integer);
            Integer t = child.getT();
            System.out.println(t);
    
            List<? super Serializable> list = child.getList();
            list.add(new Date());
            list.add(new java.sql.Date(System.currentTimeMillis()));
            list.add(new ArrayList<Timestamp>().add(null));
            ArrayList<Long> currentTimeMillis = new ArrayList<>();
            currentTimeMillis.add(new GregorianCalendar().getTime().getTime());
    
            System.out.println(list);
            System.out.println(currentTimeMillis);
            System.out.println(child.m1(new String(), Integer.MAX_VALUE));
        }

    test:


自定义泛型方法(常用):

  1. 泛型方法声明格式:方法修饰符 <泛型标识符> 返回值类型 方法名(泛型 形参名,[其他数据类型 形参名]){return 返回值;}
  2. public class GenericTest3 {
        public static void main(String[] args) {
            Generic<Generic> generic = new Generic<>();
            Generic generic1 = generic.isNotGenericMethod(generic);
            System.out.println(generic);
            System.out.println(generic1);
    
            String s = generic.isGenericMethod("是String类型");
            System.out.println(s);
            ArrayList<Date> dates = new ArrayList<>();
            List<ArrayList<Date>> listInstance = Generic.getListInstance(dates);
            listInstance.add(dates);
            System.out.println(listInstance.getClass());
        }
    
    }
    
    class Generic<T> {
        private T t;
    
        public Generic(){}
        // 此方法并不是泛型方法
        public T isNotGenericMethod(T t) {
            return isNotGenericMethod2(t);
        }
        // 此方法也不是泛型方法
        private T isNotGenericMethod2(T t) {
            T tt = null;
            try {
                // 通过这样的反射方式 创建本类对象,构造器必须显示初始化才可以!
              tt = (T) t.getClass().getConstructor().newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return tt;
        }
    
        // 是泛型实例方法
        public <S> S isGenericMethod(S s){
            if ("是String类型".equals(s)){
                return s;
            }
            return null;
        }
    
        // 是泛型静态方法
        public static <U> List<U> getListInstance(U u){
            if (u instanceof List){
                return new LinkedList<>();
            }
            return null;
        }
    }
    result:,泛型在编译时的数据类型:
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值