阿里开源5天被网友疯狂转载的Java泛型详解手册,带你一文通关

665 阅读7分钟

Hello,今天给各位童鞋们分享的是Java泛型,赶紧拿出小本子记下来吧

image.png

java泛型

为了让集合记住其元素的数据类型而不是都作为object处理,参数化类型,aka,泛型从java5开始引进。

泛型类、泛型接口、泛型方法、类型通配符、逆变、协变等!

java7以前构造器后面还是需要在尖括号后面加上类型的,7之后不需要了,支持菱形语法。9开始可以在创建匿名内部类时使用菱形语法。

使用var声明变量时,程序无法使用菱形语法。

泛型定义

所谓泛型就是允许在定义类、接口、方法时使用类型形参(泛型),这个类型参数将在声明变量、创建对象、调用方法时动态地指定(类型实参)。示例:

image.png 对于自定义泛型声明类的构造器,构造器还是原来的类名,不加菱形。

从泛型类派生子类

当使用这些泛型声明的接口或父类时,不能再包含泛型形参。下面是错的如:

public class Menu extends MenuReverseController{}

<泛型参数> 应该删去或者传入具体的类型来代替Y。以下两种方式均可。

public class Menu extends MenuReverseController{}

//原始类型//原始类型会被当做Object类型

public class Menu extends MenuReverseController{}

image.png

并不存在泛型类

ArrayList< String>不是新类;

image.png 输出为true;因此都会被当做同一个类处理,因此关于类的声明(静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用泛型形参。)

由于系统并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。

类型通配符

List是个泛型接口,使用时要传入泛型实参,但是List< String>不是List< Object>的子类,java泛型的设计原则是,只要代码在编译时候没有出现警告,就不会遇到运行时ClassCastException异常。

加入Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型,但G< Foo>不是G< Bar>的子类型。Foo[]自动向上转型为Bar[]的方式称为型变。数组支持型变,集合不支持。

image.png

使用类型通配符

为了表示各种泛型List的父类,可以使用类型通配符,即问号?将一个问号作为实参进行传递,但是它仅仅表示它是各种泛型的父类,并不能把元素加入其中。只可以加入null(所有引用类型的实例)。

image.png

另一方面,程序可以返回List<?>指定索引处的元素,返回值肯定是一个Object。因此把返回值赋值给一个Object类型的变量。

设定类型通配符上限

有时候会想要设定某一类泛型List的父类。List<? extend Shape>上限为Shape,这个可以表示Listxxx的父类,因为无法确定这个受限制的通配符的具体类型,所以不能把shape对象或其子类对象加入到这个泛型集合中。主要是程序无法确定类型是哪个!

这种指定通配符上限的集合只能从集合中取元素(取出的元素总是上限的类型或其子类),不能向集合中添加元素(因为编译器无法确定集合元素实际是哪种子类型)。

这种型变方式称为“协变”!

协变泛型只能调用泛型类型作为返回值类型的方法(编译器会将该方法返回值当成通配符上限的类型);而不能调用泛型类型作为参数的方法。口诀是只出不进。没有指定通配符上限,相当于上限为Object。

设定类型通配符下限

跟上面的上限相反,用A<? super Shape>。

程序可将Shape的父类泛型赋值过去。这种型变方式称为逆变!

编译器只知道集合元素是下限的父类型,但具体哪种父类类型不确定,此类泛型集合能向其中添加元素(因为实际赋值的集合元素总是逆变声明的父类),从集合中取元素时只能被当做Object类型处理(编译器无法确定取出的到底是哪个父类对象)。

逆变的泛型只能调用泛型类型作为参数的方法,而不能调用泛型类型作为返回值类型的方法。口诀是只进不出。

泛型方法的定义:在修饰符与返回值之间用尖括号定义,见示例代码:

image.png

设定泛型形参上限

不仅允许在使用通配符时候设定上限,还可以在定义泛型形参时候设置上限,用于表示传给该泛型形参的实际类型要么是该上限类型,要么是该上限类型的子类。

在一种极端情况下,程序需要为泛型形参设定多个上限(至多一个父类上限,多个接口上限),表明该泛型形参必须是其父类的子类(是父类本身也行),并且实现多个上限接口。类上限必须位于第一位。

image.png

泛型方法

泛型方法的定义

泛型方法是指在声明方法时,定义一个或多个泛型形参。语法格式为:

修饰符 <T,S> 返回值类型 方法名(形参){

…………

}

方法声明中定义的泛型只能在该方法里使用,而接口、类声明中定义的泛型可以在整个接口、类中使用。

方法中的泛型参数无需显式传入实际类型参数,而是根据实参推断出泛型所代表的类型。

为了避免类型推断错误,可以使用通配符上限,比如:

image.png

问:何时使用泛型方法,何时使用类型通配符呢?二者区别是什么?

泛型方法与类型通配符的区别

大多数时候都可以用泛型方法来代替类型通配符,比如:

image.png 虽然可以转化,但方法中的泛型形参T只使用了一次,唯一效果是可以在不同的调用点传入不同的实际类型,对于这种情况,应该使用通配符,它毕竟是被设计用来支持灵活的子类化的。

泛型方法允许泛型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不该使用泛型方法。

ps:如果有需要,也可以同时使用泛型方法与通配符。支持协变的集合可以安全地取出元素。比如:Collections.copy()方法

image.png

菱形语法与泛型构造器

java也允许在构造器签名中声明泛型形参,类似于在方法签名中声明泛型形参一样。这样就产生了所谓的泛型构造器。例如:

image.png 前面介绍了菱形语法:它允许调用构造器时在构造器后使用一对尖括号来代表泛型信息,但是如果程序显式指定了泛型构造器中声明的泛型形参的实际类型,则不可以使用菱形语法,例如:

image.png

泛型方法与方法重载

重载(两同一不同):同一个类中,同一方法名,形参列表不同才是重载!

重写(两同两小一大):

  1. 同一个方法名,同一个形参列表;
  2. 子类方法返回值与子类方法声明抛出的异常类应比父类方法返回类型更小或相等,比父类方法声明抛出的异常类更小或相等;
  3. 子类方法的访问权限要比父类大或相等。

因为泛型允许设定通配符上限与通配符下限,从而允许在一个类里包含如下两个方法定义:

image.png 编译器无法确定函数调用了哪个copy方法,会引起编译错误。

好啦,今天的文章就到这里了,希望能够帮助到屏幕前迷茫的你们