Java基础-泛型

Android知识总结

一、泛型基础

1)、什么示泛型
Java泛型是JDK1.5中引入的一个新特性,泛型提供了编译时类型安全检查机制,该机制允许程序员在编译时检到非法类型。
泛型的本质是参数类型,也就是说所操作的数据类型指定为一个参数类型。
泛型不存在与JNM虚拟机。
2)、为什么使用泛型

1、泛型可以增强编译时错误检查,减少因类型问题引发的运行异常(ClassCastException),因此泛型具有更强的类型检查。

2、泛型可以避免类型转换。

    //没使用泛型
    private void fun1(){
        List nameList = new ArrayList();
        nameList.add("XaoMing");
        String name = (String) nameList.get(0);
    }
    
    //使用泛型
    private void fun2(){
        List<String> nameList = new ArrayList();
        nameList.add("XaoMing");
        String name = nameList.get(0);
    }

3、泛型可以泛型算法,增加代码复用性。

    private <T extends Comparable<T>> int countComparable(T[] array, T t2) {
        int count = 0;
        for (T e : array){
            if (e.compareTo(t2) > 0){
                count ++;
            }
        }
        return count;
    }

3)、Java中的泛型

1、泛型类
泛型类格式:
class name <T1,T2,...Tn>{}
泛型类之影响内部的普通方法和变量
2、泛型接口
泛型格式:
interface NameInterface<T> {}
3、泛型方法
定义格式:
private <K,V> boolean compare(Pair<K, V> p1, Pare<K, V>p2){}
调用格式:
Util.<K, V> compare(p1, p2)

//泛型类, 泛型接口的使用
class Men1<T, V> : MenInterface<V>{
    var name: T
    var str : V? = null

    constructor(name: T) {
        this.name = name
    }
    
    //泛型方法
    fun <E> getStr1 (str: E) {

    }
    //泛型方法, 其中的T和类泛型中的T不是一个T
    fun <T> getStr2 (str: T) {

    }

    private fun <T : Comparable<T>?> countComparable(array: Array<T>, t2: T): Int {
        var count = 0
        for (e in array) {
            if (e!! > t2) {
                count++
            }
        }
        return count
    }
    override fun getMen(): V {
        return str!!
    }

    override fun setMen(men: V) {
        this.str = men
    }


}

//泛型接口的使用
class Men2 : MenInterface<String>{
    override fun getMen(): String {
        return "XaoMing"
    }

    override fun setMen(men: String) {
        TODO("Not yet implemented")
    }

}

//泛型接口
interface MenInterface<V>{
    fun getMen() : V
    fun setMen(men : V)
}

4)、常见类型变量名称

  • E : 表示集合元素类型(在Java集合框架中广泛运用)
  • K : 表示关键字类型
  • N:表示数字类型
  • V : 表示类型
  • T : 表示任意类型
  • S,U,V:第二,第三,第四个类型
  • ? :通配符,不能用在类中,可应用在方法和参数

5)、原始类型
缺少实际类型变量的泛型就是一个原始类型
如:
java

class Box<T>{}
Box box = new Box(); //这个Box就是Box<T>的原始类型

kotlin

class GG<T>
 val gg: GG<*> = GG<Any?>()

二、泛型限制

对泛型变量的范围作出限制
1)、单一限制
<T extends X> //表示类型的上界,类型参数是X的子类
<T super X> //表示类型的下界,类型参数是X的超类
2)、多种限制
<T extends A & B & C>

  • extends 表达的意识:这里指的是广义上的扩展,兼有类型继承接口实现之意。
  • 多种限制下的格式语法要求:如果上限类型是一个类,必须第一位标识出,否着编译错误。且只能由一个类,多个接口
    泛型算法实现的关键:利用受限类型参数
public class A {}
public interface B {}
public interface C {}
public class D<T extends A & B & C> { }
public static <T extends A & B & C> void setData(T data) { }

kotlin

interface A{
    fun setName()
}
interface B{
    fun setSex()
}
class D<T> where T: A, T: B  {
}

三、PESC原则

//获取的值是Number,不能设置值
List<? super Number> list1;
//设置Number和子类,获取对象是Object
List<? extends Object> list2;

