user2469763
user2469763

Reputation: 21

Inner class and this() constructor

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

Answers (2)

Brian
Brian

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 use this or super 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 the Person's constructor, as long as it is not inside the this() method. I can do this: Date dt = new Date(birthMonth, birthDay, birthYear); What is the difference between the above and this(...., 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

Ray Toal
Ray Toal

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

Related Questions