I want to print hi GrandFather;but it seems to print hi father

扶醉桌前 提交于 2020-07-18 05:31:05

问题


I want to print hi GrandFather

But it seems to print hi father. And I am not understand how to diff the use between findSpecial and findVirtual

I want someone can help me. Thank you

class GrandFather{
    void thinking(){
        System.out.println("hi GrandFather");

    }
}
class Father extends GrandFather{
    void thinking(){
        System.out.println("hi Father");
    }
}
class Son extends Father{
    void thinking(){
            MethodType mt=MethodType.methodType(void.class);

            //MethodHandle mh=MethodHandles.lookup().findVirtual(GrandFather.class,"thinking",mt).bindTo(this);
            MethodHandle mh;
                mh = MethodHandles.lookup().findSpecial(GrandFather.class,"thinking",mt,getClass());
                mh.invoke(this);
    }
}
public static void main(String[] args){
    (new MethodHandleTest().new Son()).thinking();
}

console screenshot showing "hi Father" printed


回答1:


The last argument to findSpecial specifies the context class for the invocation. You specified getClass(), which will result in Son.class. A super invocation from Son can only end up in Father, just like an ordinary super.thinking() call in source code.

You need to specify Father.class as context class, as Father is allowed to perform a super invocation to GrandFather. If you do this without further changes, you’d get an exception like java.lang.IllegalAccessException: no private access for invokespecial…. You have to change the context of your lookup() object, to be able to access private features of Father:

MethodType mt = MethodType.methodType(void.class);
MethodHandle mh = MethodHandles.lookup().in(Father.class)
    .findSpecial(GrandFather.class, "thinking", mt, Father.class);
mh.invoke(this);

This works, because Son and Father are inner classes of the same top level class, so accessing each other’s private members is allowed. If they were not classes within the same top level class, in(…) would change the context class but clear the private access permission. In that case, only Java 9 and newer have an official solution:

MethodType mt = MethodType.methodType(void.class);
MethodHandle mh = MethodHandles.privateLookupIn(Father.class, MethodHandles.lookup())
    .findSpecial(GrandFather.class, "thinking", mt, Father.class);
mh.invoke(this);

This works when Father and Son are in the same module or Father’s module has opened Father’s package to Son’s module for Reflection.




回答2:


Holger as usual is correct. It actually took me a while to understand what is going on, please treat this as an amendment to the other very good answer. To understand this, I had to do 3 different examples.

public class FindSpecialFailure {

    public static void main(String[] args) {
        try {
            MethodType mt = MethodType.methodType(void.class);
            Lookup l = MethodHandles.lookup();
            MethodHandle mh = l.findSpecial(Son.class, "go", mt, Son.class);
            mh.invoke(new Son());
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    static class Parent {
        void go() {
            System.out.println("parent");
        }
    }

    static class Son extends Parent {
        void go() {
            System.out.println("son");
        }
    }
}

If you run this, it will fail with java.lang.IllegalAccessException: no private access for invokespecial.... The documentation gives proper directions of why this happens:

In general, the conditions under which a method handle may be looked up for a method M are no more restrictive than the conditions under which the lookup class could have compiled, verified, and resolved a call to M.

That same documentation explains this also:

The caller class against which those restrictions are enforced is known as the lookup class.

In our case lookup class is FindSpecialFailure and as such, this would be used to be able to tell if method go from Son.class could be compiled, verified, and resolved.

You can think about it simpler, a bit. Would you be able (in theory) to create an invokeSpecial byte code instruction in FindSpecialFailure::main and invoke it? Again, in theory. You could create it there:

invokeSpecial Son.go:()V

Can you invoke it though? Well, no; specifically, in my understanding, this rule would be broken:

If the symbolic reference names a class (not an interface), then that class is a superclass of the current class.

Obviously Son is not a superclass of FindSpecialFailure.

To prove my point, you could change the above code to (second example):

// example 1
public class FindSpecialInterface {

    public static void main(String[] args) {
        try {
            MethodType mt = MethodType.methodType(void.class);
            Lookup l = MethodHandles.lookup();
            // <---- Parent.class
            MethodHandle mh = l.findSpecial(Parent.class, "go", mt, Son.class);
            mh.invoke(new Son());
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    // <---- this is now an interface
    interface Parent {
        default void go() {
            System.out.println("parent");
        }
    }

    static class Son implements Parent {
        public void go() {
            System.out.println("son");
        }
    }
}

This time everything will work just fine, because (from the same specification):

Otherwise, if C is an interface and the class Object contains a declaration of a public instance method with the same name and descriptor as the resolved method, then it is the method to be invoked.

How do I fix the first example though? (FindSpecialFailure) You need to add an interesting option:

public static void main(String[] args) {
    try {
        MethodType mt = MethodType.methodType(void.class);
        // <--- Notice the .in(Son.class) --> 
        Lookup l = MethodHandles.lookup().in(Son.class);
        MethodHandle mh = l.findSpecial(Son.class, "go", mt, Son.class);
        mh.invoke(new Son());
    } catch (Throwable t) {
        t.printStackTrace();
    }
} 

If you go to the same documentation, you will find:

In some cases, access between nested classes is obtained by the Java compiler by creating an wrapper method to access a private method of another class in the same top-level declaration. For example, a nested class C.D can access private members within other related classes such as C, C.D.E, or C.B, but the Java compiler may need to generate wrapper methods in those related classes. In such cases, a Lookup object on C.E would be unable to those private members. A workaround for this limitation is the Lookup.in method, which can transform a lookup on C.E into one on any of those other classes, without special elevation of privilege.

The third example somehow starts to look more like your examples:

public class FinSpecialMoveIntoSon {

    public static void main(String[] args) {
        new Son().invokeMe();
    }

    static class Parent {
        public void go() {
            System.out.println("parent");
        }
    }

    static class Son extends Parent {

        void invokeMe() {
            try {
                MethodType mt = MethodType.methodType(void.class);
                Lookup l = MethodHandles.lookup();
                MethodHandle mh = l.findSpecial(Son.class, "go", mt, Son.class);
                mh.invoke(new Son());
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }

        public void go() {
            System.out.println("son");
        }
    }
}

The point of this one is that the documentation of findSpecial says about the first parameter:

refc the class or interface from which the method is accessed.

That is why it will print Son, and not Parent.


Armed with this, your example becomes easier to understand:

static class Son extends Father {

    void thinking() {
        try {
            MethodType mt = MethodType.methodType(void.class);
            MethodHandle mh = MethodHandles.lookup().findSpecial(GrandFather.class, "thinking", mt, getClass());
            mh.invoke(this);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

The lookup class is Son.class and method resolution and refc (there the class or interface from which the method is accessed) is GranFather. So the resolution does start with GrandFather::thinking, but since you can't call super.super methods in java, that is "downgraded" to Father::thinking.

All I could suggest here was to use .in to solve this, I was not aware of privateLookupIn.



来源:https://stackoverflow.com/questions/60322808/i-want-to-print-hi-grandfatherbut-it-seems-to-print-hi-father

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!