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