Leitfaden zu Kotlin-Schnittstellen

1. Übersicht

In diesem Tutorial wird erläutert, wie Sie Schnittstellen in Kotlin definieren und implementieren.

Wir werden uns auch ansehen, wie mehrere Schnittstellen von einer Klasse implementiert werden können. Dies kann sicherlich zu Konflikten führen, und wir werden lernen, wie Kotlin sie lösen muss.

2. Schnittstellen in Kotlin

Eine Schnittstelle ist eine Möglichkeit, eine Beschreibung oder einen Vertrag für Klassen in der objektorientierten Programmierung bereitzustellen. Sie können je nach Programmiersprache abstrakte oder konkrete Eigenschaften und Funktionen enthalten. Wir werden die Details der Schnittstellen in Kotlin durchgehen.

Schnittstellen in Kotlin ähneln Schnittstellen in vielen anderen Sprachen wie Java. Sie haben jedoch eine bestimmte Syntax. Lassen Sie uns sie in den nächsten Unterabschnitten überprüfen.

2.1. Schnittstellen definieren

Beginnen wir mit der Definition unserer ersten Schnittstelle in Kotlin:

interface SimpleInterface

Dies ist die einfachste Schnittstelle, die vollständig leer ist. Diese werden auch als Marker-Schnittstellen bezeichnet .

Fügen wir nun einige Funktionen zu unserer Benutzeroberfläche hinzu:

interface SimpleInterface { fun firstMethod(): String fun secondMethod(): String { return("Hello, World!") } }

Wir haben unserer zuvor definierten Schnittstelle zwei Methoden hinzugefügt:

  • Eine davon, die als erste Methode bezeichnet wird, ist eine abstrakte Methode
  • Während die andere mit dem Namen s econdMethod eine Standardimplementierung hat.

Lassen Sie uns jetzt einige Eigenschaften zu unserer Benutzeroberfläche hinzufügen:

interface SimpleInterface { val firstProp: String val secondProp: String get() = "Second Property" fun firstMethod(): String fun secondMethod(): String { return("Hello, from: " + secondProp) } }

Hier haben wir unserer Schnittstelle zwei Eigenschaften hinzugefügt:

  • Einer von ihnen namens firstProp ist vom Typ String und abstrakt
  • Der zweite mit dem Namen secondProp ist ebenfalls vom Typ string, definiert jedoch eine Implementierung für seinen Accessor.

Beachten Sie, dass Eigenschaften in einer Schnittstelle den Status nicht beibehalten können . Das Folgende ist also ein illegaler Ausdruck in Kotlin:

