Java 8 für Android-Entwicklung Standardmethoden und statische Methoden

Java 8 war ein großer Schritt nach vorne für die Programmiersprache. Mit der Veröffentlichung von Android Studio 3.0 haben Android-Entwickler nun endlich Zugriff auf die integrierte Unterstützung einiger der wichtigsten Funktionen von Java 8.

In dieser dreiteiligen Serie haben wir uns mit den Java 8-Funktionen beschäftigt, die Sie in Ihren Android-Projekten verwenden können heute. In Cleaner Code Mit Lambda Expressions haben wir unsere Entwicklung so eingerichtet, dass die Java 8-Unterstützung der Standard-Toolchain von Android verwendet wird, bevor die Lambda-Ausdrücke eingehender untersucht werden.

In diesem Beitrag werden zwei Möglichkeiten beschrieben, wie Sie in Ihren Schnittstellen nicht abstrakte Methoden deklarieren können (was in früheren Java-Versionen nicht möglich war). Wir werden auch die Frage beantworten, dass Schnittstellen jetzt Methoden implementieren können genau ist der Unterschied zwischen abstrakten Klassen und Schnittstellen?

Wir werden auch eine Java 8-Funktion behandeln, mit der Sie dieselbe Anmerkung beliebig oft am selben Ort verwenden können, während sie rückwärtskompatibel mit früheren Versionen von Android ist.

Schauen wir uns zunächst ein Java 8-Feature an, das in Kombination mit den Lambda-Ausdrücken des vorherigen Postens verwendet werden soll.

Schreiben Sie sauberere Lambda-Ausdrücke mit Methodenreferenzen

Im letzten Beitrag haben Sie gesehen, wie Sie Lambda-Ausdrücke verwenden können, um viel Boilerplate-Code aus Ihren Android-Anwendungen zu entfernen. Wenn ein Lambda-Ausdruck jedoch einfach eine einzelne Methode aufruft, die bereits einen Namen hat, können Sie mithilfe einer Methodenreferenz noch mehr Code aus Ihrem Projekt ausschneiden.

Bei diesem Lambda-Ausdruck wird beispielsweise nur die Arbeit auf ein vorhandenes umgeleitet handleViewClick Methode:

 FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (view -> handleViewClick (view));  private void handleViewClick (Ansichtsansicht) 

In diesem Szenario können wir mit dem Namen auf diese Methode verweisen :: Methodenreferenzoperator. Sie erstellen diese Art von Methodenverweis im folgenden Format:

Objekt / Klasse / Typ :: Methodenname

In unserem Beispiel für eine Floating-Aktionsschaltfläche können wir eine Methodenreferenz als Körper unseres Lambda-Ausdrucks verwenden:

 FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (this :: handleViewClick); 

Beachten Sie, dass die referenzierte Methode die gleichen Parameter wie die Schnittstelle annehmen muss. In diesem Fall heißt das Aussicht.

Sie können den Methodenreferenzoperator verwenden (::) um auf eines der folgenden Elemente zu verweisen:

Eine statische Methode

Wenn Sie einen Lambda-Ausdruck haben, der eine statische Methode aufruft:

(args) -> Class.staticMethod (args)

Dann können Sie daraus eine Methodenreferenz machen:

Klasse :: staticMethodName

Zum Beispiel, wenn Sie eine statische Methode hatten PrintMessage in einem Meine Klasse Klasse, dann würde Ihre Methodenreferenz so aussehen:

public class myClass public static void PrintMessage () System.out.println ("Dies ist eine statische Methode");  public static void main (String [] args) Thread Thread = Neuer Thread (myClass :: PrintMessage); thread.start ();  

Eine Instanzmethode eines bestimmten Objekts

Dies ist eine Instanzmethode eines Objekts, das vorab bekannt ist. Sie können Folgendes ersetzen:

(Argumente) -> enthaltendesObjekt.instanceMethodName (Argumente)

Mit:

containObject :: instanceMethodName

Wenn Sie also den folgenden Lambda-Ausdruck hatten:

MyClass.printNames (Namen, x -> System.out.println (x));

Wenn Sie dann eine Methodenreferenz einführen, erhalten Sie Folgendes:

MyClass.printNames (Namen, System.out :: println);

Eine Instanzmethode eines beliebigen Objekts eines bestimmten Typs

Dies ist eine Instanzmethode für ein beliebiges Objekt, das später bereitgestellt und im folgenden Format geschrieben wird:

ContainingType :: methodName

Konstruktorreferenzen

Konstruktorreferenzen ähneln Methodenreferenzen, außer dass Sie das Schlüsselwort verwenden Neu den Konstruktor aufrufen. Zum Beispiel, Button :: neu ist eine Konstruktorreferenz für die Klasse Taste, Der genaue Konstruktor, der aufgerufen wird, hängt jedoch vom Kontext ab.

