Java语言进阶篇:泛型原理与Android网络应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_38703938/article/details/85317570

参考博客:https://blog.csdn.net/xialei199023/article/details/63251311

引入

看一段常见的代码

记得以前我们使用的时候都需要强转类型,现在这里居然提示这是不必要的 why?发生了什么?什么时候发生的?

我们打开这个方法,如下

 @SuppressWarnings("TypeParameterUnusedInFormals")
    @Override
    public <T extends View> T findViewById(@IdRes int id) {
        return getDelegate().findViewById(id);
    }
  @Nullable
    public <T extends View> T findViewById(@IdRes int id) {
        return getWindow().findViewById(id);
    }

以上两段代码分别来自于API25,即对应Android8.0源码中的v7包的AppCompatActivity、Activity

我们再看两段代码,如下

    @Override
    public View findViewById(@IdRes int id) {
        return getDelegate().findViewById(id);
    }
  @Nullable
    public View findViewById(@IdRes int id) {
        return getWindow().findViewById(id);
    }

以上两段代码分别来自于API24,即对应Android7.0源码中的v7包的AppCompatActivity、Activity

Ps:你可以试着把module的依赖的SDK版本和AppCompatActivity的版本降低到24及以下,就会出现需要如下情况

 

对比两个版本的代码,出现了重点,就是< T extends View > T 代替了原来的View。这种代码就是使用泛型的栗子。

泛型的作用与定义

1.作用

1.代码写起来比较方便,直接fbi一气呵成(如上栗),不用担心控件的类型转换问题。

再举个栗子,

        List list = new ArrayList();
        list.add(123);
        list.add("123");

        int i = (Integer) list.get(1);

        System.out.println(i);

可在main方法里执行以上代码,编译器并不会报错,但执行的时候会出现类的强制转换错误,这里的创建了ArrayList的实例,即默认为Object,所以无论是123还是“123”都被当初object添加,但是,取出的时候会被自动强转成添加进去的类型,即list.get(1)取出的是String类型,而String类型是不能强转成Integer类型的。

如果我们使用泛型呢

2.代码的运行更加安全,能有效的避免运行时才知道类型转换错误,可以提前发现错误,进行修正。

2.定义

       泛型,即参数化类型,是在JDK1.5之后才开始引入的。所谓参数化类型,是指所操作的数据类型在定义是被指定为一个参数,然后在使用时传入具体的类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。

3.使用

上述例子就已有使用,我们可以发现List是个泛型接口,即List<E>   然后看到它的get方法,返回值即传入的类型,E只是个形参,Integer才是实参,所以当我们把泛型设置成Integer的时候,此时的list数据集合的类型被确定,get出来的即为Integer,所以再次添加String类型即报错。

下面列出每个用例的标准类型参数:

• E:元素

• K:键

• N:数字

• T:类型

• V:值

• S、U、V 等:多参数情况中的第 2、3、4 个类型

使用的注意事项:

① 所有泛型声明都有一个类型参数声明部分(由尖括号分隔),泛型方法的该类型参数声明部分在方法返回类型之前,泛型类和接口在直接在名称后面添加了类型参数声明部分(例如<T>)

② 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。

③ 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。

④ 泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。 这里的限定使用关键字extends,后面可以是类也可以是接口。但这里的extends已经不是继承的含义了,应该理解为T类型是实现XX接口的类型,或者T是继承了XX类的类型。 <T extends SomeClass & interface1 & interface2 & interface3>

⑤ 泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");

1、如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类了。也就是任意类。

2、通配符泛型不单可以向上限制,如<? extends Collection>,还可以向下限制,如<? super Double>,表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例。

一、泛型类的使用

    //泛型类的使用
    ClassName<String, String> a = new ClassName<String, String>();
    a.doSomething("hello world");

    //**************************分割线***********************
    //泛型类的定义
    static class ClassName<T1, T2> { // 可以任意多个类型变量

        public void doSomething(T1 t1) {
            System.out.println(t1);
        }
    }

