泛型(Generics)
- 在泛型出现之前,我们是怎么解决打印Sting类型和Integer
package org.generics;
/**
* @author Alex
* @version 1.0
*/
public class IntegerPrinter {
Integer content;
IntegerPrinter(Integer content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
package org.generics;
/**
* @author Alex
* @version 1.0
*/
public class StringPrinter {
String content;
StringPrinter(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
package org.generics;
/**
* @author Alex
* @version 1.0
*/
public class Main {
public static void main(String[] args) {
IntegerPrinter integerPrinter = new IntegerPrinter(123);
integerPrinter.print();
StringPrinter stringPrinter = new StringPrinter("你好");
stringPrinter.print();
}
}
可见我们想要打印哪种类型的数据,那我们就得创建哪种类型的class。
3. 当引入泛型 Generics 之后
package org.generics;
/**
* @author Alex
* @version 1.0
*/
public class Printer<T> {
T content;
Printer(T content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
package org.generics;
/**
* @author Alex
* @version 1.0
*/
public class Main {
public static void main(String[] args) {
Printer<String> stringPrinter = new Printer<>("你好");
stringPrinter.print();
Printer<Integer> integerPrinter = new Printer<>(100);
integerPrinter.print();
}
}
声明一个Generics类,我们在类的主体大括号和类名之间的地方去声明,定义一个类型变量名,用尖括号< T >包裹起来就OK了,类型变量名可以是Anything,比如T,K,V
这个时候我们在需要去打印一个String类型的数据时,就不需要在去定义一个StringPrinter的类了。这个时候我们只需要定义一个新的数据类型
需要注意的是: <> 中的类型参数不能是Java 的基本数据类型,比如说< int >,< float >等,我们必须使用一个包装后的一个类型,< String >,< Integer >
5. 在实际项目中,类型参数 T 不需要满足所有的类型,这时我们需要对类型参数进行约束,比如说传入的参数必须是某个类型的子类型也可以是某个类型。< T extends Animal> 代码如下
package org.generics;
/**
* @author Alex
* @version 1.0
* 泛型类
*/
public class Printer<T extends Animal> {
T content;
Printer(T content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
使用这种类型参数必须是Animal的子类的好处是,参数变量T是可以获得Animal方法的,比如说在Animal中定义的方法,可以使用content.方法名() 获取到。
如果说我们不去继承Animal,再去调用Animal中的方法,是报错的,显然是因为Java不知道我们传入的参数类型中是否有这么个方法。
package org.generics;
/**
* @author Alex
* @version 1.0
*/
public class Main {
public static void main(String[] args) {
// 这里的类型参数就得是Animal 的子类型,就是Dog
Printer<Dog> stringPrinter = new Printer<>(new Dog());
stringPrinter.print();
}
}
这种有约束的操作,在Java中我们称之为有界限的泛型。当然我们也可以使用接口来约束,代码如下,使用接口类型进行约束,也必须使用extends,而不能使用implements。
Animal & Thing 表示必须是 Animal的子类,也必须实现Thing接口
package org.generics;
/**
* @author Alex
* @version 1.0
*/
public class Printer<T extends Animal & Thing> {
T content;
Printer(T content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
- 其实我们在集合框架中,就使用到了泛型,比如说List、ArrayList、Set等等。
package org.generics;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<>();
strings.add("hello");
strings.add("hi");
//其实我们可以通过修改参数类型,可以实现传入任意类型的元素。
ArrayList<Object> items = new ArrayList<>();
strings.add("hello");
strings.add(124);
}
}
在Java中,不推荐这么做,因为这会带来一个type-safe(类型安全)的问题,举个例子
package org.generics;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Object> items = new ArrayList<>();
items.add("hello");
items.add(1234);
String item1 =(String) items.get(0);
System.out.println(item1);
//但是我们获取一下第2个元素
String item2 = (String) items.get(1);
//这里打印就会出错了
System.out.println(item2);
}
}
报错内容
可以看出,在编译阶段是没有问题的,但是在运行过程中就会出现问题,以为你想把一个integer类型的数据转成String类型,那怎么可能不报错呢,相当于想把Dog变成Cat。
出现这个问题的原因是:泛型的工作方式是在编译阶段进行类型检查的。而不是运行时。
9. 泛型也经常应用到函数上,称为Generics Method
package org.generics;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
print("hello");
print(1424);
print(12L);
}
private static <T> void print(T content){
System.out.println(content);
}
}
我们也可以对方法的参数进行约束,跟泛型类的约束是一样的。
10. 最后说一下这个通配符,我们还是通过一个例子引出
package org.generics;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> item1 = new ArrayList<>();
items.add("hello");
items.add("nihao");
print(items);
//如果我们需要存储Integer类型的元素,并且打印
ArrayList<Integer> item2 = new ArrayList<>();
item3.add(12);
item3.add(22);
print(item3);
//这样的话,我就需要改print方法的形参类型,这样是很麻烦的。
//如果说我们把String改成Object是不是就不用改了。
//答:这样是不可以的,因为Integer是Object的子类,
//但是ArrayList<Integer>并不是ArrayList<Object>的子类,因为是一个整体。
//注意:这里还需要说明一点,泛型是没有继承的。
}
private static <T> void print(ArrayList<String> content){
System.out.println(content);
}
}
所以我们引出通配符 ?, 这样的话,我们就能
package org.generics;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> item1 = new ArrayList<>();
items.add("hello");
items.add("nihao");
print(items);
ArrayList<Integer> item2 = new ArrayList<>();
item3.add(12);
item3.add(22);
print(item3);
}
private static <T> void print(ArrayList<?> content){
System.out.println(content);
}
}
如果说我们不想匹配所有的类型,像前面的泛型类约束是一样的。
11. 还有一种是下界通配符 <? super Dog> 这就要求我们所传入的参数类型必须是Dog的父类,或者是Dog本身。