15

When an anonymous class with no references to its enclosing class is returned from an instance method, it has a reference to this. Why?

Consider the following code:

package so;

import java.lang.reflect.Field;

public class SOExample {

    private static Object getAnonymousClassFromStaticContext() {
        return new Object() {
        };
    }

    private Object getAnonymousClassFromInstanceContext() {
        return new Object() {
        };
    }

    public static void main(String[] args) throws NoSuchFieldException, SecurityException {

        Object anonymousClassFromStaticContext = getAnonymousClassFromStaticContext();
        Object anonymousClassFromInstanceContext = new SOExample().getAnonymousClassFromInstanceContext();

        Field[] fieldsFromAnonymousClassFromStaticContext = anonymousClassFromStaticContext.getClass().getDeclaredFields();
        Field[] fieldsFromAnonymousClassFromInstanceContext = anonymousClassFromInstanceContext.getClass().getDeclaredFields();

        System.out.println("Number of fields static context: " + fieldsFromAnonymousClassFromStaticContext.length);
        System.out.println("Number of fields instance context: " + fieldsFromAnonymousClassFromInstanceContext.length);
        System.out.println("Field from instance context: " + fieldsFromAnonymousClassFromInstanceContext[0]);

    }

}

This is the output:

Number of fields static context: 0
Number of fields instance context: 1
Field from instance context: final so.SOExample so.SOExample$2.this$0

