1. 创建泛型
1.1 创建
//泛型类public class Wrapper<T> {T instance;public T get() {return instance;}public void set(T newInstance) {instance = newInstance;}}//泛型接口public interface Shop<T> {T buy();float refund(T item);}//多个类型public interface Map<K, V> {public void put(K key, V value);public V get(K key);}
1.2 继承
public class WrapperNew extends Wrapper<Test> {@Overridepublic Test get() {return super.get();}@Overridepublic void set(Test newInstance) {super.set(newInstance);}}
1.3 为什么要使用泛型?
将Wrapper.class中的
public class Wrapper {Object instance;public Object get() {return instance;}public void set(Object newInstance) {instance = newInstance;}}//使用:Wrapper wrapper = new Wrapper();wrapper.set(new Test());Test test = (Test) wrapper.get();
乍一看;从达到「目的」上,不管用不用泛型都没太大区别。仔细分析会发现使用 get()方法的时候多了一步强制转换。但是:假设set()的时候是一个TestNew而不是Test,在get()时强转为Test;此时就会报转换异常
因此得出结论:泛型可以自动强制转型
1.4 泛型常见的方式以及实例化:
- 这个String其实就是确定T的值,也就是List的类型参数的实例化
如果往一个泛型类型为String的List添加一个Int值或者非String的值,会报错List<String> data = ArrayList<>();
因此得出结论:泛型可以帮助检查代码中的类型、提前报错List<String> data = ArrayList<>()data.add(1)//报错
1.5 是什么
泛型括号<>内的名称可以自定义,但不能重复,T只是一个代号
1.6 「创建一个泛型类型」到底是为了什么?
- 本质目标或原因:这个类型的不同实例的具体类型可能会有不同,针对的是实例
- 因此,静态字段和静态方法不能使用泛型类型的类型参数(也就是那个 T )
- 静态字段不可以有自己的泛型,静态方法可以,称为「泛型方法」
2. 泛型类型实例化的上界与下界
2.1 上界 ?extends Xxx
设置泛型内的类型是Xxx的子类,一般在「场景化」下使用
ps:在声明时不会这么用:
ArrayList<? extends Fruit> data = new ArrayList<Apple>();
使用方式:假设需要获取商店内出售货物的总重量:
使用?extends Xxx不能使用set()系列方法,但是可以使用get()系列方法
2.2 下界 ?super Xxx
假设代码这么写:
List<Apple> data1 = new ArrayList<Fruit>();//这是报错的//使用 ?super Xxx使其不报错List<? super Apple> data2 = new ArrayList<Fruit>();data2.add(new Apple());//没有问题Apple apple = data2.get(0);//报错
也是在**「场景化**」下使用<br /><br />使用? Xxx不能使用`get()`系列方法,但是可以使用`set()`系列方法
3. 泛型方法
最典型的莫过于View的
@SuppressWarnings("TypeParameterUnusedInFormals")@Overridepublic <T extends View> T findViewById(@IdRes int id) {return getDelegate().findViewById(id);}
当一个方法需要返回「不确定」类型的值时,使用泛型方法可解决类型推导以及解决这种「不确定」性。
泛型方法的生命:
interface Test {//参数和返回值一致<E> E test(E item);//或者<R> R test();}//使用TestCLass class = test.<TestClass>test(new TestClass);//泛型被实例化的时候,程序可以自动推断出类型参数的实际类型,从而不必自己指明类型TestCLass class = test.test(new TestClass);
4. 本质
到底什么时候使用泛型?
通常情况下,使用Object也能达到同样的目的,但面对多类型的时候,Object就进退两难了。所以在「不确定」类型的情况下可使用泛型。泛型的意义在于:**泛型的创建者让泛型的使用者可以在使用时(实例化时) 细化类型信息,从而可以触及到「使用者所细化的子类」的 API。 或者,泛型是「有远⻅的创造者」创造的「方便使用者」的工具**
在方法返回值上同等;不确定返回值的情况下使用泛型代替
以上文字说明的经典案例:Comparable接口,提供了compareTo方法,其参数是一个「不确定」类型
泛型的延伸用途:类型约束
可规定返回值的类型在某某范围之内(某个父类下的全部子类)
5. 场景再归纳
- T的场景归纳,Type Parameter和Type Argument
public class Shop<T>这个T就是Type Parameter。new Shop<Apple>这个Apple就是Type Argument - ?的场景归纳
- 只能往Type Argument的地方写
- ?extends或者?super表示「 这个类型是什么都行,只要不超出 ? extends 或者 ? super 的限制 」
- 不能用于Type Parameter的时候
new Shop<? extends Xxx>(); //不能这么写 - 类型还待确认:嵌套泛型实例化。(注意区分Parameter和Argument)
List<? extends Shop<? extends Fruit>> shops = new ArrayList<>();
- extends场景归纳
?号出现的地方,对类型参数设置上界
所有的<>在运行时都会被擦除
public interface Shop<T> {T buy();float refund(T item);}public interface AppleShop extends Shop<Apple> {@OverrideApple buy();@Overridefloat refund(Apple item);}//类型擦除后public interface AppleShop extends Shop {@OverrideObject buy();@Overridefloat refund(Object item);}//bridge method 桥接方法//其实最终的效果如下float refund(Apple item)@Overridefloat refund(Object item) {return refund((Apple) item)}//再比如List<String> strings = new ArrayList<>();//类型擦除后List strings = new ArrayList();
为什么会有「类型擦除」?因为「兼容性」和「性能」。兼容还没有泛型功能的JDK以及如果不用擦除的内存问题
总结:
- 运行时,所有的 T 以及尖括号里的东⻄都会被擦除;
- List 和 List
以及 List<Integer 都是一个类型; - 但是所有代码中声明的变量或参数或类或接口,在运行时可以通过反射获取到泛型信息;
- 但但是,运行时创建的对象,在运行时通过反射也获取不到泛型信息(因为 class 文件里面没有);
但但但是,有个绕弯的方法就是创建一个子类(哪怕用匿名类也行),用这个子 类来生成对象,这样由于子类在 class 文件里就有,所以可以通过反射拿到运行 时创建的对象的泛型信息。
比如 Gson 的 TypeToken 就是这么干的。
7. Kotlin的泛型
- 场景跟 Java 一样,不过用法有一点不一样;
- Java 的 <? extends> 在 Kotlin 里写作
;Java 的 <? super> 在 Kotlin 里写作 ; - 另外,Kotlin 还增加了 out T in T 的修饰,来在类或接口的声明处就限制使用,这样你在使用时就不必再每次都写;
- Kotlin 的 * 号相当于 Java 的 ? 号,基本一样,只是有些细节不一样。