class Foot{}
class Fruit extends Foot {}
class Apple extends Fruit {}
class HongFuShi extends Apple {}
class Orange extends Fruit {}
class GenericType<T>{
    private T date;
    public T getDate() {
        return date;
    }
    public void setDate(T date) {
        this.date = date;
    }
}

class FruitTest{
    public static void print1(GenericType<? extends Fruit> fruit){
        System.out.println(fruit.getDate().toString());
    }
    public static void print2(GenericType<? super Apple> apple){
        System.out.println(apple.getDate().toString());
    }
    public static void use1(){
        print1(new GenericType<Fruit>());  //true
        print1(new GenericType<Apple>()); //true
        print1(new GenericType<HongFuShi>());  //true
        print1(new GenericType<Foot>()); //error 超过了上线
        
        GenericType<? extends Fruit> genericType = new GenericType<>();
        genericType.setDate(new Apple()); //error 不能设置数据
        Fruit date = genericType.getDate(); //只能访问数据
    }

    public static void use2(){
        print2(new GenericType<Apple>()); //true
        print2(new GenericType<Fruit>());  //true
        print2(new GenericType<HongFuShi>()); //error 超过线下
        print2(new GenericType<Orange>()); //error 不是同一个类型
        
        //因为 Apple和下限HongFuShi可以安全转型为Apple, Fruit不能安全转型
        GenericType<? super Apple> genericType = new GenericType<>();
        genericType.setDate(new Apple()); //true
        genericType.setDate(new HongFuShi()); //true
        genericType.setDate(new Fruit()); //error super 设置数据,只能设置自己和下限
        Object date = genericType.getDate(); //获取的数据是Object类型
    }
}

四、泛型擦除

功能:保证了泛型不在运行时出现
类型消除应用场合

  • 编译器会把泛型类型中所有的类型参数替换成他们的上(下)限如果没有对应类型作出限制,那么就会替换成Object类型。因此,编译出的字节码仅仅包含常规类,接口和方法。
  • 多种限制<T extends A & B & C>擦除后用A。
  • 在必要的时候插入类型转换以保证类型安全。
  • 生成桥方法以在扩展泛型时保持多态性。
    Bridge Methods 桥方法
  • 当编译一个扩展参数化类的类,或一个实现参数化类型接口的接口时,编译器有可能会创建一个合成方法,名为桥方法。它是类型擦除过程的一部分。
  • java 的泛型伪泛型,JVM中不支持泛型,为了兼容低版本(JDK1.5以下)

五、编译

  • 1)、用javac把java文件编译成class文件
    javac DD.java


  • 2)、用javap反编译class文件字节码
    javap -c DD.class


六、知识点

1、) ArrayList<String> 、ArrayList<Object>和ArrayList<?>是否可以相互转化
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
ArrayList<?> list3 = new ArrayList<>();
//不能同一类型,虽然String是Object的子类,但是ArrayList<String> 、ArrayList<Object>不是同一类型
list2 = list1;
//可以直接转换,因为?是通配符
list3 = list1;

但是泛型类可以继承或者扩展其他泛型类,比如List和ArrayList


2、) 限制
class D<T> {
    private T data;
   
    public D() {
         //不能实例化类型变量
        this.data = new T(); //error
    }
    
    //泛型类的静态上下文中类型变量失效, 所以静态域或方法里不能引用类型变量
    //引文不知道泛型类型,泛型类型是做类初始化时候才知道的
    private static T instance1(){} //error

    //静态方法是放行方法可以
    private static <T>T instance2(T data){  //true
        return data;
    }
    //error
    private <V extends Exception> void doWork1(V v){
        try {
            
        } catch (T e){  //error 不能捕获泛型类的实例
            
        }
    }
    //true 泛型可以抛出异常 
    private <V extends Exception> void doWork2(V v) throws V{
        try {

        } catch (Exception e){
            throw v;
        }
    }
    public static void main(String[] argc){
        //不能用基本类型实例化类型参数
        D<double> d1; //error
        //包装类型可以作为泛型
        D<Double> d2 = new D<>(); //true

        //运行时类型查询只适用于原始类型
        //泛型不能用 instanceof
        if (d1 instanceof D<Double>){} //error
         if (d1 instanceof D<T>){} //error

        D<String> d3 = new D<>(); 
        //因为泛型擦除,获取泛型的原生类型进行比较
        System.out.println(d2.getClass() == d3.getClass()); //true

        //不能创建参数化类型的数组
        //可以定义泛型数组,但不能new一个泛型数组
        D<String>[] d4; //true
        D<String>[] d5 = new D<String>[10]; //error
    }
}

