//, /* */) {...})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:
class Hallo{
public static void main (String args[]) {
System.out.println("Hallo: Ich kann noch nicht Java");
}
}
Es besteht aus einer Klasse mit Bezeichner 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.javaHallo.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:
String[] Wortliste = {"Frau","Mann","Kind"};
oder
double[][] A={{1,2},{8,9}};
Zum Vergleich in Pascal:
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);
int i=1; while(i<=6) {System.out.println(i);i++;}
oder
int i=1; do {System.out.println(i);i++;} while (i<=6);
Schleifen werden mit 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.
System.out.println("Das Pivotelement der Matrix lautet:"+A[i][i]);
Kompliziert sind formatgerechte Ausgaben von Gleitpunktzahlen. Hierzu
muß man sich eigene Methoden schreiben.
main()
enthält, welche automatisch ausgeführt
wird, wenn
die Klasse interpretiert wird. Ich gebe zwei solche Beispiele.
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
Dieses Programm ist praktisch eine 1-1 Übersetzung eines Pascal-Programms
in Java. Es enthält eine Methode ohne Wertzuweisung
(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.
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
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.
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.
class 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
Der Konstruktor kann
einfach als eine Methode mit dem
gleichen Namen wie die Klasse
verstanden werden.
Die Anweisung 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.
class 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
Hier berechnet ein Konstruktor
(wieder mit Hilfe des Schlüsselworts
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.
import 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);
}
Dieses Programm (nach seiner Compilierung als *.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.