Überladung

Sprachen ohne Unterstützung für Standardargumente verwenden oft Überladung, um dieses Merkmal zu imitieren.

Der Begriff Überladung bezieht sich auf den Namen einer Funktion: Sie verwenden denselben Namen (“überladen” diesen Namen) für verschiedene Funktionen, solange sich die Parameterlisten unterscheiden. Hier überladen wir die Memberfunktion f():

// Overloading/Overloading.kt
package overloading
import atomictest.eq

class Overloading {
  fun f() = 0
  fun f(n: Int) = n + 2
}

fun main() {
  val o = Overloading()
  o.f() eq 0
  o.f(11) eq 13
}

In Overloading sehen Sie zwei Funktionen mit demselben Namen, f(). Die Signatur einer Funktion besteht aus dem Namen, der Parameterliste und dem Rückgabetyp. Kotlin unterscheidet eine Funktion von einer anderen, indem es die Signaturen vergleicht. Beim Überladen von Funktionen müssen die Parameterlisten einzigartig sein—man kann nicht nur über die Rückgabetypen überladen.

Die Aufrufe zeigen, dass es sich tatsächlich um unterschiedliche Funktionen handelt. Eine Funktionssignatur beinhaltet auch Informationen über die umschließende Klasse (oder den Empfangstyp, wenn es sich um eine Erweiterungsfunktion handelt).

Wenn eine Klasse bereits eine Mitgliedsfunktion mit derselben Signatur wie eine Erweiterungsfunktion hat, bevorzugt Kotlin die Mitgliedsfunktion. Sie können jedoch die Mitgliedsfunktion mit einer Erweiterungsfunktion überladen:

// Overloading/MemberVsExtension.kt
package overloading
import atomictest.eq

class My {
  fun foo() = 0
}

fun My.foo() = 1             // [1]

fun My.foo(i: Int) = i + 2   // [2]

fun main() {
  My().foo() eq 0
  My().foo(1) eq 3
}
  • [1] Es ist sinnlos, eine Erweiterung zu deklarieren, die ein Mitglied dupliziert, da sie niemals aufgerufen werden kann.
  • [2] Sie können eine Mitgliedsfunktion mit einer Erweiterungsfunktion überladen, indem Sie eine andere Parameterliste bereitstellen.

Verwenden Sie das Überladen nicht, um Standardargumente zu imitieren. Das heißt, tun Sie dies nicht:

// Overloading/WithoutDefaultArguments.kt
package withoutdefaultarguments
import atomictest.eq

fun f(n: Int) = n + 373
fun f() = f(0)

fun main() {
  f() eq 373
}

Die Funktion ohne Parameter ruft einfach die erste Funktion auf. Die beiden Funktionen können durch eine einzelne Funktion ersetzt werden, indem ein Standardargument verwendet wird:

// Overloading/WithDefaultArguments.kt
package withdefaultarguments
import atomictest.eq

fun f(n: Int = 0) = n + 373

fun main() {
  f() eq 373
}

In beiden Beispielen können Sie die Funktion entweder ohne ein Argument oder durch Übergeben eines Ganzzahlwerts aufrufen. Bevorzugen Sie die Form in WithDefaultArguments.kt.

Bei der Verwendung von überladenen Funktionen zusammen mit Standardargumenten sucht der Aufruf der überladenen Funktion nach der “nächsten” Übereinstimmung. Im folgenden Beispiel ruft der foo()-Aufruf in main() nicht die erste Version der Funktion mit ihrem Standardargument von 99 auf, sondern stattdessen die zweite Version, die ohne Parameter:

// Overloading/OverloadedVsDefaultArg.kt
package overloadingvsdefaultargs
import atomictest.*

fun foo(n: Int = 99) = trace("foo-1-$n")

fun foo() {
  trace("foo-2")
  foo(14)
}

fun main() {
  foo()
  trace eq """
    foo-2
    foo-1-14
  """
}

Sie können das Standardargument 99 niemals nutzen, da foo() immer die zweite Version von f() aufruft.

Warum ist das Überladen nützlich? Es ermöglicht Ihnen, “Variationen eines Themas” klarer auszudrücken, als wenn Sie gezwungen wären, unterschiedliche Funktionsnamen zu verwenden. Angenommen, Sie möchten Additionsfunktionen:

// Overloading/OverloadingAdd.kt
package overloading
import atomictest.eq

fun addInt(i: Int, j: Int) = i + j
fun addDouble(i: Double, j: Double) = i + j

fun add(i: Int, j: Int) = i + j
fun add(i: Double, j: Double) = i + j

fun main() {
  addInt(5, 6) eq add(5, 6)
  addDouble(56.23, 44.77) eq
    add(56.23, 44.77)
}

addInt() nimmt zwei Ints und gibt ein Int zurück, während addDouble() zwei Doubles nimmt und ein Double zurückgibt. Ohne Überladen kann man die Operation nicht einfach add() nennen, daher kombinieren Programmierer typischerweise was mit wie, um eindeutige Namen zu erzeugen (man kann auch eindeutige Namen mit zufälligen Zeichen erstellen, aber das typische Muster ist die Verwendung von aussagekräftigen Informationen wie Parametertypen). Im Gegensatz dazu ist das überladene add() viel klarer.

  • -

Das Fehlen des Überladens in einer Sprache ist keine große Bürde, aber das Feature bietet wertvolle Vereinfachung, wodurch der Code lesbarer wird. Mit Überladung sagt man einfach was, was die Abstraktionsebene erhöht und die geistige Belastung für den Leser verringert. Wenn man wissen will wie, schaut man sich die Parameter an. Beachten Sie auch, dass Überladen Redundanz reduziert: Wenn wir addInt() und addDouble() sagen müssen, wiederholen wir im Wesentlichen die Parameterinformationen im Funktionsnamen.

Übungen und Lösungen finden Sie auf www.AtomicKotlin.com.