zoukankan      html  css  js  c++  java
  • java<T>泛型

    泛型

    1、泛型的概述

    在JDK1.5之前,把对象放入到集合中,集合不会记住元素的类型,取出时,全都变成Object类型。
    泛型是jdk5引入的类型机制,就是将类型参数化,它是早在1999年就制定的jsr14的实现。泛型机制将类型转换时的类型检查从运行时提前到了编译时,
    使用泛型编写的代码比杂乱的使用object并在需要时再强制类型转换的机制具有更好的可读性和安全性。
    例如在集合接口中,集合类中出现的<>就是泛型,即参数化类型 ,<>中的字母代表的是类型。
    泛型程序设计意味着程序可以被不同类型的对象重用。

    1. 可读性,从字面上就可以判断集合中的内容类型;
    2. 类型检查,避免插入非法类型。
    3. 获取数据时不在需要强制类型转换

    总之:

    泛型(Generics)是把类型参数化,运用于类、接口、方法中,可以通过执行泛型类型调用分配一个类型,将用分配的具体类型替换泛型类型。
    然后,所分配的类型将用于限制容器内使用的值,这样就无需进行类型转换,还可以在编译时提供更强的类型检查。

    2、泛型的使用标准

    类型参数(又称类型变量)用作占位符,指示在运行时为类分配类型。

    根据需要,可能有一个或多个类型参数,并且可以用于整个类。

    根据惯例,类型参数是单个大写字母,该字母用于指示所定义的参数类型。下面列出每个用例的标准类型参数:

    3、泛型类 

    在类上添加一个类型的定义,在使用类时指定一个类型,就可以对传入的数据进行类型检查,

    在取出时,不必进行类型转换了,这样的类就是泛型类。

    泛型类的缺点:
    每次针对不同的类型的参数,都必须创建不同类型的对象,这使得方法依赖于创建对象时的类型,它们之间的耦合度太高了。
    为了降低这种耦合度,可以把泛型定义在方法上,这就是泛型方法。

    4、泛型方法

    泛型类的问题:带泛型参数的方法,和对象的类型耦合度太高.
    泛型方法:把泛型直接定义在方法上.调用方法的时候,确定参数的类型

    修饰符 <T> 返回值类型 方法名(){
    
    }

    严格的调用方式:

      对象.<T>method()

     

    一般情况下调用时可以省略:

      对象.method()

    5、泛型方法和泛型类混用看归属

    泛型方法有自己的类型参数,泛型类的成员方法使用的是当前类的类型参数。

    方法中有<T> 是泛型方法;没有的,称为泛型类中的成员方法。

    package wang;
    
    
    
    class Clazz1<E>{
        public void test1(E e){
            System.out.println(e.getClass());
        } 
        
        public <E> void test2(E e){
            System.out.println(e.getClass());
        } 
        public <T> void test3(T e){
            System.out.println(e.getClass());
        } 
        public <T> void test4(E e){
            System.out.println(e.getClass());
        } 
        
        
        
    }
    public class GenericDemo4 {
        public static void main(String[] args) {
            Clazz1<String> clazz1=new Clazz1<>();
            clazz1.test1(1);
            
            clazz1.test2(1);
            
            clazz1.test3(1);
            
            clazz1.test4(1);
        }
        
        
    
    }
    泛型方法和泛型类归属

     6、泛型的限定

    6.1泛型的限定:

    如果限制只有特定某些类可以传入T参数,那么可以对T进行限定,如:只有实现了特定接口的类:<T extends Human>,表示的是Human及其子类型。

    为什么是extends不是 implements,或者其他限定符?
    
    该表达式意味着:`T subtypeOf Human`,jdk不希望再引入一个新的关键词;
    
    其次,T既可以是类对象也可以是接口,如果是类对象应该是`Human`,而如果是接口,则应该是`extends`;从子类型上来讲,extends更接近要表达的意思。

    限定符可以指定多个类型参数,分隔符是 &,不是逗号,因为在类型参数定义中,逗号已经作为多个类型参数的分隔符了,如:<T,S extends Comparable & Serializable>。

    泛型限定的优点:

    限制某些类型的子类型可以传入,在一定程度上保证类型安全;

    可以使用限定类型的方法。

    加上限定符,就可以访问限定类型的方法了,类型更明确。

    注:

    我们知道final类不可继承,在继承机制上class SomeString extends String是错误的,但泛型限定符使用时是可以的:<T extends String>,只是会给一个警告。

     

    后面的通配符限定有一个super关键字,这里没有。

    6.2通配符:

    子类型限定通配符,又称上边界通配符(upper bound wildcard Generics),代表继承它的所有子类型,通配符匹配的类型不允许作为参数传入,只能作为返回值。

    下边界通配符(lower bound wildcard Generics),通配符匹配的类型可以为方法提供参数,能得到返回值。

    List<? extends T> 大家以为元素为 T以及其所有子类的对象 的List。其实不是。元素类型 仅指T的某一个不确定的子类,是单一的一个不确定类,没有具体哪个类。因此不能插入一个不确定的。

    List<? super T> 大家以为元素为 T以及其父类的对象 的List。其实不是,元素类型 仅指T的某一个不确定的父类,是单一的一个不确定类(只确定是T的父类),没有具体哪个类。

     

    import java.util.ArrayList;
    
    public class GTtest {
        static class Species{};
        static public class Human extends Species{};
        static public class Man extends Human{};
        static public class Woman extends Human{};
        public static void main(String[] args) {
            ArrayList<Human> list = new ArrayList<Human>();
            list.add(new Man());
            list.add(new Woman());
            Man human1 = (Man)list.get(0);//OK
            System.out.println(human1);
            Man human2 = (Man)list.get(1);
            System.out.println(human2);//运行时报错
            
            ArrayList<? extends Human> list2= new ArrayList<>();
            list2.add(new Man());//编译时报错
            list2.add(new Woman());//编译时报错
            list2.add(new Human());//编译时报错
            list2.add(new Species());//编译时报错
            
            
            ArrayList<? super Human> list3= new ArrayList<>();
            list3.add(new Man());//OK
            list3.add(new Woman());//OK
            list3.add(new Human());////OK
            list3.add(new Species());//编译时报错
            
            
        }
    
    }
    泛型的限定

    因此:

    不能往List<? extends T>中插入任何类型的对象。唯一可以保证的是,你可以从中读取到T或者T的子类。

    可以往List<? super T>中插入T或者T子类的对象,但不可以插入T父类的对象。可以读取到Object或者Object子类的对象(你并不知道具体的子类是什么)。

    总结:

    • 如果频繁支持读取数据,不要求写数据,使用<? extends T>。即生产者 使用 <? extends T>

    • 如果频繁支持写入数据,不特别要求读数据,使用<? super T>。即消费者 使用 <? super T>

    • 如果都需要支持,使用<T>。 

    无限定通配符
    Pair<?> 就是 Pair<? extends Object>

    因此,无限定通配符可以作为返回值,不可做入参。

    返回值只能保存在Object中。

    P<?> 和P

    Pair可以调用setter方法,这是它和Pair<?>最重要的区别。

    P<?> 不等于 P<Object>

    P<Object>是P<?>的子类。

    通配符的捕获

    通配符限定类中可以使用T,编译器适配类型。

    使用通配类型创建一个swap方法交换key-value,交换时需要先使用一个临时变量保存一个字段:

    这里有一个办法解决它,再封装一个swapHelper():

    这种方式,称为:通配符捕获,用一个Pair<T> 来捕获 Pair<?>中的类型。

    注:
    
    当然,你完全可以直接使用swapHelper,这里只是为了说明这样一种捕获机制。
    
    只允许捕获单个、确定的类型,如:ArrayList<Pair<?>> 是无法使用 ArrayList<Pair<T>> 捕获的。

    7、泛型的擦除与残留

    泛型只在编译阶段有效,编译后类型被擦除了,也就是说jvm中没有泛型对象,只有普通对象。所以完全可以把代码编译为jdk1.0可以运行的字节码。

    7.1泛型的擦除方式:

    定义部分

    定义部分中尖括号中间的部分直接擦除。

    擦除后:

    引用部分

    引用部分,其中的T被替换成对应的限定类型.

    擦除后:

     没有限定类型:

    如果没有限定类型,替换为object,

     擦除后

    有多个限定符

    有多个限定符的,替换为第一个限定类型名。如果引用了第二个限定符的类对象,编译器会在必要的时候进行强制类型转换。

    擦除后:

    而表达式返回值返回时,泛型的编译器自动插入强制类型转换。

    7.2泛型擦除的残留

    反编译GenericClass:

    好像前面说的不对啊,这还是T啊,没有擦除呀?
    这就是擦除的残留。反汇编:

    其中:
    descriptor:对方法参数和返回值进行描述; signature:泛型类中独有的标记,普通类中没有,JDK5才加入,标记了定义时的成员签名,包括定义时的泛型参数列表,参数类型,返回值等;

    可以看到public T field1;是签名,还保留了定义的格式;其对应的参数类型是Ljava/lang/Object;。

    最后一行是类的签名,可以看到T后面有跟了擦除后的参数类型:<T:Ljava/lang/Object;>。
    这样的机制,对于分析字节码是有意义的

    7.3擦除的冲突

    从擦除的机制得知,擦除后的class文件为:

     发现重载无效了。这是泛型擦除造成的,无论是否在setName(String)是否标注为@Override都将是重写,都不是重载。而且,即便你不写setName(String)方法,编译器已经默认重写了这个方法。

    8、泛型的约束和限制

    不能使用8个基本类型实例化类型参数

    原因在于类型擦除,Object不能存储基本类型:

    byte,char,short,int,long,float,double,boolean

    从包装类角度来看,或者说三个: Number(byte,short,int,long,float,double),char,boolean

    类型检查不可使用泛型

    不能创建泛型对象数组

    可以定义泛型类对象的数组变量,不能创建及初始化。

    注,可以创建通配类型数组,然后进行强制类型转换。不过这是类型不安全的。

    不可以创建的原因是:因为类型擦除的原因无法在为元素赋值时类型检查,因此jdk强制不允许。

    不能实例化泛型对象

    解决办法是传入Class<T> t参数,调用t.newInstance()。

    不能在泛型类的静态域中使用泛型类型

    但是,静态的泛型方法可以使用泛型类型:

     

    9、泛型的反射

  • 相关阅读:
    SpringMVC中静态获取request对象 Spring中获取 HttpServletRequest对象【转载】
    springcloud 的loadbalancer 轮询算法切换方法 2021.4.3
    springboot项目启动增加图标
    rabbitmq 端口作用以及修改方法
    centos8 安装rabbitmq
    springcloud config client Value获取不到信息的问题的处理方法
    springcloud config配置git作为数据源然后启动报错 If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    Sublime Text的列模式如何操作
    centos8 安装redis
    jQuery简单的Ajax调用
  • 原文地址:https://www.cnblogs.com/wqbin/p/11218446.html
Copyright © 2011-2022 走看看