目录
一、前言
泛型在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> {
@Override
public void run(String value) {
System.out.println(value);
}
}
class IntegerImpl implements GenericInterface<Integer> {
@Override
public 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.java
package 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;
@Override
public 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.java
package 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.java
package com.cjian.generic;
// Referenced classes of package com.cjian.generic:
// Comparable
public class User
implements 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;
}