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.
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:
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 ();
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);
Dies ist eine Instanzmethode für ein beliebiges Objekt, das später bereitgestellt und im folgenden Format geschrieben wird:
ContainingType :: methodName
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 int
s ist int [] :: neu
.
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-Streamstream () 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 ();
Ä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.
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:
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:
@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.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.
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!