Java-Zusatzmaterial Grundlagen der Programmkonstruktion 13S

[Übungsangaben (auf Homepage)]

Dieser Teil der Vorlesung verwendet das Skriptum als Unterlage, daher sind hier nur Beispielprogramme und einige Stichworte zu finden.

15.4.2013

Programme: fib, fib2

Syntax

Anweisungen (Statements), Ausdrücke, Deklarationen, Definitionen

Umgebung

Editor oder IDE (Netbeans oder Eclipse), Compiler javac, Laufzeitsystem java

Variablen

Wert, Name, Speicheradresse, Typ, Lebensdauer, Gültigkeitsbereich, Sichtbarkeit

Lebensdauer und Speicher von Variablen bei fib(3):

public static int fib(int n) {
	if (n<2)
		return n;
	else
		return fib(n-1)+fib(n-2);
}

Datentypen

Elementare Typen für Zahlen: int, long, float, double. Explizite und implizite Typumwandlungen.

Referenztypen behandeln wir später

Java API

Java™ Platform, Standard Edition 6 API Specification

Interessant ist dabei besonders java.lang und java.util.

18.04.2013

Wiederholung

Fakultät berechnen:

public class Fact { // Dateiname Fact.java

    public static int fact(int n) {
        if(n == 0) {
            return 1;
        } else {
            return n * fact(n - 1);
        }
    }

    public static void main(String[] args) {
        System.out.println(fact(Integer.parseInt(args[0])));
    }
}

Ein-/Ausgabe mit Scanner

Eingabe mit args beschränkt. Alternative: System.in.

Problem: Wie sollen Zeichen der Eingabe interpretiert werden? Lösung: Scanner

import java.util.Scanner;

public class Fact {

    public static int fact(int n) {
        /* ... */
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int i = sc.nextInt();

        System.out.println(i);
    }
}

Schleifen

while-Schleife

Fakultät von n! wird meist eher so berechnet, dass man bei 1 beginnend mit der nächst größeren Zahl multipliiert, bis man n erreicht.

10! = 1 * 2 * 3 * ... * 10

Als Programm: Beginne bei 1 und multipliziere auf, bis n erreicht wird.

    public static int fact(int n) {
        int i = 0;
        int result = 1;

        while(i <= n) {
            result *= i;
            i ++;
        }

        return result;
    }

while-Schleife immer bei "Solange Bedingung, wiederhole".

for-Schleife

Spezialfall von while-Schleife: For-Schleife mit Initialisierungsteil und Aktualisierungsteil.

for-Schleife

Spezialfall von while-Schleife: For-Schleife mit Initialisierungsteil und Aktualisierungsteil.

    public static int fact(int n) {
        int result = 1;

        for(int i = 0 ; i <= n ; i ++) {
            result *= i;
        }

        return result;
    }

Übungsaufgabe: Testen, ob eine Zahl eine Primzahl ist

do-while-Schleife

Unterschied zu while: Wird mindestens 1x durchlaufen.

    public static main(String[] args) {
        Scanner sc = new Scanner(System.in);

        do {
            /* ... */
            System.out.println("Noch einmal?");
        } while(sc.next().equals("ja"));
    }

Debuggen

Debuggen hilft beim Nachvollziehen des Programms. Je besser strukturiert das Programm, desto einfacher wird Debuggen. Debuggen ersetzt nicht den Designprozess!

Möglichkeiten zum Debuggen

Arrays

Arrays sind Objekte, die aus mehreren Elementen - alle vom selben Datentyp - zusammengesetzt sind. Arrays sind Referenztypen, das heißt, Variablen vom Typ Array enthalten das Array nicht, sondern zeigen auf ein Array. Wie alle Objekte müssen Arrays angelegt werden.

public class ArrayTest {
    public static void testSwapInts(int i0, int i1) {
        int tmp = i0;
        i0 = i1;
        i1 = tmp;
    }

