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 al supporto integrato per alcune delle funzionalità più importanti di Java 8.
In questa serie in tre parti, abbiamo esplorato le funzionalità di Java 8 che è possibile iniziare a utilizzare nei progetti Android oggi. Nel codice Cleaner con Lambda Expressions, abbiamo impostato il nostro sviluppo per utilizzare il supporto Java 8 fornito dalla toolchain predefinita di Android, prima di dare uno sguardo approfondito alle espressioni lambda.
In questo post, esamineremo due diversi modi in cui puoi dichiarare metodi non astratti nelle tue interfacce (cosa che non era possibile nelle versioni precedenti di Java). Risponderemo anche alla domanda di, ora che le interfacce possono implementare i metodi, che cosa Esattamente è la differenza tra classi astratte e interfacce?
Copriremo anche una funzionalità di Java 8 che ti dà la libertà di utilizzare la stessa annotazione tutte le volte che vuoi nella stessa posizione, pur restando retrocompatibile con le versioni precedenti di Android.
Ma prima, diamo un'occhiata a una funzionalità di Java 8 che è stata progettata per essere utilizzata in combinazione con le espressioni lambda che abbiamo visto nel post precedente.
Nell'ultimo post, hai visto come è possibile utilizzare le espressioni lambda per rimuovere un sacco di codice boilerplate dalle tue applicazioni Android. Tuttavia, quando un'espressione lambda chiama semplicemente un singolo metodo che ha già un nome, puoi tagliare ancora più codice dal tuo progetto usando un riferimento al metodo.
Ad esempio, questa espressione lambda sta davvero semplicemente reindirizzando il lavoro a un esistente handleViewClick
metodo:
FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (view -> handleViewClick (view)); private void handleViewClick (Visualizza vista)
In questo scenario, possiamo fare riferimento a questo metodo per nome, usando il ::
operatore di riferimento del metodo. Si crea questo tipo di riferimento al metodo, utilizzando il seguente formato:
Oggetto / Classe / Tipo :: nomeMetodo
Nel nostro esempio di pulsante di azione mobile, possiamo utilizzare un riferimento al metodo come corpo della nostra espressione lambda:
FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (questo :: handleViewClick);
Si noti che il metodo di riferimento deve assumere gli stessi parametri dell'interfaccia, in questo caso, vale a dire vista
.
È possibile utilizzare l'operatore di riferimento del metodo (::
) per fare riferimento a uno dei seguenti:
Se hai un'espressione lambda che chiama un metodo statico:
(args) -> Class.staticMethod (args)
Quindi puoi trasformarlo in un riferimento al metodo:
Class :: staticMethodName
Ad esempio, se hai un metodo statico PrintMessage
in un La mia classe
classe, quindi il riferimento al metodo sarebbe simile a questo:
public class myClass public static void PrintMessage () System.out.println ("Questo è un metodo statico"); public static void main (String [] args) Thread thread = new Thread (myClass :: PrintMessage); Thread.start ();
Questo è un metodo di istanza di un oggetto noto in anticipo, che consente di sostituire:
(argomenti) -> containingObject.instanceMethodName (argomenti)
Con:
containingObject :: instanceMethodName
Quindi, se avessi la seguente espressione lambda:
MyClass.printNames (nomi, x -> System.out.println (x));
Quindi l'introduzione di un riferimento al metodo ti darebbe quanto segue:
MyClass.printNames (nomi, System.out :: println);
Questo è un metodo di istanza di un oggetto arbitrario che verrà fornito in seguito e scritto nel seguente formato:
ContainingType :: methodName
I riferimenti del costruttore sono simili ai riferimenti al metodo, tranne per il fatto che si utilizza la parola chiave nuovo
per invocare il costruttore. Per esempio, Pulsante :: new
è un riferimento costruttore per la classe Pulsante
, sebbene il costruttore esatto invocato dipenda dal contesto.
Utilizzando i riferimenti del costruttore, puoi attivare:
(argomenti) -> new ClassName (argomenti)
In:
ClassName :: new
Ad esempio, se hai avuto il seguente MyInterface
interfaccia:
interfaccia pubblica myInterface public abstract Student getStudent (Nome stringa, Integer age);
Quindi potresti usare i riferimenti del costruttore per crearne di nuovi Alunno
casi:
myInterface stu1 = Student :: new; Studente stu = stu1.getStudent ("John Doe", 27);
È anche possibile creare riferimenti al costruttore per i tipi di array. Ad esempio, un riferimento costruttore per un array di int
s è int [] :: nuova
.
Prima di Java 8, si potevano includere solo metodi astratti nelle proprie interfacce (cioè i metodi senza un corpo), il che rendeva difficile l'evoluzione delle interfacce, la post-pubblicazione.
Ogni volta che hai aggiunto un metodo a una definizione di interfaccia, qualsiasi classe che implementava questa interfaccia avrebbe improvvisamente perso un'implementazione. Ad esempio, se tu avessi un'interfaccia (MyInterface
) usato da La mia classe
, quindi aggiungere un metodo a MyInterface
romperebbe la compatibilità con La mia classe
.
Nel migliore dei casi in cui sei stato responsabile per il piccolo numero di classi che hai usato MyInterface
, questo comportamento sarebbe fastidioso ma gestibile - dovresti solo dedicare del tempo ad aggiornare le tue lezioni con la nuova implementazione. Tuttavia, le cose potrebbero diventare tanto più complicato se viene implementato un gran numero di classi MyInterface
, o se l'interfaccia è stata utilizzata in classi di cui non eri responsabile.
Mentre c'erano un certo numero di soluzioni alternative per questo problema, nessuno di loro era l'ideale. Ad esempio, potresti includere nuovi metodi in una classe astratta, ma questo richiederebbe comunque a tutti di aggiornare il codice per estendere questa classe astratta; e mentre tu poteva estendere l'interfaccia originale con una nuova interfaccia, chiunque volesse utilizzare questi nuovi metodi dovrebbe quindi riscrivere tutti i loro riferimenti all'interfaccia esistenti.
Con l'introduzione dei metodi predefiniti in Java 8, è ora possibile dichiarare metodi non astratti (cioè i metodi con un corpo) all'interno delle interfacce, in modo da poter finalmente creare implementazioni predefinite per i propri metodi.
Quando si aggiunge un metodo all'interfaccia come metodo predefinito, qualsiasi classe che implementa questa interfaccia non deve necessariamente fornire la propria implementazione, il che consente di aggiornare le interfacce senza comprometterne la compatibilità. Se si aggiunge un nuovo metodo a un'interfaccia come metodo predefinito, quindi ogni classe che utilizza questa interfaccia ma non fornisce la propria implementazione erediterà semplicemente l'implementazione predefinita del metodo. Poiché la classe non manca un'implementazione, continuerà a funzionare normalmente.
Infatti, l'introduzione di metodi predefiniti è stata la ragione per cui Oracle è stata in grado di realizzare un numero così elevato di aggiunte all'API Collections in Java 8.
Collezione
è un'interfaccia generica che viene utilizzata in molte classi diverse, quindi l'aggiunta di nuovi metodi a questa interfaccia ha il potenziale di rompere innumerevoli linee di codice. Piuttosto che aggiungere nuovi metodi al Collezione
Interfaccia e rottura di ogni classe derivata da questa interfaccia, Oracle ha creato la funzione del metodo predefinito e quindi ha aggiunto questi nuovi metodi come metodi predefiniti. Se dai un'occhiata al nuovo metodo Collection.Stream () (che esploreremo in dettaglio nella terza parte), vedrai che è stato aggiunto come metodo predefinito:
flusso di defaultstream () return StreamSupport.stream (spliterator (), false);
La creazione di un metodo predefinito è semplice: basta aggiungere il predefinito
modificatore alla firma del metodo:
interfaccia pubblica MyInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Questo è un metodo predefinito");
Ora se La mia classe
usi MyInterface
ma non fornisce la propria implementazione di defaultMethod
, erediterà solo l'implementazione predefinita fornita da MyInterface
. Ad esempio, la seguente classe sarà ancora compilata:
La classe pubblica MyClass estende AppCompatActivity implementa MyInterface
Una classe di implementazione può sovrascrivere l'implementazione predefinita fornita dall'interfaccia, quindi le classi hanno ancora il controllo completo delle loro implementazioni.
Mentre i metodi predefiniti sono una gradita aggiunta per i progettisti di API, possono occasionalmente causare un problema agli sviluppatori che cercano di utilizzare più interfacce nella stessa classe.
Immagina che oltre a MyInterface
, hai il seguente:
interfaccia pubblica SecondInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Questo è anche un metodo predefinito");
Tutti e due MyInterface
e SecondInterface
contengono un metodo predefinito con esattamente la stessa firma (defaultMethod
). Ora immagina di provare ad utilizzare entrambe queste interfacce nella stessa classe:
classe pubblica MyClass estende AppCompatActivity implementa MyInterface, SecondInterface
A questo punto hai due implementazioni in conflitto di defaultMethod
, e il compilatore non ha idea del metodo che dovrebbe usare, quindi incontrerai un errore del compilatore.
Un modo per risolvere questo problema è sovrascrivere il metodo in conflitto con la propria implementazione:
public class MyClass estende AppCompatActivity implementa MyInterface, SecondInterface public void defaultMethod ()
L'altra soluzione è specificare quale versione di defaultMethod
vuoi implementare, utilizzando il seguente formato:
.super. ();
Quindi se volevi chiamare il MyInterface # defaultMethod ()
implementazione, quindi useresti quanto segue:
public class MyClass estende AppCompatActivity implementa MyInterface, SecondInterface public void defaultMethod () MyInterface.super.defaultMethod ();
Analogamente ai metodi predefiniti, i metodi di interfaccia statica consentono di definire metodi all'interno di un'interfaccia. Tuttavia, a differenza dei metodi predefiniti, una classe di implementazione non può sovrascrivere un'interfaccia statico metodi.
Se si dispone di metodi statici specifici per un'interfaccia, i metodi di interfaccia statica di Java 8 consentono di inserire questi metodi nell'interfaccia corrispondente, anziché doverli memorizzare in una classe separata.
Si crea un metodo statico posizionando la parola chiave statico
all'inizio della firma del metodo, ad esempio:
interfaccia pubblica MyInterface static void staticMethod () System.out.println ("Questo è un metodo statico");
Quando si implementa un'interfaccia che contiene un metodo di interfaccia statico, quel metodo statico è ancora parte dell'interfaccia e non è ereditato dalla classe che lo implementa, quindi è necessario prefisso il metodo con il nome dell'interfaccia, ad esempio:
public class MyClass estende AppCompatActivity implementa 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 in fase di compilazione.
L'aggiunta di metodi di interfaccia statici e metodi predefiniti ha portato alcuni sviluppatori a chiedersi se le interfacce Java stanno diventando più simili a classi astratte. Tuttavia, anche con l'aggiunta di metodi di interfaccia statici e di default, ci sono ancora alcune differenze notevoli tra interfacce e classi astratte:
Tradizionalmente, una delle limitazioni delle annotazioni Java è che non è possibile applicare la stessa annotazione più di una volta nella stessa posizione. Prova a utilizzare la stessa annotazione più volte e incontrerai un errore in fase di compilazione.
Tuttavia, con l'introduzione delle annotazioni ripetute di Java 8, ora sei libero di utilizzare la stessa annotazione tutte le volte che vuoi nella stessa posizione.
Per garantire che il codice rimanga compatibile con le versioni precedenti di Java, è necessario memorizzare le annotazioni ricorrenti in un'annotazione del contenitore.
Puoi dire al compilatore di generare questo contenitore, completando i seguenti passi:
@Ripetibile
meta-annotazioni (un'annotazione che viene utilizzata per annotare un'annotazione). Ad esempio, se si desidera rendere il @Fare
annotazione ripetibile, dovresti usare: @Repeatable (ToDos.class)
. Il valore tra parentesi è il tipo di annotazione del contenitore che verrà generato dal compilatore.public @interface ToDos ToDo [] value ();
Se si tenta di applicare la stessa annotazione più volte senza prima dichiarare che è ripetibile, si verificherà un errore in fase di compilazione. Tuttavia, dopo aver specificato che si tratta di un'annotazione ripetibile, puoi utilizzare questa annotazione più volte in qualsiasi posizione in cui utilizzi un'annotazione standard.
In questa seconda parte della nostra serie su Java 8, abbiamo visto come è possibile tagliare ancora più codice standard dai progetti Android combinando espressioni lambda con riferimenti al metodo e come migliorare le interfacce con i metodi predefiniti e statici.
Nella terza e ultima puntata, vedremo una nuova API Java 8 che consente di elaborare enormi quantità di dati in modo più efficiente e dichiarativo, senza doversi preoccupare della concorrenza e della gestione dei thread. Inoltre, legheremo insieme alcune delle diverse funzionalità che abbiamo discusso in questa serie, esplorando il ruolo che le interfacce funzionali devono svolgere nelle espressioni lambda, i metodi di interfaccia statica, i metodi predefiniti e altro ancora.
Infine, anche se stiamo ancora aspettando che le nuove API Date e Time di Java 8 arrivino ufficialmente su Android, mostrerò come puoi iniziare a utilizzare questa nuova API nei tuoi progetti Android oggi, con l'aiuto di alcune terze parti librerie.
Nel frattempo, controlla alcuni dei nostri altri post su sviluppo di applicazioni Java e Android!