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; }