Java泛型
2019-08-26 本文已影响0人
燕城白夜
Java 1.5版本中增加了泛型。
为什么需要引入泛型?
在引入泛型之前,读取集合中的每一个元素都需要进行转换,如果在集合中插入了错误类型的对象,那么就会在程序运行时报类型转换的错误。在引入泛型后我们可以告诉编译器需要向集合中插入的元素类型,编译器会在插入元素时进行自动转换,并在编译时报告往集合中插入错误类型的代码。
在Java代码中还可以继续使用原生态类型例如List等的原因是因为向后兼容,让以前未使用泛型的代码保存合法,并且能够与使用泛型的代码进行交互。在新代码中不推荐使用List等原生态类型。
List 和 List<Object> 区别?
两种集合都允许插入任何类型的对象。简单说List逃避了编译器的类型检查,而List<Object>则明确告知编译器其可以保存任何类型的对象。
泛型有子类型化的规则,List<String> 是原生态类型List的子类型而不是List<Object>的子类型,所有可以将List<String> 类型的对象传递给List类型的参数但不能传递给List<Object>类型的参数:
package template;
import java.util.ArrayList;
import java.util.List;
/**
* @author: zhouzhaoping
* @description:
* @date: 2019-08-25
*/
public class Test1 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
test1(list);
test2(list); // 报错
}
public static void test1(List list) {
for (Object o : list) {
System.out.println("test1:" + o.toString());
}
}
public static void test2(List<Object> list) {
for (Object o : list) {
System.out.println("test2:" + o.toString());
}
}
}
使用原生态类型List会失去类型安全性,使用List<Object>这样的参数化类型则不会,为了更好的说明请参考下面的代码。
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0);
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
在最新的Java8中,上述的代码已经无法通过编译。
将List替换为List<Object>能看到更明确的报错信息。
在不确定集合元素类型的情况下使用无限制的通配符类型代替使用原生态类型
如果你需要筛选集合的交集,使用原生态类型你可以这样写:
public static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1: s1) {
if (s2.contains(o1)) {
result++;
}
}
return result;
}
由于使用了原生类型,这样写是很危险的。
如果要定义一个参数化的类型(例如List<String>,因为不能使用List<E>定义),但不确定或不关心实际的参数类型则可以使用一个?来代替。
public static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o1: s1) {
if (s2.contains(o1)) {
result++;
}
}
return result;
}
可以将任何元素放入原生态类型的集合中,因此很容易破坏该集合的类型约束条件,例如如下代码可通过编译:
public static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1: s1) {
if (s2.contains(o1)) {
result++;
}
}
s1.add("1");
s2.add(2);
return result;
}
但如下代码却不行:
public static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o1: s1) {
if (s2.contains(o1)) {
result++;
}
}
s1.add("1");
s2.add(2);
return result;
}
通过以上例子不难看出?通配符主要使用在函数的形参上且在该函数内部不会修改该集合的场景中。
数组与泛型
数组与泛型相比有两个重要的不同点,第一个是数组是协变的而泛型不是,例如如果Sub是Super的子类型,那么Sub[]也是Super[]的子类型。相反泛型则是不可变的:对于任意两个不同的类型Type1和Type2,List<Type1>和List<Type2>没有关系( 其实这是数组设计的缺陷而不是泛型)。
这个不同导致使用泛型可以比使用数组更容易发现错误,参考如下的代码:
public static void main(String[] args) {
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in.";
}
这段代码可以通过编译但在执行时会抛出异常。
public static void main(String[] args) {
List<Object> o1 = new ArrayList<Long>();
o1.add("I don't fit in.");
}
而这段代码则无法通过编译,对比数组我们可以更早的发现错误。