`
dandy
  • 浏览: 67606 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

不情愿的构造器

    博客分类:
  • java
阅读更多
出自《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(){ }


总之,实例初始化操作是先于构造器的程序体而运行的。实例初始化操作抛出的任何异常都会传播给构造器。如果初始化操作抛出的是被检查异常,那么构造器必须声明也会抛出这些异常,但是应该避免这样做,因为它会造成混乱。最后,对于我们所设计的类,如果其实例包含同样属于这个类的其他实例,那么对这种无限递归要格外当心。
2
0
分享到:
评论

相关推荐

    深入理解java构造器机理

    当我们写一个没有构造器的类,编译的时候,编译器会自动加上一个不带参数的构造器。 六、结论 构造器是 Java 类中最重要的一个概念,用于初始化对象的创建。了解构造器的机理、执行顺序、作用及与其他概念的区别...

    java 构造器的调用

    这个构造器不执行任何初始化操作,仅调用超类的默认构造器。 - 一旦为类定义了构造器,Java将不再提供默认构造器。因此,如果需要无参构造器,必须手动声明。 4. **继承中的构造器调用**: - 子类可以调用父类的...

    理解构造器--构造器和方法的区别

    但构造器不能声明为抽象、最终、本地(native)、静态或同步的,这些都是方法可以具有的非访问性修饰符。 返回类型是两者之间另一个关键区别。方法可以有任意类型的返回值,或者无返回值(void),而构造器没有...

    NX10.0后处理构造器.rar

    《NX10.0后处理构造器:深入解析与应用》 NX10.0,作为UGS(Unigraphics Solutions)系列软件的版本之一,是Siemens PLM Software公司推出的高级三维计算机辅助设计、制造和工程分析(CAD/CAM/CAE)系统。其强大的...

    NX10.0后处理构造器

    "NX10.0后处理构造器"是一个用于创建、编辑和定制数控(NC)代码后处理程序的强大工具。在数控编程中,后处理是将高级的几何模型或刀具路径转换为特定机床语言(如G代码)的过程,使得机床能够理解和执行。此工具主要...

    Java入门理解构造器

    - **静态构造器不存在**:Java中没有静态构造器的概念,所有构造器都用于创建对象实例。 #### 七、示例代码解析 在提供的部分代码中,可以看到如下示例: ```java public class Platypus extends Mammal { String...

    构造器和方法的区别

    然而,构造器不能使用`abstract`、`final`、`native`、`static`或`synchronized`等非访问修饰符,这些修饰符仅适用于方法。 构造器没有返回类型,而方法可以有任意类型的返回值,包括`void`表示无返回值。此外,...

    NX12.0.2.9 后处理构造器

    【NX12.0.2.9 后处理构造器】是UGS(Unigraphics Solutions)NX软件中的一个重要组成部分,主要用于创建自定义的数控(NC)代码,以适应各种机床和控制器的需求。UGS NX是一款强大的计算机辅助设计、制造和工程分析...

    JPA构造器的使用样例

    总的来说,JPA构造器的使用样例展示了如何通过自定义构造器将多表查询的结果映射到自定义的Java对象上,从而更方便地处理数据库查询结果。这种方法不仅可以减少手动转换数据的工作,还可以使代码更清晰、更易于理解...

    HYPERMILL五轴后处理构造器

    【标题】:“HYPERMILL五轴后处理构造器”是一种高级...总之,HYPERMILL五轴后处理构造器是现代制造业中不可或缺的工具,它使工程师能够充分利用五轴机床的潜力,实现高效、精确的零件加工,推动制造业向更高水平发展。

    NX9.0加工后处理构造器

    标题中的“NX9.0加工后处理构造器”是指UGS(Unigraphics Solutions)的NX9.0版本中的一个特定功能,它涉及到CAD/CAM/CAE一体化软件的后处理部分。在数控编程(NC Programming)中,后处理是将经过 CAM(计算机辅助...

    当用LABVIEW加载 C# 生成的DLL文件 时显示 该类不包括任何公共构造器-解决方案

    当用LABVIEW加载 C# 生成的DLL文件 时显示" 该类不包括任何公共构造器"。 原因:类,或方法未放PUBLIC关键字。 解决办法:1、加上关键字;2、重新命名 命名空间(如果不重新命名,在加载时会依然报警,似乎...

    04实现mybatis条件构造器代码demo

    04实现mybatis条件构造器代码demo04实现mybatis条件构造器代码demo04实现mybatis条件构造器代码demo04实现mybatis条件构造器代码demo04实现mybatis条件构造器代码demo04实现mybatis条件构造器代码demo04实现mybatis...

    MyBatisPlus条件构造器 -Wrapper详解.ziw

    MyBatisPlus条件构造器 -Wrapper详解(为知笔记版,可用网页打开),详解wrapper条件构造器的各种使用方法及其扩展类的使用方法。

    JS+HTML版表单构造器(Fom Builder)适合Web系统使用

    **Bootstrap表单构造器_files**可能是一个文件夹,包含了与Bootstrap表单构造器相关的资源文件,如CSS样式表、JavaScript库、图片和其他必要的Web资产。这些文件是构建和运行表单构造器所必需的,它们确保了表单构造...

    JAVA构造器

    JAVA构造器

    因为一个Crash引发对Swift构造器的思考分析

    Swift 构造器的思考分析 Swift 语言提供了两种构造器:指定构造器和便利构造器。类倾向于拥有极少的指定构造器,普遍的是一个类只拥有一个指定构造器。每一个类都必须至少拥有一个指定构造器。Swift 语言中的构造...

    构造器的解释.

    5. **默认构造器**:如果类中没有显式地定义任何构造器,Java编译器会自动提供一个不带参数的默认构造器。 #### 三、构造器的应用实例 下面通过两个例子来具体说明构造器的作用和使用方法。 ### 实例一:基本构造...

    C#程序设计-3期(KC008) KC008110100021-C# 构造器和C++ 构造器是否相同?.docx

    C++中的构造器可以是虚函数,而C#的构造器则不能。在C++中,如果子类重写了父类的构造器,可以通过基类指针调用子类的构造器。而在C#中,由于构造器不能是虚函数,这种行为是不可能的。C#提供的是继承链上的构造器...

    spring介绍(set和构造器注入)

    - **构造器注入**更适用于那些必不可少的依赖,因为它强制在实例化时提供所有的必需依赖,增强了对象的内聚性。 4. **选择注入方式**: - 如果一个bean的所有依赖都是必须的,那么构造器注入更为合适,因为它可以...

Global site tag (gtag.js) - Google Analytics