Хольгер как всегда прав. На самом деле мне потребовалось некоторое время, чтобы понять, что происходит, пожалуйста, относитесь к этому как к поправке к другому очень хорошему ответу. Чтобы понять это, мне пришлось сделать 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
Вы можете вызвать его, хотя? Нет; конкретно, в моем понимании, это правило было бы нарушено:
Если символическая ссылка называет класс (не интерфейс), то этот класс является суперклассом текущего класса.
Очевидно, что 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 является интерфейсом, а объект класса содержит объявление общедоступного метода экземпляра с тем же именем и дескриптором, что и разрешенный метод, то вызывается именно этот метод.
Как мне исправить первый пример? (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 путем создания метода-оболочки для доступа к закрытому методу другого класса в том же объявлении верхнего уровня. Например, вложенный класс C.D может обращаться к закрытым членам других связанных классов, таких как C, C.D.E или C.B, но компилятору Java может потребоваться сгенерировать методы-оболочки в этих связанных классах. В таких случаях объект Lookup в C.E не сможет получить доступ к этим закрытым членам. Обходным путем для этого ограничения является метод 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
, но, поскольку вы не можете вызывать методы super.super
в java, оно "понижено" до Father::thinking
.
Все, что я мог предложить здесь, это использовать .in
для решения этой проблемы, я не знал о privateLookupIn
.
person
Eugene
schedule
25.02.2020
super.super.method()
(как в Python), поэтому вы НЕ можете получить доступ к более чем одному классу выше. - person Boken   schedule 20.02.2020