Aufrufe nicht-statischer Ausdruckmethoden
Motivation Mindestens 80 Prozent aller Methoden der Standardbibliothek sind nicht-statische Methoden. Man kann Java also nur richtig nutzen, wenn man sich auch mit nicht-statischen Methoden auskennt.
Aufrufe nicht-statischer Methoden sind das Thema dieser Lektion.
»java.lang.System.out« als Feld [d]
Statische Felder, wie »java.lang.Math.PI«, wurden bereits behandelt.
- Dokumentation von »java.lang.Math.PI« (gekürzt, vereinfacht und übersetzt)
- Modul java.base
- Packet java.lang
- Klasse Math
- Feldzusammenfassung
static double PI
- Die Kreiszahl ‹ π ›.
Auch »java.lang.System.out« bezeichnet ein statisches Feld, nämlich ein statisches Feld im Typ »java.lang.System«. Während der Typ von »java.lang.Math.PI« jedoch ein elementarer Typ ist, ist der Typ von »java.lang.System.out« ein Referenztyp.
Um die Dokumentation zu »java.lang.System.out.println( "Hallo, Welt" )« zu finden, müssen wir zuerst den Typ des Kontexts ermitteln. Der Kontext ist »java.lang.System.out«. Dieser Kontext ist ein Feld namens »out« aus dem Referenztyp »java.lang.System«. Den Typ dieses Feldes finden wir also in der Dokumentation des Typs »java.lang.System«.
- »java.lang.System.out« (überarbeitet und übersetzt)
java.lang.System.out
static java.io.PrintStream out
- Der Standardausgabestrom.
Das Feld »out« ist ein Teil des Typs »java.lang.System«, aber sein Typ ist nicht »java.lang.System«, sondern »java.io.PrintStream«. Das Feld »java.lang.System.out« hat also eine direkte Beziehung zu zwei Typen : Es ist im Typ »java.lang.System« enthalten, aber seine Methoden sind in der Dokumentation des Typs »java.io.PrintStream« dokumentiert.
Der Dokumentation können wir nun entnehmen, daß der Typ des Feldes »java.lang.System.out« gleich »java.io.PrintStream« ist. Die Dokumentation der mit diesem Kontext aufgerufenen Methode finden wir also in der Dokumentation des Typs »java.io.PrintStream«.
Das folgende Programm zeigt, wie der Typ eines Ausdrucks auch mit einem Java -Programm ermittelt werden kann. Bei vielen Java -Implementation erscheint der Typ des Ausdrucks »java.lang.System.out« innerhalb einer vom Compiler ausgegebenen Fehlermeldung.
Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.error(); }}transcript
Main.java:3: error: cannot find symbol
{ java.lang.System.out.error(); }}
^
symbol: method error()
location: variable out of type PrintStream
1 error- Aussprachehinweis
- symbol ˈsɪmbəl
Man kann den Wert dieses Feldes auch ausgeben, doch ist die Ausgabe nicht besonders verständlich oder informativ.
Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( java.lang.System.out ); }}java.lang.System.out
java.io.PrintStream@1e30857
Erkennen nicht-statischer Methoden in der Dokumentation [d]
Eine nicht-statische Methode erkennt man daran, daß in der Proklamation vor ihrem Namen nicht »static« steht.
Als Beispiel einer Dokumentation einer nicht-statischen Methode zeigen wir hier die Kurzdokumentation der nicht-statischen Methode »println()« im Typ »java.io.PrintStream«.
- Die Dokumentation der nicht-statischen Methode »println()« im Typ »java.io.PrintStream« (Java 10 )
void println()
- Terminates the current line by writing the line separator string.
Ein Beispiel eines Aufrufs einer nicht-statischen Methode
Ein Ausdruck als Kontext erlaubt es jedoch zusätzlich auch nicht-statische Methoden seines Typs aufzurufen.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( "7".valueOf( 3 ));
java.lang.System.out.println( "abcd".indexOf( "c" )); }}Protokoll
3
2
Der Aufruf einer nicht-statischen Methode besteht, wie Aufrufe statischer Methoden (wenn es sich nicht um Verbaufrufe handelt), aus einem Kontext, einem Punkt, und einem Verbaufruf.
(Der Kontext und der Verbaufruf in dem folgenden Syntaxdiagramm gibt jeweils ein Beispiel wieder, aber ist nicht als eine allgemeine Definition von „Kontext“ und „Verbaufruf“ zu verstehen.)
- Analyse des Aufrufs »"abcd".indexOf( "c" )« ::=
Ausdruck
.---------. .-. .------------.
--->| Kontext |--->( . )--->| Verbaufruf |--->
'---------' '-' '------------'Kontext
.------.
--->( "abcd" )--->
'------'Verbaufruf
.-------. .-. .---. .-.
--->( indexOf )--->( ( )--->( "c" )--->( ) )--->
'-------' '-' '---' '-'
Die nicht-statische Methode »indexOf(java.lang.String)« ergibt die Position ihres Arguments im Kontext, hier also die Position des »a« in der Zeichenfolge »abcd«. Das Ergebnis lautet »0« und nicht »1«, weil das Zählen in diesem Falle mit der Null begonnen wird.
Die objektorientierte Programmierung ist durch die Verwendung nicht-statischer Methoden gekennzeichnet.
Eine nicht-statische Methode kann nicht mit einem Typ als Kontext aufgerufen werden.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( java.lang.System.getProperties() );
java.lang.System.out.println( java.lang.String.valueOf( 3 ));
java.lang.System.out.println( java.lang.String.indexOf( "c" )); }}Protokoll
Main.java:4: error: non-static method indexOf(String) cannot be referenced from a static context
java.lang.System.out.println( java.lang.String.indexOf( "c" )); }}
^Protokoll (übersetzt)
Main.java:4: Fehler: nicht-statische Methode indexOf(String) kann nicht über statischen Kontext referenziert werden
java.lang.System.out.println( java.lang.String.indexOf( "c" )); }}
^
Man beachte, daß die originale Fehlermeldung des Java -Compilers (aus dem JDK9) genau die Begriffe verwendet, die wir auch in diesem Kurs verwenden: »non-static method« und »static context«.
Ein weiteres Beispiel eines Aufrufs einer nicht-statischen Methode
In einem Aufruf wie »java.lang.System.out.println()« gehört das Verb »println« nicht zu einer statischen Methode. Es handelt sich vielmehr um eine nicht-statische Methode.
- Analyse des Aufrufs »java.lang.System.out.println()« ::=
Ausdruck
.---------. .-. .------------.
--->| Kontext |--->( . )--->| Verbaufruf |--->
'---------' '-' '------------'Kontext
.----. .-. .----. .-. .------. .-. .---.
--->( java )--->( . )--->( lang )--->( . )--->( System )--->( . )--->( out )--->
'----' '-' '----' '-' '------' '-' '---'Verbaufruf
.-------. .-. .-.
--->( println )--->( ( )--->( ) )--->
'-------' '-' '-'- ? Beispiel
java.lang.System.out . println()
In »java.lang.System.out.println()« wird die Methode »println()« des Ausdrucks »java.lang.System.out« aufgerufen.
Der Kontext des Verbaufrufs »println()« ist hier kein Referenztypname (wie bisher), sondern der Ausdruck »java.lang.System.out«.
»java.lang.System.out« ist der Name eines Feldes genauso wie »java.lang.Math.PI« – nur ist der Typ von »java.lang.Math.PI« der elementare Typ »double«, während der Typ von »java.lang.System.out« der Referenztyp »java.io.PrintStream« ist.
- Dokumentation von »java.lang.Math.PI« (gekürzt, vereinfacht und übersetzt)
- Modul java.base
- Packet java.lang
- Klasse Math
- Feldzusammenfassung
static double PI
- Die Kreiszahl ‹ π ›.
- Der Name »java.lang.Math.PI« steht zu zwei Typen in einer engen Beziehung
enthaltender Typ Typ
java.lang.Math <------------------------ java.lang.Math.PI -------------------------> double- »java.lang.System.out« (überarbeitet und übersetzt)
java.lang.System.out
static java.io.PrintStream out
Der Standardausgabestrom.
- Der Name »java.lang.System.out« steht zu zwei Typen in einer engen Beziehung
enthaltender Typ Typ
java.lang.System <------------------------- java.lang.System.out -------------------------> java.io.PrintStream
(Zwar findet sich das Symbol »Name« in unserem Syntaxdiagramm für »Ausdruck«, doch nicht jeder Name ist ein Ausdruck: Namen von Paketen, Typen und Methoden sind keine Ausdrücke.)
Der Typ von »java.lang.Math.PI« ist nicht »java.lang.Math«, und genauso ist der Typ von »java.lang.System.out« auch nicht »java.lang.System«.
»java.lang.Math.PI« ist kein Typname, obwohl es mit dem Typnamen »java.lang.Math« beginnt, sondern der Name eines Feldes. »java.lang.Math.PI« ist ein Ausdruck. Typen und Ausdrücke sind streng getrennt: ein Ausdruck ist nie ein Typ und ein Typ ist nie ein Ausdruck.
Daß »java.lang.System.out« kein Name eines Referenztyps, sondern eine Name eines Feldes ist, kann der Dokumentation entnommen werden (es folgt nicht aus der Java -Syntax alleine).
Der Kontext eines Aufrufs einer nicht-statischen Methode ist ein Ausdruck, wie beispielsweise »java.lang.System.out«.
Der Verbaufruf »println()« hat nicht den Typ »java.lang.System« als Kontext, denn zwischen beidem steht ja noch ».out.«!
Daß »java.lang.System.out.println« kein Name einer Methode ist, sondern eine Kombination aus dem Namen eines Feldes, einem Punkt und einem Verb, kann der Dokumentation entnommen werden (es folgt nicht aus der Java -Syntax alleine): Der Name eines Feldes kann (anders als der Name einer Referenztyps) nicht durch einen nachgestellten Namen zu einem neuen Namen erweitert werden. Ein nachgestelltes ».println« kann diesen Namen deswegen nicht zu einem noch längeren Namen erweitern. Im Ausdruck »java.lang.System.out.println()« ist »java.lang.System.out.println« also kein Name! Der Ausdruck muß vielmehr zerlegt werden in den Namen »java.lang.System.out« und den Verbaufruf »println()«. Der Feldname ist darin der Kontext für den Verbaufruf. Die Groß- und Kleinschreibung liefert allerdings schon ohne das Lesen der Dokumentation ein Indiz dafür, daß »java.lang.System« ein Name eines Referenztyps und »java.lang.System.out« kein Name eines Referenztyps ist.
Solch einen Ausdruck als Kontext (einen Kontextausdruck ) nennt man auch einen nicht-statischen Kontext , weil sein Wert ja erst zur Laufzeit festgelegt wird, er ist also nicht statisch (also nicht schon durch den Quelltext festgelegt).
Der Typ solch eines Kontextausdrucks muß ein sogenannter Referenztyp (wie beispielsweise eine Klasse oder eine Schnittstelle) sein. Ein Referenztyp kann daran erkannt werden, daß sein eigentlicher Name groß geschrieben wird.
Einen Ausdruck, dessen Typ ein Referenztyp ist, nennen wir auch einen Referenzausdruck.
Der Aufruf »2.println()« ist beispielsweise nicht erlaubt, weil der Typ des Kontextausdrucks »2« kein Referenztyp ist. Vergleichsweise ist aber der Aufruf »"".valueOf( 3 )« gestattet, weil der Typ des Kontextausdrucks »""« ein Referenztyp ist.
- Tabelle
Aufruf ------------------------------------. Beschreibung --------------------------------------------------------.
| |
Kontext Punkt Verbaufruf Kontextgattung Typ des Kontext Kontextart Methodenart java.lang.Thread . dumpStack() Referenztypname - statisch statisch
java.lang.Math . random() Referenztypname - statisch statisch
java.lang.Math . floor( 2.7 ) Referenztypname - statisch statisch
java.lang.String . valueOf( 3 ) Referenztypname - statisch statisch "" . valueOf( 3 ) Ausdruck java.lang.String nicht-statisch statisch "abcd" . indexOf( "c" ) Ausdruck java.lang.String nicht-statisch nicht-statisch
java.lang.System.out . println() Ausdruck java.io.PrintStream nicht-statisch nicht-statisch
java.lang.System.out . println( 2 ) Ausdruck java.io.PrintStream nicht-statisch nicht-statisch
Während »java.lang.Thread.dumpStack« ein Methodenname ist, gilt »java.lang.System.out.println« nicht als Methodenname, da der letzte Punkt darin eine Form der Verbindung repräsentiert, die in Namen nicht erlaubt ist (denn vor ihm steht bereits ein maximal qualifizierter Name).
Bei Aufrufen statischer Methoden dient der Name eines Referenztyps (oder – selten – ein Ausdruck) als Kontext. Eine Referenztypname bildet einen statischen Kontext. Ein isoliert betrachteter Verbaufruf ist ein Aufruf ohne Kontext.
Methoden, die mit einem Referenztypnamen als Kontext aufgerufen werden können, wie die Methode »java.lang.Thread.dumpStack()«, werden auch als statische Methoden bezeichnet. Methoden, die mit einem Ausdruck als Kontext aufgerufen werden können, wie die Methode »java.lang.System.out.println()« sind dementsprechend nicht-statische Methoden.
Ein Referenztypname ist immer der Kontext eines statischen Aufrufs.
Ein Ausdruck ist der übliche Kontext eines nicht-statischen Aufrufs.
Allgemein kennzeichnet das Adjektiv „statisch“ bereits durch den Quelltext (bei der „Übersetzungszeit“) Festgelegtes oder Gehörendes (wie Referenztypen) und das Adjektiv „nicht-statisch“ oder „nichtstatisch“ (auch: „dynamisch“) erst bei der Programmausführung (bei der „Laufzeit“) Festgelegtes oder Gehörendes (wie Werte von Ausdrücken).
Daß ein nicht-statischer Kontext ein Ausdruck ist, kann man auch daran erkennen, daß man ihn – wie alle Ausdrücke – einklammern kann: »( java.lang.System.out ).println()«. Ein Referenztypname ist hingegen kein Ausdruck und kann nicht eingeklammert werden: »( java.lang.Thread ).dumpStack()« ist also nicht erlaubt. Genauso sind auch Methodennamen keine Ausdrücke: »( java.lang.Thread.dumpStack )()« ist also ebenfalls nicht erlaubt.
Feldnamen sind Ausdrücke, Typnamen und Methodennamen nicht.
Hier geben wir die obenstehende Regel noch einmal in einer kurzen Merkform an:
Referenztypname: statischer Kontext
Ausdruck: nicht-statischer Kontext
»out« ist ein statisches Feld, es steht in »java.lang.System.out« direkt hinter einem Typnamen. Obwohl das Feld »out« als Feld des Typs »java.lang.System« das Attribut „statisch “ hat, gilt ein Ausdruck für dieses Feld als ein nicht-statischer Kontext, weil Ausdrücke stets nicht-statische Kontexte sind.
»println« ist eine nicht-statische Methode, ihr Name steht in »java.lang.System.out.println« direkt hinter einem Ausdruck.
- Aufruf einer Methode, die übliche Dichotomie ::=
.-------------. .---. .-------------------------------------------.
--->| Referenztyp |--->( . )--->| Verbaufruf einer statischen Methode |--->
'-------------' '---' '-------------------------------------------'.-------------. .---. .-------------------------------------------.
--->| Ausdruck |--->( . )--->| Verbaufruf einer nicht-statischen Methode |--->
'-------------' '---' '-------------------------------------------'- technisch möglich (wie bei »"".valueOf( 3 )«) aber unüblich ::=
.-------------. .---. .-------------------------------------------.
--->| Ausdruck |--->( . )--->| Verbaufruf einer statischen Methode |--->
'-------------' '---' '-------------------------------------------'
Ausgabeanweisungen
Da wir im Grundkurs Aufrufe nicht-statischer Methoden noch nicht weiter behandeln wollten, wurden dort extra „Ausgabeanweisungen“ eingeführt. Nun haben wir gelernt, daß »java.lang.System.out.println()« ein Ausdruck ist. Damit können wir nun erkennen, daß die bisherigen „Ausgabeanweisungen“ in Wirklichkeit nichts weiter als Aufrufanweisungen sind. Deswegen ist es nun nicht mehr nötig weiterhin, von „Ausgabeanweisungen“ zu sprechen. Es spricht aber auch nichts dagegen, auch weiterhin gelegentlich eine Aufrufanweisung, die etwas ausgibt, als eine „Ausgabeanweisung“ zu bezeichnen.
Syntax
Das folgende Syntaxdiagramm für Ausdrücke wurde um die Syntax für Aufrufe nicht-statischer Methoden erweitert.
- Neue, erweiterte Syntax (vereinfacht)
Ausdruck
.----------.
---.----------->| Literal |---------------------------.---->
| '----------' |
| .----------. |
'----------->| Name |---------------------------'
| '----------' |
| .-. .----------. |
'--->( - )-->| Ausdruck |---------------------------'
| '-' '----------' |
| .-. .----------. |
'--->( + )-->| Ausdruck |---------------------------'
| '-' '----------' |
| .-. .----------. .-. |
'--->( ( )-->| Ausdruck |-->( ) )-------------------'
| '-' '----------' '-' |
| .----------. .-. .----------. |
'----------->| Ausdruck |-->( / )-->| Ausdruck |----'
| '----------' '-' '----------' |
| .----------. .-. .----------. |
'----------->| Ausdruck |-->( + )-->| Ausdruck |----'
| '----------' '-' '----------' |
| .----------. .-. .----------. |
'----------->| Ausdruck |-->( - )-->| Ausdruck |----'
| '----------' '-' '----------' |
| .----------. .-. .----------. |
'----------->| Ausdruck |-->( * )-->| Ausdruck |----'
| '----------' '-' '----------' |
| .---------------------------------. |
'----------->| Aufruf einer statischen Methode |----'
| '---------------------------------' |
| .---------------------------. |
'----------->| nicht-statischer Aufruf |----------'
'---------------------------'Name
.-----------------------.
| .------. .-. v .------------------------.
---'--->| Name |-->( . )---'--->| Bezeichnerzeichenfolge |--->
'------' '-' '------------------------'Ausdruckliste
.---------------------------.
| .----------. v
---'---.--->| Ausdruck |---.---'--->
^ '----------' |
| .-. |
'-------( , )<------'
'-'Aufruf einer statischen Methode
.------. .-. .---------------. .-.
--->| Name |--->( ( )--->| Ausdruckliste |--->( ) )--->
'------' '-' '---------------' '-'nicht-statischer Aufruf
.-----------. .-. .------------. .-. .---------------. .-.
--->| Ausdruck |--->( . )--->| Bezeichner |--->( ( )--->| Ausdruckliste |--->( ) )--->
'-----------' '-' '------------' '-' '---------------' '-'- Nicht-syntaktische Anforderungen
- Der »Name« in der Produktionsregel »Ausdruck« muß ein Name einer Variablen sein (Namen von Typen oder Methoden sind keine Ausdrücke).
- Der »Name« in der Produktionsregel »Aufruf einer statischen Methode« muß ein Name einer Methode sein.
Übungsfragen
? Kontext
Würde es sich bei den folgenden Texten, wenn man sie als Kontext verwenden würde, jeweils um einen statischen oder um einen nicht-statischen Kontext handeln?
- »java.lang.Math«
- »java.lang.System.out«
? Kontext (1)
Warum würde der folgende Aufruf, selbst wenn er erlaubt wäre, keinen Sinn ergeben?
- Aufruf
java.lang.String.indexOf( "c" )
Anmerkungen und Zitate *
Syntax eines Aufrufs einer statischen Methode *
Zitate aus der JLS, leicht umgestellt und vereinfacht:
- Aufruf einer statischen Methode
- 〈MethodInvocation 〉 ::=
- 〈TypeName 〉 "." 〈Identifier 〉 "(" [〈ArgumentList 〉] ")".
- Aufruf einer nicht-statischen Methode
- 〈MethodInvocation 〉 ::=
- 〈Expression 〉 "." 〈Identifier 〉 "(" [〈ArgumentList 〉] ")".