出自《java puzzle》
尽管在一个方法声明中看到一个throws子句是很常见的,但是在构造器的声明中看到一个throws子句就很少见了。下面的程序就有这样的一个声明。那么,它将打印出什么呢?
public class Reluctant {
private Reluctant internalInstance = new Reluctant();
public Reluctant() throws Exception {
throw new Exception("I'm not coming out");
}
public static void main(String[] args) {
try {
Reluctant b = new Reluctant();
System.out.println("Surprise!");
} catch (Exception ex) {
System.out.println("I told you so");
}
}
}
main方法调用了Reluctant构造器,它将抛出一个异常。你可能期望catch子句能够捕获这个异常,并且打印I told you so。凑近仔细看看这个程序就会发现,Reluctant实例还包含第二个内部实例,它的构造器也会抛出一个异常。无论抛出哪一个异常,看起来main中的catch子句都应该捕获它,因此预测该程序将打印I told you应该是一个安全的赌注。但是当你尝试着去运行它时,就会发现它压根没有去做这类的事情:它抛出了StackOverflowError异常,为什么呢?
与大多数抛出StackOverflowError异常的程序一样,本程序也包含了一个无限递归。当你调用一个构造器时,实例变量的初始化操作将先于构造器的程序体而运行[JLS 12.5]。在本谜题中, internalInstance变量的初始化操作递归调用了构造器,而该构造器通过再次调用Reluctant构造器而初始化该变量自己的internalInstance域,如此无限递归下去。这些递归调用在构造器程序体获得执行机会之前就会抛出StackOverflowError异常,因为StackOverflowError是Error的子类型而不是Exception的子类型,所以catch子句无法捕获它。
对于一个对象包含与它自己类型相同的实例的情况,并不少见。例如,链接列表节点、树节点和图节点都属于这种情况。你必须非常小心地初始化这样的包含实例,以避免StackOverflowError异常。
至于本谜题名义上的题目:声明将抛出异常的构造器,你需要注意,构造器必须声明其实例初始化操作会抛出的所有被检查异常。下面这个展示了常见的“服务提供商”模式的程序,将不能编译,因为它违反了这条规则:
public class Car {
private static Class engineClass = ...;
private Engine engine = (Engine)enginClass.newInstance();
public Car(){ }
}
尽管其构造器没有任何程序体,但是它将抛出两个被检查异常,InstantiationException和IllegalAccessException。它们是Class.Instance抛出的,该方法是在初始化engine域的时候被调用的。订正该程序的最好方式是创建一个私有的、静态的助手方法,它负责计算域的初始值,并恰当地处理异常。在本案中,我们假设选择engineClass所引用的Class对象,保证它是可访问的并且是可实例化的。
下面的Car版本将可以毫无错误地通过编译:
//Fixed - instance initializers don’t throw checked exceptions
public class Car {
private static Class engineClass = ...;
private Engine engine = newEngine;
private static Engine newEngine() {
try {
return (Engine)engineClass.newInstance();
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InstantiationException e) {
throw new AssertionError(e);
}
}
public Car(){ }
总之,实例初始化操作是先于构造器的程序体而运行的。实例初始化操作抛出的任何异常都会传播给构造器。如果初始化操作抛出的是被检查异常,那么构造器必须声明也会抛出这些异常,但是应该避免这样做,因为它会造成混乱。最后,对于我们所设计的类,如果其实例包含同样属于这个类的其他实例,那么对这种无限递归要格外当心。
分享到:
相关推荐
谜题 40:不情愿的构造器 尽管在一个方法声明中看到一个 throws 子句是很常见的,但是在构造器的声明 中看到一个 throws 子句就很少见了。下面的程序就有这样的一个声明。那么, 它将打印出什么呢? public class ...
谜题40:不情愿的构造器 谜题41:域和流 谜题42:异常为循环而抛 谜题43:异常地危险 谜题44:切掉类 谜题45:令人疲惫不堪的测验 Java谜题5——类谜题 谜题46:令人混淆的构造器案例 谜题47:啊呀!我的猫...
小姐姐炫酷唯美引导页.zip
scratch少儿编程逻辑思维游戏源码-让它们跳起来.zip
少儿编程scratch项目源代码文件案例素材-战斗塔防.zip
前端开发_Vue全家桶_vw适配_vux组件库_mescroll滚动_fastclick优化_mock模拟_less预处理器_移动端脚手架_基于Vue2和Webpack3构建的移动
数据可视化分析_微信小程序开发_JavaScript_Canvas图表渲染_数据助手工具_仿微信小程序数据助手_支持线性图圆环图柱状图条形图_动态效果适配_商业数据分析_移动端数据
少儿编程scratch项目源代码文件案例素材-章鱼怪.zip
音乐流媒体_API开发_网易云音乐接口封装与数据抓取_提供完整的网易云音乐API调用解决方案_包含歌曲搜索_MP3资源获取_歌词解析_播放列表管理_歌手专辑查询_专辑详情展示_MV
物联网与健康监测_基于RISC-V架构ESP32-C3芯片与MAX30102MAX30205传感器_LVGL图形界面与WiFiBLE双模通信的智能手表系统_实时监测心率血氧体温
wangtengfei-hn_EmployeesExample_23540_1745868671962
Android开发_自定义View绘制动画效果_模仿小米手环App首页计步数据展示_实现可自定义目标步数当前步数颜色字体大小圆点尺寸的动态步数统计图表组件_用于健康运动类App展示
少儿编程scratch项目源代码文件案例素材-钻机机器人.zip
scratch少儿编程逻辑思维游戏源码-狂暴坦克.zip
scratch少儿编程逻辑思维游戏源码-拳击.zip
scratch少儿编程逻辑思维游戏源码-魔方冲刺.zip
少儿编程scratch项目源代码文件案例素材-重返危机.zip
杰奇CMS小说网站系统_php53-73_mysql5-MariaDB10_utf8mb4编码_GBK自动转码_INNODB存储引擎_多模块支持_前后台分离_电脑手机双版适配_
linyuan620_StockAnalysisAssistant_17128_1745866117970
本系统采用的数据库是Mysql,使用SpringBoot框架开发,运行环境使用Tomcat服务器,ECLIPSE 是本系统的开发平台。在设计过程中,充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。