0

So i was playing around in java once again and while doing so, i ran into an interesting problem.

I was trying to write my self a little registration service. If an object of a certain type is instantiated via its constructor it would retrieve an id from my registration service and is added to the services list of objects.

Here is an example object

public class Test {

  private final Long id;

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

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

And here is an example service

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);
  }
}

Now as you can see, the constructor of my class Test can't successfully execute at all since it always throws an IllegalAccessError. But the method register(...) of my TestRegistration class is called in the constructor before this error is thrown. And inside this method it is added to the registrations Map. So if i would run for example this code

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);
}

My TestRegistration does in fact contain the object created when the constructor of my Test class was called and i can (here using variable t2) access it, even tho it wasn't successfully created in the first place.

My question is, can i somehow detect from within my TestRegistration class if the constructor of Test was executed successfully without any exceptions or other interruptions?

And before you ask why i throw this exception in the first place here is my answer. Test may have potential subclasses i don't know of yet but still will be registered in my TestRegistration class. But since i don't have influence on the structure of those subclasses i can't tell if their constructor will throw an exception or not.

Basti
  • 1,117
  • 12
  • 32

2 Answers2

1

It does not appear to be possible.

I've included the best I can gather from the JLS below.

As can be seen from that, the execution of the body of the constructor is the very last step in the creation of an object.

Thus I don't see any evidence to suggest that raising an exception would result in anything not happening which you could've checked to see whether the execution was successful (apart from the rest of the body of the constructor, obviously).

This may not seem particularly convincing, although I'm not convinced that you'd find anything more concrete than this (it is, after all, notoriously difficult to prove that something doesn't exist).


I would recommend putting the logic that modifies external data somewhere else.

It wouldn't be entirely unreasonable to require that a register method (either in the TestRegistration class, or in the object itself) be explicitly called after construction of an object.

It could also work to, instead of calling the constructors directly, have a class containing methods to return objects (which sounds a lot like the Factory Design Pattern), where you can then put this external modification logic.


From the JLS:

15.9.4. Run-Time Evaluation of Class Instance Creation Expressions

At run time, evaluation of a class instance creation expression is as follows.

First, if the class instance creation expression is a qualified class instance creation expression, the qualifying primary expression is evaluated. If the qualifying expression evaluates to null, a NullPointerException is raised, and the class instance creation expression completes abruptly. If the qualifying expression completes abruptly, the class instance creation expression completes abruptly for the same reason.

Next, space is allocated for the new class instance. If there is insufficient space to allocate the object, evaluation of the class instance creation expression completes abruptly by throwing an OutOfMemoryError.

The new object contains new instances of all the fields declared in the specified class type and all its superclasses. As each new field instance is created, it is initialized to its default value (§4.12.5).

Next, the actual arguments to the constructor are evaluated, left-to-right. If any of the argument evaluations completes abruptly, any argument expressions to its right are not evaluated, and the class instance creation expression completes abruptly for the same reason.

Next, the selected constructor of the specified class type is invoked. This results in invoking at least one constructor for each superclass of the class type. This process can be directed by explicit constructor invocation statements (§8.8) and is specified in detail in §12.5.

The value of a class instance creation expression is a reference to the newly created object of the specified class. Every time the expression is evaluated, a fresh object is created.


12.5. Creation of New Class Instances

...

Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:

  1. Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.

  2. If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.

  3. This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.

  4. Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5.

  5. Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.

Community
  • 1
  • 1
Bernhard Barker
  • 54,589
  • 14
  • 104
  • 138
  • Thank you, exactly the answer i was looking for. Im going to use factories instead then and force later subclasses to have a constructor with only the id then. In the constructor of my parent class i can check if it was called from the factory using the stacktrace :) – Basti Mar 18 '18 at 20:55
0

Weather or not the constructor comples does not influence whether or not the object is created. All instance variables will be created the super() will still be called and memory will still be allocated.

In reality the exception will always be thrown, every object in your registration service will have thrown an exception. As children will have to call super() in order to be registered and following registration an exception is thrown.

klonq
  • 3,535
  • 4
  • 36
  • 58
  • Yes i am aware about all of that. I don't think you understand my question. My question is if there is some way to detect if a constructor executed the whole way without throwing an exception or similar interruptions. – Basti Mar 18 '18 at 16:37
  • In your example, all constructors throw an exception. The only way to know if an exception is thrown is to catch it and you can only catch an exception from higher up in the stack trace – klonq Mar 18 '18 at 16:39
  • So in other words what you are trying to say is no, there is no way to see if a method or constructor executed successfully from a lower place in the stack trace? I know that in my example all of them throw an exception. Read my post to the very end and you ll see that i only want to know this to prevent furture subclasses to cause this behaviour – Basti Mar 18 '18 at 16:42