Each method, although seemingly calling the same code, is doing something different. It looks to me that the instance method is returning a nested class, whereas the static method is returning a static nested class (as a static member, it obviously can't have a reference to this).

Given the fact that there's no reference to the enclosing class, I can't see the benefit in this.

What's going on behind the scenes?

Robert Bain
  • 9,113
  • 8
  • 44
  • 63
  • 1
    This is why closures don't do this. I assume this simplified the implementation to not optimize the away the `this` or the outer class if not actually needed. – Peter Lawrey Jan 05 '16 at 21:41
  • 1
    what do you mean with *Given the fact that there's no reference to the enclosing class* – wero Jan 05 '16 at 21:54
  • http://stackoverflow.com/a/1353326/2680506 – Dave S Jan 05 '16 at 21:55
  • @wero I mean that in the instance method, in the anonymous class returned, we're not saying something like `SOExample.this.someField = "foo"` – Robert Bain Jan 05 '16 at 21:59
  • 2
    @RobertBain The reference still exists, it would be quite similar to your anonymous class having a `private SOExample foobar` member that's initialized to the outer instance - it's still there even if you don't have any code that uses it. That reference to the outer instance is there because the Java spec says that's how it is - there's no special case that omits the reference even if there's no code using it. – nos Jan 05 '16 at 22:27
  • 1
    This happens for all local classes, not only anonymous ones. And honestly I don't see what is so strange about letting local class created in non-static content being allowed to have access to other members of outer classes (and to make it possible it needs reference which will hold specific instance of outer class). Local classes created in static context can't know anything about instance so they don't need field to hold such instance. – Pshemo Jan 05 '16 at 22:29

3 Answers3

8

There is a design principle behind anonymous / inner classes: Each instance of an inner class belongs to an instance of the outer class.

Leaving out the reference to the inner class would change behavior of garbage collection: The way it's implemented, the outer class can not be garbage collected as long the inner class is alive.
This supports the idea that the inner class can not exist without the outer class.

Applications might rely on this behavior by for example by creating a temporary file and deleting it in the destructor. This way, the file will only be deleted when all inner classes are gone.

This also means that the current behavior can not be changed as changing it might break existing applications.

So you should always mark inner classes as static when you don't need the reference, because that could lead to some nice memory leaks.

Edit: Example of what I am trying to say (sorry for the terrible code quality):

class Ideone
{
    static Object[] objects = new Object[2];

    public static void main (String[] args) throws java.lang.Exception
    {
        M1();
        M2();
        System.gc();
    }

    static void M1() {
        objects[0] = new Foo().Bar();
    }
    static void M2() {
        objects[1] = new Foo().Baz();
    }
}

class Foo {
    static int i = 0;
    int j = i++;

    public Foo() {
        System.out.println("Constructed: " + j);
    }

    Object Bar() {
        return new Object() {

        };
    }
    static Object Baz() {
        return new Object() {

        };
    }

    protected void finalize() throws Throwable {
        System.out.println("Garbage collected " + j);
    }
}

Output:

Constructed: 0
Constructed: 1
Garbage collected 1

As you can see, the first Foo is not garbage collected, because there is still an "inner instance" alive. For this behavior to work, the inner class needs a reference.

Of course, it could also be implemented differently. But I would say, keeping the reference is a design decision made on purpose, so that the "inner instance" will not outlive its parent.

BTW: The Java language reference states this quite cryptically (There is no exception for inner classes which do not access the outer class):

An instance i of a direct inner class C of a class or interface O is associated with an instance of O, known as the immediately enclosing instance of i. The immediately enclosing instance of an object, if any, is determined when the object is created (§15.9.2).

Matthias
  • 12,053
  • 4
  • 49
  • 91
  • I must be missing something but from what I gather, what you're saying begs the same question as the one posed. If there's no reference to the enclosing class, surely the instance method would be better off returning an `Object` without a reference to `this`, I.E. a static nested class. – Robert Bain Jan 05 '16 at 22:03
  • No. In this case (returning a static nested class), keeping alive the returned instance (somewhere else) will not keep alive the outer class. If returning an inner class will. I will try to add an example. – Matthias Jan 05 '16 at 22:05
  • Added example. I hope it helps clarifying things. – Matthias Jan 05 '16 at 22:15
  • 1
    +1 for "Of course, it could also be implemented differently. But I would say, keeping the reference is a design decision made on purpose, so that the "inner instance" will not outlive its parent." Looking for small micro optimizations in specific cases doesn't seem productive when there's a larger design pattern in play. – Dave S Jan 05 '16 at 22:18
  • You can't draw any conclusions from this `finalize` thing. This method may or may not be called, you never know... – Marco13 Jan 05 '16 at 22:20
  • 1
    The problem is: Once it has been implemented this way at any point of time, you can't change it because of backwards compatibility. So maybe, the optimization might actually beneficial for 99% of the applications out there, but because of the 1% that will break, we are stuck with this behavior. Relying on this behavior would be a terrible way to write Java code, in my opinion. – Matthias Jan 05 '16 at 22:21
  • @Marco13: True in general. However, in this case the finalize for one Foo instance was luckily called and the other will never be called because of said reference. So the example should still be valid in terms of illustrating what I said. But I agree: Definitely don't rely on this behavior :-) – Matthias Jan 05 '16 at 22:23
2

I'd simply say: It has a reference to this, because it may need it.

Imagine a slight modification of the program:

public class SOExample
{
    private static Object getAnonymousClassFromStaticContext()
    {
        return new Object()
        {
            @Override
            public String toString()
            {
                // ERROR: 
                // "No enclosing instance of the type SOExample is accessible in scope"
                return SOExample.this.toString(); 
            }
        };
    }

    private Object getAnonymousClassFromInstanceContext()
    {
        return new Object()
        {
            @Override
            public String toString()
            {
                // Fine
                return SOExample.this.toString(); 
            }
        };
    }
}

Obviously, the object that is created in the instance context needs the reference to this, because it must have the possibility to access methods (or fields, if they existed) of the enclosing instance.

The fact that in your original example, you did not access the enclosing instance in any way does not mean that this this reference does not exist by default.

At which point should the decision be made otherwise? Should the compiler check whether the this reference is actually required, and throw it away if not? If you don't want a this, then create a static inner class (or create this instance from a static context). Having a reference to the enclosing instance is simply how inner classes are implemented.


By the way: The comparison with equal would return false even for two objects that are both created from the same "context", as long as you don't implement your own equals method in the returned objects accordingly

Marco13
  • 53,703
  • 9
  • 80
  • 159
0

Even if we can't see any visible reference it will still exist. See the code below.

package jetty;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Arrays;

public class SOExample2 {

    private static Object staticField = new Object () { };
    private Object nonStaticField = new Object () { };

    private static Object getAnonStatic() {
        return new Object() { };
    }

    private Object getAnonNonStatic() {
        return new Object() { };
    }

    public static void main(String[] args) throws NoSuchFieldException, SecurityException {
        System.out.println("Started");

        class StaticMethodLocal {

        }

        System.out.println("############## Fields ##############");
        printClassInfo(staticField.getClass());
        printClassInfo(new SOExample2().nonStaticField.getClass());

        System.out.println("############## Methods ##############");
        printClassInfo(getAnonStatic().getClass());
        printClassInfo(new SOExample2().getAnonNonStatic().getClass());

        System.out.println("############## Method Local ##############");
        printClassInfo(new StaticMethodLocal().getClass());
        printClassInfo(new SOExample2().getNonStaticMethodLocal().getClass());
    }

    public static <T>void printClassInfo(Class<T> klass) {
        System.out.println("Class : " + klass);
        String prefix = "\t";

        System.out.println(prefix + "Number fields : " + klass.getDeclaredFields().length);
        if(klass.getDeclaredFields().length > 0) {
            System.out.println(prefix + "fields : " + Arrays.toString(klass.getDeclaredFields()));
        } else {
            System.out.println(prefix + "no fields");
        }
        System.out.println(prefix + "modifiers : " + Modifier.toString(klass.getModifiers()));

        //Constructors
        Constructor<?>[] constructors = klass.getDeclaredConstructors();
        for(Constructor<?> constructor : constructors) {
            System.out.println(prefix + "constructor modifiers : " + Modifier.toString(constructor.getModifiers()));
            System.out.println(prefix + "constructor parameters : " + Arrays.toString(constructor.getParameterTypes()));
        }
        System.out.println("");
    }

    private Object getNonStaticMethodLocal () {
        class NonStaticMethodLocal {
        }
        return new NonStaticMethodLocal();
    }
}

Output:

Started
############## Fields ##############
Class : class jetty.SOExample2$1
    Number fields : 0
    no fields
    modifiers : 
    constructor modifiers : 
    constructor parameters : []

Class : class jetty.SOExample2$2
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2$2.this$0]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]

