【问题标题】:I want to print hi GrandFather;but it seems to print hi father我想打印 hi GrandFather;但它似乎打印 hi Father
【发布时间】:2020-06-04 22:45:29
【问题描述】:

我要打印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();
}

【问题讨论】:

  • 您可以在此处找到更多信息:stackoverflow.com/questions/586363/…。在 Java 中,您不能调用 super.super.method()(就像在 Python 中一样),因此您不能访问上述多个类。

标签: java java-8 jvm invoke methodhandle


【解决方案1】:

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

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

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 的模块以进行反射时,此方法有效。

【讨论】:

  • 我能问一下为什么需要第一类参数吗?如果您说invokeSpecial 的“构建”基于Son.class - 它只能 转到Farther.class,那么第一个参数的意义何在?我知道我遗漏了一些东西,但这个 API 对我来说从来没有意义。
  • @Eugene 语义应该与invokespecial 字节码指令匹配,因此,当您要调用接口的default 方法时,接收器类型会有所不同。此外,您可以使用它在您自己的类中调用private 方法。问题应该是反过来的,当调用者类参数的唯一合理值是已经封装在Lookup 对象中的查找类时,为什么我们需要最后一个参数。
【解决方案2】:

霍尔格像往常一样是正确的。实际上我花了一段时间才明白发生了什么,请将其视为对另一个非常好的答案的修正。为了理解这一点,我不得不做 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... 失败。 The documentation 给出了发生这种情况的正确方向:

一般来说,可以为方法 M 查找方法句柄的条件并不比查找类可以编译、验证和解析对 M 的调用的条件更严格。

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

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

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

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

invokeSpecial Son.go:()V

你可以调用它吗? Well, no;具体来说,据我了解,这条规则会被打破:

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

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

这一次一切都会好起来的,因为 (from the same specification):

否则,如果 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();
    }
} 

如果你去the same documentation,你会发现:

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

第三个示例开始看起来更像您的示例:

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 的文档中提到了第一个参数:

引用从其访问方法的类或接口。

这就是为什么它会打印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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-04
    • 2022-07-26
    • 1970-01-01
    • 2019-07-28
    • 1970-01-01
    • 2017-05-06
    • 2021-10-01
    • 2019-03-24
    相关资源
    最近更新 更多