Java 泛型优点之编译时类型检查

简介: Java 泛型优点之编译时类型检查使用泛型代码要比非泛型代码更有优势,下面是 Java 官方教程对泛型其中一个优点的介绍:“Stronger type checks at compile time.

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"));
...

示意图如下:

img_545c46bd8680a316ff4d27f8441f7a4f.png

当我们提取容器中的某一个 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 中的泛型机制的不同

https://stackoverflow.com/questions/36347/what-are-the-differences-between-generic-types-in-c-and-java

运行时 VS 编译时

https://stackoverflow.com/questions/846103/runtime-vs-compile-time

《Java 编程思想》第四版 通过异常处理错误 (为什么编译时解决问题要比运行时解决问题要好的原因之一)

目录
相关文章
|
6月前
|
安全 Java 编译器
Java类型提升与类型转换详解
本文详解Java中的类型提升与类型转换机制,涵盖类型提升规则、自动类型转换(隐式转换)和强制类型转换(显式转换)的使用场景与注意事项。内容包括类型提升在表达式运算中的作用、自动转换的类型兼容性规则,以及强制转换可能引发的数据丢失和运行时错误。同时提供多个代码示例,帮助理解byte、short、char等类型在运算时的自动提升行为,以及浮点数和整型之间的转换技巧。最后总结了类型转换的最佳实践,如避免不必要的转换、使用显式转换提高可读性、金融计算中使用BigDecimal等,帮助开发者写出更安全、高效的Java代码。
343 0
|
6月前
|
安全 IDE Java
Java记录类型(Record):简化数据载体类
Java记录类型(Record):简化数据载体类
517 143
|
6月前
|
Java 测试技术
Java浮点类型详解:使用与区别
Java中的浮点类型主要包括float和double,它们在内存占用、精度范围和使用场景上有显著差异。float占用4字节,提供约6-7位有效数字;double占用8字节,提供约15-16位有效数字。float适合内存敏感或精度要求不高的场景,而double精度更高,是Java默认的浮点类型,推荐在大多数情况下使用。两者都存在精度限制,不能用于需要精确计算的金融领域。比较浮点数时应使用误差范围或BigDecimal类。科学计算和工程计算通常使用double,而金融计算应使用BigDecimal。
2538 102
|
4月前
|
存储 算法 安全
Java集合框架:理解类型多样性与限制
总之,在 Java 题材中正确地应对多样化与约束条件要求开发人员深入理解面向对象原则、范式编程思想以及JVM工作机理等核心知识点。通过精心设计与周密规划能够有效地利用 Java 高级特征打造出既健壮又灵活易维护系统软件产品。
149 7
|
5月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
409 10
|
5月前
|
Java 开发者
Java 函数式编程全解析:静态方法引用、实例方法引用、特定类型方法引用与构造器引用实战教程
本文介绍Java 8函数式编程中的四种方法引用:静态、实例、特定类型及构造器引用,通过简洁示例演示其用法,帮助开发者提升代码可读性与简洁性。
|
6月前
|
安全 算法 Java
Java泛型编程:类型安全与擦除机制
Java泛型详解:从基础语法到类型擦除机制,深入解析通配符与PECS原则,探讨运行时类型获取技巧及最佳实践,助你掌握泛型精髓,写出更安全、灵活的代码。
|
Java API 容器
Java泛型的继承和通配符
Java泛型的继承和通配符
124 1
|
安全 Java API
Java一分钟之-泛型通配符:上限与下限野蛮类型
【5月更文挑战第19天】Java中的泛型通配符用于增强方法参数和变量的灵活性。通配符上限`? extends T`允许读取`T`或其子类型的列表,而通配符下限`? super T`允许向`T`或其父类型的列表写入。野蛮类型不指定泛型,可能引发运行时异常。注意,不能创建泛型通配符实例,也无法同时指定上下限。理解和适度使用这些概念能提升代码的通用性和安全性,但也需兼顾可读性。
363 3
|
Java 编译器
[java进阶]——泛型类、泛型方法、泛型接口、泛型的通配符
[java进阶]——泛型类、泛型方法、泛型接口、泛型的通配符
195 0