Italian (Italiano) translation by Loris Pizii (you can also view the original English article)
Java 8 è stato un enorme passo avanti per il linguaggio di programmazione e ora, con il rilascio di Android Studio 3.0, gli sviluppatori Android hanno finalmente accesso a un supporto integrato per alcune delle funzioni più importanti di Java 8.
In questa serie di tre parti, abbiamo esplorato le funzionalità di Java 8 che puoi iniziare a utilizzare nei tuoi progetti Android oggi.In codice più pulito con le espressioni Lambda, abbiamo impostato il nostro sviluppo per utilizzare il supporto Java 8 fornito dal toolchain di default di Android, prima di approfondire le espressioni di lambda.
In questo post esamineremo due modi diversi per dichiarare metodi non-astratti nelle vostre interfacce (qualcosa che non era possibile nelle versioni precedenti di Java).Risponderemo anche alla domanda, ora che le interfacce possono implementare metodi, qual è la differenza tra le classi astratte e le interfacce?
Copriamo anche una funzionalità Java 8 che ti dà la libertà di utilizzare la stessa annotazione più volte che desideri nella stessa posizione, pur restando indietro compatibile con le versioni precedenti di Android.
Ma prima di tutto, esaminiamo una funzionalità Java 8 che è stata progettata per essere utilizzata in combinazione con le espressioni lambda che abbiamo visto nel post precedente.
Scrivere le espressioni Lambda Cleaner, con i riferimenti di metodo
Nell'ultimo messaggio, hai visto come puoi usare le espressioni lambda per rimuovere un sacco di codice di avvio dalle applicazioni Android. Tuttavia, quando un'espressione lambda sta semplicemente chiamando un singolo metodo che ha già un nome, è possibile tagliare ancora di più il codice dal progetto utilizzando un riferimento di metodo.
Ad esempio, questa espressione lambda è veramente reindirizzando il lavoro ad un metodo handleViewClick
esistente:
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(view -> handleViewClick(view)); } private void handleViewClick(View view) { }
In questo scenario, possiamo fare riferimento a questo metodo per nome, utilizzando l'operatore di riferimento del metodo ::
. Si crea questo tipo di riferimento di metodo, utilizzando il seguente formato:
Object/Class/Type::methodName
Nel nostro esempio del pulsante di azione galleggiante, possiamo utilizzare un riferimento metodico come corpo dell'espressione lambda:
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(this::handleViewClick); }
Si noti che il metodo a cui si fa riferimento deve avere gli stessi parametri dell'interfaccia - in questa istanza, ovvero Visualizza.
È possibile utilizzare l'operatore di riferimento del metodo (: :
) per fare riferimento a una delle seguenti opzioni:
Un metodo statico
Se si dispone di un'espressione lambda che chiama un metodo statico:
(args) -> Class.staticMethod(args)
Poi puoi trasformarlo in un riferimento di metodo:
Class::staticMethodName
Ad esempio, se aveste un metodo statico PrintMessage
in una classe MyClass
, il tuo metodo di riferimento potrebbe essere simile a questo:
public class myClass { public static void PrintMessage(){ System.out.println("This is a static method"); } } public static void main(String[] args) { Thread thread = new Thread(myClass::PrintMessage); thread.start(); } }
Metodo di istanza di un oggetto specifico
Questo è un metodo di istanza di un oggetto che è conosciuto in anticipo, consentendo di sostituire:
(arguments) -> containingObject.instanceMethodName(arguments)
Con:
containingObject::instanceMethodName
Quindi, se hai avuto la seguente espressione lambda:
MyClass.printNames(names, x -> System.out.println(x));
Quindi introduzione di un metodo di riferimento vi darà quanto segue:
MyClass.printNames(names,System.out::println);
Un metodo di istanza di un oggetto arbitrario di un particolare tipo
Questo è un metodo di istanza di un oggetto arbitrario che verrà fornito in seguito e scritto nel seguente formato:
ContainingType::methodName
Riferimenti del costruttore
I riferimenti del costruttore sono simili ai riferimenti di metodo, ad eccezione che si utilizza la nuova
parola chiave per invocare il costruttore. Ad esempio, Button::new
è un riferimento costruttore per il Button
di classe, anche se il costruttore esatto richiamato dipende dal contesto.
Utilizzando i riferimenti del costruttore, è possibile attivare:
(arguments) -> new ClassName(arguments)
In:
ClassName::new
Ad esempio, se si avesse la seguente interfaccia MyInterface:
public Interface myInterface{ public abstract Student getStudent(String name, Integer age); }
Quindi è possibile utilizzare riferimenti costruttori per creare nuove istanze Studente:
myInterface stu1 = Student::new; Student stu = stu1.getStudent("John Doe", 27);
È inoltre possibile creare riferimenti di costruttori per i tipi di array. Ad esempio, un riferimento costruttore per un array di int
è int []::new.
Aggiungi metodi predefiniti alle interfacce
Prima di Java 8, è possibile includere solo metodi astratti nelle interfacce (ovvero metodi senza un corpo) che hanno reso difficile l'evoluzione delle interfacce, post-pubblicazione.
Ogni volta che hai aggiunto un metodo a una definizione di interfaccia, qualsiasi classe che ha implementato questa interfaccia improvvisamente manca un'implementazione. Ad esempio, se si dispone di un'interfaccia (MyInterface)
utilizzata da MyClass
, aggiungere un metodo a MyInterface
interromperà la compatibilità con MyClass.
Nel migliore dei casi dove siete stati responsabili del piccolo numero di classi che utilizzavano MyInterface
, questo comportamento sarebbe fastidioso ma gestibile: dovrai solo mettere da parte un po 'di tempo per aggiornare le tue classi con la nuova implementazione. Tuttavia, le cose potrebbero diventare molto più complicate se un gran numero di classi implementa MyInterface
o se l'interfaccia è stata utilizzata in classi per cui non sei responsabile.
Mentre ci sono stati diversi soluzioni per questo problema, nessuno di loro era ideale. Ad esempio, è possibile includere nuovi metodi in una classe astratta, ma ciò richiederebbe comunque a tutti di aggiornare il proprio codice per estendere questa classe astratta; e, mentre potresti estendere l'interfaccia originale con una nuova interfaccia, chiunque volesse utilizzare questi nuovi metodi avrebbe bisogno di riscrivere tutti i riferimenti di interfaccia esistenti.
Con l'introduzione di metodi predefiniti in Java 8, è ora possibile dichiarare metodi non-astratti (cioè metodi con un corpo) all'interno delle interfacce, in modo da poter finalmente creare implementazioni predefinite per i metodi.
Quando si aggiunge un metodo all'interfaccia come metodo predefinito, qualsiasi classe che implementa questa interfaccia non necessariamente necessita di fornire una propria implementazione, che consente di aggiornare le interfacce senza interrompere la compatibilità. Se si aggiunge un metodo nuovo a un'interfaccia come metodo predefinito, ogni classe che utilizza questa interfaccia ma non fornisce la propria implementazione erediterà semplicemente l'implementazione predefinita del metodo. Poiché la classe non manca di un'implementazione, continuerà a funzionare come normale.
Infatti, l'introduzione di metodi predefiniti è stata la ragione per cui Oracle ha potuto apportare un numero così elevato di aggiunte all'API Collection in Java 8.
La raccolta
è un'interfaccia generica utilizzata in molte classi diverse, quindi l'aggiunta di nuovi metodi a questa interfaccia ha potuto interrompere innumerevoli righe di codice. Invece di aggiungere nuovi metodi all'interfaccia di raccolta
e di rompere ogni classe derivata da questa interfaccia, Oracle ha creato la funzionalità di metodo predefinito, quindi aggiunge questi nuovi metodi come metodi predefiniti. Se si esamina il nuovo metodo Collection.Stream () (che esploreremo in dettaglio nella parte tre), vedrai che è stato aggiunto come metodo predefinito:
default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }
La creazione di un metodo predefinito
è semplice: basta aggiungere il modificatore predefinito alla tua firma del metodo:
public interface MyInterface { void interfaceMethod(); default void defaultMethod(){ Log.i(TAG,"This is a default method”); } }
Ora, se MyClass
utilizza MyInterface
ma non fornisce la propria implementazione di defaultMethod,
erediterà l'implementazione predefinita fornita da MyInterface.
Ad esempio, la classe seguente si compila ancora:
public class MyClass extends AppCompatActivity implements MyInterface { }
Una classe di implementazione può ignorare l'implementazione predefinita fornita dall'interfaccia, in modo che le classi continuino a controllare pienamente le loro implementazioni.
Mentre i metodi predefiniti sono un'aggiunta benvenuta per i progettisti API, possono occasionalmente causare un problema per gli sviluppatori che tentano di utilizzare più interfacce nella stessa classe.
Immagina che, oltre a MyInterface,
hai i seguenti:
public interface SecondInterface { void interfaceMethod(); default void defaultMethod(){ Log.i(TAG,"This is also a default method”); } }
Sia MyInterface
che SecondInterface
contengono un metodo predefinito con la stessa firma (defaultMethod)
. Ora immaginate di provare a utilizzare entrambe queste interfacce nella stessa classe:
public class MyClass extends AppCompatActivity implements MyInterface, SecondInterface { }
A questo punto si dispongono di due implementazioni in conflitto di defaultMethod
e il compilatore non ha idea di quale metodo deve utilizzare, per cui si incontra un errore di compilatore.
Un modo per risolvere questo problema è quello di ignorare il metodo in conflitto con la propria implementazione:
public class MyClass extends AppCompatActivity implements MyInterface, SecondInterface { public void defaultMethod(){ } }
L'altra soluzione è specificare quale versione di defaultMethod
da implementare, utilizzando il seguente formato:
<interface name>.super.<method name>();
Quindi, se voleste chiamare l'implementazione MyInterface#defaultMethod ()
, allora utilizzeresti quanto segue:
public class MyClass extends AppCompatActivity implements MyInterface, SecondInterface { public void defaultMethod(){ MyInterface.super.defaultMethod(); } }
Utilizzo di metodi statici nelle interfacce di Java 8
Simile ai metodi predefiniti, i metodi di interfaccia statica forniscono un modo per definire i metodi all'interno di un'interfaccia. Tuttavia, a differenza dei metodi predefiniti, una classe di implementazione non può ignorare i metodi statici di un'interfaccia.
Se si dispone di metodi statici specifici per un'interfaccia, i metodi di interfaccia statica di Java 8 consentono di mettere questi metodi all'interno dell'interfaccia corrispondente, invece di memorizzarli in una classe separata.
Si crea un metodo statico inserendo la parola chiave statica
all'inizio della firma del metodo, ad esempio:
public interface MyInterface { static void staticMethod(){ System.out.println("This is a static method"); } }
Quando si implementa un'interfaccia che contiene un metodo di interfaccia statica, quel metodo statico è ancora parte dell'interfaccia e non viene ereditato dalla classe che lo implementa, pertanto è necessario prefisso del metodo con il nome dell'interfaccia, ad esempio:
public class MyClass extends AppCompatActivity implements MyInterface { public static void main(String[] args) { MyInterface.staticMethod(); ... ... ...
Ciò significa anche che una classe e un'interfaccia possono avere un metodo statico con la stessa firma. Ad esempio, utilizzando MyClass.staticMethod
e MyInterface.staticMethod
nella stessa classe non causerà un errore di compilazione.
Quindi, sono interfacce essenzialmente solo classi astratte?
L'aggiunta di metodi di interfaccia statica e di metodi predefiniti ha indotto alcuni sviluppatori a stabilire se le interfacce Java stanno diventando più simili a classi astratte. Tuttavia, anche con l'aggiunta di metodi di interfaccia statica e di default, esistono ancora alcune differenze tra le interfacce e le classi astratte:
- Le classi astratte possono avere variabili definitive, non finali, statiche e non statiche, mentre un'interfaccia può avere solo variabili statiche e definitive.
- Le classi astratte consentono di dichiarare campi che non sono statici e finali, mentre i campi di un'interfaccia sono intrinsecamente statici e finali.
- Nelle interfacce, tutti i metodi dichiarati o definiti come metodi predefiniti sono intrinsecamente pubblici, mentre nelle classi astratte è possibile definire metodi concreti pubblici, protetti e privati.
- Le classi astratte sono classi e quindi possono avere lo stato; le interfacce non possono avere stato associato a loro.
- È possibile definire costruttori all'interno di una classe astratta, cosa che non è possibile all'interno di interfacce Java.
- Java consente solo di estendere una classe (indipendentemente dal fatto che sia astratto), ma è possibile implementare tante interfacce come è necessario. Ciò significa che le interfacce hanno tipicamente il bordo quando si richiede un'eredità multipla, anche se devi guardare il diamante mortale della morte!
Applicare la stessa annotazione come molti tempi come vuoi
Tradizionalmente, una delle limitazioni delle annotazioni Java è stata che non puoi applicare la stessa annotazione più di una volta nella stessa posizione. Provare a utilizzare la stessa annotazione più volte e troverai un errore di compilazione.
Tuttavia, con l'introduzione di annotazioni ripetute di Java 8, è ora libero di utilizzare la stessa annotazione più volte che si desidera nella stessa posizione.
Per garantire che il codice rimanga compatibile con le versioni precedenti di Java, è necessario memorizzare le annotazioni ripetute in un'annotazione del contenitore.
Puoi dire al compilatore di generare questo contenitore, completando le seguenti operazioni:
- Annota l'annotazione in questione con la meta-annotazione
@Repeatable
(un'annotazione utilizzata per annotare un'annotazione). Ad esempio, se voleste rendere ripetibile l'annotazione@ToDo
, utilizzare:@Repeatable(ToDos.class).
Il valore tra parentesi è il tipo di annotazione del contenitore che il compilatore genererà. - Dichiarare il tipo di annotazione contenente. Questo deve avere un attributo che è una matrice del tipo di annotazione ripetuta, ad esempio:
public @interface ToDos { ToDo[] value(); }
Tentando di applicare la stessa annotazione più volte senza prima dichiarare che è ripetibile, si verifica un errore al momento della compilazione. Tuttavia, una volta che hai specificato che si tratta di un'annotazione ripetibile, puoi utilizzare questa annotazione più volte in qualsiasi posizione in cui utilizzare un'annotazione standard.
Conclusione
In questa seconda parte della nostra serie su Java 8 abbiamo visto come puoi tagliare ancora più codice di boilerplate dai tuoi progetti Android combinando espressioni di lambda con i riferimenti di metodo e come migliorare le interfacce con i metodi predefiniti e statici.
Nella terza e ultima versione verrà esaminata una nuova API Java 8 che consente di elaborare quantità enormi di dati in modo più efficiente e dichiarativo, senza dover preoccuparsi della concorrenza e della gestione dei thread. Saremo anche legando alcune delle diverse funzioni che abbiamo discusso in questa serie, esplorando il ruolo che le interfacce funzionali devono svolgere in espressioni lambda, metodi di interfaccia statica, metodi predefiniti e altro ancora.
E infine, anche se stiamo ancora aspettando l'API di Data e Ora di Java 8 per arrivare ufficialmente in Android, mostrerò come puoi iniziare a utilizzare questa nuova API nei tuoi progetti Android oggi con l'aiuto di alcuni terzi librerie.
Nel frattempo, guarda alcuni dei nostri altri post sullo sviluppo di applicazioni Java e Android!
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post