编写:bravo1988
校审:养兔子的大叔
上篇提到泛型可以看做是对变量类型的抽取,它把原本必须确定的对象类型也弄成了变量,最终得到代码模板。
但是,敏感的读者马上就会发现这tm是个悖论啊:对象类型不确定,JVM怎么创建对象啊!
要回答这个问题,我们必须了解Java的两个阶段:编译期、运行期。你可以理解为Java代码运行有4个要素:
- 源代码
- 编译器
- 字节码
- 虚拟机
也就是说,Java有两台很重要的机器,编译器和虚拟机。
在代码编写阶段,我们确实引入了泛型对变量类型进行泛化抽取,让类型是不特定的(不特定的即通用的),从而创造了通用的代码模板,比如ArrayList<T>:
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}
模板定好后,如果我们希望这个ArrayList只处理String类型,就传入类型参数,把T“赋值为”String,比如ArrayList<String>,此时你可以理解为代码变成了这样:
public class StringArrayList {
private String[] array;
private int size;
public void add(String e) {...}
public void remove(int index) {...}
public String get(int index) {...}
}
所以add(1)会编译报错。
但事实真的如此吗?
我们必须去了解泛型的底层机制。
泛型擦除与自动类型转换
我们来研究以下代码:
public class GenericDemo {
public static void main(String[] args) {
UserDao userDao = new UserDao();
User user = userDao.get(new User());
List<User> list = userDao.getList(new User());
}
}
class BaseDao<T> {
public T get(T t){
return t;
}
public List<T> getList(T t){
return new ArrayList<>();
}
}
class UserDao extends BaseDao<User> {
}
class User{
}
编译得到字节码:
![69418160603820014284a9bf3cf3ec54.png](https://i-blog.csdnimg.cn/blog_migrate/4e8e248d9cdf73aef910f7402c724bfd.jpeg)
通过反编译工具,反编译字节码得到:
public class GenericDemo {
// 编译器会为我们自动加上无参构造器
public GenericDemo() {}
public static void main(String args[]) {
UserDao userDao = new UserDao();
/**
* 两点变化:
* 1.原先代码是 User user = userDao.get(new User());
* 编译器根据泛型类型User,帮我们强转了
* 2.List<User>的泛型被擦除了,只剩下List
*/
User user = (User)userDao.get(new User());
java.util.List list = userDao.getList(new User());
}
}
class BaseDao {
BaseDao() {}
// 编译器编译后的字节码中,其实是没有泛型的,泛型T其实底层还是JDK1.5的Object
public Object get(Object t)