Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic construction of anonymous class confusion

I'm trying to make instances of anonymous classes using reflection. But ocassionally I've seen strange behaviour during instantination.

Please, look at these similar fragments of code

public class HideAndSeek {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws IllegalAccessException, InstantiationException{

        final String finalString = "I'm final :)";

        Object object2 = new Object(){

            {
                System.out.println("Instance initializing block");
                System.out.println(finalString);
            }           

            private void hiddenMethod() {
                System.out.println("Use reflection to find me :)");
            }
        };

        Object tmp = object2.getClass().newInstance();
    }

}

This code works well, and the output expected

Instance initializing block
I'm final :)
Instance initializing block
I'm final :)

After this I've decided to change code in simple way (just added java.util.Calendar)

import java.util.Calendar;

    public class HideAndSeek {

        @SuppressWarnings("unchecked")
        public static void main(String[] args) throws IllegalAccessException, InstantiationException{

            final String finalString = "I'm final :)";

            final Calendar calendar = Calendar.getInstance();
            System.out.println(calendar.getTime().toString()); //works well

            Object object2 = new Object(){

                {
                    System.out.println("Instance initializing block");
                    System.out.println(finalString);

                    //simply added this line
                    System.out.println(calendar.getTime().toString());
                }           

                private void hiddenMethod() {
                    System.out.println("Use reflection to find me :)");
                }
            };

            Object tmp = object2.getClass().newInstance();
        }

    }

And here is output that I've got:

Wed Aug 17 02:08:47 EEST 2011
Instance initializing block
I'm final :)
Wed Aug 17 02:08:47 EEST 2011
Exception in thread "main" java.lang.InstantiationException: HideAndSeek$1
    at java.lang.Class.newInstance0(Unknown Source)
    at java.lang.Class.newInstance(Unknown Source)
    at HideAndSeek.main(HideAndSeek.java:29)

As you may see - new instance hasn't been created.

Could anybody explain me, the reason of such changes?

Thanks

like image 608
stemm Avatar asked Aug 16 '11 23:08

stemm


People also ask

What's meant by Anonymous class explain with example?

It is an inner class without a name and for which only a single object is created. An anonymous inner class can be useful when making an instance of an object with certain “extras” such as overriding methods of a class or interface, without having to actually subclass a class.

Can an anonymous class have a constructor?

Since anonymous inner class has no name, an anonymous inner class cannot have an explicit constructor in Java.

What are the two ways to create anonymous inner class?

Java Anonymous inner class can be created in two ways: Class (may be abstract or concrete). Interface.

Which construction inner instance is Anonymous?

Explanation: D is correct. It defines an anonymous inner class instance, which also means it creates an instance of that new anonymous class at the same time. The anonymous class is an implementer of the Runnable interface, so it must override the run() method of Runnable.


2 Answers

This is a very simple question with a very complex answer. Please bear with me as I try to explain it.

Looking at the source code where the exception is raised in Class (I'm not sure why your stack trace doesn't give the line numbers in Class):

try
{
  Class[] empty = {};
  final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
  // removed some code that was not relevant
}
catch (NoSuchMethodException e)
{
  throw new InstantiationException(getName());
}

you see that NoSuchMethodException is being rethrown as InstantiationException. This means there is not a no-arg constructor for the class type of object2.

First, what type is object2? With the code

System.out.println("object2 class: " + object2.getClass());

we see that

object2 class: class junk.NewMain$1

which is correct (I run sample code in package junk, class NewMain).

What then are the constructors of junk.NewMain$1?

Class obj2Class = object2.getClass();
try
{
  Constructor[] ctors = obj2Class.getDeclaredConstructors();
  for (Constructor cc : ctors)
  {
    System.out.println("my ctor is " + cc.toString());
  }
}
catch (Exception ex)
{
  ex.printStackTrace();
}

which gives us

my ctor is junk.NewMain$1(java.util.Calendar)