Mit Hilfe von Konstruktorreferenzen können Sie:

(Argumente) -> neuer Klassenname (Argumente)

In:

Klassenname :: neu

Zum Beispiel, wenn Sie Folgendes hätten MeinInterface Schnittstelle:

 public Interface myInterface public abstract Student getStudent (Stringname, Integer-Alter); 

Dann könnten Sie Konstruktorreferenzen zum Erstellen neuer verwenden Student Instanzen:

myInterface stu1 = Student :: new; Student stu = stu1.getStudent ("John Doe", 27);

Es ist auch möglich, Konstruktorreferenzen für Array-Typen zu erstellen. Beispielsweise eine Konstruktorreferenz für ein Array von ints ist int [] :: neu.

Fügen Sie Ihren Schnittstellen Standardmethoden hinzu

Vor Java 8 konnten Sie nur abstrakte Methoden in Ihre Schnittstellen einschließen (d. H. Methoden ohne Rumpf), wodurch die Entwicklung von Schnittstellen nach der Veröffentlichung erschwert wurde.

Jedes Mal, wenn Sie einer Schnittstellendefinition eine Methode hinzugefügt haben, fehlt bei allen Klassen, die diese Schnittstelle implementiert haben, plötzlich eine Implementierung. Wenn Sie beispielsweise eine Schnittstelle hatten (MeinInterface) das wurde von verwendet Meine Klasse, dann fügen Sie eine Methode zu MeinInterface würde die Kompatibilität mit brechen Meine Klasse.

Im besten Fall waren Sie für die geringe Anzahl der verwendeten Klassen verantwortlich MeinInterface, Dieses Verhalten wäre ärgerlich, aber überschaubar - Sie müssen lediglich etwas Zeit einplanen, um Ihre Klassen mit der neuen Implementierung zu aktualisieren. Es könnten jedoch Dinge werden viel komplizierter, wenn eine große Anzahl von Klassen implementiert wird MeinInterface, oder wenn die Schnittstelle in Klassen verwendet wurde, für die Sie nicht verantwortlich waren.

Zwar gab es eine Reihe von Problemumgehungen für dieses Problem, aber keine davon war ideal. Sie könnten beispielsweise neue Methoden in eine abstrakte Klasse einschließen, dies erfordert jedoch weiterhin, dass jeder seinen Code aktualisiert, um diese abstrakte Klasse zu erweitern. und während du könnte Erweitern Sie die ursprüngliche Benutzeroberfläche mit einer neuen Benutzeroberfläche. Jeder, der diese neuen Methoden verwenden wollte, muss dann umschreiben alles ihre vorhandenen Schnittstellenreferenzen.

Mit der Einführung von Standardmethoden in Java 8 ist es jetzt möglich, nicht abstrakte Methoden (d. H. Methoden mit Rumpf) in Ihren Schnittstellen zu deklarieren, sodass Sie schließlich Standardimplementierungen für Ihre Methoden erstellen können.

Wenn Sie Ihrer Schnittstelle eine Methode als Standardmethode hinzufügen, muss jede Klasse, die diese Schnittstelle implementiert, nicht notwendigerweise eine eigene Implementierung bereitstellen. Auf diese Weise können Sie Ihre Schnittstellen aktualisieren, ohne die Kompatibilität zu beeinträchtigen. Wenn Sie einer Schnittstelle eine neue Methode als hinzufügen Standardmethode, Jede Klasse, die diese Schnittstelle verwendet, jedoch keine eigene Implementierung bereitstellt, erbt einfach die Standardimplementierung der Methode. Da der Klasse keine Implementierung fehlt, funktioniert sie weiterhin normal.

Tatsächlich war die Einführung von Standardmethoden der Grund dafür, dass Oracle so viele Ergänzungen zur Collections-API in Java 8 vorgenommen hat.

Sammlung ist eine generische Schnittstelle, die in vielen verschiedenen Klassen verwendet wird. Das Hinzufügen neuer Methoden zu dieser Schnittstelle hatte das Potenzial, unzählige Codezeilen zu brechen. Anstatt neue Methoden zum. Hinzuzufügen Sammlung Wenn die Schnittstelle alle Klassen abbricht, die von dieser Schnittstelle abgeleitet wurden, hat Oracle die Standardmethodenfunktion erstellt und diese neuen Methoden als Standardmethoden hinzugefügt. Wenn Sie einen Blick auf die neue Collection.Stream () -Methode werfen (die wir in Teil drei ausführlich untersuchen werden), werden Sie feststellen, dass sie als Standardmethode hinzugefügt wurde:

 Standard-Stream stream () return StreamSupport.stream (spliterator (), false); 

