Reputation: 21
I have 2 classes: Date and Person
Person has two attributes of Date class
Case 1
Date
class is separate class from Person class. I have this piece of code working properly:
private String name;
private Date born;
private Date died; // null indicates still alive.
public Person(String initialName, int birthMonth, int birthDay,
int birthYear) {
// requirement from the instructor:
// **implement using the this() constructor**
this(initialName, new Date(birthMonth, birthDay, birthYear), null);
}
Case 2: Inner class (an assignment requirement)
I put the Date
as the private inner class of Person
Now the above constructor code does not work anymore. Here is the error message:
Description Resource Path Location Type No enclosing instance of type Person is available due to some intermediate constructor invocation Person.java /Wk03_Ch10_FileIO_Ch13_Interfaces/wk03_Ch10_FileIO_Ch13_Inner_Classes line 43 Java Problem`
How do I solve the problem? I can do this:
Date dt = new Date(birthMonth, birthDay, birthYear);
Unfortunately this()
has to be the first line in the constructor
Another work around is
public Person(String initialName, int birthMonth, int birthDay,
int birthYear) {
// implement using the this() constructor
this.name = initialName;
this.born = new Date(birthMonth, birthDay, birthYear);
this.died = null;
}
However the last piece of code does not satisfy my instructor requirement of using this()
method inside the constructor.
Upvotes: 2
Views: 2760
Reputation: 17299
You can't create inner member (non-static
) classes within a call to another constructor. From JLS §8.8.7.1:
An explicit constructor invocation statement in a constructor body (sic: the call to
this()
) may not refer to any instance variables or instance methods or inner classes declared in this class or any superclass, or usethis
orsuper
in any expression; otherwise, a compile-time error occurs.
The reason is that non-static
inner classes may require access to the class while it's being constructed. For example:
public class OuterClass {
private String name;
private InnerClass inner;
public OuterClass(String name, InnerClass inner) {
this.name = name;
this.inner = inner;
}
public OuterClass(String name) {
this(name, new InnerClass()); // Will not compile
}
public class InnerClass {
public InnerClass() {
// Access to name is allowed since this inner class isn't static
System.out.println(name);
}
}
}
The major problem here is that when we construct the non-static
inner class, it can access non-static
members from its enclosing instance (the OuterClass
, but the enclosing OuterClass
hasn't yet made its call to super()
and is therefore considered unsafe to access. In fact, if that code was allowed to compile, it would print null
due to constructor invocation order. In short, the InnerClass
would be created before the call to this.name = name
, a similar concept to the information presented in this question.
The solution is to make InnerClass
a static
inner class and pass the name to it directly:
public class OuterClass {
private String name;
private InnerClass inner;
public OuterClass(String name, InnerClass inner) {
this.name = name;
this.inner = inner;
}
public OuterClass(String name) {
this(name, new InnerClass(name));
}
public static class InnerClass {
public InnerClass(String name) {
System.out.println(name);
}
}
}
InnerClass
cannot access name
from the OuterClass
once it's declared static
, so we have to pass it explicitly on construction now, but this is better since the initial code would have been broken anyway.
Edit:
Per your question:
What confused me is that I can create an object of a
Date
type in thePerson
's constructor, as long as it is not inside thethis()
method. I can do this:Date dt = new Date(birthMonth, birthDay, birthYear);
What is the difference between the above andthis(...., new Date(birthMonth, birthDay, birthYear), ...)
?
The difference is that in the call outside of this()
, all the calls to super()
have taken place, they take place as part of this()
because of implicit calls to super()
, so the object has reached a point where it is considered okay to be accessed. Your Date
instance can't access the Person
class because there is no context for the Person
and its fields yet, so the compiler doesn't allow it.
In short, once this()
has been called, then at least the calls to super()
have happened, which is the driving force behind this constraint, and also why overridable method calls are discouraged. If a method is overridden by a subclass and then called in the superclass' constructor, fields from the subclass can be accessed before the subclass has even been initialized, even resulting in null
being returned for final
fields. Basically, it's all about protecting yourself from accessing your class before a call to super()
has been invoked.
Upvotes: 2
Reputation: 88378
While I would never create a Date
class inside of a Person
class (sounds like a bad idea from an application-modeling perspective!), you seem to have said that it is a requirement in some assignment.
If you are set up like this:
public class Person {
...
class Date {
...
}
}
Then inside methods of Person
, you will need to invoke the Date
constructor with:
this.new Date(...)
That is the syntax Java uses. You need an enclosing instance of type Person on which to create objects of the inner class. The thing about inner classes (whether they are member, local, or anonymous) is that each instance exists bound to an instance of the outer class. So if I had a person instance p
, I could say:
p.new Date(...)
The big problem here though is that you cannot create dates in the Person
constructor that use the about-to-be-created person! For example, this fails:
public Person() {
this(this.new Date());
}
because the value of this
isn't ready for use in this way yet (although interestingly, you can sometimes use this
inside constructors in other cases, such as storing it in arrays, for example).
Like you realized, making Date
a static nested class is fine, because instances of static nested classes are not tied to any instances of the enclosing class, so this is the best solution. If you really have to have an inner class, you're not going to be able to pass a new date as an "argument" of a this()
expression and have it bound to the person you are creating! Maybe that is the point of the assignment (is this a graduate class? :-))
Upvotes: 0