关于java:Java泛型详解史上最全图文详解

35次阅读

共计 5880 个字符,预计需要花费 15 分钟才能阅读完成。


泛型在 java 中有很重要的位置,无论是开源框架还是 JDK 源码都能看到它。

毫不夸大的说,泛型是通用设计上必不可少的元素,所以真正了解与正确应用泛型,是一门必修课。

一:泛型实质

Java 泛型(generics)是 JDK 5 中引入的一个新个性, 泛型提供了编译时类型平安检测机制,该机制容许程序员在编译时检测到非法的类型。

泛型的实质是参数化类型,即给类型指定一个参数,而后在应用时再指定此参数具体的值,那样这个类型就能够在应用时决定了。这种参数类型能够用在类、接口和办法中,别离被称为泛型类、泛型接口、泛型办法。

二:为什么应用泛型

泛型的益处是在编译的时候查看类型平安,并且所有的强制转换都是主动和隐式的,进步代码的重用率。

(1)保障了类型的安全性。

在没有泛型之前,从汇合中读取到的每一个对象都必须进行类型转换,如果不小心插入了谬误的类型对象,在运行时的转换解决就会出错。

比方:没有泛型的状况下应用汇合:

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); // 编译不通过
}

有了泛型后,定义好的汇合 names 在编译的时候 add(123)就会编译不通过。

相当于通知编译器每个汇合接管的对象类型是什么,编译器在编译期就会做类型查看,告知是否插入了谬误类型的对象,使得程序更加平安,加强了程序的健壮性。

(2)打消强制转换

泛型的一个附带益处是,打消源代码中的许多强制类型转换,这使得代码更加可读,并且缩小了出错机会。\
还是举例说明,以下没有泛型的代码段须要强制转换:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

当重写为应用泛型时,代码不须要强制转换:

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast

(3)防止了不必要的装箱、拆箱操作,进步程序的性能

在非泛型编程中,将筒单类型作为 Object 传递时会引起 Boxing(装箱)和 Unboxing(拆箱)操作,这两个过程都是具备很大开销的。引入泛型后,就不用进行 Boxing 和 Unboxing 操作了,所以运行效率绝对较高,特地在对汇合操作十分频繁的零碎中,这个特点带来的性能晋升更加显著。

泛型变量固定了类型,应用的时候就曾经晓得是值类型还是援用类型,防止了不必要的装箱、拆箱操作。

object a=1;// 因为是 object 类型,会主动进行装箱操作。int b=(int)a;// 强制转换,拆箱操作。这样一去一来,当次数多了当前会影响程序的运行效率。

应用泛型之后

public static T GetValue<T>(T a)
 
{return a;}
 
public static void Main()
 
{int b=GetValue<int>(1);// 应用这个办法的时候曾经指定了类型是 int,所以不会有装箱和拆箱的操作。}

(4)进步了代码的重用性。

三:如何应用泛型

泛型有三种应用形式,别离为:泛型类、泛型接口和泛型办法。

1、泛型类

泛型类:把泛型定义在类上

定义格局:\

public class 类名 < 泛型类型 1,...> {}

注意事项:泛型类型必须是援用类型(非根本数据类型)

定义泛型类,在类名后增加一对尖括号,并在尖括号中填写类型参数,参数能够有多个,多个参数应用逗号分隔:

public class GenericClass<ab,a,c> {}

当然,这个前面的参数类型也是有标准的,不能像下面一样随便,通常类型参数咱们都应用大写的单个字母示意:

T:任意类型 type\
E:汇合中元素的类型 element\
K:key-value 模式 key\
V:key-value 模式 value\
示例代码:

泛型类:

public class GenericClass<T> {
    private T value;
 
 
    public GenericClass(T value) {this.value = value;}
    public T getValue() {return value;}
    public void setValue(T value) {this.value = value;}
}

测试类:

//TODO 1: 泛型类
GenericClass<String> name = new GenericClass<>("mikechen 的互联网架构");
System.out.println(name.getValue());
 
 
GenericClass<Integer> number = new GenericClass<>(123);
System.out.println(number.getValue());

运行后果:

2、泛型接口

泛型办法概述:把泛型定义在办法上 \
\
定义格局:

public < 泛型类型 > 返回类型 办法名(泛型类型 变量名){}

留神要点:

    • 办法申明中定义的形参只能在该办法里应用,而接口、类申明中定义的类型形参则能够在整个接口、类中应用。当调用 fun()办法时,依据传入的理论对象,编译器就会判断出类型形参 T 所代表的理论类型。
public interface GenericInterface<T> {void show(T value);}
}
public class StringShowImpl implements GenericInterface<String> {
@Override
public void show(String value) {System.out.println(value);
}}
 
public class NumberShowImpl implements GenericInterface<Integer> {
@Override
public void show(Integer value) {System.out.println(value);
}}

留神:应用泛型的时候,前后定义的泛型类型必须保持一致,否则会呈现编译异样:

GenericInterface<String> genericInterface = new NumberShowImpl();// 编译异样

或者罗唆不指定类型,那么 new 什么类型都是能够的:

