Deklarative und programmierte Oberflächen: Swing und JavaFX im Vergleich

Grundsätzlich können grafische Oberflächen über eine Programm-API aufgebaut werden oder in einer deklarativen Beschreibung spezifiziert werden.

· Programmierte Oberflächen: Der traditionelle Bau von grafischen Oberflächen in Java weist die Besonderheit auf, dass das Design der Oberfläche in Java-Code gegossen werden muss. Jede Komponente muss mit new erzeugt werden und mithilfe eines Layouts explizit angeordnet werden. Komplexe Oberflächen bestehen dann aus fast unwartbaren Mengen von Programmcode zum Aufbau der Komponenten, zum Setzen der Eigenschaften und Platzierung auf dem Container. Die Änderung des Layouts ist natürlich sehr schwierig, da mitunter auch für kleinste Änderungen viel Quellcode bewegt wird. In der Versionsverwaltung sieht das mitunter schrecklich aus.

· Deklarative Oberflächen stehen im Gegensatz zu den programmierten Oberflächen. Bei ihnen ist die Beschreibung des Layouts und die Anordnung der Komponenten nicht in Java ausprogrammiert, sondern in einer externen Ressourcen-Datei beschrieben. Das Format kann etwa XML sein, und spiegelt wieder, wie das Objektgeflecht aussieht. Eine Ablaufumgebung liest die Ressourcen-Datei und übersetzt die Deklarationen in ein Geflecht von GUI-Komponenten. Im Hauptspeicher steht dann am Ende das gleiche wie bei der programmierten GUI: ein Objekt-Graph.

