目录
一、前言
二、什么泛型
三、为什么要使用泛型
3.1、保证了类型的安全性。
3.2、消除强制转换
3.3、提高程序的性能
3.4、 提高了代码的重用性
四、如何使用泛型
4.1、 泛型类
4.2、泛型接口
4.3、泛型方法
五、泛型通配符
5.1、无边界的通配符
5.2、固定上边界的通配符
5.3、固定下边界的通配符
六、泛型的实现原理
6.1、配置JAD
6.2、反编译分析
一、前言
泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它。
毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课
二、什么泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
三、为什么要使用泛型
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
3.1、保证了类型的安全性。
比如:没有泛型的情况下使用集合:
public static void test1(){ArrayList arr = new ArrayList<>();arr.add(1);arr.add(new Object()); //编译正常}
在不指定泛型类型时候,arr可以add任何元素,但是显然这不是我们想要的结果,因为这样就导致在遍历arr的时候,我们不知道下一个元素会是什么类型。
当我们使用泛型指定类型时,编译不通过
相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,使得程序更加安全,增强了程序的健壮性。
3.2、消除强制转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。
不使用泛型的时候:
public static void test1() {ArrayList arr = new ArrayList<>();arr.add(1);Object o = arr.get(0);//获取出来不知道是什么类型,就算你知道是int,在使用的时候需要进行转换 int o1 = (int) arr.get(0);}
使用泛型的时候:
3.3、提高程序的性能
在非泛型编程中,将简单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作:
object a = 1;//由于是object类型,会自动进行装箱操作。int b = (int) a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。
3.4、 提高了代码的重用性
如何提高的显而易见,假如ArrayList不使用泛型,你自己想吧~。~
四、如何使用泛型
泛型有三种使用方式,分别为:泛型类、泛型接口和泛型方法。
4.1、 泛型类
格式:public class 类名 <泛型类型1,...> { }
例如:public class GenericClass<ab,a,c> {}
当然,这个后面的参数类型也是有规范的,不能像上面一样随意,通常类型参数我们都使用大写的单个字母表示:
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(Java 类)
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- ? - 表示不确定的 java 类型
package com.cjian.generic;/*** @Author: cjian* @Date: 2023/5/30 10:33* @Des:*/
public class GenericClass <T> {private T type;public T getType() {return type;}public void setType(T type) {this.type = type;}public GenericClass(T type) {this.type = type;}public static void main(String[] args) {GenericClass<String> g1 = new GenericClass<>("string");System.out.println(g1.getType());GenericClass<Integer> g2 = new GenericClass<>(1);System.out.println(g2.getType());}
}
4.2、泛型接口
格式:public <泛型类型> 返回类型 方法名(泛型类型 变量名) { }
方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用run()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。
package com.cjian.generic;/*** @Author: cjian* @Date: 2023/5/30 10:39* @Des:*/
public interface GenericInterface<T> {void run(T value);
}class StringImpl implements GenericInterface<String> {@Overridepublic void run(String value) {System.out.println(value);}
}class IntegerImpl implements GenericInterface<Integer> {@Overridepublic void run(Integer value) {System.out.println(value);}
}
4.3、泛型方法
定义格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
package com.cjian.generic;import java.util.ArrayList;/*** @Author: cjian* @Date: 2023/5/30 9:51* @Des: 使用泛型打印不同类型的数组*/
public class Demo {public static void main(String[] args) {String[] strings = {"a", "b", "c"};Integer[] integers = {1, 2, 3};Double[] doubles = {1.1, 2.2, 3.3};printArr(strings);printArr(integers);printArr(doubles);test1();}public static <E> void printArr(E[] arr) {for (E e : arr) {System.out.printf("%s ", e);}System.out.println();}public static void test1() {ArrayList<Integer> arr = new ArrayList<>();arr.add(1);int o1 = arr.get(0);}
}
五、泛型通配符
//表示类型参数可以是任何类型
public class GenericClass<?>{}//表示类型参数必须是A或者是A的子类
public class GenericClass<T extends A>{}//表示类型参数必须是A或者是A的超类型
public class GenericClass<T supers A>{}
5.1、无边界的通配符
就是<?>, 比如List<?>
无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.
package com.cjian.generic;import java.util.ArrayList;
import java.util.List;/*** @Author: cjian* @Date: 2023/5/30 10:47* @Des:*/
public class GenericTest {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);}public static void getData(List<?> data) {System.out.println("data :" + data.get(0));}
}
5.2、固定上边界的通配符
采用<? extends E>的形式
使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。
要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界。
注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类
package com.cjian.generic;/*** @Author: cjian* @Date: 2023/5/30 10:33* @Des:*/
public class GenericClass<T> {private T type;public T getType() {return type;}public void setType(T type) {this.type = type;}public GenericClass(T type) {this.type = type;}// 只能接收Number及其Number的子类public static void printNum(GenericClass<? extends Number> temp) {System.out.print(temp + "、");}public static void main(String[] args) {GenericClass<String> g1 = new GenericClass<>("string");System.out.println(g1.getType());GenericClass<Integer> g2 = new GenericClass<>(1);System.out.println(g2.getType());}
}
5.3、固定下边界的通配符
采用<? super E>的形式
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据.。
要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界.。
注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
package com.cjian.generic;/*** @Author: cjian* @Date: 2023/5/30 10:33* @Des:*/
public class GenericClass<T> {private T type;public T getType() {return type;}public void setType(T type) {this.type = type;}public GenericClass(T type) {this.type = type;}// 只能接收String或Object类型的泛型,String类的父类只有Object类public void printString(GenericClass<? super String> value) {System.out.print(value);}public static void main(String[] args) {GenericClass<String> g1 = new GenericClass<>("string");System.out.println(g1.getType());GenericClass<Integer> g2 = new GenericClass<>(1);System.out.println(g2.getType());}
}
六、泛型的实现原理
泛型本质是将数据类型参数化,它通过擦除的方式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。
Java 编译器通过如下方式实现擦除:
- 用 Object 或者界定类型替代泛型,产生的字节码中只包含了原始的类,接口和方法;
- 在恰当的位置插入强制转换代码来确保类型安全;
- 在继承了泛型类或接口的类中插入桥接方法来保留多态性。
6.1、配置JAD
使用jad反编译Generic.classs
下载地址:JAD Java Decompiler Download Mirror
下载完毕后,新建一个系统变量
然后添加到Path:
cmd输入jad出现下面的代表安装成功:
6.2、反编译分析
来到class文件所在目录,进行反编译:
package com.cjian.generic;/*** @Author: cjian* @Date: 2023/5/30 10:33* @Des:*/
public class GenericClass<T> {private T type;public T getType() {return type;}public void setType(T type) {this.type = type;}public GenericClass(T type) {this.type = type;}// 只能接收Number及其Number的子类public static void printNum(GenericClass<? extends Number> generic) {System.out.print(generic.getType());}// 只能接收String或Object类型的泛型,String类的父类只有Object类public void printString(GenericClass<? super String> generic) {System.out.print(generic.getType());}public static void main(String[] args) {GenericClass<String> g1 = new GenericClass<>("string");System.out.println(g1.getType());GenericClass<Integer> g2 = new GenericClass<>(1);System.out.println(g2.getType());}
}
提示不用管,当前路径下已经生成了Generic.jad文件,打开:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: GenericClass.javapackage com.cjian.generic;import java.io.PrintStream;public class GenericClass
{public Object getType(){return type;}public void setType(Object type){this.type = type;}public GenericClass(Object type){this.type = type;}public static void printNum(GenericClass generic){System.out.print(generic.getType());}public void printString(GenericClass generic){System.out.print(generic.getType());}public static void main(String args[]){ GenericClass g1 = new GenericClass("string");// 合适的地方静心类型强转System.out.println((String)g1.getType());GenericClass g2 = new GenericClass(Integer.valueOf(1));System.out.println(g2.getType());}// 使用Object替代泛型private Object type;
}
发现编译器擦除 GenericClass类后面的两个尖括号,并且将 type的类型定义为 Object 类型,且在使用的地方进行了类型强转。
那么是不是所有的泛型类型都以 Object 进行擦除呢?大部分情况下,泛型类型都会以 Object 进行替换,而有一种情况则不是。那就是使用到了extends和super语法的有界类型,如:
public class Generic<T extends String> {private T num;
}
这种情况的泛型类型,num 会被替换为 String 而不再是 Object。
再看下插入桥接方法的场景:
Comparable.java:
package com.cjian.generic;/*** @Author: cjian* @Date: 2023/5/30 13:54* @Des:*/
public interface Comparable<T> {int compareTo(T obj);
}
User.java
package com.cjian.generic;/*** @Author: cjian* @Date: 2023/5/30 13:58* @Des:*/
public class User implements Comparable<User> {private String name;@Overridepublic int compareTo(User other) {return this.name.compareTo(other.name);}
}
Comparable.jad
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Comparable.javapackage com.cjian.generic;public interface Comparable
{public abstract int compareTo(Object obj);
}
User.jad
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: User.javapackage com.cjian.generic;// Referenced classes of package com.cjian.generic:
// Comparablepublic class Userimplements Comparable
{public User(){}public int compareTo(User other){return name.compareTo(other.name);}// 插入桥接方法来保留多态性public volatile int compareTo(Object obj){return compareTo((User)obj);}private String name;
}