//, /* */
) {...}
)double, int
) double x=1.234;
)String, char
)final
)++, +=,...
) double[], double[][]
) for(i=1;i<=n;i++){...}
) if (...) {...} else {...}
)System.out.println("Java ist cool")
)Nun scheint es so, daß Pascal keine Zukunft hat. Mittelfristig muß man sich daher umorientieren. Das moderne Programmierungskonzept heißt Objektorientiertes Programmieren (OOP). Ich halte es für sehr sinnvoll, wenn Studierende von Beginn an mit diesem Konzept konfrontiert werden. Hierfür scheint mir Java hervorragend geeignet, weil es dieses Konzept verbindlich vorschreibt, einfacher zu erlernen sein soll als der mächtige(re) Vertreter C++ und plattformunabhängig ist. Daher soll in Hamburg im WiSe 99/00 erstmalig der Versuch unternommen werden, den zweisemestrigen Numerik-Kurs für Anfänger mit Java zu verknüpfen. Um Mißverständnissen vorzubeugen: nicht, weil ich glaube, daß Java in hervorragender Weise die Implementierung numerischer Algorithmen unterstützt, sondern weil mit Java eine sehr moderne Programmierrsprache erlernt wird, die das Tor zum OOP weit aufstößt. Ein weiterer Gesichtspunkt ist, daß das Erlernen von Java ein wichtiger Aspekt der Qualifikation für einen späteren Beruf darstellt - in viel höherem Maße als dies z.B. Pascal vermag.
Die große Popularität verdankt Java dem Internet und der Tatsache, daß Java-Applets von WWW-Browsern interpretiert werden können. Das zur Zeit beste (mathematische) Zeugnis der Mächtigkeit solcher Applets findet man meines Erachtens in der Mathe Online - Galerie
Der Einsatz von Java in der Erstsemster-Numerik soll sich aber nicht an Applets orientieren, sondern der Implementierung von grundlegenden numerischen Algorithmen dienen. Weder sollen Input-Output- noch Grafiktechniken Gegenstand des Unterrichts sein. Hierfür werden Klassen zur Verfügung gestellt werden, die auf den Numerikeinsatz zugeschnitten sind und als Black-Box benutzt werden.
Das hier angebotene Tutorium richtet sich an Studierende, aber auch an Kollegen. Es soll helfen, sich mit Java vertraut zu machen. Vorkenntnisse in Pascal, aber auch ein erstes Verständnis von OOP sind ausgesprochen hilfreich. Wiederholt wird auf Analogien bei Pascal hingewiesen. Dass die Sprachelemente vor einer Einführung in Objekte und Klassen zur Sprache kommen, bedeutet nicht, dass diese Reihenfolge beim Lernen eingehalten werden sollte. Anf&aumL,nger sollten möglichst bald mit dem Konzept des OOP konfrontiert werden. Das wird in den begleitenden Kursmaterialien versucht.
Man findet aber auch viele Dokumentationen und Tutorials im Web:
Der Autor ist alles andere als ein Profi in Sachen Programmiersprachen, insbesondere fehlt ihm der tiefer gehende Einblick in die Struktur von höheren Programmiersprachen. Er ist blutiger Anfänger in Sachen OOP und auch schon relativ alt. Er bittet daher, möglichen Ungereimtheiten, insbesondere in der Fachsprache, mit Nachsicht zu begegnen.
Das Grundkonzept von Java beruht auf dem objektorientierten
Programmieren (OOP). Ein Objekt
ist eine (neuartige) Variable vom Typ einer Klasse
, in Abgrenzung zu den primitiven (einfachen)
Datentypen (Zahlen: int, double,...
, logische Variable:
boolean
, Zeichen: char
). Objekte werden durch
ihre Attribute (Eigenschaften, Daten, Variable) und ihre
Methoden
(Verhalten) charakterisiert. OOP ist des weiteren
durch die
Möglichkeit der Bildung von Unterklassen
mit
Vererbung und
Polymorphie gekennzeichnet.
Dennoch sollen zunächst die sehr Pascal-verwandten Sprachelemente
wie Datentypen, Verzweigungen, Schleifen behandelt werden.
Wer dennoch sehr schnell ein lauffähiges Programm schreiben will,
orientiere sich an der folgenden Variante des unvermeidlichen
Hello World-Programms:
Es besteht aus einer Klasse mit Bezeichnerclass Hallo{ public static void main (String args[]) { System.out.println("Hallo: Ich kann noch nicht Java"); } }
Hallo
und
der Methode main()
, die stets für die Ausführung
eines
Anwendungsprogramms verantwortlich ist. Der Nutzen des Klassenkonzepts
kommt hier überhaupt nicht zum Tragen.
Der Java-Interpreter interpretiert nur eine Klasse, die
die Methode main()
enthält, wobei das Argument
(der Parameter) String args[]
für Kommandozeileneingaben vorgesehen ist und daher häufig
gar keine Bedeutung hat, aber dennoch dort immer stehen muß.
println()
ist eine Methode des Objekts namens
out
der Klasse System
, die zur offiziellen Java-Bibliothek
gehört.
Man achte schon jetzt auf die geschweiften Klammern, die den Beginn und
das Ende von Blöcken markieren.
Man speichere obiges Programm unter dem Dateinamen Hallo.java
ab, compiliere es mit
Hallo.java
Hallo.class
mittels
Hallo
.
// Kommentar
,
bei mehreren Zeilen umfassenden
Blöcken wird der Kommentar durch /*
und */
eingeschachtelt.
class Hallo{ //class ist das wichtigste Schlüsselwort /* Man achte stets auf die Groß- und Kleinschreibung. args ist ein Bezeichner für eine Stringvariable und kann auch durch irgendetwas anderes ersetzt werden. String und System als Bezeichner vordefinierter Klassen werden groß, class und out dagegen klein geschrieben. */ public static void main (String args[]) { System.out.println("Hallo: Ich kann immer noch nicht Java"); } }
;
abgeschlossen. Eine Anweisung ist der kleinste Baustein, er kann eine
Deklaration enthalten, eine Auswertung oder Zuweisung vornehmen oder
einen Programmablauf steuern.
int k = 5;
ist eine Deklaration einer Variablen namens k
vom Integer-Typ int
, die mit
einer Zuweisung (Initialisierung)
verbunden wird.
int,
boolean, void
, ... ), auch Schlüsselworte
genannt.
{, }
markieren den Beginn und Ende eines Blocks
und entsprechen den Wörtern begin, end
in Pascal.
Wie dort können
diese Blockklammern bei einem Block mit nur einer
Anweisung entfallen.
int, double, char, boolean
bzw. ihre
nicht ganz so wichtigen Varianten byte, short,
long, float
.
long k=2222222222;
oder auch etwas länglicher
long k; k=2222222222;
.
String
(eine Zeichenkette)
kennzeichnet im Gegensatz zu Pascal
keinen Grunddatentyp, sondern eine Klasse. Daher
ist
String s = "rot"; if s == "rot" ...
falsch. Richtig ist
String s="rot"; if s.equals("rot") ...
equals()
ist eine Methode eines Objekts der
Klasse String
.
Man achte auf die Punktnotation,
die eine Methode an ihr Objekt bindet
(Objekt.Methode
).
Trotz des Klassentyps muß eine Stringvariable nicht mit Hilfe
von new
(s.u.) initialisiert werden wie bei allgemeinen Objekten
(auch bei Arrays). Beispiel:
String s="Idiot";
Ein String-Literal (Darstellung des Wertes einer
Zeichenketten-Variablen) wird durch doppelte Hochkommata
eingeschlossen, ein
char-Literal durch einfache Hochkommata:
String s="Eureka - I hob's"; char c='J';
final
. Beispiel:
final double pi=3.1416;
.
Im Gegensatz zu Variablen können Konstanten
nicht lokal definiert werden.
=
, aber auch der Inkrementoperator
++
) und
bin"aren
Operatoren (z.B. die arithmetischen Operatoren
+, -, *, /
, die Vergleichsoperatoren
<, <=, ==
), die
logischen Operatoren:
!, &, ^, |
).
Die arithmetischen Operatoren
liefern als Ergebnis
auch bei Eingabeparametern vom Grundtyp byte, short, char
stets den Typ int
oder -
falls einer vom Typ long
ist -
den Typ long
.
Kurzoperatoren wie ++, --
:
Beispiel: a++
oder ++a
ist äquivalent mit a=a+1
, ist also eine Verquickung
einer Zuweisungs- mit einer arithmetischen Operation. Aber Achtung:
b=++a
bzw. b=a++
liefert zwar
für a
jeweils einen um
1 erhöhten Wert, für b
jedoch nur im ersten Fall. Im zweiten Fall
wird erst b=a
gerechnet und dann a
um 1 erhöht.
Sehr nützlich, gerade für numerische Algorithmen, sind die
Kurzoperatoren +=, *=,...
.
So bedeutet
a+=5;
dasselbe wie a=a+5;
.
Vergleichsoperatoren sind ==
(im Gegensatz zu Pascal: =
) für Gleichheit,
!=
f"ur Ungleichgleichheit
(Pascal <>
), ansonsten
<, <=, >, >=
wie in Pascal.
Logische Operatoren werden in Pascal mit
and, or, xor, not
ausgeschrieben, in Java mit &
(and),
!
(not),
|
(or) und ^
(xor) abgekürzt.
Bitweise Operatoren werden hier nicht behandelt, da in der Numerik wohl unbedeutend.
Stringadditionen sind in Java sehr bequem:
aber auch (!)String s="Hallo" + " " + "Numerik-Freunde";
Dies ist sehr wichtig für numerische Ausgaben. Zahlen werden hierbei automatisch in Zeichenketten umgewandelt.int anzahl=5; String s="Hier sitzen "+anzahl+" Zuhörer";
int k[];
oder int [] k;
Entscheidend ist das Klammerpaar []
, nicht etwa wie
in Pascal das Wort array.
Ein Array ist ein Objekt, seine Deklaration erzeugt es noch nicht,
sondern weist einer (Referenz-) Variablen nur einen Typ zu. Die Erzeugung
(Speicher-Allokation)
geschieht wie bei allen Objekten
mit new
in Verbindung mit einer Dimensionsangabe:
int[] k = new int[7];
oder auch int[] k; k= new int[7];
Es kann bequemer sein, einen Array manuell (ohne new
) zu erstellen:
oderString[] Wortliste = {"Frau","Mann","Kind"};
Zum Vergleich in Pascal:double[][] A={{1,2},{8,9}};
Wortliste: Array[1..3] of String =("Frau","Mann","Kind");Achtung! Das erste Listenelement eines Feldes hat stets den Index Null - das ist sehr gewöhnungsbedürftig:
int[] k = new int[5]; k[0]=2; k[4]=4;
Hier hat k
5 Listenelemente, das letzte hat den Index 4.
Ein Array Typ[n]
hat stets n Listenelemente, beginnend beim
Index 0, endend beim Index n-1.
Mehrdimensionale Arrays:
double[][] A; A = new double[4][6]
erzeugt eine 4 x 6 -Matrix mit den Zeilen 0,1,2,3 und den
Spalten 0,1,2,...,5.
Bei einer solchen Initialisierung erhält A die Elemente Null.
Das ist fehleranfällig! Einzelne Elemente werden mit
A[i][j]
angesprochen (Pascal: A[i,j]
).
for i:=1 to n do
schreibt
man
for(int i=1;i<=n;i++)
.
Eine Doppelschleife
lautet z.B.
for (i=1;i<=n;i++) for (j=1;j<=n;j++) A[i-1][j-1]=1/(i*j);
oderint i=1; while(i<=6) {System.out.println(i);i++;}
Schleifen werden mitint i=1; do {System.out.println(i);i++;} while (i<=6);
break
verlassen
(auch mit Verwendung eines Labels),
dagegen mit continue
durch Sprung an den Anfang fortgesetzt.
Blockbildung ist sinnvoll! Das Semikolon vor demif (a<b) s="a ist kleiner als b"; else s="a >= b";
else
muß stehen!
(Es fehlt das then von Pascal. Boolesche Ausdrücke
stehen stets in runden Klammern.)
case
:
char c; switch(c) { case 'J':'j': //Java-Anweisungen break; case 'N':'n': //Java-Anweisungen break; default: //noch mehr Anweisungen }
System.out.println();
(Pascal writeln();
).
Grundsätzlich werden als Parameter der
Methode System.out.println()
Parameter in Form von
Zeichenketten übergeben, wobei
Zahlen automatisch in Zeichenketten (String) umgewandelt
werden.
Kompliziert sind formatgerechte Ausgaben von Gleitpunktzahlen. Hierzu muß man sich eigene Methoden schreiben.System.out.println("Das Pivotelement der Matrix lautet:"+A[i][i]);
main()
enthält, welche automatisch ausgeführt
wird, wenn
die Klasse interpretiert wird. Ich gebe zwei solche Beispiele.
Dieses Programm ist praktisch eine 1-1 Übersetzung eines Pascal-Programms in Java. Es enthält eine Methode ohne Wertzuweisung (class Matrix{ // METHODEN ----------------------------------------------------------- static void LRZerlegung(int n, double[][] A){ int i,j,k; for(k=0;k<n-1;k++) for(i=k+1;i<n;i++){ A[i][k]/=A[k][k]; for (j=k+1;j<n;j++) A[i][j]-=A[i][k]*A[k][j]; }//Ende for i }// Ende LRZerlegung() static double[] LoeseLGS(int n, double[][] A, double[] b){ int i,j; LRZerlegung(n,A); //beginn vorwaertseinsetzen for(i=1;i<n;i++) for(j=0;j<i;j++) b[i]-=A[i][j]*b[j]; //ende Vorwaertseinsetzen //beginn rueckwaertseinsetzen for(i=n-1;i>=0;i--){ for(j=n-1;j>i;j--) b[i]-=A[i][j]*b[j]; b[i]/=A[i][i]; }//ende Rueckwaertseinsetzen return b; }//ende LoeseLGS() static void main(String args[]){ //so faengt jedes Hauptprogramm an int n=3; double[] b={3.1, 3, 0}; double[][] A={{2.1,-1, 1},{2, -4, 3},{-4, 8,-4}}; b=LoeseLGS(n,A,b); System.out.println("Lösungsvektor:"); for(int i=0;i<n;i++) System.out.println("x["+i+"]="+b[i]); }//ende main() }// ende class Matrix
void
, eine "Prozedur"),
eine mit Wertzuweisung (double[]
,
eine "Funktion") und einen Hauptteil, die Methode
main()
, in der alle Daten
als lokale Variablen dieser Methode festgesetzt und die anderen Methoden
benutzt werden. Alle drei Methoden besitzen den Zusatz
static
, weil diese Methoden
ohne Objektbildung der Klasse Matrix
benutzt werden.
Alle Methoden haben Parameter, wobei die Variable double[][] A
durch die Methode LRZerlegung()
überschrieben wird: sie
ist eine Referenzvariable.
Hier gibt es vier Methoden, mit und ohne Wertzuweisung, mit und ohne Parameter. Im Gegensatz zum Matrix-Beispiel wurden hier die Daten nicht als lokale Variable der main-Methode, sondern als globale Variable der Klasse behandelt. Dies erlaubt eine wesentliche Reduktion der Parameter in den Methoden.class NewtonPolynom{ //Variablen: static int Grad=3; static double[] x={1,2,3,4}; //Knoten static double[] y={1,4,9,16};//Werte static double[] c=new double[Grad+1]; //Div. Differenzen //Methoden: static void DividierteDifferenzen(){ for(int i=0;i<=Grad;i++) c[i]=y[i]; for(int k=1;k<=Grad;k++) for(int i=Grad;i>=k;i--) c[i]=(c[i]-c[i-1])/(x[i]-x[i-k]); }//Ende DividierteDifferenzen() static double[] Horner(double xi){ int j; double[] b = new double[Grad]; DividierteDifferenzen(); b[Grad-1]=c[Grad]; for(j=Grad-2;j>=0;j--) b[j]=c[j+1]+(xi-x[j+1])*b[j+1]; return b; }//ende Horner() static double getWert(double xi){ double[] b = new double[Grad]; if (Grad==0) return c[0]; else{ b=Horner(xi); return b[0]*(xi-x[0])+c[0]; }//Ende else }//Ende getWert() static void main(String[] args){ System.out.println("Wert an der Stelle 1.1: "+getWert(1.1)); }//Ende main() }//ende class NewtonPolynom
this
verwendet, und mit Hilfe des
new
-Operators.
Die Methoden des Objekts werden sodann mit der
Punktnotation aufgerufen.
Matrix
oder NewtonPolynom
Gebrauch machen
können.
Der Konstruktor kann einfach als eine Methode mit dem gleichen Namen wie die Klasse verstanden werden. Die Anweisungclass Matrix{ //DATEN (Variable) --------------------------------------- int n; // Abmessung einer quadratischen Matrix double[][] Data, LR; //Data: Elemente der Matrix, LR: nach der LR-Zerlegung //KONSTRUKTOR --------------------------------------------------------- Matrix(int n){ //Konstruktor einer quadratischen Matrix int i; this.n=n; Data=new double[n][n]; LR=new double[n][n]; }//Ende Konstruktor // METHODEN ----------------------------------------------------------- LRZerlegung(){ int i,j,k; for(i=0;i<n;i++) for(j=0;j<n;j++) LR[i][j]=Data[i][j]; //LR=Data bewirkt, dass in Folge LR=Data, d.h. Data veraendert wird. //Der Grund: LR und Data sind Referenzvariable for(k=0;k<n-1;k++) for(i=k+1;i<n;i++){ LR[i][k]/=LR[k][k]; for (j=k+1;j<n;j++) LR[i][j]-=LR[i][k]*LR[k][j]; }//Ende for }// Ende LRZerlegung() double[] LoeseLGS(double[] b){ int i,j; LRZerlegung(); //beginn vorwaertseinsetzen for(i=1;i<n;i++) for(j=0;j<i;j++) b[i]-=LR[i][j]*b[j]; //ende Vorwaertseinsetzen //beginn rueckwaertseinsetzen for(i=n-1;i>=0;i--){ for(j=n-1;j>i;j--) b[i]-=LR[i][j]*b[j]; b[i]/=LR[i][i]; }//ende Rueckwaertseinsetzen return b; }//ende LoeseLGS() static void main(String args[]){ //so faengt jedes Hauptprogramm an int n=3; Matrix A=new Matrix(n); double[] b={3.1, 3, 0}; double[][] a={{2.1,-1, 1},{2, -4, 3},{-4, 8,-4}}; A.Data=a; //besser A.setData(a); b=A.LoeseLGS(b); //wird automatisch eine LR-Zerlegung gemacht System.out.println("Lösungsvektor:"); for(int i=0;i<n;i++) System.out.println("x["+i+"]="+b[i]); }//ende main() }// ende class Matrix
this.n=n;
bedarf einer Erklärung:
Da n
eine lokale Variable der Methode Matrix(int n)
,
aber auch eine Variable der Klasse Matrix
ist, wird
auf letztere mit dem Schlüsselwort this
zugegriffen.
Bemerkenswert ist auch, daß der neue Datentyp Matrix
(eine Klasse) in der Methode main()
Verwendung findet, indem
eine Variable namens A
durch Matrix A=new Matrix(n);
deklariert
und initialisiert (konstruiert) wird, um dann mittels
A.LoeseLGS(b)
(beachte die Punktnotation)
zu einer Lösung eines linearen Gleichungssystems führt. In
der OOP-Sprache sagt man, es wird eine Nachricht an das
Objekt code>A gesendet, es möge seine Methode
LoeseLGS()
einsetzen und das Ergebnis übermitteln.
Hier berechnet ein Konstruktor (wieder mit Hilfe des Schlüsselwortsclass NewtonPolynom{ //DATEN (Attribute, Mitgliedsvariable): int Grad; double x[]; //Knoten double y[]; //Werte double c[]; //Koeffizienten (p=c0+c1*(x-x0)+....+cn*(x-x0)*...(x-x_n-1) //KONSTRUKTOR: NewtonPolynom(int Grad,double[] x, double[] y){ this.Grad=Grad; this.x=x; this.y=y; c=DividierteDifferenzen(); }//Ende Konstruktor //METHODEN: double[] DividierteDifferenzen(){ double[] c=new double[Grad+1]; for(int i=0;i<=Grad;i++) c[i]=y[i]; for(int k=1;k<=Grad;k++) for(int i=Grad;i>=k;i--) c[i]=(c[i]-c[i-1])/(x[i]-x[i-k]); return c; }//Ende DividierteDifferenzen() double[] Horner(double xi){ int j; double[] b = new double[this.Grad]; b[Grad-1]=c[Grad]; for(j=Grad-2;j>=0;j--) b[j]=c[j+1]+(xi-x[j+1])*b[j+1]; return b; }//ende Horner() double getWert(double xi){ if (Grad==0) return c[0]; else{ double[] b=Horner(xi); return b[0]*(xi-x[0])+c[0]; }//Ende else }//Ende getWert() static void main(String[] args){ double[] x={1,2,3,4}; double[] y={1,4,9,16}; NewtonPolynom p=new NewtonPolynom(3,x,y); System.out.println("Wert an der Stelle 1.1: "+p.getWert(1.1)); }//Ende main() }//ende class NewtonPolynom
this
)
das Interpolationsplolynom mit Hilfe der beiden
Methoden Horner(), DividierteDifferenzen()
, von denen
beide als Werte Felder (double[]
) haben.
Bemerkenswert ist, daß der neue Datentyp NewtonPolynom
(eine Klasse) in der Methode main()
Verwendung findet, indem
eine Variable namens p
durch
NewtonPolynom p=new NewtonPolynom(3,x,y);
deklariert
und initialisiert (konstruiert) wird, um dann mittels
p.getWert(1.1)
(beachte die Punktnotation)
an einer Stelle ausgewertet zu werden.
In
der OOP-Sprache sagt man, es wird eine Nachricht an das
Objekt code>p gesendet, es möge seine Methode
getWert()
einsetzen und das Ergebnis übermitteln.
Später kann die Klasse NewtonPolynom
als Unterklasse einer abstrakten Klasse Funktion
dargestellt, wodurch weitere Methoden, die z.B. den
Graphen zeichnet (Polymorphie),
eine Wertetabelle erstellt oder die Ableitung
an einer Stelle mit Hilfe eines Differenzenquotienten approximiert
(Vererbung), zur Verfügung stehen.
Dieses Programm (nach seiner Compilierung alsimport java.applet.*; //alle Klassen des package java.applet import java.awt.Graphics; //nur die Klasse Applet des Pakets java.awt //awt= Abstract Window Toolkit public class Hello extends Applet { public void paint(Graphics g){ g.drawString("Hello world!",50,25); }
*.class
)
muß in ein HTML-File eingebaut werden.
Die Methode paint()
hat dieselbe Funktion wie main()
in Anwendungsprogrammen.
aufgerufen werden, wobei eine Textdatei namensTools.SetmeinOut("DateiName")
DateiName
geöffnet wird, in die danach mit
Tools.meinOut.println()
oder aber auch (!) direkt mit
den diversen Schreibe()
-Methoden geschrieben werden kann.
Der Trick ist, daß ohne der Spezifizierung einer Ausgabedatei
die Voreinstellung Tools.meinOut=System.out
benutzt wird, so daß ohne
Verwendung von Tools.SetmeinOut()
ganz normal auf den Bildschirm
ausgegeben wird.
Die öffentliche, statische Methode
Schreibe(double[][] A, int m, int n, int vk, int nk)
schreibt zeilenweise die Matrix A (mit m Zeilen und n Spalten) in
einem Format, das vk
Vorkomma- und nk
Nachkommastellen verwendet.
Diese Methode Schreibe()
wird
mannigfach überlagert, indem statt A
eine Zahl
oder ein Vektor
(auch Integer-Vektor) ausgegeben wird.
double x=Tools.LeseR();
Diese Methode läuft nur unter JDK1.2. Sie stammt
aus Krüger, S.98, und ist ziemlich kompliziert.