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 中的泛型机制的不同

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 编程思想》第四版 通过异常处理错误 (为什么编译时解决问题要比运行时解决问题要好的原因之一)

作者:Skipper

出处:http://www.cnblogs.com/backwords/p/9336714.html

本博客中未标明转载的文章归作者 Skipper 和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

Java 泛型优点之编译时类型检查的更多相关文章

  1. Java泛型函数的运行时类型检查的问题

    在一个数据持久化处理中定义了数据保存和读取的 泛型函数的,但是在运行时出现类型转换错误,类型不匹配,出错的位置不是load方法,而是在调用load方法之后,得到了列表数据,对列表数据进行使用时出现的. ...

  2. 编译期类型检查 in ClojureScript

    前言  话说"动态类型一时爽,代码重构火葬场",虽然有很多不同的意见(请参考),但我们看到势头强劲的TypeScript和Flow.js,也能感知到静态类型在某程度上能帮助我们写出 ...

  3. java多态的向上转型与向下转型(与编译时类型与运行时类型有关)

    1.编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定. 当编译时类型和运行时类型不一致时,就会出现所谓的多态. 因为子类是一个特殊的父类,因此java允许把一个子类对象直接 ...

  4. Java泛型总结---基本用法,类型限定,通配符,类型擦除

    一.基本概念和用法 在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化.例如在哈希表的存取中,JDK1.5之前使用HashMap的 ...

  5. java 泛型的内部原理:类型擦除以及类型擦除带来的问题

    一.Java泛型的实现方法:类型擦除前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首要前提是理解类型擦出(ty ...

  6. Java 泛型 四 基本用法与类型擦除

    简介 Java 在 1.5 引入了泛型机制,泛型本质是参数化类型,也就是说变量的类型是一个参数,在使用时再指定为具体类型.泛型可以用于类.接口.方法,通过使用泛型可以使代码更简单.安全.然而 Java ...

  7. 8. 多态——编译时类型&运行时类型

    一.引用变量的两种类型 1. 编译时类型:由声明该变量时使用的类型决定 2. 运行时类型:由实际赋给该变量的对象决定 如果编译时类型和运行时类型不一致,就可能出现多态. class BaseClass ...

  8. java如何在eclipse编译时自动生成代码

    用eclipse写java代码,自动编译时,如何能够触发一个动作,这个动作是生成本项目的代码,并且编译完成后,自动生成的代码也编译好了, java编辑器中就可以做到对新生成的代码的自动提示? 不生成代 ...

  9. 一句话,讲清楚java泛型的本质(非类型擦除)

    背景 昨天,在逛论坛时遇到个这么个问题,上代码: public class GenericTest { //方法一 public static <T extends Comparable< ...

随机推荐

  1. 列式数据库~clickhouse 场景以及安装

    一 简介:列式数据库clickhouse的安装与基本操作二 基本介绍:ClickHouse来自俄罗斯,是一款列式数据库三 适用场景: 简单类型的大数据统计四 限制     1 不支持更新操作,不支持事 ...

  2. IP分片丢失重传 - Sacrifice的日志 - 网易博客

        尽管IP分片看起来是是透明的,但有一点让人不想使用它:即使只丢失一片数据也要重传整个数据报.为什么会发生这种情况呢?     因为IP层本身没有超时重传的机制--由更高层来负责超时和重传(TC ...

  3. 【转】Windows下安装python2和python3双版本

    [转]Windows下安装python2和python3双版本 现在大家常用的桌面操作系统有:Windows.Mac OS.ubuntu,其中Mac OS 和 ubuntu上都会自带python.这里 ...

  4. windows上python上传下载文件到linux服务器指定路径【转】

    从windows上传文件到linux,目录下的文件夹自动创建 #!/usr/bin/env python # coding: utf-8 import paramiko import datetime ...

  5. Bootstrap3.0学习第四轮(排版)

    详情请查看http://aehyok.com/Blog/Detail/10.html 个人网站地址:aehyok.com QQ 技术群号:206058845,验证码为:aehyok 本文文章链接:ht ...

  6. 研究slatstack时踩过的坑,注意点及解决方案

    运行问题 1.直接物理性移除minion或者更换minion原先连接的master,会导致先前的master始终无法ping通minion [root@localhost salt]# salt '* ...

  7. grep基础用法

    功能:全面搜索正则表达式并把行打印出来,是一种强大的文本搜索工具. grep  yuan  filename :在文件中搜索yuan 这个字符串,并把含有此字符串的行打印出来,也可以多文件搜索.  g ...

  8. 『实践』Yalmip获取对偶函数乘子

    『实践』Yalmip获取对偶函数乘子 一.sdpsetting设置 Yalmip网站给出的说明 savesolveroutput默认为0,需要设置为1才会保存输出结果. 下面是我模型的约束个数: 二. ...

  9. C/C++:函数调用规则__stdcall,__cdecl,__pascal,__fastcall

    __cdecl __cdecl 是 C Declaration  的缩写,表示 C 语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈.被调用函数不会要求调用者传递多 ...

  10. centos6中创建软raid方法

    raid概述: 组建raid阵列命令: mdadm:模式化的工具 /etc/mdadm.conf     -A  Assemble 装配模式     -C  Create 创建模式     -C:专用 ...