Reputation: 1430
Can anyone comment with their opinion on the best way to resolve the following issue? I've tried init blocks and initializing the field in the constructor, but neither get called before the overridden method tries to clear it.
class TestRunner {
public static void main(String[] args) {
ftw ftw = new ftw();
ftw.dontPanic();
}
}
class wtf {
wtf(){
this.dontPanic();
}
void dontPanic() {
System.out.println("super don't panic");
}
}
class ftw extends wtf {
final HashSet variableOfDoom = new HashSet();
ftw(){
super();
}
void dontPanic() {
super.dontPanic();
System.out.println("sub don't panic");
variableOfDoom.clear(); //This line prints a null pointer exception, because the field hasn't been intialized yet.
}
}
Upvotes: 2
Views: 2710
Reputation: 29710
quick & dirty solution (not very nice, I know)
...
System.out.println("sub don't panic");
// must test if object fully initialized, method called in constructor
if (variableOfDoom != null) {
variableOfDoom.clear();
}
Upvotes: 0
Reputation: 47243
Firstly, don't do that.
Secondly, if you absolutely have to do that, do this:
class wtf {
wtf(){
this.dontPanic();
}
void dontPanic() {
System.out.println("super don't panic");
}
}
class ftw extends wtf {
HashSet _variableOfDoom; // underscore to remind you not to access it directly
ftw(){
super();
}
private HashSet getVariableOfDoom()
{
if (_variableOfDoom == null) _variableOfDoom = new HashSet();
return _variableOfDoom;
}
void dontPanic() {
super.dontPanic();
System.out.println("sub don't panic");
getVariableOfDoom().clear(); // FOR GREAT JUSTICE!
}
}
Note that you can't have variableOfDoom be final.
Upvotes: 1
Reputation: 5275
The takeaway from this example is: super class initialization cannot depend on the child class being completely initialized.
If ftw did not extend wtf then it would be ok to assume that any initialization specified at the field definition will be done before the constructor is called. However, since ftw extends wtf, wtf must be fully initialized before any initialization can happen in ftw. Since part of wtf initialization depends on variableOfDoom in the sub class to have been initialized you are getting a null pointer exception.
The only way out of this is for you to separate your call to dontPanic outside the constructor.
Upvotes: 2
Reputation: 4189
You might note that private methods are not prevented from calling overrides; so even then, exercise some caution in calling anything that is overridden from constructors. In other languages the advice is to avoid calling any virtual methods from a constructor because the object may not be fully constructed when the method is called.
An alternative to consider is two-stage construction. http://www.artima.com/forums/flat.jsp?forum=106&thread=106524
There's a related idiom in the .NET Framework Design Guidelines book called "create-set-call". Create the object, set some properties, call some methods. I've seen this usage sometimes criticized as being less discoverable than the simple construction call (i.e., just create-and-use).
Upvotes: 1
Reputation: 87593
Don't call non-final methods from within constructors - not all instance variables may be initialized yet - your code being a classic example.
Without know what you're really trying to accomplish, its difficult to recommand "the best way to resolve the issue", but here's a go:
class TestRunner {
public static void main(String[] args) {
ftw ftw = new ftw();
ftw.dontPanic();
}
}
class wtf {
wtf(){
wtfDontPanic();
}
private final void wtfDontPanic() {
System.out.println("super don't panic");
}
void dontPanic() {
wtfDontPanic();
}
}
class ftw extends wtf {
final HashSet variableOfDoom = new HashSet();
ftw(){
super();
ftwDontPanic();
}
private final ftwDontPanic() {
System.out.println("sub don't panic");
variableOfDoom.clear(); //This line prints a null pointer exception, because the field hasn't been intialized yet.
}
private void dontPanic() {
super.DontPanic();
ftwDontPanic();
}
}
Upvotes: 0
Reputation: 72019
This is why they recommend you don't call non-final
methods from inside a constructor. private
methods are also safe, since they can't be overriden.
Assuming that you can't follow that sound advice, for some reason, here are some other observations that might help:
.clear()
doesn't really accomplish anything in the example; you may be able to just delete it.HashSet
instead of invoking .clear()
.Upvotes: 7
Reputation: 192025
Here's the problem: Your execution flow looks like this:
main
->ftw()
->wtf()
->wtf.dontPanic()
But since method invocations are determined at runtime in Java, ftw.dontPanic()
is the method that's really being called. And variableOfDoom
won't be set until the constructor completes, but the constructor will never complete because of the exception.
The solution is not to do work in your constructor (at least not with non-private
non-final
methods).
Upvotes: 7