JAVA | 第1期 - 关于泛型的内容回顾~
释义
Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
什么是泛型,为什么要使用泛型?
通俗地讲,泛型的本质其实就是“参数化类型”,将需要传入的类型参数化。其实说的再简单一点,泛型就类似于我们的模板代码,我们可以在使用的时候替换成指定的类型,而不需要为涉及到的每一种类型都写一套相同逻辑的代码,并且合理的使用泛型可以使得我们的程序变得更加通用和易于扩展。
一个说烂了的例子
这个例子被很多人用来阐述我们在没有泛型的时候,是如何操作的,比如在 JDK5 以前,我们是这样写的:
public static void main(String[] args) {
List list = new ArrayList();
list.add(1); // Integer
list.add("String"); // String
list.add(...); // Others
// 因为 List 存放的是 Object 对象,所以下面强制转换为 String 会抛出异常。
for (int i = 0, s = list.size(); i < s; i++) {
System.out.println((String) list.get(i)); // ClassCastException
}
}
再比如:
public class Caller {
public void call(int index) {
System.out.printf("index: %d\n", index);
}
}
// 没有使用泛型
// 必须得强转才能操作集合里面的元素
List callers = Arrays.asList(
new Caller(),
new Caller()
);
for (Object caller : callers)
((Caller) caller).call(i);
}
// 当我们用了泛型后,就可以直接对其进行操作了。
// 少了很多代码,并且编辑器也可以对代码进行提示了,简直不要太方便。
List callers = Arrays.asList(
new Caller(),
new Caller(),
);
for (Caller caller: callers) {
caller.call(i);
}
试想一下,如果不使用泛型,那么在进行操作的时候是不是每次都需要强制转换,很麻烦对不对。再者使用泛型一定程度上提供了程序的安全性,防止出现类型不匹配的低级错误,也算是一种安全检测机制,毕竟 JAVA 作为一种强类型就需要适当的对参数进行一定约束。
一些约定俗成泛型符号
- E - 通常用来表示某个元素,比如 List 中就是用了 E 代表了元素的类型 ;
- T - 通常代表某个参数类型,一般情况下一个泛型入参的时候会高频率的使用这个字符;
- K - 通常代表一个键的类型,比如 Map 中就使用 K 表示数据键的类型;
- V - 通常代表一个值的类型,比如 Map 中就使用 V 表示数据值的类型;
- N - 通常用于表示一种数字类型;
- ? - 代表不确定的类型(这是一个特殊的类型,需要和 Object 区分开来);
当然了以上这些符号不是固定的,只要你符合变量命名规则都可以使用,但为了更好的识别它是一个泛型,最好统一使用单个有意义的字母或者 大写+下划线 的方式来命名。
泛型的使用
- 泛型类 - 在类的定义中使用泛型(作用域是整个类);
- public class Example<T> {}
- 泛型接口 - 在接口的定义中使用泛型(作用域是接口内部或者实现类中);
- public interface Example<T> {}
- 泛型方法 - 在方法的定义中使用泛型(仅在方法内部有效);
- public <T> void method(T value) {}
高级通配符
public class Animal {}
public class Dog extends Animal {}
public class Wolf extends Animal {}
public class Rabbit extends Animal {}
6.1、 extends T>:上界通配符,表示的是类型的上界,接受 T 或者 T 的子类;
小提示:extends 顾名思义是继承的意思,? 继承了 T,那 T 就是顶点,依次向下匹配,所以叫做上界通配符。
List extends Animal> upperBounds = null;
// 正常匹配
upperBounds = new ArrayList(); // Animal 本身
upperBounds = new ArrayList(); // Animal 的子类
upperBounds = new ArrayList(); // Animal 的子类
upperBounds = new ArrayList(); // Animal 的子类
// 编译报错,超出了限定类型
upperBounds = new ArrayList();
6.2、 super T>:下界通配符,表示的是类型的下界,接受类型 T 或者 T 的超类,并且往上直到 Object;
小提示:super 就是代表父类、超类的意思,代表由 T 往上的所有超类,所以 T 就是最下面那个,因此叫做下界通配符。
public class Animal extends Creature {} // 中间叠加 N 个父类,也可以从下往上一直匹配
// 正常匹配
List super Dog> lowerBounds = null; // 从 Dog 这一级一直往上匹配
lowerBounds = new ArrayList(); // Dog 本身
lowerBounds = new ArrayList(); // Dog 的父类 Animal
lowerBounds = new ArrayList(); // Animal 的父类 Creature
lowerBounds = new ArrayList
6.3、>:无界通配符,可以表示任何类型;
List> bounds = null;
bounds = new ArrayList();
bounds = new ArrayList();
bounds = new ArrayList();
bounds = new ArrayList
注意:通配符限定最好是用在入参或者返回类型上面,如果在变量中使用,则会出现一些奇奇怪怪的问题,因此变量的声明中最好明确指定泛型的具体类型。
// 情况1
// 和下界通配符的解释不相符,此处仅能传入 Dog 类型,按照它的说法,理论上以下代码应该是成立的,但。。。
List super Dog> bounds = new ArrayList<>();
bounds.add(new Dog()); // 正常
bounds.add(new Animal()); // 编译报错
bounds.add(new Wolf()); // 编译报错
bounds.add(new Creature()); // 编译报错
// 情况2
// 这样写,却达到了上界通配符的效果,实际和它的定义却是相悖的。
List super Animal> bounds = new ArrayList<>();
bounds.add(new Animal()); // 正常
bounds.add(new Dog()); // 正常
bounds.add(new Wolf()); // 正常
bounds.add(new Rabbit()); // 正常
// 情况3
// 无法直接使用,任何类型都无法传入。
List extends Animal> bounds = new ArrayList<>();
bounds.add(new Animal()); // 编译报错
bounds.add(new Dog()); // 编译报错
bounds.add(new Wolf()); // 编译报错
bounds.add(new Rabbit()); // 编译报错
// 情况4
// 以下的代码直接报错,没有任何意义。
List> bounds = new ArrayList<>();
bounds.add(1); // 编译报错
bounds.add(1.0F); // 编译报错
bounds.add(1.0D); // 编译报错
bounds.add("String"); // 编译报错
bounds.add(new Animal(); // 编译报错
"?" 与 "Object" 是同一个东西吗?
Object:代表所有的类型(但不包括 "?" );
?:表示无限制的类型,包括 Object,它是一个特殊的类型,通常用在 函数入参和返回 中,单独使用没啥意义,其次就是用于 通配符限定 中。
泛型擦除
泛型虽然很好用,但是,我们需要明白的是,泛型只是辅助我们开发的一种方式,说白了也是一种语法糖,它的存在只是为了在开发过程中配合编辑器提早的给出提示,在最终程序运行中其实是抹除了泛型,没有任何约束存在的,底层的操作也还是通过类型的强制转换来完成的,只不过由编译器来帮我们做了这个操作。
所以在 JAVA 中,泛型其实也可以叫做伪泛型,因此在程序代码中无法通过泛型去拿到它的一些信息,比如:
T.class // 异常,T 不是一个数据类型
T t = new T(); // 异常,T 不是一个数据类型
虽说存在泛型擦除,但是我们还是可以通过反射去来拿到部分泛型的信息,就像下面这样:
public class Example {
public final Class type;
public Example() {
ParameterizedType pt = (ParameterizedType) getClass().getGenericSuperclass();
this.type = (Class) pt.getActualTypeArguments()[0];
}
}
public class ChildA extends Example {}
public class ChildB extends Example {}
public static void main(String[] args) {
// 可以通过继承拿到泛型的具体类型,因为继承可以保留泛型的基础信息
System.out.println(new ChildA().type); // class java.lang.Integer
System.out.println(new ChildA().type); // class java.lang.Integer
// 但是无法通过类直接去获取它的泛型,因为存在泛型擦除,是获取不到的。
// Expected a Class, ParameterizedType,
// but is of type java.lang.Class
System.out.println(new Example().type);
}
如何正确地使用?
首先,如果单纯的使用一个泛型其实是毫无意义的,我们需要配合一些场景才能发挥出它的正确用法。
比如:单纯的像以下这样使用,意义不大。
public void doSomething(List values) {
for (T element : values) {
System.out.println(element);
}
}
场景一:类型转换器
public interface Converter {
OUT convert(IN input);
default OUT doConvert(IN input) {
return input == null ? null : convert(input);
}
}
public class IntConverter implements Converter {
@Override
public Integer convert(String input) {
return Integer.parseInt(input);
}
}
public class LocalDateConverter implements Converter {
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-DD HH:mm:ss");
@Override
public LocalDate convert(String input) {
return LocalDate.parse(input, formatter);
}
}
public class UserConverter extends Converter {
@Override
public User convert(String input) {
return JSON.parseObject(input, User.class);
}
}
public static void main(String[] args) {
Object input = XXX; // 外部值
List> converters = Arrays.asList(
new IntConverter(),
new LocalDateConverter(),
new UserConverter(),
);
for (Converter converter : converters) {
Object output = converter.doConvert(input);
System.out.printf("input: %s, output: %s\n", value, output);
}
}
场景二:校验器
public interface Validator {
boolean validate(T input);
}
public class Required implements Validator
场景三:Spring 中的事件通知,就可以根据具体的类型去监听不同的事件。
public class MyEvent extends ApplicationEvent {
private final Object payload;
public MyEvent(Object payload) {
this.payload = payload;
}
public Object payload() {
return this.payload;
}
}
@Component
public class MyEventListener implements ApplicationListener {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println(event.payload());
}
}
// 模拟 Spring 环境
@Autowired
private ApplicationEventPublisher publisher;
public static void main(String[] args) {
publisher.publishEvent(new MyEvent("Message"));
}