Construções sintéticas em Java
1. Visão geral
Neste tutorial, daremos uma olhada nas construções sintéticas de Java, código introduzido pelo compilador para lidar de forma transparente com o acesso a membros que de outra forma seriam inacessíveis devido à visibilidade insuficiente ou referências ausentes
2. Sintético em Java
A melhor definição desynthetic que poderíamos encontrar vem diretamente da Especificação da linguagem Java (JLS 13.1.7):
Quaisquer construções introduzidas por um compilador Java que não tenham uma construção correspondente no código-fonte devem ser marcadas como sintéticas, exceto os construtores padrão, o método de inicialização da classe e os valores e métodos valueOf da classe Enum.
Existem diferentes tipos de construções de compilação, como campos, construtores e métodos. Por outro lado,although nested classes can be altered by the compiler (i.e. anonymous classes), they aren’t considered synthetic.
Sem mais delongas, vamos nos aprofundar em cada um deles.
3. Campos Sintéticos
Vamos começar com uma classe aninhada simples:
public class SyntheticFieldDemo {
class NestedClass {}
}
Quando compilado,any inner class will contain a synthetic field which references the top level class. Coincidentally, this is what makes possible to access the enclosing class members from a nested class.
Para ter certeza de que isso é o que está acontecendo, implementaremos um teste que obtém os campos de classe aninhados por reflexão e os verifica usando o métodoisSynthetic():
public void givenSyntheticField_whenIsSynthetic_thenTrue() {
Field[] fields = SyntheticFieldDemo.NestedClass.class
.getDeclaredFields();
assertEquals("This class should contain only one field",
1, fields.length);
for (Field f : fields) {
System.out.println("Field: " + f.getName() + ", isSynthetic: " +
f.isSynthetic());
assertTrue("All the fields of this class should be synthetic",
f.isSynthetic());
}
}
Outra maneira de verificar isso seria executando o desmontador por meio do comandojavap. Em ambos os casos, a saída mostra um campo sintético chamadothis$0.
4. Métodos Sintéticos
Em seguida, adicionaremos um campo privado à nossa classe aninhada:
public class SyntheticMethodDemo {
class NestedClass {
private String nestedField;
}
public String getNestedField() {
return new NestedClass().nestedField;
}
public void setNestedField(String nestedField) {
new NestedClass().nestedField = nestedField;
}
}
Neste caso,the compilation will generate accessors to the variable. Without these methods, it’d be impossible to access a private field from the enclosing instance.
Mais uma vez, podemos verificar isso com a mesma técnica que mostra dois métodos sintéticos chamadosaccess$0eaccess$1:
public void givenSyntheticMethod_whenIsSynthetic_thenTrue() {
Method[] methods = SyntheticMethodDemo.NestedClass.class
.getDeclaredMethods();
assertEquals("This class should contain only two methods",
2, methods.length);
for (Method m : methods) {
System.out.println("Method: " + m.getName() + ", isSynthetic: " +
m.isSynthetic());
assertTrue("All the methods of this class should be synthetic",
m.isSynthetic());
}
}
Observe quein order to generate the code, the field must actually be read from or written to,otherwise, the methods will be optimized away. Esta é a razão pela qual também adicionamos um getter e um setter.
4.1. Métodos de ponte
Um caso especial de métodos sintéticos são os métodos de ponte, que tratam da eliminação de tipos de genéricos.
Por exemplo, vamos considerar um simples Comparator:
public class BridgeMethodDemo implements Comparator {
@Override
public int compare(Integer o1, Integer o2) {
return 0;
}
}
Emboracompare() receba dois argumentosInteger na fonte, uma vez compilado, ele pegará dois argumentosObject, devido ao apagamento do tipo.
Para gerenciar isso,the compiler creates a synthetic bridge which takes care of casting the arguments:
public int compare(Object o1, Object o2) {
return compare((Integer) o1, (Integer) o2);
}
Além de nossos testes anteriores, desta vez também chamaremosisBridge() da classeMethod:
public void givenBridgeMethod_whenIsBridge_thenTrue() {
int syntheticMethods = 0;
Method[] methods = BridgeMethodDemo.class.getDeclaredMethods();
for (Method m : methods) {
System.out.println("Method: " + m.getName() + ", isSynthetic: " +
m.isSynthetic() + ", isBridge: " + m.isBridge());
if (m.isSynthetic()) {
syntheticMethods++;
assertTrue("The synthetic method in this class should also be a bridge method",
m.isBridge());
}
}
assertEquals("There should be exactly 1 synthetic bridge method in this class",
1, syntheticMethods);
}
5. Construtores Sintéticos
Por fim, adicionaremos um construtor privado:
public class SyntheticConstructorDemo {
private NestedClass nestedClass = new NestedClass();
class NestedClass {
private NestedClass() {}
}
}
Desta vez, assim que executarmos o teste ou o desmontador, veremos que, na verdade, existem dois construtores, um dos quais é sintético:
public void givenSyntheticConstructor_whenIsSynthetic_thenTrue() {
int syntheticConstructors = 0;
Constructor>[] constructors = SyntheticConstructorDemo.NestedClass
.class.getDeclaredConstructors();
assertEquals("This class should contain only two constructors",
2, constructors.length);
for (Constructor> c : constructors) {
System.out.println("Constructor: " + c.getName() +
", isSynthetic: " + c.isSynthetic());
if (c.isSynthetic()) {
syntheticConstructors++;
}
}
assertEquals(1, syntheticConstructors);
}
Semelhante aos campos sintéticos,this generated constructor is essential to instantiate a nested class with a private constructor from its enclosing instance.
6. Conclusão
Neste artigo, discutimos construções sintéticas geradas pelo compilador Java. Para testá-los, usamos a reflexão, que você pode aprender mais sobrehere.
Como sempre, todo o código está disponívelover on GitHub.