byte, short, int, long
)
und reellen (float, double
) Zahlen entsprechen.
Jede Variable eines solchen Datentyps
beansprucht bei ihrer Allokierung (Initialisierung)
einen Speicherplatz fester Größe,
die in Byte gemessen wird. Dadurch kann es nur
endlich viele Zahlen des jeweiligen Typs geben.
int
-Zahl verwendet.
Daher gibt es hiervon genau 2^(32) Zahlen, nämlich die Zahlen
-2^(31) bis 2^(31)-1.
8 Byte werden für eine double
-Zahl verwendet.
Diese werden intern als Gleitpunkt-Dualzahlen dargestellt mit 11
Bit für den Exponenten, 1 Bit für das Vorzeichen
und verbleibenden 52 Bit für die Mantisse. Es gibt insgesamt
2^64 double
-Zahlen, die ungleichabständig auf dem
Zahlenstrahl verteilt sind. Näheres in der Vorlesung.
class
,
die Methode main()
, do-while
-Schleife,
logischer Operator ==
,
primitive Datentypen byte, int
und ihre
Deklaration, Kommentar //
,
Blöcke { }
, In- und Dekrementoperatoren
(++ --
), Ausgabe von Zeichenketten auf
den Bildschirm mit System.out.println()
und Addition
von Zeichenketten (und Zahlen) Die Java-spezifischen Schlüsselworte ("Terminalsymbole") sind alle fett hervorgehoben, die besonders wichtigen sind rot. In der folgenden Erläuterung habe ich die Termini, die die Grammatik beschreiben ("Nichtterminalsymbole"), ebenfalls rot hervorgehoben.1 class bsp1{ 2 static void main(String[] args){ 3 byte b=0; 4 int i=0; 5 do{ 6 b++; 7 i++; 8 }//Ende do-Block 9 while (i==b); 10 System.out.println("Nach Verlas- sen der Schleife: i="+i+" b="+b); 11 i--; 12 b--; 13 System.out.println("Vor Verlas- sen der Schleife: i="+i+" b="+b); 14 }//Ende main() 15 }//Ende class
Das Programm besteht aus einer einzigen Klasse
( class) mit
Bezeichner bsp1
: Es wird ausführbar durch
die Methode main()
,
deren Programmzeile 2 hier noch nicht erläutert wird. Man spricht daher
auch von einer Applikationsklasse, da nie eine Instanz dieser
Klasse gebildet wird. Dieses Programm hat mit OOP nichts zu tun.
Dennoch hilft es, mit den ersten Sprachelementen vertraut zu machen.
Zur Strukturierung achte man auf
(ineinandergeschachtelte) Blöcke
, die durch geschweifte Klammern {
und } eingeschlossen werden und
Anweisungen enthalten.
Der größte Block ist der, der die Klasse umfasst. Dann
kommt der Teilblock zur Methode main
, als kleinster
der "do-Block" in
den Zeilen 6-7. Kommentare (in einer Zeile)
werden durch zwei Slashs (//
) eingeleitet.
Die Zeilen 3 und 4 sind Deklarationen
der Variablen mit Namen (Bezeichner) b
und i
vom Typ byte
und int
, verbunden mit
einer Wertzuweisung.
Grundsätzlich muss jede Variable im Programm deklariert werden:
ihr Name und ihr Typ wird hierdurch festgelegt. Die Deklaration kann,
muss aber nicht mit einer Wertzuweisung (bei Objekten Allokation)
verbunden werden.
In den Zeilen 4-9 steht eine
do-while-Schleife, die solange ("while")
durchlaufen wird, wie die Bedingung in den runden Klammern
(ein Boole'scher Ausdruck)
nach while
erfüllt (true
) ist.
Sie werden noch andere Schleifen (mit while (ohne do)
und for)
kennenlernen. Eine do-while-Schleife zeichnet sich dadurch aus,
dass der
do-Block mindestens einmal durchlaufen, weil die while-Bedingung
nachgestellt ist.
Die Anweisungen in
den Zeilen 6 und 7 erhöhen den Wert von b
und
i
jeweils um 1 (Inkrementoperator
). Entsprechendes gilt für die Zeilen 11, 12
(Dekrementoperator).
System.out.println() in den
Zeilen 10, 13 ist eine von Java gelieferte Methode, die Ausdrücke
in Form von Zeichenketten
(Schlüsselwort String
) auf dem Bildschirm
ausgibt. Dabei bewirkt die Addition von Zeichenketten
(+) ein Aneinanderreihen, wobei
Zahlen automatisch in Zeichenketten umgewandelt werden.
Falls in Zeile 10 i=b=7
, so erscheint auf
dem Bildschirm Nach Verlassen der Schleife: i=7 b=7
.
Das Ziel dieses kleinen, aber doch schon nicht ganz
einfachen Programms ist die Ermittlung der größten
byte-Zahl (127=2^7-1). Dazu muss man den
Boole'schen Ausdruck in den
runden Klammern, i==b
, verstehen. Dieser
ist wahr, falls i
und b
gleiche
Werte haben (bei ==
handelt es sich um einen logischen
Operator). Auch muss man wissen, dass
b++
die kleinste (negative) byte-Zahl
(-128) liefert, falls b
die größte
byte-Zahl ist (127++=-128). Man kann sich die 256 byte-Zahlen
auf einem Kreis angeordnet denken, wobei 127 und -128 benachbart sind.
Da die int-Zahlen die byte-Zahlen umfassen, wird erstmals die
Gleichheit i==b
verletzt, wenn i=128, b=-128
.
Schlüsselworte
(Terminalsymbole) nennt man
übrigens alle von Java schon vordefinierten Worte. Im obigen
Programm sind dies
class, static, void, main, String, byte, int, do, while, System
.
Die von mir "erfundenen" Worte sind
Bezeichner, hier
bsp1, args, b, i
.
Will man dieses Programm "laufen" lassen (run),
so muss man es erst in eine Text-Datei namens bsp1.java
mit einem Texteditor (z.B. emacs)
schreiben, abspeichern (gleicher Name wie die
Klasse!), sodann durch
javac bsp1.java
(z.B. auch unter emacs!)
compilieren
, wodurch eine Datei
bsp1.class
erzeugt wird,
und schließlich (z.B. wieder unter emacs) durch
java bsp1
) ausführen - das geht nur, weil
es eine Methode main()
gibt.
Die Klasse bsp1
enthält zwei Methoden
, main()
und System.out.println()
,
welche
in runden Klammern () eingeschlossene
Parameter enthalten - deshalb füge ich
bei allen Methodennamen runde Klammern an.
/* */
), der
primitive Datentyp double
, for
-Schleife,
Kurzoperatoren
+=, -=, /=, *=
, die Methode Math.pow
des Pakets Math
Am Anfang steht ein sich & uuml;ber mehrere Zeilen erstreckende Kommentar, der durch /* und */ eingeschlossen wird./* Ziel des Programms: Ermittlung der groessten double-Zahl. Dabei wird benutzt: Fuer den Exponenten zur Basis 2 werden 11 Bit verwendet, der groesste ist also 2^10-1. Fuer das Vorzeichen wird 1 Bit verwendet, bleiben fuer die Mantisse 64-12=52 Bit (t=53).*/ 1 class bsp2{ 2 static void main(String[] args){ 3 double d=0; 4 double x=2; 5 for(int i=0;i<53;i++){ //for-Schleife 6 x/=2;// x wird fortlaufend halbiert 7 d+=x;//zu d wird fortlaufend x addiert 8 }//Ende for 9 d*=Math.pow(2,Math.pow(2,10)-1); //(1+2^(-1)+...+2^(-52))2^(2^10-1) 10 System.out.println("groesste double-Zahl: "+d);//1.79769...E308 11 System.out.println("Maschinen- genauigkeit: "+Math.pow(2,-53)); //1.1102...E-16 12 }//Ende main 13 }//Ende class
Die for
-Schleife in den Zeilen 5-8 wird 53 mal durchlaufen.
Der Laufindex i
wird bei seinem erstmaligen Auftreten
in Zeile 5 vom Typ int
vereinbart. Solange der Boole'sche
Ausdruck i<53
wahr ist, wird i
um eins
erhöht
(i++
), x
in Zeile 6 durch 2 dividiert und zu
d
addiert (Zeile 7). Am Ende der Schleife ist d
gleich dem ersten Faktor im Kommentarteil von Zeile 9.
Sie sollten die Syntaxregeln für die verschiedenen Schleifenarten
( for, while, do-while
) in einem Lehrbuch studieren.
Die Deklaration des Schleifenzählers i
durch
int i=0;
innerhalb der Schleife hat den Vorteil einer
lokalen Deklaration.
Man beachte die Verwendung der Kurzoperatoren /=, +=, *=
in den Zeilen 6, 7 und 9. x/=2
ist eine Kurzform
von x=x/2
.
Mathematische Funktionen stellt Java in der Klasse Math
bereit. Eine ihrer Methoden ist die Potenzfunktion ("power")
pow()
. Genauer: Math.pow(x,y)
berechnet x^y (Basis x, Exponent y). Siehe Zeilen 9, 11.
Statt der for-Schleife hätte es auch eine while-Schleife
getan:
Der Unterschied einer while- und for-Schleife zur do-while-Schleife ist der, dass in letzterem Fall der do-Block mindestens einmal durchlaufen wird, weil die (while-)Bedingung erst am Ende überprüft wird. Bei einer for- und while-Schleife wird die Bedingung zu Beginn getestet. Ist sie nicht erfüllt, wird nichts getan.double x=2; double d=0; int i=0; while(i< 53) { x/=2; d+=x; i++}
Auch diese Klasse ist wie das Beispiel 1 eine reine Applikationsklasse. Auch hier ist noch nichts von OOP zu spüren.
return
,
eindimensionale Felder (z.B. byte[] m, String[] args
) als
Objekte mit dem Attribut length
, der Operator
new
zur Initialisierung (Allokierung) eines Objekts, die
Punktnotation zum Zugriff auf Daten und Methoden von Objekten,
Zeichenkette String
, das Schlüsselwort
this
main()
, die es ausführbar
macht. Im Gegensatz zu seinen Vorgängern wird jedoch ein
Objekt der Klasse gebildet und eine Methode dieses Objekts
angewendet, d.h es kommen erste Elemente des OOP vor. Daher ist der
Umfang von neuen Begriffen und Konzepten gewaltig. Aber keine Angst:
in den nachfolgenden Beispielen werden diese ständig wieder
aufgegriffen.
Die Systematik in der Darstellung (fett, Farbe) ist dieses Mal eine
andere. Es soll mehr der Blick auf das Neue dieses Beispiels gelenkt
werden.
Vor den ersten Kontakten zum OOP soll der neue Datentyp
1 class Gleitpunktzahl{
2 //Daten:
3 byte[] m; //Mantisse - ein Array (Feld)
4 byte N; //Basis
5 int t;//Mantissenlaenge - Laenge des Feldes m
6 int e; //Exponent
7 //Konstruktor:
8 Gleitpunktzahl(byte[] mm, byte NN, int tt, int ee){
9 m=mm;
10 N=NN;
11 t=tt;
12 e=ee;
13 }//Ende Konstruktor
14 //Methode mit Wert
double
:
//ohne Parameter - (), Bezeichner Dezimalwert
15 double Dezimalwert(){
16 //wandelt in Standard-Dezimalform um
17 double y=m[0];
18 for(int i=1;i<t;i++) y+=Math.pow(N,-i)*m[i];
19 y*=Math.pow(N,e);
20 return y;
21 } //Ende Methode Dezimalwert()
22 static void main(String[] args){
23 //Ziel: groesste float-Zahl
24 byte[]
m={1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
25 byte N=2;
26 int t=m.length; //t=24
27 int e=127; //groesster Exponent
28 Gleitpunktzahl x
=new Gleitpunktzahl(m,N,t,e);
29 double y=x.Dezimalwert();
30 System.out.println("Groesste float-Zahl: "+y);
31 //3.402823466....E38
32 }//Ende main()
33 }//Ende class
m
der Länge t
einer Gleitpunktzahl
besteht bekanntlich aus t
Ziffern zur Basis
N
(hier vom Typ byte
), indiziert von 0 bis
t-1
, in Java mit m[0], m[1],
...,m[t-1]
bezeichnet. Zu jedem Datentyp kann es Array's
(Felder) geben: Mit byte[] m;
(Zeile 3) wird ein Feld von
noch nicht bekannter Länge mit Bezeichner m
vom Typ
byte
(als Referenzvariable, hierzu später
mehr) deklariert. Technisch wird ihm erst Speicherplatz zugeordnet,
wenn das Feld mit gegebener Länge allokiert wird, z.B. in
Zeile 24, denkbar wäre aber auch m=new
byte[24]
.
Das Schlüsselwort new
taucht stets im Zusammenhang mit der Allokierung (Initialisierung)
von Objekten einer Klasse auf; gewissermaßen ist
Array eine vordefinierte Klasse. Dies wird auch durch die
Punktnotation in Zeile 26 (m.length)
deutlich, wobei das Schlüsselwort length
eine der
Daten (Attribute) eines Feldes ist und die Länge
des Feldes ergibt (die hätte man in Zeile 24 auch ablesen
können. Zählen Sie die Einsen!).
Jetzt verstehen wir auch etwas besser (formal) den Term
String[] in der Parameterliste
der Methode main()
: es handelt sich um ein Feld von
Zeichenketten (String) mit
Bezeichner args
(dem eventuelle Eingaben in der Kommandozeile
beim Aufrufen des Java-Compilers zugeordnet werden).
wiedergegeben werden können. Man achte auf das wichtige Schlüsselwort new in Zeile 28b, das hier verbunden wird mit einer ganz speziellen, zur Klasse zugehörigen Methode, dem Konstruktor, der den gleichen Bezeichner wie die Klasse haben muss (Zeilen 7, 8) und keinen Rückgabewert haben darf. Nach erfolgter Anweisung 28b (Konstruktion des Objektes x) weist der Compiler der Variablen (dem Objekt)28a Gleitpunktzahl x; //Deklaration eines Objekts namens x 28b x=new Gleitpunktzahl(m,N,t,e); //Initialisierung (Allokierung) des Objekts x
x
einen Speicherplatz zu,
der dem Umfang seiner in der Parameterliste
aufgeführten Daten (Zeilen 2-7)
und der mit ihm verbundenen Methoden
(Zeilen 14-21) entspricht.
Gleitpunktzahl
(Zeilen 3-6). Die Verwendung der
Grunddatentypen byte
und int
in den Zeilen 3-6 ist etwas willkürlich
und soll (durch byte
) Speicherplatz sparen.
Auf die Daten eines Objekts wird durch die
Punktnotation zugegriffen: So hätte man durch
die Mantisse ausgeben können! Diese Punktnotation kommt auch inString s=x.m[0]+"."; for (int i=1;i<x.t;i++) s+=x.m[i]; System.out.println(s);
System.out.println()
vor: println
ist eine Methode des Objekts out
der Klasse
System
.
Aber Achtung: Wenn innerhalb einer Klasse auf Daten oder Methoden dieser Klasse zurückgegriffen wird, ist eine Punktnotation allenfalls nach Voranstellen von this möglich. I.a. wird auf diese ohne jede Punktnotation zugegriffen!
Diese Punktnotation kommt auch in einer eleganteren Fassung der Zeilen 7-13 unter Verwendung des Schlüsselworts this vor:
Um dieses zu verstehen, ist es nötig zu bemerken, dass die Namensgebung für die Parameter dieser Methode in Zeile 8a identisch ist mit der für die Daten der Klasse - im Gegensatz zu Zeile 8. Innerhalb des Methodenblocks können daher die Daten der Klasse nicht mehr mit ihrem Namen angesprochen werden: sie wurden schon intern vergeben (lokale Variablen). Um sie anzusprechen dient der Zusatz this.7 //Konstruktor: 8a Gleitpunktzahl(byte[] m, byte N, int t, int e){ 9a this.m=m; 10a this.N=N; 11a this.t=t; 12a this.e=e; 13 }//Ende Konstruktor
name
in den Zeilen
9a-12a. Gut verstehen kann man dies, wenn man wartet, bis der
Konstruktor in Zeile 28 aufgerufen wird. In den Zeilen 9a-12a wird dann
this durch den Bezeichner
x des zu konstuierenden Objekts
ersetzt. So muss Zeile 9a so gelesen werden:
Das Attribut x.m
des Objekts x
wird durch den
Parameter m
des Konstruktors besetzt. Verstanden?
Wir haben in den ersten beiden Beispielen die Methode main() kennengelernt, die die Ausführung
erzwingt. In diesem Beispiel gibt es zusätzlich noch die
spezielle Methode eines Konstruktors, aber auch - und das ist ein
weiteres neues Konzept - die Methode Dezimalwert()
in Zeilen 15-21. Ziel dieser Methode ist die Umwandlung der
"konstruierten" Gleitpunktzahl (Zeile 28) in eine Dezimalzahl. Das
Ergebnis (der Rückgabe-Wert der
Methode) ist sinnvollerweise eine Variable vom Typ
double
, wie in Zeile 15 vereinbart wird. Der
Methodenblock muss am Ende mit Hilfe von return
das Ergebnis zurückgeben. Die Zeilen 17-19 leisten
offenbar das Gewünschte.
Eine Methode muss keinen Rückgabeert haben - dann trägt sie den
Zusatz void wie die Methode
main(). Aber Achtung: Zwar hat
ein Konstruktor keinen Wert, dennoch darf der Zusatz void
hier nicht stehen.
Am Ende des Bezeichners einer Methode kann es eine in runden Klammern
eingeschlossene Parameterliste geben.
Hier gibt es eine solche bei dem Konstruktor
und der Methode main(),
nicht aber bei der Methode
Dezimalwert()
. Dennoch müssen die
runden Klammern immer mitgeführt werden (Zeilen 15, 29).
Was leistet die Methode main()
hier? Eine (positive)
float
-Zahl
ist bekanntlich durch 23 Bits der Mantisse, 1 Bit des
Vorzeichens und 7 Bits des Exponenten bestimmt. Der größte
Exponent ist daher 127 (Zeile 27), die größte Mantisse
besteht aus lauter Einsen (Zeile 24). Durch Zeile 29 wird also
der Dezimalwert der größten float-Zahl ermittelt.
In den mir bekannten Lehrbüchern wird das Grundkonzept
OOP an Hand nichtmathematischer Objekte wie Fahrrad, Auto,
Bankkonto erklärt. In diesem Zusammenhang ist dann auch
von Kapselung von Daten die Rede, ein Konzept, das ich
erst später im Rahmen von Zugriffsrechten von
Attributen und Methoden wie public, private behandeln
werde. Auch wird im Zusammenhang mit Methoden gerne davon
gesprochen, dass eine Nachricht von einem Objekt an
ein anderes gesendet wird. In diesem Sprachgebrauch
wird in der Methode main()
an das frisch
konstruierte Objekt x
der Klasse Gleitpunktzahl
in Zeile 29 die Nachricht gesendet, es möge doch bitte seine
Methode Dezimalwert()
einsetzen und ihren Wert y
übermitteln.
Etwas komplexer als die Umrechnung einer Gleitpunktzahl in eine Dezimalzahl
ist der umgekehrte Vorgang - die Umrechung einer Dezimalzahl
in eine Gleitpunktzahl zu gegebener Basis N und Mantissenlänge t.
Hier verweise ich auf die Applikationsklasse
DezimalUmwandeln mit der Methode
Dezimal()
mit Wert Gleitpunktzahl
.
Wem die Klasse Gleitpunktzahl
mathematisch zu kompliziert
ist, sei die Klasse DoubleFeld empfohlen,
eine Klasse, die als Daten ein double-Feld x
und dessen
Länge und als Methode Max()
mit Wert
double
besitzt, die die maximale Komponente des Feldes (Vektor)
x
berechnet. In der Applikationsklasse Applikation
werden dann ein Objekt namens X
vom Typ
DoubleFeld
zu einem künstlichen Vektor
x
mit Hilfe des Konstruktors und dem new
-Operator
"instanziiert" und X.Max()
ausgegeben.