Ausarbeitung 5AHITS

Werbung
Ausarbeitung
Fragenkatalog
APR
Angewandte Programmierung
5AHITS
2013/14
1/89
Inhalt
Programmerstellung in C und Java
Sprachumfang von C und Java
Datentypen in C und Java
UML
Relationen zwischen Klassen in Java
Streams in Java
Fehlerbehandlung in C und Exceptions in Java
Nebenläufige Programmierung in Java – Threads
Dateiarten - Lesen und Schreiben von Dateien in C und in Java
Dynamische Datenstrukturen
Collections in Java
Graphische Oberfläche in Java
JDBC
RMI
Java und XML
Java und Sockets
Java Generics
Drucken in Java
Android Programmierung
Logging und Unit-Testing in Java
2/89
Programmerstellung in C und Java
Die Erstellung von Programmen in C und Java ist in den wesentlichen Aspekten (Editor,
Präprozessor, Compiler, Objekt-/Byte-Code, Linker, make, Bibliothek, Dokumentation,
Sourcecode-Verwaltung, Programm, Ausführung, ...) zu erklären.
Anfertigen
C und Java Programme können ansich in jedem gewöhnlichen ASCII - Texteditor geschrieben
werden. Gespeichert werden C Programme (Quellcodedateien) mit der Endung .c und Java
Programme mit .java
Übersetzung eines C Programmes
●
●
●
●
C Programmcode mit .c gespeichert.
Der Präprozessor verarbeitet alle Zeilen, die mit einer Direktive (#) beginnen.
○ Header Files einfügen (#include…)
○ definierte Konstanten einfügen (Makros)
○ bedingte Kompilierungen (#if, #endif)
Der Compiler (gcc) übersetzt den Quellcode in eine Objektdatei (Modul, .obj) Es enthält den
Maschinencode. Jedes C File wird zu einem .obj File übersetzt.
Der Linker bindet alle Objektdateien sowie statische und dynamische Bibliotheken
zusammen -> man bekommt eine exe-Datei (Ausführbare Datei).
Übersetzung eines Java Programmes
●
●
●
Java Programmcode mit .java speichern
Der Compiler (javac aus JDK) erstellt eine gleichnamige .class Datei, diese enthält Bytecode
und ist somit Plattformunabhängig
Die JVM (Java Virtuell Machine) interpretiert den Bytecode während der Ausführung
Ausführen
Das C Programm kann einfach über die .exe ausgeführt werden. Für Java benötigt man zum
Ausführen das Java Runtime Enviroment (ist im JDK enthalten), um es auszuführen.
Compiler vs. Interpreter
Ein Compiler übersetzt das ganze Programm auf einmal. Alle Befehle werden in Maschinensprache
übersetzt und das fertige Maschinen-Programm wird zur Ausführung gespeichert.
Der Interpreter hingegen übersetzt während der Ausführung einen Programmteil, führt ihn aus,
übersetzt den nächsten usw. (Schritt für Schritt).
Sourcecodeverwaltung
Versionsmanagemantprogramme (z.B.: SVN - Subversion, GIT) verwalten die verschiedenen
Versionen der Programme. Somit können mehrere Benutzer gleichzeitig an einem Projekt arbeiten.
3/89
Man kann auch auf verschiedene Versionen vom Programmcode zugreifen und diese
Wiederherstellen.
make
Das Compilieren von Quelltext Dateien kann mit dem Programm make und einem makefile
organisiert werden. Mann kann eigenständig im makefile Abhängigkeiten von Dateien und diverse
Schritte wie löschen und kopieren zusammenfassen. Man gibt z.B. an, dass das ein Programm aus
den Quelldateien “Datei1.c”,“Datei2.c”, usw. erstellt wird.
Bibliotheken
Bibliotheken stellen bereits programmierte Datenstrukturen, Funktionen, Methoden etc. zur
Verfügung.
Statische Bibliotheken (in C)
Nach dem Kompilieren verbindet der Linker die Objektdatei mit dem Code aus den Bibliotheken.
Danach setzt der Linker das ausführbare Programm zusammen, dass alle statischen Bibliotheken
beinhaltet.
Dynamische Bibliotheken (in C)
Der Linker lädt die Programmteile erst bei Bedarf in den Arbeitsspeicher.
Beim dynamischen Binden werden Code und Bibliothek nur lose mit Referenzen verknüpft. Das
auflösen der Referenzen geschieht dann entweder vor Start des eigentlichen Programms oder erst
beim Laden der Bibliothek.
Vorteil von dynamischen Bibliotheken ist es, dass Programme von nachträglichen Fehlerbehebung in
der dyn. Bibliothek profitieren und nicht neu augeliefert werden müssen. Außerdemo muss eine
Bibliothek nur einmal für mehrere Anwendungen auf dem System vorhanden sein.
Nachteil, die Bibliotheken können auch so verändert werden, dass das Programm nicht mehr
funktioniert .
Dokumentation
C
Doxygen: Wenn man die Kommentierung richtig formatiert, kann man sich eine HTML Seite
(vergleichbar zur API Doc in Java), LATEX-Code, RTF-Dateien,... generieren lassen.
Java
Javadoc: ein Programm welches aus Quelltexten und den entsprechenden Kommentaren eine
Dokumentation im HTML - Format erstellt. abc.java → abc.html
Tag & Parameter
Ausgabe
Verwendung in
@author name
Beschreibt den Autor.
Klasse, Interface
4/89
@serial
Bei einem Serializeable Objekt
Klasse
beschreibt es die serialisierten
Daten des Objekts.
@param name
Parameterbeschreibung einer
description
Methode.
@return description
Beschreibung des
Methode
Methode
Rückgabewerts einer Methode.
Beispiel:
5/89
Sprachumfang von C und Java
Was bedeuten prozedural bzw. objektorientiert? (Siehe VSDB Ausarbeitung Frage 22
Softwaretechnik) Bestandteile der beiden Sprachen
(Kontrollstrukturen, Datentypen, …). Was ist nicht Teil der Sprache?
Prozedurale Programmierung
Das Programm wir solange in Teilprogrammen unterteilt (Prozeduren), biskurze zusammengehörige
Befehlsfolgen übrig sind. Diese werden in Prozedur zusammengefasst, als solche aufgerufen und
dann Schritt für Schritt abgearbeitet. Ein wichtiger Bereich hier sind die Algorithmen (Folge von
Anweisungen und Anleitung zum lösen eines Problems). Des weiteren gibt es diverse Methoden, um
einen Algorithmus zu beschreiben. (Pseudocode - Programmablaufpläne - Flussdiagramme)
call-by-value
Bei CBV werden die an eine Funktion übergebenen Daten KOPIERT. Es besteht dann keine
Verbindung mehr zwischen den Daten beim Aufrufer und den Daten in der Funktion. Werden große
Objekte auf diese Weise übergeben, so ist das sehr kostspielig in Bezug auf Rechenzeit (der
Kopiervorgang) und Speicherplatz (Daten sind mehrmals vorhanden).
call-by-reference
Anstatt die Daten zu kopieren werden Referenzen auf die Daten übergeben. Man kann sich das als
Zeiger vorstellen: Beim Aufrufer zeigt eine Variable auf ein bestimmtes Datum, in der Funktion
zeigt eine andere Variable auf dasselbe Datum. Methodenaufrufe an einem so übergebenen Objekt
arbeiten also auf demselben Objekt, das auch außerhalb sichtbar ist.
Übergabe Parameter der Main-Funktion in C
Die Übergabe von Funktionsargumenten ist Ihnen sicherlich bekannt. Sie haben die Möglichkeit, z.B. Werte,
Strings, Zeiger und jegliche Datentypen bei einem Funktionsaufruf zu übergeben. Selbstverständlich können
Sie auch an die Funktion main() Parameter übergeben. Dies erfolgt beim Aufruf der Funktion, also beim
Programmaufruf. Die bisherige Definition der Funktion:
int main(void)
wird ersetzt durch die vollständige Definition:
int main(int argc, char *argv[])
Das Argument int argc repräsentiert dabei die Anzahl der übergebenen Parameter, die char *argv[] beinhaltet.
Bei char *argv[] handelt es sich um einen Vektor mit Zeigern auf Strings, also auf die übergebenen
Parameter.
6/89
argv[0] beinhaltet immer den String des Programmaufrufes wie z.B. C:\Programme\main.exe, argc hätte
somit den Wert 1, da kein zusätzlicher Parameter übergeben wurde.
Lautet der Programmaufruf stattdessen: C:\Programme\main.exe -debug -1000, dann hätte argc den Wert 3,
und der Inhalt von char *argv[] sähe wie folgt aus:
argv[0] enthält: C:\Programme\main.exe
argv[1] enthält: -debug
argv[2] enthält: -1000
Objektorientierte Programmierung
Ziel ist nicht, die Funktionsweise von Rechnern, sondern eine Abbildung der “realen” Welt
abzubilden. Im besten Fall soll ein reales “Ding” in der Programmiersprache beschrieben werden.
Klassen, welche den Bauplan für diverse reale Gegenstände liefern, beinhalten desweiteren
● einen Zustand, beschrieben durch die Werte ihrer Attribute
● ein Verhalten, beschrieben durch die Methoden
● eine Identität, Name des Objekte
● Methoden können wie Variablen gekapselt werden (heißt: public, private, protected, … sein)
Dynamische Bindung:
Bei der Vererbung haben wir eine Form der Ist-eine-Art-von-Beziehung, sodass die
Unterklassen immer auch vom Typ der Oberklassen sind. Die sichtbaren Methoden, die
die Oberklassen besitzen, existieren somit auch in den Unterklassen. Der Vorteil bei der
Spezialisierung ist, dass die Oberklasse eine einfache Implementierung vorgeben und
eine Unterklasse diese überschreiben kann. Bietet eine Oberklasse eine sichtbare
Methode an, so wissen wir immer, dass alle Unterklassen diese Methode haben werden,
egal, ob sie die Methode überschreiben oder nicht.
Kontrollstrukturen in C und Java
Sind Verzweigungen und Schleifen, welche in C und Java von der Syntax her identisch sind.
Schleifen
●
●
●
for - Schleife
○ for(Definierung ; Bedingung ; Ereignis)
○ for( int i = 0; i < 10 ; i++)
while - Schleife
○ while(Bedingung)
○ while (i < 10 )
do - while - Schleife
○ do { }while(Bedingung)
○ gleich wie While, wird aber mindestens einmal ausgeführt.
Bedingungen
●
●
if - else
○ if(Bedingung) {} else{} if … else
switch/case
7/89
○
switch (Ausdruck){
case Vergleich1: Anweisung1; break;
case Vergleich2: Anweisung2; break;
default: Anweisung2; break;
};
Break und Continue lässt in C und Java Kontrollstrukturen beeinflussen. Mit break kann man
Schleifen verlassen, mit continue überspringt man den restlichen schleifen Körper bleibt aber n der
Schleife.
In C gibt es noch goto, um im Programm hin und her zu springen. Dies sollte aber nicht verwendet
werden, da es unübersichtlichtlich wird.
(helf): Nicht Teil der Sprache: Ein-/Ausgabe, Parallelisierung, mathem. Funktionen,
(michi): Nicht Teil der Sprache: Alles wofür man ein #include braucht
8/89
Datentypen in C und Java
Welche Arten von Datentypen (Werte, Byteanzahl, Zeichenkette, zusammengesetzte
Datentypen, Verweise auf Variablen) unterstützen die beiden Sprachen?
Was ist ein Datentyp
Definierte Variablen welche einen Bestimmten Wert speichern. Es wird zwischen vordefinierten
Datentypen und selbst erstellten unterschieden. Ein Datentyp besteht im Wesentlichen aus zwei
Teilen. Dem Typ, dementsprechend wird der Speicher zugewiesen und der Name, mit dem man den
Datentyp ansprechen kann.
(helf): Datentyp: Name, Speicherbedarf, Wertebereich, interne darstellung
Berechnung der Größen
1 Byte = 8 Bit, 2^8 = 256
z.B. short (2 Byte) … 2^16
short (von – bist + ) positiver Bereich 2^16 / 2 (0 - ((2^16)/2-1))
unsigned short(nur plus Bereich) 2^16 ~65.000
Hinweis: Wenn man unsigned vor einen Datentyp setzt, bleibt der Speicherplatz gleich, nur der
Wertebereich wird verändert.
Datentypen in C
Elementare Datentypen
●
●
●
●
●
●
char (1 Byte)
short (2 Byte)
int (2/4 Byte)
long (4 Byte)
long long (8 Byte)
(helf): eigentl. nur hinsichtlich Länge zueinander festgelegt: char <= short <= ...
Gleitpunkttypen
●
●
●
float (4 Byte)
double (8 Byte)
long double (10 Byte)
Strukturen
Sammlung von Verschiedenen Datentypen => ergibt einen neuen Datentyp.
Schlüsselwort: struct
9/89
Anlegen des neuen Datentyps
Struct StructName {typ1 komp1; ...};
typedef …
variablendeklaration
Zugreifen auf eine Komponente
variable.komponentenName
ZeigerAufStruktur->KomponentenName
(*ZeigerAufStruktur).KomponentenName
Pointer
Eine Variable, in der die Speicheradresse einer anderen Variable gespeichert ist.
(helf): Felder/Zeichenketten
(helf): Wert-/Referenzsemantik
Datentypen in Java
Elementare (Primitive) Datentypen
●
●
●
●
●
●
●
●
●
boolean (1 Byte)
char (2 Byte)
short (2 Byte)
int (4 Byte)
long (8 Byte)
float (4 Byte)
double (8 Byte)
mit/ohne Vorzeichen?
(helf): String
Klassen in Java
Klassen sind das wichtigste Merkmal objektorientierter Programmiersprachen. Eine Klasse definiert
einen neuen Typ, beschreibt die Eigenschaften der Objekte und gibt somit den Bauplan an. Jedes
Objekt ist ein Exemplar (auch ‚Instanz‘) einer Klasse.
Was enthält eine Klasse
Attribute:
Bestehend aus den elementaren Datentypen
Konstruktor: Funktion, welche bei der Initialisierung aufgerufen wird. Methode, welche den
Namen der Klasse hat und keinen Rückgabewert hat.
Wird ein Objekt initialisiert, wird eine Referenz auf das Objekt gelegt (ähnlich wie ein Zeiger in C)
Haus meinHaus = new Haus();
meinHaus hat nun eine „Referenz“ auf Haus
Haus einAnderesHaus = meinHaus;
einAnderesHaus und meinHaus sind eigtl. gleich, es wird nicht komplett Kopiert sondern nur die
Referenz auf das Haus
Null Referenz: Jede Referenz kann auf null gesetzt werden
10/89
Wrapper-Klassen
Für alle primitiven Datentypen gibt es in Java auch eine Wrapper Klasse (Adapter). Diese sind (wie
alle Klassen) eine Unterklasse von Object, d.h.: Sie können auch wie ein Objekt behandelt werden.
primitiver Datentyp
boolean
char
int
…
Wrapper-Klasse
Boolean
Character
Integer
…
11/89
UML (Unified Modeling Language)
Durch welche Konzepte/Werkzeuge/... kann UML die Software-Entwicklung unterstützen?
Wichtige Diagramme sind exemplarisch zu erklären. Welche Möglichkeiten zur
Synchronisation mit dem Quellcode bestehen?
Was ist UML?
●
●
●
●
●
Notation für die Modellierung
Allgemeine Diagramme, Software, Hardware, ...
Verschiedene Diagrammtypen
Umfangreiche Notation
Einsatzgebiete:
○ Spezifizierung
○ Visualisierung
○ Dokumententation
○ Konstruktion eines Entwurfs
○ Konstruieren der Architektur einer Anwendung
Diagramme im Überblick
Strukturdiagramme
Strukturdiagramme stellen den technischen Aufbau dar. Zum Beispiel den Aufbau von Datenbanken
oder Klassen.
● Klassendiagramm (class diagram)
● Objektdiagramm (object diagram)
● Kompositionsstrukturdiagramm (composite structure diagram)
● Komponentendiagramm (component diagram)
● Verteilungsdiagramm (deployment diagram)
● Paketdiagramm (package diagram)
● Profildiagramm
Klassen Diagramm (class diagram)
● Darstellung von Klassen und deren Beziehungen
● Verwendet in der OOP
● Dargestellt wird:
○ Klassenname
○ Eigenschaften (Attribute)
○ Methoden (Operationen)
○ Beziehungen (Assoziationen)
○ Vererbungen (Generalisierungen)
○ (helf): Realisierung/Implementierung
● Sichtbarkeit:
12/89
●
○ + public
○ # protected
○ ~ package
○ - private
Kardinalität:
○ 1 für genau ein Objekt
○ * für viele, d.h. Null oder mehr Objekte
○ 0..1 für höchstens 1 Objekt
○ m..n für mindestens m aber höchstens n Objekte
Beispiel
Objektdiagramm (object diagram)
● Darstellung von Objekten und deren Assoziationen
● Möglichkeit nur bestimmte Attribute aufzulisten
● Aufbau ähnlich dem Klassendiagramm
● Änderungen:
○ Anstatt Klassenname: Instanzname+ : [Classifiziername bzw. Typ]
● Darunter steht anschließend:
○ Wertepaar (Variablenname : Wert der Variable)
● Kardinalität:
○ Beziehung zwischen 2 Attributen
Beispiel
Verhaltensdiagramme
Im Verhaltensdiagramm wird beschrieben WER WAS macht. Danach wird erst ein Strukturdiagramm
erstellt.
● Anwendungsfalldiagramm (use case diagram)
● Sequenzdiagramm (sequence diagram)
13/89
●
●
●
●
●
Aktivitätsdiagramm (activity diagram)
Kommunikationsdiagramm (communication diagram)
Interaktionsübersicht (interaction overview diagram)
Zeitverlaufsdiagramm (timing diagram)
Zustandsdiagramm (state chart diagram)
Anwendungsfalldiagramm (use case diagram)
● Stellt Szenarien und Akteure mit ihren Abhängigkeiten und Beziehungen dar
● zeigt eine bestimmte Sicht auf das erwartete Verhalten eines Systems (Verwendung für die
Spezifikation)
● Beschreibt was zwischen Akteure und System passiert, nicht wie es im System passiert
● Akteure können von einander erben
● Notation:
○ <include> ... unbedingter Aufruf von Teilfunktionalität (immer)
○ <extends> ... bedingter Aufruf einer Teilfunktionalität (nur wenn notwendig)
Beispiel
Sequenzdiagramm (sequence diagram)
● Stellt den zeitlichen Ablauf des Austausch von Nachrichten dar
● Stellt einen Weg durch einen Entscheidungsbaum innerhalb eines Systemablaufes dar
● Entscheidungsbaum = aufeinanderfolgende, hierarchische Entscheidungen
● Zeigt welche Objekte zu welcher Zeit erstellt werden und deren Lebensdauer
● Zeigt Methodenaufrufe zwischen den Objekten
● Beispiel, Message-Typen, async/sync/verzögerte Message
Programmieren mit Hilfe von UML und Synchronisation von Quellcode
●
●
●
●
Grafische Darstellung von Codeteilen
Erzeugen von Programmcode von einem UML Diagramm
○ In Java und C++
Erzeugen von UML Diagrammen aus dem Quellcode (Reverse Engineering)
Software:
○ von IBM Rational Rose 1 Lizenz für Java entwickler 6050 $
○ Mit Argo UML oder Star UML ist es möglich Quellcode aus einem Diagramm oder ein
Diagramm aus einem Quellcode zu generieren.
14/89
Relationen zwischen Klassen in Java
Welche Relationen bestehen zwischen Klassen und welche Attribute können diese haben?
Was bedeuten Polymorphie bzw. ORM?
(LINK VOM GAMS: http://timpt.de/topic95.html )
Eine Relation zwischen zwei Klassen lässt sich am besten mit einer Assoziation beschreiben. Die
Assoziation beschreibt desweiteren, wie oft eine Verbindung besteht. Die Assoziation wird mit den
Phrasen
● benutzt eine
● ist zugeordnet zu
● hat eine Beziehung zu
beschrieben.
In einer Verbindung stehen immer zwei unabhängige Objekte welche durch den Aufruf der
Methoden “verbunden” werden. Nachdem das ende der Methode und somit das Ziel der Beziehung
erreicht wurde können beide Objekte weiterbestehen.
Kardinalität
●
●
●
●
1:1
1:0
1:n
n:n
eines hat genau ein anderes
eines hat genau ein anderes oder auch keines
eines hat eines oder mehrere
eines oder mehrere können eines oder mehrere haben
Beziehungen von Klassen
Vererbung
Die Verbung von Klassen untereinander wird durch eine durchgezogene Linie mit einem offenen
Pfeil, der auf die Oberklasse zeigt, dargestellt.
15/89
Schnittstellenimplementation
Die Implementation einer Schnittstelle wird mit einer gestrichelten Linie dargestellt. An der
Schnittstelle wird ein geschlossener, nicht ausgefüllter Pfeil angebracht.
Assoziation
Eine Assoziation von Klassen wird durch eine durchgezogene Linie, die beide Klassen miteinander
verbindet, dargestellt.
Multiplizität
Die Multiplizität eines Objektes gibt an, wieviele Objekte des einen Typs mit Objekten des anderen
Typs (oder Objekten anderer Typen) verbunden sein können oder verbunden sein müssen.
Folgende Angaben zur Multiplizität können angegeben werden:
16/89
Beschreibung
Beispiel
Bedeutung
Angabe einer definierten Zahl
3
Genau diese Anzahl von Objekten
Angabe von Minimum und
3 .. 7
Mindestens 3 Objekte und maximal 7
Maximum
Objekte
Der Joker
*
Beliebig viele Objekte
Die Angabe von Multiplizitäten könen durch Trennung von Kommata auch kombiniert werden.
Beispiele:
1,3,6
Ein, drei oder 6 Objekte
0, 4 .. 9
Entwender kein Objekt oder vier bis neun
Objekte
0 .. *
Beliebig viele Objekte
*
Beliebig viele Objekte
4
Genau vier Objekte
Das nachfolgende Diagramm zeigt die Assoziation von Auto, Fahrer und Mitfahrer bei einer
Autofahrt. Es wird dabei davon ausgegangen, dass der Wagen für 5 Personen zugelassen ist.
Aggregationen
Desweiteren gibt es noch Aggregationen. Dies sind besondere Assoziationen. Es sind Teile-einerganzen-Beziehung. Unterschieden wird zwischen starker (die Teile sind Existenzabhängig vom
Ganzen) und schwacher (jedes Teil kann selbst existieren) Aggregation. Jedes Teil kann nur eine
Aggregation haben.
Als Aggregation Unternehmen - Abteilung - Mitarbeiter, wenn sich die Abteilung auflöst gehören die
Mitarbeiter noch zum Unternehmen.
Kompositionen
Ein weiterer Sonderfall der Assoziation ist die Komposition. Der Unterschied hier ist, dass alle
abhängig von einander sind. Unternehmen und Abteilung, wenn sich das unternehmen auflöst wird
es die Abteilung auch nicht mehr geben.
17/89
Als Aggregation Unternehmen - Abteilung - Mitarbeiter, wenn sich das Unternehmen auflöst, kann
die Abteilung nicht weiter existieren.
Beschreibung mit Bildern
1. Assoziation
1.a. ungerichtete Assoziation
Bild
Code
Code
class Buch{
Verlag v;
}
class Verlag{
ArrayList<Buch> buecher;
}
Einfache Beziehung der Klassen, die Klassen kennen sich und greifen auf die Methoden voneinander
zu.
1.b. gerichtete Assoziation
Bild
Code
class Buch{
Verlag v;
}
Das Buch hat einen Verlag. (hat es doch bei ungerichteter auch? what kind of sorcery is this?)
2. Schwache Aggregation
Bild
Code
class Buch{
ArrayList<Artikel> artikel;
public void addArtikel(Artikel artikel){ … }
}
Ist eine besondere Form der Assoziation. Bedeutet “Ist Teil von”.
Artikel ist Teil eines Buches. Ein Buch kann mehrere Artikel haben.
Wird das Buch gelöscht, besteht der Artikel weiter, und umgekehrt.
3. Komposition
Bild
Code
class Buch{
ArrayList<Verzeichnis> verzeichnisse;
}
“Besteht aus”
Verzeichnis ist Teil des Buches. Ein Buch kann mehrere Verzeichnisse haben.
Wird das Buch gelöscht, wird auch das Verzeichnis gelöscht und umgekehrt.
18/89
Polymorphismus
Bezieht sich auf die Methoden von Java Klassen und bedeutet Vielgestaltigkeit. Wenn Klassen den
selben Methodennamen verwenden aber die Implementierung unterschiedlich ist. Beispiel die
Klasse Hund und die Klasse Katze erben von Tier die abstrakte Funktion gibLaut(), bei Hund würde
man “Wuff” und bei Katze “Miau” programmieren. Je nachdem, ob das Objekt Tier eine Katze oder
ein Hund ist, wird eine andere Funktion aufgerufen.
Tier a = new Hund();
a.gibLaut();
Tier b = new Katze();
b.gibLaut();
Object Relational Mapping (ORM) (Von Wikipedia: object-relational mapping)
Object-relational-mapping sind Techniken, um Assoziationen aus Programmen in Datenbanken zu
übertragen. Implementiert wird diese Technik über Bibliotheken. Eine standardisierte Schnittstelle
für Java ist z.B. Java Persistence API. Abgebildet werden die Objekte und Relationen in einem
relationalen Datenbankmodell.
SIEHE VSDB AUSARBEITUNG UNTER DEM PUNKT DATENMODELLE.
Weiter unten gibt es zwei Unterpunkte namens Objekt rationales Mapping (ORM) und Impedance
mismatch
19/89
Streams in Java
Wie unterstützen Streams den Zugriff auf Daten? Welche Arten von Streams sind wofür gut
geeignet? Wie können Streams kombiniert werden?
●
●
●
●
Java verwirklicht die I/O Verarbeitung mithilfe von Streams
Ein Stream ist entweder eine Quelle (Input) oder ein Ziel (Output)
Streams können Filezugriffe, Daten von Sockets sowie auch diverse Peripheriegerät sein.
Wichtig ist die richtige Fehlerbehandlung:
Immer im finally Block den Stream wieder schließen
Beispiel
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader("textfile.txt"));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
...
}
} catch (IOException e) {
System.err.println(e.getMessage());
} finally {
try { in.close(); } catch (IOException e) { }
}
File mit ObjectOutputStream
●
●
●
Objekt muss serializeable sein
Schreiben kein Problem
Beim Lesen muss der Typ des Objekts bekannt sein
ObjectOutputStream output = new ObjectOutputStream(
new FileOutputStream("object.data"));
MyClass object = new MyClass();
output.writeObject(object);
output.close();
Wie unterstützen Streams den Zugriff auf Dateien?
Bytes, primitive Datentypen, Unicode Character oder serialisierte Objekte.
InputStream
:
Lesen von einer Datei
(binär)
20/89
OutputStream :
Schreiben in eine Datei (binär)
Reader
Writer
Lesen Schreiben -
:
:
Unicode
Unicode
Gegenüberstellung der verschiedenen Klassen
Char Streams
Reader
Writer
FileReader
FileWriter
InputStreamReader
BufferedReader
BufferedWriter
CharArrayReader
CharArrayWriter
Byte Streams
InputStream
OutputStream
FileInputStream
FileOutputStream
OutputStreamWriter
BufferedInputStream
BufferedOutputStream
ByteArrayInputStream
ByteArrayOutputStream
Welche Arten von Streams sind wofür gut?
Standard Streams :
System.in
System.out
System.err
-
Konsole
InputStreamReader(System.in)
System.out.println
Byte Streams :
liest je ein Byte -> Objektdaten(Sockets,Grafiken,Objects,Videos,....)
Char Streams :
liest je ein Zeichen -> Textdaten
Object Streams:
Schreibt ein ganzes Objekt. Um schreiben/lesen zu könen, muss das Objekt
serialized sein. Beim Lesen (InputStream) muss man wissen, um welchen Objekttyp es sich handelt.
Wie können Streams kombiniert werden?
Da alle Streams von nur 4 abstrakten Basisklassen abgeleitete sind, bieten einige Streams die
Möglichkeit, sich mit anderen Streams verketten zu lassen => Erweiterung der Funktionen.
Byte für byte lesen einer Datei -> Langsam
Großen Block an Daten lesen und diesen nachher byte für byte verarbeiten -> buffering
z.B. InputStream in BufferedInputStream
BufferedInputStream input = new BufferedInputStream(
new FileInputStream("input-file.txt"));
Inputstream auf Reader
21/89
InputStream inputStream = new FileInputStream("c:\\data\\input.txt");
Reader
reader
= new InputStreamReader(inputStream);
int data = reader.read();
while(data != -1){
char theChar = (char) data;
data = reader.read();
}
reader.close();
22/89
Fehlerbehandlung in C und Exceptions in Java
Fehlerbehandlung wird in C und Java unterschiedlich unterstützt. Wie läuft Fehlerbehandlung
ab und wie sieht gute Fehlerbehandlung aus?
Fehlerbehandlung in C
Da C (standardmäßig) keine Exceptions oder ähnliche Mechanismen unterstützt, muss jeder
mögliche Fehlerfall manuell überprüft werden. Dazu gibt es mehrere Möglichkeiten.
Rückgabewert überprüfen
Die meisten Funktionen in C geben einen speziellen Rückgabewert zurück, wenn ein Fehler
aufgetreten ist.
Zeiger überprüfen
Nach jeder Aktion die einen Zeiger zurückgibt, muss überprüft werden, ob dieser Zeiger nicht NULL
(0) ist. Die meisten Funktionen (fopen, malloc, …) geben 0 zurück wenn die Aktion fehlgeschlagen ist.
Nachdem eine Datei geöffnet wurde, muss überprüft werden, ob die Aktion auch erfolgreich war.
Die Funktion fopen(...) gibt NULL (0) zurück, wenn die Datei nicht geöffnet werden konnte. Wenn
das der Fall ist, sollte eine Fehlermeldung ausgegeben und das Programm beendet werden. Die
Fehlermeldung wird in den Standard-Error-Stream stderr geschrieben.
int main(void) {
FILE *fp = fopen("keinedatei.dat", "r");
if (fp == NULL) {
fputs("Datei konnte nicht geöffnet werden!", stderr);
return 1;
}
return 0;
}
Das Makro assert
Das Makro assert(...) ist in der Header Datei assert.h definiert. Es wird verwendet, um zu überprüfen,
ob eine Bedingung Wahr ist. Wenn dem Makro assert ein wahrer Wert (TRUE) übergeben wird, läuft
das Programm normal weiter. Wenn der Wert hingegen falsch (FALSE) ist, wird das Programm mit
einer Fehlermeldung abgebrochen. Diese Fehlermeldung enthält den Dateinamen und die
Zeilennummer, in der das assert-Makro steht, wodurch die Fehlersuche erleichtert wird.
int main(void) {
FILE *fp = fopen("keinedatei.dat", "r");
assert(fp != NULL)
return 0;
}
23/89
Fehlercodes mit errno
Eine weitere Möglichkeit, Fehler in C zu behandeln, sind Error Codes.
In der Header Datei errno.h ist eine globale Variable errno definiert. Viele Funktionen aus der
Standardbibliothek verwenden diese Variable, um über aufgetretene Fehler zu informieren.
Außerdem sind in der Header Datei Makros für die einzelnen Fehlerfälle definiert.
Je nachdem, welcher Fehler auftritt, wird der Variable errno ein entsprechender Wert
zugewiesen.
Beispiele für Error Codes:
EDOM
(Domain error) Unzulässiges Argument für eine mathematische Funktion.
EILSEQ
(Illegal sequence) Es wurden Bytes entdeckt, die keine gültigen Zeichen darstellen.
ERANGE
(Range error) Das Ergebnis liegt außerhalb des darstellbaren Bereichs.
0
(NULL) Kein Fehler aufgetreten.
Diese vier Werte sind standardisiert, allerdings definieren die meisten Compiler noch viele weiter
Werte.
Die Funktion perror(...) gibt die übergebene Fehlermeldung und eine zu errno gehörende
Fehlermeldung auf stderr aus.
#include <errno.h>
#include <math.h>
#include <stdio.h>
int main(void) {
sqrt(-1);
if(errno != 0)
perror("Fehler bei sqrt");
return 0;
}
Ausgabe: Fehler bei sqrt: Domain error
Exceptions in Java
In Java werden (die meisten) Fehler als Exceptions (“Ausnahmen”) dargestellt. Exceptions können in
einem try-catch Block abgefangen und verarbeitet werden. Wobei Programmteile, die eine
Exception werfen können, in einem try-Block eingeschlossen werden, und die Behandlung dieser
Exceptions im nachfolgenden catch-Block geschieht. Wenn im try ein Fehler auftritt, wird die
Programmausführung sofort unterbrochen und der passende catch-Block ausgeführt. Danach läuft
das Programm aber normal weiter.
24/89
Wenn im try mehrere verschiedene Exceptions auftreten können, sollten auch mehrere catch-Blöcke
für die verschiedenen Exceptions vorhanden sein. Nachdem ein Fehler aufgetreten ist, wird nach
dem ersten passenden catch-Block gesucht, darum sollten auch die speziellen vor den generellen
Fällen stehen.
In Java zwingt einen der Kompiler dazu, bestimmte Funktionen mit einem try-catch zu versehen.
Allerdings ist es nicht sinnvoll, den catch-Block leer zu lassen, da dann die Fehlermeldung einfach
unterdrückt wird.
Vorteile
● Keine Fehlercodes, die manuell verwaltet werden müssen
● Genaue Lokalisierung des Fehlers mit Hilfe des Stacktrace
● Trennung von Programmlogik und Fehlerbehandlung
● Threadsicher
Beispiel
String string = "19%";
try{
Integer.parseInt( string );
}
catch ( NumberFormatException e ){
//Spezielle Exception
System.err.println( "Konvertierung fehlgeschlagen!" );
}
catch ( Exception e ){
//Generelle Exception
e.printStackTrace();
}
Der Stacktrace
Im Stacktrace wird der Name der Exception, die Klasse, die Funktion sowie die Zeilennummer
angezeigt, in der der Fehler aufgetreten ist.
Exceptions fangen
Exceptions sollten immer dort gefangen werden, wo sie am besten behoben werde können. Aber sie
sollten spätestens dann gefangen werden, bevor sie den Einflussbereich des Programmierers
verlassen.
Exceptions sollten spätestens gefangen werden in der
● main() Methode.,
● run() Methode von Threads
● event() Methoden von GUIs
● doGet() oder doPost() Methode von Servlets
Exceptions weiterreichen
Wenn eine Exception nicht sofort behandelt werden soll, kann sie an den Aufrufer weitergegeben
werden. Dazu wird die Funktion mit einem throws versehen. Dadurch muss sich der Aufrufer um
die Behandlung der Exception kümmern.
int convertString(String string) throws NumberFormatException {
return Integer.parseInt( string );
}
25/89
Abschlussbehandlung mit finally
Der finally-Bock kommt nach dem catch-Block und wird in jedem Fall ausgeführt. Er wird ausgeführt,
egal ob eine Exception aufgetreten ist oder nicht und sogar wenn im try oder catch-Block ein
return steht.
Der finally-Block sollte dazu verwendet werden, um z.B. alle geöffneten Ressourcen wieder zu
schließen oder Änderungen aus dem try-Block rückgängig zu machen.
Im nachfolgenden Beispiel wird im finally-Block überprüft, ob die Datei geöffnet ist. Wenn das der
fall ist, wird versucht sie zu schließen. Allerdings muss die Methode close() auch wieder von
einem try-catch umrahmt werden.
RandomAccessFile f = null;
try{
f = new RandomAccessFile( "randomFile.dat", "r" );
f.close();
}
catch ( FileNotFoundException e ){
System.err.println( "Datei ist nicht vorhanden!" );
}
catch ( IOException e ){
System.err.println( "Allgemeiner Ein-/Ausgabefehler!" );
}
finally{
if ( f != null )
try { f.close(); } catch ( IOException e ) { }
}
Exception und RuntimeException
Die Klasse RuntimeException ist eine Unterklasse von Exception mit einer Besonderheit: sie muss
nicht abgefangen werden. RuntimeExceptions werden genutzt, um Programmierfehler, wie z.B.
IndexOutOfBound, anzuzeigen. Diese Exceptions können zwar mit einem catch-Block abgefangen
werden, sollten es aber nicht, da sie einen Programmierfehler anzeigen. RuntimeExceptions werden
auch als ungeprüfte Ausnahmen und Exceptions als geprüfte Ausnahmen bezeichnet.
Ungeprüfte ausnahmen sollen für Fehler verwendet werden, die nicht mehr behoben werden
können (z.B. Programmierfehler, …).
Exception Objekte
Alle Exceptions sind vom Interface Throwable abgeleitet und alle speziellen Exceptions (z.B.
NumberFormatException) sind wiederum von Exception abgeleitet. Dadurch lassen sich mehrere
Exceptions mit ihrer Oberklasse abfangen
Methoden von Exceptions
Einige wichtige Methoden von Exceptions sind:
e.getClass().getName(); //Name der Exception (java.lang.NumberFormatException)
26/89
e.getMessage(); //genauere Beschreibung (For input string: "19%")
e.toString();
//Name und Beschreibungjava.lang.NumberFormatException: For input
Exception werfen
Um eine Exception auszulösen, muss zuerst ein Exception Objekt erzeugt und dieses dann mit
throw geworfen werden.
Player( int age ){
if ( age <= 0 )
throw new IllegalArgumentException("Alter <= 0!" );
this.age = age;
}
Achtung: In diesem Fall muss kein throws IllegalArgumentException angegeben werde,
da RuntimeExceptions nicht extra angegeben werden müssen.
Eigene Exceptions
Um eine eigene Exception-Klasse zu bilden, muss diese von Exception (oder einer Unterklasse davon)
abgeleitet sein. Außerdem sollte es einen Konstruktor geben, der einen String als Argument
annimmt und ihn an die Oberklasse weiterleitet.
public class MyException extends Exception
{
public MyException () { }
public MyException ( String s )
super( s );
}
{
}
Aufruf
Diese Exception kann dann mit throw geworfen werden.
if ( age <= 0 )
throw new MyException( "Kein Alter <= 0 erlaubt!" );
27/89
Nebenläufige Programmierung in Java – Threads
Nebenläufigkeit bekommt durch Vielkernprozessoren an Bedeutung. Was bietet Java für eine
multithreaded Anwendung? Welche Probleme ergeben sich und wie kann diesen begegnet
werden?
Nebenläufigkeit allgemein
Konkurrierender Zugriff auf eine begrenzte Ressource. Die Prozesse in einem Betriebssystem
konkurrieren um den Prozessor des Computers. Das Betriebssystem gibt jedem Prozess für eine
bestimmte Zeit Zugriff auf die Ressource. → Keine tatsächliche Nebenläufigkeit.
Jedes Programm an sich ist bereits ein Thread, hat jedoch die Möglichkeit weitere Threads zu
starten. In Java werden diese Threads dann der JVM zugeteilt.
Prozess vs Thread
Prozesse laufen innerhalb eines Betriebssystems und werden von diesem verwaltet - Threads sind
Bestandteil eines solchen Prozesses.
Da jeder Prozess seinen eigenen Speicherbereich zur Verfügung hat, kann kein Prozess die Daten
eines anderen lesen, sie kooperieren nicht und müssen voreinander geschützt werden.
Threads innerhalb eines Prozesses haben den selben Heap-Speicher zur Verfügung (Speicher in dem
die Java Objekte liegen) über welchen sie untereinander kommunizieren können.
Scheduling
Der Scheduler hat die Aufgabe den Threads Prozessoren für eine gewisse Zeit zuzuweisen. Der
Scheduler ist in Java Bestandteil der JVM. Das Betriebssystem hat für seine Prozesse ebenfalls einen
Scheduler.
28/89
Java Komponenten für Nebenläufige Programmierung
Java bietet zur Erstellung von Threads im wesentlichen 2 Komponenten an:
● Klasse Thread
○ Vorteil: Code in run() kann andere Thread Methoden nutzen
○ Nachteil: Klasse kann nicht mehr von anderen erben
● Interface Runnable
○ Vorteil: Die Klasse kann noch erben
○ Nachteil: Kein direkter Zugriff auf Thread Methoden
Beispielcode
Runnable
Thread
class workThread implements Runnable{
public void run(){
//Do things
}
}
class workThread extends Thread{
public void run(){
//Do things
}
}
//Start
Thread t = new Thread(new
workThread());
t.start();
//Start
Thread t = new workThread();
t.start();
//Stoppen
t.interrupt();
//Stoppen
t.interrupt();
Warum nicht stoppen mit stop()?
Es ist bei Threads, die eine endlosschleife abarbeiten sinnvoll im Kopf der Schleife das interne Flag
mittels isInterruped() abzufragen, um bei einer Unterbrechung von außen noch alle notwendigen
Aktionen (Ressourcen wieder freigeben, Persistieren, …) ausführen zu können.
Die veraltete (deprecated) Methode stop() beendet den Thread abrupt, egal in welcher Phase seiner
Ausführung er sich befindet.
Probleme und Lösungen bei Nebenläufiger Programmierung
Synchronisation
Damit 2 Threads das selbe Objekt bearbeiten können, ist es notwendig, jeweils nur einem Thread die
Bearbeitung zu erlauben und den anderen auszuschließen. Wird dies nicht berücksichtigt, können
unvorhersehbare Ergebnisse auftreten. Bei reinen Lesezugriffen ist es nicht notwendig, 2 Threads zu
synchronisieren, da keine Daten Inkonsistenzen entstehen können.
Race Condition = Wettbewerbssituation
29/89
Mittel zur Vermeidung von Datenverlusten:
● “synchronized” Mutex (Mutual Exclusion) = Wechselseitiger Ausschluss
● wait(), notify(), notifyAll() (helf): unsicher
● Semaphore
● (helf): synchronisierte Klassen wie ArrayBlockingQueue aus java.util.concurrent
Mutual Exclusion
Kritischer Abschnitt: Abfolge von Befehlen, die ein Thread ausführen muss bevor ein anderer Thread
die Befehle ausführen darf, auch wenn der Prozessor an einen anderen Thread abgegeben wird.
● Es darf sich maximal 1 Thread in diesem Abschnitt befinden
● Die Threads schließen sich gegenseitig aus
Java: “synchronized”-Schlüsselwort (Monitorkonzept)
Beim Eintritt in einen synchronized Block oder eine synchronized Methode wird eine Sperre auf das
Objekt (= Monitor) gesetzt, damit jeder andere Thread ausgesperrt wird. Jedes Objekt kann ein
Monitor sein.
Ausgesperrte Threads werden von Scheduler nicht mehr berücksichtigt.
Beim Austreten wird diese Sperre wieder aufgehoben.
Bedingte kritische Abschnitte:
Ein Thread, der (z.B. wegen vollem Puffer) nichts machen kann, hat die Möglichkeit sich selbst in
eine Warteliste (eigentlich Wartemenge, weil Reihenfolge nicht beachtet wird) zu setzen mittels
wait() (Wait akzeptiert auch die maximale Wartezeit in Millisekunden als Parameter)
30/89
Ein Thread, der wieder Ressourcen bereitstellt (z.B. Element aus Puffer nehmen) kann die wartenden
Threads mittels notify() / notifyAll() darauf hinweisen, dass wieder Arbeit vorhanden ist. Diese
Methoden dürfen nur innerhalb eines synchronized Blocks / Methode aufgerufen werden.
Beispiel
class Puffer{
private int[] feld = new int[10];
private int anzahl = 0;
public synchronized void erzeuge(int w ){
while (anzahl == 9)
wait();
feld[anzahl] = w;
anzahl++;
if (anzahl==9)
notifyAll();
}
public synchronized int verbrauche(){
while(anzahl == 0)
wait();
ergebnis = feld[anzahl];
anzahl--;
// Feld umkopieren
if (anzahl==0)
notifyAll();
return ergebnis;
}
(helf): wait/notify sind Semephoren unterlegen (Deadlocks, …)
Semaphore
Der vorhin bei wait(), notify(), notifyAll() beschriebene Puffer kann auch durch Semaphoren realisiert
werden.
Ein Semaphore stellt sicher, dass sich nur eine definierte Anzahl von Prozessen in einem
Programmabschnitt befinden.
Erstellen eines neuen Semaphore mit 2 zu vergebenden Plätzen.
Semaphore s = new Semaphore(2);
Der aktuelle Thread verlangt einen freien Platz, ist keiner frei, wird an dieser Stelle gewartet.
s.aquire();
Der aktuelle Thread gibt seinen beanspruchten Platz frei (Sollte immer im finally-block stehen).
s.release();
Mittels Semaphoren kann auch ein Mutex wie bei Synchronized nachgebildet werden, dazu wird
einfach nur 1 freier Platz vergeben.
31/89
Ein Semaphore beachtet ohne den Parameter “true” im Konstruktor nicht wie lange ein Thread
schon wartet, sondern vergibt die Plätze willkürlich. (Unfaire Semaphore)
new Semaphore(1, true) erzeugt eine faire Semaphore.
Thread Befehle
sleep(long ms)
sleep(long ms,
int ns)
Thread schläft für mindestens ms Millisekunden (und ns Nanosekunden)
Wirft “InterruptedException” wenn das Schlafen von einem anderen
Thread unterbrochen wird.
yield()
Thread setzt sich in die Wartemenge zurück (“Ich setze eine Runde aus”)
interrupt()
Setzt ein Flag im Thread Objekt welches mit “isInterruped” abgefragt
werden kann. sleep(), wait(), und join() werfen eine
InterruptedException, wenn sie durch die interrupt()-Methode
unterbrochen werden
join()
join(long ms)
Wenn mehrere Threads Teilaufgaben bearbeiten, kommt irgendwann die
Zeit, wenn die Teilergebnisse zum Gesamten zusammengesetzt werden
müssen.
t.start() //Der Thread t wird gestartet
t.join() //An dieser Stelle wird (maximal ms Millisekunden) gewartet, bis
der Thread fertig ist
Deadlock
Ein Deadlock kommt zustande, wenn Thread A eine Ressource belegt, die Thread B Braucht und
umgekehrt B eine Ressource belegt, die A haben möchte.
Thread Pooling
Einen Threadpool erzeugen:
ExecutorService threadpool = Executors.newCachedThreadPool();
Thread im Pool ausführen lassen:
threadpool.execute(t);
32/89
Dateiarten - Lesen und Schreiben von Dateien in C und in Java
Das sequentielle bzw. wahlfreie Lesen und Schreiben von Dateiinhalten in C und Java ist zu
erklären.
Sequentielles vs. wahlfreies Lesen und Schreiben
Bei sequentiellem Lesen und Schreiben wird eine Datei vom Anfang durchlaufen und byte für byte
sequentiell, der Reihe nach, gelesen bzw. geschrieben. Sowohl in C als auch in Java läuft das Lesen
und schreiben normalerweise so ab. Beide Sprachen bieten allerdings die Möglichkeit, sich frei in
einer Datei zu positionieren, wobei in C dafür die Datei im update-Modus geöffnet sein muss.
Dateizugriff in C
Funktionen zum erstellen, öffnen und bearbeiten von Dateien stehen in C in der Standard Ein- und
Ausgabe Bibliothek zur Verfügung. Um diese in seinem Programm zu verwenden muss das stdio.hHeader-File inkludiert werden.
Umbenennen und Löschen von Dateien kann ohne öffnen der Datei mit den Funktionen int
remove(const char* filename) und int rename(const char* oldname, const char* newname) zur
Verfügung. Im Fehlerfall wird ein Wert ungleich 0 zurückgegeben.
Zum Bearbeiten von Dateien muss eine Datei zunächst geöffnet werden. Dazu steht die Funktion
FILE* fopen(const char* filename, const char* mode) zur Verfügung. Der Rückgabewert ist im
Erfolgsfall ein Stream (FILE*) zum angegeben Dateinamen, ansonsten NULL.
Modi
"r"
"w"
"a"
"r+"
"w+"
"a+"
b
read: Datei zum Lesen öffnen. Die Datei muss existieren.
write: Erstellt eine neue leere Datei zum Schreiben. Existiert die Datei bereits, so wird
der Inhalt der Datei gelöscht.
append: Datei zum Schreiben am Ende der Datei öffnen. Existiert die Datei nicht, wird
sie erstellt.
read/update: Eine Datei zum Lesen und updaten öffnen. Die Datei muss existieren.
write/update: Datei zum Schreiben und Updaten (Sowohl Lesen als Schreiben ist
erlaubt).
append/update: Datei zum Schreiben am Ende der Datei und Lesen öffnen. Wird die
Position in der Datei verschoben wird sie mit der nächsten Ausgabeoperation wieder ans
Ende der Datei verschoben.
binary: Kann nur in Verbindung mit einem der anderen Modi angegeben werden. Öffnet
eine Datei im binären Modus. Es wird keine Transformation von Zeilenenden auf
Windows durchgeführt. \n -> \r\n
Ein Dateistream wird mit int fclose(FILE* filepointer) wieder geschlossen. Im Erfolgsfall wird 0
zurückgegeben.
33/89
Beispiel Datei öffnen und schließen:
int doSomething()
{
FILE * fp;
fp = fopen("testdatei.txt", "w"); //oeffnen der Datei im
Schreibmodus
if (fp == NULL)
{
printf("Fehler beim Oeffnen der Datei!");
return 1;
}
/*
* Schreiben in die Datei
*
*/
fclose(fp); //schließen der Datei
}
Textdatei einlesen
Text aus einer Textdatei wird am besten zeilenweise über die Funktion char * fgets(char * str, int
num, FILE * stream) eingelesen, wobei maximal num, normalerweise bis zum Zeilenende inklusive
Stringendezeichen, Zeichen aus dem Datei-Stream eingelesen und in den String str geschrieben. Der
String wird auch noch zurückgegeben.
Beispiel
...
char str[BUFSIZ];
while(!feof(fp)) // solange das Dateiende nicht erreich wurde
{
fgets(str, BUFSIZ, fp);
printf("Eingelesene Zeile: %s\n", str);
}
…
Textdatei schreiben
Zum zeilenweise Schreiben steht analog zu fgets() die Funktion int fputs(const char * str, FILE *
stream) zur Verfügung. Es wird der String str in den Stream stream hinausgeschrieben und im
Erfolgsfall 0 zurückgeliefert.
Beispiel
...
char str[] = "Hallo Welt";
int i;
for(i = 0; i < 100; i++) //100 Zeilen schreiben
{
fputs(str, fp);
}
34/89
...
(helf): scanf und printf zum formatierten Einlesen bzw. Ausgeben von Variablen/...
Binärdatei lesen und schreiben
Zum lesen und schreiben von Binärdateien werden in C normalerweise die Funktionen size_t
fread(void * ptr, size_t size, size_t count, FILE * stream) und size_t fwrite(const void * ptr, size_t
size, size_t count) verwendet. Es wird die Zahl der tatsächlich gelesen bzw. geschriebenen Blöcke
zurückgeliefert. Bei fread werden aus dem Stream size*count Bytes gelesen und in den Buffer ptr
geschrieben. Bei fwrite werden size*count Bytes des Buffers ptr in den Stream geschrieben.
Beispiel:
//Lesen:
//Dateigröße bestimmen
...
fseek (pFile , 0 , SEEK_END); //zum Dateiende springen
lSize = ftell (pFile); //aktuelle Bytepositione auslesen
rewind (pFile); //und wieder zurück zum Anfang
// Speicher für die Datei reservieren
buffer = (char*) malloc (sizeof(char)*lSize);
// Buffer in die Datei schreiben
fread (buffer, 1, lSize, fp);
...
//Schreiben
...
char buffer[] = "Hallo Welt";
fwrite (buffer , sizeof(char), strlen(buffer), fp);
...
Wahlfreier Dateizugriff
In C steht für den Positionswechsel die Funktion int fseek ( FILE * stream, long int offset, int origin );
zur Verfügung, wobei die Position um offset Bytes von origin aus verschoben wird. Danach kann über
fread/fwrite normal gelesen werden.
(helf): fseek gefolgt von fread/fwrite
35/89
Dateizugriff in Java
Zum Bearbeiten der Metadaten einer Datei, wie Dateiname, Pfad, Änderungsdatum, Zugriffsrechte,
… steht in Java die Klasse java.io.File zur Verfügung. Mit dieser Klasse ist es auch möglich eine Datei
zu erstellen. Außerdem ist es mit Hilfe dieser Klasse möglich durch Verzeichnisstrukturen zu laufen,
da es sowohl möglich ist alle Dateien und Verzeichnisses in einem Verzeichnis aufzulisten über die
Methode Files[] listFiles(), als auch auf übergeordnete Verzeichnisse mit der Methode
getParentFile() zuzugreifen.
Um festzustellen ob ein File eine Datei oder Verzeichnis ist stehen die Methoden isFile() bzw.
isDirectory() zur Verfügung.
Beispiel
File f = new File("info.txt");
f.createNewFile(); //erstellt die Datei, falls sie nicht existiert
f.renameTo(new File(“info2.txt”); // umbenennen einer Datei
f.setLastModified(System.getCurrentMillis()); //Änderungsdatum
f.delete(); //löschen der Datei
Um Inhalte einer Datei in Java zu verändern, muss mit Streams gearbeitet werden. Für Textdateien
stehen die Klassen FileReader und FileWriter zur Verfügung. Will man zeilenweise einlesen oder
hinausschreiben, so kann mit BufferedReader bzw. BufferedWriter gearbeitet werden.
Beispiel
//Lesen:
try (BufferedReader br = new BufferedReader(new
FileReader("C:\\testing.txt"))) //try-with-resorce konstrukt
{
String sCurrentLine;
//lesen solange Zeilen verfügbar sind
while ((sCurrentLine = br.readLine()) != null) {
System.out.println(sCurrentLine);
}
} catch (IOException e) {
e.printStackTrace();
}
//Schreiben:
try {
String content = "This is the content to write into file";
File file = new File("/users/mkyong/filename.txt");
file.createNewFile(); //Datei erzeugen falls sie nicht existiert
FileWriter fw = new FileWriter(file.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fw);
bw.write(content);
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
36/89
(helf): Pfadtrennzeichen aus File.separator[Char] nehmen
Für das Schreiben und Lesen von Binärdateien sind die Klassen FileInputStream und
FileOutputStream vorhanden. Mit ihnen können Dateien byteweise eingelesen bzw. geschrieben
werden.
Beispiel
//Lesen
File file = new File("C:/robots.txt");
try (FileInputStream fis = new FileInputStream(file)) { //try-with-resource
int content;
while ((content = fis.read()) != -1) {
// convert to char and display it
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
}
//Schreiben
File file = new File("c:/newfile.txt");
String content = "This is the text content";
try (FileOutputStream fop = new FileOutputStream(file)) {
// if file doesn't exists, then create it
file.createNewFile();
// get the content in bytes
byte[] contentInBytes = content.getBytes();
fop.write(contentInBytes);
fop.flush();
fop.close();
} catch (IOException e) {
e.printStackTrace();
}
Wahlfreier Dateizugriff
In Java muss ich für wahlfreien Dateizugriff auf die Klasse RandomAccessFile zurückgreifen, die
ähnlich wie der File-Pointer in C arbeitet und die Methode seek(int pos) zum freien Positionieren in
der Datei bietet. Konstruktoren: RandomAccessFile(File file, String mode) und
RandomAccessFile(String filename, String mode). Über die Methode read() können einzelne Bytes
einfach eingelesen werden. Mit close() wird die Datei geschlossen.
37/89
Dynamische Datenstrukturen
Neben Feldern und Strukturen spielen verkettende Strukturen eine wichtige Rolle in der
Verwaltung von Datenbeständen. Die wesentlichen Vertreter solcher dynamischen
Datenstrukturen sind zu erklären.
Dynamische Datenstrukturen bezeichnen in der Informatik und Programmierung Datenstrukturen,
die eine flexible Menge an Arbeitsspeicher reservieren. Anders als statische Variablen, bei denen die
fixe Größe bekannt ist und die während der Ausführung nicht verändert werden können, können
dynamische Datenstrukturen während der Laufzeit erzeugt und in ihrer Größe verändert werden.
Da man in der Programmierung häufig in Situationen kommt, bei denen im Vorhinein nicht klar ist,
wie viel Speicher benötigt wird, sind dynamische Datenstrukturen für viele Programmieraufgaben
unumgänglich. Die Dynamik liegt im Einfügen neuer Listenelemente - auch Knoten (Node) genannt dem Entfernen vorhandener Elemente und in der Änderung der Nachbarschaftsbeziehungen
(Verschieben eines Knoten). Die Beziehung zwischen den einzelnen Knoten wird über Zeiger
hergestellt, die jeweils auf den Speicherort des logischen Nachbarn zeigen. Jeder Knoten enthält
daher neben den zu speichernden Nutzdaten mindestens einen Zeiger auf die jeweiligen
Nachbarknoten. Man nennt solche Strukturen daher auch verkettete oder rekursive
Datenstrukturen. Der Vorteil gegenüber anderen Datenstrukturen, beispielsweise Arrays, liegt also
darin, dass das Einfügen und Löschen eines Knoten an jedem Punkt in der Liste möglich ist. Dies liegt
daran, dass nur Verweise (Zeiger) neu gesetzt werden und nicht wie bei Arrays alle nachfolgenden
Elemente verschoben werden müssen. Die Liste verbraucht jedoch mehr Speicher, da die Verweise
auf andere Listenelemente gespeichert werden müssen.
Zusammengefasst: Dynamische Datenstrukturen stellen eine flexible und Größenunabhängige Art
der Datenspeicherung dar, der Speicher wird während der Laufzeit angefordert und kann auch
während der Laufzeit wieder freigegeben werden. Je nach Art der Datenstruktur werden die Daten
innerhalb der Struktur unterschiedlich angeordnet und miteinander verknüpft. Die Beziehung
zwischen den Elementen wird über Zeiger hergestellt, was Änderungen innerhalb der Struktur
vereinfacht, da lediglich Verweise verändert werden müssen und die Daten selbst unberührt bleiben.
Die wichtigsten Datenstrukturen:
Einfach verkettete Liste
Bei der einfach verketteten Liste enthält jeder Knoten einen Zeiger auf sein Nachfolgeelement, einzig
das letzte Element in der Liste verweist auf NULL. Das erste Element der Liste wird auch als RootElement bezeichnet, von dem aus jede Operation (Löschen, Einfügen, Suchen) in der Liste begonnen
wird.
38/89
Doppelt verkettete Liste
Im Gegensatz zur einfach-verketteten Liste hat bei der doppelt verketteten Liste jedes Element
sowohl einen Zeiger auf das nachfolgende als auch auf das vorhergehende Element. Der VorgängerZeiger des ersten und der Nachfolger-Zeiger des letzten Elementes zeigen auf den Wert NULL. Diese
besonderen Elemente dienen zum Auffinden des Anfangs und des Endes einer doppelt verketteten
Liste.
Stack
Der Stack (Stapel) realisiert die so genannte LIFO-Strategie (Last In, First Out). Das bedeutet, dass
jenes Element, welches zuletzt eingefügt wurde auch wieder zuerst entfernt wird.
● Init: Initialisiert einen neuen Stack.
● Push: Legt ein Element auf dem Stack ab.
● Pop: Holt das oberste Element des Stacks, dabei wird es vom Stapel gelöscht.
● Top/Peek: Liest das oberste Element im Stack, jedoch ohne es zu löschen.
● Empty: Überprüft ob der Stack leer ist.
Realisierung: Liste mit entsprechenden Zugriffsmethoden
Queue
Die Queue (deutsch Warteschlange) ist eine lineare dynamische Datenstruktur. Eine Queue
funktioniert nach dem so genannten FIFO-Prinzip (First In, First Out). Das heißt die Elemente werden
am Ende hinzugefügt und am Anfang entfernt. Die Operationen zum Einfügen neuer und Entfernen
bestehender Elemente werden enqueue und dequeue bezeichnet.
Realisierung: Liste mit entsprechenden Zugriffsmethoden
39/89
Baum
Ein Baum ist eine besteht aus einem Wurzelknoten (root). Dieser Knoten enthält neben den Daten
diverse Zeiger auf unterliegende Elemente. Jedes dieser unterliegenden Elemente kann wiederum
entweder der Wurzelknoten eines sog. Teilbaumes sein oder ein Leaf (Blatt), welches nur Daten und
keinen Verweis auf unterliegende Elemente.
Ein Baum kann z.B. zur Darstellung einer Verzeichnisstruktur verwendet werden.
Binärer Baum
Eine spezielle Form des Baums ist der binäre Baum. Hierbei verweist jeder Knoten auf 2 Elemente:
left und right. Alle Elemente des linken Teilbaums sind hierbei kleiner als die des jeweiligen Knotens
und alle Elemente rechts sind größer.
(helf): Algorithmen im Pseudocode/PAP/… für Einfügen/Aushängen/… bei
leerer/einelementiger/mehrelementiger Liste/...
40/89
Collections in Java
Die Verwaltung von Sammlungen von Objekten ist ein zentraler Aspekt der Programmierung.
Es ist ein Überblick über Java-Collections und deren Verwendungsmöglichkeiten zu geben.
Collections ermöglichen eine einfache Verwaltung beliebiger Objekte, es ist dynamisches hinzufügen
und entfernen mögliches und effizientes Suchen. Im Packet java.util sind alle wichtigen Klassen zur
Verwendung von Collections enthalten.
Interface von Collections:
add(); clear(); contain(); remove(); size();
Iterator
●
●
●
●
●
●
Eines der wichtigsten Interfaces für Collections
ein Iterator “wandert” durch die Collection
Object next()
liefert nächstes Object zurück
boolean hasNext()
schaut ob es ein nächstes Object gibt
remove()
entfernt das zuletzt aufgelistete Element
(helf): ermöglicht mehrere Iterationen gleichzeitig
Arten von Collection
●
Set (HashSet, TreeSet, ...)
○ enthält alle Collection Methoden
○ kann Elemente nicht doppelt enthalten
○ keine bestimmte Reihenfolge
○ schnelle suche
●
List (ArrayList, LinkedList, Vector, Stack)
○ enthält alle Collection Methoden
○ kann Elemente doppelt enthalten
○ haben bestimmte Reihenfolge
○ schnelles einfügen/löschen
●
Map (HashTable, TreeMap,...)
○ schnelles auffinden von Elementen über einen “key”
○ jedes Element muss eindeutigen key haben
○ Methoden:
containsKey(), containsValue(); get(); put(); remove(); keySet();
●
Queue
○ FIFO Prinzip
41/89
Erweiterte for-Schleife
Bei jedem Durchgang wird das nächste Element der Liste in “e” gespeichert, bis jedes Element
einmal durchgegangen wurde.
ArrayList<Element> elements = new ArrayList<Element>();
for(Element e : elements){
//Do sth with e
}
Wichtige Methoden des Collection Interfaces:
●
●
●
●
●
●
●
●
toArray(); //wandelt die Daten der collection in einen Array um
removeAll(Collection<?> c); //Alle Elemente der Collection die auch in Collection c
enthalten sind werden entfernt
isEmpty(); //liefert true oder false zurück wenn die Collection leer oder nicht leer ist
clear(); //alle Elemente in der Collection werden gelöscht
contains(Object o); //sieht nach ob das Objekt in der collection vorhanden ist liefert
true,false zurück
containsAll(Collection c); //liefert true zurück wenn alle objekte von c vorhanden sind
add(Object o); //hinzufügen eines Objektes zur coll.
addAll(Collection c); //alle elemente der collection c werden zur aktuellen hinzugefügt
HINWEIS: Mengenoperation sind solche die mit mehrern Objekten , Collections arbeiten wie:
containsAll, clear, removeAll
42/89
Graphische Oberfläche in Java (Swing)
Welche Klassen bietet Swing für die Erstellung einer GUI. Wie erfolgt die Reaktion auf
Benutzerinteraktionen? Welche Unterstützung für die Platzierung von Komponenten wird
angeboten?
Seit Java JDK Version 1.0 bietet Java mit dem Abstract Window Toolkit (AWT) die Möglichkeit
grafische Benutzeroberflächen zu gestalten. Mit Version 1.2 wurden die Java Foundation Classes und
damit Swing in Java integriert. Die Java Foundation Classes enthalten neben Swing noch Bibliotheken
für 2D Programmierung, Drag&Drop, Accessability und das AWT. Swing baut auf AWT auf. Die
Hauptalternative zu Swing ist das von IBM für Eclipse entwickelete Standard Widgets Toolkit, dass im
Gegensatz zu Swing auf die Betriebssystemeigenen GUI-Komponenten zurückgreift.
Welche Klassen bietet Swing für die Erstellung einer GUI?
Grundlegende Hierarchie
(helf): Swing: mit J beginnende Klassen -> jeder JButton, JTextField ist ein Container, ...
Container
Container sind Komponenten, die weitere Komponenten enthalten können. Container können
wiederum auch weitere Container enthalten. Zu Containern zählen beispielsweise JScrollPane,
JToolBar, JSplitPane (in mehrere Flächen geteilte Pane), JPanel, JTabbedPane (mehrere Tabs),
JRootPane und JLayeredPane, aber auch GUI-Elemente wie JButton, JTextField…, sowie die
folgenden Top-Level Container:
43/89
JDialog - Dialogfenster
JFrame - Fenster
JApplet - Erweiterung der Applets um Swing-Komponenten
Aufbau der Pane Hierarchie
44/89
GUI-Komponenten
JComboBox:
JList:
JMenu:
JSlider:
JLabel:
JTextField:
JCheckBox:
JRadioButton:
JButton:
(helf): essentielle Methoden (setText, setSelected, …) Selektionsmodi, ButtonGroup,
Mit Hilfe der Methode setText kann der Text auf zahlreichen Komponenten, wie JLabel, JButton,
JTextField…. Mit der Methode setSelected kann der Selektionszustand einer Komponente geändert
werden. Mit der Methode doClick kann ein Mausklick simuliert werden.
Um zu verhindern, dass beispielsweise mehrere JRadioButtons gleichzeitig selektiert werden,
können diese in einer ButtonGroup mit der Methode add() hinzugefügt werden. Wird dann ein
Element der ButtonGroup selektiert werden automatisch alle anderen Elemente der ButtonGroup
deselktiert.
Wie erfolgt die Reaktion auf Benutzerinteraktionen?
Das Reagieren auf Benutzerinteraktion erfolgt in Swing über das Listener-Konzept. Das heißt man
registriert bei der Komponente, auf die man reagieren will, eine Listener-Implementierung, die den
auszuführenden Programmcode enthält. Wird nun die gewünschte Aktion ausgeführt, so kümmert
sich die Komponente darum, die entsprechende Methode des Listeners aufzurufen.
Ein Listener stellt lediglich ein Interface dar und muss selbst implementiert werden.m
Um nicht alle Methoden eines Listeners selbst implementieren zu müssen, stehen sogenannte
Adapter zur Verfügung. Diese haben bereits alle Methoden des entsprechenden Listeners als leere
45/89
Methode implementiert und so kann von der AdapterKlasse abgeleitet werden und nur die Methode
zur Aktion, auf die reagiert werden soll, muss überschrieben werden.
Ereignisse
Es existieren verschiedenste Ereignisse und dazugehörende Listener:
Listener/Event
Beschreibung
ActionListener/ActionEvent
für Buttonklicks, Menüklicks,
Kombobox/Radiobutton Selektion, Entertaste in
einem Textfeld
ChangeListener/ChangeEvent
Veränderung bei Slider, Spinner, Progress
Bar,Toggle Button, Checkbox
MouseListener/MouseEvent
Für Mausklicks, kann auf jeder
Swingkomponente registriert werden
MouseMotionListener/MouseMotionEvent
Für Mausbewegungen, kann auf jeder
Swingkomponente registriert werden
KeyListener/KeyEvent
Für Tastatureingaben
...
...
Welche Unterstützung für die Platzierung von Komponenten wird
angeboten?
Zur Platzierung und Anordnung von Komponenten bietet Swing verschiedenste StandardLayoutManager an. Ein solcher kann auch selbst implemtiert werden. Es ist außerdem möglich jede
Komponente in einem Container anhand genauer Pixelangaben fix zu positionieren, was allerdings
beim Skalieren von Fenstern oder Verwendung anderer Look-And-Feels Probleme bereitet.
Jedem Container kann ein eigener Layoutmanager zugewiesen werden, wobei sich der
LayoutManager um die Anordnung und Skalierung der Komponenten in diesem Container kümmert
und automatisch anpasst.
Folgende Layout-Manager stehen in Swing zur Verfügung:
● FlowLayout
● BorderLayout
● GridLayout
● GridBagLayout
● BoxLayout
● kein Layout
● eigens definiertes Layout
46/89
FlowLayout
Es werden so viele Komponenten in einer Zeile wie möglich angezeigt. Hat eine Komponente in einer
Zeile nicht mehr Platz wird eine Zeile begonnen.
BorderLayout
Der Container wird in 5 Bereiche eingeteilt. Oben PAGE_START, unten PAGE_END, links LINE_START,
zentral CENTER und links LINE_END. In alten Java Versionen wurden die Himmelsrichtungen (NORTH,
SOUTH, …) verwendet.
GridLayout
Der Container wird in n*m Felder unterteilt, die jeweils eine Komponente enthalten.
47/89
GridBagLayout
Der Container wird in mehrere Felder unterteilt, wobei einzelne Komponenten auch mehrere Felder
in Anspruch nehmen können.
BoxLayout
Entspricht einem erweiterten FlowLayout, wobei das BoxLayout erlaubt zu entscheiden, ob die
Komponenten übereinander angezeigt werden sollen oder nacheinander zeilenweise wie beim
FlowLayout.
Beispiel
import
import
import
import
import
java.awt.Component;
java.awt.Container;
javax.swing.BoxLayout;
javax.swing.JButton;
javax.swing.JFrame;
public class BoxLayoutDemo {
public static void addComponentsToPane(Container pane) {
pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
addAButton("Button 1", pane);
addAButton("Button 2", pane);
addAButton("Button 3", pane);
addAButton("Long-Named Button 4", pane);
addAButton("5", pane);
}
private static void addAButton(String text, Container container) {
JButton button = new JButton(text);
48/89
button.setAlignmentX(Component.CENTER_ALIGNMENT);
container.add(button);
}
public static void main(String []args) {
//Create and set up the window.
JFrame frame = new JFrame("BoxLayoutDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up the content pane.
addComponentsToPane(frame.getContentPane());
//Display the window.
frame.pack();
frame.setVisible(true);
}
}
49/89
JDBC (Java Database Connectivity)
Der Zugriff auf Datenbanken ist ein wesentlicher Aspekt moderner Anwendungen. Welche
Architekturen, unterstützende Klassen und zentrale Code-Fragmente können hier genannt
werden?
Wie wird auf eine Datenbank über Java zugegriffen?
Java Database Connectivity (JDBC) ist eine Datenbankschnittstelle der Java-Plattform, die eine
einheitliche Schnittstelle zu Datenbanken verschiedener Hersteller bietet und speziell auf relationale
Datenbanken ausgerichtet ist.
JDBC ist in seiner Funktion als universelle Datenbankschnittstelle vergleichbar mit z.B. ODBC unter
Windows.
Der Datenbankzugriff erfolgt unabhängig vom verwendeten DBMS. Der Datenbank-Client braucht
nicht mit einem bestimmten DBMS entwickelt werden. Alle DBMS- spezifischen Details übernimmt
der JDBC- Treiber. Dieser ist ebenfalls in Java geschrieben und wird von den Datenbankherstellern
oder von Dritten angeboten, um bessere und performantere Treiber entwickeln zu können. Zu den
Aufgaben von JDBC gehört es, Datenbankverbindungen aufzubauen und zu verwalten, SQL-Anfragen
an die Datenbank weiterzuleiten und die Ergebnisse in eine für Java nutzbare Form umzuwandeln
und dem Programm zur Verfügung zu stellen.
Beispiel
...
try {
Class.forName("Treiber“).newInstance( ); // Treiber laden
Connection conn =
DriverManager.getConnection("jdbc:DBMS:Datenbank",“User",“Passwort");
// Verbindung aufbauen
/* Verbindung verwenden*/
conn.close();
// Verbindung schließen
} catch (SQLException ex) { ... }
Welche Treibertypen gibt es?
Grundsätzlich gibt es 4 verschiedene Treiberarten wobei Typ-1 und Typ-2 nicht Netzwerkfähig sind.
Die Treibertypen Typ-3 und Typ-4 sind Netzwerkfähig.
50/89
JDBC-ODBC-Bridge-Treiber (Typ-1)
JDBC-Anweisungen werden in native Anweisungen des ODBC Treibers übersetzt, dieser Treiber ist
nur ein mal vorhanden. Hauptsächlich unter Windows. Für jedes Datenbanksystem ist ein eigener
ODBC-Treiber erforderlich
JDBC-Native-API-Treiber (Typ-2)
Dieser Treiber übersetzt JDBC-Aufrufe an eine nativ implementierte datenbankspezifische
Bibliothek. Dazu wird nativer Code ausgeführt und somit ist die Platformunabhängigkeit nicht mehr
gegeben. Dafür ist diese Lösung sehr schnell und wird für zeitkritische Anwendungen eingesetzt.
Dieser Treiber kann nicht von Applets verwendet werden, da Applets keinen nativen Code von
anderen Quellen laden können.
JDBC-Pure-Java-Treiber (Typ-3)
JDBC-Anweisungen werden von Java über ein Netzwerkprotokoll an einen auf dem Datenbankserver
vorhandenen JDBC-Server übergeben und von diesem in Datenbankanweisungen übersetzt
Native-Java-Protokoll-Treiber(Typ-4)
Direkte Kommunikation zwischen dem JDBC-Treiber und dem Datenbankmanagementsystems. Ein
Middleware-Treiber wird dabei nicht verwendet. Typ-4-Treiber eignen sich gut für IntranetLösungen, die schnelle Netzprotokolle nutzen wollen.
Wie funktioniert das Auslesen von Metadaten?
Dient zum Ermitteln über die Struktur einer Datenbank z.B. Auslesen der Tabellen in einer
Datenbank oder der Datentypen einer Tabelle usw.
● DatabaseMetaData: ermittelt Informationen der Datenbank
● ResultSetMetaData: ermittelt Informationen über ein ResultSet
● ParameterMetaData: ermittelt Informationen zum Typ und Eigenschaften von Parametern
51/89
ResultSetMetaData meta = rs.getMetaData();
int numberOfColumns = meta.getColumnCount(); // Anzahl der Spalten auslesen
DatabaseMetaData meta = con.getMetaData(); // URL zur verbundenen Datenbank
String url = meta.getURL(); // URL zur verbundenen Datenbank
Wie führt man SQL-Anweisungen aus?
Um einen SQL-Befehl an die Datenbank zu schicken, benötigt man eine Instanz der Klasse
Statement. Mit der Instanz von Statement kann man nun eine Anfrage mit der Methode
executeQuery() ausführen. Als Ergebnis erhält man eine Instanz der Klasse ResultSet,
welche das gesamte Ergebnis enthält.
Mit der Methode executeUpdate() der Klasse Statement kann man INSERT, UPDATE und
DELETE SQL Anweisungen abschicken. Diese Methode liefert keine Ergebnismenge zurück, wie ein
SQL DDL Statement sondern liefert die Anzahl der eingefügten, gelöschten oder veränderten
Datensätzen zurück, oder 0 wenn das SQL Statement nichts zurückgeben soll.
Die Methode executeBatch() ermöglicht es, mehrere SQL-Anweisungen gleichzeitig
abzuschicken. Mit der Methode addBatch() können weitere Anweisungen angehängt werden,
dies hat den Vorteil, dass nicht jede Anweisung einzeln abgeschickt werden muss und nur so einmal
eine Verbindung aufgebaut werden muss.
Beispiel
...
Statement state = conn.createStatement();
// Statement erstellen
ResultSet rs = state.executeQuery(“SELECT * FROM Table”);
// Datensatz
auslesen
String name = rs.getString(1);
// String weiterverarbeiten
String id = rs.getInt(2);
// Int weiterverarbeiten
state.executeUpdate(„DELETE FROM table WHERE id = 1“);
// Datensatz
löschen
...
Was sind PreparedStatements und warum verwendet man sie?
PreparedStatements bieten die Möglichkeit mittels ? einen Platzhalter in der SQL-Anweisung zu
platzieren, welcher später nachträglich eingefügt werden kann. Dies ist nützlich wenn man sehr oft
dieselbe Abfrage tätigen möchte und die Abfrage von den Eingaben des Benutzers abhängen.
Außerdem sind PreparedStatements vor SQL-Injections sicher.
Beispiel
...
PreparedStatement ps=
conn.prepareStatement(“select * from table where id = ?”);
// PreparedStatement erstellen
ps.setInt (1,3158);
// Parameter setzten
ResultSet rs = ps.executeQuery();
// Statement abschicken
...
52/89
Was sind Scrollable ResultSets?
Bei Scrollable ResultSets, kann das Ergebnis vom Statement frei bearbeitet werden. Beim Statement
muss man den Typ und die Rechte setzten. Man kann sich frei im ResultSet bewegen und auch
Datensätze ändern. Diese werden auch in der Datenbank übernommen.
resultSetType
●
●
●
ResultSet.TYPE_FORWARD_ONLY
ResultSet.TYPE_SCROLL_INSENSITIVE
ResultSet.TYPE_SCROLL_SENSITIVE (Änderungen, während das ResultSet offen ist)
resultSetConcurrency
●
●
ResultSet.CONCUR_READ_ONLY
ResultSet.CONCUR_UPDATABLE
Beispiel
...
Statement state = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
// Statement erstellen
ResultSet rs = state.executeQuery(„SELECT * FROM table“);
// ResultSet erstellen
rs.absolute(5);
// auf die 5. Zeile positionieren
rs.updateInt("ID", 5893); // neuen Wert setzten
rs.updateRow(); // Wert updaten
...
Was versteht man unter Transaktionen?
Technisch sind Transaktionen immer nur eine Anweisung, welche abgearbeitet wird. Transaktionen
werden in der Informatik als eine Folge von Operationen, die als eine logische Einheit betrachtet
werden, bezeichnet. Insbesondere wird für eine Transaktion gefordert, dass sie entweder vollständig
ausgeführt wird oder keine Auswirkung hat (atomarität). Transaktionen stellen Konsistenz sicher.
ACID-Prinzip
●
A – Atomicity
Unteilbarkeit: alle oder keine Anweisung wird ausgeführt.
●
C – Consistency
Konsistenz: Wenn das System vor der Transaktion in einem gültigen Zustand war, dann ist es
dies auch nachher.
●
I – Isolation
Isolierung: Transaktionen laufen ungestört von anderen Transaktionen ab (es können nicht
die selben Daten von verschiedenen Transaktionen bearbeitet werden).
●
D – Durability
53/89
Dauerhaftigkeit: Mit commit abgeschlossene Änderungen sind persistent.
Probleme
Wenn Transaktionen dem ACID-Prinzip entsprechen sollen, müssen sie serialisiert nacheinander
durchgeführt werden. Aus Performance-Gründen wird hiervon oft abgewichen und ein niedrigerer
"Transaction Isolation Level" eingestellt und dies kann zu folgenden Fehler führen:
●
Dirty Read
Es können von anderen Transaktion geschriebene Daten gelesen werden, für die noch kein
"Commit" erfolgte und die eventuell per "Rollback" zurückgesetzt werden.
●
Non-repeatable Read (Stale Data)
Während einer laufenden Transaktion können Daten von anderen Transaktionen geändert
und committed werden, so dass in der ersten Transaktion ein zweites Auslesen zu anderen
Daten führt.
●
Phantom Read
Eine erste Transaktion liest über eine "Where-Klausel" eine Liste von Datensätzen. Eine
zweite Transaktion fügt weitere Datensätze hinzu (inkl. Commit). Wenn die erste
Transaktion wieder über die gleiche "Where-Klausel" Datensätze liest oder bearbeitet, gibt
es mehr Datensätze als vorher.
Lösung: Isolationsstufen
●
Read Uncommitted
Geringste Isolation, höchste Performance, es können Dirty Reads, Non-repeatable Reads und
Phantom Reads auftreten
●
Read Committed
Es gibt keine Dirty Reads mehr, aber es gibt weiterhin Non-repeatable Reads und Phantom
Reads
●
Repeatable Read
Keine Dirty Reads und keine Non-repeatable Reads, aber weiterhin Phantom Reads
●
Serializable
Keine Dirty Reads, keine Non-repeatable Reads und keine Phantom Reads, höchste Isolation,
geringste Performance
Ablauf in Java
Die Default-Einstellung für eine JDBC-Connection (diese verwaltet die Transaktionen) ist auf
autoCommit gesetzt, was so viel heißt, wie jedes Statement ist eine eigene Transaktion. Um dies zu
ändern, muss autoCommit auf false gesetzt werden. Anschließend werden alle Statements erst bei
der Anweisung commit abgeschickt. Falls ein Fehler auftritt, können über rollback alle Änderungen
54/89
rückgängig gemacht werden. Es gibt auch noch Savepoints um nicht die gesamte Transaktion
rückgängig zu machen.
Beispiel
...
try
{
conn.setAutoCommit(false); // AutoCommit deaktivieren
state.executeUpdate(„UPDATE table SET ID = … “);
// Werte ändern
state.executeUpdate(„UPDATE table SET NAME = … “); // Werte ändern
conn.commit( );
// Transaktion abschicken
}
catch (SQLException e)
{
conn.rollback();
}
...
// Bei einem Fehler, Änderungen rückgängig machen
...
SavePoint sp1 = conn.setSavePoint(„Sicherung1“);
// Sicherungspunkt
erstellen
conn.rollback(sp1);
// Sicherungspunkt zurückspielen
...
55/89
RMI (Remote Method Invocation)
Wie kann der Zugriff auf Java-Anwendungen auf entfernten Computern realisiert werden?
Wie muss ein Server aufgesetzt werden bzw. wie wird auf den Server zugegriffen?Wie sehen die
beiden Enden der Verbindung aus und wie werden Argumente übertragen?
Was ist RMI?
RMI steht für Remote Method Invocation, was übersetzt entfernter Methodenaufruf bedeutet.
Remote Method Invocation (RMI) ist ein Modell für die objektorientierte Client-ServerKommunikation bei verteilten Java-Anwendungen. RMI ermöglicht es einem Client, serverseitige
(entfernte) Objekte zu nutzen und deren Methoden aufzurufen.
Wie kann der Zugriff auf Java-Anwendungen auf entfernten Computern
realisiert werden?
Sockets
Der Zugriff kann über Sockets realisiert werden. Der Weg über Sockets ist relativ kompliziert da die
verschiedenen Datentypen und Objekte mit dem ObjectOutputStream verschickt und mit den
ObjectInputStream empfangen werden müssen. Alle Objekte die man über Sockets versenden
möchte müssen das Interface Serializable implementieren.
RMI
Mit RMI funktioniert das ganze wesentlich einfacher. Dem Client wird vorgespielt er verwende eine
lokale Methode. Der Client verwendet also eine Methode die in einem Interface liegt. Zu diesem
Interface gibt es am Server eine Implementierung. Der Client hat also nur den Namen der Methode
und ihre Parameter. Wichtig ist, dass das Interface das Interface Remote implementiert und jede
Methode im Interface die RemoteException werfen kann.
Wie muss ein Server aufgesetzt werden bzw. wie wird auf den Server
zugegriffen?
56/89
Die Codierung und Decodierung der Aufrufparameter und Rückgabewerte von Methodenaufrufen
und die Übermittlung der Daten zwischen Client und Server wird vom RMI-System und den
generierten Klassen, beim Client Stub und beim Server Skeleton genannt, geregelt. Der Stub fungiert
als lokaler Stellvertreter (Proxy) des entfernten Objekts. Stubs und Skeleton, die Java Programme,
die auf Grund der Interface Definitionen erstellt werden (wobei Skeletons bei neueren Releases, ab
1.5.x, nicht mehr benötigt werden), sind sozusagen die Schnittstelle zum Anwendungsprogramm und
dem Reference Layer. Der Reference Layer ist nötig, da Referenzen in der einen JVM auf Referenzen
in einer eventuell entfernten JVM abgebildet werden müssen. Der Stub und der Skeleton übertragen
die Daten am Netzwerk. Das Einpacken der Objekte wird Marshalling und das Auspacke
Unmarshalling genannt.
Bei RMI wird ein Objekt mit bestimmten Anforderungen, dass exportiert und ans Netzwerk
gebunden wird, erstellt. Dieses muss in die Registry eingetragen werden, also registriert werden. Am
Server gibt es einen Skeleton der ähnlich wie ein Proxy funktioniert. Er weiß wo die Anfrage
herkommt, berechnet und schickt sie zum Client zurück.
Remote Object
Das RemoteObject stellt das entfernte Objekt dar und liegt auf dem Server. Es implementiert das
RemoteInterface und das Verhalten der für die Clients zur Verfügung stehenden entfernten
Methoden. Vom Server können eine oder mehrere Instanzen des Remote-Objekts erstellt werden.
Ein Remote Object muss von UnicastRemoteObject abgeleitet sein und muss einen parameterlosen
Konstruktor haben, denn dieser ruft nur den Konstruktor von UnicastRemoteObject auf und könnte
eine RemoteException auslösen. Jede Methode muss eine RemoteException deklarieren, auch der
parameterlose Konstruktor.
Registry
Die Registry, ein Namensdienst, ist ein serverseitiger Vermittlungsdienst, der einem Client auf
Anfrage mitteilt, welche Remote-Objekte auf dem Server vorhanden und nutzbar sind. Der
Namensdienst kann als ein eigenständiger (nebenläufiger) Serverprozess laufen oder er kann von
innerhalb des Serverprogramms gestartet werden.
57/89
Ablauf
Das Serverprogramm registriert seine Remote-Objekte beim Namensdienst mit bind(...) bzw.
rebind(...). Ein Client kann dann mit list(...) bzw. lookup(...) den Namensdienst befragen und erhält
eine Liste der registrierten Remote-Objekte bzw. eine Referenz auf das gewünschte Remote-Objekt.
Übertragung
Der Stub ist so etwas wie Object, also muss man wieder casten, um seinen gewünschten Datentyp zu
erhalten.
Die Dienste die zwischen Server und Client vorhanden sein sollten, sollten in einem Interfaces
erstellt werden. Die Implementierung ist nicht am Client möglich, denn die Funktionen
können/sollen nur am Server aufgerufen werden. Der Client weiß nur die Aufrufe. Am Server wird
dieses Interface implementiert. Das Interface, muss wiederum das Interface Remote
implementieren.
Da die Methoden über das Netzwerk übertragen werden, kann ein Fehler beim Übertragen
auftreten. Darum müssen die Methoden RemoteException werfen, da sie sonst nicht aufgerufen
werden können.
Lokal und über das Netzwerk wird meistens auf dasselbe Interface zugegriffen, deshalbe sollte auch
beim Lokalen Interface Remote implementiert werden. Die Methoden der Interfaces sollen die
RemoteException werfen.
Die Methode rebind() liegt am Server und man kann damit die OID und den Namen verbinden. Dies
wird zum Beispiel bei der Registry benötigt.
Was sind Stub und Skeleton Klassen?
Stub
●
●
●
Proxy an der Clientseite
“Beinhaltet” alle Netzwerkfähigkeiten (Serialisierung, Sockethandling,...)
Restlicher Java-Code “sieht” Stub wie einfache Methoden
Skeleton
●
●
Wie Stub, nur auf Serverseite
Teilweise andere Funktionalität (Nachladen, ...)
Wie sehen die beiden Enden der Verbindung aus und wie werden
Argumente übertragen?
Allgemeine Beschreibung der Enden
Client
● Hat nur das Interface um die Methoden aufrufen zu können
● Cast auf das Interface beim Empfangen der Daten
● hat den Stub
Server
● Implementiert das Interface
● hat den Skeleton
58/89
Eclipse
● 3 Projekte:
○ Projekt mit Serverklassen
○ Projekt mit Clientklassen
○ Projekt mit Interfaces, dieses Projekt wird auf die anderen Projekte referenziert
○ Durch diese Trennung sind am Server und am Client nur die benötigten Methoden
und Klassen verfügbar
Impelemtierung der Enden
2 Varianten beim Remote Interface
Entweder erbt das Interface von Remote oder von UnicastRemoteObject. Erbt es von Remote dann
sieht die Implementierung wie im folgenden Beispiel aus. D.h. im Server muss noch
UnicastRemoteObject.exportObject() aufgerufen werden. Würde das Remote-Interface einfach von
UnicastRemoteObject erben, dann würde die Erweiterung mit einer weiteren Klasse nicht möglich
sein. Also ist der saubere Weg über die UnicastRemoteObject.exportObject() Variante, weil man sich
selbst die Hierarchie zusammenstellen kann.
Remote Interface
Zuerst erstellt man ein Java Interface. Dieses erweitert das Interface java.rmi.Remote. Das Remote
Interface definiert die Sicht des Clients auf das entfernte Objekt. In dem neuen Interface werden alle
Methoden deklariert, die Remote aufrufbar sein sollen. Da die Methoden auch unter Umständen
über das Netzwerk aufgerufen werden, kann es natürlich zu Kommunikationsproblemen kommen,
daher muss zu jeder Methodendefinition ein throws java.rmi.RemoteException hinzugefügt werden.
public interface OperationsCalc extends Remote {
int sum(int a, int b) throws RemoteException;
int sub(int a, int b) throws RemoteException;
int mul(int a, int b) throws RemoteException;
}
Remote Klasse
Nun wird eine weitere Klasse hinzugefügt, die dieses Interface implementiert.
public class OperationsCalcImpl implements OperationsCalc{
@Override
public int sum(int a, int b) throws RemoteException {
return a+b;
}
@Override
public int sub(int a, int b) throws RemoteException {
return a-b;
}
59/89
@Override
public int mul(int a, int b) throws RemoteException {
return a*b;
}
}
Server
public static void main(String[] args) {
try {
String name = "CalcServer";
OperationsCalcImpl server = new OperationsCalcImpl();
OperationsCalc stub =
(OperationsCalc) UnicastRemoteObject.exportObject(server, 0);
Registry registry = LocateRegistry.createRegistry(12345);
//Port
registry.rebind(name, stub);
System.out.println("CalcServer gebunden");
} catch (RemoteException e) {
e.printStackTrace();
}
}
Client
Nun wird natürlich noch ein Client benötigt, der die Methoden des Servers aufruft. Hier verwendet
man die Klasse java.rmi.Naming. Mit folgendem Code holt man sich eine Remote Referenz des
Servers und kann dann die Methoden des Remote - Interfaces aufrufen.
public static void main(String[] args) {
OperationsCalc op;
int a=3;
int b=2;
try {
Registry registry = LocateRegistry.getRegistry("localhost",12345);
op = (OperationsCalc) registry.lookup("CalcServer");
System.out.println("Addieren von "+a+" und "+b+": "+op.sum(a, b));
System.out.println("Subtrahieren von "+a+" und "+b+": "+op.sub(a, b));
System.out.println("Multiplizieren von "+a+" und "+b+": "+op.mul(a, b));
} catch (RemoteException e) {
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
}
Argumente übertragen
Bei RMI gibt es 3 verschiedene Arten der Parameterübergabe und Rückgabe:
60/89
Basisdatentypen
int, double, boolean,etc. - Werte werden über das Netzwerk übertragen
Auf Senderseite werden Werte „gemarshaled“ (Kodierung, Übersetzung von z.B. Linux auf Windows
nennt man Marshalling) und über das Netzwerk gesendet. Auf der Empfängerseite werden die
Werte wieder entpackt und an Client geliefert.
Serialisierbaren Objekten
Objekte die das Interface Serializable implementieren: Objekte werden serialisiert über das
Netzwerk übertragen und auf der anderen Seite eine Kopie aufgebaut.
Inhalt des Objekts wird über den Java-Serialisierungsmechanismus serialisiert und über das
Netzwerk übertragen
●
●
auf der Empfängerseite wird ein neues Objekt mit gleichem Inhalt aufgebaut
Problem, wenn Klasse des serialisierten Objekts auf Empfängerseite nicht bekannt ist!
Remote Objekten
Objekte die das Interface Remote implementieren: Es wird auf der empfangenden Seite ein Stub für
das Objekt aufgebaut.
Es wird kein Wert übergeben sondernhy
● über das RMI-System wird eine Remote-Referenz auf das Server-Objekt übermittelt
● beim Client wird dafür ein Stub erzeugt und Referenz auf den Stub dem Empfänger
übergeben
61/89
Andere
Keine Basisdatentyp, nicht serialisierbar oder nicht Remote - können nicht als Parameter übergeben
werden
62/89
Java und XML (Extended Markup Language)
Die Nutzung von XML in Java-Anwendungen ist anhand der wesentlichen Verfahren der
XML-Verarbeitung in Java zu erläutern. Dabei ist sowohl die Typisierung von XMLDokumenten als auch die Transformation von XML-Dokumenten zu erklären.
XML document
Ein XML Dokument gilt als wohlgeformt, wenn es ein oder mehrere Elemente enthält und genau ein
Wurzelelement. Ein Wurzelelement befindet sich in keinem anderen Element. Jedes andere Element
wird durch Start- und End-Tag begrenzt, welche sich im Kontext des selben Elements befinden
müssen und daher richtig geschachtelt sind.
XML-Document-Kopf:
<?xml version="1.0" encoding="UTF-8"?>
Schema Einbinden:
<ns:Element xmlns:ns="http://www..example.com/schemalocation">
ns <- frei wählbar
Beispiel
<tag1>
<tag2>text</tag2>
<tag3 id=“1“ />
</tag1>
Element
Ein Element wird in XML entweder mit Start- und Endtag (<tag> </tag>) begrenzt oder mit einem
Empty-Element-Tag (<etag />) definiert.
In einem Element kann es auch mehrere Attribute geben, diese sind im Start- oder im EmptyElement-Tag zu finden. Sie sind in Name und Wert aufgeteilt (attr=“val“), dabei ist darauf zu achten
das der Wert in Doppelte Anführungszeichen zu setzen ist und der Attribut-Name nicht doppelt im
selben Tag vorkommen darf.
Empty-Element-Tags haben den Sinn, dass Werte in Attributen gespeichert werden können, ohne
einen Content zu haben (man spart sich das End-Tag).
Beispiel
<tag1 id=“2“ name=“Hugo“>
<tag3 id=“1“ />
</tag1>
63/89
XPath
Ist eine Abfragesprache für XML-Dokumente. Man navigiert in der Baum-Struktur über
child/parent/root/following/etc. Und kann auch auf Attribute oder Texte abfragen.
Beispiele
//child::Buch/Kapitel
Wählt alle „Kinder“ von allen Büchern die Kapitel sind.
//child::Buch[2]/Kapitel[1]
Wählt das 1. Kapitel vom 2. Buch
//Kapitel[@titel='Kapitel Eins']
Wählt alle Kapitel die das Attribute „titel“ mit dem Wert „Kapitel Eins“ haben.
/Buch[count(Seite)>10]
Erstes Buch mit mehr als 10 Seiten
Schema
Mit einem Schema kann man bestimmen ob ein Dokument nicht nur wohlgeformt, sondern auch
korrekt/valide ist. Hier legt man fest, welche Attribute/Kinder/etc. eine Element haben darf und
welchen Restriktionen diese unterliegen.
Beispiele
<xs:complexType name=“schueler“> //definiert einen Typen
<xs:element name=“name“ type=“xs:string“ minOccurs=“1“ maxOccurs=“1“ />
//schueler hat genau ein Kinderelement „name“
...
<xs:attribute name=“id“ type=“xs:integer“ />
//schueler hat ein Attribute namens id
</xs:complexType>
Ein korrekter Schueler im XML sieht daher so aus:
<schueler id=“1“>
<name>Hugo</name>
...
</schueler>
Man kann unter type auch selbst definierte Typen verwenden.
Beispiel Einbindung eigener Typen
<xs:complexType name=“klasse“>
<xs:sequenz> //mehrere Objekte
<xs:element name=“schueler“ type=“schueler“>
</xs:sequenz>
</xs:complexType>
Beispiel für simple Typen
<xs:simpleType name=“monatInt“>
<xs:restriction base=“xs:integer“>
64/89
<xs:minInclusive value=“1“/>
<xs:maxInclusive value=“12“/>
</xs:restriction>
</xs:simpleType>
einfache Typen
xs:string/decimal/integer/float/boolean/date/time/anyType
xs:QName/anyURI/language/ID/IDREF
XML in Java-Anwendungen
Bei der DOM-API wird das gesamte Document in den Speicher geladen und ist daher sehr
speicherintensiv.
Laden
Beispiel
Document doc = new SAXBuilder().build(file);
Element root = doc.getRootElement()
List<Element> elemente = root.getChildren("tag1");
elemente.get(1).getAttributeValue(„id“);
Ändern
Beispiel
element.setAttribute(name,value);
element.setAttribute(attribute);
element.setText(text);
element.setContent(child);
...
Speichern
Beispiel
Document doc = new Document();
//... doc befüllen mit .addContent(), etc.
XMLOutputter out = new XMLOutputter(Format);
//normalerweise Format.getPrettyFormat();
out.output(doc, new FileWriter(new File(file));
XPath-Anwendung in Java
XPath xPath = XPath.newInstance(XPath-Expression als String);
List<?> nodeList = xPath.selectNodes(doc.getRootElement());
JDOMXPath xpath = new JDOMXPath(XPath-Expression als String);
List<?> ergebnis = xpath.selectNodes(doc);
65/89
Java und Sockets
Erklären Sie die Grundsätze der Netzwerkprogrammierung, Welche Socket-Arten gibt es und
wie unterscheiden sie sich? Auf welcher Netzwerkschicht operieren welche Socket-Arten?
Nennen die wichtigsten Klassen? Wie schreibe ich in ein Socket und wie lese ich von einem
Socket? Skizziere einen rudimentären Client/Server Zugriff.
Vergleich TCP UDP
UDP
●
●
●
●
TCP
●
●
●
●
Verbindungslos
Ankunft der Pakete nicht garantiert
Empfangsreihenfolge != Sendereihenfolge
Wenig Overhead (billig)
Verbindungsorientiert
Ankunft der Pakete garantiert (oder Fehlermeldung bei Unzustellbarkeit)
Empfangsreihenfolge = Sendereihenfolge
Viel Overhead (teuer)
Protokoll = Vereinbarung der Kommunikationsabwicklung
TCP und UDP operieren auf der Transportschicht (4) des OSI Modells.
Sockets
Das Programm, welches Dienste anbietet: Server
Das Programm, welches Dienste benutzt: Client
Der Client sendet dabei Aktiv eine Anfrage an den Server, der passiv auf Anfragen wartet.
Sockets bilden eine Plattformunabhängige, standardisierte API zwischen dem Netzwerkprotokoll des
OS und der Anwendungssoftware.
Charakterisierung
Eine Socket Verbindung kann mit den folgenden Parametern charakterisiert werden:
● Name des Quellrechners (IP-Adresse)
● Port des Quellrechners
● Name des Zielrechners (IP-Adresse)
● Port des Zielrechners
Komponenten (wichtige Klassen)
●
●
●
Adress- und Namensauflösung
○ InetAddress
UDP Kommunikation
○ DatagramPacket (Beinhaltet die Daten, die gesendet werden)
○ DatagramSocket (Senden und Empfangen von Packets)
TCP Kommunikation
66/89
●
○
○
ServerSocket (Auf Verbindung warten)
Socket (Senden und Empfangen)
○
Java Abbildung einer URL
URL
UDP
Der Server und der Client besitzen beiderseits ein DatagramSocket, über welches sie Pakete
(DatagramPackets) versenden (send-Methode) und empfangen (receive-Methode). Die Größe der
Pakete ist limitiert.
Ein UDP-Socket wird an einen Port des lokalen Rechners gebunden. Er kann nun Datagramme
senden und empfangen. new DatagramSocket(int port).
Die zu sendenden Daten werden in ein DatagramPaket gespeichert.
new DatagramPacket(byte[] daten, int laenge, InetAddress empfaenger,
int port);
Beispiel
Server
DatagramSocket socket = new DatagramSocket( 4711 );
while ( true ){
DatagramPacket packet = new DatagramPacket( new byte[1024], 1024 );
//Auf Anfrage warten
socket.receive( packet );
//Paket verarbeiten
packet.getData()
}
Client
InetAddress ia = InetAddress.getByName("localhost");
int port = 4711;
String s = "Hallo";
byte[] data = s.getBytes();
packet = new DatagramPacket(data, data.length, ia, port);
DatagramSocket toSocket = new DatagramSocket();
toSocket.send(packet);
TCP
Sockets sind eine Programmierschnittstelle für Streambasierte Kommunikation. Das Lesen und
Schreiben von Daten über Sockets erfolgt über Input- bzw. Outputstreams.
Ablauf der Kommunikation
1. Aufbau einer Verbindung über IP und Port
2. Lesen und Schreiben
67/89
3. Schließen der Verbindung
Die Verbindung wird aufgebaut, indem man ein neues Socket Objekt anlegt.
new Socket(String address, int port)
new Socket(InetAddress address, int port)
Zur Kommunikation wird ein Input oder Outputstream verwendet.
socket.getInputStream();
socket.getOutputStream();
Am Ende werden die Streams geschlossen und die Verbindung beendet. Dies sollte im Finally-Block
geschehen, damit auch bei einem auftretenden Fehler die Verbindung getrennt wird.
inputstream.close();
outputstream.close();
socket.close();
Beispiel
Server
ServerSocket serverSocket = new ServerSocket(6789);
while(true){
//Auf Anfrage warten
Socket connectionSocket = serverSocket .accept();
InputStream is = connectionSocket.getInputStream();
OutputStream os = connectionSocket.getOutputStream();
//Daten Verarbeiten
}
(helf): Request in eigenem Thread abwickeln
Client
String nachricht = “Hallo”;
Socket clientSocket = new Socket("localhost", 6789);
OutputStream os = clientSocket.getOutputStream();
InputStream is = clientSocket.getInputStream();
outToServer.writeBytes(nachricht.getBytes());
Datenaustausch
Der Datenaustausch ist streambasiert bei (TCP) Sockets (InputStream, OutputStream).
Der Ablauf einer Verbindung ist: Verbindung aufbauen, Daten austauschen, Schließen der
Verbindung.
Bei UDP (Datagrampackets) werden die Daten in einem Bytearray gesendet.
Obwohl es eine Klasse Socket (für Client) und ServerSocket (für Server) gibt, erfolgt die
Kommunikation auf beiden Seiten über eine Instanz der Klasse Socket. (serversocket.accept() liefert
ein Socket Objekt zur Kommunikation)
68/89
Java Generics
Motivation für generische Typen, Beispiel für parametrisierte Klassen in Java Bibliotheken,
Beispiel für eine eigene generische Klasse, Generische Methoden, Beispiel und
Einsatzmöglichkeit, Generische Methoden mit Typparameter, Generische Methoden mit
Wildcards, Einschränkungen für den Einsatz von Wildcards.
Die generischen Typ-Variablen repräsentieren zum Zeitpunkt der Implementierung unbekannte
Typen. Erst bei der Verwendung der Klassen, Schnittstellen und Methoden werden diese TypVariablen durch konkrete Typen ersetzt. Das bedeutet, dass während der Entwicklung von Klassen
und Methoden der genaue Typ von Variablen nicht feststehen muss. Erst bei der Verwendung wird
der Typ festgelegt.
Durch die Verwendung von generischen Klassen kann typsicher programmiert werden, da der
Compiler die Typen kontrollieren kann.
Beispiel mit ArrayList
Wenn zum Beispiel die Java Collection Klasse ArrayList ohne Generics verwendet wird, kann jedes
beliebige Element hinzugefügt werden. Daher, in die ArrayList al könnte zu den Integer Werten
auch ein String hinzugefügt werden, ohne dass der Compiler einen Fehler meldet.
Wenn die Elemente aus der Liste herausgenommen werden, müssen sie wieder auf ihren
ursprünglichen Wert gecastet werden.
ArrayList al = new ArrayList();
int sum = 0;
al.add(1234);
al.add(5678);
al.add("test");
//Keine Typ-Kontrolle
for (int i = 0; i < al.size(); i++) {
sum += (Integer)al.get(i);
//Ein Cast ist notwendig
//Fehler bei String "test"
}
Durch die Verwendung der generischen Klasse ArrayList fallen diese zwei Probleme weg. Denn durch
die generizität kann nur noch der vorgegebene Typ zur ArrayList hinzugefügt werden und beim
Herausnehmen der Elemente aus der Liste ist auch kein Cast mehr notwendig, da der Typ bereits
festgelegt wurde.
ArrayList<Integer> al = new ArrayList<Integer>();
int sum = 0;
al.add(1234);
al.add(5678);
//al.add("test");
//Nicht mehr möglich
69/89
for (int i = 0; i < al.size(); i++) {
sum += al.get(i);
//KEIN Cast notwendig
}
Eigene generische Klasse
Eine eigene Generische Klasse kann folgendermaßen erstellt werden. Der Typparameter T kann
jeden beliebigen Namen haben, allerdings ist es Konvention, einen großen Buchstaben zu
verwenden.
Generische Klassen können beispielsweise für Sortieralgorithmen verwendet werden.
public class Point<T>
{
private T x;
private T y;
public Point( T x, T y ) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public T getY() {
return y;
}
}
Verwendung
Unsere eigene Klasse kann wie folgt verwendet werden.
Point<Integer> = new Point<Integer>(2, 3);
Point<Double> = new Point<Double>(4.5, 5.6);
Die Doppelte Angabe der Typen kann weggelassen werden, wenn die Typen zuvor angegeben
wurden.
List<Map<Date, String>> listOfMaps = new ArrayList<Map<Date,
String>>();
Die folgende Zeile ist ebenfalls gültig.
List<Map<Date, String>> listOfMaps = new ArrayList<>();
Generische Methode
Eine generische Methode wird wird wie in einer generischen Klasse definiert.
public T compare(T m, T n) {
return m > n;
}
70/89
Probleme bei generischen Typen
Folgende Konstrukte sind mit generischen Typen nicht möglich:
● new
new T();
● instanceof
p instanceof Pocket<Number>
● casten
(Pocket<String>) new Pocket<Integer>();
● .class
Pocket<String>.class;
● Exceptions
class MyException<T> extends Exception { }
● statische Methoden
public static boolean isEmpty( T value )
● Arrays von generischen Klassen
pockets = new Pocket<String>[1];
Einschränkung von generischen Typen
Um den generischen Typ zu beschränken, kann mit extends eine Klasse angegeben werden die
der generische Typ erweitern soll. Zum Beispiel können mit extends Number nur Klassen die
Number erweitern (Integer, Float, Double, …) verwendet werden.
public class Point<T extends Number>
{
private T x;
private T y;
public Point( T x, T y ) {
this.x = x;
this.y = y;
}
}
Es kann auch vorgeschrieben werden, dass der generische Typ ein bestimmtest Interface
implementieren muss. Zum Beispiel sollte der generische Typ T das Interface Comparable
implementieren.
public static <T extends Comparable<T>> T max( T m, T n )
{
return m.compareTo( n ) > 0 ? m : n;
}
Mehrere Einschränkungen mit &
Mit & können mehrere Einschränkenungen verknüpft werden.
<T extends Serializable & Comparable>
Wildcards
Das Wildcard ? wird verwendet, um Klassen mit verschiedenen generischen Typen zu unterstützen.
Der Funktion printPoints können Objekte mit jedem beliebigen generischen Typ übergeben werden.
void printPoints(Point<?> points) {
for (Object p : points) {
System.out.println(p);
71/89
}
}
Wildcards einschränken
Wildcards können wie generische Typen mit extends eingeschränkt werden. Außerdem kann mit
super bestimmt werden, dass der Typ des Wildcard über dem angegebenen Typ stehen muss.
void printPoints(Point<? extends Number> points)
Alle Typen die Number erweitern sind erlaubt
void printPoints(Point<? super Integer> points)
Alle Typen über Integer sind erlaubt (also Integer, Number, Object)
72/89
Drucken in Java
Die Erzeugung von Druckwerken muss auch in Java-Anwendungen möglich sein. Das
Ausdrucken von mehrseitigem, formatiertem Text, Bildern im Posterdruck bzw. skaliert und
Texten mit Bildern ist zu erklären.
In Java ist es möglich, Daten über eine Schnittstelle zum Drucker zu senden damit diese dann
gedruckt werden. Der erste Schritt um dies durchzuführen ist ein neues Objekt der Klasse
„Printerjob“ zu erstellen.
PrinterJob job = PrinterJob.getPrinterJob();
Dieses Objekt ist sozusagen der Controller des gesamten Druckauftrages, es kontrolliert welche
Seitenformate auf welche Seite angewandt werden und weißt jeder Seite den definierten Inhalt zu.
Es gibt 2 Ansätze in Java zu drucken:
1. Möglichkeit
Das Betriebssystem eigene Dialogfeld zum drucken wird Aufgerufen und die Daten werden an das
Java-Programm weitergegeben und damit gedruckt. Dieser Weg ist der einfachste und braucht nur
einen Befehl:
job.printDialog();
Folgendes Feld wird unter Windows gestartet:
Nun kann man in einer übersichtlichen Benutzeroberfläche den Drucker und andere
Formatierungsoptionen auswählen.
73/89
Wenn der „Abbrechen“ Button gedrückt wird liefert die Methode false zurück.
2. Möglichkeit:
Ohne dem Standard Dialogfeld vom Betriebssystem, also alle Einstellungen werden vom Programm
selbst durchgeführt und der Benutzer hat keinen Einfluss darauf.
Drucker einstellen
Um einen Drucker einzustellen müssen wir uns ein Feld mit allen installierten Druckern vom System
holen dies geschieht mit dem Folgenden Befehl:
PrintService[] printers = PrinterJob.lookupPrintServices();
Um unserem Druckauftrag letztenendes einen Drucker zuzuweisen wird folgender Befehl benötigt:
job.setPrintService(printers[1]);
PageFormat
Als nächstes muss noch das Seitenformat eingestellt warden in dem gedruckt warden soll, in
meinem Fall A4. Dazu erstellt man ein „PageFormat“ Objekt und setzt die gewünschte Orientierung (
PageFormat.LANDSCAPE – Querformat , PageFormat.PORTRAIT – Hochformat )
PageFormat normalFormat = new PageFormat();
normalFormat.setOrientation(PageFormat.LANDSCAPE);
Nun kann man noch den Bereich der zu Drucken ist auf der Seite festlegen. Hierzu erstellt man ein
neues Objekt der Klasse „Paper“ und ruft die Methode „setImagableArea“ auf. Zu letzt wird das
Objekt dem Pageformat zugewiesen.
Paper paper = new Paper();
paper.setSize(594.936, 841.536); //Set to A4 Size
paper.setImageableArea(0, 0, 800 , 841.536);
//Parameter: X-Startpunkt, Y-Startpunkt, breite, höhe
normalFormat.setPaper(paper);
74/89
Inhalt des Ausdruck
Eine Printable sieht dann wie folgt aus , wichtig ist das links oben der Punkt (0/0) ist:
Grundsätzlich ist das Drucken des Inhaltes nur von einer Klasse möglich die das Interface Printable
implementiert. In der Nachfolgenden Grafik sieht man wie das Zusammenspiel zwischen Printerjob
und Printable abläuft:
Eine leere Klasse mit dem Printable interface implementiert sieht dann wie folgt aus:
import java.awt.print.Printable;
public class testprint implements Printable{
@Override
75/89
public int print(Graphics graphics, PageFormat pf, int pageIndex)
throws PrinterException {
return 0;
}
}
Die „print“ Methode wird dabei automatisch während des Druckvorgangs aufgerufen. Des Weiteren
implementiert das Interface 2 Konstanten:
● NO_SUCH_PAGE
● PAGE_EXISTS
Diese werden ausschließlich als Return-Werte benutzt und sagen dem Printerjob ob diese Seite
gedruckt werden soll „PAGE_EXISTS“ oder nicht „NO_SUCH_PAGE“ (z.B. weil einFehler beim Befüllen
des Printables aufgetreten ist).
(helf): NO_SUCH_PAGE auch bei der ersten nicht mehr befüllten Seite
Befüllen des Printables
Das Befüllen des Printables ist ähnlich wie bei Erstellung der GUI. Man zeichnet bestimmte
Komponenten bei bestimmten Koordinaten auf die Seite, doch zunächst wandeln wir das
übergebene „Graphics“ Objekt in ein „Graphics2D“ Objekt um:
Graphics2D g2d = (Graphics2D) graphics;
Auf diesem g2d Objekt kann dann ganz einfach mit verschieden Methoden geometrische Formen
und Bilder gezeichnet werden:
g2d.drawImage(bufferedImage , 10 , 10 , null);
g2d.drawLine(20, 20, 50, 50);
g2d.drawRect(30, 30, 200, 100);
1. Es wird ein Buffered Image auf die Grafik des Printables gezeichnet an der Position (10/10)
2. Es wird eine Linie gezeichnet vom Punkt (20, 20) zu (50, 50)
3. Es wird ein Rechteck gezeichnet mit einem Linkenoberen Eckpunkt (30, 30) und einer Seitenlänge
von 200 und Höhe von 100
Drucken von Text
Das Drucken von Text fällt etwas schwieriger aus , hier ein fertiger Code der im einzelnen erklärt
wird:
public int print(Graphics g, PageFormat format, int pageIndex) {
Graphics2D g2d = (Graphics2D) g;
//Beginn in den Druckbaren Bereich verschieben
g2d.translate(format.getImageableX(), format.getImageableY());
g2d.setPaint(Color.black);
76/89
AttributedString myStyledText = new AttributedString(“TEST”);
//LineBreakMeasurer um die Zeilenlänge zu ermitteln
Point2D.Float pen = new Point2D.Float();
AttributedCharacterIterator charIterator = mStyledText.getIterator();
LineBreakMeasurer measurer = new
LineBreakMeasurer(charIterator,g2d.getFontRenderContext());
float wrappingWidth = (float) format.getImageableWidth();
//Über die Seite laufen und sie mit Text füllen
while (measurer.getPosition() < charIterator.getEndIndex()) {
TextLayout layout = measurer.nextLayout(wrappingWidth);
pen.y += layout.getAscent();
float dx = layout.isLeftToRight() ? 0 : (wrappingWidth –
layout.getAdvance());
layout.draw(g2d, pen.x + dx, pen.y);
pen.y += layout.getDescent() + layout.getLeading();
return Printable.PAGE_EXISTS;
}
Kurz gesagt muss man hier die Y-Koordinate nach jeder Zeile um eine gewisse Höhe nach unten
Schieben nämlich die Höhe der Schriftart (Ascent + Descent) + Zeilenabstand (Leading), die nächste
Grafik sollte das ganze verdeutlichen:
Bilder im Posterdruck
Um ein Poster zu drucken das über mehrere Seiten gehen soll ist ein Pageable notwendig.
Der erste Schritt der gemacht werden muss ist das zerteilen des Bildes in kleinere Teile die genauso
groß sind wie der druckbare Bereich (ImageableArea) der Seite. Ein Code zum teilen sieht man hier:
77/89
Code Erklärung:
1. wir holen die breite und Höhe des bildes
2. wer berechnen wieviele Seiten wir brauchen und speichern sie in “anzhor”
(anzahlhorizontal) und “anzvert” (anzahlvertikal) ab. Werte sind GANZZAHLEN weil int/int.
Bsp. wir wissen dann das wir z.b 3x4 Seiten brauchen
3. Der restliche Bereich wird berechnet der nicht mehr eine ganze Seite gebraucht hat.
4. Nun muss bei unsere “Matrix” von vorhin bei horizontal und vertikal 1nes dazugezählt
werden wenn die restbreite und resthöhe größer 0 sind
5. Zu guter letzt wird mit einer doppelschleife durchgegangen und die einzelnen Teilbilder mit
“.getSubimage()” vom Orginalbild in eine Arraylist (bilder) gespeichert.
Der Restliche Aufbau des Pageables ist nicht sehr kompliziert. Die getNumberofPages Methode kann
diesmal die Länge der Arraylist zurückgeben damit das Pageable weiß wenn es keine Printables mehr
holen soll.
78/89
Die getPrintable() Methode liefert nur ein Printable zurück das ich als nächstes erkläre:
Es wird dabei nur das Graphics2D objekt um ImageableX und ImageableY verschoben (translate) und
dann wird das bild auf(0/0) gezeichnet.
Wichtig: 0/0 ist jetzt ImageableX/ImageableY durch das translate wird der 0/0 punkt verschoben
79/89
Was ist Android?, Aufbau der Architektur, AVD-Emulator, Zusammenhänge: Activity, View,
Event, Intent, GUIs mit Android, File und Dateizugriff, Anwendungen auf‘s Handy bringen.
Was ist Android?
Android ist ein offenes und freies Betriebssystem, welches von der Open Handset Alliance (OHA)
entwickelt und kostenlos zur Verfügung gestellt wird. Hauptsächlich wird es auf Mobiltelefonen und
Tablets verwendet, aber auch Wearables (Uhren, Brillen, …) basieren zunehmend auf diesem OS.
Nebem dem Betriebssystem, welches auf einem Linux-Kernel aufbaut, werden auch gleich die
notwendigen Hardware-Treiber und umfassende Standard-Apps als “Gesamtpaket” mitgeliefert. Da
Android sehr modular aufgebaut ist, ermöglicht dies dem Entwickler, einzelne Komponenten des
Systems (wie den Launcher, Mailprogramme, …) problemlos gegen eigene Applikationen
auszutauschen.
Aufbau der Architektur
Android ist als Schichtenmodell konzipiert und in 4 Ebenen unterteilt: (von oben nach unten)
4. Applications
3. Application Framework
2. Libaries + Android Runtime
1. Linux Kernel
1. Linux Kernel
Der Linux-Kernel bildet die Basis von Android und kümmert sich um die Prozesskommunikation und
die Prozessverwaltung. Außerdem sind auf dieser Ebene die notwendigen Hardware-Treiber
vorhanden, so dass diese eine Schnittstelle zwischen Software und Hardware darstellen und sich
bequem von den darüber liegenden Schichten ansteuern lassen.
2. Libaries + Android Runtime
Diese Schicht ist in 2 Bereiche unterteilt, einerseits in die Libaries (= Systembibliotheken) und
andererseits in die Android Runtime (= Android Laufzeitumgebung).
Libaries / Systembibliotheken
Hier sind die Basisbibliotheken beheimatet, welche meist in C oder C++ geschrieben sind und den
Entwicklern fertige Methoden und Eigenschaften bereitstellen. Ein Beispiel hierfür sind der
Browser(Webkit), Datenbanken (SQLite), Verschlüsselung, Touch-Gesten oder Grafik und
Multimedia.
Android Runtime/Laufzeitumgebung
Hier ist die Dalvik Virtuelle Maschine beheimatet, welche ähnlich wie die JAVA Virtuelle Maschine
dafür sorgt, dass der Java Code interpretiert wird. Jede App, die der Benutzer am Smartphone
ausführt, wird in einer eigenen Instanz einer DVM gestartet. Dadurch läuft jede App als ein
Betriebssystemprozess mit eigenen Speicherbereich und somit ist eine höhere
80/89
Sicherheit/Verfügbarkeit der App gewährleistet. Sollte eine App abstürzen, funktioniert der Rest
problemlos weiter.
3. Application Framework (Programmierschnittstelle)
Eine Schicht weiter oben liegt das Application Framework. Hier werden von Android viele
Programmierschnittstellen zur Verfügung gestellt, welche es den einzelnen Anwendungen erlauben,
dass sie miteinander kommunizieren.
Beispiele sind:
● Activity Manager (verwaltet Anwendungen und steuert deren Lebenszyklus)
● Dienste für Telefonie
● Ortung
● Notification Manager (Informationen werden in der Statusleiste gesammelt/angezeigt)
● View System (also Eingabefelder, Textboxen, Buttons, …)
Besonders ist hier der Activity Manager hervorzuheben, welcher sich um alles kümmert, das mit der
Anzeige der Activity zu tun hat. “Surft” man durch die Activity und betätigt beispielsweise die
“Zurück”-Taste, so wird automatisch die vorherige Activity aufgerufen, ohne dass sich der Entwickler
darum kümmern muss.
4. Applications (Anwendungsschicht)
Auf dieser obersten Ebene werden die Anwendungen (= Apps) installiert. Dies ist die Fläche, welche
der Anwender letztendlich zu Gesicht bekommt. All diese Apps (Kamera, Kalender, Mails, Browser,
…) sind i.d.R. in JAVA programmiert.
AVD-Emulator
Mithilfe des AVD-Emulators lassen sich unter Windows/... virtuelle Androidgeräte erzeugen und
somit die Android-Oberfläche emulieren. Dazu notwendig sind die Bibliotheken, welche über den
SDK Manager bezogen werden können.
Der AVD-Emulator ist dann sinnvoll, wenn man kein Android-Gerät zur Verfügung hat, aber dennoch
gerne auf ein Androidsystem zugreifen möchten, zB aus Testzwecken. Vor allem selbst entwickelte
Apps lassen sich auf Funktionstüchtigkeit und Benutzerfreundlichkeit der GUI prüfen. Allerdings ist
zu beachten, dass der Emulator viele Ressourcen benötigt und daher meist sehr langsam ist. Zudem
werden nicht alle Hardwarekomponenten bzw. -funktionen emuliert oder unterstützt (zB GPS, …).
Daher kann man ihn nur bedingt zum Testen einsetzen. Ein schnellerer Emulator ist geny-Motion.
Grundbausteine
Activity
Repräsentiert ein sichtbares Benutzerinterface, also das, was der Benutzer am Bildschirm angezeigt
bekommt. Eine Applikation kann aus mehreren "Activitys" bestehen. Jedoch kann im Lebenszyklus
einer Applikation immer nur eine "Activity" zur selben Zeit aktiv sein. Das bedeutet, wenn man in
Activity A die Activity B aufruft, A auf den Back-Stack gelegt wird, bis dass B wieder geschlossen wird
81/89
und A erneut angezeigt wird. Jeder Activity muss ein Layout-File (XML-File) zugeordnet werden und
jede Activity muss in der Manifest-Datei vermerkt sein.
(helf): bsp-code
View
Ist ein bestimmtes GUI-Element (Tabelle, Button, Text, …), welches von der Activity über eine
eindeutige ID angesprochen und verändert werden kann (eingefärbt, befüllt, …). Es kann entweder in
einem XML-File beschrieben werden oder programmtechnisch (also durch Java-Code) erzeugt
werden.
82/89
Event
Werden ausgelöst, sobald der Benutzer mit dem Smartphone interagiert (z.B. einen Button drücken,
Text eingeben, …) oder andere Ereignisse (Anrufe, SMS, …) auftreten. Diese Events können
überwacht werden, indem man zuvor einen entsprechenden Listener registriert. Sobald ein Listener
anschlägt (z.B. empfang eine SMS), wird der sich in der Listener-Methode befindliche Code
ausgeführt.
Intent
Rufen andere Activity auf und können Parameter zwischen 2 Apps austauschen. Zum Beispiel in
einer eigenen entwickelten App die Kamerafunktion aufrufen oder ein Spielergebnis per SMS mit
anderen Teilen.
GUIs mit Android
GUIs in Android zu erstellen kann generell über 2 Methoden erfolgen. Entweder über ein XML-File
(Standardweg) oder programmtechnisch.
In jedem Fall muss aber zumindest pro Activity 1 Layout-XML-File mit eindeutiger ID vorhanden sein.
In diesem Layout-File können nun die verschiedenen Layouts eingefügt werden:
● Linear Layout
alles wird untereinander (horizontal) oder nebeneinander (vertical) angeordnet
● Relative Layout
die genaue Position eines Elements wird angegeben (10px von rechtem Rand)
● Table Layout
ist eine Tabelle, bei der beliebig viele Zeilen erstellt werden können, welche anschließend
mit Inhalten befüllt werden können.
Jedes dieser Layouts kann beliebig viele Layouts als Unterelemente haben. Man kann aber auch
normale Layoutkomponenten in ein Layout einfügen.
Mit jeder neuen Android-Hauptversion wächst auch die Anzahl der Layoutkomponenten, so dass es
für beinahe jeden Anlass etwas gibt:
Buttons, Textfelder (nur Anzeige von Text), veränderbare Textfelder (Anzeige + Eingabe),
Checkboxen, RadioButtons, TextView, Picker (zur Auswahl von Datum, Zeit, …), Fortschrittsanzeige,
etc.
Jede dieser Komponenten kann wieder programmtechnisch zur View hinzugefügt werden oder
statisch im XML-File eingetragen werden. Dabei ist zu beachten, dass die Komponenten später über
eine ID angesprochen werden müssen, um sie zu ändern oder die eingegeben Werte abzurufen.
File / Dateizugriff
Da Android auf Java aufbaut, ist auch der Filezugriff halbwegs ident. Der einzige Unterschied besteht
darin, dass man verschiedene Speicherbereiche zur Verfügung hat:
● internal storage
immer vorhanden, auf die Daten kann nur über deine App zugegriffen werden, wenn die
App deinstalliert wird, werden auch die Daten gelöscht
● external storage
auf die Dateien kann Systemweit zugegriffen werden, sie werden auch bei einer
Deinstallation nicht gelöscht
83/89
Mithilfe des Names und des Pfad kann nun die Datei verwendet werden:
Verzeichnis holen → File öffnen → Reader/Writer setzen → lesen schreiben → Reader/Writer
schließen → File schließen
Beispiel
// Verzeichnis holen
File verzeichnis = context.getDir("testDir", MODE_PRIVATE);
// Referenz auf File holen
test = new File(verzeichnis, "test.txt");
// Reader öffnen
FileInputStream inputStream = new FileInputStream(test);
BufferedReader fileInReader = new BufferedReader(new
InputStreamReader(inputStream));
// Eine Zeile einlesen und Ausgeben
System.out.println(fileInReader.readLine());
// Reader schließen
fileInReader.close();
ACHTUNG: in der Manifest muss die Berechtigung gesetzt sein!!
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Anwendungen aufs Handy bringen
Fertig entwickelte Apps werden über sogenannte Marketplaces an die Anwender verteilt. Allem
voran ist hier der Google Play Store als größer Appmarkt für Android zu erwähnen. Die fertige App
muss vom Entwickler mit einer Signatur versehen werden, wird dann anschließend in den Google
Play Store hochgeladen und kann nach positivem Abschluss diverser Prüfungen (Malware, Inhalte,
…) verteilt werden.
Für Apps, welche sich noch in der Entwicklungsphase befinden, ist ein anderer Weg möglich.
Man verbindet das Smartphone/Tablet mit dem PC und installiert die nötigen Treiber. Anschließend
aktiviert man auf dem Gerät die “Debugging-Funktion”, damit man die Konsolenaushaben unter
Eclipse einsehen kann. Nun wird in Eclipse auf “Ausführen” geklickt, danach wird ausgewählt, auf
welchem Gerät die App installiert werden soll. Eine gleiche oder höhere Androidversion auf Gerät
wie in Manifestdatei ist nötig.
Ein anderer Weg wäre noch, eine .apk-Datei zu erstellen, diese manuell aufs Gerät zu spielen und
dort zu installieren (“Installation von Fremdquellen” muss aktiviert sein).
84/89
Logging und Unit-Testing in Java
Einsatz von Logging?, Log Levels, Logger Objekt, Handler, Formatter, Beispiel, Was sind
Unit-Tests?, Wie werden sie eingesetzt?, JUnit, Prinzipien, Nenne und erkläre die wichtigsten
Annotationen
Allgemeines zu Logging
Das Loggen (Protokollieren) von Informationen über Programmzustände ist ein wichtiger Teil, um
später den Ablauf und Zustände von Programmen rekonstruieren und verstehen zu können. Mit
einer Logging-API lassen sich Meldungen auf die Konsole oder in externe Speicher wie Text- oder
XML-Dateien bzw. in eine Datenbank schreiben oder über einen Chat verbreiten. Des Weiteren
können so auch Fehler protokolliert werden und somit die Fehlerquelle gefunden werden.
Logging-APIs
Bei den Logging-Bibliotheken und APIs ist die Java-Welt leider gespalten. Da die JavaStandardbibliothek in den ersten Versionen keine Logging-API anbot, füllte die Open-SourceBibliothek log4j schnell diese Lücke. Sie wird heute in nahezu jedem größeren Java-Projekt
eingesetzt. Als Sun dann in Java 1.4 eine eigene Logging-API (JSR-47) einführte, war die JavaGemeinde erstaunt, dass java.util.logging (JUL) weder API-kompatibel zum beliebten log4j noch so
leistungsfähig wie log4j ist.
Kurz gesagt gibt es eine Standardbibliothek (java.util.logging) von SUN für Logging, dennoch wird
meist die Open-Source-Bibliothek log4j benutzt, denn diese hat sich etabliert und ist
leistungsfähiger.
Log Levels
Loglevels stellen verschiedene Dringlichkeitsstufen für die jeweiligen Nachrichten dar. Das Log Level
kann also als zentrale Steuerung gesehen werden, die steuert welche Nachricht von welcher Klasse
mit welcher Dringlichkeit mitprotokolliert wird. Die logging.properties Datei liegt im Java
Installationsverzeichnis im lib Ordner.
Log Levels in absteigender Ordnung
●
●
●
●
●
●
●
SEVERE (höchste Dringlichkeit)
WARNING
INFO
CONFIG
FINE
FINER
FINEST (niedrigste Dringlichkeit)
Das Log Level muss natürlich auch gesetzt werden.
LOGGER.setLevel(Level.INFO) //Level auf INFO setzten
85/89
Das Logger-Objekt
Das zentrale Logger-Objekt wird über Logger.getAnononymousLogger(), oder über
Logger.getLogger(String name) geholt. Wobei für name in der Regel der voll qualifizierte
Klassenname steht.
In der Regel ist der Logger als private static final in der Klasse deklariert.
Beispiel
private static final Logger jlog= Logger.getLogger(“test”);
jlog.log(Level.SEVERE, “Nachricht”);
Ausgabe:
11.01.2007 14:41:48 test <clinit> SCHWERWIEGEND: Nachricht
Imports
●
●
java.util.logging.Level
java.util.logging.Logger
Handler
Jeder Logger kann mehrere Handler haben. Ein Handler kann mit setLevel() ein- und mit
setLevel(Level.OFF) ausgeschaltet werden.
Es gibt verschiedene Handler:
● ConsoleHandler (Schreibt die log Nachricht in die Konsole)
● FileHandler (Schreibt die log Nachricht in ein File)
● StreamHandler (Schreibt die log Nachricht an einen OutputStream)
● SocketHandler (Schreibt die log Nachricht an TCP Ports)
● MemoryHandler (Buffert die log Nachrichten in den Speicher)
Wobei ab LogLevel INFO automatisch an die Konsole gemeldet wird.
Beispiel zum setzen eines FileHandlers
Handler fileHandler = new FileHandler("log.txt");
log.addHandler(fileHandler);
Formatter
Jede Handler-Ausgabe kann mittels eines Formatierers konfiguriert werden.
Der Formatter wird sozusagen auf den Handler gesetzt. Dies geschieht mit der Methode
setFormatter: fileHandler.setFormatter(new SimpleFormatter());
Was sind Unit-Tests
Unit-Tests sind Tests möglichst kleiner Code-Stücke. Also am besten einzelne Funktionen/Methoden.
Im Deutschen werden sie auch “Modultest” oder “Komponenten-Test” genannt, da wirklich immer
86/89
nur Code Stücke/Module/Komponenten getestet werden. Ganz im Gegensatz zum Integrations- und
Systemtest.
Inhalt von Unit-Tests
●
●
●
Logisch:
1 Testfall pro Verhalten einer Funktion
aber auch alle Sonderfälle, Randfälle und Fehlerfälle
Technisch:
1 Testfall pro möglichen Codepfad
Weiters:
1 Testfall für jeden bekannten Bug:
Hilft dem Entwickler beim Reproduzieren
Testet die Behebung des Bugs
Schützt davor, dass sich derselbe Bug in Zukunft nochmals einschleicht
Was ist JUnit
Junit ist ein Test-Framework. Es bietet Klassen an um geschriebenen Quelltext leicht zu prüfen.
Außerdem verlangt JUnit während des Testen keine Benutzerinteraktion und ist einfach
anzuwenden, aber verlangt dafür ein wenig Disziplin.
Prinzipien
●
●
●
Erst denken, dann programmieren.
Erst testen, dann programmieren.
“Test a little, write a little, test a little, write a little, ….”
JUnit ersetzt jedoch nicht das Schreiben der Testklassen!
Wie werden JUnit Tests eingesetzt
Für JUnit Tests werden eigene Testklassen geschrieben, welche mit Annotationen gekennzeichnet
werden (Dazu mehr im nächsten Unterpunkt).
Die Tests ansich werden mit sogenannten Assertions durchgeführt.
Assertions
assertEquals(Object expected, Object actual)
Vergleicht zwei Objekte auf Gleichheit (Vergleichbar mit equals)
assertEquals(double expected, double actual, double delta)
Vergleicht zwei double Werte. Mit delta kann eine Abweichung angegeben werden
assertSame(Object expected, Object actual)
Vergleicht zwei Objekte auf totale Gleichheit (Vergleichbar mit == )
87/89
assertTrue(boolean expression)
Liefert true wenn der Parameter true ist
assertFalse(boolean expression)
Liefert false wenn der Parameter true ist
assertNull(Object actual)
Liefert true wenn der Parameter null ist
assertNotNull(Object actual)
Liefert true wenn der Parameter nicht null ist
Annotationen
@Test
Testmethoden werden mit der @Test Annotation markiert.
@Test
public void addition() {
assertEquals(12, simpleMath.add(7, 5));
}
//Liefert true
@Before und @After
Methoden die mit @Before oder @After markiert werden, werden entweder vor oder nach jedem
Testfall durchlaufen.
@Before
public void runBeforeEveryTest() { //Wird vor jeden Testfall durchlaufen
simpleMath = new SimpleMath();
}
@After
public void runAfterEveryTest() {
simpleMath = null;
}
//Wird nach jeden Testfall durchlaufen
@BeforeClass und @AfterClass:
Methoden die mit @BeforeClass und @AfterClass markiert sind, werden einmal vor bzw. nachdem
alle Testfälle durchlaufen sind ausgeführt.
@BeforeClass
public static void runBeforeClass() {
// run for one time before all test cases
}
@AfterClass
public static void runAfterClass() {
88/89
// run for one time after all test cases
}
89/89
Herunterladen