Я хочу напечатать привет дедушка, но, кажется, напечатать привет отец - PullRequest
5 голосов
/ 20 февраля 2020

Я хочу напечатать hi GrandFather

Но, похоже, он печатает привет, папа. И я не понимаю, как разграничить использование между findSpecial и findVirtual

Я хочу, чтобы кто-то мог мне помочь. Спасибо

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

Ответы [ 2 ]

7 голосов
/ 20 февраля 2020

Последний аргумент findSpecial указывает класс контекста для вызова. Вы указали getClass(), что приведет к Son.class. Вызов super из Son может закончиться только в Father, как обычный вызов super.thinking() в исходном коде.

Вам необходимо указать Father.class в качестве класса контекста, например Father разрешено выполнять super вызов GrandFather. Если вы сделаете это без дальнейших изменений, вы получите исключение, например java.lang.IllegalAccessException: no private access for invokespecial…. Вы должны изменить контекст вашего lookup() объекта, чтобы иметь возможность доступа к private функциям Father:

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

Это работает, потому что Son и Father являются внутренними классами одного класса высшего уровня, поэтому разрешен доступ к закрытым членам друг друга. Если бы они не были классами внутри одного и того же класса верхнего уровня, 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);

Это работает, когда Father и Son находятся в одном модуле или модуль Father открыл Father пакет для Son модуля для Reflection.

2 голосов
/ 25 февраля 2020

Хольгер как обычно правильный. На самом деле мне потребовалось некоторое время, чтобы понять, что происходит, пожалуйста, отнеситесь к этому как к поправке к другому очень хорошему ответу. Чтобы понять это, мне пришлось сделать 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 class равен FindSpecialFailure и, как таковой, это будет использоваться для определения, может ли метод go из Son.class быть скомпилированным, проверенным и разрешенным .

Вы можете подумать об этом проще, немного. Сможете ли вы (теоретически) создать инструкцию invokeSpecial в FindSpecialFailure::main и вызвать ее? Опять теоретически. Вы можете создать его там:

invokeSpecial Son.go:()V

Можете ли вы вызвать его, хотя? Ну нет ; в частности, в моем понимании, это правило будет нарушено:

Если ссылка symboli c называет класс (не интерфейс), то этот класс является суперклассом текущего класса.

Очевидно, 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 содержит объявление метода экземпляра publi c с тем же именем и дескриптором, что и у разрешенного метода, тогда он метод должен быть вызван.

Как мне исправить первый пример? (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();
    }
} 

Если вы go до той же документации , вы найдете:

В некоторых случаях доступ между вложенными классами получается компилятором Java путем создания метода-обертки для доступа к закрытому методу другого класса в том же объявлении верхнего уровня. Например, вложенный класс C .D может обращаться к закрытым членам в других связанных классах, таких как C, C .DE или C .B, но компилятору Java может потребоваться генерировать методы-оболочки в тех родственных классах. В таких случаях объект Lookup в C .E не сможет получить доступ к этим закрытым членам. Обходным решением для этого ограничения является метод 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 говорит о первом параметре:

ref c класс или интерфейс, из которого осуществляется доступ к методу.

Именно поэтому он будет печатать 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, но, поскольку вы не можете вызывать super.super методы в java, это "понижено" до Father::thinking.

Все I Я мог бы предложить здесь было использовать .in, чтобы решить эту проблему, я не знал о privateLookupIn.

...