Java braucht ein "static"-Keyword
Der Titel sagt alles. Ich bin fest davon überzeugt, dass Java ein static
-Keyword braucht!
Ein geneigter Fan der Sprache könnte sich jetzt fragen, was ich damit meine,
da Java seit dem initialen Release der JDK 1.1 (1996)
ein static
-Keyword hat, jedoch meine ich ein anderes: Ich wünsche mir das static
aus php.
Was ist überhaupt “static”?
Wenn man über das handelsübliche static
redet, dann meint man in den meisten objektorientierten
Programmiersprachen Deklarationen, die nicht an einer konkreten Instanz eines Objektes hängen.
Ein Beispiel:
// Wir haben eine Klasse für einen Hund
public class Dog {
/** Ein Hund hat ein Alter. */
private int ageInYears;
/** Wenn ein Hund geboren wird, ist er 0 Jahre alt. */
public Dog() { this.ageInYears = 0; }
/** Diese Methode gibt das Alter zurück. (Getter) */
public int getAgeInYears() { return this.ageInYears; }
/** Diese Methode lässt den Hund um ein Jahr altern (Setter) */
public void birthday() { this.ageInYears++; }
}
Diese Klasse könnte man jetzt wie folgt nutzen:
public static void main(String[] args) {
// Der Hund Bernhard wird geboren.
Dog bernhard = new Dog();
// Bernhard hat Geburtstag 🥳
bernhard.birthday();
// Wir geben sein neues Alter auf der Konsole aus:
System.out.println("Bernhard ist " + bernhard.getAgeInYears() + " Jahr(e) alt.");
}
Man beachte an dieser Stelle, dass am Hund nichts static
war.
Ein konkreter Bernhard kann ein’ Geburtstag und ein Alter haben, schwieriger ist es jedoch das “Konzept Hund” zu fragen, wie alt es ist.
Bernhard ist in diesem Fall eine Instanz der Klasse Dog
. Wir können bernhard.birthday()
, aber nicht Dog.birthday()
schreiben. Dann gäb’s ‘nen dicken Compilerfehler; und zwar mit Recht.
Etwas, was hier jedoch static
sein könnte, wäre die maximale Lebenserwartung eines Hundes, da dies eine Information ist, die eher an dem Konzept, als an einem bestimmten Hund hängt, auch wenn es bestimmte Hunde geben kann, die tatsächlich so alt werden.
class Dog {
/** Jeder beliebige Hund könnte so alt werden. */
static int MAX_AGE_IN_YEARS = 31;
}
Diese Information könnte man jetzt sowohl mit bernhard.MAX_AGE_IN_YEARS
, als auch mit Dog.MAX_AGE_IN_YEARS
abrufen.
Übrigens:
Die Funktion1 main
ist auch deshalb static, weil sie an keiner Instanz hängt. Natürlich braucht man Bernhard nicht, um sein Java-Programm zu starten. Das muss es von alleine können.
Statische Vererbung
Wie man vielleicht schon in meinem letzten Post gemerkt hat, liebe ich Interfaces. Sie sind hauptsächlich dafür da, zu signalisieren, dass ein Objekt eine bestimmte Funktionalität bereitstellt, ohne die tatsächliche Implementierung zu verraten.
Zum Beispiel wäre das hier ein minimales Interface für einen Stack:
/** @param <E> ist der Datentyp von den Elementen auf dem Stapel. */
public interface Stack<E> {
/** Lege ein Element auf den Stapel. */
void push(E element);
/**
* Nimm das oberste Element wieder runter.
* @return {@code null} wenn der Stapel leer ist.
*/
E pop();
}
Ich würde mich jetzt allerdings freuen, wenn es auch noch eine Funktion gibt, mit der wir einen Stack erstellen können, welcher sich aus einer Reihe bereits bestehender Elemente zusammenbaut.
Aber, wie kriegen wir das hin?
Kleiner Exkurs: php
In php gibt es zwar leider noch keine Generics, wir können jedoch ein sehr ähnliches Interface schreiben:
interface Stack {
/** Lege ein Element auf den Stapel. */
abstract function push(mixed $element): void;
/**
* Nimm das oberste Element wieder runter.
* @return {@code null} wenn der Stapel leer ist.
*/
abstract function pop(): mixed;
}
Der entscheidende Vorteil ist jedoch, dass wir jetzt den gewünschten Konstruktor in Form einer Factory-Funktion zum Contract des Interfaces hinzufügen können:
interface Stack {
/** Jede Klasse die Stack implementiert, muss diese static-Factory bereitstellen. */
abstract static function from(mixed ...$elems): Stack;
}
Der Grund hierfür ist statische Vererbung, ein Feature, das ich absolut liebe, und für das ich bereit wäre, Geld zu bezahlen, um es eines fernen Tages in Java zu sehen. (Es wird sicher noch ein dedizierter Post hierzu folgen!)
In einer Subklasse kann das jetzt so aussehen:
class ArrayStack implements Stack {
private array $content;
public static function from(mixed ...$elems): Stack {
foreach($elems as $elem) $this->push($elem);
}
/* ... */
}
…und woanders so benutzt werden:
// $stack ist hier gerade nur irgendein Stack
$stack = ArrayStack::from('a', 'b', 'c');
Größerer Exkurs: Covariance
Sowohl in php, als auch in Java gibt es sog. “covariant return-types”. Klingt cool und kompliziert, ist aber recht einfach. Die Idee ist, dass der Rückgabetyp einer Funktion in der Subklasse immer spezifischer sein kann als der in der Oberklasse.
Noch ‘n Beispiel gefällig?
Unser Stack
ist zwar ein Interface, wir nennen es jetzt aber einfach mal Superklasse von ArrayStack
, weil hier Funktionalität vererbt wird. Erinnerst du dich noch an diese Zeile?
abstract static function from(mixed ...$elems): Stack;
from
in Stack
gibt irgendeinen Stack
zurück.
Weil ArrayStack
aber eine spezifische Implementierung (nennen wir es Subklasse) von Stack
ist, darf auch der Rückgabewert der überschriebenen Funktion spezifischer sein. Wir können also hier Folgendes schreiben…
abstract static function from(mixed ...$elems): ArrayStack; // <- Guck mal!
... es wird aber nicht erzwungen!
Static als Return-Type
Um in allen Implementierungen zu erzwingen, dass nicht nur irgendein Objekt der Superklasse zurückgegeben wird, sondern jede Klasse eine Instanz von sich selbst zurückgeben muss, kann man in php static
als Return-Type verwenden.
abstract static function from(mixed ...$elems): static; // <- Siehst du das?
… und genau das wünsche ich mir auch in Java!
Zugegeben, das Keyword sollte vielleicht doch nicht static
sein, sonst endet man mit Syntax wie:
public static static from(E... elems);
aber self
klingt doch auch ganz toll und ist zumindest in Java noch nicht reserviert.2
Was kann man denn jetzt alles damit machen?
Nun, wo wir das imaginäre JEP “Static inheritance and forced covariant types” (wat ‘nen cooler Name) implementiert haben, könnten wir zum Beispiel java.lang.Cloneable
bzw. Object#clone
überarbeiten:
public interface Cloneable {
/** Erstellt eine Kopie von genau diesem Objekt. */
public self clone();
}
Man könnte auch abstrakte Builder bauen:
interface Something {
/** Jede implementierende Klasse muss einen Builder bereitstellen. */
static SomeBuilder<self> builder();
}
abstract class SomeBuilder<E extends Something> {
SomeBuilder() { /* ... */ }
// Jede Build-Methode gibt immer genau sich selbst zurück
self a() { /* ... */ }
self b() { /* ... */ }
self c() { /* ... */ }
E build() { /* ... */ }
}
…und man könnte sicherstellen, dass an Objekte, auf die Object#equals
ausgeführt wird, auch nur Objekten derselben Klasse, und nicht einfach zufälliger Kram übergeben wird:
public boolean equals(self obj) {
return this == obj;
}
Fazit
Auch wenn der Titel von diesem Post am Ende Clickbait war, würde ich mich doch sehr freuen, wenn es das self
-Keyword in Java gäbe. Oder irgendein anderes Keyword, es ist eigentlich egal, Hauptsache die besprochenen Features schaffen es in die Sprache.
php ist meines Erachtens leider auch nicht perfekt, da es hier noch keine default-implementierungen in Interfaces gibt und Traits nicht als Datentypen gelten. Die Tatsache, dass jedoch beide Konzepte getrennt voneinander existieren, lässt mich jedoch hoffen, dass wir eines Tages in einer besseren Welt leben können, in der beides vereint wird.