    public static void swapInts(int[] arr) {
        int tmp = arr[0];
        arr[0] = arr[1];
        arr[1] = tmp;
    }

    public static void main(String[] args) {
        int i0 = 5;
        int i1 = 6;

        testSwapInts(i0, i1);

        System.out.printf("%d, %d\n", i0, i1); // Keine Änderung.

        int[] arr = new int[2]; // Erzeugt ein Array mit 10 Elementen.

        arr[0] = 5; // Erstes Element hat immer Index 0.
        arr[1] = 6;

        // Alternative: int[] arr = new int[]{5, 6};

        // Zugriff auf arr[2] fuehrt zu einem Fehler.

        swapInts(arr);

        // Neuer Schleifentyp: For-Each-Schleife. Gibt alle Elemente zurueck:

        for(int e : arr) {
            System.out.println(e);
        }

        /* Aequivalent zu
        for(int j = 0; j < arr.length; j++) {
            System.out.println(arr[j]);
        }
        */
    }
}

Übungsaufgabe: Sieb des Eratosthenes

Strings

Strings sind Zeichenketten, d.h., Datenstrukturen in denen man Text speichern kann.

Strings sind ein Referenztyp. Es gibt allerdings keine Methoden, mit denen man einen String inhaltlich ändern kann. Alle Methoden, die eine Zeichenkette zurückgeben, erzeugen eine neue Zeichenkette. Daher keine Seiteneffekte.

22.4.2013

Stoff: Datenstrukturen (Kap. 4) und Objekte (Kap. 3)

Datenstrukturen

Verkettete Liste (linked list):

nur ein Element bei der Implementierung von Datenstrukturen. Eine Datenstruktur enthält Daten und Operationen auf den Daten. Beispiel: Stack auf der Basis von linked lists. Andere naheliegende Datenstrukturen mit verketteten Listen: Menge, Sequenz.

Implementierung von Stack (oder Queue) mit Array

Gleiches Interface, (fast) gleiches Verhalten, unterschiedliche Implementierung: abstrakter Datentyp

Objekte

Gleiches Interface, teilweise unterschiedliches Verhalten. Dynamische Bindung von Methoden.

25.4.2013

Interfaces, Abstrakte Klassen, und Klassen

Beispiel: intstack mit Interface oder Abstrakter Klasse. Klassenhierarchie:

Methoden

Object

Ist oberste Klasse. Methoden: werden an alle Nachkommen vererbt.

Strings

"a"+b ist äquivalent zu "a"+b.toString();.

29.4.2013

Objektidentität

== vs. equals

Vererbung

am Beispiel toString, equals, hashCode, push, pop, isEmpty.

Beachte: Zusammenhang zwischen equals und hashCode muss man selbst sicherstellen

2.5.2013

Polymorphismus

Object hashCode
       toString
IntStackA hashCode
          toString
IntStackI hashCode
          toString

Verschiedene Variablen

lokale Variablen und Instanzvariablen, Lebensdauer

Information hiding

public und private

Zweck: Kommunikation mit anderen Programmierern (oder auch mit dem zukünftigen Ich): Auf welche Teile der Klasse kann man sich verlassen, und welche Teile können sich ändern; kann ich z.B. die Datenrepräsentation von Array auf verkettete Liste ändern?

Allgemeinere Stacks

Stack von Objekten oder generischer Stack

6.5.2013

Allgemeinere Stacks (Fortsetzung)

gebundene Generizität

Gutes Design? Klassenzusammenhalt (Cohesion): Man kann isIncreasing() ohne weiteres aus der Klasse entfernen, also geringer Zusammenhalt.

Assertions, Invarianten

13.5.2013

Testen (Skriptum 5.3). Black box test, white box test, unit test, integration test, regression test. "Tests können nur die Anwesenheit von Fehlern beweisen, nicht die Abwesenheit".

Debuggen (Skriptum 5.4) mittels Ausgabe-Anweisungen ("printf-Debugging"). Debuggen mit netbeans; bequemer, aber oft Zeitfresser.