Das Erstellen einer Standardmethode ist einfach - fügen Sie einfach das hinzu Standard Modifikator für Ihre Methodensignatur:

öffentliche Schnittstelle MyInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Dies ist eine Standardmethode");

Wenn ja Meine Klasse Verwendet MeinInterface bietet aber keine eigene Implementierung von defaultMethod, es erbt nur die Standardimplementierung, die von bereitgestellt wird MeinInterface. Zum Beispiel wird die folgende Klasse noch kompiliert:

public class MyClass erweitert AppCompatActivity implementiert MyInterface 

Eine implementierende Klasse kann die von der Schnittstelle bereitgestellte Standardimplementierung überschreiben, sodass Klassen immer noch die vollständige Kontrolle über ihre Implementierungen haben.

Standardmethoden sind zwar eine willkommene Ergänzung für API-Designer, sie können jedoch gelegentlich ein Problem für Entwickler verursachen, die versuchen, mehrere Schnittstellen in derselben Klasse zu verwenden. 

Stellen Sie sich das zusätzlich vor MeinInterface, Sie haben folgendes:

öffentliche Schnittstelle SecondInterface void interfaceMethod (); default void defaultMethod () Log.i (TAG, "Dies ist auch eine Standardmethode");

Beide MeinInterface und SecondInterface enthalten eine Standardmethode mit genau derselben Signatur (defaultMethod). Stellen Sie sich nun vor, Sie versuchen beide Schnittstellen in derselben Klasse zu verwenden:

public class MyClass erweitert AppCompatActivity implementiert MyInterface, SecondInterface 

An diesem Punkt haben Sie zwei widersprüchliche Implementierungen von defaultMethod, Der Compiler hat keine Ahnung, welche Methode er verwenden soll. Daher wird ein Compiler-Fehler angezeigt.

Eine Möglichkeit, dieses Problem zu beheben, besteht darin, die in Konflikt stehende Methode mit Ihrer eigenen Implementierung zu überschreiben:

public class MyClass erweitert AppCompatActivity implementiert MyInterface, SecondInterface public void defaultMethod () 

Die andere Lösung besteht darin, welche Version von anzugeben defaultMethod Sie möchten mit folgendem Format implementieren:

.Super.();

Also wenn du das anrufen wolltest MyInterface # defaultMethod () Implementierung, dann würden Sie Folgendes verwenden:

public class MyClass erweitert AppCompatActivity implementiert MyInterface, SecondInterface public void defaultMethod () MyInterface.super.defaultMethod (); 

Statische Methoden in Ihren Java 8-Schnittstellen verwenden

Ähnlich wie bei Standardmethoden bieten statische Schnittstellenmethoden die Möglichkeit, Methoden innerhalb einer Schnittstelle zu definieren. Im Gegensatz zu Standardmethoden kann eine implementierende Klasse eine Schnittstelle jedoch nicht überschreiben statisch Methoden.

Wenn Sie über statische Methoden verfügen, die für eine Schnittstelle spezifisch sind, bieten Ihnen die statischen Schnittstellenmethoden von Java 8 eine Möglichkeit, diese Methoden in der entsprechenden Schnittstelle zu platzieren, anstatt sie in einer separaten Klasse speichern zu müssen.

Sie erstellen eine statische Methode, indem Sie das Schlüsselwort platzieren statisch am Anfang der Methodensignatur, zum Beispiel:

public interface MyInterface static void staticMethod () System.out.println ("Dies ist eine statische Methode"); 

Wenn Sie eine Schnittstelle implementieren, die eine statische Schnittstellenmethode enthält, ist diese statische Methode immer noch Teil der Schnittstelle und wird nicht von der implementierenden Klasse geerbt. Daher müssen Sie der Methode den Namen der Schnittstelle voranstellen. Beispiel:

public class MyClass erweitert AppCompatActivity implementiert MyInterface public static void main (String [] args) MyInterface.staticMethod ();… 

Dies bedeutet auch, dass eine Klasse und eine Schnittstelle eine statische Methode mit derselben Signatur haben können. Zum Beispiel mit MyClass.staticMethod und MyInterface.staticMethod in derselben Klasse wird kein Fehler bei der Kompilierung verursacht.

Sind Schnittstellen also im Wesentlichen nur abstrakte Klassen?

