为什么需要泛型
在没有泛型时,假如我们现在需要一个类来打印Integer类型的变量
package org.albert;
public class InetgerPrinter {
Integer content;
InetgerPrinter(Integer content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
public class Main {
public static void main(String[] args) {
InetgerPrinter printer = new InetgerPrinter(123);
printer.print();
}
}
//输出:123
内容是这样的。
现在有一个问题,如果这个时候我想换一种类型打印呢?
显然我们需要重建一个StringPrinter类,
package org.albert;
public class StringPrinter {
String content;
StringPrinter(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
public class Main {
public static void main(String[] args) {
StringPrinter printer = new StringPrinter("hello world");
printer.print();
}
}
//输出:hello world
这里说明,如果我们要新打印一个类型的话,就必须重建一个class。
这样会给代码带来很多的重复性,因此我们引进了Generics的概念,以至于我们建立一个类,就可以处理所有的类型。
这里我们新建一个Printer类,来处理所有类型变量的打印问题。
如何来声明一个Generics类呢?
在类的主题大括号和类名之间去声明,通过用尖括号包裹起来的一个类型参数来声明泛型类。
例如这里的
public class Printer<T> {
}
这里就是用T来声明泛型类,代表着这个类可以传入任何类型的参数(字母T可以更改)
那么仿照上面的模型,完整的Printer类:
public class Printer<T> {
T content;
Printer(T content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
那么在Main函数中我们要如何使用Printer类呢?
public class Main {
public static void main(String[] args) {
//假如我们要打印Integer类的变量
Printer<Integer> printer = new Printer<>(123);
printer.print();
}
}
//输出:123
在这里我们可以得到泛型的声明格式为
public class 类名 <T>{
...
}
使用格式:
类名<ElemType> 实例名 = new 类名<>(初始化数据);
这里的T是自定义的任何字符。
-
实际上T为何种类型,是由调用时我们传进去的类型决定的。
-
泛型的作用在于,如果我们需要打印出String类型或Double类型的变量时,不需要再重新书写出它们各自的打印类,只需要改变< >中的类型即可。
-
并且注意,< >中不能是基本数据类型,即int、char、double等,必须是包装后的类型,如Integer、String等。
也可以是我们自己声明的一些类,必须Car、Vehicle、bus等等。
多泛型参数
例如,
package org.albert;
public class Printer<T, K> {
T content;
K content2;
Printer(T content, K content2) {
this.content = content;
this.content2 = content2;
}
public void print() {
System.out.println(content);
System.out.println(content2);
}
}
那这时我们在Main函数中的应用则为:
package org.albert;
public class Main {
public static void main(String[] args) {
//假如我们要打印Integer类和String类的变量
Printer<Integer, String> printer = new Printer<>(123, "Hello World");
printer.print();
}
}
//输出:
123
Hello World
bounded generics
指有界限的泛型:对泛型参数进行约束
我们实际在做项目的时候,可能这个类型参数T不需要满足所有的类型,这时,我们可以对这个类型参数T进行约束,通过继承的方法来对类型参数T进行约束,就是说,T类型参数比如是什么类型的子类型。
package org.albert;
public class Printer<T extends Vehicle> {
T content;
Printer(T content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
重点看< >部分,T必须是继承Vehicle的子类,即Car或Bus等。
package org.albert;
public class Main {
public static void main(String[] args) {
Printer<Car> printer = new Printer<>(new Car());
printer.print();
}
}
传递进去的类型必须是Vehicle的子类型。
当然我们也可以用接口的方式来约束。
package org.albert;
public class Printer<T extends Thing> {
...
}
但需要注意的是,虽然是类实现接口,但在这里需要用extends关键字来说明,类型参数T是实现接口的子类型,而不是用implements关键字。
或者,我们可以同时用类和接口去约束泛型。
package org.albert;
public class Printer<T extends Vehicle & Thing> {
...
}
用一个&来连接,注意这里必须是父类在接口前面,否则会报错。
这里参数T必须是Vehicle的子类,同时也是实现接口Thing的子类。
泛型方法
假如我们在类中想声明一个能打印任何类型参数的方法,则是
package org.albert;
public class Main {
public static void main(String[] args) {
//假如我们要打印Integer类和String类的变量
print(123);
print("Hello World");
}
public static <T> void print(T content) {
System.out.println(content);
}
}
在函数的返回值类型前面加上即可
可以对泛型方法的T进行约束,如
public static <T extends Vehicle & Thing> void print(T content) {
System.out.println(content);
}
也可以传入多个参数。
public static <T, K> void print(T content, K conten 2) {
System.out.println(content);
System.out.println(content2);
}