//泛型不能继承异常
class A<T> extends Exception { //error
}
    //无法创建类型化实例
    private static <E> void append(List<E> list) throws Exception{
        E elem = new E(); //compile-time error
        list.add(elem);
    }
    
    //通过反射创建一个参数化类型实例
    private static <E> void append(List<E> list, Class<E> clazz) throws Exception{
        E e = clazz.newInstance();
        list.add(e);
    }

七、虚拟机是如何实现泛型的

  • 泛型思想早在C++语言的模板(Template)中就开始生根发芽,在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。,由于Java语言里面所有的类型都继承于java.lang.Object,所以Object转型成任何对象都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会转嫁到程序运行期之中。
  • 泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符),或是运行期的CLR中,都是切实存在的,List<int>与List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型。
  • Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此,对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类,所以泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。
  • 将一段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型
    public static String method(List<String> str){
        return "ok";
    }
    public static Integer method(List<Integer> str){
        return 1;
    }
  • 开发工具编译器:检测是否为同一个类类型,检测方法名和参数是否相同。
  • JDK编译器:检测是否为同一个类类型,检测方法名、返回类型和参数是否相同。

解析:在开发工具编译器中是同一个类型,因为参数List<T>擦除后是Object对象,所以参数相同。JDK编译器 中不是同种方法,因为返回类型不同。

  • 上面这段代码是不能被编译的,因为参数List<Integer>和List<String>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两种方法的特征签名变得一模一样。
  • 由于Java泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类型等。因此,JCP组织对虚拟机规范做出了相应的修改,引入了诸如Signature、LocalVariableTypeTable等新的属性用于解决伴随泛型而来的参数类型的识别问题,Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。修改后的虚拟机规范要求所有能识别49.0以上版本的Class文件的虚拟机都要能正确地识别Signature参数。
  • 另外,从Signature属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。

八、泛型擦除后恢复实例

因为在编译成CLASS是,在JVM有个Signature 会对泛型弱记忆;然后可以Type类中设置泛型类型,从而找到Signature 中记忆的泛型类型。

1)、导入google 的 gson 原理

 implementation 'com.google.code.gson:gson:2.6.2'

2)、代码

        Response<Date> dateResponse = new Response<>("200", "true", new Date("XaoHua"));
        String str = gson.toJson(dateResponse);
        System.out.println(str);

        /**
        * 用自定义的 TypeRefrence 代替 google 的 TypeToken
         * 有花括号{}:代表匿名内部类,创建一个匿名内部内实力对向
         * 无花括号{}:创建实例对象
         */
        Response<Date> date = gson.fromJson(str, new TypeRefrence<Response<Date>>(){}.getType());
        System.out.println(date.toString());

3)、自定义gson的TypeToken类型

    /**
     * 当构造方法为protected, 或者  abstract class 创建时必须带花括号{}
     *
     * @param <T>
     */
    abstract class TypeRefrence<T> {
        Type type;

        protected TypeRefrence() {
            //获得泛型类型
            Type genericSuperclass = getClass().getGenericSuperclass();
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            //因为泛型类型可以定以多个A<T, v ...>所以是数组
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            type = actualTypeArguments[0];
        }

        public Type getType() {
            return type;
        }
    }

4)、type类型子接口

  • TypeVariable:泛型类型变量,可以泛型上下限等信息
  • ParameterizedType:具体的泛型类型,可以获取元数据中泛型签名类型(泛型真实类型)。
  • GenericArrayType:当需要描述的泛型是泛型类数组时,比如List[],Map[],此接口会作为Type的实现。
  • WildcardType:通配符泛型,获取上下限信息。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容