Das Hinzufügen von statischen Schnittstellenmethoden und Standardmethoden hat einige Entwickler dazu veranlasst zu hinterfragen, ob Java-Schnittstellen eher abstrakten Klassen ähneln. Trotz der Hinzufügung von Standard- und statischen Schnittstellenmethoden gibt es jedoch noch einige bemerkenswerte Unterschiede zwischen Schnittstellen und abstrakten Klassen:

  • Abstrakte Klassen können endgültige, nicht endgültige, statische und nicht statische Variablen haben, wohingegen eine Schnittstelle nur statische und endgültige Variablen haben kann.
  • Mit abstrakten Klassen können Sie Felder deklarieren, die nicht statisch und endgültig sind, während die Felder eines Interfaces von Natur aus statisch und endgültig sind.
  • In Schnittstellen sind alle Methoden, die Sie deklarieren oder als Standardmethoden definieren, grundsätzlich öffentlich, während Sie in abstrakten Klassen öffentliche, geschützte und private konkrete Methoden definieren können.
  • Abstrakte Klassen sind Klassen, und kann daher Zustand haben; Schnittstellen können keinen Status zugeordnet sein.
  • Sie können Konstruktoren innerhalb einer abstrakten Klasse definieren. Dies ist in Java-Schnittstellen nicht möglich.
  • Mit Java können Sie nur eine Klasse erweitern (unabhängig davon, ob sie abstrakt ist), aber Sie können beliebig viele Schnittstellen implementieren. Dies bedeutet, dass Schnittstellen in der Regel einen Vorteil haben, wenn Sie mehrere Vererbungen benötigen, obwohl Sie sich vor dem tödlichen Diamanten des Todes hüten müssen!

Wenden Sie dieselbe Anmerkung so oft an, wie Sie möchten

Traditionell besteht eine der Einschränkungen von Java-Annotationen darin, dass Sie dieselbe Annotation nicht mehrmals an derselben Stelle anwenden können. Verwenden Sie dieselbe Annotation mehrmals, und es wird ein Fehler bei der Kompilierung auftreten.

Mit der Einführung der wiederholten Anmerkungen von Java 8 können Sie dieselbe Anmerkung jedoch beliebig oft an derselben Stelle verwenden.

Um sicherzustellen, dass Ihr Code mit früheren Java-Versionen kompatibel bleibt, müssen Sie Ihre sich wiederholenden Anmerkungen in einer Containernotation speichern.

Sie können den Compiler anweisen, diesen Container zu generieren, indem Sie die folgenden Schritte ausführen:

  • Markieren Sie die betreffende Anmerkung mit @Wiederholbar Meta-Annotation (eine Anmerkung, die zur Annotation einer Anmerkung verwendet wird). Zum Beispiel, wenn Sie das machen wollten @Machen Anmerkung wiederholbar, würden Sie verwenden: @Repeatable (ToDos.class). Der Wert in Klammern ist der Typ der Containerkommentare, die der Compiler eventuell generiert.
  • Deklarieren Sie den enthaltenen Annotationstyp. Dieses Attribut muss ein Attribut haben, das ein Array des sich wiederholenden Annotationstyps ist. Beispiel:
public @interface ToDos ToDo [] value (); 

Wenn Sie versuchen, dieselbe Annotation mehrmals anzuwenden, ohne vorher zu erklären, dass sie wiederholbar ist, wird dies zur Kompilierzeit zu einem Fehler führen. Wenn Sie jedoch angegeben haben, dass es sich um eine wiederholbare Anmerkung handelt, können Sie diese Anmerkung an jedem Ort, an dem Sie eine Standardanmerkung verwenden, mehrmals verwenden.

Fazit

In diesem zweiten Teil unserer Serie zu Java 8 haben wir gesehen, wie Sie noch mehr Boilerplate-Code aus Ihren Android-Projekten schneiden können, indem Sie Lambda-Ausdrücke mit Methodenreferenzen kombinieren, und wie Sie Ihre Schnittstellen mit Standard- und statischen Methoden verbessern.

In der dritten und letzten Ausgabe werden wir uns eine neue Java 8-API ansehen, mit der Sie große Datenmengen effizienter und deklarativer verarbeiten können, ohne Sorgen um Parallelität und Thread-Management. Wir werden auch einige der verschiedenen Features zusammenfassen, die wir in dieser Serie besprochen haben, indem wir die Rolle untersuchen, die funktionale Schnittstellen in Lambda-Ausdrücken, statischen Schnittstellenmethoden, Standardmethoden und mehr spielen müssen.

Auch wenn wir immer noch darauf warten, dass die neue Datums- und Zeit-API von Java 8 offiziell auf Android ankommt, werde ich Ihnen zeigen, wie Sie diese neue API heute in Ihren Android-Projekten mit Hilfe von Drittanbietern einsetzen können Bibliotheken.

In der Zwischenzeit finden Sie einige unserer anderen Beiträge zur Entwicklung von Java- und Android-Apps!