二、泛型方法的使用

//泛型方法的使用
  System.out.println(getMax("hello world","hello world !!!")); 
//**********************分割线****************************
//泛型方法的定义
    static  <T extends Comparable<T>> T getMax(T t1, T t2) {
        if (t1.compareTo(t2) > 1) {
            return t1;
        } else {
            return t2;
        }
    }

三、泛型接口的使用

 //泛型接口的使用
 InterfaceName<String, String> b = new ConcreteName<String>();
 b.doSomething("hello world !!!");
 //*********************分割线****************************
 //泛型接口的定义
     interface InterfaceName<T1, T2> { // 可以任意多个类型变量

        public void doSomething(T1 t1);
    }

    static class ConcreteName<T2> implements InterfaceName<String, T2> {

        public void doSomething(String t1) {
            System.out.println(t1);
        }
    }

4.原理(重点)

       Java中的泛型是通过类型擦除来实现的。所谓类型擦除,是指通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。

举个栗子

package upupup.fanxing;

import java.util.ArrayList;

/**
 * @version: v1.0
 * @description: 泛型原理的探究
 * @package: upupup.fanxing
 * @author: 酥小鱼
 * @date :2018/12/28
 */
public class test01 {
    public static void main(String[] args) {

        /**
         * 证明只生成了一个类,两个实例共享
         */
        // 声明一个具体类型为String的ArrayList
        ArrayList<String> arrayList1 = new ArrayList<String>();
        arrayList1.add("abc");

        // 声明一个具体类型为Integer的ArrayList
        ArrayList<Integer> arrayList2 = new ArrayList<Integer>();
        arrayList2.add(123);

        // 结果为true
        System.out.println(arrayList1.getClass() == arrayList2.getClass());


        /**
         * 证明了在编译后,擦除了Integer这个泛型信息,只保留了原始类型
         */
        ArrayList<Integer> arrayList3 = new ArrayList<Integer>();
        arrayList3.add(1);
        try {
            arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");
            for (int i = 0; i < arrayList3.size(); i++) {
                System.out.println(arrayList3.get(i)); // 输出1,asd
            }
            // NoSuchMethodException:java.util.ArrayList.add(java.lang.Integer
            arrayList3.getClass().getMethod("add", Integer.class).invoke(arrayList3, 2);
        }catch (Exception e){
            e.printStackTrace();
        }



    }

}

这里暴露了一个问题,既然擦除了,那么返回给我们收到的应该是一个object对象,为什么我们能直接得到我们需要的对象?不需要进行强转?原因是Java的泛型除了类型擦除之外,还会自动生成checkcast指令进行强制类型转换

看个例子

public static void main(String[] args) {
        List<Integer> a = new ArrayList<Integer>();
        a.add(1);
        Integer ai = a.get(0);
        System.out.println(ai);
    }

我们来编译一下,看他的字节码,即.class文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package upupup.fanxing;

import java.util.ArrayList;

public class test02 {
    public test02() {
    }

    public static void main(String[] var0) {
        ArrayList var1 = new ArrayList();
        var1.add(1);
        Integer var2 = (Integer)var1.get(0);
        System.out.println(var2);
    }
}

我们干了啥?这和我们直接强转有区别吗?泛型同样需要强转,并不会提高运行效率,但是会降低编程时的错误率,即我们刚开始所说的泛型的好处。

通配符泛型方法和嵌套

举个栗子

package upupup.fanxing;

import java.util.ArrayList;
import java.util.List;

/**
 * @version: v1.0
 * @description: 通配符泛型方法和嵌套
 * @package: upupup.fanxing
 * @author: 酥小鱼
 * @date :2018/12/28
 */
public class test03 {
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();

        name.add("icon");
        age.add(18);
        number.add(314);