GenericInterface g1 = new NumberShowImpl();
GenericInterface g2 = new StringShowImpl();

3、泛型办法

泛型办法,是在调用办法的时候指明泛型的具体类型。\

  •  定义格局:

修饰符 < 代表泛型的变量 > 返回值类型 办法名(参数){}

例如:

/**
     *
     * @param t 传入泛型的参数
     * @param <T> 泛型的类型
     * @return T 返回值为 T 类型
     * 阐明:*   1)public 与 返回值两头 <T> 十分重要,能够了解为申明此办法为泛型办法。*   2)只有申明了 <T> 的办法才是泛型办法,泛型类中的应用了泛型的成员办法并不是泛型办法。*   3)<T> 表明该办法将应用泛型类型 T,此时才能够在办法中应用泛型类型 T。*   4)与泛型类的定义一样,此处 T 能够轻易写为任意标识,常见的如 T、E 等模式的参数罕用于示意泛型。*/
    public <T> T genercMethod(T t){System.out.println(t.getClass());
        System.out.println(t);
        return t;
    }
 
 
public static void main(String[] args) {GenericsClassDemo<String> genericString  = new GenericsClassDemo("helloGeneric"); // 这里的泛型跟上面调用的泛型办法能够不一样。String str = genericString.genercMethod("hello");// 传入的是 String 类型, 返回的也是 String 类型
    Integer i = genericString.genercMethod(123);// 传入的是 Integer 类型, 返回的也是 Integer 类型
}
 
 
class java.lang.String
hello
 
 
class java.lang.Integer
123

这里能够看出,泛型办法随着咱们的传入参数类型不同,他失去的类型也不同。泛型办法能使办法独立于类而产生变动。

四:泛型通配符

Java 泛型的通配符是用于解决泛型之间援用传递问题的非凡语法, 次要有以下三类:

// 示意类型参数能够是任何类型
public class Apple<?>{}
 
// 示意类型参数必须是 A 或者是 A 的子类
public class Apple<T extends A>{}
 
// 示意类型参数必须是 A 或者是 A 的超类型
public class Apple<T supers A>{}

1. 无边界的通配符(Unbounded Wildcards), 就是 <?>, 比方 List<?>

无边界的通配符的次要作用就是让泛型可能承受未知类型的数据.

2. 固定上边界的通配符(Upper Bounded Wildcards),采纳 <? extends E> 的模式

应用固定上边界的通配符的泛型, 就可能承受指定类及其子类类型的数据。

要申明应用该类通配符, 采纳 <? extends E> 的模式, 这里的 E 就是该泛型的上边界。

留神: 这里尽管用的是 extends 关键字, 却不仅限于继承了父类 E 的子类, 也能够代指浮现了接口 E 的类

3. 固定下边界的通配符(Lower Bounded Wildcards),采纳 <? super E> 的模式

应用固定下边界的通配符的泛型, 就可能承受指定类及其父类类型的数据.。

要申明应用该类通配符, 采纳 <? super E> 的模式, 这里的 E 就是该泛型的下边界.。

留神: 你能够为一个泛型指定上边界或下边界, 然而不能同时指定高低边界。

 

五:泛型中 KTVE 的含意

果点开 JDK 中一些泛型类的源码,咱们会看到上面这些代码:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{...}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {...}

下面这些泛型类定义中的泛型参数 E、K 和 V 都是什么意思呢?其实这些参数名称是能够任意指定,就想办法的参数名一样能够任意指定,然而咱们通常会起一个有意义的名称,让他人一看就晓得是什么意思。泛型参数也一样,E 个别是指元素,用来汇合类中。

常见泛型参数名称有如下:

E:Element (在汇合中应用,因为汇合中寄存的是元素)\
T:Type(Java 类)\
K:Key(键)\
V:Value(值)\
N:Number(数值类型)\
?:示意不确定的 java 类型

六:泛型的实现原理

泛型实质是将数据类型参数化,它通过擦除的形式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。

看一个例子就应该分明了,例如:

public class Caculate<T> {private T num;}

咱们定义了一个泛型类,定义了一个属性成员,该成员的类型是一个泛型类型,这个 T 具体是什么类型,咱们也不晓得,它只是用于限定类型的。

反编译一下这个 Caculate 类:

public class Caculate{public Caculate(){}
private Object num;
}

发现编译器擦除 Caculate 类前面的两个尖括号,并且将 num 的类型定义为 Object 类型。

那么是不是所有的泛型类型都以 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 指令用于强制类型转换,这一个过程就叫做『泛型翻译』。

七:最初

以上我就别离从 Java 泛型的诞生,再到泛型的应用,以及泛型的实现原理等六个方面进行了残缺详解,心愿对你有所用!


对于作者:mikechen,十余年 BAT 架构教训,资深技术专家,曾任职阿里、淘宝、百度。

浏览 mikechen 的互联网架构更多技术文章合集

Java| 数据库 | 框架 | 分布式 | 微服务 | 中间件 | 架构师

在公众号菜单栏对话框回复 【架构】 关键词,即可查看我原创的 300 期 +BAT 架构技术系列文章与 1000+ 大厂面试题答案合集。

正文完
 0