繁体   English   中英

我想打印 hi GrandFather;但它似乎打印 hifather

[英]I want to print hi GrandFather;but it seems to print hi father

我想打印hi GrandFather

但它似乎打印喜父亲。 我不明白如何findSpecialfindVirtual之间的使用

我想要有人可以帮助我。 谢谢

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();
}

控制台屏幕截图显示打印了“嗨父亲”

findSpecial的最后一个参数指定调用的上下文类。 您指定了getClass() ,这将导致Son.class 来自Sonsuper调用只能在Father结束,就像源代码中的普通super.thinking()调用一样。

您需要将Father.class指定为上下文类,因为允许FatherGrandFather执行super调用。 如果您在没有进一步更改的情况下执行此操作,您将收到类似java.lang.IllegalAccessException: no private access for invokespecial…的异常。 您必须更改您的lookup()对象的上下文,才能访问Father private功能:

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

这是有效的,因为SonFather是同一个顶级类的内部类,因此允许访问彼此的私有成员。 如果它们不是同一顶级类中的类,则in(…)将更改上下文类但清除私有访问权限。 在这种情况下,只有 Java 9 及更新版本有官方解决方案:

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

FatherSon在同一个模块中或Father的模块已将Father的包打开到Son的模块进行反射时,此方法有效。

Holger 像往常一样是正确的。 我实际上花了一段时间才明白发生了什么,请将其视为对另一个非常好的答案的修正。 为了理解这一点,我不得不做 3 个不同的例子。

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");
        }
    }
}

如果你运行它,它会失败并显示java.lang.IllegalAccessException: no private access for invokespecial... 文档给出了为什么会发生这种情况的正确说明:

通常,可以为方法 M 查找方法句柄的条件并不比查找类可以编译、验证和解析对 M 的调用的条件更具限制性

相同的文档也解释了这一点:

强制执行这些限制的调用者类称为查找类

在我们的例子中, lookup classFindSpecialFailure ,因此,这将用于判断来自Son.class方法go Son.class可以被编译、验证和解析

你可以想得更简单一点。 你能(理论上)在FindSpecialFailure::main创建一个invokeSpecial字节码指令并调用它吗? 再次,理论上。 你可以在那里创建它:

invokeSpecial Son.go:()V

你能调用它吗? 嗯,没有 具体来说,以我的理解,这条规则将被打破:

如果符号引用命名一个类(不是接口),则该类是当前类的超类。

显然Son不是FindSpecialFailure的超类。

为了证明我的观点,您可以将上面的代码更改为(第二个示例):

// 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");
        }
    }
}

这一次一切都会正常工作,因为( 来自相同的规范):

否则,如果 C 是一个接口并且类 Object 包含一个与解析方法具有相同名称和描述符的公共实例方法的声明,那么它就是要调用的方法。

我如何修复第一个例子? ( FindSpecialFailure ) 您需要添加一个有趣的选项:

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();
    }
} 

如果您转到相同的文档,您会发现:

在某些情况下,嵌套类之间的访问是由 Java 编译器通过创建包装器方法来访问同一顶级声明中另一个类的私有方法来获得的。 例如,嵌套类 CD 可以访问其他相关类(如 C、CDE 或 CB)中的私有成员,但 Java 编译器可能需要在这些相关类中生成包装方法。 在这种情况下,CE 上的 Lookup 对象将无法访问这些私有成员。 此限制的一种解决方法是 Lookup.in 方法,它可以将 CE 上的查找转换为任何其他类上的查找,无需特殊提升特权。

第三个例子不知何故开始看起来更像你的例子:

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");
        }
    }
}

这个问题的重点是findSpecial的文档说明了第一个参数:

refc 访问方法的类或接口。

这就是为什么它会打印Son而不是Parent


有了这个,你的例子就更容易理解了:

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();
        }
    }
}

查找类Son.class ,方法解析和refc从那里访问方法的类或接口)是GranFather 所以决议确实GrandFather::thinking开始,但由于你不能在java中调用super.super方法,所以它被“降级”为Father::thinking

我在这里只能建议使用.in来解决这个问题,我不知道privateLookupIn

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM