深入理解Java泛型,Java的泛型,是在
深入理解Java泛型,Java的泛型,是在
未完待续
一、引言
泛型(Generics)和面向对象、函数式编程一样,也是一种程序设计的范式,泛型允许程序员在定义类、接口和方法时使用引用类型的类型形参代表一些以后才能确定下来的类型,在声明变量、创建对象、调用方法时像调用函数传参一样将具体类型作为实参传入来动态指明类型。
Java的泛型,是在jdk1.5中引入的一个特性,最主要应用是在jdk1.5的新集合框架中。作为Java语法层面的东西,本博客原本不打算介绍,但考虑到泛型理解和使用起来有一定的难度,应用的还很普遍,再加上自己工作多年好像也没有能够完全理解和灵活的运用泛型,因此还是决定看一些相关的书籍中与泛型有关的内容,并用一些篇幅总结下学习成果,介绍下我理解的泛型。
二、泛型类(接口)
2.1 创建泛型类
先来看两个类
public class StringPrinter {
private String thingsToPrint;
public StringPrinter() {
}
public StringPrinter(String thing) {
thingsToPrint = thing;
}
public void setThingsToPrint(String thing) {
thingsToPrint = thing;
}
public void print() {
System.out.println(thingsToPrint);
}
}
public class IntegerPrinter {
private String thingsToPrint;
public IntegerPrinter() {
}
public IntegerPrinter(String thing) {
thingsToPrint = thing;
}
public void setThingsToPrint(String thing) {
thingsToPrint = thing;
}
public void print() {
System.out.println(thingsToPrint);
}
}
两个类的作用相当,都是将传进来的参数进行打印,类的功能几乎完全相同,唯一的不同是参数的类型不一样,假如要为很多类型实现这个打印功能,就会编写很多的Printer类,如果要实现一个类统一实现这个功能,就可以采用泛型。
先来讲讲泛型语法,泛型用一个“菱形”<>
声明,<>
中是类型形参列表,如有多个类型形参,使用英文逗号,
隔开。
下面程序定义了一个带有泛型声明的Printer类,有一个类型形参T
(Type),声明了类的泛型参数后,就可以在类内部使用此泛型参数,构造函数名仍然是类名本身不需要加泛型
public class Printer<T> {
private T thingsToPrint;
public Printer() {
}
public void setThingsToPrint(T thing) {
thingsToPrint = thing;
}
public Printer(T thing) {
thingsToPrint = thing;
}
public void print() {
System.out.println(thingsToPrint);
}
}
Java还能在定义类型参数时设置限制条件,如下例定义了一个NumberPrinter类,通过extends
指定T的类型上限只能是Number。
⚠️注意:类型参数和第四章提到的类型通配符是不一样的,类型参数上的限制不能用
super
关键字,因为会造成不确定,使用extends
指定T的类型上限,编译器至少知道T是个Number,如果是super
关键字,编译器根本不知道T有哪些属性和方法。
public class NumberPrinter<T extends Number> {
private T thingsToPrint;
public NumberPrinter() {
}
public void setThingsToPrint(T thing) {
thingsToPrint = thing;
}
public NumberPrinter(T thing) {
thingsToPrint = thing;
}
public void print() {
System.out.println(thingsToPrint);
}
public T get() {
return thingsToPrint;
}
}
还可以设置多个限制条件,extends
后面只能有一个类但是可以有多个接口:
public class NumberPrinter<T extends Number & Comparable<T>> {
private T thingsToPrint;
}
创建泛型接口同理,例如jdk中的List实际上就是一个接口。
public interface List<E> extends Collection<E> {
}
并非任何类都能声明为泛型类,Java规定:异常类(java.lang.Throwable
)不得带有泛型
public class MyException<T> extends Exception { //编译出错❌,Generic class may not extend 'java.lang.Throwable'
T msg;
}
public class MyException<T> extends RuntimeException { //编译出错❌,Generic class may not extend 'java.lang.Throwable'
T msg;
}
public class MyException<T> extends Throwable { //编译出错❌,Generic class may not extend 'java.lang.Throwable'
T msg;
}
2.2 实例化泛型类
使用泛型类创建对象时就可以为类型形参T
传入具体类型,就可以生成类似Printer<String>
,Printer<Double>
的类型
public static void main(String[] args) {
// 构造器T形参是String,只能用String初始化
Printer<String> printer1 = new Printer<String>("apple");
printer1.print(); //apple
// 构造器T形参是Double,只能用Double初始化
Printer<Double> printer2 = new Printer<Double>(3.8);
printer2.print(); //3.8
}
jdk1.7以后,支持泛型类型推断,可以简写为:
Printer<String> printer1 = new Printer<>("apple");
Printer<Double> printer2 = new Printer<>(3.8);
如不指定类型实参默认为Object类型,因为所有引用类型都能被Object代表,int、double、char等基本数据类型不能被Object代表,这就是类型实参必须是引用类型的原因,不过注意如果定义类型形参时通过entends
指定了上限例如NumberPrinter<T extends Number>
,则不传递类型实参时默认为上限类型Number
public static void main(String[] args) {
Printer printer1 = new Printer("apple");
printer1 = new Printer(12);
printer1 = new Printer(new Date());
NumberPrinter numberPrinter1 = new NumberPrinter(5);
NumberPrinter numberPrinter2 = new NumberPrinter(5.8);
NumberPrinter numberPrinter3 = new NumberPrinter(""); //编译出错❌
}
2.3 派生泛型类
派生该类时,需要指定类型实参
public class HPPrinter extends Printer<Integer> {
}
通过entends
指定了上限的类型需不超过上限类型,以下同理
public class SuperNumberPrinter extends NumberPrinter<Double> {
}
public class SuperNumberPrinter extends NumberPrinter<Date> { //编译出错❌
}
如不使用泛型,不指定类型实参,则泛型转换为Object类型或上限类型
public class HPPrinter extends Printer {
public static void main(String[] args) {
HPPrinter hpPrinter = new HPPrinter();
hpPrinter.setThingsToPrint(new Object());
hpPrinter.setThingsToPrint("hello");
hpPrinter.setThingsToPrint(12);
}
}
还可以子类和父类声明同一个类型形参,子类中也不确定具体的类型,需要子类被实例化时将类型间接传递给父类,同时子类还可以一同定义自己的泛型
public class HPPrinter<T> extends Printer<T> {
}
public class HPPrinter<T, E> extends Printer<T> {
}
子类确定父类泛型类型的同时,又可以有自己的泛型
public class HPPrinter<E> extends Printer<Integer> {
}
使用泛型又不指定类型的写法是错误的
public class HPPrinter extends Printer<T> { //编译出错❌
}
三、泛型方法和泛型构造器
有时候,在类和接口上不需定义类型形参,只是具体方法中的某个类型不确定,需要在方法上面定义类型形参,这个也是支持的,jdk1.5提供了对于泛型方法的支持。
3.1 泛型方法
声明方法时,在返回值前指明泛型的类型形参列表<>
,类型形参仅作用于方法内,这个方法就声明为了泛型方法。类型形参可以出现在参数和返回值中,调用方法时指定具体类型。泛型方法可以根据需要声明为静态。任何类中都可以存在泛型方法,而不是只有泛型类中才能声明泛型方法。
用户点评