1. 通配符
有时泛型实例的作用域无法指明具体的参数类型。
通配符类型,表示任何类型,通配符类型的符号是“?”,
因此通配符类型可应用与所有继承自Object的类上。
1.1 通配符的使用
示例:
package Generic;
import java.util.Date;
public class Test<T> {
public static void main(String[] args) {
Class<?> c1 = Integer.class;
System.out.println(c1.getCanonicalName());
Class<?> c2 = String.class;
System.out.println(c2.getCanonicalName());
Class<?> c3 = Date.class;
System.out.println(c3.getCanonicalName());
}
}
运行结果:
1.2 通配符的捕获
考虑下面问题:现有一个方法用于交换List中的两个元素,
由于事先并无法知道List中存在的元素类型,
所以将参数类型设置成了含有通配符的实例,如List<?>list。
对于该方法来说,并不知道通配符所代表的具体类型,
因此,零时存储List中的元素变得困难,因为Java不允许声明通配符常量,
即 ? a 这样声明对象是不合法的。
所以出现了一种巧妙的解决办法。
示例:
package Generic;
import java.util.ArrayList;
import java.util.List;
public class Tool {
public <T> void exchange(List<T> list, int i, int j) {
T t = list.get(i);
list.set(i, list.get(j));
list.set(j, t);
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(5);
new Tool().exchange(list, 0, 1);
System.out.println(list);
}
}
运行结果:
2. 泛型边界
边界是指为某一区域划定一个界限,在界限内是允许的,如果超出了界限就不合法
Java的泛型边界是指为泛型参数指定范围,在范围内可以合法访问,如果超出了这
个界限就是非法访问。
Java泛型系统允许使用extends和 super关键字设置边界。
前者设定上行边界,即指明参数类型的顶层类,
限定实例化泛型类时传入的具体类型,只能是继承自顶层类的。
后者设置下行边界,即指定参数类型的底层类,
限定传入的参数类型只能是设定类的父类。
但设定下行边界时有一定的限制,必须与通配符连用。
要为通配符建立一个上行边界:
<? extends superclass>
.superclass用作上行边界的类名
还可以指定通配符的下行边界:
<? super subclass>
·只有subclass或其超类接受实参
2.1 含边界的泛型类
为什么要定义泛型边界呢?就拿工厂作为例子把,对于Car工厂来说,
它能生产的产品只能是Car,如果让它生产Computer,显然就不合理了。
对于Car工厂来说还存在下列问题,对于不同品牌的汽车来说,除了有轮子、
发动机、座椅零件外还要有品牌标识,这样各种品牌汽车就不同了,
因此对于汽车工厂来说,除了能生产通用的汽车之外,还应能生产具体品牌的汽车。
如何做呢?Java泛型的边界给我们提供了解决方案。
car.java
package Generic.boundary;
public class Car {
}
BenzCar.java
package Generic.boundary;
public class BenzCar extends Car {
public BenzCar() {
System.out.println("生产奔驰汽车!");
}
}
BMWCar.java
package Generic.boundary;
public class BMWCar extends Car {
public BMWCar() {
System.out.println("生产宝马汽车!");
}
}
CarFactory.java
package Generic.boundary;
public class CarFactory<T extends Car> {
public T create(Class<T> clazz) throws Exception {
System.out.println("装载发动机");
System.out.println("装载座椅");
System.out.println("装载轮子");
return clazz.newInstance();
}
}
GenericTest.java
package Generic.boundary;
public class GenericTest {
public static void main(String[] args) throws Exception {
CarFactory<BenzCar> carFactory01 = new CarFactory<>();
CarFactory<BMWCar> carFactory02 = new CarFactory<>();
System.out.println("开始生产奔驰汽车");
carFactory01.create(BenzCar.class);
System.out.println("🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟");
System.out.println("开始生产宝马汽车");
carFactory02.create(BMWCar.class);
}
}
运行结果:
2.2 含边界的方法
含边界的的泛型方法与泛型类定义类似,只需要在参数声明参数类型时,
使用extends关键字即可,具体的使用方法如下。
示例:
package Generic;
public class Sortor {
public <T extends Integer> T getMax(T x, T y) {
if ((int) x > (int) y) {
return x;
} else
return y;
}
public static void main(String[] args) {
System.out.println("泛型方法获取的最大数是: " + new Sortor().getMax(5, 4));
}
}
运行结果:
2.3 多边界
Java泛型还允许为参数类型设置多个边界,设置的方法如下:
示例:
Speakable .java
package Generic.multipleBorders;
public interface Speakable {
String speak();
}
Flyable.java
package Generic.multipleBorders;
public interface Flyable {
}
Parrot.java
package Generic.multipleBorders;
public class Parrot implements Speakable, Flyable {
@Override
public String speak() {
return "我是一只鹦鹉";
}
}
Factory.java
package Generic.multipleBorders;
public class Factory<T extends Speakable & Flyable> {
public T create(Class<T> t) throws Exception {
return t.newInstance();
}
}
FactoryTest.java
package Generic.multipleBorders;
public class FactoryTest {
public static void main(String[] args) throws Exception {
Factory<Parrot> factory = new Factory<>();
Parrot parrot = factory.create(Parrot.class);
System.out.println(parrot.speak());
}
}
运行结果:
这个例子定义了一个多边界的泛型Factory,其表现方式为:Factory<T extends Speakable & Flyable> ,表示传入的参数必须同时继承自Speakable和Flyable
2.4 通配符与边界
除了可以在泛型类和泛型方法定义时进行边界限定,还可以对泛型实例进行边界
限定,对泛型实例的限定需要与通配符一起使用,其使用方法如下:
示例:
Animal .java
package Generic.wildcardsAndBoundaries;
public class Animal {
}
Bird.java
package Generic.wildcardsAndBoundaries;
public class Bird extends Animal {
}
Fish.java
package Generic.wildcardsAndBoundaries;
public class Fish extends Animal {
}
Zoo.java
package Generic.wildcardsAndBoundaries;
public class Zoo<T> {
private T t;
public Zoo(T t) {
this.t = t;
}
public T pop() {
return this.t;
}
}
Test.java
package Generic.wildcardsAndBoundaries;
public class Test {
public static void main(String[] args) {
Zoo<? extends Animal> zoo = new Zoo<Bird>(new Bird());
zoo = new Zoo<Fish>(new Fish());
// zoo = new Zoo<>(new Integer(2)); 报错
}
}
这个例子在实例化参数Zoo时,
使用了通配符与extends关键字对参数类型进行限定:
Zoo<? extends Animal> zoo
限定了实例 zoo 中持有的对象只能是Animal的子类
注意:创建具体实例时,必须指明具体的参数类型,
如 Z00=new Zoo<Bird> ( new Bird()),
这里就不能使用<? extends Animal>,
而必须使用像Bird这样具体的参数。
除了可以使用通配符与extends关键子限制参数类型范围以外,还可以super关键连用限定参数类型的范围。其使用方法如下。
package Generic.wildcardsAndBoundaries;
public class Test {
public static void main(String[] args) {
Zoo<? super Bird> zoo = new Zoo<Bird>(new Bird());
zoo = new Zoo<Animal>(new Animal());
// zoo = new Zoo<Fish>(new Fish()); 会报错
}
}
这个例子使用通配符和super 关键字共同为声明的实例限定了参数范围:
Zoo<?super Bird> zoo,表明实例化时传入的参数只能是 Bird类或其父类。
3. 泛型与继承
泛型的继承很容易给人带来一个误区,考虑下面的代码是否合法?
package Generic.wildcardsAndBoundaries;
public class Test {
public static void main(String[] args) {
Zoo<Animal> zoo = new Zoo<>(new Animal());
Zoo<Bird> birdZoo = new Zoo<>(new Bird());
// zoo=birdZoo; 编译器会报错
}
}
运行结果:
编译器会报错
直觉可能会告诉你合法,因为Animal是Bird的父类,那么Zoo<Animal>就是
Zoo<Bird>的父类,但事实并非如此,当试图 zoo=birdZoo时 编译器会报错。
其实Zoo<Animal>和Zoo<Bird>什么关系都没有,为什么要这样设计呢,因为
这是为了确保泛型的安全。