Erweiterungsfunktionen (extension functions) sind eines der wichtigsten Unterschiede zwischen Kotlin und Java, die erstmals mit den ersten Versionen der Sprache unter dem Einfluss von C# aufkamen. Sie ermöglichen es, neue Methoden zu bestehenden Klassen hinzuzufügen, ohne die ursprüngliche Klasse erben oder ändern zu müssen. Dies erhöht die Ausdruckskraft und Prägnanz des Codes erheblich.
In Java und vielen anderen Sprachen ist es nicht möglich, Methoden zu einer bereits existierenden Klasse (z. B. String oder List) hinzuzufügen, ohne Vererbung oder das Decorator-Muster zu verwenden. Kotlin behebt diese Einschränkung durch den Mechanismus der Erweiterungsfunktionen.
Viele Klassen der Bibliothek (z. B. String, Int, List) können nicht direkt geändert werden. Oft sind zusätzliche Methoden erforderlich, um den Code lesbarer zu machen und die Logikduplizierung zu vermeiden. Erweiterungsfunktionen ändern jedoch die echte Klasse nicht - sie werden statisch erstellt und haben keinen Zugriff auf private/protected Mitglieder.
In Kotlin sieht die Deklaration einer Erweiterungsfunktion folgendermaßen aus:
fun String.lastChar(): Char = this[this.length - 1]
Jetzt kann man diese Methode auf jedem String aufrufen:
"Kotlin".lastChar() // gibt 'n' zurück
Kann eine Erweiterungsfunktion im Erben der Klasse überschrieben werden?
Nein. Erweiterungsfunktionen sind keine Mitglieder der Klasse, sie können nicht durch override überschrieben werden: Ihre Statischheit zeigt sich sogar für Nachfolger - der Compiler wählt die Funktion basierend auf dem Typ des Ausdrucks und nicht auf dem tatsächlichen Typ des Objekts.
open class Base class Derived : Base() fun Base.foo() = "base" fun Derived.foo() = "derived" fun printFoo(b: Base) { println(b.foo()) } val d = Derived() printFoo(d) // gibt "base" aus
Können Erweiterungsfunktionen den Zustand eines Objekts ändern?
Erweiterungsfunktionen können nicht auf private/protected Zustände zugreifen, wenn der Typ jedoch veränderbar ist (z. B. List oder eine benutzerdefinierte Klasse), können sie den zugänglichen externen Zustand über öffentliche Methoden ändern.
fun MutableList<Int>.addTwice(elem: Int) { add(elem) add(elem) }
Konfliktieren Erweiterungsfunktionen mit Mitgliedern der Klasse mit demselben Namen?
Wenn der Name der Erweiterungsfunktion mit dem Namen einer echten Methode übereinstimmt, hat das Mitglied der Klasse Vorrang. Die Erweiterung wird nur aufgerufen, wenn die Mitglieder nicht vorhanden oder nicht sichtbar sind.
class Foo { fun bar() = "member" } fun Foo.bar() = "extension" val f = Foo() println(f.bar()) // "member"
Ein Entwickler erweitert den Standard-String mit einer Funktion, die ein projektspezifisches Format festlegt, und verwendet diese überall - während andere Entwickler nicht bemerken, dass dies keine Standardoperation der Klasse String ist.
Vorteile:
Nachteile:
Eine Erweiterung der Standardklasse mit einer Utility-Methode, die genau im Paket utils lokalisiert und gut dokumentiert ist. Die Nutzung ist auf klar definierte Abschnitte des Codes beschränkt.
Vorteile:
Nachteile: