看一段常见的代码java
记得之前咱们使用的时候都须要强转类型,如今这里竟然提示这是没必要要的 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、Activityjson
咱们再看两段代码,以下小程序
@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:末尾有惊喜bash
做用微信
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.代码的运行更加安全,能有效的避免运行时才知道类型转换错误,能够提早发现错误,进行修正。
定义
泛型,即参数化类型,是在JDK1.5以后才开始引入的。所谓参数化类型,是指所操做的数据类型在定义是被指定为一个参数,而后在使用时传入具体的类型。这种参数类型能够用在类、接口和方法的建立中,分别称为泛型类、泛型接口和泛型方法。
使用
上述例子就已有使用,咱们能够发现List是个泛型接口,即List 而后看到它的get方法,返回值即传入的类型,E只是个形参,Integer才是实参,因此当咱们把泛型设置成Integer的时候,此时的list数据集合的类型被肯定,get出来的即为Integer,因此再次添加String类型即报错。
下面列出每一个用例的标准类型参数:
• E:元素
• K:键
• N:数字
• T:类型
• V:值
• S、U、V 等:多参数状况中的第 二、三、4 个类型
使用的注意事项:
① 全部泛型声明都有一个类型参数声明部分(由尖括号分隔),泛型方法的该类型参数声明部分在方法返回类型以前,泛型类和接口在直接在名称后面添加了类型参数声明部分(例如)
② 每个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
③ 类型参数能被用来声明返回值类型,而且能做为泛型方法获得的实际参数类型的占位符。 泛型方法体的声明和其余方法同样。注意类型参数只能表明引用型类型,不能是原始类型(像int,double,char的等)。
④ 泛型的参数类型可使用extends语句,例如。习惯上称为“有界类型”。 这里的限定使用关键字extends,后面能够是类也能够是接口。但这里的extends已经不是继承的含义了,应该理解为T类型是实现XX接口的类型,或者T是继承了XX类的类型。 <T extends SomeClass & interface1 & interface2 & interface3>
⑤ 泛型的参数类型还能够是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");
一、若是只指定了<?>,而没有extends,则默认是容许Object及其下的任何Java类了。也就是任意类。
二、通配符泛型不单能够向上限制,如<? 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);
}
}
复制代码
咱们干了啥?这和咱们直接强转有区别吗?泛型一样须要强转,并不会提升运行效率,可是会下降编程时的错误率,即咱们刚开始所说的泛型的好处。
举个栗子
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类型
嵌套涉及的一些知识
嵌套后如何进行类型擦除?又会产生什么新的问题呢?
类型擦除的规则:
这个规则叫作保留上界,很容易想到这个,但咱们未给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)相互之间没有继承关系。
泛型如何变化
因此这个时候咱们须要经过嵌套来实现,即
<? extends>实现了泛型的协变,好比:List<? extends Number> list = new ArrayList();
<? super>实现了泛型的逆变,好比:List<? super Number> list = new ArrayList();
这两个是彻底没有问题的,再回到咱们的栗子,咱们只需修改第二种super来实现添加,首先若是咱们使用extends,如上所示,咱们能够添加Number的子类,但具体添加哪种?没法识别,而后咱们使用super,如上所示,咱们添加Number的父类,咱们能够直接使用Object,就能直接添加了。
这个例子结合咱们使用通配符的例子,总结一下就是
要从泛型类取数据时,用extends
要往泛型类写数据时,用super
1.泛型类:网络请求返回的结果类封装、适配器的基类封装等
2.泛型方法:网络请求的json转存对象的方法等
3.泛型接口:这个和方法相似,用的少
若是你看到了这里,以为文章写得不错就给个小赞呗?若是你以为那里值得改进的,请给我留言。必定会认真查询,修正不足。谢谢。
最后针对Android程序员,小编这边给你们整理了一些资料,其中分享内容包括不限于**【高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术】**但愿能帮助到你们,也节省你们在网上搜索资料的时间来学习,也能够分享动态给身边好友一块儿学习!
为何某些人会一直比你优秀,是由于他自己就很优秀还一直在持续努力变得更优秀,而你是否是还在知足于现状心里在窃喜!但愿读到这的您能点个赞和关注下我,之后还会更新技术干货,谢谢您的支持!
转发分享+关注,欢迎加入Android开发交流群(820198451)获取更多资料
Android架构师之路很漫长,一块儿共勉吧!