Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
170 views
in Technique[技术] by (71.8m points)

java - 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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...