Inner classes and the myth of the default constructor

How many times have you read the following sentence : "When you don't provide any constructor in a class, the compiler automatically inserts a default, no-args constructor for you" ?

So now you assume that every class with no apparent constructor has a hidden, default, no-arg constructor. Big mistake.

But one bazillion Java books can't be wrong ?

Nope. But there is one case where a class does not have a no-arg constructor, even when you don't implement any yourself : non-static inner classes.

A non-static inner class is logically a member of its enclosing class, and as such can access its private fields and methods ; but it will be physically compiled as a separate entity, named by the pattern <outer>$<inner>.
Question : how can Java let this external entity access the outer class' private fields without breaking the sacred encapsulation principle ?
Answer : by transparently passing a reference to the outer class' instance as a first parameter of the inner class' constructors - even the default, no-arg constructor, which then becomes a one-arg constructor.

I want to believe

Let's code an Outer class with a non-static Inner class, and use some reflection (black) magic to list the inner class' available constructors and their parameters.

Fig. 1 : no declared constructor in the inner class

public class OuterClass {
    class InnerClass {
    }
}

Let's list the available constructors :

Constructor<?>[] constructors = InnerClass.class.getDeclaredConstructors();
    for (Constructor<?> constructor : constructors) {
        System.out.println(Arrays.toString(constructor.getParameterTypes()));
}

The Inner class has only one constructor, and its first argument is a reference to an instance of its enclosing class :

[class OuterClass]

What happens if we provide a no-arg constructor ?

Fig. 2 : with a no-arg constructor

public class OuterClass {
    public class InnerClass {
        public InnerClass() {
        }
    }
}

Same result :

[class OuterClass]

And if we provide a constructor with arguments ?

Fig. 3 : with a constructor with arguments

public class OuterClass {
    public class InnerClass {
        public InnerClass(String foo, int bar) {
        }
    }
}

The reference is inserted as the first parameter of our constructor :

[class OuterClass, class java.lang.String, int]

Conclusion

Don't trust books !
Just kidding. Just know that this case exists, and you'll be able to impress people at your next technical interview.

By the way, if you had fun reading this article, you should probably take a look at synthetic methods - it's another Java trick to deal with inner classes.


Commentaires

1. Le jeudi 29 septembre 2011, 09:47 par adiGuba

This is not the only one case !
The compiler use the same behavior on "local-class" and "anonymous-class", in order to access parameters or local variables :

public class OuterClass {

public static void main(final String args) throws Throwable {

final Date date = new Date();

class LocalClass {
@Override
public String toString() {
return date + ": " + Arrays.toString(args);
}
}

System.out.println(new LocalClass());

// anonymous :
System.out.println(new Object() {
@Override
public String toString() {
return date + ": " + Arrays.toString(args);
}
});
}
}

These classes will have a two-params constructor (java.util.Date,java.lang.String)

a++

2. Le jeudi 29 septembre 2011, 11:12 par Olivier Croisier

You're right.

I didn't mention it because I was focusing on the "when you don't provide any constructor..." thing - and you cannot provide any constructor to an anonymous class, because, well, it's anonymous.

The local class case is interesting though.

3. Le vendredi 30 septembre 2011, 14:35 par Piwaï

And of course, things get even more complicated with local classes in non static methods. Because then the constructor has a reference to the outer class & references to local variables. But this doesn't include variables that can be determined at compile time (for example, "final toto = 2;")

I played a lot with that in FunkyJFunctional, see this class : https://github.com/pyricau/FunkyJFu...

4. Le mardi 4 octobre 2011, 13:33 par Heinz

You can provide a "constructor" for an anonymous class using the initializer block { }

5. Le mardi 4 octobre 2011, 13:58 par Olivier Croisier

@Heinz Good point. And this might be an interesting topic for a quiz :)

6. Le dimanche 30 septembre 2012, 22:43 par Hamy

For those coming from google-ing the xstream no-args constructor issue:

   Error: Cannot construct com.myclass as it does not have a no-args constructor

This is caused by the issue mentioned in this blog post. Fix by terming all of your inner classes as static.

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.