看一段常见的代码
记得以前我们使用的时候都需要强转类型,现在这里居然提示这是不必要的 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。这种代码就是使用泛型的栗子。
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.代码的运行更加安全,能有效的避免运行时才知道类型转换错误,可以提前发现错误,进行修正。
/泛型类的使用 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); } } 复制代码
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.泛型经历了类型擦除过后的继承关系