So your anonymous class is looking for a Calendar to be passed in. This will then work for you:

Object newObj = ctors[0].newInstance(Calendar.getInstance());

If you have something like this:

final String finalString = "I'm final :)";
final Integer finalInteger = new Integer(30);
final Calendar calendar = Calendar.getInstance();
Object object2 = new Object()
{
  {
    System.out.println("Instance initializing block");
    System.out.println(finalString);
    System.out.println("My integer is " + finalInteger);
    System.out.println(calendar.getTime().toString());
  }
  private void hiddenMethod()
  {
    System.out.println("Use reflection to find me :)");
  }
};

then my call to newInstance won't work because there are not enough arguments in the ctor, because now it wants:

my ctor is junk.NewMain$1(java.lang.Integer,java.util.Calendar)

If I then instantiate that with

Object newObj = ctors[0].newInstance(new Integer(25), Calendar.getInstance());

a peek inside using the debugger shows that finalInteger is 25 and not the final value 30.

Things are slightly complicated because you're doing all of the above in a static context. If you take all your code above and move it into a non-static method like so (remember, my class is junk.NewMain):

public static void main(String[] args)
{
  NewMain nm = new NewMain();
  nm.doIt();
}

public void doIt()
{
  final String finalString = "I'm final :)";
  // etc etc
}

you'll find the ctor for your inner class is now (removing my added Integer reference):

my ctor is junk.NewMain$1(junk.NewMain, java.util.Calendar)

The Java Language Specification, section 15.9.3 explains it this way:

If C is an anonymous class, and the direct superclass of C, S, is an inner class, then:

  • If the S is a local class and S occurs in a static context, then the arguments in the argument list, if any, are the arguments to the constructor, in the order they appear in the expression.
  • Otherwise, the immediately enclosing instance of i with respect to S is the first argument to the constructor, followed by the arguments in the argument list of the class instance creation expression, if any, in the order they appear in the expression.

Why does the anonymous constructor take arguments at all?

Since you can't create a constructor for an anonymous inner class, the instance initializer block serves that purpose (remember, you only have one instance of that anonymous inner class). The VM has no knowledge of the inner class as the compiler separates everything out as individual classes (e.g. junk.NewMain$1). The ctor for that class contains the contents of the instance initializer.

This is explayed by JLS 15.9.5.1 Anonymous Constructors:

...the anonymous constructor has one formal parameter for each actual argument to the class instance creation expression in which C is declared.

Your instance initializer has a reference to a Calendar object. How else is the compiler going to get that runtime value into your inner class (which is created as just a class for the VM) except through the constructor?

Finally (yay), the answer to the last burning question. Why doesn't the constructor require a String? The last bit of JLS 3.10.5 explains that:

Strings computed by constant expressions are computed at compile time and then treated as if they were literals.

In other words, your String value is known at compile time because it's a literal so it does not need to be part of the anonymous constructor. To prove this is the case we'll test the next statement in JLS 3.10.5:

Strings computed by concatenation at run time are newly created and therefore distinct.

Change your code thusly:

String str1 = "I'm";
String str2 = " final!";
final String finalString = str1 + str2

and you'll find your ctor is now (in the non-static context):

my ctor is junk.NewMain$1(junk.NewMain,java.lang.String,java.util.Calendar)

Phew. I hope this makes sense and was helpful. I learned a lot, that's for sure!

like image 152
Paul Avatar answered Oct 29 '22 09:10

Paul


Because in the second case there is no default constructor anymore.

In the first case the final string gets inlined because it is a constant.

In the second case the anonymous inner class has to accept instance of Calendar into its constructor to capture the state. You can easily confirm that doing this:

Object tmp = object2.getClass().getDeclaredConstructor(Calendar.class).newInstance(calendar);
like image 36
denis.solonenko Avatar answered Oct 29 '22 09:10

denis.solonenko