Beweisen. Model Checking.

16.5.2013

LookupTable: Interface, linked list implementation

23.5.2013

Laufzeit von Algorithmen und algorithmische Komplexität (O(n)-Notation, Kapitel 4.3). Beispiel: Ramanujans Taxi; Problembeschreibung, Implementierung in Java mittels Table

Algorithmische Komplexität

Vorhersage: Äußere Schleife in taxi.main(): O(limit1/3) Iterationen; innere Schleife: O(limit1/3) Iterationen pro äussere Iteration, O(limit2/3) Iterationen insgesamt (A). Länge der Liste (B): Annähernd O(limit2/3) (fast immer ein put pro innerer Iteration in taxi.main()). Zahl der Iterationen in ListTable.get() insgesamt (C): O(limit4/3).

Messwerte/Faktor dazwischen:

   limit     A      B          C   t
 1250000  5110   5059   12893829   0.380s
       8  4.008  4.019    16.1158   20.55
10000000 20480  20330  207795013   7.808s
       8  4.001  4.009    16.0459   16.71
80000000 81937  81506 3334249330 130.476s

Erklärung dafür, dass der Faktor bei t größer ist als bei C: Caches funktionieren besser bei kurzen Listen.

27.5.2013

Einfache Hash-Tabelle

Messwerte/Faktor dazwischen:

    limit      A           C   t
  1250000   5110       78139   0.196s
        8   4.008     16.3183    2.18
 10000000  20480     1275098   0.428s
        8   4.001     16.1345    7.75
 80000000  81937    20573083   3.316s
        8   4.002     16.1052    9.44
640000000 327889   331332685  31.298s

Weiterhin: A~O(limit2/3), C~O(limit4/3), t~O(limit4/3) (wirkt sich bei t erst bei grossem limit aus, da bei kleinem limit auch nennenswert Zeit ausserhalb von get() verbracht wird).

Aber die konstanten Faktoren für C und t sind deutlich anders (Faktor zwischen den C-Werten):

           1250000  10000000   80000000 
ListTable 12893829 207795013 3334249330
            165.01    162.96     162.07
HashTable    78139   1275098   20573083

Der C-Faktor ist hier kleiner als 256, weil sich die Einträge nicht völlig gleich auf die Buckets (Einträge im Hash-Tabellen-Array) verteilen.

Allerdings kann man die algorithmische Komplexität hier leicht ändern, indem man die Anzahl der Buckets von limit abhängen läßt. Wenn die Anzahl der Buckets mit O(limit2/3) steigt, dann sollte die erwartete Anzahl der Elemente pro bucket O(1) ("konstant") betragen und C mit O(limit2/3) steigen.

    limit      A           C   t
  1250000   5110       14381   0.208s
        8   4.008       1.77    1.21
 10000000  20480       25476   0.252s
        8   4.001       7.27    1.94
 80000000  81937      185098   0.488s
        8   4.002       2.21    2.07
640000000 327889      408269   1.008s
Insgesamt ist der C-Faktor zwischen limit=1250000 und limit=640000000 gleich 28.39 (erwartet: Faktor 64).

In der Praxis ist die Anzahl der Einträge nicht so gut vorhersagbar wie bei diesem Beispiel. Daher setzt man gerne Hash-Tabellen ein, bei denen die Anzahl der Buckets verdoppelt wird, wenn die durchschnittliche Zahl der Einträge pro Bucket (load-factor) eine Grenze überschreitet.

3.6.2013

Binärer Suchbaum

Quellcode

Algorithmische Komplexität: get, put: O(ln(n)) bei balanciertem Baum, O(n) bei degeneriertem Baum. Beim Taxi-Problem ist der Baum nicht gut balanciert:

    limit     A          C   t
  1250000   5110    448003   0.244s
        8   4.008     7.77    1.54
 10000000  20480   3482773   0.376s
        8   4.001     7.77    3.79
 80000000  81937  27068328   1.424s
        8   4.002     7.80   10.00