        getData(name);
        getData(age);
        getData(number);

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

//        getUperNumber(name);//1
        getUperNumber(age);//2
        getUperNumber(number);//3

    }

    public static void getData(List<?> data) {
        System.out.println("data :" + data.get(0));
    }

    public static void getUperNumber(List<? extends Number> data) {
        System.out.println("data :" + data.get(0));
    }
}

通过栗子我们很明显知道,这个通配符是和泛型搭配使用的,因为我们需要得到不同类型的data,如果我们不使用泛型取出的时候就需要逐个进行强转,而使用?通配符就把这个问题解决了,但注意这并不是通配符泛型方法,但我们可以修改一下

//通配符泛型方法的使用
System.out.println("?data :" +getData1(name).get(0));
//***********************分割线****************
//定义通配符泛型方法
public static List<?> getData1(List<?> data) {

        return data;
    }

这里应该很容易就能明白,通配符即适配任一类型,表示未知(单一)的Object类型

嵌套涉及的一些知识

嵌套后如何进行类型擦除?又会产生什么新的问题呢?

类型擦除的规则:

  • <T>擦除后变为Obecjt
  • <? extends A>擦除后变为A
  • <? super A>擦除后变为Object

这个规则叫做保留上界,很容易想到这个,但我们未给List设置泛型的时候,即默认为Object就是这个道理

给个栗子看一哈

Number num = new Integer(1);  
//type mismatch
ArrayList<Number> list = new ArrayList<Integer>(); 

List<? extends Number> list = new ArrayList<Number>();
list.add(new Integer(1)); //error
list.add(new Float(1.2f));  //error

为什么Number的对象可以由Integer实例化,而ArrayList<Number>的对象却不能由ArrayList<Integer>实例化?list中的<? extends Number>声明其元素是Number或Number的派生类,为什么不能add Integer和Float?

要弄明白上述问题,我们先得了解一哈 

1.里氏替换原则

2.逆变与协变用来描述类型转换(type transformation)后的继承关系

3.泛型经历了类型擦除过后的继承关系

里氏替换原则

所有引用基类(父类)的地方必须能透明地使用其子类的对象。

LSP包含以下四层含义:

子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法。

子类中可以增加自己的方法。

当子类覆盖或实现父类的方法时,方法的形参要比父类方法的更为宽松。

当子类覆盖或实现父类的方法时,方法的返回值要比父类更严格。

这里可以想到我们使用的向上造型是不是就是这个原理

逆变和协变

逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:

如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类);

f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;

f(⋅)是协变(covariant)的,当A≤B时f(A)≤f(B)成立;

f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。

3.泛型如何变化

f(A)=ArrayList<A>,那么f(⋅)时逆变、协变还是不变的呢?如果是逆变,则ArrayList<Integer>ArrayList<Number>的父类型;如果是协变,则ArrayList<Integer>ArrayList<Number>的子类型;如果是不变,二者没有相互继承关系。前面我们用ArrayList<Integer>实例化list的对象错误,则说明泛型是不变的。

所以这个时候我们需要通过嵌套来实现,即

<? extends>实现了泛型的协变,比如:List<? extends Number> list = new ArrayList<Integer>();

<? super>实现了泛型的逆变,比如:List<? super Number> list = new ArrayList<Object>();

这两个是完全没有问题的,再回到我们的栗子,我们只需修改第二种super来实现添加,首先如果我们使用extends,如上所示,我们可以添加Number的子类,但具体添加哪一种?无法识别,然后我们使用super,如上所示,我们添加Number的父类,我们可以直接使用Object,就能直接添加了。

这个例子结合我们使用通配符的例子,总结一下就是

要从泛型类取数据时,用extends

要往泛型类写数据时,用super

Android上泛型的应用

1.泛型类:网络请求返回的结果类封装、适配器的基类封装等

2.泛型方法:网络请求的json转存对象的方法等

3.泛型接口:这个和方法类似,用的少

猜你喜欢

转载自blog.csdn.net/weixin_38703938/article/details/85317570