О внутренних классах Java
Java допускает классы-члены (классы, которые определены внутри других классов), локальные классы (классы, которые определены внутри блоков операторов) и анонимные классы (классы без имен):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class Outer { Object anonymous = new Object(){}; // this is an anonymous class // anonymous initialisation block { // this is a local class class Local{} Local l = new Local(); } Outer() { // this is a local named class in a constructor class Local{} Local l = new Local(); } void method() { // this is a local named class in a method class Local{} Local l = new Local(); } // this is a member class class Inner{} Inner i = new Inner(); } |
Спецификация языка Java классифицирует членские, локальные и анонимные классы как внутренние классы .
Реализация «деталей»
Спецификации языка Java или виртуальной машины не говорят вам о том, как они реализованы. Некоторые из них объясняются уже в других статьях , например, как компилятор Java генерирует синтетические методы, чтобы эти классы-члены могли получить доступ к закрытым полям, что не было бы разрешено JVM.
Еще одна полезная деталь реализации внутренних классов — это то, что конструкторы внутренних классов принимают дополнительные синтетические параметры. Относительно хорошо известно, что первым синтетическим параметром конструктора внутреннего класса будет его включающий экземпляр, который он будет хранить в this$X
синтетическом поле this$X
Это справедливо для всех трех типов внутренних классов: членских, локальных и анонимных.
Но, как правило, не известно, что локальные классы, которые захватывают непостоянные конечные переменные, потребуют передачи всех этих переменных в качестве дополнительных параметров синтетического конструктора (захваченные постоянные конечные переменные будут встроены и не будут генерировать дополнительные параметры синтетического конструктора):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
class Outer { void method() { final String constant = "foo" ; final String nonConstant = "foo" .toUpperCase(); class Local{ /* synthetic fields and constructor: Outer this$0; String nonConstant; Local(Outer this$0, String nonConstant){ this.this$0 = this$0; this.nonConstant = nonConstant; } */ } Local l = new Local(); } } |
Хорошо, но почему я должен заботиться?
В большинстве случаев вам все равно, кроме вашего собственного любопытства. Но если вы делаете отражение Java с внутренними классами, есть несколько вещей, которые вы должны знать, и, поскольку я не нашел их в списке или в сети, я подумал, что было бы важно составить список вещей, чтобы помочь другим понять это потому, что разные компиляторы будут давать разные результаты в API отражения Java.
Вопрос в том, что происходит, когда вы используете отражение Java для получения экземпляра java.lang.reflect.Constructor
для конструкторов внутреннего класса? В частности, что происходит с методами, которые позволяют получить доступ к типам параметров (pre-generics: getParameterTypes()
), универсальным типам параметров (post-generics: getGenericParameterTypes()
) и аннотациям ( getParameterAnnotations()
), а также к ответу это: это зависит .
Предположим, следующий класс Java:
1
2
3
4
5
6
7
|
class Outer { class Inner { Inner(){} Inner(String param){} Inner( @Deprecated Integer param){} } } |
Ниже приведены размеры массивов, возвращаемых этими тремя методами отражения для каждого нашего конструктора, и их различия в зависимости от используемого компилятора Java:
Outer.Inner.class .getDeclaredConstructor () |
Outer.Inner.class .getDeclaredConstructor ( String.class) |
Outer.Inner.class .getDeclaredConstructor ( Integer.class) |
|
---|---|---|---|
getParameterTypes () .length |
1 | 2 | 2 |
getGenericParameterTypes () длина, скомпилированная с Eclipse |
1 | 2 | 2 |
getGenericParameterTypes () длина скомпилирована с Javac |
0 | 1 | 1 |
getParameterAnnotations () .length |
1 | 2 | 1 |
Как видите, синтетические параметры всегда включаются в getParameterTypes()
, но включаются только в getGenericParameterTypes()
при компиляции с Eclipse.
getParameterAnnotations()
с другой стороны, всегда будет включать синтетические параметры, кроме случаев, когда хотя бы один из ваших параметров конструктора аннотирован.
С помощью этой информации вы теперь понимаете различия между результатами этих методов, но до сих пор я не нашел способа определить, какой параметр является синтетическим или нет, потому что, хотя вы можете сделать правильное предположение для this$X
синтетического this$X
Параметр, который требуется для каждого внутреннего класса, у вас нет возможности узнать количество непостоянных захваченных переменных, которые в конечном итоге станут синтетическими параметрами для конструкторов локальных классов.