学编程,来源栈;先学习,再交钱
当前系列: Java语法 修改

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#的可空类型


作业

见:J&C:泛型:作用 / 具象化 / 约束 / 继承


泛型 泛型擦除
觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 Java语法 中继续学习:

我们的 特色

  • 先学习,后付费
  • 线上/线下,自由组合

更多了解

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