Das andere Ufer: Microsoft erkannte ebenfalls die Notwendigkeit einer deklarativen Beschreibung von Oberflächen und nutzt intensiv XAML (Extensible Application Markup Language). Gleichzeitig gibt es leistungsfähige Tools und Designer für XAML. Die Firma Soyatec versucht sich mit eFace an einer Java-Realisierung (http://www.soyatec.com/eface/).

Gui-Beschreibungen in JavaFX

JavaFX unterstützt beide Möglichkeiten zum Aufbau von grafischen Oberflächen. Zum einen ist da die klassische API, die Knoten in einen Baum hängt, viel interessanter ist aber der deklarative Ansatz, der sehr schön Präsentation und Logik trennt. JavaFX selbst bietet eine Beschreibung auf XML-Basis, genannt FXML. XML ist selbst hierarchisch, kann also die grundlegende hierarchische Gliederung einer GUI in Containern und Komponenten sehr gut abbilden.

Neben FXML gibt es weitere proprietäre Beschreibungen und Mischformen. Eine davon ist FXGraph vom Projekt e(fx)clipse (http://www.eclipse.org/efxclipse/), einer JavaFX-Unterstützung in Eclipse. Die Beschreibung ist eine DSL[1] und definiert den Objekt-Graphen, der im Hintergrund in FXML umgesetzt wird. FXGraph ist kompakter als FXML und erinnert entfernt an JSON. Auch kann die JavaFX-API in alternativen Sprachen angesprochen werden, JavaScript und weitere Skriptsprachen wie Groovy (und der Hilfe von GoovyFX[2]) oder Scala (zusammen mit ScalaFX[3]) zeigen interessante Wege auf. Allerdings mischt sich dann doch wieder schnell die Deklaration der GUI mit Logik, was die Trennung zwischen Präsentation und Logik aufweicht. Es ist guter Stil, die Beschreibung der Benutzerschnittstelle und der Logik zu trennen, um auch Tests leichter zu realisieren.

Deklarative GUI-Beschreibungen für Swing?

Für AWT und Swing hat sich für deklarative Oberflächen in den letzten Jahren kein Standard gebildet, und Oberflächen werden heute noch so programmiert wie vor 15 Jahren. Dass Swing-Oberflächen immer programmiert werden müssen hält auf, auch wenn ein GUI-Builder heutzutage die Schmerzen minimiert. Über die WYSIWYG-Oberfläche wird in der Regel das Layout mit allen Komponenten zusammengeklickt und im Hintergrund erzeugt der GUI-Builder den Programmcode. Für die Laufzeitumgebung hat sich also nichts verändert, aber für uns schon.

Um auch in Swing in die Richtung von deklarativen Oberflächen zu kommen, gibt es unterschiedliche Open-Source-Lösungen, da Oracle nichts im Angebot hat.

· Swixml (http://www.swixml.org/) nutzt das XML-Format zur Beschreibung von GUIs und bildet jede Swing-Klassen auf ein XML-Element ab. Später nutzt Swixml dann SAX und JDOM, um die XML-Datei einzulesen und zu repräsentieren und um zur Laufzeit eine Swing-Komponentenbaum aufzubauen. Die Folien unter http://www.swixml.org/slides.html geben einen Einblick in die Möglichkeiten. Seit Mitte 2011 wird Swixml nicht mehr erweitert.

· Eine weitere Lösung zur deklarativen Beschreibung von Swing-Oberflächen bietet der Swing JavaBuilder (http://code.google.com/p/javabuilders/). Die Open-Source-Bibliothek steht unter der Apache-Lizenz und nutzt statt XML das kompaktere YAML-Format, dessen Schreibweise noch weiter verkürzt wurde. Das letzte Release stammt von Ende 2011, eine Weiterentwicklung ist unwahrscheinlich.

Die Inaktivität lässt entweder so erklären dass die Produkte perfekt sind, oder sich Entwickler mit den klassischen Code-generierenden GUI-Buildern anfreunden konnten, oder die Tools mit dem Aufkommen von JavaFX einfach unattraktiv werden.


[1] Eine domain-specific-language (DSL) ist eine „Spezialsprache“, die ein exklusives Format für einen klar abgegrenzten Anwendungsfall definiert.

[2] http://groovyfx.org/

[3] https://code.google.com/p/scalafx/

Geschichte von JavaFX: JavaFX 1, JavaFX 2, JavaFX 8

Ursprünglich wollte Sun/Oracle JavaFX als Flash-Ersatz im Internet positionieren, doch dafür ist die Kombination HTML5 + CSS3 + JavaScript zu attraktiv. JavaFX ist in erster Linie eine großartige GUI-Bibliothek für klassische Client-Anwendungen, die langsam auch auf mobile Endgeräte vorrückt. So nahm die Entwicklung auch unterschiedliche Richtungen an.

JavaFX ist schon sehr lange in Entwicklung und viele interne Swing-Entwickler wurden bei Sun/Oracle auf das Projekt angesetzt – umgekehrt passierte bei AWT/Swing nicht mehr viel, Bugfixes kommen aber immer noch brav. Im Jahr 2007 wurde JavaFX auf der SunOne-Konferenz vorgestellt, Ende 2008 erschien das Release JavaFX 1.0 zusammen mit der Programmiersprache JavaFX Script. Die besondere Sprache machte es einfach möglich, hierarchische Objektgrafen aufzubauen, und bot eine nette Syntax für Object-Binding, sodass Zustände synchronisiert werden konnten.

Im Oktober 2011 erschien JavaFX 2.0 mit vielen Neuerungen und auch Änderungen. So wurde JavaFX Script entfernt, denn Oracle wollte keine weitere Programmiersprache aufbauen, sondern eine pure Java API, die Entwickler dann von unterschiedlichen existierenden Skriptsprachen ansprechen können. Das ist sicherlich eine gute Entscheidung, denn unter JavaScript und Groovy[1] sieht das sehr schlank aus, fast wie mit JavaFX Script. Mit dem Update auf JavaFX 2.0 ändert sich auch die API, sodass alter Code angefasst werden musste. Heute ist JavaFX 1.x ist veraltet, genauso wie die Literatur.

JavaFX entwickelte sich immer mehr als Alternative zu Swing/AWT und so integrierte Oracle im August 2012 das Java FX 2.2 SDK und die Runtime in das JDK 7u6 and JRE 7u6. Der Schritt war ungewöhnlich, denn so große Ergänzungen waren bisher im JRE/JDK noch nie gemacht worden. Neben der Integration bewegte sich auch das ehemals geschlossene JavaFX in Richtung Open-Source und mündete in der Implementierung OpenJFX. Mit dem OpenJDK und OpenJFX lässt sich ein komplett freies Java-System mit GUI-Stack unter der GPL bauen, was strikte Linux-Distributionen freuen wird. Die Offenheit führte schon zu sehr spannenden Experimenten, etwa einer Java-Version für iOS.

Mit Java 8 zieht auch JavaFX 8 fest in die Distribution ein, der nächste Sprung mit 3D-Unterstützung.


[1] http://groovyfx.org/

Best Practice: Teste eine Web-Anwendung in jedem Browser

Negativbeispiel vom Vorzeigeunternehmen Ryanair beim Versuch online einzuchecken und die Board-Karte zu beziehen. Hier im IE:

ryanair

Beim gleichen Vorgang in Chrome:

Segments - Ryanair.com

Also gelingt es in beiden Browsern nicht, die Board-Karte zu bekommen! Die Antwort vom Kundendienst:

Bezugnehmend auf Ihre aktuelle Korrespondenz moechten wir Sie bitten auf unsere Webseite die neueste Version Ihres Browsers benutzen. Auβerdem empfehlen wir Ihnen den Browser Verlauf und die Cookies zu löschen.

Ehr ungewöhnlich, dass man den neusten Browser verwenden soll, viele Unternehmen zwingen sich selbst noch zu so alten Trümmern wie dem IE 6. Und der selbst-aktualisierende Chrome soll nicht aktuell sein? Es ist eher ein Cookie-Problem, was aber nicht beim Anwender liegt, sondern klar bei den Web-Entwicklern. Weil Ryanair also schlechte IT-Produkte hat, müssen Benutzer zu allen möglichen Tricks greifen, um das Problem zu lösen, und wenn das nicht gelingt, auch noch bei der kostenpflichten Hotline anrufen. So macht man auch Geld …

Test-Werkzeuge wie Selenium helfen.

Best Practice: Behebe technische Fehler schnell und professionell

Was im Grunde offensichtlich ist, wird von Ryanair beflissen auf die lange Bank geschoben. Vor ein paar Tagen hatte ich schon das schlechte Fehlerhanding kritisiert, diese Fehlermeldung ist noch übler:

PaymentSportsChange - Ryanair.com

Wenn man einen Dienst anbietet, hat man:

  • Fehler schnell zu beheben (dieser Fehler kommt seit mehren Tagen beim Versuch ein Gepäckstück hinzuzufügen),
  • dem Benutzer in der jeweiligen Landessprache zuerst die Fehlermeldung zu präsentieren (diese große Webseite hat neun lokalisierte Fehlermeldungen untereinander, die Italiener müssen also ganz nach untern scrollen),
  • hinter dem Link “Bitte warten Sie 10 Sekunden und wenn Sie nicht automatisch zurückgeleitet werden, klicken Sie hier.” auch eine Aktion verbinden, und nicht als nutzloses “<a href="#" id="A2">klicken Sie hier</a>” realisieren (JavaScript gibt es auch nicht, sonst könnte man das ja noch verstehen),
  • die Fehlermeldung in das Standard-Template einbetten und keine rohe HTML-Seite präsentieren.

Fazit: Eine schlechte Webseite und schlampige Programmierung kostet dem Interessenten nur Zeit (und damit Geld, war er über die günstigen Flüge ja eigentlich sparen wollte) und natürlich auch dem Unternehmen.

PS: Wer dieses Problem hat, sollte seine Cookies löschen bzw. ausnahmsweise einen anderen Browser nutzen. Als Chrome-Nutzer konnte ich im IE mein Gepäckstück hinzufügen. Die Entwickler von Ryanair habe also irgendwie ein Cookie-Problem.

IdentityHashMap

Es gibt eine besondere Datenstruktur mit dem Namen IdentityHashMap, die statt der internen equals(…)-Vergleiche einen Identitätsvergleich mit == durchführt. Die Implementierung ist selten im Einsatz, kann aber im Bereich der Performance-Optimierung eine interessante Rolle übernehmen und auch das Problem lösen, wenn in der Map denn absichtlich Objekte enthalten sein sollen, die equals-gleich, aber nicht identisch sind. Es lässt sich auch so sehen: IdentityHashMap ist attraktiv, wenn als Schlüssel Objekte zum Einsatz kommen, bei denen Gleichheit und Identität dasselbe bedeuten.

Hinweis: An Integer-Objekten in einer IdentityHashMap zeigt sich genau der Unterschied zur klassischen Map, wie einer HashMap. Nehmen wir

Integer key1 = 1;
Integer key2 = 1;
Integer key3 = 1000;
Integer key4 = 1000;

dann sind wegen dem Autoboxing und wegen dem internen Cache key1 == key2, aber key3 != key4 (die Integer-Klasse cached standardmäßig Ganzzahlen im Wertebereich eines byte). Anfragen mit equals-gleichen Integer-Objekten sind in der HashMap üblich, laufen aber bei IdentityHashMap ins Leere, da es unmöglich ist, zum Beispiel später über Integer.value(1000) ein genau identisches Integer-Objekte aufzubauen, sodass es als Schlüssel im IdentityHashMap „passt“ und der Identitätsvergleich wahr wird.

Algorithmen für Rastergrafik, 2. Einfache Füllalgorithmen

In der Computergrafik sind Objekte, die durch Linien und Punkte abgegrenzt sind selten. Vielmehr bekommen die Flächen eine Farbe oder ein Überzug von einer Textur. Ein ausgefülltes Objekt kann durch zwei Techniken entstehen. Zunächst einmal kann es sofort so konstruiert werden, dass es gefüllt erscheint, beispielsweise ergeben viele gleichförmige Linien ein Rechteck. Aber Objekte können nachträglich gefüllt werden. Dazu sind Begrenzungen nötig. Ein einfacher Algorithmus, der sich auf die Informationen des Bildschirmspeichers verlässt ist Seed-Fill. Die Idee von Seed-Fill ist einfach: Wir starten bei einem bekannten Punkt — dem Seed (zu deutsch Korn) — und geben dem Nachbarpixel die gleiche Füllfarbe, wenn dieser nicht die Begrenzungsfarbe besitzt. Der Algorithmus ist schnell rekursiv definiert. Wenn wir auf den Bildschirmspeicher zugreifen, so umschreiben wir dies mit einem Zugriff auf das Feld pixelArray. Und boundaryValue bezeichnet die begrenzende Farbe und fillValue die zu füllende Farbe:

void seedFill( int x, int y )
{
 if ( ( pixelArray[x,y] != boundaryValue ) && ( pixelArray[x,y] != fillValue ) ) {
  setPixel( x, y, fillValue );
  seedFill( x+1, y );
  seedFill( x-1, y);
  seedFill( x+1, y+1 );
  seedFill( x-1, y-1 );
 }
}

Die rekursive Implementierung krankt an dem Problem, dass der interne Stack sehr schnell ansteigt. Auch besitzt diese Implementierung den Nachteil, dass für jeden zu setzenden Punkt immer der Bildschirmspeicher ausgelesen wird und mehrere gleiche Punkte auf den Stack kommen und zurückgewiesen werden.

Eine mögliche Verbesserung erhalten wir dadurch, dass wir die direkten Rekursion eliminieren. Dadurch wird dieser Algorithmus aber nicht schneller, da das setzen eines Pixels vier Stack-Operationen nach sich zieht. Außerdem ist eine Implementierung wie die folgende schon deswegen nicht schneller, da bei der Stack-Implementierung der Objekt-Overhead mit sich gezogen wird. Daher dient folgender Algorithmus lediglich zur Anschauung, wie Flood-Fill mit einem Stack umgesetzt werden kann.

void seedFill( int xSeed, int ySeed )
{
 Stack s = new Stack();
 floodValue = pixelArray[xSeed, ySeed];
 s.push( xSeed );
 s.push( ySeed );
 while ( s.notEmpty() )
 {
  y = s.pop();
  x = s.pop();
  setPixel( x, y );
  if ( pixelArray[x+1, y] == floodValue ) {
   s.push( x+1 ); s.push( y );
  }
  if ( pixelArray[x-1, y] == floodValue ) {
   s.push( x-1 ); s.push( y );
  }
  if ( pixelArray[x, y+1] == floodValue ) {
   s.push( x ); s.push( y+1 );
  }
  if ( pixelArray[x, y-1] == floodValue ) {
   s.push( x ); s.push( y-1 );
 }
}

Überlegen wir, wodurch diese Implementierung ineffizient wird. Ein Grund haben wir schon erschlossen: jeder gesetzte Punkt wird mehrmals überprüft. Nehmen wir einen Punkt (x,y) heraus. Dann wird (x+1,y) auf den Stapel gesetzt aber mit diesen Koordinaten wird wiederum (x+1-1,y) überprüft. Eine verbesserte Variante vom Seed-Fill müsste sich daher einfach die Richtung merken und daraufhin die Rückrichtung nicht mehr gehen. Anstelle der Methode seedFill(…) treten nun vier Implementierungen an, für jede Richtung eine Funktion. Zur Demonstration sei seedFillLeft(…) aufgeführt.

seedFillLeft( int x, int y )
{
 if ( pixelArray[x, y] != boundaryValue ) {
  setPixel( x, y, fillValue );
  seedFillLeft( x+1, y );
  seedFillUp( x-1, y);
  seedFillDown( x+1, y+1 );
}

Wollen wir noch schnellere Füllalgorithmen benutzen, so kommen wir mit diesem Ansatz nicht weiter. Mit einer modifizierten Version des Scan-Line-Algorithmus ist dies aber zu schaffen.

Thema der Woche: Performant gedacht?

Diskutiere folgende Fragen:

Frage

richtig

falsch

Native Compiler können viel kompakteren Maschinencode erstellen.

   

n+n ist schneller als 2*n.

   

Anweisungen wie n/2, n/4, n*2 sollte man immer durch Verschieben nachbilden.

   

Für eine switch-Anweisung kennt der Compiler zwei unterschiedliche Umsetzungen in Bytecode.

   

Schleifen soll man immer runterzählen. Das ist viel schneller.

   

Über Weak-Reference verwaltet Objekte werden früher freigegeben, als über Soft-Reference verwaltete Objekte.

   

Synchronisierte Methoden sind nicht viel langsamer im Aufruf als nicht-synchronisierte Methoden.

   

Wenn man schnellen Zugriff auf ein Attribut haben muss, dann würde man auf die get()-Funktion verzichten und das Attribut zum Lesen freigeben. Das ist zwar kein Gutes Design, aber im Zugriff viel schneller.

   

Je tiefer die Vererbungshierarchie ist, desto langsamer ist eine Objekterzeugung.

   

Statische Eigenschaften sind im Zugriff immer viel schneller als Objekteigenschaften.

   

Wenn man intern eine Liste als Datenstruktur benutzt, man möchte diese aber sicher nach außen weitergeben (etwa durch getListe()), dann muss man die Liste immer kopieren.

   

Felder beliebiger Größe zu kopieren ist mit System.arraycopy() immer am schnellsten.

   

Ein String-Objekt aus einem StringBuffer zu erstellen kostet noch einmal Speicher für die gesamte Zeichenkette.

   

""+ganzzahl ist in nettes Idiom zur Konvertierung von Ganzzahlen in Strings, was auch in der Performance nicht schlecht ist.

   

intern() ist eine nützliche Funktion, die mehr Vorteile als Nachteile mit sich bringt.

   

charAt() von String ist schneller als charAt() von StringBuffer.

   

substring() erzeugt ein neues Zeichenfeld-Objekt, das genau den ausgeschnittenen Teil speichert.

   

Liest eine Schleife Zeilen aus einer Datei aus, so kann ein StringTokenizer gut benutzt werden, um die Zeilen zu untersuchen.

   

StringBuffer ist von der Performance ausgezeichnet.

   

Es spielt keine große Rolle, ob man einer HashMap eine Initialgröße gibt oder nicht.

   

Eine Menge von Wahrheitswerten sind in einem boolean-Array gut aufgehoben.

   

Ein BufferedOutputStream um einen beliebigen Stream bringt in jedem Fall einen Geschwindigkeitsvorteil.

   

Eine ArrayList kann viel kompakter serialisiert werden als eine LinkedList.

   

Mit java.nio kann man Dateien viel schneller kopieren als ohne.

   

Ein Server sollte eine Anfrage immer mit einem neuen Thread beantworten.

   

SELECT * FROM Tabelle ist unproblematisch.

   

Es ist egal, ob ich einen ResultSet mit getXXX(String Spaltenname) oder getXXX(int Spaltennummer) abfrage.

   

Für mehrere Anfragen an eine Datenbank ist immer ein PreparedStatement einzusetzen.

   

Eine JTable holt sich beim Scrollen jedes Element neu aus dem Model.

   

Wenn man SwingWorker durch FoxTrot ersetzt, gewinnt man noch einmal Geschwindigkeit bei Swing-Programmen.

   

Ein Obfuscator führt bei Applets zu einer Minimierung der Übertragungszeit.

   

Klausuraufgabe: JavaServer Pages und verwandte Technologien

Am 8. Feb. 2002 unterrichtete ich noch am Bildungszentrum für informationsverarbeitende Berufe e. V. (BIB) Informatik und insbesondere Java und stellte unter anderem diese Klausur. Wie würdet ihr abschneiden?

Aufgabe 1

Ja/Nein-Fragen. Kreuze die jeweils richtige Aussage an.

Frage

Ja

Nein

Ein Assoziativspeicher kann mit einem Schlüssel mehr als nur einen Wert verbinden.

   

Auf der Server-Seite haben wir es niemals mit Socket-Objekten zu tun.

   

Sei o eine Referenz-Variable für ein beliebiges aber gesetztes Objekt. Dann ist der Ausdruck o instanceof Object immer wahr.

   

Der Konstruktor von java.io.RandomAccessFile kann eine IOException werfen, die abgefangen werden muss.

   

Die Klasse java.io.File bietet eine Funktion readLine()

   

OutputStream und InputStream sind abstrakte Klassen

   

Das Skriptlet <%= out.println(2); %> wird vom JSP-Compiler zurückgewiesen.

   

Mit dem Objekt request kommt man an die Daten, die der Server an den Browser stellt.

   

Implizite Objekte müssen vom Benutzer erst erzeugt werden.

   

Bei Optionsfeldern kann es passieren, dass Parameter gleich benannt sind.

   

Ein Formular mit der GET-Methode erzeugt eine URL mit etwa folgendem Aufbau: http://horst/test.jsp&x=12?y=100

   

Eine Java Bean ist eine Klasse, die eine spezielle Schnittstelle implementieren muss.

   

Eine Java Bean ist eine Klasse, die von keine spezielle Klasse erben muss.

   

Sei a ein Attribut vom Typ int. Dann hat die Setze-Funktion die Signatur void setA( int i ).

   

JSP bietet eine Möglichkeit, dass Formular-Einträge automatisch in eine Bean eingetragen werden.

   

Mit einem Cookie kann man Informationen auf der Server-Seite hinterlegen.

   

Zum Einbinden von java.util und java.sql schreibt man die Direktive

<%! Include"java.util", "java.sql" %>

   

Ein Webkontext in der server.xml Konfigurationsdatei von Tomcat assoziiert einen beliebigen Server mit einem physikalischen Verzeichnis.

   

Erfragt man mit request.getParameter() einen nicht existierenden Parameter, wird eine NullPointerException ausgelöst.

   

Kleine Informationseinheiten können auf der Client-Seite des Benutzers abgelegt werden.

   

Aufgabe 2

Schreibe JSP-Code, welches eine Funktion bold() deklariert. Die Methode soll eine Zeichenkette s entgegennehmen und eine Zeichenkette zurückliefen, die den übergebenen String mit <b> und </b> umrahmt. Beispiel: Aus Peter Pan wird <b>Peter Pan</b>.

 

 

 

 

 

 

Nutze die Funktion ist einem JSP-Ausdruck.

<%=                                              %>

Aufgabe 3

Gegeben ist ein Formular mit einem Textfeld. Nach dem Abschicken wird eine URL mit dem Namen http://server.org/db/klein/send.jsp?name=paulchen formuliert. Wie sieht ein Skriptlet aus, welches den existierenden Namen ausliest?

<%

String name;

 

 

%>

Nun soll die Länge des Namens (die Länge eines Strings kann man mit der Funktion length() erfragen) ausgegeben werden. Was muss man schreiben, wenn es Fehler bei der URL gibt? Beachte nicht existierende Parameter und leere Parameter. Gibt es Fehler, soll die Länge –1 ausgegeben werden.

<%

String name; int len = -1;

 

 

 

 

%>

<%=                                   %>

Sonnenfinsternis in der Schule. Die Eclipse-IDE erfolgreich im Informatikunterricht einsetzen

Viel wurde über das Für und Wider von Java im schulischen Unterricht diskutiert. Fakt ist jedoch, dass heute viele Lehrpläne Java als Programmiersprache auflisten und neue Lehrkräfte an den Universitäten mit dieser imperative objektorientierte Programmiersprache in Kontakt kommen und sich schon aus diesem Grunde heute für Java entscheiden. Steht die Entscheidung für eine Programmiersprache fest, ist die Wahl der Entwicklungsumgebung (neudeutsch IDE für „Integrated Development Environment“) nicht einfacher. Im Allgemeinen steht als Basis für die Entwicklung von Java-Programmen das Java SE von Sun; es besteht aus Compiler und Laufzeitumgebung (JVM) und einer Reihe Tools. Das Java SE – früher J2SE – steht in zwei Spielarten zum Download bereit: Java Runtime Environment (JRE) und Software Development Kit (SDK), auch JDK genannt. (Sun macht es einen mit häufigen Umbenennungen nicht einfach.) Das JDK bildet für den Schulunterricht eine gute Basis, da die kompletten Quellen der Java-Typen beiliegen und diese von Eclipse für die API-Dokumentation benutzt werden.

Ohne zusätzliche Installation einer Entwicklungsumgebung ermöglicht das JDK sofort auf einem Windows oder Unix-System die Java-Entwicklung. Sun liefert die Kommandozeilenprogramme javac zum Übersetzen der Quellen und java zum Ausführen der Programme mit aus. Eine grafische Entwicklungsumgebung enthält das JDK oder JRE zwar nicht, doch da bei einem zeitgemäßen Betriebssystem ein Editor zur Standardinstallation gehört, ließe sich dieser zum Schreiben des Java-Quellcodes verwenden. Der Quellcode kann mit dem Editor erstellt und gespeichert werden und eine Kombination von javac und java auf der Kommandozeile kann zum Übersetzen und Ausführen der Programme genutzt werden. Das hat zwei Schwächen, wobei sich die erste auf die Betriebssysteme und Editoren bezieht. Unter Windows gibt es für Textdateien standardmäßig nur Notepad, was den Quellcodeautor durch fehlende farbliche Hervorhebung der Schlüsselworte und Zeichenketten nicht großartig unterstützt. Immerhin produzierte der Markt in den letzten Jahren qualitative (auch freie) Alternativen, etwa Notepad++. Unter Linux leben dann gleich mehrere Editoren nebeneinander. Der zweite Makel ergibt sich durch den Kontextwechsel Texteditor und Kommandozeile. Der Wechsel kostet Zeit und bedarf bei – bedauerlicherweise nicht auszuschließenden – Compilerfehlern einer lästigen Synchronisation zwischen den von javac angezeigtem Fehlerzeilen und dem Editor mit anschließender Fehlerbehebung.

Integrierte Entwicklungsumgebungen versuchen das Manko zu beheben, in dem sie die Vorgänge Editieren, Refactoring, Übersetzen, Ausführen und Debuggen näher zusammenbringen, also in einer Software integrieren. Soll eine IDE das Team Kommandozeile und Standard-Editor absetzen, folgt die nächste Frage: Welches Produkt? Einige Kriterien allgemeiner Art, ohne auf eine Programmiersprache im Besonderen einzugehen, sind:

  • Entfallen auf die Software Kosten, ist das eine Bremse für die Verbreitung. Idealerweise sollte die Software kostenlos sein, damit Lizenzkosten nicht anfallen. Kommerzielle Produkte könnten Lernende dazu verführen, die Software illegal zu kopieren – Lehrende sind davon prinzipbedingt auch nicht ausgeschlossen. Diese Form der Urheberrechtsverletzungen darf in keinem Fall geduldet werden.
  • Die Anforderungen an Hardware und Software (Betriebssystem) stimmt mit der Situation in den Schulen überein. Nutzen Schulen nicht Windows, sondern Linux oder MacOS, so muss die IDE auf dem System ohne Problem laufen.
  • Im besten Fall gibt es eine gute Programmdokumentation, die aufzeigt, welche Voraussetzungen die Software hat und wie sie sich starten, bedienen und konfigurieren lässt.

Für Java gibt es nach 15 Jahren Präsenz eine große Anzahl Entwicklungsumgeben. Für Industrieprogrammierer eignen sich allerdings nur vier wirklich: Eclipse (Eclipse Foundation), NetBeans (Sun Microsystems), JDeveloper (Oracle Corporation), IntelliJ (JetBrains). In der Entwicklergemeinde hat Eclipse die meisten Anhänger und ist die beliebteste IDE überhaupt. Aber rechtfertigt dies, auch im Unterricht die Lernenden mit Eclipse zu beschäftigen? Sollten nicht vielleicht Alternative in Betracht gezogen werden? Als Argumente für und gegen Eclipse lassen sich anführen:

  • Eclipse ist frei und quelloffen. Es gibt viele Zusätze – Plugins genannt – etwa für UML-Diagramme. Da Eclipse eine Rahmenwerk ist, welches auch andere Programmiersprachen unterstützt, die im Unterricht eine weitere Rolle spielen können, wie PHP, muss sich der Lernende nur in eine Umgebung eingewöhnen.
  • Eclipse läuft unter diversen Betriebssystemen, wie Linux, Mac OS und Windows. Die Installation und der Betrieb läuft ohne Eingriff in System-Dateien und ist auch auf Rechnern mit eingeschränkten Benutzerrechten möglich.
  • Die Hardwareanforderungen sind nicht unerheblich. Die Rechner sollten 128 MB Hauptspeicher haben und schneller als 500 MHz sein. Der Festplattenbedarf für ein Java-Projekt liegt inklusive einiger Eclipse-Zusätze bei etwa 500 MB.
  • Es gibt internationalisierte Versionen, insbesondere eine deutschsprachige Oberfläche.
  • Der erste Blick auf die gestartete Oberfläche ist mit etwa 20 Symbolen, 10 Menüzeilen und insgesamt 100 Menüeinträgen in der Java-Perspektive beunruhigend.
  • Die Oberfläche von Eclipse lässt sich flexibel anpassen und „entschlacken“, sodass nur noch wenige Symbole Einsteiger verwirren können.
  • Eclipse hat gegenüber anderen Umgebungen den Vorteil, dass der Editor besonders Spracheinsteigern hilft, sich mit der Syntax von Java „anzufreunden“. Dazu unterschlängelt Eclipse ähnlich wie moderne Textverarbeitungssysteme fehlerhafte Stellen.
  • Vom im Editor geschriebenen zum ausgeführten Programm liegt eine transparente Übersetzung mittels des im Hintergrund ablaufenden Java-Compilers. Eclipse stößt beim Speichern automatisch den Compiler an, und aktualisiert in dem Zyklus die Fehlermeldungen. Da die Fehleranzeige unterschiedlich aktuell ist, müssen Lernende die Aktualisierung der Anzeige verstehen.
  • Die IDE bietet notwendigen Hilfen beim Entwickeln, wie etwa automatische Codevervollständigung.
  • Eclipse setzt auf dem Java SDK auf und unterstützt jede gewünschte Java-Versionen.
  • Lernende könnten mit ihrem Wissen im Umgang mit Eclipse vor den Lehrenden sein, was im Unterricht immer eine Herausforderung ist. Nicht jeder Lehrende fühlt sich wohl in der Haut, wenn Schüler im Stoff weiter sind und unangenehme Fragen stellen, und kann den Wissensvorsprung seiner Lernenden produktiv im Unterricht einbringen.
  • Mit F1 ist die API-Dokumentation der Java-Typen und Eigenschaften in einem eigenen Unterfenster (in Eclipse View genannt) sofort zu sehen.

Die Eclipse IDE ist sehr leistungsfähig und die Einarbeitung kostet Zeit. Bei jeder Software steht der Lehrende vor dem Problem, eine Produktschulung zu geben, die unterschiedlich lang dauern kann. Eclipse benötigt eine längere Einarbeitungszeit als ein vergleichsweise einfacher Notepad, und die Zeit geht von der wertvollen Unterrichtszeit ab, die zur Vermittlung wichtiger Konzepte benötigt wird. Nach eigener Erfahrung als Java-Dozent mit drei Jahren Eclipse-Erfahrung in der Lehre kann ich Entwarnung geben, denn die Grundlagen zu Eclipse sind in 45 Minuten vermittelt und das erste „Hallo Welt“-Java-Programm implementiert.

Von den Vorteilen von Eclipse möchte ich zwei näher ausführen:

  • Die syntaktische Prüfung parallel zum Schreiben ist eine unglaubliche Hilfe. Vielen Lehrenden ist es schmerzlich bewusst, dass die wertvolle Unterrichtszeit mit der Vermittlung und Korrektur von syntaktischen Dingen wie Semikolon am Ende, geschweiften Klammen und ähnlichen Dingen dahinschmilzt. Mit roten Schlangenlinien unter fehlerhaften Stellen und einer Glühbirne zur automatischen Korrektur können Lernende leichter die Fehler erkennen und korrigieren. (Die Glühbirne ist aber Fluch uns Segen zugleich: Ein unreflektierter Gebrauch führt dazu, dass Fehler durch Varianten korrigiert werden, die Lernende nicht kennen, und ohne nachzudenken, irgendeine Lösung akzeptieren, ohne den Fehler selbst erkannt zu haben.)
  • Eclipse eignet sich für den Schulunterricht genauso wie für ernsthafte Entwicklung. Das ermöglicht fortschrittliches Arbeiten der Lernenden zuhause.

Problemorientierter Unterricht soll nicht heißen, dass die Lernenden laufend Probleme mit der IDE haben. Dennoch lässt sich Eclipse so vorbereiten, dass Schwierigkeiten minimiert werden. Dazu einige Grundlagen. Nach dem Download von Eclipse unter http://www.eclipse.org/downloads/ ist für Windows ein Zip-Archiv auszupacken. Im ausgepackten Ordner befindet sich das Hauptprogramm eclipse.exe. Nach dem Start muss ein Pfad für den Workspace vergeben werden; das ist ein Verzeichnis, in dem Eclipse die Projekte ablegen kann (aber nicht muss) und Konfigurationsdateien speichert. Einem Workspace können beliebige viele Java-Projekte zugeordnet sein, aber eine Eclipse-Instanz kann nur einen Workspace zu einer Zeit geöffnet haben. Wurde Eclipse zum ersten Mal gestartet, folgt ein Willkommens-Bildschirm (der geschlossen werden kann) auf der Arbeitsoberfläche – die Workbench genant wird. Für die Java-Programmierung muss ein neues Java-Projekt angelegt werden. Dazu ist im Menü File > New > Project > Java Project zu wählen. Nach der Angabe eines Projektnamens im Dialog lässt sich eine Java-Klasse in das Java-Projekt setzen. Dazu wird File > New > Class verwendet. Nach Eingabe eines Klassen- und Paketnamens kann optional der Schalter für einen Rumpf der main()-Methode aktiviert werden. Nach dem Abschluss des Dialoges öffnet sich ein Editor mit der neuen Klasse. An der Stelle ist interessant, den Begriff View zu erklären. Eine View ist alles das, was im Fenster im abgerundeten Bereich zu sehen ist. Dazu gehören: der Package-Explorer, die Outline, die Console, der Editor selbst. Welche Views gerade sichtbar sind, bestimmt die Perspektive. Es kann immer nur eine Perspektive zu einer Zeit sichtbar sein und welche das ist, ist in der Symbolleiste oben rechts abzulesen. Im Editor können Lernende jetzt das obligatorische System.out.println("Hallo Welt"); schreiben und mit der automatischen Fehlerprüfung experimentieren. Nach dem Speichern und ohne rote Kringel ist das Programm korrekt. Um es auszuführen bietet Eclipse mehrere Möglichkeiten. Eine ist, im Editor das Kontextmenü aufzurufen und unter Run As > Java Application zu wählen. Die Ausgabe steht im Konsolenfenster.

clip_image002

Abbildung 1: Eclipse mit Fehleranzeige

Die genannten Schritte zum lauffähigen Beispiel sind zu vollziehen, wenn man mit einer neuen Installation von Eclipse beginnt. Für den Unterricht können diverse Dinge vorbereitet werden:

  • Eclipse ist schon ausgepackt und ein Symbol zum Starten von Eclipse ist im Menü des grafischen Betriebssystems eingetragen.
  • Eclipse wurde schon gestartet und ein Workspace zugewiesen. Der Schalter bei der Angabe des Workspace-Pfades wurde gesetzt, dass der Dialog nicht immer wieder kommt. Der Willkommens-Schirm ist schon geschlossen.
  • Im Workspace gibt es ein leeres Java-Projekt. Idealerweise trennen src/– und bin/-Order die Java-Quellcode-Dateien und die Java-.class-Dateien.
  • Das Java-Projekt enthält ein Beispielprogramm mit der Konsolenausgabe, was nur gestartet werden muss.

Haben Lehrende dies mit einer Eclipse-Installation schon gemacht, und etwa das Workspace-Verzeichnis unter das Eclipse-Installationsverzeichnis gelegt, kann dieses Eclipse-Verzeichnis einfach auf andere PCs übertragen werden. Die PCs „erben“ damit diese Standardeinstellungen.

Neben der Möglichkeit, ein erstes Projekt schon direkt eingestellt zu haben, ist weiterhin interessant, die Oberfläche zu vereinfachen. Dazu können einige Views wie Hierarchy oder JavaDoc geschlossen werden. Was genau in eine Perspektive für Views eingerichtet sind, und was zum Teil für Menüpunkte eingeblendet sind, bestimmen Einstellungen unter Window > Customize Perspective…

Eclipse bietet eine Reihe von Möglichkeiten, die jeden Informatikunterricht bereichern. Dazu zählen insbesondere die Refactoring-Möglichkeiten, um Bezeichner umzubenennen oder automatisch Schnittstellen aus Java-Klassen zu extrahieren. Die IDE ist sehr leistungsfähig, mächtiger als das, was im Unterricht je angesprochen werden könnte. Dennoch finden Nutzer immer wieder neue Kniffe, die sie gerne mit anderen teilen und die sie zum Erforschen anregen. Insbesondere die Glühbirne, die Fehler automatisch korrigiert, hat es Lernenden meiner Erfahrung nach angetan. Zu den Eclipse-Erweiterungen – den Plugins – gehören Produkte wie das Eclipse Web Tools Platform (WTP) das im Bündel Eclipse IDE for Java EE Developers eingebracht ist, die Eclipse erweiterte Funktionalität im Bereich XML, Datenbanken oder Web-Entwicklung geben. Mit dem WPT lassen sich etwa XML-Dokumente mit syntaktischer Hervorhebung und paralleler Fehlerprüfung schreiben, Datenbankverbindungen aufbauen und Tabellen visualisieren und einfache Web-Anwendungen mit JavaServer Pages schreiben. Das auch frei verfügbare Plugin Omondo (http://www.omondo.com/) gibt Einblicke in die UML-Entwicklung und kann Klassendiagramme mit Quellcode synchron halten.

DESIGN PATTERN kompakt

Grundlegende Pattern

Schnittstellen
Interfaces

Schnittstellen definieren die Operationen einer Klasse, und schreibt Verhalten vor, welches die Objekt dann zeigen.

Delegate
Delegate

Delegiere die Arbeit an ein anderes Objekt. Erweitere die Funktionalität einer Klasse nicht durch Vererbung, sondern durch Assoziation.

Markierungsschnittstelle
Marker-Interface

Durch die Implementierung einer Schnittstellen, die keine Operationen vorschreibt, wird das Objekt markiert und bekommt somit einen Typ.

Immutable
Abstract Factory

Die Zustände eines Objekts lassen sich nicht mehr verändern. Unveränderbare Objekte.

Typsicherer Aufzählungen
Lasse Konstanten typsicher sein, dass keine Verwechselungen der Reihenfolge, und der Inhalte auftreten.

Null-Objekt
Null object

Implementiere die Schnittstelle ohne Funktionalität.

Objekt-Pool
Object Pool

Halte teuer zu erzeugende Objekte in einem Objekt-Pool.

Erzeuger-Pattern (Creational Pattern)

Abstrakte Fabrik
Abstract Factory

Stelle eine Schnittstelle bereit, um eine Familie von verwandten Objekten zu erzeugen, ohne die konkreten Klassen zu offenbaren.

Erbauer
Builder

Trenne die Erzeugung und die Repräsentation eines komplexeren Objektes, so dass sich die Konstruktion auch mit anderen Repräsentationen durchführen lasst.

Fabrikmethode
Factory Method

Stelle für die Erzeugung von Objekten eine Schnittstelle bereit, lass aber eine Fabrik entscheiden, welche Unterklassen erzeugt wird.

Prototyp
Prototype

Lege durch eine Prototyp fest, welche Objekten zu erzeugen sind und erzeuge neue Objekte als Kopie dieses Prototyps.

Singleton
Stelle sicher, dass es nur ein Exemplar eine Klasse gibt. Dieses lässt sich durch eine statische Funktion erfragen.

Struktur-Muster (Structural Pattern)

Adapter
Passe die Schnittstelle eine Klasse auf die Schnittstelle einer anderen Klasse an. So können Klassen zusammenarbeiten, die sonst wegen inkompatibler Schnittstellen nicht miteinander zusammenarbeiten könnten.

Brücke
Bridge

Löse die Abstraktion von der Implementierung, so dass beide unabhängig verändert werden können.

Kompositum
Composite

Zusammengesetzte Objekte bilden über eine Teil/Ganzes-Beziehung eine Baumstruktur. Die Blätter und Knoten sollen gleich behandelt werden können.

Dekorator
Decorator

Ein Objekt kann (auch dynamisch) weitere Verantwortlichkeiten enthalten.

Fassade
Facade

Stelle eine einfache Schnittstelle für ein komplexes System bereit. Damit wird das tiefer liegende System einfacher zu nutzen.

Fliegengewicht
Flyweight

Behandle eine große Menge kleiner Objekte effizient, in dem unveränderliche Objekte gemeinsam genutzt werden.

Proxy
Stelle einen Stellvertreter für ein Objekt bereit.

Verhaltsmuster  (Behavioral Pattern)

Verantwortungskette
Chain Of Responsibility
Gehe eine Kette von Objekten durch und frage sie, ob sie eine Anfrage annehmen. Entkopple dadurch Sender und Empfänger.

Kommando
Command
Eine Operation wird als Objekt verpackt.

Interpreter
Definiere zu einer Sprache eine Grammatik und baue einen Interpreter, der Sätze der Sprache erkennt.

Iterator
Ermögliche ein sequenzielles Ablaufen einer Datensammlung ohne die interne Repräsentation der Daten freizugeben.

Vermittler
Mediator

Entkopple Objekte, damit sie sich nicht mehr direkt kommunizieren müssen.

Memento
Speichere ohne Freigabe der inneren Struktur den internen Zustand eines Objektes um ihn später wieder Herstellen zu können.

Beobachter
Observer
Definiere eine 1:n-Beziehung zwischen Objekten, so dass bei einer Zustandsänderung die Interessenten automatisch informiert werden.

Zustand
State
Erlaube bei einer Änderung des Zustandes eine Änderung des Verhaltens.

Strategie
Strategy
Definiere für jeden Algorithmus (Strategie) eine Klasse, die dann später vom Klienten genutzt werden kann.

Schablonenmethode
Template Methode

Definierte das Skelett einer Anwendung durch eine Reihe von Operationen und verschiebe die Implementierung der Operationen auf die Unterklassen.

Besucher
Visitor
Trenne Algorithmus und Datenstruktur voneinander, sodass sich insbesondere Verhalten anpassen lässt, ohne die Klasse für die Datenstruktur anfassen zu müssen.

Links

Was ist eigentlich kein Pattern?
Bjarne Stroustrup, OOPSLA 95

Der Report-Generator JasperReport

JasperReport ist eine weit verbreitete Open-Source-Software unter der LGPL-Lizenz zum Erstellen von Reports. Auf der Basis einer Vorlagendatei im XML-Format setzt JasperReports die Eingabe, die etwa aus einer relationalen Datenbank stammt, in eine Ausgabe um. Als Ausgabeformate unterstützt der Generator drei Typen von Ausgaben: Text-/Zeichenbasierte Dokumente HTML, RTF, CSV, TXT und XML, Dokumente in den Binärdateiformaten PDF, XLS und Export auf ein Grafik-Gerät (Graphics2D und Java Print Service). Der Unterschied ist, dass HTML, XLS und CSV nur eingeschränkte Positionierungen erlauben und nicht wie die anderen Formate pixelgenaue Boxen.

JasperReport greift auf weitere Open-Source-Bibliothek zurück, etwa auf iText (http://itextpdf.com/) für die PDF-Ausgabem, JFreeChart (http://www.jfree.org/jfreechart/) für diverse Diagramme oder Apache POI zu Generieren von Excel XLS Dokumenten (dann aber ohne Charts). Rund um JasperReport (http://community.jaspersoft.com/project/jasperreports-library) ist ein Ökosystem mit weiteren Tools entstanden. JasperReports wird zusammen mit anderen Tools von der JasperSoft Corporation (http://www.jaspersoft.com/) entwickelt. Das Unternehmen hat weiterhin die freie Swing-Anwendung iReport (http://community.jaspersoft.com/project/ireport-designer) zur Erstellung von JasperReport-Vorlagendateien im Portfolio. JasperAssistant (http://www.jasperassistant.com/) ist ein Beispiel für eine kommerzielle Software (129 US Dollar für eine Lizenz), um die Vorlagendateien grafisch in Eclipse zu erstellen.

Ein Report besteht bei JasperReport aus so genannten Bands, die auch Report-Sektionen genannt werden. Es lassen sich drei Typen von Sektionen ausmachen:

  • Sektionen, die nur genau einmal im Report auftauchen können, wie der Titel am Anfang, oder eine Zusammenfassung am Ende.
  • Sektionen, die auf jede Seite stehen können, wie die Seitenkopf- und Fußzeile.
  • Eine Detail-Sektion, die einer Tabelle mit Einträgen aus Datenquellen wie Datenbanken ähnelt.

Alle Bands haben eine feste Reihenfolge und eine benutzerdefinierte Höhe und Breite. Die Reihenfolge der Segmente ist: Hintergrund, Titel, Seitenkopf, Spaltenkopf, Gruppierungskopf, Detail, Gruppierungsende, Spaltenende, Seitenende, letztes Seitenende, Zusammenfassung.

Die Elemente dieser Bands nennen sich Report-Objekte. JasperReport definiert zwei Textelemente, vier grafische Objekte und einen Sub-Report – ein Sub-Report ist ein im Report eingebetteter Unter-Report. Dass es als Report-Objekte zweimal Textfelder gibt, liegt daran, dass JasperReport zwischen statischem Text, etwa für einen festen Spaltennamen, und einem dynamischen Text unterscheidet. Der dynamische Text kann sich während der Reporterstellung ändern, wenn etwa ein Feld aus einer Datenbank stammt oder die Seitenzahl angezeigt wird. Zu den grafischen Report-Objekten zählen Linie, Rechteck (auch abgerundet) und Ellipse. Alle weisen eine Vielzahl von Eigenschaften auf, wie Strichtypen, Dicken und Farben. Dazu kommen Bilder, die gewünscht skaliert und positioniert vom Datensystem, Datenbank oder über das Netzwerk kommen können.

Während bei statischen Textelemente sich der Werte nicht ändern, stellen dynamischen Textelemente so genannte Ausdrücke dar. Ein Ausdruck wird zur Laufzeit berechnet und als String in den Report gesetzt. Der Ausdruck kann auf verschiedene Datenspeicher zurückgreifen:

  • Parameter. Die Parameter sind Werte aus einer java.util.Map, die vor dem Aufbau eines Reports initialisiert wurde.
  • Felder. Sie ergeben sich aus Einträgen einer Datenquelle, wie der Datenbank oder einer Sammlung von Hibernate-Objekten.
  • Einträge aus einem Resource-Bundle.
  • Variablen. Während des Reports kann eine neu definierte Variable einen Wert zwischenspeichern. JasperReport bietet einige vorbelegte Variablen, zum Beispiel für die aktuelle Seite oder die aktuelle Spalte in der Verarbeitung.

Die Ausdrücke können mit den bekannten Operatoren aus Java zu komplexeren Ausdrücken zusammengesetzt werden, denn JasperReport erlaubt die gesamte Syntax der Sprache Java. Variablenzugriffe stehen in einer besonderen Notation: $P{name}, $F{name}, $K{name} und $V{name}.

Eine interessante Alternative ist Eclipse BIRT (http://www.eclipse.org/birt/phoenix/).

Fragen zur Java-Zertifizierung, Standardklassen

Frage

Was ist die Ausgabe des folgenden Programmstücks bei der Ausführung?

int i = 3;
int j = 0;
float k = 3.2F;
long m = -3;
if ( Math.ceil(i) < Math.floor(k) )
  if ( Math.abs(i) == m )     System.out.println( i );
  else     System.out.println( j );
else   System.out.println( Math.abs(m) + 1 );
  1. -3
  2. 0
  3. 3
  4. 4
  5. keine dieser

Die Ausgabe ist 4.

Frage

Welche der folgenden sind gültige Objektmethoden der Klasse String?

  1. equals( String )
  2. equals( Object )
  3. trim()
  4. round()
  5. toString()

equls(Object) und toString() sind zwei Objekte, die von Object kommen. Also sind B und E richtig. Eine equals() Funktion mit der Parameter String gibt es nicht und muss es auch nicht geben. Also ist A falsch. Denn da equals(Object) für alle Objekt gilt, gilt im besonderen auch equals(String). Da die String Klasse equals() überschreibt, wird schon darauf geachtet, dass keine beliebigen Objekte verglichen werden. trim() schneidet Leerzeichen vor und hinter der Zeichenkette ab, auch C ist richtig. Eine Rundungsfunktion ist bei String Objekten nicht nötig und möglich. round() ist eine Funktion der Klasse Math. Also ist D falsch.

Frage

Welche Anweisung ergibt den Wert -4.0?

  1. Math.floor( -4.7 )
  2. Math.round( -4.7 )
  3. Math.ceil( -4.7 )
  4. Math.min( -4.7 )

Antwort A und B erzeugen die Ausgabe -5 sind also falsch. Antwort C erzeugt die entsprechende Ausgabe. Lösung D kann nich kompiliert werden,da die min() Funktion zwei Parameter erwartet.

Frage

Was schreibt das Programm auf den Schirm?

String s = "Ulli"; 
int begin = 1;
char end = 3;
System.out.println( s.substring(begin, end) );
  1. Ull
  2. ll
  3. lli
  4. error: no method matching substring(int,char)

B) ll

Frage

Welche Operationen sind bei den gegeben Deklarationen erlaubt?

String s1 = new String("Hello")

String s2 = new String("there");

String s3 = new String();

  1. s3 = s1 + s2;
  2. s3 = s1-s2;
  3. s3 = s1 & s2
  4. s3 = s1 && s2

A s3 = s1 + s2;

Frage

Gegeben ist das folgende Programm:

import java.util.Date;

public class Datum {
  public static void main( String args[] ) {
    Date d1 = new Date( 99, 11, 31 );
    Date d2 = new Date( 99, 11, 31 );
    foo( d1, d2 );
    System.out.println( "d1 ist " + d1 + "\nd2 is " + d2 );
  }
  public static void foo( Date d1, Date d2 ) {
   d2.setYear( 100 );
   d1 = d2;
  }
}

Welche Aussage trifft auf das Programm zu?

  1. Das Programm kompiliert und die Ausgabe ist

    d1 ist Fri December 31 00:00:00 GMT 1999

    d2 ist Fri December 31 00:00:00 GMT 1999

  2. Das Programm kompiliert und die Ausgabe ist 
    d1 ist Fri December 31 00:00:00 GMT 1999

    d2 ist Sun December 31 00:00:00 GMT 2000

  3. Das Programm kompiliert und die Ausgabe ist 
    d1 ist Sun December 31 00:00:00 GMT 2000

    d2 ist Sun December 31 00:00:00 GMT 2000

  4. Die Anweisung ‚d1 = d2‘ wird vom Compiler zurückgewiesen, da Date den Gleich-Operator nicht überladen hat.
  5. Der Ausdruck (d1 is " + d1 + "\nd2 is " + d2) wird vom Compiler zurückgewiesen, da die Date Klasse den Plus-Operator nicht überladen kann.

Das Programm lässt sich korrekt kompilieren, da keine Zeile fehlerhaft ist. Daher sind Antwort D und E falsch. Mit dem Gleichheitszeichen werden lediglich die Referenzen unterschieden und das ist immer erlaubt. Auch der Plusoperator bereitet keine Probleme, da in im Zusammenbauen eines Ausgabestrings auf die Date-Objekte immer toString() aufgerufen wird. Fast alle Objekte nutzen diese Methode zur Identifikation ihrer inneren Werte. Nun bleiben zwei Dinge interessant. Zunächst einmal der Konstruktor mit den Werten und die Methode foo(). Der Konstruktor ist in Ordnung und in der Ausgabe sind Stunde, Minute und Sekunde auf Null, da sie von uns nicht gesetzt wurde. Werfen wir einen Blick auf foo(). Es ändert im ersten Objekt das Jahr auf 100. Aber da die Zuweisung nur lokal wirkt, ändert sich d2 nicht und es ist ein klassischer Programmierfehler. Lokale Variablen haben keine Außenwirkung. Nur im Block der Methode ist nun d1 mit d2 identisch, da d2 auf d1 zeigt. So sind auch die Jahreszahlen gleich. Aber nach dem Verlassen der Methode verschwindet auch die Zuweisung, nur die Änderung in d2 bleibt gültig. d1 behält das Jahr 99. Also ist C falsch. Es bleiben noch A und B als Lösungen. Nun muss man wissen, dass setYear(100) das Jahr tatsächlich auf  2000 setzt. Somit ergibt sich als richtige Lösung nur B, da d1 und d2 die Jahre 1999 und 2000 beschreiben.

Fragen zur Java-Zertifizierung, Threads

Frage

Welche Aussage ist wahr für die Funktion notify() in Verbindung mit wait()?

  1. Wenn mehr als ein Thread auf eine Bedingung wartet, wird nur der Thread benachrichtigt, der am längsten wartet.
  2. Wenn mehr als ein Thread auf eine Bedingung wartet, läßt sich nicht ausmachen, welcher Thread benachrichtigt wird.
  3. notify() ist in der Klasse Thread definiert.
  4. Es ist nicht unbedingt erforderlich, den Lock für das Objekt zu besitzen, dessen notify() Methode wir aufrufen.
  5. notify() sollte nur innerhalb einer while Schleife aufgerufen werden.

Frage

Gegeben sei die folgende Klasse

class Counter {
 public int start = 1;
 public int end = 100;
 public static void main( String args[] ) {
  new Counter().go();
 }
 void go() {
  // A
  Thread t = new Thread( a );
  t.start();
 }
}

Welcher Block kann in der Zeile A eingesetzt werden, so dass das Programm von start bis end zählt?

  1. Runnable a = new Runnable() {

    public void run() {for ( int i = start; i <= end; i++ )

    System.out.println( i );

    }

    }

  2. a implements Runnable {

    public void run() {for ( int i = start; i <= end; i++ )

    System.out.println( i );

    }

    }

  3. Thread a = new Thread() {

    public void run() {for ( int i = start; i <= end; i++ )

    System.out.println( i );

    }

    }

Frage

Analysiere das folgende Programm:

class WasPassiert implementes Runnable
{
 public static void main( String args[] )
 {
  Thread t = new Thread( this );
  t.start()
 }
 public void run()
 {
  System.out.println( "Schnickschnack" );
 }
}
  1. Das Programm lässt sich nicht kompilieren
  2. Das Programm lässt sich kompilieren, aber nichts erscheint auf der Standardausgabe
  3. Das Programm lässt sich kompilieren und das Wort „Schnickschnack“ erscheint einmal auf der Standardausgabe.
  4. Das Programm lässt sich kompilieren und das Wort „Schnickschnack“ erscheint ununterbrochen auf der Standardausgabe, bis der Benutzer mit Control-C das Programm beendet.

Frage

Was muss für die Klasse RunHandler gelten, so dass eine Instanz von RunHandler wie folgt benutzt werden kann?

class Test  
{
 public static void main( String args[] )
 {
  Thread t = new Thread( new RunHandler() );
  t.start();
 }
}
  1. RunHandler muss das Interface java.lang.Runnable implementieren.
  2. RunHandler muss die Klasse Thread erweitern.
  3. RunHandler muss eine Methode run() anbieten, die öffentlich ist und void zurückgibt.
  4. RunHandler muss eine init() Methode anbieten.

Fragen zur Java-Zertifizierung, Überladen, Überschreiben, Typen zur Laufzeit und Objektorientierung

Frage

Das Design einer Klasse erfordert, dass eine Instanzvariable von Unterklassen verändert werden kann. Klassen, die allerdings im gleichen Paket liegen, sollen keinen Zugriff haben. Wie muss der Modifier gewählt werden, so dass dies möglich wird?

  1. public
  2. private
  3. protected
  4. Kein besonderer Modifier.
  5. private und die es sollte Zugriffsmethoden geben.

Public würde die Variable für alle sichtbar machen, daher ist A falsch. Private würde die Variable auch für Unterklassen unsichtbar machen, daher ist auch B falsch. C ist korrekt, da die Variable von erbenden Klassen geändert werden kann. Gibt man keinen Modifier an, so können auch Klassen im gleichen Paket (also Verzeichnis) die Variable verändern. Dies ist aber in der Aufgabe verboten. So ist auch D falsch. Obwohl Zugriffsmethoden im Design einer Klasse zu einer schöne Modellierung führt, verbieten diese jedoch nicht, dass nur Methoden der Unterklassen auf die Variablen zugreifen können. Also ist auch E falsch.

Frage

Was passiert, wenn folgende Klasse compiliert und ausgeführt wird?

class Test
{
  static int myArg = 1;
  public static void main( String args[] )
  {
    int myArg;
    System.out.println( myArg );
  }
}
  1. Der Code lässt sich kompilieren und bei der Aufführung erscheint 0 auf dem Bildschirm.
  2. Der Code lässt sich kompilieren und bei der Aufführung erscheint 1 auf dem Bildschirm.
  3. Dieses Programmstück lässt sich nicht kompilieren, da eine lokale Variable nicht den gleichen Namen besitzen darf, wie eine statische Variable.

  4. Dieses Programmstück lässt sich nicht kompilieren, da die lokale Variable vor der Nutzung noch nicht initialisiert ist.

Der Programmcode wird nicht kompiliert, da myArg nicht initialisiert wurde, Antwort D ist korrekt. A ist falsch, da lokale Variablen nicht automatisch mit 0 initialisiert werden. B ist falsch, da der Zugriff erst auf innere Variablen erfolgt.

Frage

Welches der folgenden Code Fragmente ist geeignet, um die Anzahl der Argumente, die einer Java Applikation übergeben werden, in count zu speichern. Die Deklaration der main() Funktion ist wie folgt:

public static void main( String args[] )
{
  // Hier den Block einsetzen
}
  1. int count = args.length;
  2. int count = args.length – 1;
  3. int count = 0;

    while ( args[count] != null )

      count++;

  4. int count = 0;

    while ( !(args[count].equals("") ) )

      count++;

A ist korrekt. B ist falsch, da in Java der Name des Programmes nicht im ersten Argument steht und daher der Zähler nicht erniedrigt werden muss. Da alle Argumente gültige Strings sind, kann C nicht richtig sein. Leerstring können ebenso nicht in der Folge vorkommen, daher ist auch D falsch.

Frage

Analysiere die folgenden beiden Klassen:

final class First
{
  static int a = 3;
}
class Second extends First
{
  public void method() {     System.out.println( a );
  }
}
  1. Die Klasse First lässt sich kompilieren, aber nicht die Klasse Second.
  2. Die Klasse Second lässt sich kompilieren, aber nicht die Klasse First.
  3. Keine der beiden Klassen lässt sich kompilieren.
  4. Beide Klassen lassen sich kompilieren und wenn method() aufgerufen wird, schreibt es 3 auf die Standardausgabe.
  5. Beide Klassen lassen sich kompilieren, aber ein Aufruf von method() mündet in eine Exception.

Die Klasse First lässt sich problemlos kompilieren. Die Kompilation der Klasse Second scheitert jedoch daran, dass man final Klassen nicht erweitern kann. So ist A, C und D falsch und B die richtige Antwort. C ist falsch, da sich First kompilieren lässt.

Frage

Was passiert, wenn folgender Programmcode kompiliert und die main() Methode von B aufgerufen wird?

class A {
  int i;
  A( int i ) {
    this.i = i*2;
  }
}
class B extends A {
  public static void main( String args[] ) {
    B b = new B(2);
  }
  B( int i ) {     System.out.println( i );
  }
}
  1. Die Instanzvariable i bekommt den Wert 4.
  2. Die Instanzvariable i bekommt den Wert 2.
  3. Die Instanzvariable i bekommt den Wert 0.
  4. Der Code lässt sich nicht kompilieren.

Da sich der Programmcode nicht kompilieren lässt ist A, B und C falsch und D richtig. In der Vererbung ruft der Konstruktor der Klasse B den Standard-Konstruktor von A auf. Dieser existiert aber nicht. Um da Programm zum Laufen zu bringen, muss im Konstruktor B(int) mit super() der parametrisierte Konstruktor in A aufgerufen werden.

Frage

Was ist an folgendem Programmstück falsch?

final class First
{
  private int a = 1;
  int b = 2;
}
class Second extends First
{
  public void method() {     System.out.println( a + b );
  }
}
  1. Die println() Methode kann nicht ohne einen String Parameter benutzt werden.
  2. Da die Variable a privat ist, hat keine Klasse außer First Zugriff auf die Variable.
  3. Second kann First nicht erweitern.
  4. final ist kein gültiges Schlüsselwort für eine Klasse.

Eine Klasse kann eine final Klasse nicht erweitern. Daher ist Antwort C korrekt. println() ist auf verschiedenen Parametern definiert, auch mit int. Ließe man final weg, so käme dann der Fehler vom privaten Attribut a. final ist natürlich ein gültiges Schlüsselwort für Klassen.

Frage

Folgende Klassen sind auf einem Dateisystem, welches Groß/Kleinschreibung unterscheidet unter Fred.java gesichert. Welches sind korrekte Klassendeklarationen? (Zwei Antworten.)

  1. public class Fred {
      public int x = 0;
      public Fred( int x ) {
        this.x = x;
      }
    }
  2. public class fred {
      public int x = 0;
      public fred( int x ) {
        this.x = x;
      }
    }
  3. public class Fred extends MyBaseClass, MyOtherBaseClass {
      public int x = 0;
      public Fred( int xval ) {
        x = xval;
      }
    }
  4. protected class Fred {
      private int x = 0;
      private Fred( int xval ) {
        x = xval;
      }
    }
  5. import java.awt.*;
    public class Fred extends Object {
      int x;
      private Fred( int xval ) {
        x = xval;
      }
    }

Antwort A ist korrekt, da die Klasse die gleiche Groß/Kleinschreibung wie der Dateiname besitzt. B ist falsch, die die Schreibweise immer übereinstimmen muss. In Java gibt es keine Mehrfachvererbung, daher ist C falsch. Heißt die Klasse Fred.java, so muss sie auch von außen sichtbar sein. Daher muss sie public sein. Währe die Klasse zusammen mit einer anderen öffentlichen Klasse in einer Datei, so wäre dies korrekt. Auch E ist korrekt. Das Abstract Window Toolkit gehört zum Kern Paket von Java, ist also immer vorhanden. Obwohl jedes Objekt implizit Object erweitert, kann man dies noch einmal hinschreiben. Wenn der Konstruktor privat ist, lässt er sich nicht von außen aufrufen. So macht also diese Klasse wenig Sinn, da man sie nicht erzeugen kann. Man macht oft den Standard-Konstruktor private, wenn man den Benutzer zwingen will, einen parametrisieren zu verwenden.

Frage

Was passiert, wenn versucht wird, den folgenden Programmcode zu kompilieren und auszuführen?

class Mystery {
  String s;
  public static void main( Sting args[] ) {
    Mystery m = new Mystery();
    m.go();
  }
  void Mystery()
  {
    s = "palümpalüm";
  }
  void go()
  {
    System.out.println( s );
  }
}
  1. Dieser Code lässt sich nicht kompilieren.
  2. Dieser Code kompiliert, aber wirft eine Exception zur Laufzeit .
  3. Dieser Code läuft aber nichts erscheint in der Standard Ausgabe.
  4. Dieser Code läuft und schreibt "palümpalüm" in die Standard Ausgabe.
  5. Dieser Code läuft und schreibt "null" in die Standardausgabe.

Der Programmcode lässt sich nicht kompilieren, da die Methode Mystery() so heißt wie die Klasse. Da nur Konstruktoren so heißen dürfen wie die Klasse, Konstruktoren aber keine Rückgabewerte haben (sie geben explizit den this Zeiger des neuen Objektes zurück) muss man void von der Deklaration nehmen, so dass die Kompilation erfolgreich ist. So ist A korrekt. Wäre das void nicht vor dem Konstruktor würde das Programm palümpalüm ausgeben.

Frage

Welche der folgenden inneren Klassen sind korrekt für die Klasse A?

  1. class B { }
  2. class B extends A { }
  3. class B {
      B() {     System.out.println( "i = " + i );
      }
    }
  4. class B {
      class A { }
    }
  5. class A {
    }

Antwort A, B sind richtig. Antwort C ist falsch, da kein i deklariert wurde. D und E führen den Jikes Compiler an seine Grenzen.

Frage

Warum lassen sich die folgenden Klassen nicht kompilieren?

class A
{   private int x;
  public static void main( String args[] ) {
    new B();
  }
  class B {
    B() {       System.out.println( x );
    }   }
}
  1. Die Klasse B versucht, Zugriff zu der privaten Variablen zu bekommen, die in der äußeren Klasse definiert ist.
  2. Die Klasse A versucht, eine Instanz von B zu erzeugen, obwohl es keine gültige Instanz der Klasse A gibt.
  3. Der Konstruktor der Klasse B muss öffentlich sein.

Eine innere Klasse ist immer mit einer äußeren Klasse verbunden. Da aber eine static Method nicht an ein Objekt gebunden ist, wurde versucht ein B Objekt ohne Bezug zu einem A Objekt zu erzeugen. Dies ergibt die Fehlermeldung. So ist B korrekt. Folgende Zeilen würde die main() Funktion korrigieren.

public static void main( String args[] ) {

A a = new A();

A.B b = a.new B();

}

Frage

Was produziert das folgende Programm:

public class Test {
  static int total = 10;
  public static void main (String args []) {
    new Test();
  }
  public Test() {
    System.out.println( "In Test" );
    System.out.println( this );
    int temp = this.total;
    if ( temp > 5 )
      System.out.println( temp );
  }
}
  1. Die Klasse lässt sich nicht kompilieren.
  2. Der Compiler meldet einen Fehler in Zeile 2.
  3. Der Compiler meldet einen Fehler in Zeile 9.
  4. Der Wert 10 ist unter der Ausgabe.
  5. Die Klasse wird kompiliert, erzeugt jedoch einen Laufzeitfehler.

Die Klasse lässt sich kompilieren. Daher sind die Antworten A, B und C falsch. Die main() Methode erzeugt ein Test Objekt und dabei wird der Konstruktor aufgerufen. Dieser gibt erst "In Test" aus und anschließend den Hash Wert des Objektes. Da temp größer 5 ist, wird die Verzweigung genommen und 10 auf dem Schirm ausgegeben.

Frage

Was erfolgt, wenn man folgenden Progammcode kompiliert und ausführt.

abstract class Base {
  abstract void method(); 
  static int i;
}

public class Mine extends Base
{
  public static void main( String args[] ) {
  int[] a = new int[5];
  for( i = 0; i < a.length; i++ )
    System.out.println( a[i] );
  }
}
  1. Es werden  fünf Nullen ausgegeben.
  2. Das Feld a wird benutzt, bevor es initialisiert wurde.
  3. Kompilierfehler. Mine muss abstakt definiert werden.
  4. IndexOutOfBoundes Error i.

Abstrakte Klasse können Objekt- und Klassenvariablen besitzen sowie ausprogrammierte und wieder abstrakte Methoden. Erweitert eine Funktion eine abstrakte Klasse, so muss sie alle nicht ausprogrammierten Methoden implementieren, sonst ist diese Klasse wieder abstrakt. Mine erweitert zwar Base, überschreibt jedoch nicht die abstrakte Methode method(). Daher meldet der Compiler einen Fehler. So ist Antwort C korrekt und die anderen falsch.

Frage

Was passiert, wenn man folgendes Programm kompiliert und laufen lässt?

1: class Base {}
2: class Sub extends Base {}
3: class Sub2 extends Base {}
4:
5: public class Super {
6:   public static void main( String args[] ) {
7:     Base b = new Base();
8:     Sub s = (Sub) b;
9:   }
A: }
  1. Das Programm kompiliert und läuft ohne Fehler.
  2. Das Programm kompiliert nicht.
  3. Das Programm kompiliert aber liefert einen Fehler zur Laufzeit.

Die Definition aller vier Klassen ist korrekt und führt zu keinem Compilerfehler. Sub2 wird nicht benötigt und dient der Verwirrung. Die main() Methode legt eine lokale Variable b an. Diese Variable ist die Basisklasse von Sub. In Zeile 8 versuchen wir aber diese einfache Klasse über die explizite Typumwandlung zur Klasse Sub aufzubauen. Dies lässt der Compiler durch,  aber da Base einfach kein Sub werden kann, führt die Ausführung zu einem Laufzeitfehler. Ohne die Typumwandlung würde es schon zu einem Compilerfehler kommen. Daher ist Antwort C alleine korrekt.

Frage

Welche Modifier lassen sich im Programmcode für die innere Klasse korrekt einsetzen?

public class Clazz {
  public static void main( String args[] ) { }
  
  /* Modifier */ class MyInner { }
}
  1. public
  2. private
  3. static
  4. friend

public, private, static sind mögliche Modifier für innere Klassen. Also ist A, B und C korrekt. Es ist Eigenheit der Sprache C++ Freunde zu definieren, friend ist in Java kein eingetragenes Schlüsselwort.

Fragen zur Java-Zertifizierung, Flusskontrolle und Ausnahmebehandlung

Frage

Was wird ausgegeben, wenn die folgende Methode aufgerufen wird?

void test() {
one:
two:
  for ( int i = 0; i < 3; i++ ) {   three:
    for ( int j = 10; j < 30; j++ ) {      System.out.println( i + j );
      if ( i > 2 )
      continue one;
    }
  }
}
  1. 10 und 20
  2. 11 und 21
  3. 12 und 22
  4. 13 und 23
  5. 30, 31, 32, 33

Frage

Analysiere den folgenden Programmtext:

1: void schleife() {
2:   int x = 0;
3: one:
4:   while ( x < 100 ) {
5:   two:
6:     System.out.println( ++x );
7:     if ( x > 3 )
8:     break two;
9:   }
0: }
  1. Der Code lässt sich nicht kompilieren.
  2. Die Funktion schreibt die Zahl 0 in die Standardausgabe.
  3. Die Funktion schreibt die Zahl 1 und 2 in die Standardausgabe.
  4. Die Funktion schreibt die Zahl 3 in die Standardausgabe.
  5. Die Funktion schreibt die Zahl 4 in die Standardausgabe.
  6. Die Funktion schreibt die Zahlen 5 bis 9 in die Standardausgabe.
  7. Die Funktion schreibt die Zahl 10 in die Standardausgabe.

Der Programmcode kann nicht kompiliert werden, da Zeile 8 eine ungültige Sprungadresse enthält. Daher ist Antwort A korrekt. Die break Anweisung dient immer dazu, aus einer Schleife zu entkommen und nicht aus einer if Anweisung. Würde der Sprung nach two: gelingen, wäre man aber immer noch in der Schleife. Eine gültige Anweisung für Zeile 8 ist break one;

Frage

Betrachte den folgenden switch Block. (Zwei Antworten.)

char mychar = 'c';
switch ( mychar )
{   default:
  case 'a': System.out.println( "a" ); break;
  case 'b': System.out.println( "b" ); break;
}

Welche der folgenden Antworten ist richtig?

  1. Der switch Block ist illegal, weil nur Ganzzahlen in der switch Anweisung benutzt werden können.
  2. Mit der switch Anweisung ist alles in Ordnung.
  3. Der switch Block ist illegal, da die default Anweisung zum Schluss kommen muss.
  4. Wenn das Programmstück läuft, wird nichts auf die Standardausgabe geschrieben.
  5. Wenn das Programmstück ausgeführt wird, erscheint der Buchstabe "a" auf dem Bildschirm.

Der Ausdruck in einer switch Anweisung muss sich auf ein 32-Bit Wert konvertieren lassen. Also sind auch die kleineren Datentypen byte, char, short und int erlaubt. Im Beispiel ist ein Buchstabe ein char, der auf ein int konvertiert wird. Dies ist korrekt, also ist Antwort A falsch. Da die Kompilation somit in Ordnung ist, ist Antwort B korrekt. Die Reihenfolge, wann default auftaucht ist egal, somit ist C falsch. Daher folgt auch erst die richtige Ausgabe. Denn der Buchstaben ‚c‘ passt nicht auf ‚a‘ und ‚b‘, somit kümmerst sich der default Teil um die Eingabe. Da hinter default jedoch kein break steht läuft der Programmcode direkt in die Ausgabe "a" hinein. Also ist ebenfalls E korrekt und D falsch.

Frage

Welche der folgenden Aussagen über try, catch und finally sind wahr?

  1. Einem try Block muss immer ein catch Block folgen.
  2. Einem try Block kann entweder ein catch Block, ein finally Block oder beides folgen.
  3. Ein catch Block muss immer mit einem try Block verbunden sein.
  4. Ein finally kann nicht ohne ein try Block auftauchen.
  5. Keine dieser Aussagen sind wahr.

Einem try Block muss entweder ein finally ein catch oder beides folgen. Also ist A falsch und B richtig. Wenn man ein catch oder ein finally schreibt, so ist dies immer mit einem try verbunden, daher ist auch C und D richtig. Da mindestens eine Antwort richtig ist, ist E falsch.

Frage

1: public class Clazz {
2:   public static void main( String args[] ) {
3:     String s = null;
4:     if ( s != null & s.length() > 0 );
5:     if ( s != null && s.length() > 0 );
6:     if ( s == null | s.length() > 0 );
7:     if ( s == null || s.length() > 0 );
8:   }
9: }

Welche der Aussagen ist wahr?

  1. Zeile 4 erzeugt eine NullPointerException
  2. Zeile 5 erzeugt eine NullPointerException
  3. Zeile 6 erzeugt eine NullPointerException
  4. Zeile 7 erzeugt eine NullPointerException

Antwort A und C sind korrekt und Zeile 4 und 6 erzeugen eine NullPointerException. Man muss wissen, dass & und | beide Seiten auswerten. Ist also die Referenz null, so ist auch s.length() ungültig. In Zeile 4 und 6 kommen aber diese Auswertungen vor. && wertet den rechten Ausdruck nur dann aus, wenn der linke wahr ist. Also wird in Zeile 5 keine Länge abgefragt. || wertet nur dann die rechte Seite aus, wenn die linke Seite falsch ist. s == null ist aber wahr und die Länge muss nicht ausgewertet werden.

Frage

Welche Methoden kann man für das Kommentar //XX einsetzen?

class Base{
  public void method( int i ) { }
}
public class Extension extends Base {
  public static void main( String args[] ) { }
  //XX
}
  1. void method( int i ) throws Exception {}
  2. void method( long i ) throws Exception {}
  3. void method( long i ) {}
  4. public void method( int i ) throws Exception {}

Eine Methode kann in einer Unterklasse überschieben oder überladen werden. Überladene Funktionen haben den selben Namen wie eine andere Funktion in der Klasse oder der Basisklasse, jedoch verschiedene Parameter. Da in Antwort B und C die Methode mit einem long definiert ist, hat sie mit der in der Klasse Base definierten Methode außer den Namen nichts gemeinsam. So überlädt B und C method(int) aus Base und fügt sie der Klasse Extension zu. B und C sind korrekte Antworten und führen zu keinen Übersetzungsfehler. Anders dagegen A und D. Beide führen zu Compilerfehlern, da sie versuchen die Methode aus der Basisklasse zu überschreiben und eine Exception hinzuzufügen. Auch die Methode in Base müsste die Ausnahme schmeißen.

Fragen zur Java-Zertifizierung, Deklarationen und Zugriffskontrolle

Frage

Analysiere die Quellcodezeile

if ( 5 & 7 > 0 ) System.out.println( "Hullahupp-Reifen" );

  1. Diese Zeile lässt sich nicht kompilieren
  2. Diese Zeile lässt sich kompilieren, aber nichts erscheint auf der Standardausgabe
  3. Dieser Code lässt sich kompilieren und das Wort "Hullahupp-Reifen" erscheint in der Standard-Ausgabe.

Innerhalb vom Schleifen und Verzweigungen muss der Typ boolean sein. 5&7 ergeben zwar einen Wert, der verglichen mit 0 wahr oder falsch sein kann, jedoch ist die richtige Antwort A. Das Problem sind die unterschiedlichen Vorrangsregeln für Operationszeichen. Hier bindet 7>0 stärker als 5&7. So wird der Versuch unternommen 5 mit einem Wahrheits-Wert Und zu verknüpfen. Richtig wäre (5&7)>0.

Frage

Welche der Zeilen erzeugt ein Array mit fünf leeren Strings?

  1. String a [] = new String [5];
  2. for (int i = 0; i < 5; a[i++] = "") ;
  3. String a [] = {"", "", "", "", ""};
  4. String a [5];
  5. String [5] a;
  6. String [] a = new String [5];
    for ( int i = 0; i < 5; a[i++] = null ) ;

In Antwort A werden keine Strings erzeugt, sondern nur ein Feld mit 5 Referenzen. Somit ist A falsch. In B werden fünf leere Stings zugewiesen, ebenso in Antwort C, daher sind beide korrekt. Diese Strings sind natürlich nicht individuell, sondern die alle fünf Referenzen werden auf den gleichen Leerstring zeigen. Antwort D und E sind falsche Deklarationen für ein Feld. Antwort F ist falsch, da die Referenz auf Null zeigt, nicht aber auf einen leeren String "".

Frage

Welche Aussage beschreibt am besten die folgende Programmzeile?

String[][] s = new String[10][];

  1. Diese Zeile ist illegal.
  2. Es ist ein zweidimensionales Feld mit 10 Zeilen und 10 Spalten.
  3. Es ist ein Feld von 10 Feldern.
  4. Jedes Element in s ist mit "" initialisiert.
  5. Jedes Element in s ist nicht initialisiert und muss daher vor der Nutzung initialisiert werden.

Diese Zeile kann normal kompiliert werden, daher ist A falsch. C ist dagegen richtig, denn die Programmzeile schafft Platz für ein Array mit 10 Einträgen in einer Dimension. Wie groß die andere Dimension ist, bleibt offen. Daher ist B falsch. Da die andere Dimension in ihrer Größe noch nicht definiert ist, ist ein Zugriff auf ein Element noch ungültig. Daher kann jedes Element auch noch nicht mit einem Leerstring initialisiert sein. D ist also falsch und E richtig.

Frage

Welche Felddeklaration ist korrekt?

  1. String temp[] = new String { "u" "l" "l" "i" };
  2. String temp[] = { "u " "l" "l" "i" };
  3. String temp = { "a", "b", "c" };
  4. String temp[] = { "a", "b", "c" };

Antwort A, B und C erzeugen einen Übersetzungsfehler und D ist korrekt. Bei A und B fehlen für die Feldelemente das trennende Komma. Dann wäre B korrekt. Da A der Versuch einer Deklaration eines anonymen Feldes ist, muss noch hinter dem Typ String Klammern stehen. Korrigierte man A in String temp[] = new String[] { "u", "l", "l", "i" } ließe es sich kompilieren. Bei C fehlen ebenfalls die Klammern, da die geschweiften Klammern Elemente eines Feldes bezeichnen und nicht zu einem String zusammengesetzt werden können.

Frage

Was passiert, wenn folgendes Programm kompiliert wird?

1: public class Clazz {
2:  public static void main( String args[] ) {
3:    int feld[] = new int[]{1,2,3};
4:    System.out.println( feld[1] );
5:  }
6:}
  1. 1
  2. 2
  3. Übersetzungsfehler, da feld vor der Benutzung nicht definiert wurde.
  4. Übersetzungsfehler, da die Feldgröße festgelegt werden muss.

B ist die korrekte Antwort, da wir in Zeile 3 ein anonymes Feld mit den Elementen 1, 2 und 3 anlegen. An der Stelle 1 befindet sich die Zahl 2, da in Java genauso wie in C(++) der Feldindex bei Null beginnt. Da Felder in Java besondere Objekte sind (sogenannte Array-Objekte innerhalb der virtuellen Maschine) weist die Zuweisung in Zeile 2 dem feld Objekt eine gültige Referenz zu. Demnach ist C falsch. Die Feldgröße kann in Java nur durch die spezielle Form des new-Operator festgelegt werden, nicht durch reine Deklaration des Feldes. Also ist auch D falsch.

Frage

Was passiert, wenn folgendes Programm kompiliert wird?

public class Clazz {
  public static void main( String args[] ){
    int feld[] = new int[5];
    System.out.println( feld[0] );
  }
}
  1. Übersetzungsfehler, da feld vor der Benutzung nicht definiert wurde.
  2. null
  3. 0
  4. 5

Das Programm definiert eine Feld und weist der Referenz ein Feld der Größe von 5 int zu. Das Feld ist dabei mit 0 Werten initialisiert. Demnach ist der Zugriff auf feld[0] bis feld[4] erlaubt und jeder der Einträge ist 0. C ist die einzige richtige Antwort.

Fragen zur Java-Zertifizierung, Operatoren und Zuweisungen

Frage

Was ist die Ausgabe, wenn folgende Anweisungen ausgeführt werden?

Boolean b1 = new Boolean( true );
Boolean b2 = new Boolean( true );
Object obj1 = (Object)b1;
Object obj2 = (Object)b2;

if ( obj1 == obj2 )
  if ( obj1.equals(obj2) )     System.out.println( "a" );
  else
    System.out.println( "b" );
else
  if ( obj1.equals(obj2) )     System.out.println( "c" );
  else     System.out.println( "d" );
  1. a
  2. b
  3. c
  4. d

Die Ausgabe des Programms ist „c“, daher ist Antwort C korrekt. Da die Objektreferenz obj1 und obj2 nicht gleich sind, da sie auf verschiedene Boolean Objekte zeigen, kann die Ausgabe nicht „a“ oder „b“ sein. Da jedoch die equals() Methode den Wert (true) vergleicht, und beide Objekte den gleichen Wert haben, ist die Ausgabe „c“.

Frage

Analysiere die beiden Quellcodezeilen:

float f = 3.2;
int i = f;
  1. Dieses Programmstück wird nicht kompiliert
  2. Dieses Programmstück lässt sich kompiliert und i erhält der Wert 3
  3. Die zweite Zeile würde sich kompilieren lassen, wenn dort stände:

    int i = (byte) f;

  4. Die erste Zeile ließe sich kompilieren, wenn wir schreiben würden:

    float f = 3.2f;

Das Programm liefert beim Kompilieren eine Fehlermeldung, die besagt, dass der Datentyp float nicht mit double kompatibel ist. Der Datentyp double umfasst einen größeren Zahlenbereich und 3.2 ist als Literal automatisch ein double. Also ist A korrekt und B falsch. Der Fehler ist in der ersten Zeile ließe sich beheben, wenn man hinter dem 3.2 ein f hängt, wie es Lösung D vorschlägt. Somit ist D korrekt. Unabhängig von der erste Zeile ist auch mit der Typumwandlung auch die zweite Zeile richtig.

Frage

Welche der folgenden Anweisungen sind korrekt?

  1. 128 >> 1 ergibt 64
  2. 128 >>> 1 ergibt 64
  3. 128 >> 1 ergibt –64
  4. 128 >>> 1 ergibt –64

Antworten A und B sind korrekt.

Frage

Welche der folgenden Anweisungen ergeben den Rückgabewert true?

  1. „ulli“ == „ulli“
  2. „ulli“.equals(„ulli“)
  3. „ulli“ = „ulli“
  4. „ulli“.equals(new Button(„ulli“))

Da sich die virtuelle Maschine gleiche Strings in einem Konstanten-Pool hält, ist Antwort A korrekt. Bei B wird ein Zeichen-zu-Zeichen Vergleich unternommen. Da beide Strings identisch sind, ist auch hier das Ergebnis true, also ist auch Antwort B wahr. Man sollte sich nur bewusst sein, dass bei A kein inhaltlicher Vergleich stattfindet. Antwort C ergibt einen Compilerfehler, da ein einfaches Gleichheitszeichen eine Zuweisung definiert. Auf der linken Seite darf aber kein Literal stehen. Somit kann C nicht richtig sein. Die equals Methode, die auf der String Klasse definiert ist, überschreibt die equals() Methode der Klasse Object. Sie erlaubt aber als Parameter nur String Objekte. Es ist nicht so, dass bei nicht-Sting Objekten die toString() Methoden aufgerufen wird. Der Rückgabewert ist false. Also ist auch D falsch.

Frage

Welche der Anweisungen führt nicht zu einem Kompilierfehler?

  1. „ulli“ + “ plus “ + “ plus“
  2. „ulli“ + 29
  3. 345 + 45
  4. 5 + 5.5

Keine der Anweisungen führen zu einem Laufzeitfehler. Also sind Antwort A, B, C und D korrekt.

Frage

Welche der folgenden logischen Operatoren nennen sich „short circuit“?

  1. &
  2. ||
  3. &&
  4. |

B und C sind die kurzen Operatoren, die dann gelten, wenn der entsprechende Teil gilt.

Frage

Welchen Wert enthält die Variable temp nach der Ausführung der beiden Zeilen?

long temp = (int) 3.9;
temp %= 2;
  1. 0
  2. 1
  3. 2
  4. 3
  5. 4

Das Ergebnis ist 1, also die Antwort B wahr. Die Typumwandlung schneidet den Nachkommateil von 3.9 ab und das Ergebnis 3 Modulo 2 ist 1.

Frage

Welche Bildschirmausgabe erzeugt die folgende Anweisung?

System.out.println( 4 | 7 );

  1. 0
  2. 4
  3. 5
  4. 6
  5. 7

Die binäre Repräsentation der Zahl 4 ist 100 und von 7 ist 111. Beide mit Oder verknüpft gibt 111 und somit wiederum 7. Also ist Antwort E korrekt.