- 泛型的优点和作用
- 面向抽象 / 可复用
- 编译期安全检测
- 代码可读性
- 泛型定义和使用
- 泛型类 / 泛型接口
- 泛型方法
- 泛型标识符:T, E, K, V, S
- 通配符<?>
- <?>结合容器时,无法使用增删改查方法,编译期报错
- 泛型擦除
- class Test<T>:会擦除泛型,相关的参数都变Object
- class Test<T extends Clz>:会擦除泛型,相关的参数都变Clz
本篇是参考来的,参照的Reference:https://blog.csdn.net/briblue/article/details/76736356
1. 泛型的优点和作用
- 它提供了一种扩展能力,它更符合面向抽象开发的软件编程宗旨,使得代码更加可复用
- 泛型又提供了一种类型检测的机制,只有相匹配的数据才能正常的赋值,否则编译器就不通过,可以说是一种编译期安全检测机制
- 泛型提高了程序代码的可读性,不必要等到运行的时候才去强制转换
2. 泛型类
- 泛型类的定义和使用
-
//泛型类的定义 public class Test<T> { T field1; } //泛型类的使用 Test<String> test1 = new Test<>(); Test<Integer> test1 = new Test<>();
- 泛型还可以接受多个参数类型
-
public class MultiType <E,T>{ E value1; T value2; public E getValue1(){ return value1; } public T getValue2(){ return value2; } }
-
- 泛型接口:泛型接口和泛型类差不多,所以一笔带过
-
public interface Iterable<T> { }
-
- 泛型标识符
- T:Type,代表一般的任何类
- E :Element ,也指元素,一般代表集合中的类;或者 Exception 异常的意思
- K :Key ,参见HashMap的源码
- V :Value ,通常与 K 一起配合使用
- S :Subtype ,文章后面部分会讲解示意
- 其他任何字符串:出于java规范,不推荐使用
3. 泛型方法
- 使用
-
public class Test1<T>{ //泛型类的普通方法 public void method1(T t){ System.out.println(t.getClass().getName()); } //泛型方法 public <E> E method2(E e){ return e; } //泛型方法 public <T> void method3(T e){} }
-
- 注意:
- 上代码中method1是普通方法,method3是泛型方法
- 区分泛型方法就看方法前面的尖括号<T>,泛型方法始终以自己定义的类型参数为准,建议泛型方法的符号和泛型类的符号不要一样,容易搞混
4. 通配符<?>
- 存在的原因
-
//父类 class Base{} //子类 class Sub extends Base{} List<Sub> lsub = new ArrayList<>(); List<Base> lbase = lsub; //编译报错
- 最后一行会编译报错,因为尽管 Sub 是 Base 的子类,不代表
List<Sub>
和List<Base>
有继承关系 - <?> 解决了这个问题
-
- 使用方式
-
//错误用法,public后面不应该加<?> public <?> void test1(Collection<?> collection){ collection.size(); } //正确用法 public void test1(Collection<?> collection){ collection.size(); }
-
- <?>的三种形式
<?>
被称作无限定的通配符<? extends T>
被称作有上限的通配符<? super T>
被称作有下限的通配符
- 其他
- 1. 如果<?>与容器配合使用,则只能调用与泛型无关的方法,不然会报错。但是<T>不会报错,前提是需要强制转换
-
//<?>提供了只读的功能,也就是它删减了增加具体类型元素的能力,只保留与具体类型无关的功能。它不管装载在这个容器内的元素是什么类型,它只关心元素的数量、容器是否为空 public void test1(Collection<?> collection){ collection.add(123); //编译报错 collection.add("hello"); //编译报错 collection.add(new Object()); //编译报错 collection.iteration().next(); //通过 collection.size(); //通过 } public <T> void test2(Collection<T> collection){ collection.add((T)new Integer(12)); //通过 collection.add((T)"123"); //通过 collection.add("123"); //编译报错 }
-
- 2. 泛型参数相互依赖的情况
-
//泛型类 public class Test2 <T,E extends T>{ T value1; E value2; } //泛型方法 public <D,S extends D> void test(D d,S s){} //配合通配符使用 public <T> void test(T t,Collection<? extends T> collection){}
-
- 1. 如果<?>与容器配合使用,则只能调用与泛型无关的方法,不然会报错。但是<T>不会报错,前提是需要强制转换
5. 泛型擦除
- 泛型擦除的概念:由于Java1.5才引进泛型,为了向上兼容,JVM会对泛型擦除,即:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除
- 例子1:容器类中的泛型被擦除,只留下了容器本身的信息
-
List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass()); //List.class System.out.println(l2.getClass()); //List.class System.out.println(l1.getClass() == l2.getClass()); //true
-
- 例子2:普通类中的泛型被擦除,也只留下了类本身的信息,所有泛型类型会变成Object
-
public class Erasure <T>{ T aaa; public Erasure(T object) { this.object = object; } } Erasure<String> erasure = new Erasure<String>("hello"); Class eclz = erasure.getClass(); //只打印出了Erasure,而泛型String被擦除了 String clzName = eclz .getName(); //Erasure Field[] fs = eclz.getDeclaredFields(); for ( Field f:fs) { //泛型属性的类型被擦除了 System.out.println("Field name:" + f.getName()); //aaa System.out.println("type:" + f.getType().getName()); //java.lang.Object }
-
- 例子3:泛型有上限时,会被擦除成上限类
-
public class Erasure <T extends String>{ T bbb; public Erasure(T object) { this.object = object; } } Erasure<String> erasure = new Erasure<String>("hello"); Class eclz = erasure.getClass(); //只打印出了Erasure,而泛型String被擦除了 String clzName = eclz .getName(); //Erasure Field[] fs = eclz.getDeclaredFields(); for ( Field f:fs) { //泛型属性的类型被擦除了 System.out.println("Field name:" + f.getName()); //aaa System.out.println("type:" + f.getType().getName()); //java.lang.String }
-