什么是泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
例如我们平常声明一个集合变量List<String> list = new ArrayList<String>(); 这里
泛型的本质
泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型的作用
提高了代码的复用性
// 没有泛型之前private static int add(int a, int b) {System.out.println(a + "+" + b + "=" + (a + b));return a + b;}private static float add(float a, float b) {System.out.println(a + "+" + b + "=" + (a + b));return a + b;}private static double add(double a, double b) {System.out.println(a + "+" + b + "=" + (a + b));return a + b;}// 有了泛型之后private static <T extends Number> double add(T a, T b) {System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));return a.doubleValue() + b.doubleValue();}
保证了类的安全性
相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。
// 没有泛型的时候public static void noGeneric() {ArrayList names = new ArrayList();names.add("mikechen的互联网架构");names.add(123); //编译正常}// 有泛型的时候public static void useGeneric() {ArrayList<String> names = new ArrayList<>();names.add("mikechen的互联网架构");//names.add(123); //编译不通过}
消除强制转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。
// 没有泛型的代码段需要强制转换List list = new ArrayList();list.add("hello");String s = (String) list.get(0);//当重写为使用泛型时,代码不需要强制转换List<String> list2 = new ArrayList<String>();list2.add("hello");String s2 = list2.get(0); // no cast
避免了不必要的装箱、拆箱操作,提高程序的性能
在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作。
public static void main(String[] args) {// 没有泛型之前Object a = 1;//由于是object类型,会自动进行装箱操作。int b = (int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率// 有了泛型之后int value = getValue(1);}public static <T>T getValue(T a){return a;}
怎么使用泛型
泛型类
泛型类就是把泛型定义在类上
public class 类名 <泛型类型1,...> {}
:::info 注意事项:泛型类型必须是引用类型(非基本数据类型) ::: 当然,这个后面的参数类型也是有规范的,不能像上面一样随意,通常类型参数我们都使用大写的单个字母表示:
- T:任意类型的type
- E:集合中元素的类型Element
- K:k-v形式的Key
V:k-v形式的V
public class Test6<T> {private T value;public Test6(T value) {this.value = value;}public T getValue() {return value;}public void setValue(T value) {this.value = value;}}
public class Test7<E> {private List<E> data;public Test7(List<E> data) {this.data = data;}public E get(int index){return data.get(index);}}
public class Test8<K,V> {private Map<K,V> data;public void setData(Map<K, V> data) {this.data = data;}public V get(K k){return data.get(k);}public static void main(String[] args) {Test8<String,Integer> data = new Test8<>();Map<String,Integer> map = new HashMap<>();map.put("a",97);data.setData(map);Integer a = data.get("a");System.out.println(a);}}
泛型接口
```java public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
}
:::info方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。:::```javapublic interface Test9<T> {T show(T param);}class Test94String implements Test9<String>{@Overridepublic String show(String param) {return param;}}class Test94Integer implements Test9<Integer>{@Overridepublic Integer show(Integer param) {return param;}}class Test9main{public static void main(String[] args) {//Test9<String> test9 = new Test94Integer(); // 编译异常Test9<Integer> test9 = new Test94Integer(); // 编译正常// 或者干脆不指定类型,那么 new 什么类型都是可以的:Test9 test1= new Test94String();Test9 test2= new Test94String();}}
泛型方法
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){}
public class Test10 {public <T> T get(T t){System.out.println(t.getClass());System.out.println(t);return t;}public static void main(String[] args) {Test10 test10 = new Test10();Integer integer = test10.get(1);String a = test10.get("a");}}
这里可以看出,泛型方法随着我们的传入参数类型不同,他得到的类型也不同。泛型方法能使方法独立于类而产生变化。
泛型通配符
Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法, 主要有以下三类:
- <?> :无边界的通配符,表示类型参数可以是任何类型
- <? extends E> :固定上边界的通配符,表示类型参数必须是A或者是A的子类
- <? extends E> :固定上边界的通配符,表示类型参数必须是A或者是A的超类型
<?> :无边界的通配符
无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.<? extends E> :固定上边界的通配符
使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。
要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界。
注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类<? extends E> :固定上边界的通配符
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据.。
要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界.。
注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。泛型的实现原理
泛型本质是将数据类型参数化,它通过类型擦除的方式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。
例如:
我们定义了一个泛型类,定义了一个属性成员,该成员的类型是一个泛型类型,这个 T 具体是什么类型,我们也不知道,它只是用于限定类型的。public class Caculate<T> {private T num;}
反编译一下这个 Caculate 类:
发现编译器擦除 Caculate 类后面的两个尖括号,并且将 num 的类型定义为 Object 类型。public class Caculate{public Caculate(){}private Object num;}
那么是不是所有的泛型类型都以 Object 进行擦除呢?大部分情况下,泛型类型都会以 Object 进行替换,而有一种情况则不是。那就是使用到了extends和super语法的有界类型,如:
public class Caculate<T extends String> {private T num;}
这种情况的泛型类型,num 会被替换为 String 而不再是 Object。
这是一个类型限定的语法,它限定 T 是 String 或者 String 的子类,也就是你构建 Caculate 实例的时候只能限定 T 为 String 或者 String 的子类,所以无论你限定 T 为什么类型,String 都是父类,不会出现类型不匹配的问题,于是可以使用 String 进行类型擦除。
实际上编译器会正常的将使用泛型的地方编译并进行类型擦除,然后返回实例。但是除此之外的是,如果构建泛型实例时使用了泛型语法,那么编译器将标记该实例并关注该实例后续所有方法的调用,每次调用前都进行安全检查,非指定类型的方法都不能调用成功。
实际上编译器不仅关注一个泛型方法的调用,它还会为某些返回值为限定的泛型类型的方法进行强制类型转换,由于类型擦除,返回值为泛型类型的方法都会擦除成 Object 类型,当这些方法被调用后,编译器会额外插入一行 checkcast 指令用于强制类型转换,这一个过程就叫做『泛型翻译』。
