Costrutti sintetici in Java

1. Panoramica

In questo tutorial, daremo uno sguardo ai costrutti sintetici di Java, codice introdotto dal compilatore per gestire in modo trasparente l'accesso ai membri che altrimenti sarebbero irraggiungibili a causa di visibilità insufficiente o riferimenti mancanti.

Nota: a partire da JDK 11, i metodi sintetici e i costruttori non vengono più generati, poiché sono sostituiti dal controllo degli accessi basato su annidamento.

2. Sintetico in Java

La migliore definizione di sintetico che potremmo trovare proviene direttamente dalla specifica del linguaggio Java (JLS 13.1.7):

Tutti i costrutti introdotti da un compilatore Java che non hanno un costrutto corrispondente nel codice sorgente devono essere contrassegnati come sintetici, ad eccezione dei costruttori predefiniti, del metodo di inizializzazione della classe e dei valori e dei metodi valueOf della classe Enum.

Esistono diversi tipi di costrutti di compilazione, ovvero campi, costruttori e metodi. D'altra parte, sebbene le classi annidate possano essere alterate dal compilatore (cioè classi anonime), non sono considerate sintetiche .

Senza ulteriori indugi, approfondiamo ciascuno di questi.

3. Campi sintetici

Cominciamo con una semplice classe annidata:

public class SyntheticFieldDemo { class NestedClass {} }

Una volta compilata, qualsiasi classe interna conterrà un campo sinteticoche fa riferimento alla classe di primo livello. Per coincidenza, questo è ciò che rende possibile accedere ai membri della classe di inclusione da una classe annidata.

Per assicurarci che questo sia ciò che sta accadendo, implementeremo un test che ottiene i campi della classe nidificati per riflessione e li controlla usando il metodo isSynthetic () :

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()); } }

Un altro modo per verificarlo sarebbe eseguire il disassembler tramite il comando javap. In entrambi i casi, l'output mostra un campo sintetico denominato $ 0.

4. Metodi sintetici

Successivamente, aggiungeremo un campo privato alla nostra classe annidata:

public class SyntheticMethodDemo { class NestedClass { private String nestedField; } public String getNestedField() { return new NestedClass().nestedField; } public void setNestedField(String nestedField) { new NestedClass().nestedField = nestedField; } }

In questo caso, la compilazione genererà le funzioni di accesso alla variabile. Senza questi metodi, sarebbe impossibile accedere a un campo privato dall'istanza che lo racchiude.

Ancora una volta, possiamo verificarlo con la stessa tecnica che mostra due metodi sintetici chiamati access $ 0 e access $ 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()); } }

Si noti che per generare il codice, il campo deve essere effettivamente letto o scritto , altrimenti i metodi verranno ottimizzati . Questo è il motivo per cui abbiamo aggiunto anche un getter e un setter.

Come accennato in precedenza, questi metodi sintetici non vengono più generati a partire da JDK 11.

4.1. Metodi di bridge

Un caso speciale di metodi sintetici sono i metodi bridge, che gestiscono la cancellazione del tipo dei generici.

Ad esempio, consideriamo un semplice comparatore :

public class BridgeMethodDemo implements Comparator { @Override public int compare(Integer o1, Integer o2) { return 0; } }

Sebbene compare () richieda due argomenti Integer nel sorgente, una volta compilato richiederà invece due argomenti Object , a causa della cancellazione del tipo.

Per gestirlo, il compilatore crea un bridge sintetico che si occupa di lanciare gli argomenti :

public int compare(Object o1, Object o2) { return compare((Integer) o1, (Integer) o2); }

Oltre ai nostri test precedenti, questa volta chiameremo anche isBridge () dalla classe Method :

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. Costruttori sintetici

Infine, aggiungeremo un costruttore privato:

public class SyntheticConstructorDemo { private NestedClass nestedClass = new NestedClass(); class NestedClass { private NestedClass() {} } }

Questa volta, una volta eseguito il test o il disassembler vedremo che in realtà ci sono due costruttori, uno dei quali sintetico:

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); }

Analogamente ai campi sintetici, questo costruttore generato è essenziale per istanziare una classe nidificata con un costruttore privato dalla sua istanza che lo racchiude.

Come accennato in precedenza, il costruttore sintetico non viene più generato a partire da JDK 11.

6. Conclusione

In questo articolo, abbiamo discusso i costrutti sintetici generati dal compilatore Java. Per testarli, abbiamo utilizzato la riflessione, di cui puoi saperne di più qui.

Come sempre, tutto il codice è disponibile su GitHub.