############## Methods ##############
Class : class jetty.SOExample2$3
    Number fields : 0
    no fields
    modifiers : 
    constructor modifiers : 
    constructor parameters : []

Class : class jetty.SOExample2$4
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2$4.this$0]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]

############## Method Local ##############
Class : class jetty.SOExample2$1StaticMethodLocal
    Number fields : 0
    no fields
    modifiers : 
    constructor modifiers : 
    constructor parameters : []

Class : class jetty.SOExample2$1NonStaticMethodLocal
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2$1NonStaticMethodLocal.this$0]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]

I have added two more types of anonymous classes as field values and two method local classes.

Given above output it's clear that classes generated in non static context have only one constructor and this constructor has a parameter of enclosing class type.

As suggested by the output JVM adds some extra code to hold enclosing class instance reference when creating an anonymous/method local class.

This can be seen in decompiler output also.

    // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   SOExample2.java

//Static field anonynmouse class
class SOExample2$1
{
    SOExample2$1()
    {
    }
}

//Non static field anonynmouse class
class SOExample2$2
{
    final SOExample2 this$0;
    SOExample2$2()
    {
        this$0 = SOExample2.this;
        super();
    }
}

//static method anonynmouse class
class SOExample2$3
{
    SOExample2$3()
    {
    }
}

//Non static method anonynmouse class
class SOExample2$4
{
    final SOExample2 this$0;
    SOExample2$4()
    {
        this$0 = SOExample2.this;
        super();
    }
}

//Static method local class
class SOExample2$1StaticMethodLocal
{
    SOExample2$1StaticMethodLocal()
    {
    }
}

//Non static method local class
class SOExample2$1NonStaticMethodLocal
{
    final SOExample2 this$0;
    SOExample2$1NonStaticMethodLocal()
    {
        this$0 = SOExample2.this;
        super();
    }
}

Conclusion:

  1. Compiler does things when generating class files that we don't see. For example adding a default constructor to a class or adding a default super class constructor super() call to a constructor which doesn't explicitly call any this() self constructor or super() constructor. Same is true for adding a reference of enclosing type, there's no magic there. We can easily define such a class, compiler is just making our life easier by doing this for us.
  2. This is same as kind of writing your own byte code. Thus bypassing compiler itself we can do this which actual language can't.
  3. Given no modifier output in the anonymous classes it can be concluded that they are just method local classes (all class modifiers public,protected,private,abstract,static) lose meaning inside a method. They're just being called anonymous class under the guise they're method local classes.
11thdimension
  • 10,333
  • 4
  • 33
  • 71