转载

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

看一段常见的代码

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

记得以前我们使用的时候都需要强转类型,现在这里居然提示这是不必要的 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。这种代码就是使用泛型的栗子。

PS:末尾有惊喜

泛型的作用与定义

作用

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类型的。

如果我们使用泛型呢

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

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

定义

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

使用

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

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

• E:元素

• K:键

• N:数字

• T:类型

• V:值

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

使用的注意事项:

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

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

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

④ 泛型的参数类型可以使用extends语句,例如。习惯上称为“有界类型”。 这里的限定使用关键字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);
 }
 }
复制代码

原理(重点)

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);
 }
}
复制代码
Java语言进阶篇:泛型原理与Android网络应用

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

通配符泛型方法和嵌套

举个栗子

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类型

嵌套涉及的一些知识

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

类型擦除的规则:

  • 擦除后变为Obecjt
  • 擦除后变为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的对象却不能由ArrayList实例化?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)相互之间没有继承关系。

泛型如何变化

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

原文  https://juejin.im/post/5d11ba5c6fb9a07ebf4b7c36
正文到此结束
Loading...