Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: How do i determine whether a local class defined in an initializer block requires an enclosing instance for instantiation?

Right now I'm implementing a method that has one parameter of the type Class and this method returns a boolean if the given class object requires an instance of it's enclosing class for it to be instantiated.

This method currently works as follows:

    if (clazz.getEnclosingClass() == null) {
        return false;
    }
    if (clazz.isAnonymousClass() || clazz.isMemberClass()) {
        return !Modifier.isStatic(clazz.getModifiers());
    }
    if (clazz.getEnclosingConstructor() != null) {
        return true;
    }
    final Method enclosingMethod = clazz.getEnclosingMethod();
    if (enclosingMethod != null) {
        return !Modifier.isStatic(enclosingMethod.getModifiers());
    }

To explain why it is designed as such:

  1. It first checks if it's a top-level class, if so, the algorithm can safely return false
  2. If the class is anonymous or a member class, it requires a closing instance if it's not static (an anynmous class is automatically static if it's declared in a static constructor/method/initializer-block)
  3. The class can now be assumed to be a local class (disregarding arrays and primitives), so it's defined in either a constructor, method or initializer. However, unlike an anonymous class, a local class is never considered static, but still requires an enclosing instance if the local class is defined in a non-static block.
  4. A constructor is never static, so in that case return true
  5. If it's defined in a method, return true if the method isn't static

I need step 6 to determine whether the local class resides in either a Static Initializer Block or an Instance Initializer Block, so I'm done with the implementation for this function.

So here's where the reflection API falls a bit short. There's no method Class.getEnclosingInitializer() or such, nor is there a class that represents an initializer in the reflection package.

Isn't an initializer block a member of a class? In the java 1.8 spec the Member interface only has the implementation classes Field, Executable (with subclasses Constructor and Method), and then there's MemberName that's out of the scope for most users of reflection.

I'm not really sure if the people behind the spec forgot about this situation and local classes actually should be static if declared in a static method/initializer (like anonymous classes). But it seems to me that it's lacking this last bit of consistency from this point of view.

So does anybody have an idea on how to ascertain in which type of initializer block the local class is declared?

I'm not really keen on digging through fields for a synthetic one of a type that equals it's enclosing class, or looping through it's constructors for something akin to that (sidenote: Parameter objects from Constructor.getParameters() always return false on isImplicit() and isSynthetic() no matter what i try... that just seems wrong). So if I can avoid such solutions that would be great.

like image 327
dammkewl Avatar asked Feb 23 '16 10:02

dammkewl


People also ask

Can we declare a static initializer in a local class?

In Java we cannot declare static initializers or member interfaces in a local class (static members can be declared provided there are final and can be initialized at compile time).

Can a local class be static in Java?

Local classes are non-static because they have access to instance members of the enclosing block. Consequently, they cannot contain most kinds of static declarations.

What are static initializers and when would you use them?

A Static Initialization Block in Java is a block that runs before the main( ) method in Java. Java does not care if this block is written after the main( ) method or before the main( ) method, it will be executed before the main method( ) regardless.

What is initializer block in Java?

In order to perform any operations while assigning values to an instance data member, an initializer block is used. In simpler terms, the initializer block is used to declare/initialize the common part of various constructors of a class. It runs every time whenever the object is created.


1 Answers

An interesting puzzle, but I'm afraid it has no solution that satisfies your requirements. Once the source files are compiled into bytecode, the details about class enclosing scope is lost. Remember that JVM is not only for Java language, while the described problem is mostly language-specific.


I need step 6 to determine whether the local class resides in either a Static Initializer Block or an Instance Initializer Block

No such information is available in run-time. Class file has EnclosingMethod attribute for local or anonymous classes (JVMS §4.7.7). However, there is no way to distinguish between enclosing instance initializer and static initializer. The spec explicitly says that

«method_index must be zero if the current class was immediately enclosed in source code by an instance initializer, static initializer, instance variable initializer, or class variable initializer».

Consider these two cases:

class Outer {
    {
        // Local class inside instance initializer
        class Local {}
    }
}

vs.

class Outer {
    static {
        // Local class inside static initializer
        class Local {
            final Outer this$0;

            Local(Outer outer) {
                this$0 = outer;
            }
        }
    }
}

They are compiled to almost the same class file. The only difference is that field this$0 has SYNTHETIC flag in the first case, but not in the second. But checking this flag is exactly what you want to avoid. (Why?)


local classes actually should be static if declared in a static method/initializer (like anonymous classes)

I would argue that neither local nor anonymous classes should ever be static. Furthermore, Java Language Specification requires them to be non-static:

  • The modifier static pertains only to member classes, not to top level or local or anonymous classes (JLS §8.1.1).
  • An anonymous class is always an inner class; it is never static (JLS §15.9.5).

So, it's clearly a spec violation that getModifiers() sometimes returns STATIC for an anonymous class. There is a bug JDK-8034044 that has been fixed in upcoming JDK 9. And that breaks step 2 of your algorithm.


Okay, what to do then? It depends on what you actually mean by

if the given class object requires an instance of it's enclosing class for it to be instantiated

I would say, the above definition implies that all constructors of a class have an extra argument of enclosing class type. In this case there is indeed no way to instantiate a class without an instance of the enclosing class.

However, if you really wish to distinguish between initializer and static initializer, your only chance (as I've shown above) is to look for a field with SYNTHETIC flag.

like image 63
apangin Avatar answered Sep 23 '22 15:09

apangin