Java 泛型优点之编译时类型检查
使用泛型代码要比非泛型代码更有优势,下面是 Java 官方教程对泛型其中一个优点的介绍:
“Stronger type checks at compile time.
A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.”
现在我有两点 疑问 :
1、 在使用泛型时能在编译时被检测出的问题,在未使用泛型时是怎样的情况?即怎样才会出现这类上文中最后一句提到的不是更容易解决的运行时错误?(以代码举例)
2、 Java 如何提供这种编译时的更强的类型检查(第一句)。
解决
在 Java 还未明确的实现泛型机制之前,是具有泛型能力的,只不过没有进行语法层次上的包装。比如以容器举例。
容器的中的元素基本类型都是 Object,而由于 Java 的设计理念,Java 中所有类默认都是继承于 Object 的,所以容器中的每一个元素都可 hold 任意对象的实例。
代码如下:
ArrayList list = new ArrayList();
list.add(new String("over"));
list.add(new String("loard"));
...
示意图如下:
当我们提取容器中的某一个 Object 元素时,我们只能访问到 Object 对象作用域内的实例和方法。为了访问更加具体的对象(比如上图中的 String)的方法或者实例域,我们需要告诉编译器将 Object 引用转换为 String 类型(Object 引用只能访问 String 对象的一个子集,即定义在 Object 对象中的部分。即便我们的确有一个 String 对象)。当这种转换符合继承层级时(String 是 Object 的子类),转化即可以通过编译(只是通过编译)。
String str = (String)list.get(i);
自然地,现在我们可以通过 str 访问 String 对象的方法和实例域。但是这里其实是存在潜在的问题的。Object 引用能够 hold 任意对象,那么在这个例子的容器中,意味着我们可以将其他 Object 的子类类型的对象传递给容器的元素:
list.add(new Integer(1)); //通过编译
然后当我们再次执行类型转换时,编译时没问题(因为实际是 String(Object) ),但程序将会在运行时抛出一个异常。
String str = (String)list.get(i); //抛出 ClassCastException 异常
尽管异常机制会提醒我们程序发生了我们未预期的情况,并将这些错误反馈给我们,然而如果问题能在编译时被解决,我们更希望在编写代码时就将错误避免掉。
当 Java 引入泛型机制后,这一目标可以被实现。Java 的泛型机制主要特点便是在原来的类型转化机制上增加类型参数和类型擦除机制。
所以当我们再次使用容器时,我们将给它传递一个类型参数:
ArrayList<String> list = new ArrayList<>();
这样当我们将不是 String 类型的对象传递给容器的元素时,编译器将会提示我们类型错误。如此一来,之前的类型转换错误就被阻挡在了编译时期。
但是,Java 为了向前兼容使用普通的类型转换的代码而采用的擦除机制并不是很强大(相比 C++)。
比如对于泛型函数来说,使用擦除机制的泛型似乎并没有带来什么改观(类型安全方面)。
类型擦除的例子:
public <T extend SomeObject> f(T t) { //默认 T 继承于 Object
T a = t;
Sysyem.out.println(a);
}
当我们对这个方法调用后,编译器将进行对类型的擦除,经编译器处理后的代码如下:
public SomeObject f(SomeObject t) {
SomeObject a = t;
Sysyem.out.println(a);
}
由于编译器在编译时将我们传递的类型信息擦除掉(无法获得类型信息),所以一旦我们进行不合法的类型转换,编译器也不会察觉:
public <T extend Object> f(T t) {
...
String str = (String)t;
...
}
//类型擦除后
public Object f(Object t) {
...
String str = (String)t; //编译完全没问题
...
}
当我们调用该方法:
f(new Integer(1)); //在运行时将抛出一个 ClassCastException
对比 C++ 的模板,C++ 将在编译时通过传递的类型参数检测到存在非法的类型转换(C++ 元编程具有将运行时检测迁移到编译期的能力)。
所以,问题应该算是被解决了(虽然有些简陋和仓促)。
感谢阅读
转载请注明出处
参考资料:
泛型类型擦除
https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html
Java 泛型类型安全
https://stackoverflow.com/questions/44841156/java-generics-type-safety
Java 中的类型转换
https://stackoverflow.com/questions/5289393/casting-variables-in-java
C++ 和 Java 中的泛型机制的不同
运行时 VS 编译时
https://stackoverflow.com/questions/846103/runtime-vs-compile-time
《Java 编程思想》第四版 通过异常处理错误 (为什么编译时解决问题要比运行时解决问题要好的原因之一)