interface SimpleInterface { val firstProp: String = "First Property" // Illegal declaration }

2.2. Schnittstellen implementieren

Nachdem wir eine grundlegende Schnittstelle definiert haben, wollen wir sehen, wie wir dies in einer Klasse in Kotlin implementieren können:

class SimpleClass: SimpleInterface { override val firstProp: String = "First Property" override fun firstMethod(): String { return("Hello, from: " + firstProp) } }

Beachten Sie, dass , wenn wir definieren Simple als Implementierung von SimpleInterface , wir nur die Implementierung für abstrakte Eigenschaften und Funktionen zur Verfügung stellen müssen . Wir können jedoch auch alle zuvor definierten Eigenschaften oder Funktionen überschreiben.

Überschreiben wir nun alle zuvor definierten Eigenschaften und Funktionen in unserer Klasse:

class SimpleClass: SimpleInterface { override val firstProp: String = "First Property" override val secondProp: String get() = "Second Property, Overridden!" override fun firstMethod(): String { return("Hello, from: " + firstProp) } override fun secondMethod(): String { return("Hello, from: " + secondProp + firstProp) } }

Hier haben wir die Eigenschaft secondProp und die Funktion secondFunction überschrieben, die zuvor in der Schnittstelle SimpleInterface definiert wurden .

2.3 Implementieren von Schnittstellen durch Delegierung

Die Delegierung ist ein Entwurfsmuster in der objektorientierten Programmierung , um die Wiederverwendbarkeit von Code durch Komposition anstelle von Vererbung zu erreichen . Während dies in vielen Sprachen wie Java implementiert werden kann, bietet Kotlin native Unterstützung für die Implementierung durch Delegierung .

Wenn wir mit einer grundlegenden Schnittstelle und Klasse beginnen:

interface MyInterface { fun someMethod(): String } class MyClass() : MyInterface { override fun someMethod(): String { return("Hello, World!") } }

Bisher nichts Neues. Jetzt können wir eine andere Klasse definieren, die MyInterface durch Delegierung implementiert :

class MyDerivedClass(myInterface: MyInterface) : MyInterface by myInterface

MyDerivedClass erwartet einen Delegaten als Argument, das die Schnittstelle MyInterface tatsächlich implementiert .

Mal sehen, wie wir eine Funktion der Schnittstelle durch Delegieren aufrufen können:

val myClass = MyClass() MyDerivedClass(myClass).someMethod()

Hier haben wir MyClass instanziiert und als Delegat verwendet, um Funktionen der Schnittstelle auf MyDerivedClass aufzurufen, die diese Funktionen eigentlich nie direkt implementiert haben.

3. Mehrfachvererbung

Mehrfachvererbung ist ein Schlüsselkonzept im objektorientierten Programmierparadigma. Auf diese Weise kann eine Klasse Merkmale von mehr als einem übergeordneten Objekt erben, z. B. von einer Schnittstelle .

Dies bietet zwar mehr Flexibilität bei der Objektmodellierung, bringt jedoch eine Reihe von Komplexitäten mit sich. Eines davon ist das „Diamantproblem“.

Java 8 verfügt über eigene Mechanismen zur Lösung des Diamantproblems, ebenso wie jede andere Sprache, die Mehrfachvererbung ermöglicht.

Mal sehen, wie Kotlin es über Schnittstellen anspricht.

3.1. Mehrere Schnittstellen erben

Zunächst definieren wir zwei einfache Schnittstellen:

interface FirstInterface { fun someMethod(): String fun anotherMethod(): String { return("Hello, from anotherMethod in FirstInterface") } } interface SecondInterface { fun someMethod(): String { return("Hello, from someMethod in SecondInterface") } fun anotherMethod(): String { return("Hello, from anotherMethod in SecondInterface") } }

Beachten Sie, dass beide Schnittstellen Methoden mit demselben Vertrag haben.

Definieren wir nun eine Klasse, die von diesen beiden Schnittstellen erbt:

class SomeClass: FirstInterface, SecondInterface { override fun someMethod(): String { return("Hello, from someMethod in SomeClass") } override fun anotherMethod(): String { return("Hello, from anotherMethod in SomeClass") } }

Wie wir sehen, Someclass Geräte sowohl FirstInterface und SecondInterface . Während dies syntaktisch recht einfach ist, gibt es ein bisschen Semantik, die hier Aufmerksamkeit erfordert. Wir werden dies im nächsten Unterabschnitt behandeln.

3.2. Konflikte lösen

When implementing multiple interfaces, a class may inherit a function which has a default implementation for the same contract in multiple interfaces. This raises the problem of invocation for this function from an instance of the implementing class.

To resolve this conflict, Kotlin requires the subclass to provide an overridden implementation for such functions to make the resolution explicit.

For example, SomeClass above implements anotherMethod. But, if it didn't, Kotlin wouldn't know whether to invoke First or SecondInterface's default implementation of anotherMethod. SomeClass must implement anotherMethod for this reason.

However, someMethod is a bit different since there is actually no conflict. FirstInterface doesn't provide a default implementation for someMethod. That said, SomeClass still must implement it because Kotlin forces us to implement all inherited functions, no matter if they are defined once or multiple times in parent interfaces.

3.3. Resolving the Diamond Problem

A “diamond problem” occurs when two child objects of a base object describe a particular behavior defined by the base object. Now an object inheriting from both these child objects has to resolve which inherited behavior it subscribes to.

Kotlin's solution to this problem is through the rules defined for multiple inheritance in the previous sub-section. Let's define a few interfaces and an implementing class to present the diamond problem:

interface BaseInterface { fun someMethod(): String } interface FirstChildInterface: BaseInterface { override fun someMethod(): String { return("Hello, from someMethod in FirstChildInterface") } } interface SecondChildInterface: BaseInterface { override fun someMethod(): String { return("Hello, from someMethod in SecondChildInterface") } } class ChildClass: FirstChildInterface, SecondChildInterface { override fun someMethod(): String { return super.someMethod() } }

Here we have defined BaseInterface which declared an abstract function called someMethod. Both the interfaces FirstChildInterface and SecondChildInterface inherits from BaseInterface and implement the function someMethod.

Now as we implement ChildClass inheriting from FirstChildInterface and SecondChildInterface, it's necessary for us to override the function someMethod. However, even though we must override the method, we can still simply call super as we do here with SecondChildInterface.

4. Interfaces Compared to Abstract Classes in Kotlin

Abstract classes in Kotlin are classes which cannot be instantiated. This may contain one or more properties and functions. These properties and functions can be abstract or concrete. Any class inheriting from an abstract class must implement all inherited abstract properties and functions unless that class itself is also declared as abstract.

4.1. Differences Between Interface and Abstract Class

Wait! Doesn't that sound exactly like what an interface does?

Actually, at the outset, an abstract class is not very different from the interface. But, there are subtle differences which govern the choice we make:

  • A class in Kotlin can implement as many interfaces as they like but it can only extend from one abstract class
  • Properties in the interface cannot maintain state, while they can in an abstract class

4.2. When Should We Use What?

An interface is just a blueprint for defining classes, they can optionally have some default implementations as well. On the other hand, an abstract class is an incomplete implementation which is completed by the extending classes.

Typically interfaces should be used to define the contract, which elicits the capabilities it promises to deliver. An implementing class holds the responsibility of delivering those promises. An abstract class, however, should be used to share partial characteristics with extending classes. An extending class can take it further to complete it.

5. Comparison With Java Interfaces

With the changes to Java interface in Java 8, they have come very close to Kotlin interfaces. One of our previous articles captures the new features introduced in Java 8 including changes to the interface.

There are mostly syntactic differences between Java and Kotlin interfaces now. One difference which stands out is related to the keyword “override”. In Kotlin, while implementing abstract properties or functions inherited from an interface, it is mandatory to qualify them with the keyword “override“. There is no such explicit requirement in Java.

6. Conclusion

In this tutorial, we discussed Kotlin interfaces, how to define and implement them. Then we talked about inheriting from multiple interfaces and the conflict they may create. We took a look at how Kotlin handles such conflicts.

Schließlich diskutierten wir Schnittstellen im Vergleich zu abstrakten Klassen in Kotlin. Wir haben auch kurz darüber gesprochen, wie sich die Kotlin-Schnittstelle mit der Java-Schnittstelle vergleichen lässt.

Wie immer ist der Code für die Beispiele auf GitHub verfügbar.