Java:泛型:擦除 / 限制 / 上下界 / Optional 类

更多
2021年08月05日 12点18分 作者:叶飞 修改

Java中实例化泛型类时,可以省略泛型参数:

MimicStack<Integer> stack = new MimicStack<>();  //空<>,OK的


强烈建议:有条件的同学对照C#泛型学习

伪泛型

背景:Java在很长一段时间,拒不承认泛型的作用(觉得泛型破坏了面向对象的纯粹性),等到决定引入泛型的时候,Java已经是5.0版本了,为了兼容旧版本,Java不得不采用了一种被称之为泛型擦除(Type erasure)的技术。

#常见面试题#:什么是发现擦除?

即:泛型参数的类型检查仅发生在编译时。

一旦编译完成,泛型类就消失了,JVM会根据泛型参数约束(边界)的最高级类:

  • 没有约束,就是Object
  • 有约束,就是extends指向的类

生成相应的类,比如:

class MimicStack<T> {

编译后就是:

class MimicStack {
所以,
  • 你同时声明这两个类,是会报错的。
    The type MimicStack is already defined
  • 非泛型类变量,装泛型对象,是不会报错的(但会警告)
    MimicStack stack = new MimicStack<Integer>(); // OK
    Object result = stack.pop();    //用Object接pop()返回值

泛型类中的:

public T pop() {
会被编译成:
public Object pop() {
如果是:
class MimicStack<T extends Student> {
就会被编译成:
public Student pop() {

所以,这样的代码,一样不会报错:

MimicStack stack = new MimicStack<Student>(); 
Student result = stack.pop();


Java泛型不支持

泛型数组

这给我讲课带来了不少的麻烦,^_^

T[] container = new T[];

报错:

Cannot create a generic array of T

咋整?最简单的一个办法

T[] container = (T[])new Object[10];

但是,会报警告(后文@SuppressWarnings可以消除)。体会:程序员无视警告

@想一想@:为什么能行?

其他参考:How to create a generic array in Java?

实例化

直接:

return new T();

会报错的:

Cannot instantiate the type T

怎么办呢?从Java8开始,可以引入supplier(后文详述)

import java.util.function.Supplier;

然后,将supplier对象作为构造函数参数传入:

private Supplier<T> supplier;
public MimicStack(Supplier<T> supplier) {
	this.supplier = supplier;
}
实例化这个泛型类的时候,用这种语法:
MimicStack<Student> stack = new MimicStack<>(Student::new); 
最后,可以调用supplier的get()方法,拿到一个泛型参数类型实例:
return supplier.get();

其他参考:How do I get a class instance of generic type T?

静态

一言蔽之:泛型成员不能和静态沾边,以下都不行:
static T obj;    //静态成员类型
static T pop() {    //静态方法返回值
static void push(T obj) {    //静态方法参数

这个……,克服一下吧,只有。本来我们也是鼓励实例,不鼓励静态的。

但是,静态的泛型方法是OK的:

public static <T> T pop() {

调用这种无法从参数类型推导出T的类型的方法时,可以在方法名前加尖括号:

MimicStack.<Integer>pop();

基本类型

这样是不行的:
MimicStack<int> stack = new MimicStack<int>(); 

为什么就不行呢?很多文章解释是因为泛型擦除。

但其实这种解释是比较牵强的,因为基本(值)类型仍然可以装箱拆箱完成转换,于是大家觉得是因为性能的原因……(参考:Why don't Java Generics support primitive types?

个人觉得,根本的原因,还是Java追求纯粹的面向对象的原因:int不是对象,Integer才是。结果就是:

fully agree on that it's bad design which endlessly hurts beginners ano professionals alike

PS:公认的,语法特性上,Java的没有C#友好——实际上,差远了。


上界下界

怎么解决这个问题?
MimicStack<Person> mp = new MimicStack<Student>();

使用通配符?表示任何类,

MimicStack<?> mp = new MimicStack<Person>();

还可以培训关键字:

  • extends <baseType>:表示baseType以下(含本身)任何(子)类
    MimicStack<? extends Person> mp = new MimicStack<Student>();
  • super <baseType>:表示baseType以上(含本身)任何(父)类
    MimicStack<? super Student> mp = new MimicStack<Person>();

@想一想@:这有啥用呢?

  • extends:能确保输出
    MimicStack<? extends Person> mp = null;
    Person p = mp.pop();
  • super:能保证输入
    MimicStack<? super Person> mp = null;
    mp.push(new Student());

PS:对应C#中in/out(协变逆变)


Optional类

#常见面试题#:开发中你最常见的错误/异常是什么?

当然是NullPointer异常:你使用的对象并不总是自己new出来的,而更可能是其他地方传过来的,传过来的对象很有可能就是null值。为了不报错,你的代码就会有大量的类似代码:
Student student = null; // 从其他地方获得的student对象
if (student == null) {

} else {

}

看着很烦。(你觉得烦不烦?不觉得就说明功力不够)

于是Java8内置了Optional泛型类,你可以把它想象成一个容器,里面存放可能为null值的对象。对象类型不定,所以要使用泛型。

获取Optional对象可以通过调用其静态方法ofNullable():

Optional<Student> os = Optional.ofNullable(student);    
不要使用of()方法,因为of()方法的参数不能为null,没有什么意义。

它提供了orElse()方法,当存放值为null时,可以用其他值替代,避免NullPointer异常:

//这样得到的Student对象永远不会为null
Student neverNull = os.orElse(new Student());

不建议这样使用Optional:

if (os.isPresent()) {   //检查os中值是否为null
	os.get();    //从os中取出不为null的对象
}

因为这样就和null值判断没有什么区别了。

PS:可对照C#的可空类型


作业

  1. 改造Entity类,让其Id可以为任意类型
  2. 声明一个泛型的Repository(仓库)类,可以存储任意Entity对象,在里面添加一个静态方法Load(id),方法返回一个new出来的entity对象(模拟id返回对应的entity)
  3. 用泛型改造:
    1. 双向链表和MimicStack栈:存取元素类型扩展
    2. 取数组中最大值(提示:利用Comparable<T>进行比较)
    3. 二分查找
  4. 选择上题中的链表,分别用代码实现:
    1. 改造链表本身,使其只能存储Content对象(及其子对象,如Article等)
    2. 链表本身不改变,但具象化的时候,指定链表中元素
      1. 只能是Content对象
      2. 至少是Article对象
泛型 泛型擦除
赞: 0 踩: 0

打赏
已收到打赏的 帮帮币

你的 打赏 非常重要!
为了保证文章的质量,每一篇文章的发布,都已经消耗了作者 1 枚 帮帮币
没有“帮帮币”,作者无法发布新的文章。

全系列阅读
评论 / 0

后台开发


其他:WebForm和WebApi

其他ASP.NET框架,如WebForm、WebApi……

RazorPages(Core)

微软推荐的、最新的、基于Razor页面和.NET core的新一代Web项目开发技术,包括Razor Tag Helper、Model绑定和Validation、Session/Cookie、内置依赖注入等……

MVC(Framework)

过去两年间最流行的、基于.NET Framework和MVC模式的ASP.NET MVC框架,主要用于讲解安全、性能、架构和各种实战功能演示……

C#语法

从入门的变量赋值、分支循环、到面向对象,以及更先进的语言特性,如:泛型、Lambda、Linq、异步方法等…………

Java语法

面向过程的变量赋值、分支循环和函数封装;面向对象的封装、继承和多态;以及更高阶的常用类库(集合/IO/多线程……)、lambda等

Java Web开发

SpringMVC

分层架构和综合实战

J&C

Java和C#共有的语法

全部
关键字



帮助

反馈