Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What properties are guaranteed by constructors in Java?

I used to think that, intuitively speaking, a constructor in Java is the thing that makes an object, and that nothing can touch that object until its constructor returns. However, I have been proven wrong about this over and over again:

  1. uninitialized objects can be leaked by sharing this
  2. uninitialized objects can be leaked by a subclass accessing it from the finalizer
  3. uninitialized objects can be leaked to another thread before they're fully constructed

All of these facts violate my intuition of what I thought a constructor is.

I can no longer with confidence say what a constructor actually does in Java, or what it's meant to be used for. If I'm making a simple DTO with all final fields, then I can understand what the use of the constructor is, because this is exactly the same as a struct in C except it can't be modified. Other than that, I have no clue what constructors can be reliably used for in Java. Are they just a convention/syntactic sugar? (i.e If there were only factories that initialize objects for you, you would only have X x = new X(), then modify each field in x to make them have non default values - given the 3 facts above, this would be almost equivalent to how Java actually is)

I can name two properties that are actually guaranteed by constructors: If I do X x = new X(), then I know that x is an instance of X but not a subclass of X, and its final fields are fully initialized. You might be tempted to say that you know that constructor of X finished and you have a valid object, but this is untrue if you pass X to another thread - the other thread may see the uninitialized version (i.e what you just said is no different than the guarantees of calling a factory). What other properties do constructors actually guarantee?

like image 304
Dog Avatar asked May 20 '13 15:05

Dog


1 Answers

All of these facts violate my intuition of what I thought a constructor is.

They shouldn't. A constructor does exactly what you think it does.

1: uninitialized objects can be leaked by sharing this

3: uninitialized objects can be leaked to another thread before they're fully constructed

The problem with the leaking of this, starting threads in the constructor, and storing a newly constructed object where multiple threads access it without synchronization are all problems around the reordering of the initialization of non-final (and non-volatile) fields. But the initialization code is still done by the constructor. The thread that constructed the object sees the object fully. This is about when those changes are visible in other threads which is not guaranteed by the language definition.

You might be tempted to say that you know that constructor of X finished and you have a valid object, but this is untrue if you pass X to another thread - the other thread may see the uninitialized version (i.e what you just said is no different than the guarantees of calling a factory).

This is correct. It is also correct that if you have an unsynchronized object and you mutate it in one thread, other threads may or may not see the mutation. That's the nature of threaded programming. Even constructors are not safe from the need to synchronize objects properly.

2: uninitialized objects can be leaked by a subclass accessing it from the finalizer

This document is talking about finalizers and improperly being able to access an object after it has been garbage collected. By hacking subclasses and finalizers you can generate an object that is not properly constructed but it is a major hack to do so. For me this does not somehow challenge what a constructor does. Instead it demonstrates the complexity of the modern, mature, JVM. The document also shows how you can write your code to work around this hack.

What properties are guaranteed by constructors in Java?

According to the definition, a constructor:

  1. Allocates space for the object.
  2. Sets all the instance variables in the object to their default values. This includes the instance variables in the object's superclasses.
  3. Assigns the parameter variables for the object.
  4. Processes any explicit or implicit constructor invocation (a call to this() or super() in the constructor).
  5. Initializes variables in the class.
  6. Executes the rest of the constructor.

In terms of your 3 issues, #1 and #3 are, again, about when the initialization of non-final and non-volatile fields are seen by threads other than the one that constructed the object. This visibility without synchronization is not guaranteed.

The #2 issue shows a mechanism where if an exception is thrown while executing the constructor, you can override the finalize method to obtain and improperly constructed object. Constructor points 1-5 have occurred. With the hack you can bypass a portion of 6. I guess it is in the eye of the beholder if this challenges the identity of the constructor.

like image 171
Gray Avatar answered Sep 20 '22 15:09

Gray