什么是JAVA泛型的原生类型(raw type),为什么不建议使用?
在我们写代码的时候,如果无意间写出下面的代码:
List names = new ArrayList();
...
在编译时,javac会提示:Raw use of parameterized class 'List'
,那么,什么是raw type
呢?
raw type
的历史渊源
在很久很久以前,java还没有泛型,如果要声明一个名称的列表,可能代码是这样的
// name is string
List names = new ArrayList();
使用这个列表时,需要这样使用:
names.add("张三");
...
for (int i = 0; i < names.size(); i++) {
String name = (String)names.get(i);
...
}
可以看到,每次取列表的值时,都需要进行一次强制类型转换,非常繁琐;而且在使用时还要时刻注意列表里面存储的类型,使用起来很不方便。更严重的是,列表里面的内容只能人工确保类型一致,很可能出现问题,比如下面这样:
// 加了一个int进去!
names.add(1);
在使用时,代码还是(String)names.get(i);
,那么会在运行时得到一个类型转换错误,导致线上BUG。
泛型横空出世
为了解决上面的问题,java在1.5后增加了泛型,上面的代码就可以改写成这样的:
List<String> names = new ArrayList<>();
names.add("xxx");
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
...
}
在编写代码的时候,声明自己存储的是字符串类型,编译器就可以自动完成强制类型转换,不必再手动转换了。而且,当我们尝试将一个其他类型的值放到列表中时,编译器会直接报错,如下所示:
names.add(1);
// 'add(java.lang.String)' in 'java.util.List' cannot be applied to '(int)'
编译器会帮助我们做类型检查,防止错误类型进入。
什么是raw type
,为什么不建议使用
现在可以回答什么是raw type
了。raw type
就是定义了泛型的类中,不带任何实际类型参数的那种类型。比如List<E>
的类型参数是E
,那么不带类型参数的List
就是原生类型了。
由此可见,原生类型是一个历史遗留的问题,他的存在是为了兼容没有泛型时的老代码。
使用泛型,可以获得安全性(类型检查)和描述性(类型参数)的优势,而使用原生类型则已经没有任何优势。
List<Object>
,List<?>
与原生类型的区别
实践中,我们也会遇到List<Object>
和List<?>
这两种看起来和原生类型很像的,特别是List<?>
,那么,他们和原生类型有什么区别呢?
List<Object>
与原生类型
List<Object>
是一个参数化的List
,他的类型参数为Object
,相当于明确告知编译器这个列表可以持有任意类型的对象,使用这个仍然是类型安全的,和其他的类型参数相比,没有明显区别。
但是,如果我们有一个List<String>
,想把它赋值给List<Object>
,是不合法的,因为他俩不是同一类型。将List<Object>
声明为List<? extends Object>
,才可以。如下所示:
List<String> names = new ArrayList<>();
List<Object> objects;
// 非法:Incompatible types
// objects = names;
List<? extends Object> objectExtends;
// 合法
objectExtends = names;
List<?>
与原生类型
List<?>
中的?
是通配符,通配符可以被? extends
或者? super
来限制,如果没有添加任何限制,则是无限制的通配符,说明这个参数可以是任意集合。使用List<?>
是类型安全的,因为它不可以将除了null以外的任何值放到集合中(原生类型List
则可以将任何值放到集合中,导致类型不安全。)
使用raw type
的例外场景
除非迫不得已,不要使用原生类型。下面的情形就是迫不得已的时候,必须要使用原生态类型了。
在获取带泛型的类的class对象时,必须使用原生类型。就是写成List.class
是合法的,而List<String>.class
就是非法的。