Reputation: 5216
Look at the following code:
class Person {
String name;
int age;
Person(Consumer<Person> consumer) {
consumer.accept(this);
}
}
As you can see, I'm using "consumer constructor", so I can create a person like this:
var person = new Person(p -> {
p.name = "John";
p.age = 30;
})
Seems like this approach is much better than builder pattern or all-arguments constructor.
4 years have passed since Java 8 was released but nobody uses consumer constructors (at least I haven't seen it before).
I do not understand why? Is this approach has some pitfalls or limitations?
I have found one, but I don't think it is critical:
class AdvancedPerson extends Person {
String surname;
AdvancedPerson(Consumer<AdvancedPerson> consumer) {
super(); // <-- what to pass?
consumer.accept(this);
}
}
Sure we can create a no-arguments constructor in Person
and just call it in AdvancedPerson
consumer constructor. But is this a solution?
So what do you think about it?
Is it safe to use consumer constructors?
Is this a substitute for builders and all-argument constructors? Why?
Upvotes: 9
Views: 1130
Reputation: 829
It's neither safe nor elegant from my point of view. There are several reasons against this approach: The worst thing about it is that it not only allows but also forces you to let the this reference escape while the object is not initialized yet. This has several severely bad implications:
See the second chapter of effective java for best practices concerning creating objects.
Upvotes: 12
Reputation: 49606
It seems like I don't control the process of initialisation at all.
I don't know how many fields have been set, what their values are. I don't know which methods (and how many) a consumer has called. It looks like I break the encapsulation rules.
I also expose the this
reference for the outer world.
The caller may do with this
whatever they want. And I won't be notified about that. Multithreading has a term called 'improper publication'. I would use it here.
Upvotes: 8
Reputation: 178253
Consumer constructors appear to present a concise way in Java to write code that in other languages would be referred to as "properties". Just pass in a Consumer
that initializes the state of the object.
But this cannot work with proper class design, which includes encapsulation. Here, this means that name
and age
should be private
. This means that the Consumer
no longer has access to the instance variables and no longer compiles.
Encapsulation and the Builder Pattern both have the advantage that invalid states of the objects are checked and rejected.
If Java had the concept of properties, where code such as obj.prop1 = value;
and variable = obj.prop2;
in reality called methods, then this would not be a concern. But it doesn't have this concept.
The pitfall you have found could be worked around by passing a Consumer
for each superclass as well as the class in question, e.g.:
AdvancedPerson(Consumer<Person> pConsumer, Consumer<AdvancedPerson> consumer)
{
super(pConsumer); // <-- what to pass?
consumer.accept(this);
}
However, that means that a user must pass in a different Consumer
for every superclass, which doesn't make much sense.
In addition, passing this
from a constructor is not a good idea; this is an instance leak, where outside code can access an object that isn't fully constructed yet.
Encapsulation, this
leaking, and the complication of multiple Consumer
s in a class hierarchy are all good reasons not to use consumer constructors.
Upvotes: 4