640000000 327889 211217842  14.245s

Algorithmisches Prinizp: Teile und Herrsche (Skriptum 4.4).

Kein remove() in TreeTable: kompliziert, und die einfachste Version führt zu unbalancierten Bäumen.

Balancierte Bäume durch entsprechendes put: möglich, aber kompliziert.

6.6.2013

Iteratoren: Interfaces Iterable, Iterator, Klasse mit Iterator, Verwendung

10.6.2013

Mergesort (Skriptum 4.4)

Binäre Suche

13.6.2013

Collection-Klassen und Iteratoren, Maps und Iteratoren; Iteratoren sind fast immer innere Klassen, aber mit iterator() kann man Instanzen dieser Klassen herstellen und überall verwenden.

Quicksort

17.6.2013

Datenstrukturen und Kontrollfluss

Kontrollfluss orientiert sich oft an Datenstruktur:

Beipsiel: TreeTable.toString() und TreeTable.toList(); hier sieht man auch, dass eine verkettete Liste am einfachsten vom Ende her aufgebaut wird.

Vorsicht: Rekursion bei Listen und degenerierten Bäumen benötigt O(|Knoten|) Zwischenspeicher.

Mit Schleifen (und tail recursion) geht nur das Vorwärtsgehen in einer Liste, mit allgemeiner Rekursion vorwärts und rückwärts. Beispiel: Klammern

Besonders wenn mehr als eine Datenstruktur im Spiel ist, kann man u.U. den Kontrollfluss nicht an alle Datenstrukturen, die im Spiel sind, anpassen. Beispiel: gleichzeitiges Iterieren über zwei Bäume, die unterschiedlich aufgebaut sind, z.B., Ausgabe der Elemente zweier Suchbäume in sortierter Reihenfolge.

Iteratoren ermöglichen es, Datenstrukturen einfach abzuwandern (und auch mehrere zugleich). Allerdings muss man für eigene Datenstrukturen Iteratoren erst programmieren. Damit ergibt sich erst recht das Problem, dass man einen nicht zur Datenstruktur passende Kontrollfluss hat (aber wenigstens nur an einer Stelle und dort nicht vermischt mit anderen Aufgaben).

Beim konventionellen Programmieren eines Iterators muss man den Zustand des Kontrollflusses an der Stelle, an der next() das Resultat liefert, als Daten repräsentieren. Beispiel (mit Verfeinerungen): TreeTable.TTI2

20.6.2013

Multithreading

Ausführung mehrerer Kontrollflüsse gleichzeitig. Klasse Thread.

Zweck:

Probleme: Ein großes Problem dabei ist, dass solche Fehler oft schwer zu reproduzieren sind, da es davon abhängt, in welcher Reihenfolge die diversen Operationen der Threads ausgeführt werden.

Beispiel: counter

24.6.2013

Exceptions

Was tun bei einem Fehler, oder sonstigen Ausnahmezuständen, die man lokal nicht behandeln kann oder will? Man wirft eine Exception. Klasse Throwable, Beispiel.

Wenn man solche Zustände doch behandeln will (oder zumindest noch aufräumen will), kann man Exceptions auch fangen. Beispiel. Eigene Exception-Klassen machen, um sie fangen zu können:

Bei Exceptions kommt es auf den Aufrufbaum zur Laufzeit an (dynamic Scoping). Beispiel

Optimierung

Ziele in der Software-Entwicklung: Optimierung: Steigerung der Effizienz.

For Beginners: Don't do it
For Experts: Don't do it now

Aber: Effizienzaspekte nicht völlig ignorieren.

Häufiger Fehler: Viel Aufwand in die Optimierung eines Teiles stecken, der wenig kostet.

Häufiger Fehler: Optimierungstipps blind anwenden. Cargo-cult programming.

Vorlesung zu dem Thema: Effiziente Programme.

Ende der Vorlesung

Keine Vorlesung am 27.6.2013