Klassen und Objekte

Klassen fassen Daten und Methoden zusammen, die die Daten verarbeiten. Alle Konstrukte in Java sind letztlich in Klassen organisiert - auch jedes noch so kleine „Java-Programm“ bildet eine eigene Klasse. Dabei gilt: Jede Klasse definiert einen neuen Datentyp! Nach der Definition einer Klasse kann ein neues Objekt von diesem Typ erzeugt werden: Dieses neue Objekt heißt Instanz der Klasse.

Dieser Abschnitt stellt den ersten Teil der Einführung in die objektorientierte Programmierung (OOP) dar. Im Abschnitt „Objektorientierte Programmierung“ dieses Kurses werden weitergehende Techniken und komplexere Beispiele besprochen.

Einführende Beispiele

Alle Beispiele, die in diesem Kurs bisher angeführt wurden, verwenden Klassen - Java arbeitet schließlich streng objektorientiert.

Eine einfache Klasse

Die einfachsten Klassen enthalten lediglich die Methode main(). Dies ist stets erforderlich, wenn die Klasse direkt ausführbar sein soll (sei es „auf der Konsole“' oder als GUI-Anwendung). Im folgenden Beispiel wird außer der Hauptmethode main() die Instanzvariable werte verwendet; Instanzvariable werden außerhalb aller anderen Methoden deklariert - sie stehen allen Methoden zur Verfügung.

public class array7 {
    public static double werte [] = {12.2, 13.1, 15.3, 11.2, 13.4, 15.2, 15.2, 10.5};
 
    public static void main (String [] args) {
        double erg = 0;
        int k;
 
        for (int i=0; i<werte.length; i++) {
            System.out.println(werte[i]);
            erg += werte[i];
        }
 
        System.out.println("--------------");
        System.out.println("Durchschnitt: " + erg / werte.length);
    }
}

Nach der Berechnung der Gesamtsumme aller Werte wird der Mittelwert berechnet und ausgegeben:

12.2
13.1
15.3
11.2
13.4
15.2
15.2
10.5
--------------
Durchschnitt: 13.262500000000001

Methoden entsprechen (kleinen) Unterprogrammen. Jede Methode übernimmt Daten, verarbeitet diese, und gibt Ergebnisse zurück. Dieser „Datenaustausch“ wird mit Hilfe so genannter Schnittstellen realisiert.

Objekte verwenden

Das nächste Beispiel zeigt, wie eigene Objekte entworfen und deklariert werden können. Die Klasse rechteck soll die Berechnung von Umfang und Flächeninhalt eines Rechtecks erlauben. In der ersten „Fassung“ des Programmes enthält die Klasse rechteck lediglich zwei Instanzvariable für die Länge und die Breite:

class rechteck {
    double a = 4.1;
    double b = 5.3;    
}
 
public class class1 {
    public static void main (String [] args) {
        rechteck r = new rechteck();
        System.out.println("Länge: " + r.a);
        System.out.println("Breite: " + r.b);
    }
}

Die Variablen der Instanz r werden mit Hilfe des Punktes korrekt referenziert. Damit erhalten wir das folgende Ergebnis:

Länge: 4.1
Breite: 5.3

Methoden verwenden

Klassen erhalten erst durch die Verwendung (vieler) Methoden ihre umfassenden Eigenschaften - im Beispiel soll beispielsweise der Umfang berechnet und ausgegeben werden:

class rechteck {
    double a = 4.1;
    double b = 5.3;  
 
    void umfang() {
        System.out.println("Umfang (" + a + ", " + b + "): " + (2*a + 2*b));
    }
 
}
 
public class class1 {
    public static void main (String [] args) {
        rechteck r = new rechteck();
        System.out.println("Länge: " + r.a);
        System.out.println("Breite: " + r.b);
        r.umfang();
    }
}
Länge: 4.1
Breite: 5.3
Umfang (4.1, 5.3): 18.799999999999997

Einen Konstruktor verwenden

Ein Konstruktor wird beim Erstellen eines Objektes für die Initialisierung verwendet. Eine Methode wird automatisch als Konstruktor verwendet, wenn sie den gleichen Namen wie die Klasse trägt.

class rechteck {
    double a;
    double b;
 
    rechteck(double laenge, double breite) {
        a = laenge;
        b = breite;
    }
 
    void umfang() {
        System.out.println("Umfang (" + a + ", " + b + "): " + (2*a + 2*b));
    }
 
    void flaeche() {
        System.out.println("Flächeninhalt (" + a + ", " + b + "): " + (a*b));
    }
}
 
public class class1 {
    public static void main (String [] args) {
        rechteck r = new rechteck(4, 5);
        r.umfang();
        r.flaeche();
 
    }
}

Bei der Ausführung des Programmes wird zunächst eine Instanz r der Klasse rechteck erzeugt. Die angeführten Parameter werden an den Konstruktor übergeben, der diese den Instanzvariablen a und b zuweist. Anschließend werden die Methoden umfang() und flaeche() ausgeführt. Beachte die korrekte Referenzierung mit Hilfe des Punktes !

Umfang (4.0, 5.0): 18.0
Flächeninhalt (4.0, 5.0): 20.0

Parameterübergabe und Rückgabewerte

Im letzten Beispiel wurden an den Konstruktor Parameter übergeben. Die Übergabe von Parametern ist für jede Methode möglich. Das nächste Beispiel zeigt, wie Rückgabewerte einer Methode verwendet werden können:

class rechteck {
    double a;
    double b;  
 
    rechteck (double laenge, double breite) {
        a = laenge;
        b = breite;
    }
 
    double cut2 (double zahl) {
        return (int) (zahl * 100) / 100;
    }
 
    double umfang() {
        return cut2(2*(a+b));
    }
 
}
 
public class class1 {
    public static void main (String [] args) {
        rechteck r = new rechteck(4.5, 5.5);
        System.out.println("Länge: " + r.a);
        System.out.println("Breite: " + r.b);
        System.out.println("Umfang: " + r.umfang());
    }
}

Die Methode umfang() ruft die Methode cut2(double zahl) auf, die die angeführte Zahl mit 2 Dezimalstellen hinter dem Komma liefert. Wir erhalten den erwarteten Output:

Länge: 4.5
Breite: 5.5
Umfang: 20.0

Methoden überladen

Die Technik des „Überladens“ von Methoden erlaubt, Parameterlisten flexibel zu verarbeiten. Dabei ist vorausgesetzt, dass die Methoden jeweils den gleichen Namen haben, sich in der Anzahl der Parameter jedoch unterscheiden.

Ein einführendes Beispiel

public class overload {
    static void methode () {
        System.out.println("Keine Zahleneingabe");
    }
 
    static void methode (int a) {
        System.out.println("Eine Zahl eingegeben: " + a);
    }
 
    static void methode (int a, int b) {
        System.out.println("Zwei Zahlen eingegeben: " + a + ", " + b);
    }
 
    public static void main (String [] args) {
        methode();
        methode(3);
        methode(2, 5);
    }
}

Sobald Java gleichlautende Methoden findet, versucht Java die korrekte Methode anhand der Anzahl der übergebenden Parameter zu bestimmen:

Keine Zahleneingabe
Eine Zahl eingegeben: 3
Zwei Zahlen eingegeben: 2, 5

Konstruktoren überladen

Konstruktoren werden analog zu Methoden überladen. Im folgenden Beispiel können entweder die Eingabewerte fehlen, oder es wird fallweise ein Quadrat oder ein Rechteck berechnet:

class rechteck {
    double a;
    double b;  
 
    rechteck () {
        System.out.println("Keine Eingabewerte :-(");
    }
 
    rechteck (double seite) {
        a = seite;
        b = seite;
        System.out.println("Quadrat...");
    }
 
    rechteck (double laenge, double breite) {
        a = laenge;
        b = breite;
    }
 
    double cut2 (double zahl) {
        return (int) (zahl * 100) / 100;
    }
 
    double umfang() {
        return cut2(2*(a+b));
    }
}
 
public class class2 {
    public static void main (String [] args) {
        rechteck r = new rechteck();
        rechteck r1 = new rechteck(3.2);
        System.out.println("Länge: " + r1.a);
        System.out.println("Breite: " + r1.b);
        System.out.println("Umfang: " + r1.umfang());
        rechteck r2 = new rechteck(4.5, 5.5);
        System.out.println("Länge: " + r2.a);
        System.out.println("Breite: " + r2.b);
        System.out.println("Umfang: " + r2.umfang());
    }
}

In der Reihenfolge der Objekte und Methodenaufrufe erhalten wir:

Keine Eingabewerte :-(
Quadrat...
Länge: 3.2
Breite: 3.2
Umfang: 12.0
Länge: 4.5
Breite: 5.5
Umfang: 20.0

Vorgegebene Klassen und Methoden verwenden

Java stellt zahlreiche Klassen und Methoden zur Verfügung. Im Abschnitt „Strings (Zeichenketten)“ wurden beispielsweise die Methoden der Klasse String verwendet. Die Klasse String ist in der Bibliothek java.lang enthalten, die automatisch in jede Java-Anwendung importiert wird. In weiteren Paketen (Packages) stellt das JDK eine Reihe weiterer Klassen und Methoden zur Verfügung.

Zeichen in Bytes umwandeln

Für manche Anwendungen ist es günstig, den Bytecode der Zeichen einer Zeichenkette zur Verfügung zu haben. Die String-Methode getbytes() liefert den Bytecode jedes Zeichens einer Zeichenkette in Form eines Byte-Arrays:

public class byte1 {
    public static void main ( String [] args) {
        String s = "Informatik ist schoen";
        byte [] geheim = s.getBytes();
        for (int i = 0; i<geheim.length;i++) {
            System.out.print(geheim[i] + " ");
        }
    }
}

Damit erhalten wir eine Folge von Bytes und Leerräumen:

73 110 102 111 114 109 97 116 105 107 32 105 115 116 32 115 99 104 111 101 110

TypeCasting

Methoden einer Klasse liefern u.A. Werte eines bestimmten Typs zurück. Manchmal benötigt man das Ergebnis aber in einem anderen Typ: Der zurückgegebene Wert wird dann einem Typ zugewiesen, der sich vom ursprünglichen Typ unterscheidet. Sind der Quelltyp und der Zieltyp nicht identisch, so muss eine Typkonvertierung stattfinden. Ist der Zieltyp „größer“ als der Quelltyp, so findet diese Konvertierung automatisch statt; ist dies nicht der Fall, wird eine Cast-Anweisung notwendig. Wir verwenden dies im folgenden Beispiel, indem der Zieltyp (char) in runden Klammern vor der Ausgabe des Wertes des Byte-Arrays geheim[i] angegeben wird:

public class byte1 {
    public static void main ( String [] args) {
        String s = "Informatik ist schoen";
        byte [] geheim = s.getBytes();
        for (int i = 0; i<geheim.length; i++) {
            System.out.print(geheim[i] + " ");
        }
        System.out.println();
        for (int i = 0; i<geheim.length; i++) {
            System.out.print( (char) geheim[i]);
        }
    }
}

Damit werden alle Byte-Werte des Arrays geheim als Zeichenwerte ausgegeben:

73 110 102 111 114 109 97 116 105 107 32 105 115 116 32 115 99 104 111 101 110 
Informatik ist schoen

Die im obigen Beispiel angeführte Klasse byte1 lässt sich mit Type-Casting auch folgendermaßen verwirklichen:

public class byte2 {
    public static void main ( String [] args) {
        String s = "Informatik ist schoen";
        for (int i = 0; i<s.length(); i++) {
            System.out.print((byte) s.charAt(i) + " ");
        }
 
    }
}

In diesem Fall liefert die String-Methode length() die Länge der Zeichenkette, und die Methode charAt(i) das jeweilige Zeichen an der i-ten Stelle. Der zugehörige Byte-Wert wird durch Type-Casting bestimmt.

Systemzeit verwenden

Die Klasse java.util.Date stellt Datums- und Zeitfunktionen zur Verfügung. Im folgenden Beispiel verwenden wir die Methode getTime(), die die Anzahl der Millisekunden zurückgibt, die seit dem Beginn der Unix-Zeit (1.1.1970) vergangen sind.

import java.util.Date;
 
public class util1 {
    public static void main (String [] args) {
        Date date = new Date();
        System.out.println("Aktuelles Datum: " + date);
        System.out.println("Seit 1. 1. 1970 sind " + date.getTime() / 1000 + " Sekunden vergangen...");
        System.out.println("... dies sind etwa " + date.getTime() / (1000*86400*365.25) + " Jahre.");
 
    }
}

Durch entsprechendes Dividieren erhalten wir die Anzahl der Sekunden bzw. die Anzahl der Jahre:

Aktuelles Datum: Fri Dec 20 22:53:30 CET 2002
Seit 1. 1. 1970 sind 1040421210 Sekunden vergangen...
... dies sind etwa 32.96895867594494 Jahre.

Weitere Methoden erlauben z.B. Datumsvergleiche:

after(datum)

liefert true, wenn das aufrufende Objekt ein späteres Datum als das angegebene enthält, andernfalls lautet das Ergebnis false.

before(datum)

liefert true, wenn das aufrufende Objekt ein früheres Datum als das angegebene enthält, andernfalls lautet das Ergebnis false.

equals(datum)

liefert bei Gleichheit der Daten true, andernfalls false.

Kalender-Objekte

Die Klasse Calendar ist eine abstrakte Klasse (vgl. Abschnitt „Objektorientierte Programmierung“) ohne einen Konstruktor vom Typ public. Eine Instanz wird mit Hilfe der Methode getInstance() gebildet. Verwendung findet diese Klasse beispielsweise dann, wenn die einzelnen Bestandteile einer Zeitangabe ausgegeben werden sollen. Dafür stehen beispielsweise die (Ganzzahl-)Konstanten DATE (Tagesdatum), MONTH (Monatsname) und YEAR (vierstellige Jahreszahl) zur Verfügung, mit denen die Methode get() die entsprechenden Werte auslesen kann.

import java.util.Calendar;
 
public class calender1 {
    public static void main (String [] args) {
        Calendar kalender = Calendar.getInstance();
        System.out.println(kalender.get(Calendar.DATE) + ". " 
                           + ((int)(kalender.get(Calendar.MONTH))+1) + ". "
                           + kalender.get(Calendar.YEAR));
    }
}

Da die Zahl der Monate von 0 beginnend ausgegeben wird, ist es notwendig, zur Ausgabe der Monatszahl 1 zu addieren. Beachte die korrekte Klammerung!

14.3.2010

Zufallszahlen

Zufallszahlen spielen in vielen Anwendungen eine entscheidende Rolle - dabei können wir grob dann von einer Zufallszahl sprechen, wenn ihr Ergebnis nicht voraussagbar ist. Mit anderen Worten: Eine Zufallszahl zwischen 1 und 10 liegt dann vor, wenn jede der Zahlen 1, 2, 3, … 10 mit der gleichen Häufigkeit (bzw. Wahrscheinlichkeit) auftritt8.

Die Klasse Random stellt die notwendigen Methoden dazu zur Verfügung.

import java.util.Random;
 
public class zufallszahlen {
    public static void main (String [] args) {
        Random rzahl = new Random();
        for (int i = 0; i<10; i++)
            System.out.println(rzahl.nextDouble());
    }
}

Wir erkennen, dass die Zufallszahlen zwischen 0 und 1 liegen:

0.11275337171420274
0.6325291542750278
0.40505319929512495
0.14406921720773713
0.35183480446738447
0.8118952570603442
0.4441019937684132
0.8590548874782364
0.00422453966200953
0.16566583961839842

Multipliziert man mit einem passenden Faktor und addiert man eine entsprechende Konstante, so kann man Zufallszahlen in einem gewünschten Zahlenintervall ermitteln.

Häufigkeitsverteilung von Zufallszahlen

Mit dem letzten Beispiel in diesem Abschnitt ermitteln wir die Häufigkeit der Zufallszahlen zwischen 1 und 10. Dazu multiplizieren wir die von Java ermittelten Zufallszahlen jeweils mit 10 und zählen jeweils die Ergebnisse:

import java.util.Random;
 
public class verteilung {
    public static void main (String [] args) {
        Random rzahl = new Random();
        int [] zaehler = new int[10];
 
        for (int i=0; i<10000; i++)
            zaehler[(int) (rzahl.nextDouble()*10)]++;
        for (int k=0; k<10; k++) 
            System.out.print(zaehler[k] + " ");
    }
}

Theoretisch sollte jede Zahl zwischen 1 und 10 gleich oft (im Beispiel also genau 1000 mal) auftreten. Die tatsächlichen Anzahlen schwanken geringfügig um den erwarteten Wert 1000:

995 1074 975 997 1016 985 961 1016 1007 974