【问题标题】:Find out if constructor executed successfully or not查看构造函数是否成功执行
【发布时间】:2018-03-18 16:23:41
【问题描述】:

所以我又在玩java了,在这样做的时候,我遇到了一个有趣的问题。

我正在尝试为自己编写一个小的注册服务。如果某个类型的对象通过其构造函数进行实例化,它将从我的注册服务中检索一个 id 并添加到对象的服务列表中。

这是一个示例对象

public class Test {

  private final Long id;

  public Test() {
    this.id = TestRegistration.register(this);
    throw new IllegalAccessError();
  }

  public Long getId() {
    return this.id;
  }
}

这是一个示例服务

public class TestRegistration {

  private final static Map<Long, Test> registration = new HashMap<>();

  protected final static long register(final Test pTest) {
    if (pTest.getId() != null) {
        throw new IllegalStateException();
    }
    long freeId = 0;
    while (registration.containsKey(freeId)) {
        freeId = freeId + 1;
    }
    registration.put(freeId, pTest);
    return freeId;
  }

  protected final static Test get(final long pId) {
    return registration.get(pId);
  }
}

现在你可以看到,我的类Test 的构造函数根本无法成功执行,因为它总是抛出一个IllegalAccessError。但是在抛出这个错误之前,我的TestRegistration 类的register(...) 方法在构造函数中被调用。在这个方法中,它被添加到注册Map。所以如果我会运行例如这段代码

public static void main(final String[] args) {
    try {
        final Test t = new Test();
    } catch (final IllegalAccessError e) {

    }
    final Test t2 = TestRegistration.get(0);
    System.out.println(t2);
}

我的TestRegistration 实际上包含在调用我的Test 类的构造函数时创建的对象,我可以(这里使用变量t2)访问它,即使它在第一次没有成功创建地点。

我的问题是,如果 Test 的构造函数在没有任何异常或其他中断的情况下成功执行,我能否以某种方式从我的 TestRegistration 类中检测到?

在你问我为什么首先抛出这个异常之前,这是我的答案。测试可能有我还不知道的潜在子类,但仍将在我的 TestRegistration 类中注册。但由于我对这些子类的结构没有影响,我无法判断它们的构造函数是否会抛出异常。

【问题讨论】:

标签: java constructor


【解决方案1】:

似乎不可能。

我已经从下面的 JLS 中收集到了我能收集到的最好的东西。

由此可见,构造器主体的执行是创建对象的最后一步。

因此,我没有看到任何证据表明引发异常会导致任何发生,您可以检查执行是否成功(除了其余的显然是构造函数的主体)。

这似乎不是特别令人信服,尽管我不相信你会找到比这更具体的东西(毕竟,要证明某物不存在是出了名的困难)。


我建议将修改外部数据的逻辑放在其他地方。

要求在构造对象后显式调用register 方法(在TestRegistration 类中或在对象本身中)并非完全不合理。

除了直接调用构造函数之外,它还可以有一个包含返回对象方法的类(这听起来很像 工厂设计模式),然后您可以将这个外部修改逻辑。


来自 JLS:

15.9.4. Run-Time Evaluation of Class Instance Creation Expressions

在运行时,类实例创建表达式的评估如下。

首先,如果类实例创建表达式是合格的类实例创建表达式,则评估合格的主表达式。如果限定表达式的计算结果为 null,则会引发 NullPointerException,并且类实例创建表达式会突然完成。如果限定表达式突然完成,则类实例创建表达式出于同样的原因突然完成。

接下来,为新的类实例分配空间。如果没有足够的空间来分配对象,类实例创建表达式的评估会通过抛出 OutOfMemoryError 突然完成。

新对象包含在指定类类型及其所有超类中声明的所有字段的新实例。在创建每个新字段实例时,都会将其初始化为其默认值(第 4.12.5 节)。

接下来,从左到右计算构造函数的实际参数。如果任何参数计算突然完成,则不会计算其右侧的任何参数表达式,并且类实例创建表达式出于相同原因突然完成。

接下来,调用指定类类型的选定构造函数。这导致为类类型的每个超类调用至少一个构造函数。此过程可以通过显式构造函数调用语句(第 8.8 节)来指导,并在第 12.5 节中详细说明。

类实例创建表达式的值是对指定类的新创建对象的引用。每次计算表达式时,都会创建一个新对象。


12.5. Creation of New Class Instances

...

在对新创建对象的引用作为结果返回之前,使用以下过程处理指示的构造函数以初始化新对象:

  1. 将构造函数的参数分配给此构造函数调用新创建的参数变量。

  2. 如果此构造函数以同一类中另一个构造函数的显式构造函数调用(第 8.8.7.1 节)开始(使用 this),则评估参数并使用这五个相同的步骤递归地处理该构造函数调用。如果该构造函数调用突然完成,则此过程出于相同原因而突然完成;否则,继续第 5 步。

  3. 此构造函数不是以显式调用同一类中的另一个构造函数开始的(使用 this)。如果此构造函数用于 Object 以外的类,则此构造函数将以显式或隐式调用超类构造函数开始(使用 super)。使用这五个相同的步骤递归地评估超类构造函数调用的参数和过程。如果该构造函数调用突然完成,则此过程出于相同的原因突然完成。否则,继续第 4 步。

  4. 执行该类的实例初始化程序和实例变量初始化程序,将实例变量初始化程序的值分配给相应的实例变量,按照它们在源代码中以文本形式出现的从左到右的顺序。如果执行这些初始化程序中的任何一个导致异常,则不会处理更多初始化程序,并且此过程会突然完成相同的异常。否则,继续第 5 步。

  5. 执行此构造函数的其余部分。如果该执行突然完成,则此过程出于同样的原因突然完成。否则,此过程正常完成。

【讨论】:

  • 谢谢,正是我正在寻找的答案。我将改为使用工厂,然后强制以后的子类有一个只有 id 的构造函数。在我的父类的构造函数中,我可以使用堆栈跟踪检查它是否是从工厂调用的:)
【解决方案2】:

构造函数是否编译不影响对象是否被创建。将创建所有实例变量,仍将调用 super() 并且仍将分配内存。

实际上总是会抛出异常,注册服务中的每个对象都会抛出异常。由于孩子必须调用 super() 才能注册,并且在注册后会引发异常。

【讨论】:

  • 是的,我知道这一切。我不认为你理解我的问题。我的问题是是否有某种方法可以检测构造函数是否在整个过程中执行而不会引发异常或类似的中断。
  • 在您的示例中,所有构造函数都会引发异常。知道是否抛出异常的唯一方法是捕获它,您只能在堆栈跟踪中捕获更高层的异常
  • 所以换句话说,你想说的是不,没有办法从堆栈跟踪中的较低位置查看方法或构造函数是否成功执行?我知道在我的示例中它们都抛出异常。把我的帖子读到最后,你会发现我只想知道这一点,以防止未来的子类导致这种行为
猜你喜欢
  • 1970-01-01
  • 2021-07-11
  • 2012-05-14
  • 2016-04-02
  • 1970-01-01
  • 2021-11-14
  • 1970-01-01
  • 2023-03-13
相关资源
最近更新 更多