Objektorientierte Programmierung

Werbung
Inhaltsverzeichnis
Objektorientierte Programmierung.............................................................................. 2
Abstract ...................................................................................................................... 8
Interface.................................................................................................................... 12
GUI ........................................................................................................................... 18
Event Handling ......................................................................................................... 23
ODBC ....................................................................................................................... 28
Streams .................................................................................................................... 35
JSP & Servlets.......................................................................................................... 50
Exceptions ................................................................................................................ 56
Array – Collection - Generisch.................................................................................. 60
MVC (Model-View-Controller)................................................................................... 66
JAVA vs. C# ............................................................................................................. 70
Verfasser: Schlögl
Objektorientierte Programmierung
Die objektorientierte Programmierung (kurz OOP) ist ein auf dem Konzept der
Objektorientierung basierendes Programmierparadigma. Die Grundidee der
objektorientierten Programmierung ist, Daten und Funktionen, die auf diese Daten
angewandt werden können, möglichst eng in einem sogenannten Objekt
zusammenzufassen und nach außen hin zu kapseln, so dass Methoden fremder
Objekte diese Daten nicht versehentlich manipulieren können. Im Gegensatz dazu
beschreibt das vor der OOP vorherrschende Paradigma eine strikte Trennung von
Funktionen (Programmcode) und Daten, dafür aber eine schwächere Strukturierung
der Daten selbst.
Im Vergleich mit anderen Programmiermethoden verwendet die objektorientierte
Programmierung neue, andere Begriffe.
Die einzelnen Bausteine, aus denen ein objektorientiertes Programm während seiner
Abarbeitung besteht, werden als Objekte bezeichnet. Die Konzeption dieser Objekte
erfolgt dabei in der Regel auf Basis der folgenden Paradigmen:
Abstraktion
Jedes Objekt im System kann als ein abstraktes Modell eines Akteurs
betrachtet werden, der Aufträge erledigen, seinen Zustand berichten und
ändern und mit den anderen Objekten im System kommunizieren kann, ohne
offenlegen zu müssen, wie diese Fähigkeiten implementiert sind (vgl.
abstrakter Datentyp (ADT)). Solche Abstraktionen sind entweder Klassen (in
der klassenbasierten Objektorientierung) oder Prototypen (in der
prototypbasierten Programmierung).
Klasse
Die Datenstruktur eines Objekts wird durch die Attribute (auch Eigenschaften)
seiner Klassendefinition festgelegt. Das Verhalten des Objekts wird von den
Methoden der Klasse bestimmt. Klassen können von anderen Klassen
abgeleitet werden (Vererbung). Dabei erbt die Klasse die Datenstruktur
(Attribute) und die Methoden von der vererbenden Klasse (Basisklasse).
Prototype
Objekte werden durch das Klonen bereits existierender Objekte erzeugt und
können anderen Objekten als Prototypen dienen und damit ihre eigenen
Methoden zur Wiederverwendung zur Verfügung stellen, wobei die neuen
Objekte nur die Unterschiede zu ihrem Prototypen-Objekt definieren müssen.
Datenkapselung
Als Datenkapselung bezeichnet man in der Programmierung das Verbergen
von Implementierungsdetails. Der direkte Zugriff auf die interne Datenstruktur
wird unterbunden und erfolgt statt dessen über definierte Schnittstellen.
Objekte können den internen Zustand anderer Objekte nicht in unerwarteter
Weise lesen oder ändern. Ein Objekt hat eine Schnittstelle, die darüber
bestimmt, auf welche Weise mit dem Objekt interagiert werden kann. Dies
verhindert das Umgehen von Invarianten des Programms.
Polymorphie
Verschiedene Objekte können auf die gleiche Nachricht unterschiedlich
reagieren. Wird die Zuordnung einer Nachricht zur Reaktion auf die Nachricht
erst zur Laufzeit aufgelöst, dann wird dies auch späte Bindung genannt.
Feedback
Verschiedene Objekte kommunizieren über einen Nachricht-AntwortMechanismus, der zu Veränderungen in den Objekten führt und neue
Nachrichtenaufrufe erzeugt. Dafür steht die Kopplung als Index für den Grad
des Feedback.
Vererbung
Vererbung heißt vereinfacht, dass eine abgeleitete Klasse die Methoden und
Attribute der Basisklasse ebenfalls besitzt, also „erbt“. Somit kann die
abgeleitete Klasse auch darauf zugreifen. Neue Arten von Objekten können
auf der Basis bereits vorhandener Objekt-Definitionen festgelegt werden. Es
können neue Bestandteile hinzugenommen werden oder vorhandene
überlagert werden.
Persistenz
Objekt-Variablen existieren, solange die Objekte vorhanden sind und
„verfallen“ nicht nach Abarbeitung einer Methode.
Vererbung
Die Vererbung (engl. Inheritance) ist eines der grundlegenden Konzepte der
Objektorientierung und hat große Bedeutung in der Softwareentwicklung. Die
Vererbung dient dazu, aufbauend auf existierenden Klassen neue zu schaffen, wobei
die Beziehung zwischen ursprünglicher und neuer Klasse dauerhaft ist. Eine neue
Klasse kann dabei eine Erweiterung oder eine Einschränkung der ursprünglichen
Klasse sein. Neben diesem konstruktiven Aspekt dient Vererbung auch der
Dokumentation von Ähnlichkeiten zwischen Klassen, was insbesondere in den
frühen Phasen des Softwareentwurfs von Bedeutung ist. Auf der Vererbung
basierende Klassenhierarchien spiegeln strukturelle und verhaltensbezogene
Ähnlichkeiten der Klassen wider.
Die vererbende Klasse wird meist Basisklasse (auch Super-, Ober- oder
Elternklasse) genannt, die erbende abgeleitete Klasse (auch Sub-, Unter- oder
Kindklasse). Den Vorgang des Erbens nennt man meist Ableitung oder
Spezialisierung, die Umkehrung hiervon Generalisierung, was ein vorwiegend auf die
Modellebene beschränkter Begriff ist. In der Unified Modeling Language (UML) wird
eine Vererbungsbeziehung durch einen Pfeil mit einer dreieckigen Spitze dargestellt,
der von der abgeleiteten Klasse zur Basisklasse zeigt. Geerbte Attribute und
Methoden werden in der Darstellung der abgeleiteten Klasse nicht wiederholt.
Abgeleitete Klasse und Basisklasse stehen typischerweise in einer „ist-ein“Beziehung zueinander. Klassen dienen der Spezifikation von Datentyp und
Funktionalität, die beide vererbt werden können. Einige Programmiersprachen
trennen zumindest teilweise zwischen diesen Aspekten und unterscheiden zwischen
Schnittstelle (engl. Interface) und Klasse. Wenn eine abgeleitete Klasse von mehr als
einer Basisklasse erbt, wird dies Mehrfachvererbung genannt. Mehrfaches Erben ist
nicht bei allen Programmiersprachen möglich, bei manchen nur in eingeschränkter
Form.
Datenkapselung
Als Datenkapselung (englisch: encapsulation, nach David Parnas auch bekannt als
information hiding) bezeichnet man in der Programmierung das Verbergen von Daten
oder Informationen vor dem Zugriff von außen. Der direkte Zugriff auf die interne
Datenstruktur wird unterbunden und erfolgt statt dessen über definierte Schnittstellen
(Black-Box-Modell).
Kapselung ist auch ein wichtiges Konzept der objektorientierten Programmierung. Als
Kapselung bezeichnet man den kontrollierten Zugriff auf Methoden bzw. Attribute von
Klassen. Klassen können den internen Zustand anderer Klassen nicht in
unerwarteter Weise lesen oder ändern. Eine Klasse hat eine Schnittstelle, die
darüber bestimmt, auf welche Weise mit der Klasse interagiert werden kann. Dies
verhindert das Umgehen von Invarianten des Programms. Vom Innenleben einer
Klasse soll der Verwender – gemeint sind sowohl die Algorithmen, die mit der Klasse
arbeiten, als auch der Programmierer, der diese entwickelt – möglichst wenig wissen
müssen (Geheimnisprinzip). Durch die Kapselung werden nur Informationen über
das „Was“ (Funktionsweise) einer Klasse nach außen sichtbar, nicht aber das „Wie“
(die interne Repräsentation). Dadurch wird eine Schnittstelle nach außen definiert
und zugleich dokumentiert.
Für die Kapselung verwendete Zugriffsarten
Die UML als De-facto-Standardnotation erlaubt die Modellierung folgender
Zugriffsarten (in Klammern die Kurznotation der UML):
public (+)
zugreifbar für alle Ausprägungen (auch die anderer Klassen),
private (-)
Nur für Ausprägungen der eigenen Klasse zugreifbar,
protected (#)
Nur für Ausprägungen der eigenen Klasse und von Spezialisierungen
derselben zugreifbar,
package (~)
erlaubt den Zugriff für alle Elemente innerhalb des eigenen Pakets.
Anmerkung: Die Handhabung des Schlüsselwortes package ist in den verschiedenen
Programmier- bzw. Skriptsprachen unterschiedlich. Ersetzung in der jeweiligen
Sprache:
•
CSharp (C#): internal
•
•
Visual Basic .NET: friend
Java: Keine Definition bedeutet Package-Zugriff (Default).
Die Möglichkeiten zur Spezifizierung der Zugreifbarkeit sind je nach
Programmiersprache unterschiedlich.
Vorteile der Kapselung
•
•
•
•
Da die Implementierung einer Klasse anderen Klassen nicht bekannt ist, kann
die Implementierung geändert werden, ohne die Zusammenarbeit mit anderen
Klassen zu beeinträchtigen.
Erhöhte Übersichtlichkeit, da nur die öffentliche Schnittstelle einer Klasse
betrachtet werden muss.
Beim Zugriff über eine Zugriffsfunktion spielt es von außen keine Rolle, ob
diese Funktion 1:1 im Inneren der Klasse existiert, das Ergebnis einer
Berechnung ist, oder möglicherweise aus anderen Quellen (z. B. einer Datei
oder Datenbank) stammt.
Deutlich verbesserte Testbarkeit, Stabilität und Änderbarkeit der Software
bzw. deren Teile (Module).
Nachteile der Kapselung
•
•
In Abhängigkeit vom Anwendungsfall Geschwindigkeitseinbußen durch den
Aufruf von Zugriffsfunktionen (direkter Zugriff auf die Datenelemente wäre
schneller).
Zusätzlicher Programmieraufwand für die Erstellung von Zugriffsfunktionen.
Polymorphie
Polymorphie (griechisch, „Vielgestaltigkeit“) ist ein Konzept von
Programmiersprachen. Es beschreibt die Fähigkeit eines Bezeichners – der einen
Festwert (Literal) oder eine Variable repräsentieren kann – sich abhängig von seiner
Verwendung unterschiedlich darzustellen. Sie erlaubt dem Bezeichner, je nach
Kontext einen unterschiedlichen Datentypen anzunehmen.
Das Gegenteil der Polymorphie ist die Monomorphie. Die Variable oder das Literal
sind dann während der gesamten Laufzeit von genau einem Typ.
1.
2.
3.
4.
5.
Polymorphie überladener Operatoren
Polymorphie der Objektorientierten Programmierung
Polymorphie einer Funktion bzw. Prozedur
Polymorphie von Datentypen oder Klassen
Polymorphie bei der Softwareentwicklung
Polymorphie überladener Operatoren
Ein Bezeichner, der für einen Operator steht (bspw. „+“, „-“ oder „minus“), kann
mehrmals mit anderer Bedeutung implementiert werden. Für jeden Kontext, in dem
der Operator neu deklariert wurde, muss die Implementierung immer eindeutig sein.
Polymorphie der Objektorientierten Programmierung
Die Polymorphie der Objektorientierten Programmierung ist eine Eigenschaft, die
immer im Zusammenhang mit Vererbung und Schnittstellen (Interfaces) auftritt. Eine
Methode ist polymorph, wenn sie in verschiedenen Klassen die gleiche Signatur hat,
jedoch erneut implementiert ist.
Gibt es in einem Vererbungszweig einer Klassenhierarchie mehrere Methoden auf
unterschiedlicher Hierarchieebene, jedoch mit gleicher Signatur, wird erst zur
Laufzeit bestimmt, welche der Methoden für ein gegebenes Objekt verwendet wird.
Bei einer mehrstufigen Vererbung wird jene Methode verwendet, die direkt in der
Objektklasse (d. h. jene Klasse, von der das Objekt ein Exemplar ist) definiert ist,
oder jene, die im Vererbungszweig am weitesten "unten" liegt (d. h. die Methode, die
von der Vererbung her am nächsten ist).
Moderne Konzepte kennen jedoch auch Polymorphie über Klassengrenzen hinaus.
So erlaubt Objective-C die Polymorphie zwischen zwei gleichnamigen Methoden, die
in verschiedenen Klassen erstmalig definiert sind.
// NSObject kennt nicht -doSomething
@interface KlasseA : NSObject {
…
}
- (void) doSomething;
@end
@interface KlasseB : NSObject {
…
}
- (void) doSomething;
@end
// irgendwo
id object = … // Ein Objekt einer beliebigen Klasse
[object doSomething]; // polymorph zwischen KlasseA und KlasseB
Selbstverständlich gilt die Subklasse-vor-Basisklasse-Regel auch hier: Wenn die
KlasseB zur KlasseC abgeleitet wird, würde die entsprechende Methode der KlasseC
ausgeführt.
Polymorphie einer Funktion bzw. Prozedur
Ist der Rückgabewert oder ein Argument einer Funktion polymorph, heißt die
Funktion polymorphe Funktion. Mit Hilfe polymorpher Funktionen kann die
Generizität von Datenstrukturen auch in Algorithmen angewandt werden.
Polymorphie von Datentypen oder Klassen
Wird für eigene Datentypen bzw. Klassen bei der Instanzierung bzw. beim
Konstruktoraufruf ein Parameter für den tatsächlich verwendeten Datentyp
übergeben, spricht man von parametrischer Polymorphie, das semantisch mit
Generizität übereinstimmt.
Polymorphie bei der Softwareentwicklung
Es werden bei der Softwareentwicklung Datentypen von Modulen für das zu
entwickelnde System lange nicht spezifiziert, um sich diverse Optionen und
Entwicklungsentscheidungen offen zu lassen.
Verfasser: Kaindl
Abstract
Nicht immer soll eine Klasse sofort ausprogrammiert werden. Dies ist der Fall, wenn die
Oberklasse lediglich Methoden für die Unterklassen vorgeben möchte, aber nicht weiß, wie
sie diese implementieren soll. In Java gibt es dazu zwei Konzepte: abstrakte Klassen und
Schnittstellen (engl. interfaces).
Abstrakte Klassen
•
•
•
•
•
Definieren Typen (nicht instanziierbare Klassen)
Können Methoden- und Attributdefinitionen enthalten
Methoden können abstrakt definiert werden
(Methoden ohne Rumpf)
Klassen können von abstrakten Klassen erben (Einfachvererbung)
Mithilfe von abstrakten Klassen definiert man Basisfunktionalitäten, die von ableitenden
Klassen überschrieben werden können oder sollen.
Abstrakte Klassen können nie direkt instanziert werden. Derartige Klassen dienen als
Bauplan für abgeleitete Klassen und definieren in aller Regel die von diesen zu
implementierenden Funktionalitäten. Sie können jedoch selbst schon bestimmte
Basisfunktionalitäten enthalten.
Abstrakte Klassen dürfen zudem noch abstrakte Methoden anbieten.
Der Einsatzbereich von abstrakten Klassen ist klar umrissen: Diese Elemente definieren
Funktionalitäten und Verhaltensweisen, deren genaue Implementation den ableitenden
Klassen überlassen bleibt oder deren Grundfunktionalität von ableitenden Klassen verwendet
werden kann. Abstrakte Klassen stellen also meist auch Implementationen bereit, auf die
später zurückgegriffen werden kann.
Regeln:
•
Jede Klasse, die eine abstrakte Methode enthält, muss selbst abstract deklariert werden.
•
Wenn eine Subklasse einer abstrakten Klasse nicht alle abstrakten Methoden, die sie erbt,
überschreibt, ist sie ebenfalls abstrakt und muss abstract spezifiziert werden.
•
Wenn eine Subklase einer abstrakten Klasse hingegen alle abstrakten Methoden ihrer
Superklasse überschreibt, also eine Implementation für alle Methoden liefert, kann sie nicht
abstract deklariert werden, und es können keine Objekte erzeugt werden.
•
Konstruktoren wie static, final oder private deklarierte Methoden können nicht abstract sein.
Klassen die nicht abstrakt sind werden konkrete Klassen genannt.
Eine Klasse als abstrakt zu definieren dient mehreren Zwecken:
•
Von abstrakten Klassen können keine Instanzen erzeugt werden. Der Versuch das
Schlüsselwort new mit einer abstrakten Klasse zu benutzen, führt zu einem Fehler. Die Klasse
soll ausschließlich als Superklasse dienen.
•
Nur abstrakte Klassen dürfen abstrakte Methoden definieren. Dies stellt sicher, dass alle
Methoden von konkreten Klassen immer ausgeführt werden. Denn wenn man abstrakte
Methoden in konkreten Klassen zulassen würde, dann könnte man Instanzen einer Klasse
erzeugen, der eine Implementierung für eine Methode fehlt.
•
Abstrakte Klassen mit abstrakten Methoden erzwingen, dass Subklassen die abstrakt
deklarierten Methoden überschreiben und implementieren. Wenn eine Subklasse keine
Implementierung für eine geerbte abstrakte Methode anbietet, dann ist sie selbst abstrakt und
es können keine Instanzen von ihr erzeugt werden. Damit eine Subklasse konkret sein kann,
muss sie Implementierungen für alle abstrakten Methoden anbieten.
Deklaration einer abstrakten Klasse
Ein abstraktes Element wird immer durch das Schlüsselwort abstract definiert.
public abstract class SomeClass { … }
abstract void doSOmething() { … }
Implementieren der abstrakten Klasse
Da eine abstrakte Klasse nicht direkt instanziert werden kann, wird sie mehr in Form eines
Platzhalters für ihre Ableitungen zum Einsatz kommen.
Die Ableitung von der Basisklasse geschieht mithilfe des Schlüsselwortes extends.
public class AbstractClassImpl extends AbstractClass { }
Abstrakte Methoden
Der Modifizierer abstract vor dem Schlüsselwort class leitet die Deklaration einer abstrakten
Klasse ein. Eine Klasse kann ebenso abstrakt sein wie eine Methode. Eine abstrakte Methode
gibt lediglich die Signatur vor, und eine Unterklasse implementiert irgendwann diese
Methode. Die Klasse ist somit für den Kopf der Methode zuständig, während die
Implementierung an anderer Stelle erfolgt. Abstrakte Methoden drücken aus, dass die
Oberklasse keine Ahnung von der Implementierung hat und dass sich die Unterklassen darum
kümmern müssen.
Abstrakte Methoden funktionieren nach dem gleichen Prinzip wie abstrakte Klassen:
Ableitende Klassen sind gezwungen, die entsprechende Methode zu implementieren.
Deklaration einer abstrakten Methode
Eine abstrakte Methode besteht aus einer Methodensignatur ohne einen Rumpf. Stattdessen
wird der Kopf der Methode durch ein Semikolon abgeschlossen.
Sie wird mit dem Schlüsselwort abstract definiert.
abstract class KlassenName {
abstract void aAbstractMethod(int b);
}
Abstrakte Methoden sind nur innerhalb von Klassen erlaubt, die ebenfalls als abstract
deklariert sind. Definiert man eine Methode als abstract, muss man folglich auch die Klasse
als abstract deklarieren.
Vererben von abstrakten Methoden
Wenn wir von einer Klasse abstrakte Methoden erben, so haben wir zwei Möglichkeiten:
1. Wir überschreiben alle abstrakten Methoden und implementieren sie. Dann muss die
Unterklasse nicht mehr abstrakt sein (wobei sie es auch weiterhin sein kann). Von der
Unterklasse kann es ganz normale Exemplare geben.
2. Wir überschreiben die abstrakte Methode nicht, sodass sie normal vererbt wird. Das
bedeutet: Eine abstrakte Methode bleibt in unserer Klasse, und die Klasse muss wiederum
abstrakt sein.
Abstrakte Klasse – ein Beispiel
Es sollen Verschiedene Formen implementiert werden, die das gleiche Interface verwenden.
Das Interface und gemeinsameTeile werden in Shape definiert/implementiert.
public abstract class Shape {
protected Point anchor;
Shape() { this.anchor =new Point(); }
public Point position() { return anchor; }
//abstract interface
abstract public double area();
abstract public double perimeter();
}
public class Rectangle extends Shape {
protected Point rightLowerCorner; // 2. Ecke
public Rectangle() {
super();
this.rightLowerCorner = new Point();
}
// Implementierung der abstrakten Methoden
public double area() { return (width()*heigth()); }
public double perimeter() {
return (2*(width()+heigth()));}
// Zusätzliche Methoden
private double width() {
return (Math.abs(rightLowerCorner.x-anchor.x));
} ...
Verfasser: Kaindl
Interface
Bei Interfaces, oft auch direkt als Schnittstellen bezeichnet, handelt es sich um eine Abart
der abstrakten Klassendeklaration. Sie enthält neben diversen Datenelementen lediglich
abstrakte Methoden. Auch werden sie für die Mehrfachvererbung eingesetzt, denn Klassen
können beliebig viele dieser Schnittstellen implementieren.
Verwendung
Interfaces kommen dort zum Einsatz, wo die Klassen an ihre Grenzen stoßen. Genau wie
abstrakte Klassen, so können auch Interfaces Variablen und Methoden vordeklarieren. Des
Weiteren können Klassen beliebig viele Interfaces implementieren. Klassen sind in so fern
eingeschränkt, als dass sie nur eine Basisklasse in einer Vererbungshierarchie haben dürfen.
Um dieses Problem nun zu umgehen, kann man in Schnittstellendefinitionen die zu
implementierenden Methoden und Datenelemente bestimmen.
Implementiert eine Klasse ein Interface, so muss sie alle Methoden des Interface
überschreiben, da diese implizit als abstrakt deklariert werden.
Definition eines Interface
Ein Interface in Java ist die Spezifikation eines Typs (in Form eines Typnamens und einer
Menge von Methoden), die keine Implementierungen für die Methoden definiert.
Interface sind abstrakte Klassen sehr ähnlich, jedoch dürfen die Methoden keine Rümpfe
definieren. Sie sind deshalb abstrakte Klassen, in denen alle Methoden abstrakt sind, sehr
ähnlich.
Interfaces liefern einen Mechanismus, der es erlaubt, in hierarchisch nicht verwandten
Klassen denselben Satz von Methoden zu implementieren und diese von außen anzusprechen.
Grundsätzlich ist ein Interface eine Sammlung von Methodendefinitionen und konstanten
Werten, die frei von Abhängigkeiten einer speziellen Klasse oder Klassenhierarchie sind.
Wozu braucht man Interfaces?
Interfaces sind nützlich um
•
Methoden zu deklarieren, die eine oder
mehrere Klassen implementieren werden,
•
Aufschluss über das
Programmierungsinterface eines Objektes zu geben, ohne Aufschluss über seine Klasse zu
liefern.
•
Analogien zwischen nicht verwandten
Klassen aufzufangen, ohne dabei eine Klassenverwandtschaft zu erzwingen.
Interfaces in Java haben eine Reihe von festen Eigenschaften:
•
Im Kopf wird statt des Schlüsselworts class das Schlüsselwort interface verwendet.
•
Alle Methoden in einem Interface sind abstrakt; es sind keine Methodenrümpfe zugelassen.
Das Schlüsselwort abstract wird deshalb nicht benötigt.
•
Interfaces enthalten keine Konstruktoren.
•
Alle Methodensignaturen sind öffentlich sichtbar. Die Sichtbarkeit muss deshalb nicht
deklariert werden (das Schlüsselwort public wird nicht benötigt).
•
In einem Interface können nur Datenfelder definiert werden, die konstant sind (public, static
und final).Die Schlüsselwörter public, static und final können weggelassen werden, aber alle
Datenfelder haben dennoch diese Eigenschaften.
Interfacedeklaration
Ein Interface wird statt class mit dem Schlüsselwort interface deklariert.
Syntax :
[Modifikator] interface Interface
{
[final] [Modifikator] Typ Variable = Wert;
[abstract] [Modifikator] Typ Methode();
}
Das folge Beispiel definiert eine entsprechende Variable und eine abstrakte Methode.
interface MyInterface
{
public int i = 0;
public void print();
}
Der Geltungsbereich eines Interfacenamens ist, wie bei Klassennamen, das gesamte Paket, in
dem das Interface deklariert ist. Das bedeutet, dass Interfaces und Klassen im selben Paket
verschiedene Namen haben müssen.
Interfaceelemente
Neben den Elementen (Methoden und Variablen), die in seinem Rumpf deklariert sind, hat ein
Interface alle aus direkten Superinterfaces geerbten Methoden und variablen als Elemente.
Ausgenommen hiervon sind verdeckte Variablen und überschriebene Methoden, die nicht
vererbt werden.
Interfacemethoden
Alle in einem Interface deklarierten Methoden sind implizit abstract, d.h. bei ihrer Deklaration
werden lediglich Ergebnistyp und Signatur der Methode festgelegt, und der Methodenrumpf
entfällt. Darüber hinaus sind Interfacemethoden implizit public, und die Angabe dieses
Modifizierers erübrigt sich somit.
Eine Interfacemethode darf nicht static spezifiziert werden ( static Methoden können nicht
abstract sein). Sie darf auch nicht final spezifiziert werden.
Interfacevariablen
Neben abstrakten Methoden können im Rumpf einer Interfacedeklaration auch Variablen
deklariert werden. Diese sind public, static und final, es handelt sich also um
klassenspezifische symbolische Konstanten. Die Verwendung des Modifizierers ist zulässig,
aber überflüssig. Für alle Variablen muss man bei ihrer Deklaration einen Initialisierer
angeben.
Die Implementierung des Interface
Bei der Deklaration der Klasse kann festgelegt werden, dass die Klasse ein oder mehrere
Interfaces implementiert. Hierzu gibt man die Namen dieser Interfaces durch Komma
getrennt vor dem Klassenrumpf an und leitet dies Liste mit dem Schlüsselwort implements
ein.
Syntax:
[Modifikator] class Klasse
[extends Superklasse]
implements Interface
{
// Anweisungen
}
Im Unterschied zu Superklassen sind jetzt mehrere direkte Superinterfaces zulässig. Die
deklarierte Klasse erbt alle Methoden und Konstanten aus den direkten Superinterfaces, die
sie implementiert.
Für alle in den implementierten Superinterfaces enthaltenen Methoden muss die Klasse eine
Implementierung liefern, es sei denn, sie ist abstrakte Klasse.
Im folgenden Beispiel definieren wir eine Klasse, welche ein Interface implementiert.
Beachten Sie, dass die Klasse alle Methoden des Interfaces überschreiben muss. Die Signatur
muss dabei exakt mit der Basismethode übereinstimmen.
interface MyInterface
{
public String s = "Programmers";
public void print();
}
class MyClass implements MyInterface
{
public void print()
{
System.out.print(s);
}
}
public class MyTest
{
public static void main(String[] args)
{
MyClass object = new MyClass();
object.print();
}
}
Sub- und Superinterfaces
Interfaces können in Sub-/Supertyp-Beziehung stehen, wobei das Schlüsselwort extends
benutzt wird und es ist zulässig mehrere Superinterfaces zu deklarieren.
Interfaces als Typen
Ein Interface definiert genau wie eine Klasse einen Typ. Somit können Variablen von einem
Interface-Typ deklariert werden, obwohl keine Objekte dieses Typs existieren können (nur
Objekte der Subtypen).
Von Interfaces gibt es keien direkten Instanzen, aber sie dienen als Supertypen für andere
Klassen
Vererbung von Interfaces
Die Verwendung von Interfaces in einer Klassenhierarchie hat einen nützlichen Nebeneffekt.
Wenn eine beliebige Superklasse ein Interface implementiert und somit alle Datenelemente
und Methoden erbt, so wird diese Implementierung auch an alle Subklassen dieser
Vaterklasse weitergegeben. Ein einmal implementiertes Interface kommt also auch allen
Subklassen zu Gute.
Das folgende Beispiel implementiert bereits die Superklasse das Interface. Die Subklasse erbt
alle Elemente und bei der Instanziierung dieser ist es uns gestattet, die entsprechend vererbte
Methode aufzurufen.
interface MyInterface
{
public void print();
}
class MySuperClass
implements MyInterface
{
protected String s = "ProgrammersBase ";
public void print()
{
System.out.println(s);
}
}
class MySubClass
extends MySuperClass
{
}
public class MyClass
{
public static void main(String[] args)
{
MySubClass object = new MySubClass();
object.print();
}
}
Ableiten von Interfaces
Auch Interfaces können eine Vererbungshierarchie bilden und voneinander abgeleitet werden.
Dabei erbt die Subschnittstelle alle Deklarationen der Basisschnittstelle. Implementiert nun
eine Klasse diese Subschnittstelle, so muss sie natürlich auch alle Methoden der
Basisschnittstelle überschreiben.
Die Syntax für eine Schnittstellenableitung ist mit der Ableitung von Klassen nahezu
identisch.
Syntax
[Modifikator] interface Interface
extends SuperInterface
{
// Anweisungen
}
Anstatt einer Klasse zwei verschiedene Interfaces implementieren zu lassen, kann man diese
auch in einer logischen Hierarchie ableiten und in einem Subinterface zusammenfassen.
Beispiel:
interface MyInterface1
{
public void print1();
}
interface MyInterface2 extends MyInterface1
{
public void print2();
}
class MySuperClass
{
protected String s = "ProgrammersBase.NET";
}
class MySubClass extends MySuperClass
implements MyInterface2
{
public void print1()
{
System.out.println(s);
}
public void print2()
{
System.out.println("ProgrammersBase.DE");
}
}
public class MyClass
{
public static void main(String[] args)
{
MySubClass object = new MySubClass();
object.print1();
object.print2();
}
}
Vorteil: Interfaces können kaskadierend gebaut und kleinteilig gehalten werden, womit sich
auch der Schreibaufwand verringert.
statische Datenelemente
Alle Datenelemente eines Interface sind von vornherein öffentlich und konstant. Daher
können die entsprechenden Modifikatoren als optional betrachtet werden. Ansonsten können
die Elemente noch statisch deklariert sein, um in den Klassen, in denen die Schnittstelle
implementiert wird, einen globalen Gültigkeitsbereich zu erlangen.
Syntax:
[public] [final] static Typ Variable = Wert;
Mehrdeutigkeiten
Dadurch; dass eine Klasse mehrere Interfaces implementieren kann bzw. ein Interface
mehrere Superinterfaces haben kann, ist es möglich, dass verschiedene Elemente gleichen
Namens vererbt werden.
•
Wenn es sich dabei um Variablen handelt, muss der Zugriff in der Subklasse bzw. im
Superinterface qualifiziert erfolgen, ansonsten ist die Deklaration fehlerhaft.
Für Methoden liegt der Fall noch einfacher:
•
Bei gleicher Signatur überschreibt die Methode in der Subklasse oder im Subinterface die
Methode in der Superklasse bzw. im Superinterface.
•
Im Fall verschiedener Signaturen wird überladen und gegebenenfalls mit anderen
Deklarationen zusätzlich überschrieben.
•
Methoden und variablen werden aufgrund der unterschiedlichen Syntax beim Aufruf bzw.
zugriff unterschieden; hier kann es nicht zu Problemen kommen.
Verfasser: Vieghofer
GUI
GUI (engl. „Graphical User Interface“) – Grafische Benutzeroberflächen
Eine Grafische Benutzeroberfläche ist eine Software-Komponente mit welcher der
Anwender über diverse grafische Elemente mit der Maschine interagieren kann. Die
Java-Bibliothek enthält eine große Auswahl vorgefertigter Komponenten, wie zum
Beispiel Knöpfe, Menüs, Menüeinträge, Combo-Boxen, Schieberegler und viele
mehr. Das sogenannte Layout regelt wie die Komponenten am Bildschirm arrangiert
werden. Ältere GUI-Systeme haben dies mit zweidimensionalen Koordinaten
definiert. Heutzutage müssen verschiedene Bildschirmauflösungen und Zeichensätze
berücksichtigt werden und dem Benutzer wird überlassen in welcher Größe ein
Fenster dargestellt wird. Dies wir mithilfe von Layout-Managern realisiert. Java
verfügt über zwei GUI-Bibliotheken. Die ältere heißt AWT (Abstract Window Toolkit).
Später wurde eine stark verbesserte GUI-Bibliothek namens Swing hinzugefügt.
Swing benutzt einige der Klassen aus dem AWT, ersetzt einige AWT-Klassen mit
einer eigenen Version und fügt sehr viele neue Klassen hinzu. Immer dann, wenn
äquivalente Klassen in AWT und Swing existieren, sind die Swing-Versionen durch
ein „J“ am Anfang des Klassennamen gekennzeichnet (Button & JButton etc.). Fast
alles, was man in einer grafischen Oberfläche zu sehen bekommt befindet sich in
einem Fenster, einem sogenannten top-level window. Ein solches Fenster heißt top
level, weil es sich unmittelbar unter der Kontrolle der Fensterverwaltung des
jeweiligen Betriebssystems befindet und typischerweise unabhängig von anderen
Fenstern bewegt, verändern, minimieren und maximieren lässt. In Java heißen diese
top-level Fenster Frames und werden in Swing durch die Klasse JFrame
repräsentiert.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class Bildbetrachter
{
private JFrame fenster;
public Bildbetrachter()
{
fensterErzeugen();
}
// ---- Swing-Anteil zum Erzeugen des Fensters mit allen Komponenten ---/**
* Erzeuge das Swing-Fenster samt Inhalt.
*/
private void fensterErzeugen()
{
fenster = new JFrame("Bildbetrachter");
Container contentPane = fenster.getContentPane();
JLabel label = new JLabel("Ich bin ein Label. Ich kann Text anzeigen.");
contentPane.add(label);
fenster.pack();
fenster.setVisible(true);
}
}
Die Zeile 22 erzeugt einen Frame und speichert ihn in einer Instanzvariablen für die
weitere Verwendung. Ein Frame besteht aus drei Teilen: der Titelzeile, einer
optionalen Menüzeile (engl. menu bar) und der Inhaltsfläche (engl. content pane).
Das exakte Erscheinungsbild ist Betriebssystemabhängig. Unmittelbar nach
Erzeugung des JFrame ist das Fenster unsichtbar und die Inhaltsfläche ist leer.
In Zeile 23 lassen wir uns die Inhaltsfläche des Fensters geben. Dies müssen wir
immer tun – GUI-Komponenten werden in einen Frame eingefügt, indem sie in die
Inhaltsfläche eingefügt werden. Die Inhaltsfläche selbst ist vom Typ Container. Ein
solcher Container ist eine Swing-Komponente die eine beliebige Gruppe weiterer
GUI-Komponenten enthalten kann. Wir erzeugen dann ein Objekt der Klasse JLabel,
welches einen Text anzeigen kann. Dieses Objekt wird nun der Inhaltsfläche
hinzugefügt.
Zeile 26 sorgt dafür, dass das Fenster die eingefügten Komponenten ordentlich
arrangiert und sich selbst entsprechen in der Größe anpasst. In der nächsten Zeile
wird das Fenster dann noch sichtbar gemacht. Es macht Sinn erst einmal jede
einzelne Komponente zu erzeugen und auszurichten bevor das gesamte Fenster
sichtbar gemacht wird.
Um Menüs hinzuzufügen stehen die Klassen JMenuBar, JMenu, und JMenuItem zur
Verfügung. Die Klasse JFrame bietet eine Methode setJMenuBar mit welcher eine
Menüzeile übergeben werden kann. Zum erzeugen einer Menüzeile sowie eines
Menüs und eines eintrages („öffnen“) geht man wie folgt vor.
JMenuBar menuezeile = new JMenuBar();
fenster.setJMenuBar(menuezeile);
JMenu dateimenue = new JMenu(„Datei“);
menuezeile.add(dateimenue);
JMenuItem oeffnenEintrag = new JMenuItem(„Öffnen“);
dateiMenue.add(oeffnenEintrag);
Swing benutzt Layout-Manager zur Gestaltung des Layouts der Komponenten in
einer GUI. Jeder Container mit enthaltenen Komponenten, wie der Content-Pane,
verfügt über einen Layout-Manager, der sich um die Ausrichtung der Komponenten
im Container kümmert. Die wichtigsten Layout-Manager sind FlowLayout,
BorderLayout, GridLayout und BoxLayout. Jedes dieser Layouts wird durch eine
Java-Klasse der Swing-Bibliotheken repräsentiert und arrangiert die Komponenten
unter seiner Kontrolle auf andere Weise.
Ein BoxLayout arrangiert mehrere Komponenten entweder vertikal oder horizontal.
Bei einer Größenänderung wird nicht umgebrochen.
getContentPane().setLayout(
new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
JButton btn1 = new JButton("Button 1");
JButton btn2 = new JButton("Button 2");
JButton btn3 = new JButton("Button 3");
JButton btn4 = new JButton("Button 4");
getContentPane().add(btn1);
getContentPane().add(btn2);
//btn1.setAlignmentX(Component.CENTER_ALIGNMENT);
getContentPane().add(btn3);
getContentPane().add(btn4);
Ein GridLayout ist nützlich um Komponenten in einem gleichmäßigen Gitter zu
arrangieren. Die Anzahl der Zeilen und Spalten kann über den Konstruktur
GridLayout(int rows, int cols) angegeben werden, der GridLayout-Manager ordnet
alle Komponenten in derselben Größe an. Dies kann nützlich sein, um beispielweise
eine Reihe von Knöpfen in die gleiche Größe zu zweingen.
Frame fr = new Frame("GridLayout");
fr.setLayout(new GridLayout(3, 2));
Button oneB = new Button("One");
fr.add(oneB);
Button twoB = new Button("Two");
fr.add(twoB);
Ein FlowLayout arrangiert alle Komponenten sequentiell von links nach rechts. Es
lässt allen Komponenten ihre bevorzugte Größe und zentriert sie an der vertikalen
Achse. Wenn der Platz vertikal nicht ausreicht werden die Komponenten auf eine
weitere Zeile umgebrochen. Die Ausrichtung kann über die Methode setAlignment(int
align) geändert werden.
Frame fr = new Frame("FlowLayout");
fr.setLayout(new FlowLayout();
Button erster = new Button("Erster");
Button zweiter = new Button("Zweiter");
Button dritter = new Button("Der Dritte ist Laaaaaaaaannngg");
Button vierter = new Button("Vierter");
Button fünfter = new Button("Fünfter");
Ein BorderLayout
platziert
bis zu fünf Komponenten in einem festgelegten Muster:
Button sechster
= new Button("Sechster");
Button
siebenter
=
new
Button("und
so unten,
weiter");
eine in der Mitte und jeweils eine oben,
rechts, links. Jede dieser Positionen
darf auch
leer sein, so dass auch weniger als fünf Komponenten zulässig sind. Die
fr.add(erster);
fr.add(zweiter);
fünf Positionen sind mit den Konstanten CENTER, NORTH, SOUTH, EAST und
fr.add(……
WEST der Klasse BorderLayout benannt. Komponenten werden mit der Methode
Container.add(Component comp, Object constraints) hinzugefügt. Als constraints
stehen die oben erwähnten BorderLayout.NORTH, BorderLayout.SOUTH usw. zur
Frame fr = new Frame("BorderLayout");
Verfügung.
Button northB = new Button("North");
fr.add(northB, BorderLayout.NORTH);
Button southB = new Button("South");
fr.add(southB, BorderLayout.SOUTH);
Button eastB = new Button("East");
fr.add(eastB, BorderLayout.EAST);
Button westB = new Button("West");
fr.add(westB, BorderLayout.WEST);
Button centerB = new Button("Center");
fr.add(centerB, BorderLayout.CENTER);
fr.pack();
Alle oben beschriebenen Layout-Strategien sind relativ
einfach. Der Schlüssel zum
fr.setVisible(true);
Bau gut aussehender und gut benutzbarer Oberflächen liegt in einem letzten Detail:
Layouts können ineinander verschachtelt werden. Einige Swing-Komponenten sind
Container, Container erscheinen nach außen wie einzelne Komponenten, können
aber viel andere Komponenten enthalten. Jeder Container verfügt über einen
LayoutManager. Der am häufigsten verwendete Container ist die Klasse JPanel,
welche als Komponente in den Content-Pane eines Frames eingefügt werden, und in
dem JPanel können dann mehrere Komponenten ausgerichtet werden. Als kleines
Beispiel wäre das nachstehende Bild anzusehen. Der Content-Pane des Frame
benutzt ein BorderLayout, bei dem die EAST-Position unbenutzt ist. Der NORTHBereich enthält ein JPanel mit einem horizontalen Flow-Layout, das seine
Komponenten in einer Rehe arrangiert. Die SOUTH-Komponente ist ähnlich: ein
weitere JPanel mit einem FlowLayout. Der WEST-Bereich wurde zuerst in einen
JPanel mit einem einspaltigen GridLayout eingefügt, um für alle Knöpfe die gleich
Breite zu erreichen. Dieser JPanel wurde dann in einen weiteren JPanel mit einem
vertikalen FlowLayout eingefügt, damit das Gitter sich nicht über die volle Höhe des
WEST-Bereichs erstreckt. Dieser äußere JPanel schließlich wurde in den WESTBereich des Frames eingefügt.
Verfasser: Horvath
Event Handling
Allgemein
Ein Ereignis (engl. event) dient in der Softwaretechnik zur Steuerung des
Programmflusses. Das Programm wird nicht linear durchlaufen, sondern es werden
spezielle Ereignisbehandlungsroutinen (engl. listener, observer, event handler)
immer dann ausgeführt, wenn ein bestimmtes Ereignis auftritt (vergleiche
Rückruffunktion). Ein verwandtes Konzept sind Interrupts.
Ereignisse eignen sich besonders gut zur Implementierung von grafischen
Benutzeroberflächen, wobei hier die Ereignisse meist Aktionen des Benutzers sind,
wie zum Beispiel das Drücken einer Taste oder das Anklicken einer Schaltfläche. Ein
anderes wichtiges Anwendungsfeld sind Computersimulationen, die so aufgebaut
werden, dass Zustandsänderungen nur von Ereignissen ausgelöst werden, und
ihrerseits Ereignisse auslösen (siehe ereignisorientierte Simulation).
Ereignisorientierte Programmierung lässt sich gut mit den Konzepten der
objektorientierten Programmierung kombinieren: Objekte definieren dann nicht mehr
nur Eigenschaften und Methoden, sondern sind auch Ereignisquellen und bieten die
Möglichkeit, die Ereignisbehandlung zu beeinflussen. Auch die
Ereignisbehandlungsroutinen (engl. event handler, eingedeutscht der Event-Handler,
etwa „Ereignisverarbeiter“ oder „Ereignisbehandler“) und die Ereignisse selbst
werden dann als Objekte modelliert.
Ereignisse können je nach Programmierumgebung entweder nur eine
Ereignisbehandlungsroutine (wie z. B. in Borland Delphi) oder beliebig viele
Ereignisbehandlungsroutinen (wie beim Signal-Slot-Konzept) aufrufen.
JAVA spezifisch
•
•
•
Die Interaktion mit einem GUI geschieht über Events.
Ein Event ist eine Aktion, welche durch einen Benutzer initialisiert wurde, sei es durch
Drücken eines Buttons, Verschieben der Maus, Drücken einer Taste ...
Damit Java auf die Aktion eines Benutzers reagiert, muss zuerst eine EventListenerregistriert und ein zugehöriger Event-Handlerimplementiert werden.
Eventhandling in JAVA
Für jedes Event wird ein Event Listener erzeugt, welcher die entsprechende Methode
(Event Handler, Callback-Funktion) implementiert.
EventListener
• ActionListener (zB.: drücken eines Buttons)
• FocusListener
• KeyListener (drücken einer bestimmten Tastenkombination)
• MouseListener (drücken der linken oder rechten Maustaste)
• MouseWheelListener (Mausrad drehen)
• WindowListener
Einige Komponenten und ihre Events
Beispiele von EventListenerInterfaces
Public interface ActionListener extends EventListener
{
public void action Performed(ActionEvent e);
}
public interfaceKeyListener extends EventListener
{
public voidkeyPressed( KeyEvent e );
public voidkeyReleased( KeyEvent e );
public voidkeyTyped( KeyEvent e );
}
publicinterfaceMouseListenerextendsEventListener
{
public voidmouseClicked(MouseEvente);
...
}
Events auf einem Button
Import java.awt.event.*; import java.awt.*; import
javax.swing.*;
public class ButtonEventextendsJFrame{
JPanelpanel;
ButtonEvent() {
panel= newJPanel(); add(panel);
JButton b = newJButton("ChangeColor");
b.addActionListener(newbAction());
panel.add(b);
}
class bAction implements ActionListener{
public void actionPerformed(ActionEvent event) {
if(panel.getBackground() == Color.red)
panel.setBackground(Color.blue);
else
panel.setBackground(Color.red);
}
}
// main...
}
Events in einem TextField
Import javax.swing.*; import java.awt.*;import
java.awt.event.*;
Public class TextEvent1 extends
JFrameimplementsActionListener{JLabeloutput;TextEvent1() {
JTextField text = new JTextField( 20 );output=
newJLabel();text.addActionListener( this);add(text,
BorderLayout.NORTH);
add(output, BorderLayout.SOUTH);}
public void actionPerformed(ActionEvente)
{output.setText("Eingabe: " +e.getActionCommand()
);}... }
Mouse Events
Es gibt drei Sorten von MouseListeners:
• MouseListener
o publicvoidmouseClicked( MouseEvente );
o publicvoidmouseEntered( MouseEvente );
o publicvoidmouseExited( MouseEvente );
o publicvoidmousePressed( MouseEvente );
o publicvoidmouseReleased( MouseEvente );
• MouseMotionListener
o publicvoidmouseDragged( MouseEvente );
o publicvoidmouseMoved( MouseEvente );
• MouseWheelListener
o publicvoidmouseWheelMoved( MouseWheelEvente );
Verwenden von Mouse Events
importjavax.swing.*; importjava.awt.*;
importjava.awt.event.*;
public class MEventextendsJFrame{
private intxVal= 0, yVal= 0;
MEvent(){
addMouseMotionListener( newMouseMotionAdapter()
{
public void mouseDragged( MouseEvente )
{
xVal= e.getX(); yVal= e.getY();
repaint();
}
});
}
public void paint( Graphics g ) {
g.fillOval( xVal, yVal, 3, 3 ); }
public static void main(Stringargs[])
{}}
Menu-Events
// createmenubar
bar = newJMenuBar();
// createthefilemenu
fileMenu= newJMenu( "File" );
quit= newJMenuItem( "Quit" );
fileMenu.add( quit);
quit.addActionListener(new ActionListener()
{
publicvoidactionPerformed( ActionEvent e )
{ System.exit( 0 ); }
} );
// createfontmenu
fontMenu= newJMenu( "Font" );
// addmenuto menubar
bar.add( fileMenu);
bar.add( fontMenu);
// setthemenubarfortheframe
setJMenuBar( bar );
Den EventListener implementieren kann
•
•
•
die (Nutzer-) Klasse selbst
o welche das Interface implementiert
o oder von Adapter ableitet
eine separate Klasse
o welche das Interface implementiert
o oder von Adapter ableitet
eine innere Klasse
o welche das Interface implementiert
o oder von Adapter ableitet
o eventuell anonym
Verfasser: Mailer
ODBC
Einleitung
Datenbanken gewinnen gerade in heterogenen Umgebungen zunehmend an
Bedeutung. Heterogen soll bedeuten, dass es nicht nur ausschließlich für ein System
zugeschnitten ist sondern für mehrere. Nachdem man sich von Mainframes immer
mehr trennt, sind Datenbanken oft die einzige Möglichkeit, allen Benutzern eine
gemeinsame Datenbasis zur Verfügung zu stellen. Meist greifen die Anwender gar
nicht direkt auf die Datenbank zu, sondern sie benutzen Software, die Daten auf
zentralen Datenbanken speichert. Über Datenbanken können z.B. Versender ihr
Warenangebot und ihren Warenbestand auch vom Kunden abfragen lassen.
Ein großes Problem beim Einsatz von Datenbanken ist deren Abfrage, da die
Hersteller dafür proprietäre Schnittstellen benutzen. Die Datenbanken selbst laufen
auf einem oder mehreren Servern. Die abfragenden Clients kommunizieren mit dem
Server über ein proprietäres Protokoll. Wird die Datenbank gewechselt oder möchten
sich die Clients mit einer Datenbank eines anderen Herstellers verbinden, müsste die
Abfragesoftware geändert oder sogar neu geschrieben werden, was natürlich nicht
sinnvoll ist.
Um die Abfrage und Verwaltung der Datenbanken zu vereinheitlichen, wurde SQL
(Structured Query Language) entwickelt. Hierbei handelt es sich um eine reine
Abfragesprache ohne Kontrollkonstrukte wie Schleifen oder Verzweigungen. Deshalb
können Programme nicht direkt in SQL programmiert werden. Programmiert wird mit
Embedded-SQL, auch ESQL genannt.
ODBC und JDBC sind APIs von Microsoft bzw. Javasoft, die Schnittstellen zur
Verfügung stellen, um von höheren Programmiersprachen aus Datenbanken mittels
SQL zu verwalten und abzufragen. Weiterhin bieten ODBC/JDBC die Möglichkeit,
auch auf Datenbanken und andere Office-Produkte zuzugreifen, die keine SQLUnterstützung haben. Der SQL-Befehlssatz wird in diesem Fall über einen Treiber
implementiert. Auch das gleichzeitige Abfragen mehrerer Datenbanken aus einem
Programm heraus, die dazu noch auf verschiedenen Servern liegen, ist kein
Problem. Es muss gegebenenfalls nur ein weiterer Treiber auf dem Client installiert
werden.
ODBC
ODBC ist ein API von Microsoft für den Zugriff auf Daten in relationalen und nicht
relationalen Datenbank-Management-Systemen (DBMS). Mit Hilfe der ODBC-API
können Applikationen auf Daten zugreifen, die in DBMS auf PCs oder Servern
abgelegt sind, selbst wenn diese DBMSe andere Datenformate und andere
Programmierschnittstellen verwenden.
Die Unterstützung von ODBC ist nicht auf Microsoft beschränkt. Fast alle DBMSHersteller unterstützen ODBC, unter anderem Oracle, Informix, Novell, Borland, und
IBM. ODBC ist eine Plattformübergreifende Lösung.
ODBC setzt zur Kommunikation mit dem Datenbanksystem SQL ein. SQL-Befehle
werden in ODBC-Anweisungen eingepackt. ODBC interpretiert diese SQL-Befehle
nicht. Es findet auch keine Syntaxkontrolle statt. Die SQL-Befehle werden auch nicht
direkt zur Datenbank geschickt, sondern zuerst zu einem ODBC-Treiber. Dieser
Treiber wird vom Hersteller der Datenbank zur Verfügung gestellt. Er nimmt die SQLBefehle entgegen und sendet sie mit einem Herstellereigenen Protokoll zur
Datenbank. Außerdem nimmt er Antworten der Datenbank entgegen und übergibt sie
wieder an das ODBC-Programm.
Treibermodelle
Es gibt grundsätzlich drei Möglichkeiten mit ODBC auf Datenbanken zuzugreifen:
das One-Tier-, Two-Tier- oder Three-Tier-Modell.
Beim One-Tier-Modell wird entweder auf eine lokale Datei oder auf eine lokale
Datenbank zugegriffen, sprich so wie wir es in der Schule am meisten geübt und
praktiziert haben. Beim Zugriff auf eine lokale Datei muss der ODBC-Treiber hier
selbst die Informationen des Dateiaufbaus besitzen. Das One-Tier-Modell wird
hauptsächlich bei der Kommunikation mit Office-Produkten benutzt, wenn z.B. die
Textverarbeitung MS-Word sich Adressdaten von MS-Access für einen Serienbrief
holt. Der Zugriff kann auch über eine ISAM-Engine erfolgen, um Dateien von z.B. der
Tabellenkalkulation MS-Excel abzufragen. ISAM (Index Sequential Access Method)
ist eine Zugriffsfunktion für eine einfache, sequentielle Datei auf der Festplatte, die
einen Index oder mehrere Indizes einführt, um das Suchen und Sortieren von
Spalten mit Daten zu vereinfachen. Üblicherweise unterstützt der One-Tier-Driver
nicht den Mehrbenutzerzugriff, das heißt der Benutzer muss sich um die Konsistenz
der Dateien bei gleichzeitigem Lese- und Schreibzugriff selbst kümmern, sprich der
Benutzer sollte nicht Dateien während einer Abfrage ändern. Da diese Treiber oft
auch nur zur Kommunikation von Officeprogrammen benutzt werden, ist dies aber
auch nicht nötig, da fast ausschließlich lesend zugegriffen wird.
Die Besonderheit beim Zugriff über das One-Tier-Modell ist, dass die lokale
Datenbank oder die lokale Datei nicht über SQL angesprochen werden kann. Übliche
Office-Software besitzt keinen SQL-Interpreter. Diese Funktionalität muß dann vom
ODBC-Treiber nachgebildet werden.
Das Two-Tier-Modell entspricht den klassischen Client/Server-Systemen. Der Treiber
auf der Client-Seite sendet und empfängt das Datenprotokoll der Datenbank, greift
aber nicht direkt auf die Daten zu. Die Datenbank auf der Serverseite empfängt SQLAnfragen vom ODBC-Treiber, führt diese aus und sendet das Ergebnis zurück.
Aus Sicht des Clients unterscheidet sich das Three-Tier-Modell kaum von dem TwoTier-Modell. Hier ist einfach noch ein Server zwischen die Kommunikation von
Datenbank und Client geschaltet. Dieser sorgt für die Verteilung auf verschiedene
Datenbanken oder bietet den Clients eine einheitliche Zugriffsschnittstelle. So muss
beim Einrichten einer neuen Datenbank nicht auf jedem Client ein neuer ODBCTreiber installiert werden, sondern nur auf dem Server in der Mitte. Der Vorteil liegt in
der leichteren Wartung auf Client-Seite, da diese nicht verändert werden müssen.
Zusammenfassend lässt sich sagen, dass ODBC zur Zeit die am häufigsten benutzte
Möglichkeit darstellt, Daten aus einer Datenbank abzufragen. Gerade im PC-Bereich
benutzen viele Programme das One-Tier-Modell. Hier wird auf eine Version von MSAccess zugegriffen, die nur Daten entgegennimmt bzw. ausgibt, sich aber nicht über
eine grafische Benutzeroberfläche einrichten lässt. Diese Version darf von jedem
Entwickler kostenlos weitergegeben werden. Der Entwickler erstellt also eine MSAccess Datenbank und programmiert eine elegante Benutzerschnittstelle.
Eine weitere Anwendung ist natürlich die individuelle, firmeneigene Abfrage von
Datenbanken. Die Kunden sind jetzt nicht mehr an einen bestimmten Hersteller
gebunden. Sie können problemlos auf ihren Servern neue Datenbanksysteme, auch
von anderen Herstellern, installieren. Es muss lediglich der ODBC-Treiber auf den
Clients getauscht werden. Die Applikationen selbst müssen nicht geändert werden.
Auto Beispiel
Ich möchte anhand einer MS-Access Datenbank die Verwendung eines ODBC/JDBC
Treibers aufzeigen.
Dazu habe ich auf meinem Desktop eine MS-Access Datenbank namens „Autos“
erstellt.
In dieser Datenbank habe ich eine Tabelle namens „Autos“ erstellt. Diese Tabelle
wurde dann mit Testdaten versehen.
Wenn das erledigt wurde, kommen wir zu den eigentlichen Einstellungen des ODBC
Treibers. Dazu gehen wir unter Start Systemsteuerungen Verwaltung Datenquellen (ODBC)
Hier wählt man den Button „Hinzufügen..“ an und sucht den gewünschten Typen aus
(in meinem Fall: Microsoft Access Driver (*.mdb)). Dann betätigt man den Button
„Fertig stellen“ und kommt zu einer weiteren Maske in der man beim Punkt
„Datenbank:“ auf den Button „Auswählen“ klickt und dann in seinem System nach der
richtigen MS-Access Datei suchen kann (in meinem Fall liegt die Datenbank
„Autos.mdb“ auf dem Desktop).
Als Bezeichnung der Datenquelle kann ein beliebiger Name gewählt werden, dieser
wird jedoch im JAVA Teil verwendet, daher sollte dieser nicht allzu spektakulär
ausfallen.
Zum Abschluss einfach mit OK bestätigen.
Nun kommen wir zum JAVA technischen Teil. Dazu öffnen wir NetBeans und
erstellen ein neues Projekt. Danach erzeugen wir eine JAVA Datei. Ich habe es
bevorzugt die JAVA Datei (Datenbank.java) in einem Package namens „Datenbank“
zu erstellen, da es in weiterer Folge übersichtlicher ist.
Hier nun der Aufbau einer Verbindung und alles was dazu gehört.
package Datenbank;
import java.sql.*;
/**
*
* @author jagdkommando
*/
public class Datenbank {
Connection c;
public Datenbank()
{
}
public Connection getConnection()
{
return c;
}
public void verbindungAufbauen()
{
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
c = DriverManager.getConnection("jdbc:odbc:Autos");
}
catch (Exception e)
{
}
}
Danach könnten SQL Statements kommen wie z.B. folgendes
public ResultSet Autos()
{
ResultSet rs;
try{
Statement s = c.createStatement();
rs = s.executeQuery("SELECT * FROM Autos");
return rs;
}
catch(Exception e)
{
}
return null;
}
Die Verbindung wird folgendermaßen geschlossen:
public void verbindungSchliessen()
{
try
{
c.close();
}
catch (Exception e)
{
}
}
Verfasser: Filcic
Streams
Allgemeines:
Streams sind Eingabe- oder Ausgabeströme. Sie dienen dazu, Daten aus Dateien, von der Tastatur,
von einem Gerät oder aus dem Netz zu lesen oder in diese zu schreiben. Streams enthalten
Informationen über die Quelle (Source) oder das Ziel (Target), die Art der Übertragung,
Fehlerbehandlungen, Puffer usw.
Abstraktion der Kommunikation: Stream
Kommunikationsweg zwischen Datenquelle und Datensenke, über den seriell
Daten übertragen werden.
Operationen auf Streams (z.B. Lesen, Schreiben)
sind unabhängig vom konkreten Ein-/Ausgabegerät.
In Java können Datenquellen bzw. Datensenken sein:
• Dateien, Netzverbindungen (externe Datenquellen, -senken)
• byte-Feld, char-Feld, String, pipe (interne Datenquelle, -senke)
Das konkrete Ein-/Ausgabegerät ist durch die Abstraktion über Streams
austauschbar, ohne dass das Programm geändert werden muss!
Vor der Nutzung eines Streams muss man ihn erst zum Lesen oder
Schreiben öffnen, was implizit durch die Erzeugung eines Stream-Objektes
geschieht. Anschließend kann man sequentiell Daten lesen (solange Daten
vorhanden sind; EOF=End Of File) bzw. schreiben. Nach dem Schließen
eines Streams sind keine weiteren Operationen mehr erlaubt, bis man den Stream wieder öffnet.
Streamablauf beim Lesen::
Stream öffnen
Solange der Stream Informationen zurückgibt: Daten einlesen
Stream schließen
Streamablauf beim Schreiben:
Stream öffnen
Solange Informationen vorhanden sind: Informationen in Stream schreiben
Stream schließen
Die Standardbibliothek in Java bietet zahlreiche Streams an, die in Form von Klassen im Paket java.io
definiert sind. Der Paketname io steht für Input/Output, also die Datenein- und -ausgabe.
Benutzerschnittstellen, AWT oder Swing, sind zwar auch eine Form der Datenein- und -ausgabe,
diese zählen jedoch nicht zu den Streams. Während AWT und Swing das Augenmerk auf eine
übersichtliche Präsentation von Daten innerhalb der Programmoberfläche legen, sind Streams
grundlegendere Mechanismen zur Datenein- und -ausgabe, die in keinster Weise mit AWT und Swing
vergleichbar sind und auch nicht in Konkurrenz zu diesen stehen.
Standardstream:
System.in = Standardeingabe
System.out = Standardausgabe
System.err = Standardfehlerausgabe
Bsp:
•
•
Einlesen der Zeichen
read = System.in.read(buffer, 0, 80);
Umwandeln des Pufferinhaltes in einen String
input = new String(buffer, 0, read);
Ausgabe der eingelesenen Zeichen
System.out.print(input);
Der Eingabe-Stream System.in ist ein Exemplar der Klasse InputStream und besitzt somit
auch deren Methoden. Die Eingabe wird in diesem Fall mit der Methode read() vorgenommen.
Hierdurch wird das Array buffer mit den vom Benutzer auf der Standardeingabe eingegebenen
Zeichen Byte-weise gefüllt:
Das Gegenstück zum Eingabe-Stream wird ebenfalls von einer bestimmten Klasse
repräsentiert: dem OutputStream.
Zeichen in Java werden in Unicode (16 Bit) abgespeichert. Viele Daten liegen
aber als Bytes (8 Bit) vor (Zeichen im ASCII-Code, Daten). Deshalb gibt es für
die insgesamt 4 Möglichlichkeiten (Ein-/Ausgabe kombiniert mit 8/16 Bit)
jeweils eine (abstrakte) Klasse in Java (Paket java.io.*).
Eingabe
Ausgabe
Bytes
InputStream
OutputStream
Zeichen
Reader
Writer
Die 4 abstrakten Basisklassen definieren nur ein Basisprotokoll, um Streams zu
erzeugen usw. Die Klasse InputStream definiert aber z.B. nur eine abstrakte
Methode int read(byte[] b), die von konkreten Klassen implementiert
werden muss (
Springstream-/Sinkstream-Klassen).
In den Basisklassen ist z.B. auch nicht definiert, wie man ein float oder ein
String lesen kann. Dies wird Aufgabe weiterer Klassen sein, die diese
Basisklassen nutzen (
Processingstream-Klassen).
Ausgehend von den 4 abstrakten Basisklassen sind eine Vielzahl von
konkreten Klassen definiert. All diese Klassen klassifiziert man wie folgt:
• Ein Objekt einer Sinkstream-Klasse kann Daten direkt in eine Senke
schreiben.
• Ein Objekt einer Springstream-Klasse kann Daten direkt aus einer Quelle
lesen (und implementiert bei Byte-Streams u.a. die Methode int
read(byte[] b)).
• Ein Objekt einer Processingstream-Klasse erweitert die Funktionalität
eines Sinkstream- bzw. Springstream-Objektes (Beispiel: Pufferung von
Daten, Interpretation von 4 Bytes als ein int usw.)
Vorgehen:
Ein Objekt einer Springstream- bzw. Sinkstream-Klasse, um
die geeignete Datenquelle bzw. -senke anzusprechen.
// Erzeugt Stream aus Datei
FileInputStream fis = new FileInputStream("Datei.dat");
Erzeugt ein Objekt einer Processingstream-Klasse, die komfortable
Methoden bereitstellt. Der Konstruktor dafür erwartet ein
InputStream/OutputStream- bzw. Reader/Writer-Objekt.
DataInputStream dis = new DataInputStream(fis);
Arbeiten mit den komfortablen Methoden des ProcessingstreamObjektes.
// Lese einen double-Wert aus Datei
double d = dis.readDouble();
Byte-Stream:
Byte-Streams repräsentieren eine Art des Datenzugriffs, be idem jede Dateneinheit 8 Bit lang ist. Dies
ist ideal für binäre Daten oder textuelle Daten, die in einer einfachen Codierung vorliegen.
Problematisch wird dieser Ansatz, wenn wir mit Inhalten in Unicode-Codierung (Text) arbeiten wollen,
die sind so einfach nicht darstellbar.
Byte-Streams erben von den Basisklassen InputStream und OutputStream. Diese Basisklassen
stellen die grundlegende API für Streams, die Daten lesen oder Daten schreiben sollen, bereit.
Werden für denZugriff auf binäre Daten verwendet.
InputStream
Die (abstrakte) Basisklasse für den lesenden Zugriff auf Streams ist InputStream.
Methoden: int read(), int read(byte[] b) …close()
Die drei read()-Methoden lsen Bytes ein - wobei diese entweder als einzelner Wert oder
als Byte-Array zurückgegeben warden.
• ByteArrayInputStream: Auslesen von Bytes aus einem Objekt vom Typ "Feld von Bytes"
• FileInputStream: Auslesen von Bytes aus einer externen Datenquelle (z.B. Datei)
• PipedInputStream: Kommunikation von Threads über Pipes
• FilterInputStream: im Wesentlichen Oberklasse für 3 Unterklassen
• BufferedInputStream: gepufferte Eingabe (evtl. effizienter)
• DataInputStream: primitive Datentypen (int, float,...) binär einlesen (big endian)
• PushBackInputStream: 1 Byte lesen und wieder zurückstellen möglich
• ObjectInputStream: (komplexe) Objekte binär einlesen
• SequenceInputStream: Hintereinander Einlesen von mehreren Eingabe-Streams wie von
einem logischen Stream
OutputStream
Das Gegenstück zu InputStream repräsentiert OutputStream. Diese abstrakte Klasse ist
Basis aller ableitenden Byte-Output-Stream-Implementierungen.
Methoden: write(int b), write(byte[] b) …
Die drei write()-Methoden nehmen einzelne Bytes oder Byte-Arrays als Parameter
entgegen. Das Schreiben des Streams findet je nach Ableitung nicht sofort statt.
Stattdessen werden die Daten in einem Puffer zwischengespeichert – die Methode flush()
löscht diesen Puffer und sorgt dafür , dass sein Inhalt zuvor auf das Ausgabe-Medium
übertragen wird.
• ByteArrayOutputStream: Schreiben von Bytes in ein Objekt vom Typ "Feld von Bytes"
• FileOutputStream: Schreiben von Bytes in eine externe Datenquelle (z.B. Datei)
• PipedOutputStream: Kommunikation von Threads über Pipes
• FilterOutputStream: im Wesentlichen Oberklasse für 3 Unterklassen
• BufferedOutputStream: gepufferte Ausgabe
• DataOutputStream: primitive Datentypen (int, float,...) binär ausgeben (big endian)
• PrintStream: komfortable Ausgabe über Stringumwandlung (Beispiel:
System.out.println(...))
• ObjectOutputStream: (komplexe) Objekte binär ausgeben
Die Streams, wie sie im letzten Abschnitt verwendet wurden, sind alle Byte-orientiert, d. h. als kleinste
Ein- und Ausgabeeinheit wird ein Byte verwendet. Dadurch können Probleme enstehen, wenn in einer
Java-Anwendung Text verarbeitet wird. In Java werden Strings intern im Unicode-Format gespeichert,
wodurch ein Zeichen 16 Bit (2 Byte) belegt.
Auf verschiedenen Plattformen werden unterschiedliche Zeichensätze für die Kodierung von Text
verwendet. Ein solcher Zeichensatz besitzt sehr oft 256 Einträge und ordnet jedem Eintrag ein
Zeichen zu. Ein Zeichen wird also durch einen Index innerhalb eines Zeichensatzes bestimmt. Dieser
Index kann bei 256 Zeichen durch genau ein Byte dargestellt werden.
Character-Stream:
Character-Streams verwenden im Gegensatz zu Byte-Streams grundsätzlich 16 Bit, um die
Informationen zu repräsentieren. Dies ermöglicht eine deutlich bessere Anpassung an
unterschiedliche Zeichensätze. Dies prädestiniert sie für den Zugriff auf Textdateien
Reader/Writer sind nicht direkt mit externen Datenquellen
möglich, sondern nur über Brückenklassen (später).
Reader
Der Reader ist die Basisklasse aller Eingabe-Ströme im Umfeld von Character-Streams.
Methoden:
read(), read(CharBuffer target), readchar([] cbuf)
• CharArrayReader: Lesen von Zeichen aus einem Objekt vom Typ "Feld von char"
• StringReader: Lesen von Zeichen aus einem String
• PipedReader: Kommunikation von Threads über Pipes
• BufferedReader: gepuffertes Lesen von Zeichen
• LineNumberReader: Einlesen von Zeichen mit Abfragemöglichkeit für Zeilennummer
• FilterReader: Basis für eigene Filterklassen
• PushbackReader: Zurückstellen von Zeichen möglich
• InputStreamReader: Brückenklasse (byte nach char)
• FileReader: "Bequeme" Brückenklasse
Writer
Die abstrakte Klasse Writer stellt eine Basis für Klassen dar, die die zu schreibenden in
Unicode-Zeichen in die Codierung einer bestimmten Plattform wandeln können.
Methoden: write( int c) , write(char[] cbuf ), write (String str) …
• CharArrayWriter: Schreiben von Zeichen in ein Objekt vom Typ "Feld von char"
• StringWriter: Schreiben von Zeichen in einen String
• PipedWriter: Kommunikation von Threads über Pipes
• BufferedWriter: gepufferte Zeichenausgabe
• FilterWriter: Basis für eigene Filterklassen
• OutputStreamWriter: Brückenklasse (char nach byte)
• FileWriter: "Bequeme" Brückenklasse
• PrintWriter: komfortable Ausgabe über Stringumwandlung
Character- oder Byte-Stream verwenden?
Wenn man keine Kompatibilität zu Java 1.0 benötigt oder keine binären Daten man lesen oder
schreiben will, verwendet man Character-Stream. Diese sind einfacher zu handhaben und
unterstützen auch exotische Zeichensätze.
Hat man aber binäre Dateien zu verarbeiten oder eine Applikation Java 1.0 verwenden oder
Informationen schreiben die in 8Bit vorliegen, muss man den komplexeren Byte-Stream verwenden
Übersicht:
Quelle/Senke
Byte/char-Feld (intern)
String (intern)
Pipe (intern)
Datei (extern)
Funktion
Pufferung
Zurückschreiben
Textausgabe
Zeilen zählen
Ein-/Ausgabe von Daten
primitiven Typs
Objektserialisierung
Stream-Verkettung
Brückenklassen
Springstream-/Sinkstream-Klassen
byte-orientiert
ByteArrayInput-/OutputStream
PipedInput-/OutputStream
FileInput-/OutputStream
Processingstream-Klassen
byte-orientiert
BufferedInput-/Outputstream
PushbackInpustream
Printstream
zeichenorientiert
CharArrayReader/Writer
StringReader/Writer
PipedReader/Writer
FileReader/Writer
zeichenorientiert
BufferedReader/Writer
PushbackReader
PrintWriter
LineNumberReader
DateInput/OutputStream
ObjectInput-/OutputStream
SequenceInputStream
InputStreamReader
OutputStreamWriter
Beispiele:
Gepufferte Ein-/Ausgabe einzelner Bytes
import java.io.*;
class Test1
{
public static void main(String[] args) throws IOException
{
// Stream-Objekte erzeugen (Sinkstream und Processingstream)
FileOutputStream fos = new FileOutputStream("Datei1.dat");
BufferedOutputStream bos = new BufferedOutputStream(fos, 5);
// Binäres Schreiben und anschließend Datei schließen
for(int i=0; i<10; i++)
bos.write(i);
bos.flush();
bos.close();
// Stream-Objekte erzeugen (Springstream und Processingstream)
FileInputStream fis = new FileInputStream("Datei1.dat");
BufferedInputStream bis = new BufferedInputStream(fis, 2);
// Binäres Lesen und anschließend Datei schließen
int b;
for(int i=0; i<10; i++)
if((b = bis.read ()) == -1)
System.out.println("Fehler: Ende der Datei erreicht");
else
System.out.println(b);
bis.close();
}
}
Binäre Ein-/Ausgabe primitiver Datentypen
import java.io.*;
class Test2
{
public static void main(String[] args) throws IOException
{
// Stream-Objekte erzeugen (Sinkstream und Processingstream)
FileOutputStream fos = new FileOutputStream("Datei2.dat");
DataOutputStream dos = new DataOutputStream(fos);
// Binäres Schreiben und anschließend Stream schließen
dos.writeInt(4711);
dos.writeDouble(3.1415);
dos.writeChar('a');
dos.close();
// Stream-Objekte erzeugen (Springstream und Processingstream)
FileInputStream fis = new FileInputStream("Datei2.dat");
DataInputStream dis = new DataInputStream(fis)
// Binäres Lesen und anschließend Stream schließen
int i = dis.readInt();
double d = dis.readDouble();
char c = dis.readChar();
dis.close();
}
}
Z.B. binäre Ausgabe von 3: 00000000 00000000 00000000 00000011 (32 Bits)
Schachtelung von Processingstreams
import java.io.*;
class Test3
{
public static void main(String[] args) throws IOException
{
// Stream-Objekt erzeugen
FileOutputStream fos = new FileOutputStream("Datei3.dat");
BufferedOutputStream bos = new BufferedOutputStream(fos, 10);
// Aufgrund der Vererbung kann sich bos wie ein OutputStream
verhalten
PrintStream pos = new PrintStream(bos);
// Schreiben (gepuffert und komfortabel)
for(int i=0; i<1000; i++)
pos.println("i hat den Wert " + i);
// Puffer entleeren und Stream schließen
pos.flush();
pos.close();
}
}
FileInputStream und FileOutputStream / FileReader und FileWriter
Den häufigsten Kontakt mit I/O-Streams werden in Form von Dateien sein.
Hierfür dienen die Klassen FileInputStream und FileOutputStream für byteorientierte Datenstreams
sowie Filereader und FileWriter. Die Konstruktoren erwarten entweder einen String mit dem Namen
der gewünschten Datei oder ein File-Objekt:
Zb. FileInputStream(String name) , FileOutputStream(File datei)
FileInputStream - Öffnen einer Datei über einen GUI-Button:
private void OpenButtonActionPerformed(java.awt.event.ActionEvent evt)
{
JFileChooser chooser = new JFileChooser();
int ctrl= chooser.showOpenDialog(me);
if (ctrl==JFileChooser.APPROVE_OPTION)
{
openFile= chooser.getSelectedFile(); // String dateiname= openFile.getName();
try
{
FileInputStream stream = new FileInputStream(openFile.getAbsolutePath());
//ohne Button: new FileInputStream(("C:\\text.txt");
DataInputStream dos = new DataInputStream(stream);
double b;
boolean eof = false; //end-of-file
while(!eof)
{
try{
b=dos.readDouble();
textarea.append( b+ " // ");
}
catch (EOFException e)
{
eof=true;
}
}
}
catch (FileNotFoundException e)
{
………
FileOutPutStream - Zufallszahlen speichern in einer Datei:
FileOutputStream file = new FileOutputStream("C:\\text.txt");
DataOutputStream dos = new DataOutputStream(file);
Random r = new Random();
double zufall;
for(int i=0; i<1000; i++)
{
zufall= r.nextDouble();
dos.writeDouble(zufall);
}
dos.close();
FileWriter – Text in Datei schreiben:
Writer fw = null;
try
{
fw = new FileWriter( "fileWriter.txt" );
fw.write( "Zwei Jäger treffen sich..." );
fw.append( System.getProperty("line.separator") ); // e.g. "\n"
}
catch ( IOException e ) {
System.err.println( "Konnte Datei nicht erstellen" );
}
finally {
if ( fw != null )
try { fw.close(); } catch ( IOException e ) { }
}
FileReader - Zeichen mit der Klasse FileReader lesen:
Reader reader = null;
try
{
reader = new FileReader( "bin/lyrics.txt" );
for ( int c; ( c = reader.read() ) != –1; )
System.out.print( (char) c );
}
catch ( IOException e ) {
System.err.println( "Fehler beim Lesen der Datei!" );
}
finally {
try { reader.close(); } catch ( Exception e ) { }
}
Brückenklassen:
Durch Nutzung der Brückenklasse InputStreamReader und Unterklasse
FileReader kann man von einem byte-orientierten Eingabestrom
(Springstream-Objekt) direkt Zeichen lesen.
Durch Nutzung der Brückenklasse OutputStreamWriter und Unterklasse
FileWriter kann man Zeichen direkt in einem Byte-Stream (SinkstreamObjekt) schreiben.
Die Umsetzung zwischen Zeichen und Bytes wird über ein Encoding (z.B.
ASCII, ISO-8859-1, UTF-8) gesteuert.
Beispiel:
import java.io.*;
class Test4 {
public static void main(String[] args) throws IOException {
// Sinkstream-Objekt erzeugen
FileOutputStream fos1 = new FileOutputStream("Datei4a.dat");
FileOutputStream fos2 = new FileOutputStream("Datei4b.dat");
// Brückenklassenobjekte erzeugen mit Default-Encoding bzw. ASCIIEncoding
OutputStreamWriter osw1 = new OutputStreamWriter(fos1);
OutputStreamWriter osw2 = new OutputStreamWriter(fos2, "US-ASCII");
// Schreiben eines Unicode-Strings auf die Byte-Ströme
osw1.write("Äh, grützi miteinand");
osw2.write("Äh, grützi miteinand");
// Streams schließen
osw1.close();
osw2.close();
}}
Serialisierung:
Was ist Serialisierung?
Manchmal braucht man ein Objekt nicht nur so lang, wie ein Programm läuft, sondern auch danach
nochmal. Neben Datenbanken gibt es für solche Probleme den Mechanismus des Serialisierens. Beim
Serialisieren wird der wird der aktuelle Zustand eines Objekts in eine Datei geschrieben, die jederzeit
wieder ausgelesen werden kann. Mit den ausgelesenen Daten kann man sich dann einem neues
Objekt anlegen, das wieder genau den Zustand des alten Objekts hat. Das nennt man dann
Deserialisieren.
Dadurch, dass auch aggregierte Objekte beim Serialisieren berücksichtigt werden, könnte es
normalerweise zu dem unerwünschten Effekt kommen, dass sich der Algorithmus zur Serialisierung in
eine Endlosschleife verläuft, wenn zB. zwei Objekte sich gegenseitig aggregieren, oder allgemeiner,
wenn es Zyklen bei der Referenzierung gibt. An diesen Fall haben aber schon die JavaProgrammierer gedacht. Es wird deshalb beim Serialisieren eine Hash-Tabelle aller ObjektReferenzen der zu serialisierenden Objekte angelegt, mit der verhindert wird, dass ein Objekt zweimal
serialisiert wird.
Umsetzung in Java
Die Objektklasse muss die Marker-Schnittstelle Serializable Implementieren!
Zum Serialisieren braucht man folgende Stream-Klassen aus dem Package java.io:
•
•
ObjectInputStream
ObjectOutputStream
Die Konstruktoren der beiden Klassen verlangen als Parameter jeweils einen Grund-Stream von der
abstrakten Klasse InputStream, bzw. OutputStream. Die folgenden beiden konkreten Klassen sind
Erweiterungen der benötigten abstrakten Klassen und können benutzt werden, wenn man die Objekte
in Dateien speichern will.
• FileInputStream
• FileOutputStream
Die Klasse, des Objekts, das serialisiert werden soll muss das Interface Serializable implementieren.
Methoden der Object-Stream-Klassen
Die write-Methoden
Um Objekte zu serialisieren stellt die Klasse ObjectOutputStream für jeden primitiven Datentyp eine
write-Methode bereit.
Zu Serialisieren von Objekten gibt es die Methode
void writeObject( Object wert ).
Es wird also jedes Objekt beim Serialisieren auf seine Oberklasse Object gecastet. Die writeMethoden werfen alle IOExceptions, die beim Benutzen der Methoden behandelt werden müssen.
Die read-Methoden
Die read-Methoden der Klasse ObjectInputStream sind analog zu den o.g. write-Methoden. Sie haben
keine Parameter und haben als Rückgabetyp den jeweiligen Datentyp. Einige Beispiele sind:
boolean readBoolean() , int readInt()
Object readObject()
Wenn man ein Objekt wieder deserialisiert ist es vom Typ Object und muss durch einen Down-Cast
zurück in seinen ursprünglichen Datentyp gecastet werden. Der genaue Typ des Objekts muss
deshalb dem Programmierer bekannt sein, weil das System nur weiss, dass es sich um ein
Unterobjekt der Klasse Object handelt.
Die read-Methoden werfen unterschiedliche Exceptions, die abgefangen werden müssen.
Java-Code-Beispiele
Serialisierung
ein Objekt-Stream wird angelegt als Parameter wird ein Datei-Stream übergeben (20)
ein Integer wird serialisiert (22)
ein String wird als Objekt serialisiert (23)
ein Objekt der Klasse Date wird serialisiert (24)
der Datenstrom wird geschlossen (25)
Deserialisierung
ein Datei-Stream wird angelegt (27)
ein Objekt-Stream wird angelegt (28)
ein Integer wird deserialisiert (27)
ein String wurde als Objekt serialisiert, wird eingelesen und als String down-gecastet (30)
ein Objekt wird eingelesen und auf seinen ursprünglichen Datentyp Date down-gecastet (31)
der Objekt-Stream wird geschlossen (32)
Weiteres Bsp.:
import java.io.*;
public class Person implements Serializable
{
private String name;
private String vorname;
private int alter;
public Person (String n, String vn, int a)
{
this.name = n;
this.vorname = vn;
this.alter = a;
}
public void ausgeben()
{
System.out.println(vorname + " " + name + ": " + alter);
}
}
public class SeriTest
{
public static void main(String[] args)
throws IOException, ClassNotFoundException
{
String name = "Datei5.dat";
FileOutputStream fos = new FileOutputStream(name);
ObjectOutputStream oos = new ObjectOutputStream(fos);
Person p1 = new Person("Adams", "Bryan", 67);
Person p2 = new Person("Mouse", "Mickey", 82);
// Objekte serialisiert ausgeben
oos.writeObject(p1);
oos.writeObject(p2);
oos.close();
FileInputStream fis = new FileInputStream(name);
ObjectInputStream ois = new ObjectInputStream(fis);
// Objekte serialisiert einlesen (Cast notwendig)
Person q1 = (Person) ois.readObject();
Person q2 = (Person) ois.readObject();
q1.ausgeben();
q2.ausgeben();
}
}
Verfasser: Dzogaz
JSP & Servlets
Java Servlets
Servlets stellen Java-Programme dar, die auf einem Web-Server laufen. Sie erlauben
es Entwicklern
dynamische Web-Seiten mit Java zu erstellen.
Ein Servlet hat folgende Aufgaben:
• Daten, die ein Benutzer beispielsweise in ein HTML-Formular auf einer Web-Seite
eingegeben hat, werden gelesen und verarbeitet.
• Gegebenenfalls werden weitere Informationen, die mitgesendet werden,
ausgewertet.
Beispielsweise was für ein Browser oder System verwendet wird, etc.
• Ergebnisse werden anhand der vorliegenden Daten generiert. Es wird
Geschäftslogik
entweder direkt im Servlet ausgeführt oder eine andere Klasse aufgerufen, welche
die Logik
enthält oder die z.B. eine Datenbankabfrage durchführt.
• Die Ergebnisse werden formatiert. Erwartet der Browser eine Antwort im HTMLFormat, so
müssen die Ergebnisse gemäß dem Standard formatiert werden. Es ist mit Servlets
auch
möglich, Daten in einem beliebigen anderen Format zurückzuliefern (gif, jpeg, doc,
etc.).
Java Server Pages (JSP)
JavaServer Pages (JSP) sind Text-Dokumente, die reinen HTML-Dateien sehr
ähnlich sind. Jedoch
ist in JSP neben dem HTML-Code zusätzlicher Java-Code vorhanden. JavaServer
Pages erlauben
die Vermischung von regulärem, statischem HTML mit dynamisch generierten
Inhalten durch Java
Code. Anders als bei Servlets, bei denen HTML in Java-Code eingebettet wird, ist bei
JSP der JavaCode in das HTML-Dokument eingefügt.
Anwendung in der Praxis
In der Praxis findet eine strikte Trennung zwischen Logik und Darstellung statt.
Dynamische
Abläufe werden im Java-Servlet programmiert und das Resultat an eine JSP Seite
weitergegeben
(Ähnlich wie bei Template Systemen). Desöfteren ist es jedoch notwendig Ausgaben
im HTML
Format bereits im Servlet zu verankern, wie z.B. Ausgaben von Fehlermeldungen
oder Ähnlichem.
Dabei muss beachtet werden, dass der HTML Aufbau der Seite immer in JSP Files
verankert
werden sollte. Mittels JSP ruft man dann die übergebenen Daten und Werte vom
Servlet ab und
stellt diese in einer für den Menschen gut leserlichen Form dar (z.B Tabellen,
Schriftformatierung
etc.)
Servlets und Tomcat
Apache Tomcat stellt eine Umgebung zur Ausfürung von Java Code auf Webservern
bereit.
Mithilfe des JSP-Compilers „Jasper“ kann er auch JavaServer Pages in Servlets
übersetzen und
ausführen. In dieser Umgebung befindet sich auch ein kompletter HTTP Server.
Wichtige Dateien im Verzeichnisbaum (Tomcat)
Info: Die Verzeichnisstruktur ist vom jeweiligen Webserver Abhängig. In diesem Beispiel wird
die
Verzeichnisstruktur vom Tomcat Dienst beschrieben.
Das ROOT Verzeichnis
Im ROOT Verzeichnis befinden sich sämtliche Dateien,
welche keine grobe Programmierlogik beinhalten sondern
mehr für die Darstellung und Bibliotheken anderer
Programmierspachen (z.B JavaScripts) zuständig sind.
Dazu gehören Dateien verschiedener Art, mitunter: .html,
.jsp, Order für Grafiken (/img, /images), .css, Javascripts,
u.v.m
Das WEB-INF Verzeichnis
Dieses Verzeichnis beinhaltet die web.xml und den
classes Ordner in welchem die Servlets abgelegt sind.
Die web.xml
Dies ist die globale Konfigurationsdatei des
Webprojektes und wird mittels XML Tags durch
Übergabe verschiedener Parameter konfiguriert.
Der wichtigste Konfigurationsinhalt ist die Angabe der Pfade und Namen, mit
welchem die Servlets
abgerufen werden können.
Beispiel einer web.xml:
<web-app>
<servlet>
<servlet-name>MyFirstServlet</servlet-name>
<servlet-class>bsp1.MyFirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyFirstServlet</servlet-name>
<url-pattern>SerlvetTest</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout> 30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Mit dem Tag <servlet-name> vergibt man einen Namen für eine Servlet Klasse.
Über diesen
Namen identifizieret man das Servlet in der web.xml. <servlet-class> definiert die
Servlet Klasse.
In dem Tag <servlet-mapping> weist man einem Servlet eine Adresse (URL) zu,
über die der
Benutzer später das Servlet in seinem Browser aufrufen kann.
Visualisiertes Beispiel:
<url-pattern>ServletTest</url-pattern> → http://localhost/ServletTest
Genauso könnten Laufzeitabhängige Parameter an den Webserver übergeben
werden, wie z.B die
Dauer, wie lange eine Session aktiv bleiben soll etc. oder welcome Files, welche
abgerufen werden,
wenn in unserem Beispiel nur http://localhost/ an den Webserver geschickt wird.
Eine detailierte Liste aller Tags, samt Erklärungen, findet man hier:
http://www.absoluteswissen.de/web-xml_i.htm
Der classes Ordner
Jeder Unterordner bildet ein Paket welches angesprochen werden kann. In diesen
befinden sich
dann die Servlets und Java Klassen.
Servlets Funktionsweise
Info: Als Beispiel wird ein Formular genommen, welches Werte an ein Servlet sendet, und
diese
dann auswertet.
HTML Formular
„HTML stellt die Möglichkeit zur Verfügung, Formulare zu erstellen. In Formularen
kann der
Anwender Eingabefelder ausfüllen, in mehrzeiligen Textfeldern Text eingeben, aus
Listen Einträge
auswählen usw. Wenn das Formular fertig ausgefüllt ist, kann der Anwender auf
einen Button
klicken, um das Formular abzusenden.“ - SelfHTML
Wichtige Formularelemente:
<form> Tag
Jedes Formular beginnt mit einem <form enctype="multipart/form-data"
method="post"
action="index.php?form=CalendarEditComment">
und endet mit </form>. Dazwischen finden sämtliche HTML Formularelemente ihren
Platz.
Der Parameter method kann entweder mit get oder post deklariert werden. GET
Sendet alle in den
Formularfeldern eingetragenen Werte über die URL an die URL welche im action
Parameter
angegeben wurde, dabei muss beachtet werden, dass die URL auf 255 Zeichen
beschränkt ist.
POST hingegen sendet die Werte im Hintergrund weiter, ohne, dass sie dem
Benutzer sichtbar
gemacht werden.
Textfeld:
Textarea:
Radiobuttons:
Option List
<SELECT NAME=platforms>
<OPTION>Windows
<OPTION>Macintosh
<OPTION>UNIX
</SELECT>
Submit Buttons
Der Submit Button sendet alle eingetragenen Werte zwischen <form> und </form>
(In welchem
sich auch der Submit Button befindet), an die im action parameter angegebene URL.
Wurde keiner
angegeben, so werden die Werte an die selbe Seite gesendet.
Wie man sehen kann, ist es möglich jedem Formularelement einen Namen zu
vergeben. Über diese
Namen lassen sich die ausgewählten Werte dann im Servlet ansprechen.
Simples Servlet mit einer Ausgabe
protected void doGetc(HttpServletRequest request,
HttpServletResponse response) throws
ServletException, IOException {
// Typ des übertragenen Inhalts:
response.setContentType("text/html");
// Verbindungsaufbau
PrintWriter out = new PrintWriter (response.getOutputStream());
// Erzeugung des html-Files
out.println("<html>");
out.println("<head><title>ZweitesServlet</title></head>");
out.println("<body>");
out.println("Das ist das zweite Servlet mit 2 Parametern<br />");
Simples Servlet – Abruf von Formulareingaben
// beim Aufruf übertragene Parameter:
String par= request.getParameter("Familienname");
String par2= request.getParameter("Vorname");
Mittels request.getParameter(„FieldName) kann man den Wert eines
Formularfeldes, nachdem
das Formular abgesendet worden ist, auslesen. Somit lässt sich auch abfragen ob
das Formular
überhaupt abgesendet wurde.
out.println(par+" "+par2+("<pr />");
out.println("<a href=\"start.html\">zurück</a>");
out.println("</body></html>");
out.close();
Da das HTTP Protokoll zustandslos ist,ist die übergabe dieser Variablen über nur
eine Seite hinweg
möglich, klickt man, nach Auswertung der GET/POST Variablen auf eine weitere
Seite, so sind
sämtliche Ergebnisse wieder vergessen. In Kurzen Worten: POST/GET Variablen
sind nur über
einen „Seitenklick“ lebendig.
Um Variablen über die gesamte Besuchszeit eines Users speichern zu können,
sodass diese über
alle Seiten auf der Homepage abrufbar sind, benötigt man Sessions.
Sessions
Eine Session ist eine Datei welche auf einem Webserver abgelegt wird und in
welcher Variablen
gespeichert werden, sodass auf diese permanent zugegriffen kann (Solange die
Session aktiv ist).
Die Sessiondauer wird in der web.xml angegeben:
<session-config>
<session-timeout> 30</session-timeout>
</session-config>
In diesem Fall bleibt die Session für 30 Minuten aktiv, solange der Browser nicht
geschlossen, oder
eine andere Homepage besucht wird.
Eine Session erstellen
request.getSession(boolean);
request.getSession(true);
erstellt eine neue Session. Sollte bereits eine vorhanden sein, dann liefert es diese
zurück und
erstellt keine neue. Sollte man es mit einem false abfragen, so erhaltet man einen
nullwert, falls
keine Session vorhanden ist.
Sessionvariablen erstellen
session.putValue(VariablenName,Wert);
session.putValue(“Gruesse“, new String(„Hello“) );
Wie man sehen kann übergibt man der Session ein Objekt mit, genauso können es
aber Variablen,
Instanzen eigen ersteller Klassen, Klassen verschiedener Art wie ArrayLists etc., in
die Session
gepackt und nach belieben in verschiedenen Seiten abgerufen werden, nachdem
man mit
getSession() die Session geholt hat.
Objekte aus einer Session auslesen
session.getValue(name);
session.getValue(„Gruesse“);
Hiermit ruft man den Wert ab, welchen man vorher in die Sessionvariable
„Gruesse“ eingetragen
hat.
JSP
In JSPs sollte man sämtlichen HTML Code einfügen welcher zur Präsentation
notwendig ist. Da
oftmals aber auch Java Code von Nöten ist bietet JSP bestimmte
Scriptingmöglichkeiten diese zu
implementieren.
Expressions
Die einfachste und vermutlich auch verbreiteste Form des Skriptings sind die
Ausdrücke
(Expressions). Will man bspw. einen String an einer bestimmten Stelle im HTMLCode ausgeben,
so verwendet man einen entsprechenden Ausdruck. Ein Beispiel haben wir schon in
der Einleitung
kennen gelernt, als der User-Agent mit folgendem Code ausgegeben wurde:
<%= request.getHeader("User-Agent") %>
Wichtig ist das Gleichheitszeichen, das den folgenden Code als Ausdruck
auszeichnet. Zwischen
dem öffnenden "<%=" und dem schliessenden "%>" darf genau ein Ausdruck stehen.
Dabei ist zu
beachten, dass das sonst bei Java übliche Semikolon am Ende eines Statements
entfällt.
Espressions werden oft für das automatische Ausfüllen von Formularfeldern
verwendet.
Scriptlets
Skriplets können jedweden beliebigen Java-Code enthalten, vorausgesetzt die
verwendeten
Bibliotheken liegen im Classpath der Web-Anwendung und die benötigten Packages
wurden
importiert.
<html>
<head><title>TestSeite</title></head>
<body style="font-family:sans-serif;padding-top:15px;">
<h3>
<%
String[] valueArray = {"Das", "ist", "ein", "sehr", "komisches","beispiel"};
int i;
for (i = 0; i < valueArray.length; i++) {
%>
<%= valueArray[i] %>
<%
}
%>
</h3>
</body>
</html>
Deklarationen
Das letzte Skripting-Element dient zur Deklaration von Instanz-Variablen3 und zur
Definition von
Methoden und nimmt die Form <%! ... %> an.
So kann man bspw. einen Counter wie folgt definieren:
<%! int objectCounter = 0; %>
Verfasser: Horvath
Exceptions
Mit den Exceptions besitzt Java einen Mechanismus zur strukturierten Behandlung
von Fehlern, die während der Programmausführung auftreten. Tritt etwa ein
Laufzeitfehler auf, weil ein Array-Zugriff außerhalb der definierten Grenzen erfolgte
oder weil eine Datei, die geöffnet werden sollte, nicht gefunden wurde, so gibt es in
Java Sprachmittel, die eine systematische Behandlung solcher Ausnahmen
ermöglichen.
Da Exceptions ein relativ neues Feature von Programmiersprachen sind, ist es
sinnvoll, zunächst die in diesem Zusammenhang verwendeten Begriffe vorzustellen.
Als Exception wird dabei die eigentliche Ausnahme bezeichnet, die durch ein
Programm zur Laufzeit verursacht werden kann. Das Auslösen einer Ausnahme wird
im Java-Sprachgebrauch als throwing bezeichnet, wir werden meist die deutsche
Bezeichnung auslösen verwenden. Das Behandeln einer Ausnahme, also die
explizite Reaktion auf das Eintreten einer Ausnahme, wird als catching bezeichnet.
Schließlich werden wir auch die Begriffe Ausnahme und Exception synonym
verwenden.
Das Grundprinzip des Exception-Mechanismus in Java kann wie folgt beschrieben
werden:
•
•
•
•
Ein Laufzeitfehler oder eine vom Entwickler gewollte Bedingung löst eine
Ausnahme aus.
Diese kann nun entweder von dem Programmteil, in dem sie ausgelöst wurde,
behandelt werden, oder sie kann weitergegeben werden.
Wird die Ausnahme weitergegeben, so hat der Empfänger der Ausnahme
erneut die Möglichkeit, sie entweder zu behandeln oder selbst weiterzugeben.
Wird die Ausnahme von keinem Programmteil behandelt, so führt sie zum
Abbruch des Programms und zur Ausgabe einer Fehlermeldung.
Die try-catch-Anweisung
Das Behandeln von Ausnahmen erfolgt mit Hilfe der try-catch-Anweisung:
001 try {
002 Anweisung;
003 ...
004 } catch (Ausnahmetyp x) {
005 Anweisung;
006 ...
007 }
Der try-Block enthält dabei eine oder mehrere Anweisungen, bei deren Ausführung
ein Fehler des Typs Ausnahmetyp auftreten kann. In diesem Fall wird die normale
Programmausführung unterbrochen, und der Programmablauf fährt mit der ersten
Anweisung nach der catch-Klausel fort, die den passenden Ausnahmetyp deklariert
hat. Hier kann nun Code untergebracht werden, der eine angemessene Reaktion auf
den Fehler realisiert.
Beispiel:
int number = 0;
while ( true )
{
try
{
String s = javax.swing.JOptionPane.showInputDialog(
"Bitte Zahl eingeben" );
number = Integer.parseInt( s );
break;
}
catch ( NumberFormatException ó_ò )
{
System.out.println( "Das war keine Zahl!" );
}
}
System.out.println( "Danke für die Zahl " + number );
System.exit( 0 ); // Beendet die Anwendung
throws im Methodenkopf angeben
Neben der rahmenbasierten Ausnahmebehandlung – dem Einzäunen von
problematischen Blöcken durch einen try- und catch-Block – gibt es eine weitere
Möglichkeit, auf Exceptions zu reagieren: Im Kopf der betreffenden Methode wird
eine throws-Klausel eingeführt. Dadurch zeigt die Methode an, dass sie eine
bestimmte Exception nicht selbst behandelt, sondern diese unter Umständen an die
aufrufende Methode weitergibt. Nun kann von einer Funktion eine Exception
ausgelöst werden. Die Funktion wird abgebrochen und gibt ihrerseits eine Exception
zurück.
Dazu ein Beispiel: Eine Methode soll eine Datei öffnen und die erste Zeile auslesen.
Der Dateiname wird als Argument der Methode übergeben. Da das Öffnen der Datei
sowie das Lesen einer Zeile eine Ausnahme auslösen kann, müssen wir diese
Ausnahme behandeln. Wir fangen sie jedoch nicht in einem eigenen try- und catchBlock auf, sondern leiten sie an den Aufrufer weiter. Das bedeutet, dass er sich um
den Fehler kümmern muss.
String readFirstLineFromFile( String filename )
throws FileNotFoundException, IOException
{
RandomAccessFile f = new RandomAccessFile( filename, "r" );
return f.readLine();
}
Dadurch steigt der Fehler entlang der Kette von Methodenaufrufen wie eine Blase
(engl. bubble) nach oben und kann irgendwann von einem Block abgefangen
werden, der sich darum kümmert.
Ist die Fehlerbehandlung in einem Hauptprogramm ganz egal, so können wir alle
Fehler auch an die Laufzeitumgebung weiterleiten, die dann das Programm im
Fehlerfall abbricht.
throws bei überschriebenen Methoden
Beim Überschreiben von Methoden gibt es eine wichtige Regel: Überschriebene
Methoden in einer Unterklasse dürfen nicht mehr Ausnahmen auslösen, wie schon
beim throws-Teil der Oberklasse aufgeführt sind. Da das gegen das
Substitutionsprinzip verstoßen würde, kann eine Methode der Unterklasse nur
•
•
•
Dieselben Ausnahmen wie die Oberklasse auslösen,
Ausnahmen spezialisieren oder
weglassen.
Die Fehlerklassen von Java
Alle Laufzeitfehler in Java sind Unterklassen der Klasse Throwable. Throwable ist eine
allgemeine Fehlerklasse, die im wesentlichen eine Klartext-Fehlermeldung speichern
und einen Auszug des Laufzeit-Stacks ausgeben kann. Unterhalb von Throwable
befinden sich zwei große Vererbungshierarchien:
•
•
Die Klasse Error ist Superklasse aller schwerwiegenden Fehler. Diese werden
hauptsächlich durch Probleme in der virtuellen Java-Maschine ausgelöst.
Fehler der Klasse Error sollten in der Regel nicht abgefangen werden, sondern
(durch den Standard-Fehlerhandler) nach einer entsprechenden Meldung zum
Abbruch des Programms führen.
Alle Fehler, die möglicherweise für die Anwendung selbst von Interesse sind,
befinden sich in der Klasse Exception oder einer ihrer Unterklassen. Ein Fehler
dieser Art signalisiert einen abnormen Zustand, der vom Programm
abgefangen und behandelt werden kann.
Viele Pakete der Java-Klassenbibliothek definieren ihre eigenen Fehlerklassen. So
gibt es spezielle Fehlerklassen für die Dateiein- und -ausgabe, die
Netzwerkkommunikation oder den Zugriff auf Arrays. Wir werden diese speziellen
Fehlerklassen immer dann erläutern, wenn eine Methode besprochen wird, die diese
Art von Fehler erzeugt.
Die finally-Klausel
Die try-catch-Anweisung enthält einen optionalen Bestandteil, der bisher noch nicht
erläutert wurde. Mit Hilfe der finally-Klausel, die als letzter Bestandteil einer try-catchAnweisung verwendet werden darf, kann ein Programmfragment definiert werden,
das immer dann ausgeführt wird, wenn die zugehörige try-Klausel betreten wurde.
Dabei spielt es keine Rolle, welches Ereignis dafür verantwortlich war, dass die try-
Klausel verlassen wurde. Die finally-Klausel wird insbesondere dann ausgeführt,
wenn der try-Block durch eine der folgenden Anweisungen verlassen wurde:
•
•
wenn das normale Ende des try-Blocks erreicht wurde
wenn eine Ausnahme aufgetreten ist, die durch eine catch-Klausel behandelt
wurde
wenn eine Ausnahme aufgetreten ist, die nicht durch eine catch-Klausel
behandelt wurde
wenn der try-Block durch eine der Sprunganweisungen break, continue oder return
verlassen werden soll
•
•
Die finally-Klausel ist also der ideale Ort, um Aufräumarbeiten durchzuführen. Hier
können beispielsweise Dateien geschlossen oder Ressourcen freigegeben werden.
Beispiel:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
/* Listing1207.java */
public class Listing1207
{
public static void main(String[] args)
{
int i, base = 0;
try {
for (base = 10; base >= 2; --base) {
i = Integer.parseInt("40",base);
System.out.println("40 base "+base+" = "+i);
}
} catch (NumberFormatException e) {
System.out.println(
"40 ist keine Zahl zur Basis "+base
);
} finally {
System.out.println(
"Sie haben ein einfaches Beispiel " +
"sehr glücklich gemacht."
);
}
}
}
Verfasser: Horvath
Array – Collection - Generisch
Ein Array ist ein spezieller Datentyp, der mehrere Werte zu einer Einheit
zusammenfasst. Er ist mit einem Setzkasten vergleichbar, in dem die Plätze
durchnummeriert sind. Angesprochen werden die Elemente über einen ganzzahligen
Index. Jeder Platz nimmt immer Werte des gleichen Typs auf. Normalerweise liegen
die Plätze eines Arrays (seine Elemente) im Speicher hintereinander, doch ist dies
ein für Programmierer nicht sichtbares Implementierungsdetail der virtuellen
Maschine.
Jedes Array beinhaltet Werte nur eines bestimmten Datentyps bzw. Grundtyps. Dies
können sein:
• Elementare Datentypen wie int, byte, long und so weiter
• Referenztypen (Objekte)
• Referenztypen anderer Arrays, um mehrdimensionale Arrays zu realisieren
Deklaration:
Arrays werden deklariert, in dem man das Objekt das man speichern möchte mit []
versieht und dann mit = new das Objekt erstellt.
import java.util.arrays
java Code:
typ[] arrayName = new typ[ länge ]
int[] einArrayAusIntegern = new int[100];
Arrays sind nicht auf primitive Datentypen beschränkt, Arrays können alle beliebigen
Objekte speichern.
Schüler [] klassen = new Schüler[schüleranzahl];
Hier wird ein Array erstellt, dass Objekte der Klasse Schüler speichern kann. Das
Array hat so viele Plätze wie in der Variable schüleranzahl gespeichert wurde. Die
Variable schüleranzahl muss vom Typ int sein!
Wichtig: Die Stärke von Arrays kennzeichnet sich dadurch, dass in den [] ein
Ausdruck stehen kann, was bedeutet, dass hier gerechnet werden kann oder andere
Methoden die einen int als Rückgabewert haben innerhalb ausgeführt werden
können.
Arrays mit Inhalten
Die bisherigen Deklarationen von Array-Variablen erzeugen noch lange kein ArrayObjekt, das die einzelnen Array-Elemente aufnehmen kann. Wenn allerdings die
Einträge direkt mit Werten belegt werden sollen, gibt es in Java eine Abkürzung, die
ein Array-Objekt anlegt und zugleich mit Werten belegt.
int[] primiMäuschen = { 1, 2, 3, 5, 7, 7 + 4, };
String[] nouns = {
"Haus", "Maus",
"dog".toUpperCase()
};
Iteration über Arrays mit einer for-Schleife
Da man bei einer Schleife immer die Länge des Arrays wissen muss, die Länge aber
vielleicht erst durch den Ablauf des Programms feststeht gibt es eine einfache
Möglichkeit die Länge eines Arrays immer exakt zu bestimmen:
arrayName.length
Der Durchschnitt der Zahlenelemente im Array:
java Code:
for (int index = 0; index < arrayName.length; index++) {
summe += arrayName[index];
}
durchschnitt = summe / arrayName.length;
2 Dimensionale Arrays (2D Arrays)
Deklaration:
java Code:
typ[][] arrayName = new typ[ zeilen ][ spalten ]
int[][] zahlenTabelle = new int[4][6];
2D Arrays kann man sich bildlich wie eine Tabelle vorstellen, wobei die erste Zahl in
den [] die Zeilen, und die zweite Zahl die Spalten darstellt.
10 24 17 11 76 28
15 66 19 9 46 7
22 23 20 1 58 16
44 55 80 12 36 0
Die Kurzform kann hier genauso verwendet werden, folgende Anweisung gibt die
obige Tabelle:
int[][] tabelle = { {10, 24, 17, 11, 76, 28},
{15, 66, 19, 9, 46, 7},
{22, 23, 20, 1, 58, 16},
{44, 55, 80, 12, 36, 0} };
Fehler bei Arrays:
NullPointerException = Array wurde nicht mit Werten belegt;
IndexOutOfBoundsException = Es wurde eine negative Zahl im Indexer angegeben oder die
Zahl in der Schleife ist zu groß.
Collection
• Collections bieten ein Konzept, welches Objekte zu Gruppen zusammen
fasst (z.B. eine Klasse (Gruppe von Studenten), ein Postfach (Gruppe
von Emails) oder ein Telefonverzeichnis (Gruppe von NameTelefonnummer- Paaren).
• Mit einem Collection-Objekt können Instanzen beliebiger Klassen
verwaltet werden.
• Seit J2SE 5.0 gibt es Generics, davor galt:
•Spezielle Typinformation der einzelnen Objekte geht dabei verloren, da sie
als Object gespeichert werden.
•Um die Instanzen verwenden zu können, müssen auf den entsprechenden
Datentyp umgewandelt werden (Casting).
Collections sind “Behälter” für Objekte. Früher wurden dazu Arrays verwendet.
Collections haben Vorteile gegen¨uber Arrays:
• Gr¨osse muss nicht im Voraus bekannt sein
• Einfache Zugriffsmöglichkeiten
Natürlich ist es möglich, auch ohne die Java Collections eigene “Behälter” für
Daten zu implementieren. Aber Java Collections stellt viel benötigte Versionen
auf eine einfache Weise zur Verfügung.
Interfaces
• boolean add(E o)
Fügt das Objekt o hinzu und liefert true bzw. false, je nachdem, ob das Objekt
erfolgreich hinzugefügt werden konnte.
• void clear()
Löscht alle Elemente im Container.
• boolean contains(E o)
Liefert true, wenn das Objekt o im Container enthalten ist.
• boolean remove(E o)
Liefert true, wenn das Objekt o im Container gelöscht werden konnte.
• int size()
Liefert die Anzahl der Elemente.
• Iterator iterator()
Liefert ein Iterator Objekt zum Zugriff auf die Elemente des Containers.
Arten von Collections
• Set (HashSet, TreeSet,...)
• kann Elemente nicht doppelt enthalten
• schnelle Suche
• List (ArrayList, LinkedList, Vector, Stack,...)
• kann Elemente doppelt enthalten
• Elemente haben eine Reihenfolge
• variable Länge, schnelles Einfügen und Löschen
Verwendung von List (ArrayList)
List<Character> charlist = new ArrayList<Character>(); // neue Zeichen-Liste anlegen
charlist.add('a'); // Werte hinzufügen (mit Autoboxing)
charlist.add('b');
charlist.add('c');
for (Character c : charlist) {
System.out.println("Zeichen: " + c); // Jedes Zeichen auf die Konsole schreiben.
}
Sonderfall Map
• Map (Hashtable, TreeMap,...)
• schnelles Auffinden von Elemententen über einen key
• jedes Element muss einen eindeutigen key haben
• Map hat ein anderes Interface als Collections:
Object put(K key, Object V value);
Object get(K key);
Object remove(K key);
int size();
boolean isEmpty();
void putAll(Map t);
void clear();
public Set keySet();
Verwendung von Maps
import java.util.*;
class PointMap {
public static void main (String [] args) {
Map<String, Point> myPoints =
new Hashtable<String, Point>();
myPoints.put("BUBU", new Point(1.0,1.0));
// 1. einen Iterator auf das keySet erzeugen
Iterator<String> it = myPoints.keySet().iterator();
// 2. Keys durchgehen und Elemente aus der Map holen
while(it.hasNext()) {
String aKey = it.next();
Point b = myPoints.get(aKey);
System.out.println("Key: "+aKey +" Value: "+b);
}
}}
Generisch
Generische Programmierung wird in Java durch so genannte Generics ermöglicht.
Der Begriff steht synonym für „parametrisierte Typen“. Die Idee dahinter ist
zusätzliche Variablen für Typen, sog. Typ-Variablen, einzuführen. Diese
repräsentieren zum Zeitpunkt der Implementierung unbekannte Typen. Erst bei der
Verwendung der Klassen, Schnittstellen und Methoden werden diese Typ-Variablen
durch konkrete Typen ersetzt. Damit kann typsichere Programmierung gewährleistet
werden.
Beispiel:
Ein Programm verwendet eine ArrayList, um eine Liste von JButtons zu speichern.
Bisher war die ArrayList auf den Typ Object fixiert:
ArrayList al = new ArrayList();
al.add(new JButton("Button 1"));
al.add(new JButton("Button 2"));
al.add(new JButton("Button 3"));
al.add(new JButton("Button 4"));
al.add(new JButton("Button 5"));
for (int i = 0; i < al.size(); i++) {
JButton button = (JButton)al.get(i);
button.setBackground(Color.white);
}
Man beachte die notwendige explizite Typumwandlung (auch "Cast" genannt) sowie
die Typunsicherheit, die damit verbunden ist. Man könnte versehentlich ein Objekt in
der ArrayList speichern, das keine Instanz der Klasse JButton ist. Die Information
über den genauen Typ geht beim Einfügen in die Liste verloren, der Compiler kann
also nicht verhindern, dass zur Laufzeit bei der expliziten Typumwandlung von
JButton eine ClassCastException auftritt.
Mit generischen Typen ist in Java Folgendes möglich:
ArrayList<JButton> al = new ArrayList<JButton>();
al.add(new JButton("Button 1"));
al.add(new JButton("Button 2"));
al.add(new JButton("Button 3"));
al.add(new JButton("Button 4"));
al.add(new JButton("Button 5"));
for (int i = 0; i < al.size(); i++) {
al.get(i).setBackground(Color.white);
}
Beim Auslesen ist nun keine explizite Typumwandlung mehr notwendig, beim
Speichern ist es nur noch möglich, JButtons in der ArrayList al abzulegen.
Verfasser: Horvath
MVC (Model-View-Controller)
Model View Controller ist ein Architekturmuster zur Strukturierung von SoftwareEntwicklung in die drei Einheiten Datenmodell (engl. model), Präsentation (engl.
view) und Programmsteuerung (engl. controller).
Ziel des Musters ist es, einen flexiblen Programmentwurf zu machen, der eine
spätere Änderung oder Erweiterung erleichtert und eine Wiederverwendbarkeit der
einzelnen Komponenten ermöglicht. Darüber hinaus sorgt das Modell bei
umfangreichen Anwendungen für eine gewisse Übersicht und Ordnung durch
Reduzierung der Komplexität.
Die Bestandteile des MVC sind:
• Model/Modell – Das Modell enthält die darzustellenden Daten. Woher die
Daten kommen und wie diese zusammenhängen, spielt keine Rolle. So kann
es sich dabei um ein Datenmodell, Geschäftsmodell oder sogar um ein für die
Präsentation abstrahierte Modell handeln. Das Modell kennt weder die
Präsentation noch die Steuerung, es weiß somit nicht, wie, ob und wie oft es
dargestellt wird und verändert wird.
Je nach Anwendung müssen jedoch Änderungen im Modell beobachtbar sein,
beispielweise durch ein Observer-Entwurfsmuster. Es kann darüber hinaus
den View über Änderungen an den Daten informieren.
• View/Präsentation – Die Präsentation ist für die Darstellung der relevanten
Daten aus dem Modell zuständig. Sie beschafft die Daten aus dem Modell,
stellt sie dar und aktualisiert bei Änderungen im Modell entsprechend die
Darstellung. Je nach Design leitet sie auch Benutzeraktionen oder Events an
die Steuerung weiter.
• Controller/Steuerung – Die Steuerung verwaltet das Modell und die
Präsentation. Sie nimmt die Benutzeraktionen entgegen, wertet sie aus und
agiert entsprechend. Sie enthält die Intelligenz und steuert den
Ablauf(Workflow) der Präsentation.
Das MVC-Architekturmuster trifft keine Aussage über die Positionierung der
Geschäftslogik innerhalb der MVC-Klassen. Diese kann je nach Anwendungsfall
besser im Control-Modul aufgehoben sein oder in das Modell verlagert werden, wenn
z.B. mehrere Control-Module gibt.
Beispiel:
Die untere Abbildung zeigt das MVC-Modell für eine einfache Web-Registrierung.
Der Benutzer (Client) fragt als erstes die Seite register.jsp an. Er bekommt eine
Seite mit einem HTML-Formular als Antwort. Als Action ist im Formular die
validate.jsp angegeben. Also schickt der Browser nach dem Ausfüllen des
Formulars die eingegebenen Daten an das validate.jsp, das in diesem Fall das
Control-Modul ist und die eingegebenen Werte prüft. Es ist nur für die Prüfung und
Verarbeitung der Daten zuständig. Selbst gibt validate.jsp dem Benutzer kein
Feedback. Das Control-Modul gibt dazu die Kontrolle an die entsprechenden Views
weiter. In diesem Fall entweder an register.jsp, wenn die Eingaben ungültig waren,
sonst an die ok.jsp. Wird die Kontrolle wieder zurück an die register.jsp
übergeben, zeigt register.jsp dem User erneut das Formular mit z. B. einem
Fehler-Hinweis an. Der Browser schickt die korrigierten Daten wieder an die
validate.jsp. Sind die Eingaben korrekt, werden die Daten zur Speicherung an die
UsersBean übergeben. Die Kontrolle wird daraufhin an die ok.jsp abgegeben. Diese
zeigt dem User beispielsweise eine Erfolgsbestätigung.
Hier folgt nun ein einfaches Java Programmcode für die 3 Schichten:
Applikation
Die Applikation legt zuerst ein Objekt des Datenmodells an und dann ein Objekt der
Ansicht. Die Referenz auf das Datenobjekt wird der Ansicht im Konstruktor
übergeben. Die init-Methode der Ansicht wird gleich vom Konstruktor ausgeführt:
public class XxxApplication {
public static void main (String[] args] {
XxxModel model = new XxxModel();
String title = "Title String";
XxxView view = new XxxView (model, title);
}
}
Datenmodell (Model)
Das Datenmodell enthält die Datenfelder, die get- und set-Methoden für alle
Datenfelder, sowie Berechnungsmethoden, mit denen die Ergebnisse aus den
Eingabewerten berechnet werden:
public class XxxModel {
private type xxx;
...
public void setXxx (type xxx) {
this.xxx = xxx;
}
public type getXxx() {
return xxx;
}
...
}
Ansicht (View)
Die Ansicht baut in der init-Methode das graphische User-Interface auf. Das EventHandling wird jedoch nicht von dieser Klasse sondern von einem Objekt der
Steuerungsklasse oder von mehreren solchen Objekten durchgeführt. Dem
Steuerungsobjekt werden die Referenzen sowohl auf das Datenobjekt als auch auf
die Ansicht (this) als Parameter übergeben.
Im Konstruktor der Ansicht wird nicht nur der Title-String des Frame sondern auch
die Referenz auf das Datenobjekt übergeben. Die Datenfelder können dann mit den
get-Methoden des Datenmodells abgefragt und in den GUI-Komponenten graphisch
dargestellt werden.
Für komplexere Anwendungen ist es eventuell besser, dass sich alle View-Klassen
bei der zugehörigen Model-Klasse "registrieren". Eine Skizze dazu finden Sie weiter
unten am Ende dieses Beispiels.
Außerdem wird im Konstruktor auch gleich die init-Methode aufgerufen, die den
Frame komplett aufbaut.
Die Komponenten werden hier nicht als private sondern als protected oder packagefriendly deklariert, damit sie vom Steuerungsobjekt angesprochen werden können.
Die im Beans-Konzept verlangten set- und get-Methoden fehlen in dieser Skizze.
import java.awt.* ;
import java.awt.event.* ;
public class XxxView extends Frame {
private XxxModel model;
private XxxController controller;
protected TextField field1;
protected Label label1;
...
public XxxView (XxxModel m, String s) {
super(s);
this.model = m;
this.init();
}
public void init() {
controller = new XxxController (this, model);
setLayout ( new FlowLayout() );
field1 = new TextField (30);
field1.addActionListener (controller);
add(field1);
label1 = new Label ( model.getYyy() );
add (label1);
...
addWindowListener (controller);
setSize (300, 200);
setVisible (true);
}
}
Wenn das GUI aus mehreren Objekten besteht, werden dafür wiederum eigene
Klassen definiert. So werden z.B. für die graphische Darstellung von Datenfeldern
Subklassen von Canvas (oder dergleichen) verwendet, denen ebenfalls die Referenz
auf das Datenobjekt im Konstruktor übergeben wird.
Steuerung (Controller)
Die Steuerungsklasse implementiert die Listener-Interfaces und führt die
entsprechenden Aktionen durch. Ihr werden im Konstruktor die Referenzen sowohl
auf das Datenobjekt als auch auf das Ansichtobjekt übergeben. Sie kann dann je
nach den Benutzer-Aktionen die Datenfelder mit den set-Methoden des "Model"
setzen und die Anzeige der Daten und Ergebnisse mit den set- und repaintMethoden der GUI-Komponenten im "View" bewirken.
import java.awt.* ;
import java.awt.event.* ;
public class XxxController
implements YyyListener {
private XxxModel model;
private XxxView view;
public XxxController (XxxView v, XxxModel m) {
this.view = v;
this.model = m;
}
... // Methoden der Listener-Interfaces
}
Hier eine Beispielskizze, wie die Aktionen innerhalb der Methoden des ListenerInterface aussehen können:
public void actionPerformed (ActionEvent e) {
Object which = e.getSource();
if (which == view.field1) {
model.setXxx ( view.field1.getText() );
view.label1.setText ( model.getYyy() );
}
...
}
Verfassser: Horvath
JAVA vs. C#
Java ist eine objektorientierte Programmiersprache und als solche ein eingetragenes
Warenzeichen der Firma Sun Microsystems. Sie ist eine Komponente der JavaTechnologie.
Java-Programme werden in Bytecode übersetzt und dann in einer speziellen
Umgebung ausgeführt, die als Java-Laufzeitumgebung oder Java-Plattform
bezeichnet wird. Deren wichtigster Bestandteil ist die Java Virtual Machine (JavaVM), die die Programme ausführt, indem sie den Bytecode interpretiert und bei
Bedarf kompiliert (Hotspot-Optimierung).
Der Entwurf der Programmiersprache Java strebte im Wesentlichen fünf Ziele an:
Sie soll eine objektorientierte Programmiersprache sein.
Sie soll ermöglichen, gleiche Programme auf unterschiedlichen Computersystemen
auszuführen.
Sie soll eingebaute Unterstützung für die Verwendung von Computernetzen
enthalten.
Sie soll Code aus entfernten Quellen sicher ausführen können. Dieser Punkt wird
über das Sicherheitskonzept von Java erreicht, das aus drei Schichten besteht:
dem Code-Verifier (deutsch „Code-Überprüfer“), der sicherstellt, dass die JVM keinen
ungültigen Bytecode ausführen kann;
den Class-Loadern (deutsch „Klassenlader“), die die sichere Zuführung von
Klasseninformationen zur JVM steuern (diese ist dabei kein Interpreter, siehe unten);
den Security-Managern (deutsch „Sicherheitsverwalter“), die sicherstellen, dass nur
Zugriff auf Programmobjekte erlaubt wird, für die entsprechende Rechte vorhanden
sind.
Die erfolgreichen Aspekte bereits verbreiteter objektorientierter
Programmiersprachen wie C++ sollen auch für Java-Programmierer zur Verfügung
stehen.
C# greift Konzepte der Programmiersprachen Java, C++, SQL, C sowie Delphi auf.
C# zählt zu den objektorientierten Programmiersprachen und unterstützt sowohl die
Entwicklung von sprachunabhängigen .NET-Komponenten als auch COMKomponenten für den Gebrauch mit Win32-Applikationen.
Einige der Elemente von C++, die im Allgemeinen als unsicher gelten, wie
beispielsweise Zeiger, werden in C# nur für sogenannten „unsicheren Code“ erlaubt,
der in Zonen mit eingeschränkten Rechten (z. B. bei Programmen, die aus
Webseiten heraus ausgeführt werden) ohne die Zuteilung erweiterter Rechte nicht
ausgeführt wird.
Übersetzung
Interpretation
Codestrukturierung
Variablentypen
Mehrdimensionale
Arrays
Variablenübergabe
Objektorientierung
Interfaces
Zugriff auf Variablen
Polymorphie
Speichermanagement
enums
structs
GUIs
Indexer
JAVA
javac übersetzt Quellcode in
Java-Bytecode, JIT-Compiler
kann diesen zur Laufzeit in
Maschinencode übersetzen
Bytecode wird von der JRE
interpretiert
Die Klassen werden in
Packages eingeordnet, die
der Ordnerstruktur
entsprechen müssen. Diese
werden mit import importiert,
es wird dabei aber kein
Quellcode direkt in die Datei
eingebunden.
C#
csc übersetzt Quellcode in
IL-Code, JIT-Compiler kann
diesen zur Laufzeit in
Maschinencode übersetzen
IL-Code wird von der CLR
interpretiert
Die Klassen werden in
Namespaces eingeordnet.
Diese werden mit using
importiert, es wird dabei
aber kein Quellcode direkt in
die Datei eingebunden.
bool, sbyte, byte, char,
short, ushort, int, uint, long,
ulong, float, double, decimal
Mehrdimensionale Arrays
nur Arrays von Arrays
und Arrays von Arrays
möglich
möglich
by value, mit Parametern ref
by value
oder out auch by reference
Alles ist objektorientiert bis Alles ist objektorientiert bis
auf die primitiven
auf die primitiven
Datentypen. Diese werden
Datentypen. Diese werden
seit Java5 automatisch in
automatisch in ihre
ihre Wrapperklassen
Entsprechungen im SystemNamespace gecastet.
gecastet.
Existieren, können
Existieren, können Methoden Methoden, Properties,
und Konstanten enthalten
Indexer und Ereignisse
enthalten
Properties, die getter und
getter/setter-Methoden
setter Methoden implizieren
Standardmäßig
Kompilationszeitpolymorphie
Laufzeitpolymorphie (late
(early binding), mit den
binding)
Schlüsselwörtern virtual und
override auch late binding
Automatisch per Garbage
Automatisch per Garbage
Collector
Collector
ja (Seit Java 5)
Ja
Nein
Ja
Swing und AWT (One-Step- Windows Forms (One-StepCoding)
Coding)
Nein
Ja
boolean, byte, char, short,
int, long, float, double
Funktionszeiger
Reflection
Operatorüberladung
Nein
Ja
Nein
Nein, aber Delegates
Ja
Ja
Enums
Aufzählungen dienen zur automatischen Nummerierung der in der Aufzählung
enthaltenen Elemente. Die Syntax für die Definition von Aufzählungen verwendet das
Schlüsselwort enum (Abkürzung für Enumeration).
C#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
using System;
public enum DaysOfWeek{
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
}
public class Test{
public static bool isWeekDay(DaysOfWeek day){
return !isWeekEnd(day);
}
public static bool isWeekEnd(DaysOfWeek day){
return (day == DaysOfWeek.SUNDAY || day ==
DaysOfWeek.SATURDAY);
21.
}
22.
23.
public static void Main(String[] args){
24.
25.
DaysOfWeek sun = DaysOfWeek.SUNDAY;
26.
Console.WriteLine("Is " + sun + " a weekend? " +
isWeekEnd(sun));
27.
Console.WriteLine("Is " + sun + " a week day? " +
isWeekDay(sun));
28.
29.
/* Example of how C# enums are not type safe */
30.
sun = (DaysOfWeek) 1999;
31.
Console.WriteLine(sun);
32.
33.
}
34. }
---------------------------Java
1.
enum DaysOfWeek{
2.
SUNDAY,
3.
MONDAY,
4.
TUESDAY,
5.
WEDNESDAY,
6.
THURSDAY,
7.
FRIDAY,
8.
SATURDAY;
9.
10.
public boolean isWeekDay(){
11.
return !isWeekEnd();
12.
}
13.
14.
public boolean isWeekEnd(){
15.
return (this == SUNDAY || this == SATURDAY);
16.
}
17.
18. }
19.
20. public class Test{
21.
22.
public static void main(String[] args) throws Exception{
23.
24.
DaysOfWeek sun = DaysOfWeek.SUNDAY;
25.
System.out.println("Is " + sun + " a weekend? " +
sun.isWeekEnd());
26.
System.out.println("Is " + sun + " a week day? " +
sun.isWeekDay());
27.
}
28. }
Structs
Objekte werden am Stack statt am Heap angelegt (sind Werttypen).
+
speichersparend, effizient, belasten GC nicht
Lebensdauer auf Container eingeschränkt, nicht für dynamische
Datenstrukturen
public struct Koordinate
{
public double x;
public double y;
public double z;
public Koordinate(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
public override string ToString()
{
return(string.Format("x: {0}, y: {1}, z: {2}", x, y, z));
}
}
Indexer
Indexer ist eine spezielle Syntax für Überladung den Operator [] für eine Klasse. Ein
Indexers ist nützlich, wenn eine Klasse ein Container für eine andere Art des Objekts
ist. Indexer sind flexibel, in, dass Sie jeglicher Art, wie z. B. ganze Zahlen oder
Zeichenfolgen, als Indizes unterstützen. Es ist auch möglich, um Indexer zu erstellen,
die mehrdimensionales Array Syntax zu ermöglichen, wo eine mischen und
verschiedene Arten als Indizes übereinstimmen kann. Schließlich können Indexer
überladen werden.
Delegate
Ein Delegat ist ein Typ, der auf eine Methode verweist. Sobald einem Delegaten eine
Methode zugewiesen wird, verhält er sich genau wie diese Methode. Wie im
folgenden Beispiel kann die Delegatmethode wie jede andere Methode mit
Parametern und Rückgabewerten verwendet werden:
public delegate int PerformCalculation(int x, int y);
Jede Methode, die mit der aus dem Rückgabetyp und aus Parametern bestehenden
Signatur des Delegaten übereinstimmt, kann dem Delegaten zugewiesen werden.
Dies ermöglicht das programmgesteuerte Ändern von Methodenaufrufen sowie die
Integration von neuem Code in bereits vorhandene Klassen. Wenn Sie die Signatur
des Delegaten kennen, können Sie Ihre eigene delegierte Methode zuweisen.
Delegaten verfügen über folgende Eigenschaften:
•
Delegaten ähneln C++-Funktionszeigern, sind aber typsicher.
•
Delegaten ermöglichen es, Methoden als Parameter zu übergeben.
•
Delegaten können zum Definieren von Rückrufmethoden verwendet werden.
•
Delegaten können miteinander verkettet werden. So können beispielsweise
mehrere Methoden für ein einziges Ereignis aufgerufen werden.
Reflection
Die Möglichkeit, entdecken Sie die Methoden und Felder in einer Klasse, als auch
Aufrufen von Methoden in einer Klasse zur Laufzeit, in der Regel genannt Reflexion,
ist ein Feature von sowohl Java und c#. Der Hauptunterschied zwischen Reflexion in
Java versus Reflexion in c# besteht, dass Reflexion in c# auf Assemblyebene getan
wird, während Reflexion in Java auf Klassenebene getan ist. Da Assemblys in der
Regel in DLLs gespeichert werden, braucht man die DLL, die die gezielte Klasse
enthält, der in c# verfügbar sein, während in Java man die Klassendatei für die
gezielte Klasse zu laden können muss.
C#
Code kopieren
// Using GetType to obtain type information:
int i = 42;
System.Type type = i.GetType();
System.Console.WriteLine(type);
Die Ausgabe lautet:
System.Int32
JAVA
import java.lang.reflect.*;
import java.awt.*;
class SampleSuper {
public static void main(String[] args) {
Button b = new Button();
printSuperclasses(b);
}
static void printSuperclasses(Object o) {
Class subclass = o.getClass();
Class superclass = subclass.getSuperclass();
while (superclass != null) {
String className = superclass.getName();
System.out.println(className);
subclass = superclass;
superclass = subclass.getSuperclass();
}
}
}
Output:
java.awt.Component
java.lang.Object
Operatorüberladung
Um Operatoren in einer Klasse oder einer Struktur zu überladen, stellt C# das
Schlüsselwort operator zur Verfügung, das nur in Verbindung mit public
static verwendet werden darf. Hinter dem operator-Schlüsselwort wird der
Operator angegeben, der überladen werden soll.
+, -, !, ~, ++, --, true, false
Unäre Operatoren
+, -, *, /, %, &, |, ^, <<, >>
Binäre Operatoren
public static bool operator <=(GeometricObject geoObj1,
GeometricObject geoObj2) {
if(geoObj1.GetFlaeche() <= geoObj2.GetFlaeche())
return true;
return false;
}
Herunterladen