TU Wien:Einführung in die Programmierung 2 VU (Puntigam)/Zusammenfassung
Datenabstraktion[Bearbeiten | Quelltext bearbeiten]
- Datenkapselung
- Zusammenfassen von Methoden und Variablen zu Einheit
- Data-Hiding
- Verstecken vor Zugriffen von außen
class Foo {
int x; // Objektvariable
static int y; // Klassenvariable
void foo(){}; // Objektmethode
static void bar(){}; // Klassenmethode
}
Foo f = new Foo();
instanziert ein neues Objekt der Klasse Foo, die Objektvariable x
wird auf 0 vorinitialisiert und der default constructor wird aufgerufen. Wir speichern eine Referenz auf unsere neue Instanz in der Variable f
und können danach (sofern es das scope erlaubt) auf f.x
und f.foo()
zugreifen.
Objektvariablen existieren in jeder Instanz. Klassenvariablen hingegen nur einmal. Objektmethoden können nur von einem Objekt aufgerufen werden. Klassenmethoden können von überall aufgerufen werden (sofern es das scope erlaubt) und sind unabhängig von Instanzen (innerhalb von bar()
kann man beispielsweise nicht auf x
oder foo()
zugreifen).
Innerhalb von Objektmethoden/konstruktoren referenziert das Schlüsselwort this
das momentane Objekt selbst.
Queue, Stack, Deque, Map[Bearbeiten | Quelltext bearbeiten]
- Queue
- (FIFO): add, poll, peek, size
- Stack
- (LIFO): push, pop, peek, size
- Double-ended queue
- (FIFO, LIFO): addFirst, pollFirst, peekFirst, addLast, pollLast, peekLast, size
- Assoziative Datenstruktur
- put(k,v), get(k), remove(k), containsKey(k), containsValue(v), size()
Rekursive Datenstrukturen[Bearbeiten | Quelltext bearbeiten]
Eine Klasse bzw. Datenstruktur ist rekursiv, wenn ein Objekt eine Referenz auf ein anderes Objekt enthalten kann. Solche Methoden, Klassen und Datenstrukturen sind induktiv aufgebaut. Am Beginn steht eine leere Datenstruktur (Fundierung), zu der nach und nach Knoten hinzugefügt werden.
- Singly linked list
- A → B → C
- Doubly linked list
- A ↔ B ↔ C
- Binary Tree
- left ← node → right
Vererbung und Schnittstellen[Bearbeiten | Quelltext bearbeiten]
Klassen enthalten Implementierungen. Interfaces hingegen spezifizieren eine Schnittstelle. Wenn eine Klasse eine andere Klasse erweitert, erbt sie alles von der Superklasse. Wenn eine Klasse eine Schnittstelle implementiert, muss sie sämtliche im Interface spezifizierten Objektmethoden implementieren. In Java kann eine Klasse nur von einer anderen Klasse erben aber mehrere Schnittstellen implementieren.
- deklarierter Typ
- Vom Compiler ermittelt, bestimmt welche Methoden aufrufbar sind.
- dynamischer Typ
- Erst zur Laufzeit bekannt, bestimmt in welcher Klasse eine Methode ausgeführt wird. z.B. Unterklasse oder Implementierung des deklarierten Typs
- statisches Binden
- kein dynamisches Binden beim Methodenaufruf wenn
- die Methode
static
,private
, oderfinal
ist - der deklarierte Typ keine Untertypen hat
- der Compiler feststellen kann, wo die Methode auszuführen ist
- die Methode
java.lang.Object[Bearbeiten | Quelltext bearbeiten]
In Java ist jede Klasse ein Untertyp von Object und erbt daher deren Objektmethoden, u.a. folgende:
String toString()
boolean equals(Object obj){ return this == obj; }
int hashCode()
Class getClass()
liefert dynamischen Typ
- Typische Implementierung von equals
public boolean equals(Object obj) {
if (this == obj) return true;// Optimierung, garantiert x.equals(x)
if (obj == null || getClass() != obj.getClass()) {
return false; // dyn. Typen verschieden, garantiert !x.equals(null)
}
NameOfThisClass that = (NameOfThisClass) obj; // access through that
...// type-specific comparison of this and that
}
- int hashCode() Bedingungen
-
- x.equals(y) → x.hashCode() == y.hashCode()
- x unverändert → x.hashCode() == x.hashCode()
- Wenn equals überschrieben wird, sollte auch hashCode überschrieben werden.
- Kann einfach mit
return Objects.hash(Object... objects);
implementiert werden.
In IntelliJ können equals() und hashCode() mit Rechtsklick → Generate generiert werden.
Iterable und Iterator[Bearbeiten | Quelltext bearbeiten]
Iterable und Iterator sind zwei Interfaces, welche ein einheitliches Iterieren über Objekte, unabhängig von der unterliegenden Datenstruktur ermöglichen. Iterieren verändert die unterliegenden Daten nicht.
- Iterierbare Klassen implementieren
Iterable<E>
und müssen daher dieIterator<E> iterator()
Methode implementieren, welche einen neuen Iterator zurückgibt. - Iterator-Klassen implementieren
Iterator<E>
, was folgende Methoden vorschreibt:boolean hasNext()
gibt an ob der Iterator ein weiteres Element hatE next()
gibt das nächste Element zurück oder wirft eineNoSuchElementException
, falls es kein weiteres Element gibt
Die in <> übergeben Klasse ist ein sogenanntes Generic und bestimmt den Rückgabetyp von next().
public class MyLinkedList implements Iterable<MyLinkedList.Node> {
// ...
@Override
public Iterator<Node> iterator() {
return new MyIterator();
}
class MyIterator implements Iterator<Node> {
Node current = head;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Node next() {
if (current == null) throw new NoSuchElementException();
Node returned = current;
current = current.next;
return returned;
}
};
}
// ...
}
Über MyLinkedList myList = new MyLinkedList();
könnte man wie folgt iterieren:
for (Iterator<Node> i = myList.iterator(); i.hasNext();) {
Node n = i.next();
// verwende n
}
Da dies allerdings mühselig und fehleranfällig ist, bietet Java die For-Each-Schleife, welche das Gleiche macht:
for (Node n : myList){
// verwende n
}
Weil man die Iterator-Klassen generell nur in iterator()
benötigt, bietet es sich an sie als anonyme Klassen zu definieren:
public class MyLinkedList implements Iterable<MyLinkedList.Node> {
// ...
@Override
public Iterator<Node> iterator() {
return new Iterator() {
Node current = head;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Node next() {
if (current == null) throw new NoSuchElementException();
Node returned = current;
current = current.next;
return returned;
}
};
}
// ...
}
Exceptions[Bearbeiten | Quelltext bearbeiten]
throw new IllegalArgumentException();
try {
FileReader reader = new FileReader("test.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Design by contract[Bearbeiten | Quelltext bearbeiten]
- Vorbedingung: muss vor Methodenausführung erfüllt sein
- Nachbedingung: muss nach Methodenausführung erfüllt sein
- Invariante: Bedingung in Bezug auf Objektzustand
- Schleifeninvariante: beschreibt, was sich in den Iterationen nicht ändert
Testen[Bearbeiten | Quelltext bearbeiten]
- Black-Box-Tests: unabhängig von Implementierung
- White-Box-Tests: von Implementierung abgeleitet
- Grey-Box-Tests: Testfälle vor Implementierung als Spezifikation festgelegt
Weitere Tests:
- Code-Review
- Sicherheitstest (Pentest)
- Belastungstest (Stresstest, Crashtest)
- Benutzeroberflächentest
Hilfsmittel zum Finden von Fehlerursachen:
- Code-Review
- Debugger
- Log-Dateien, Traces
- Einbeziehen von Entwicklern, Experten, Nutzern
Mehrstufiges Testen:
- Modul- bzw. Komponententest
- Schnittstellentest
- Integrationstest
- Systemtest (auch Abnahmetest)
Qualität und Optimierung[Bearbeiten | Quelltext bearbeiten]
- brauchbar
- zuverlässig und sicher
- effizient
- wartbar
Maßnahmen:
- Qualitätsbewusstsein
- Anforderungsanalyse
- ständige Kontrolle
- Besprechungen
- Analyse-Werkzeuge
- formale Beweise
- aufeinander Verlassen
- Qualitätsmanagement