Studierte http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api und extrahiere die Kernthesen.
Autor: Christian Ullenboom
Prädikate und java.util.function.Predicate
Ein Prädikat ist eine Aussage über einen Gegenstand, die wahr oder falsch. Die Frage mit Character.isDigit(‚a‘), ob das Zeichen „a“ eine Ziffer ist, wird mit falsch beantwortet – isDigit ist also ein Prädikat, weil es über einen Gegenstand, einem Zeichen, eine Wahrheitsaussage fällen kann.
Flexibler sind Prädikate, wenn sie als Objekte repräsentiert werden, weil sie dann an unterschiedliche Stellen weitergegeben werden können, wenn etwa über ein Prädikat bestimmt, was aus einer Sammlung gelöscht werden soll oder ob mindestens ein Element in einer Sammlung ist, was ein Prädikat erfüllt.
Das java.util.function-Paket deklariert eine flexible funktionale Schnittstelle Predicate auf folgende Weise:
interface java.util.function.Predicate<T>
* boolean test(T t)
Führt einen Test auf t durch und liefert true, wenn das Kriterium erfüllt ist.
Beispiel
Der Test, ob ein Zeichen eine Ziffer ist, kann durch Prädikat-Objekte nun auch anders durchgeführt werden:
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
System.out.println( isDigit.test(‚a‘) ); // false
Hätte es die Schnittstelle Predicate schon früher in Java 1.0 gegeben, hätte es einer der Methode Character.isDigit(…) gar nicht bedurft, es hätte auch ein Predicate als statische Variable in Character geben können, so dass ein Test dann geschrieben würde als Character.IS_DIGIT.test(…) oder als Rückgabe von einer Methode isDigit(), mit der Nutzung Character.isDigit().test(…). Es ist daher gut möglich, dass sich in Zukunft die API dahingehend verändert, dass Aussagen auf Gegenständen mit Wahrheitsrückgabe nicht mehr als Methoden bei den Klassen realisiert werden, sondern als Prädikat-Objekte angeboten werden. Aber Methoden-Referenzen geben zum Glück die Flexibilität, dass problemlos Methoden als Lambda-Ausdrücke genützt werden können und so kommen wir wieder von Methoden zu Funktionen.
Typ Predicate in der API
Es gibt in der Java-API vier Stellen, an denen Prediate-Objekte genutzt werden:
· Als Argument für Lösch-Methoden, um in Sammlungen Elemente zu spezifizieren, die gelöscht werden sollen.
· Bei den Default-Methoden der Predicate-Schnittstelle selbst, um Prädikate zu verknüpfen.
· Bei regulären Ausdrücken, um ein Pattern als Predicate nutzen zu können.
· In der Stream-API, bei der Objekte beim Durchlaufen des Stroms über ein Prädikat identifiziert werden, um sie etwa auszufiltern.
Beispiel
Lösche aus einer Liste mit Zeichen alle die, die Ziffern sind (es bleiben nur Zeichen übrig, etwa Buchstaben).
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
List<Character> list = new ArrayList( Arrays.asList( ‚a‘, ‚1‘ ) );
list.removeIf( isDigit );
Default-Methoden von Predicate
Es gibt eine Reihe von Default-Methoden, die die funktionale Schnittstelle Predicate anbietet. Zusammenfassend:
interface java.util.function.Predicate<T>
default Predicate<T> negate()
default Predicate<T> and(Predicate<? super T> p)
default Predicate<T> or(Predicate<? super T> p)
default Predicate<T> xor(Predicate<? super T> p)
Die Methodennamen sprechen für sich.
Beispiel
Lösche aus einer Liste mit Zeichen alle die, die keine Ziffern sind.
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
Predicate<Character> isNotDigit = isDigit.negate();
List<Character> list = new ArrayList( Arrays.asList( ‚a‘, ‚1‘ ) );
list.removeIf( isNotDigit );
Funktionale Programmierung in Java am Beispiel vom Comparator
Funktionale Programmierung hat auch daher etwas akademisches, weil in den Köpfen der Entwickler oftmals dieses Programmierparadigma nur mit mathematischen Funktionen in Verbindung gebracht wird. Und die wenigsten werden tatsächlich Fakultät oder Fibonacci-Zahlen in Programmen benötigen und daher schnell funktionale Programmierung beiseite legen. Doch diese Vorurteile sind unbegründet, und es ist hilfreich, gedanklich funktionale Programmierung von der Mathematik lösen, denn die allermeisten Programme haben nichts mit mathematischen Funktionen im eigentlichen Sinne zu tun.
Betrachten wir die Sortierung von Strings. Ein Comparator ist eine einfache Funktion, mit zwei Parametern und einer Rückgabe. Diese Funktion wiederum wird an die sort(…)-Methode übergeben. Alles das ist funktionale Programmierung, denn wir programmieren Funktionen und übergeben sie. Drei Beispiele (Generics ausgelassen):
Code |
Bedeutung |
Comparator c = (c1, c2) -> … |
Implementiert eine Funktion |
Arrays.sort(T[] a, Comparator c) |
Nimmt eine Funktion als Argument an |
Collections.reverseOrder(Comparator cmp) |
Nimmt eine Funktion an und liefert auch eine zurück |
Funktionen selbst können in Java nicht übergeben werden, also helfen sich Java-Entwickler mit der Möglichkeit, die Funktionalität in eine Methode zu kapseln, sodass die Funktion zum Objekt mit einer Methode wird, was die Logik realisiert. Lambda-Ausdrücke bzw. Methoden/Konstruktor-Referenzen geben eine kompakte Syntax.
Der Typ Comparator ist eine funktionale Schnittstelle und steht für eine besondere Funktion mit zwei Parametern gleichen Typs und einer Ganzzahl-Rückgabe. Es gibt weitere funktionale Schnittstellen, die etwas flexibler sind als Comparator, in der Weise, dass etwa die Rückgabe statt int auch double oder etwas anderes sein können.
Imperative und funktionale Programmierung
In irgendeiner Weise muss ein Entwickler sein Problem in Programmform beschreiben, damit der Computer es letztendlich ausführen kann. Hier gibt es verschiedene Beschreibungsformen, die wir Programmierparadigma nennen. Bisher haben wir uns immer mit der imperativen Programmierung beschäftigt, bei der Anweisungen im Mittelpunkt stehen. Wir haben im Deutschen den Imperativ, also die Befehlsform, die sehr gut mit dem Programmierstil vergleichbar ist, denn es handelt sich in beiden Fällen um Anweisungen der Art „tue dies, tue das“. Diese „Befehle“ mit Variablen, Fallunterscheidungen, Sprüngen beschreiben das Programm und den Lösungsweg.
Zwar ist imperative Programmierung die technisch älteste, aber nicht die einzige Form Programme zu beschreiben; es gibt daneben die deklarative Programmierung, die nicht das „wie“ zur Problemlösung beschreibt, sondern das „was“, also was eigentlich gefordert ist ohne sich in genauen Abläufen zu verstricken. Auf den ersten Blick klingt das abstrakt, aber für jeden, der schon einmal
· einen Selektion wie im *.html auf der Kommandozeile genutzt,
· eine Datenbankanfrage mit SQL geschrieben,
· eine XML-Selektion mit XQuery getätigt,
· ein Build-Skript mit Ant oder make formuliert,
· eine XML-Transformation mit XSLT beschrieben hat, wird das Prinzip kennen.
Bleiben wir kurz bei SQL, um einen Punkt deutlich zu machen. Natürlich ist im Endeffekt die Abarbeitung der Tabellen und Auswertungen der Ergebnisse von der CPU rein imperativ, doch es geht um die Programmbeschreibung auf einem höheren Abstraktionsniveau. Deklarative Programme sind üblicherweise wesentlicher kürzer und damit kommen weitere Vorteile wie leichtere Erweiterbarkeit, Verständlichkeit ins Spiel. Da oftmals deklarative Programme einen mathematischen Hintergrund haben, lassen sich die Beschreibungen leichter formal in ihrer Korrektheit beweisen.
Deklarative Programmierung ist ein Programmierstil, und eine deklarative Beschreibung braucht eine Art „Ablaufumgebung“, denn SQL kann zum Beispiel keine CPU direkt ausführen. Aber statt nur spezielle Anwendungsfälle wie Datenbank- oder XML-Abfragen zu behandeln, können auch typische Algorithmen deklarativ formuliert werden, und zwar mit funktionaler Programmierung. Damit sind imperative Programme und funktionale Programme gleich mächtig in ihren Möglichkeiten.
Funktionale Programmierung und funktionale Programmiersprachen
Bei der funktionalen Programmierung stehen Funktionen im Mittelpunkt und ein im Idealfall zustandsloses Verhalten, in dem viel mit Rekursion gearbeitet wird. Ein typisches Beispiel ist die Berechung der Fakultät. Es ist n! = 1 · 2 · 3 · … · n, und mit Schleifen und Variablen, dem imperativen Weg, sieht es so aus:
public static int factorial( int n ) {
int result = 1;
for ( int i = 1; i <= n; i++ )
result *= i;
return result;
}
Deutlich sind die vielen Zuweisungen und die Fallunterscheidung durch die Schleife abzulesen; die typischen Indikatoren für imperative Programme. Bei der rekursiven Variante ist das ganz anders, hier gibt es keine Zuweisungen im Programm und die Schreibweise erinnert an die mathematische Definition:
public static int factorial( int n ) {
return n == 0 ? 1 : n * factorial( n – 1 );
}
Mit der funktionalen Programmierung haben wir eine echte Alternative zur imperativen Programmierung. Die Frage ist nur: Mit welcher Programmiersprache lassen sich funktionale Programme schreiben? Im Grunde mit jeder höheren Programmiersprache! Denn funktional zu programmieren ist ja ein Programmierstil, und Java unterstützt funktionale Programmierung, wie wir am Beispiel mit der Fakultät ablesen können. Da das im Prinzip schon alles ist, stellt sich die Frage, warum funktionale Programmierung einen so schweren Stand hat und bei den Entwicklern gefürchtet ist. Das hat mehrere Gründe:
Lesbarkeit. Am Anfang der funktionalen Programmiersprachen steht historisch LISP aus dem Jahr 1958, eine sehr flexible, aber schwer zu lesende Programmiersprache. Unsere Fakultät sieht in LISP so aus:
(defun factorial (n) (if (= n 1) 1 (* n (factorial (- n 1)))))
Die ganzen Klammern machen die Programme nicht einfach lesbar und die Ausdrücke stehen in der Präfix-Notation – n 1 statt der üblichen Infix-Notation n – 1. Bei anderen funktionalen Programmiersprachen ist es anders, dennoch führt das zu einem gewissen Vorurteil, dass alle funktionalen Programmiersprachen schlecht lesbar sind.
Performance und Speicherverbrauch. Ohne clevere Optimierungen von Seiten des Compilers und der Laufzeitumgebung führen insbesondere rekursive Aufrufe zu prall gefüllten Stacks und schlechter Laufzeit.
Rein funktional. Es gibt funktionale Programmiersprachen, die als „rein“ oder „pur“ bezeichnet werden und keine Zustandsänderungen erlauben. Die Entwicklung von Ein-/Ausgabeoperationen oder simplen Zufallszahlen ist ein großer Akt, der für normale Entwickler nicht mehr nachvollziehbar ist. Die Konzepte sind kompliziert, doch zum Glück sind die meisten funktionalen Sprachen nicht so rein und erlauben Zustandsänderungen, nur Programmierer greifen so selten wie nötig darauf zurück.
Funktional mit Java. Wenn es darum geht nur mit Funktionen zu arbeiten, kommen Entwickler schnell zu einem Punkt, dass Funktionen andere Funktionen als Argumente übergeben oder Funktionen zurückgeben. So etwas lässt sich in Java in der traditionellen Syntax nur sehr umständlich schreiben, dass alles so unlesbar wird, dass der ganze Vorteil der kompakten deklarativen Schreibweise verloren geht.
Aus heutiger Sicht stellt sich eine Kombination aus beiden Konzepten als zukunftsweisend da. Mit der in Java 8 eingeführten Schreibweise der Lambda-Ausdrücke sind funktionale Programme kompakt und relativ gut lesbar und die JVM hat gute Optimierungsmöglichkeiten. Java ermöglicht beide Programmierparadigmen und Entwickler können den Weg wählen, der für eine Problemlösung gerade am Besten ist. Diese Mehrdeutigkeit schafft natürlich auch Probleme, denn immer wenn es mehrere Lösungswege gibt, entstehen Auseinandersetzungen um die Beste der Varianten – und hier kann von Entwickler zu Entwickler eine konträre Meinung herrschen. Funktionale Programmierung hat unbestritten Vorteile und das wollen wir uns genau anschauen.
Thema der Woche: Google Cache
- Studiere aufmerksam https://code.google.com/p/guava-libraries/wiki/CachesExplained.
- Schreibe ein Swing-Programm mit einer JList und einem eigenen Listen-Model, was zu einem Index die Primfaktorzerlegung berechnet (benutze Google und suche eine Implementierung). Bei 1 wird also 1 angezeigt, bei 2 folgt 1*2, bei 3 dann 3, bei 4 folglich 2*2 usw.
- Das Ergebnis soll nicht mehr live berechnet werden, sondern über den Google Cache zwischengespeichert werden, was ein beschleunigtes Scrollen bewerkstelligen sollte. Wie lässt sich das integrieren?
- Gibt es mehrere Möglichkeiten den Google Cache in dem Szenario einzusetzen?
In eigener Sache: Die RetroBude ist auf Wachstumskurs
Um das größte Museum für Heimcomputer und Spielkonsolen aufzubauen, sucht die RetroBude (http://retrobu.de/) historische Heimcomputer, Spielkonsolen, Spiele/Software, Literatur, Werbung und weitere Artefakte aus der Vergangenheit. Hilf mit deinem Beitrag (als Spende oder gegen Bezahlung) die Geräte einem interessierten Publikum zugänglich zu machen.
Neue GWT-Bibliothek
http://gwtbootstrap.github.io, auf jeden Fall einen Blick wert.
Thema der Woche: Paketierung mit <fx:deploy>
Seit Neustem kann man mit Java auch ausführbare Dateien bzw. Installer bauen. Ließ dazu http://docs.oracle.com/javafx/2/deployment/self-contained-packaging.htm bzw. suche nach weiterer Dokumentation im Netz.
- Teste das an einer eigenen kleinen Hello-World-Anwendung.
- Wie groß ist das Ergebnis mit JRE?
- Welche Zielformate sind möglich und kann man alle etwa auf einem Linux-Build-Server bauen?
- Nutze log4j und nimm die Jar mit in das Zielformat mit auf. Lassen sich auch native Dateien einbinden?
- Gilt diese Möglichkeit nur für JavaFX oder auch für für AWT/Swing oder SWT?
Java 8 könnte auf 18. März 2014 verschoben werden, wäre dann aber “Der Profi”
Bisher Vorschläge!
2013/05/09 M7 Feature Complete 2013/07/18 Rampdown start 2013/09/05 M8 Developer Preview 2013/09/12 All Tests Run 2013/10/10 API/Interface Freeze 2013/10/24 Zero Bug Bounce 2013/11/21 Rampdown phase 2 2014/01/23 M9 Final Release Candidate 2014/03/18 GA General Availability
Der 18.03. ist bestimmt kein Zufall, denn Luc Besson hat Geburtstag, und der ist uns ja in Erinnerung mit dem 1994er Klassiker “Der Profi”mit Jean Reno (nicht der mit Jean-Paul Belmondo, der war 1981). Damit wird Java 8 bestimmt auch so ein Profi im Bereich Sicherheit (naja, der Profi stirbt am Ende), sodass wir alle sagen können “Hier wird es uns gut gehen, Java”.
Java 9 kommt dann in der ersten Hälfte von 2016. Oder irgendwann.
Buchkritik: Wicked Cool Java: Code Bits, Open-Source Libraries, and Project Ideas
Brian Eubanks; ISBN-10: 1593270615; No Starch Press; 15.11.2005; 248 Seiten
Mit dem Buch hat Brian eigentlich das gemacht, was sich jeder Autor wünscht: sich hinzusetzen und einfach mal im Artikelstil über alles zu schreiben, was einem interessiert, ohne darauf zu achten, ob das nützlich oder wichtig ist. Dafür nimmt er sich 8 Kapitel Zeit:
Chapter 1: Java Language and Core API, Chapter 2: String Utilities, Chapter 3: Processing XML and HTML, Chapter 4: Crawling the Semantic Web, Chapter 5: Math and Science, Chapter 6: Graphics and Data Visualization, Chapter 7: Multimedia and Sychronization, Chapter 8: Fun, Integration and Project Ideas. Im Grunde ist nur das erste Kapitel ein kleines Einstiegskapitel, insbesondere in die Neuerungen von Java 5, und zusammen mit dem zweiten Kapitel haben sie die Java SE selbst zum Inhalt. Das Niveau ist an Anfang niedrig und passt nicht zum Rest. Syntaktisch ist nicht immer alles sauber, so gibt es immer wieder umständliche Feldinitialisierungen wie int[] theList = new int[]{2,3,5,7}; oder java.util.Random random = new Random(); wo ich mich Frage, ob der Autor dort gerade wach war. Dann wird assert geklammert wie eine Methodenaufruf, das ist aber nur in C so, nicht in Java, wo assert ein Schlüsselwort ist und der Ausdruck nicht geklammert wird. (Macht Krüger im Buch aber leider auch so.) Leider sind auch nicht alle Beispiele konsequent auf Java 5 ausgelegt, immer wieder findet sich der Raw-Type etwa von Datenstrukturen, bei seinen verketten Listen-Implementierung oder mit verketteten Knoten wiederum fehlt ein Generic, hier steht nur Object content. An anderer Stelle im Buch gibt es den Hinweis, das ein Listing mit Generics auf der Buchseite (http://www.wickedcooljava.com/) ist, warum nicht gleich im Code? Mit Generics scheint Brian auch noch nicht so vertraut zu sein, anders kann ich mir nicht erklären, warum er eine Methode removeMatches(List<Number> aList) schreibt, denn man muss verstehen, da man so etwas nicht zum Beispiel mit einer List<Double> aufrufen kann; eleganter wäre ein Typ-Bound er Art List<? extends Number> hin. Weiter: Statt StringBuilder kommt noch StringBuffer zum Einsatz. Nicht gut gefällt mir auch der Bezug auf konkrete Klasse, statt Basistypen, etwa bei ArrayList<String> getNames(), hier würde als Rückgabe auch List oder Collection reichen. (In dem gleichen Beispiel ist auch unglücklich den Scanner auch nicht im finally zu schließen. Und getFloat() statt getDouble() zu nehmen ist auch Geschmackssache.) Bei den Farben verwendet der Autor noch die klein geschriebenen Namen also Color.blue statt Color.BLUE. Sehr geschwätzig ist auch if ( … ) { return true; } else { return false; } — das ist uncool.
Im Mittelpunkt des Buches geht es um wilde Open-Source Bibliotheken, etwa für mathematische Operationen, Textanalyse, Suche (nicht wild), semantische Netze, RDF, MIDI-Sounds. Eigentlich nichts, was man wirklich/dringend/oft bräuchte, und wenn, würde man vermutlich in ein spezielles Buch schauen. Positiv ist anzumerken, dass uns der Autor die Libs vorstellt und des Lesers Horizont erweitert. Ein Probekapitel gibt es nicht online, allerdings unter http://www.wickedcooljava.com/related.jsp eine Linkliste der Bibliotheken. April 2013
Assertions feiner aktivieren oder deaktivieren
Assertions müssen nicht global für das ganze Programm gesetzt werden, sondern könne auch feiner deklariert werden, etwa für eine Klasse oder ein Paket. Mit geschickter Variation von –ea (Assertions aktivieren) und –da (Assertions desaktivieren) lässt seht gut steuern, was die Laufzeitumgebung prüfen soll.
Beispiel
Aktiviere Assertions für die Klasse com.tutego.App:
$ java -ea:com.tutego.App AppWithMain
Aktiviere Assertions für das Default-Paket (dafür stehen die drei Punkte):
$ java -ea:… AppWithMain
Aktiviere Assertions für das Paket com.tutego inklusive aller Unterpakete (auch dafür stehen drei Punkte):
$ java -ea:com.tutego… AppWithMain
Aktiviere Assertions für das Paket com.tutego inklusive aller Unterpakete, aber desaktiviere sie für die Klasse App in dem Paket com.tutego:
$ java -ea:com.tutego… -da:com.tutego.App AppWithMain
Assertions müssen Nebeneffekt frei sein
Assertions stehen immer in der Klassendatei, da sie der Compiler immer in Bytecode abbildet. Die JVM ignoriert Assertions standardmäßig bei der Ausführung und eine Aktivierung erfolgt nur auf Befehl; ein Ablauf ohne Bedingungstests ist also der Normalfall. Daraus folgt, dass Ausdrücke in den assert-Anweisungen ohne Nebeneffekte sein müssen. So etwas wie
assert counter– == 0;
ist keine gute Idee, denn das Vermindern der Variablen ist ein Nebeneffekt, der nur dann stattfindet, wenn die JVM auch Assertions aktiviert hat. Allerdings lässt sich das auch für einen Trick nutzen, Assertions bei der Ausführung zu erzwingen. Im statischen Initialisierer einer Klasse können wir setzen:
boolean assertEnabled = false;
assert assertEnabled = true;
if ( ! assertEnabled )
throw new RuntimeException( "Assertions müssen aktiviert werden" );
Mein Abendvortrag “Java 8 Features” in Braunschweig am 13. Juni
Java User Group Ostfalen
CKC
13 Am Alten Bahnhof
38122 Braunschweig
Deutschland
Donnerstag, 13. Juni 2013, von 19:00 bis 22:00 (MESZ)
Christian Ullenboom gibt in seinem Abendvortrag einen Einblick in die Neuerungen von Java 8, angefangen von statischen Schnittstellenmethoden über Default-Methoden, Lambda-Ausdrücken, Annotationen-Ergänzungen bis zu den Erweiterungen der Standard-Bibliothek. In der gemütlichen Diskussionsrunde im Anschluss gibt es keine Denkverbote und es darf auch über Plagiate, Flughäfen in großen Städten, ausgefüllte Dirndl offen und frei gesprochen werden.
Referent:
Presenter.build().name( "Christian Ullenboom" ).born( 1973 ).studied( "Diplom-Informatik" )
.cameToJava( "im ‚Ruck‘-Jahr 1997" )
.knownFor( "Java-Blog", URI.create( "http://javainselblog.tutego.de" ) )
.autored( "Java ist auch eine Insel" ).autored( "Java – mehr als eine Insel" )
.orElse( "Den Rest des Tages verbringt er mit seiner Liebe" )
.orElse( "Erweiterung des Schulungsunternehmens tutego", URI.create( "http://tutego.de/" ) )
.orElse( "alten Computern/Videospielkonsolen", URI.create( "http://RetroBu.de/" ) ).done();
Thema der Woche: Etwas Design mit UML
DRUG LORD (http://www.old-games.com/download/5218/druglord) ist ein Spielklassiker für DOS von 1993. Mit Kauf- und Verkauf von Drogen in verschiedenen Städten muss man möglichst viel Geld verdienen.
Bild © old-games.com
Überlege, wie das Spiel in seinem Grundzügen (nur Kauf/Verkauf von Drogen, Orte und Cash aber kein Hospital/Health, keine Waffen, Bank, Schulden) objektorientiert modelliert werden kann. Nutzte UML-Diagramme zur Darstellung. Das Programm soll am Ende lauffähig sein, die Darstellung kann beliebig sein. (Wer das Retro-Feeling mag, kann http://sourceforge.net/projects/javacurses/ ausprobieren.)
Mit Java für C64 entwickeln
Knackige Überschrift, was steckt dahinter? Wir warten auf den Architekten für unser Restaurant hier in den Philippinen und so hatte ich etwas Leerlauf, um was total Unnötiges zu programmieren.
Um Java-Programme, beziehungsweise eine Teilmenge der Sprache/Bibliothek, auf dem C64 (oder anderen 8-Bit-Computern) zum Laufen zu bringen, sind mehrere Ansätze denkbar:
- Eine JVM auf dem Heimcomputer, die Bytecode interpretiert. Das würde im Prinzip gehen, es gibt auch Mini-Laufzeitumgebungen für so etwas. Auch Java Smart Card ist ein Stichwort.
- Compiler, die entweder Bytecode oder Source-Code in ein Quellformat umsetzen, was der 65xx versteht.
Ein JVM ist richtig viel Arbeit, und war in 2 Tagen nicht zu schaffen. Und mit http://sourceforge.net/projects/vm02/ gibt es so etwas auch schon für Apple II Computer. Ein (Cross-)Compiler ist deutlicher einfacher. Bytecode in 65xx-Assembler zu übersetzen ist relativ einfach, doch dann müsste ich wieder Assembler anfassen und damit das ganze schnell wird, müsste ich auch einen Code-Optimierer schreiben, denn einfach die Stack-Maschine umzusetzen, führt auch zu keinen Performance-Wundern. Da es für den C64 auch Compiler gibt, etwa für PASCAL (auf der Maschine) oder C (als Cross-Compiler etwa mit CC65), kann man aus Bytecode auch dieses Format generieren. Aber dann hätte ich wieder mit Java-Bytecode arbeiten müssen, was mir auch keine Freunde macht. Am Schnellsten verspricht Resultate eine Code-Transformation von Java nach C. Das Resultat kann dann der http://www.cc65.org/ in Maschinencode umsetzen, und dann bekommt man auch ein paar Optimierung geschenkt.
Das JDK bringt alles mit, um an den AST des Compilers zu kommen, wie schon im Blog hier beschrieben: http://www.tutego.de/blog/javainsel/2012/07/mit-der-internen-compiler-api-auf-den-ast-einer-klasse-zugreifen/. Von den erkannten Elementen (Ausdrücke, Variablenzugriff, Schleife, …) muss man nur C-Code schreiben und fertig ist. Um mir die Sache einfach zu machen vereinfache ich Java jedoch massiv:
- kein float/double/long/boolean
- kein new, keine Klassen, Stellen und sonstiges objektorientiertes “Zeugs”, das dranhängt, wie enum, erweitertes for, nur statische Methoden, keine Ausnahmen, Keine String-Konkatenation
- String-Literale können verwendet werden, allerdings nur von 0x0000 – 0x00FF (256) und eigentlich geht PETSCII nur von 0-191
- Nichts von der Java-Bibliothek
Des weiteren muss sehr “C”-ähnlich programmiert werden:
- Alle lokale Variablen müssen am Anfang einer Methode deklariert sein
- Alle Bezeichner müssen für den C-Compiler gültig sein, keine Unicodes
- Die Reihenfolge muss stimmen, der Umsetzer erzeugt keine Prototypen
- Eine main()-Methode muss etwas zurückgeben in C99, Java macht das nicht, daher nutzt man System.exit(0).
Das auf diese Weise kastrierte Java ist zwar im Prinzip für nix mehr zu gebrauchen, aber für Heimcomputer immer noch akzeptabel und eine nette Spielerei.
Wer bis dahin noch nicht das Interesse verloren hat, kann ein wenig mit dem Compiler spielen; die Source liegen unter https://code.google.com/p/java2c-transcompiler/.
Jetzt brauchen wir Input:
import static j2c.lib.Stdio.printf; // Source: http://skoe.de/wiki/doku.php?id=ckurs:04-abend4 public class Application { public static char istPrimzahl( int n ) { int divisor; int testEnde = n / 2; /* Alle potentiellen Teiler bis zur Mitte testen */ for ( divisor = 3; divisor < testEnde; divisor += 2 ) { /* Mit Rest 0 teilbar? */ if ( n % divisor == 0 ) { /* Ueberprüfung abbrechen, keine Primzahl */ return 0; } } /* Kein Test durchgefallen, ist eine Primzahl */ return 1; } public static void main( String[] args ) { int zahl; /* Von 3 beginnend jede zweite Zahl testen, bis unter 1000 */ for ( zahl = 3; zahl < 1000; zahl += 2 ) { if ( istPrimzahl( zahl ) != 0 ) { printf( "Primzahl: %u\n", zahl ); } } System.exit( 0 ); } }
Für die C-Funktionen (http://www.cc65.org/doc/funcref.html) gibt es eine paar statische Imports und native Platzhalter:
package j2c.lib; // http://www.cplusplus.com/reference/cstdio/ public class Stdio { /** * Print formatted data to stdout. * <code>int printf ( const char * format, ... );</code> * @param format * @param args * @return */ native public static int printf( String format, Object... args ); }
Das setzt der Compiler im Grunde 1:1 so um. Es lohnt sich das Eclipse CDT unter http://download.eclipse.org/tools/cdt/releases/juno zu installieren, damit die syntaktische Hervorhebung funktioniert (den CC65 Compiler einbinden könnten wir hier NICHT). Nach einer CDT-Neuformatierung ergibt sich:
#include <stdio.h> #include <stdlib.h> #include <peekpoke.h> #include <c64.h> #include <conio.h> /* CLASS Application { */ char istPrimzahl(int n) { int divisor; int testEnde = n / 2; for (divisor = 3; divisor < testEnde; divisor += 2) { if (n % divisor == 0) { return 0; } } return 1; } int main(void) { int zahl; for (zahl = 3; zahl < 1000; zahl += 2) { if (istPrimzahl(zahl) != 0) { printf("Primzahl: %u\n", zahl); } } return 0; } /* END CLASS } */
Um das Compilat zu Erzeugen muss nun der cc65 installiert werden. Unter ftp://ftp.musoftware.de/pub/uz/cc65/ lädt man die für Windows etwa die 1.3 MB große EXE und installiert. Die Eintragungen in den Path kann man vornehmen, nach der Installation folgt dann mit dem kleinen Test:
C:\..>cc65
cc65.exe: No input files
Kappt also.
Das Ganze soll im Emulator laufen, hier ist WinVICE gut: http://www.viceteam.org/#download.
Eine Batch-Datei bindet alles zusammen, also Java –> C, Compilieren und im VICE starten:
set JAVA_HOME="C:\Program Files\Java\jdk1.7.0" %JAVA_HOME%\bin\java -cp bin/;%JAVA_HOME%\lib\tools.jar j2c.J2CC65 src/java/Application.java > app.c del app.prg cl65 -o app.prg app.c "C:\Program Files\WinVICE-2.2-x64\x64.exe" app.prg
Und das Ergebnis sieht so aus:
Hat jmd. Lust das weiter zu entwickeln? Schreibt mir eine E-Mail.
Klassen mit einer abstrakten Methode als funktionale Schnittstelle in Java 8?
Als die Entwickler Lambda-Ausdrücke diskutierten, stand auch die Frage im Raum, ob abstrakte Klassen, die nur über eine abstrakte Methode verfügen – früher wurde hier die Abkürzung SAM (Single Abstract Method) genutzt –, ebenfalls für Lambda-Ausdrücke genutzt werden können. Sie entschieden sich dagegen, da bei Implementierung von Schnittstellen die JVM weitreichende Optimierungen vornehmen kann. Und bei Klassen wir das schwierig, was auch daran liegt, dass ein Konstruktor umfangreiche Initialisierungen mit Seiteneffekten vornehmen (die Konstruktoren aller Oberklassen nicht zu vergessen) sowie Ausnahmen auslösen könnte. Gewünscht ist aber nur die Ausführung einer Implementierung der funktionalen Schnittstelle und kein anderer Code.
Es gibt nun im JDK einige abstrakte Klassen, die genau eine abstrakte Methode vorschreiben, etwa jva.util.TimerTask. Solche Klassen können nicht über einen Lambda-Ausdruck realisiert werden; hier müssen Entwickler weiterhin zu Klassenimplementierungen greifen, und das kürzeste ist eine innere anonyme Klasse. Eigene Hilfsklassen können natürlich den Code etwas abkürzen, aber eben nur mit eigener Implementierung. Zwei Strategien bieten sich an: durch Delegation oder Vererbung. Nehmen wir das Beispiel für TimerTask und gehen beide Varianten durch:
import java.util.*;
class TimerTaskLambda {
public static TimerTask createTimerTask( Runnable runnable ) {
return new TimerTask() {
@Override public void run() { runnable.run(); }
};
}
public static void main( String[] args ) {
new Timer().schedule( createTimerTask( () -> System.out.println("Hi") ), 500 );
}
}
Oder mit Vererbung:
public class LambdaTimerTask extends TimerTask {
private final Runnable runnable;
public LambdaTimerTask( Runnable runnable ) {
this.runnable = runnable;
}
@Override public void run() { runnable.run(); }
}
Der Aufruf ist dann statt createTimerTask(…) der des Konstruktors:
new Timer().schedule( new LambdaTimerTask( () -> System.out.println("Hi") ), 500 );
Eclipse 4.2.2 installieren, Performanceverbesserungen
http://www.eclipse.org/downloads/ (Eclipse Juno SR2)
Inselraus: Die Farben des Systems über java.awt.SystemColor
Bei eigenen Java-Programmen ist es wichtig, dass diese sich so perfekt wie möglich in die Reihe der anderen Client-Programme einordnen, ohne großartig aufzufallen. Dafür muss ein Fenster die globalen Einstellungen wie den Zeichensatz und die Farben kennen. Für die Systemfarben gibt es die Klasse SystemColor, die alle Farben einer grafischen Oberfläche auf symbolische Konstanten abbildet. So ist SystemColor.text[1] die Hintergrundfarbe von Texteingabefeldern. Besonders praktisch ist dies bei Änderungen von Farben während der Laufzeit. Über diese Klasse können immer die aktuellen Werte eingeholt werden, denn ändert sich beispielsweise die Hintergrundfarbe der Laufleisten, ändert sich damit auch der RGB-Wert.
Die Systemfarben sind Konstanten von Typ SystemColor, was eine Unterklasse von Color ist. Damit lassen sich sich direkt nutzen, etwa über setColor(Color) oder über getRGB() der RGB-Anteil erfragen. Die Klasse SystemColor hat keine eigenen öffentlichen Methoden, sondern überschreibt nur toString().
Die Klasse deklariert die folgenden statischen finalen Variablen:
class java.awt.SystemColor extends Color implements Serializable
SystemColor |
Welche Farbe darauf anspricht |
desktop |
Farbe des Desktop-Hintergrunds |
activeCaption |
Hintergrundfarben für Text im Fensterrahmen |
activeCaptionText |
Farbe für Text im Fensterrahmen |
activeCaptionBorder |
Rahmenfarbe für Text im Fensterrahmen |
inactiveCaption |
Hintergrundfarbe für inaktiven Text im Fensterrahmen |
inactiveCaptionText |
Farbe für inaktiven Text im Fensterrahmen |
inactiveCaptionBorder |
Rahmenfarbe für inaktiven Text im Fensterrahmen |
window |
Hintergrundfarbe der Fenster |
windowBorder |
Rahmenfarbe der Fenster |
windowText |
Textfarbe für Fenster |
menu |
Hintergrundfarbe für Menüs |
menuText |
Textfarbe für Menüs |
text |
Hintergrundfarbe für Textkomponenten |
textText |
Textfarbe für Textkomponenten |
textHighlight |
Hintergrundfarbe für hervorgehobenen Text |
textHighlightText |
Farbe des Texts, wenn dieser hervorgehoben ist |
textInactiveText |
Farbe für inaktiven Text |
control |
Hintergrundfarbe für Kontrollobjekte |
controlText |
Textfarbe für Kontrollobjekte |
controlHighlight |
normale Farbe, mit der Kontrollobjekte hervorgehoben werden |
controlLtHighlight |
hellere Farbe, mit der Kontrollobjekte hervorgehoben werden |
controlShadow |
normale Hintergrundfarbe für Kontrollobjekte |
controlDkShadow |
dunklerer Schatten für Kontrollobjekte |
scrollbar |
Hintergrundfarbe der Schieberegler |
Info |
Hintergrundfarbe der Hilfe |
infoText |
Textfarbe der Hilfe |
Konstanten der Systemfarben
Hinweis: Die Klasse javax.swing.UIManager ist ein großer Assoziativspeicher, bei dem sich weitere Belegungen erfragen lassen. Es erfragt zum Beispiel UIManager.getColor("Table.background") die Tabellen-Hintergrundfarbe vom gerade eingestellen Look and Feel.[2]
[1] Sun verstößt mal wieder gegen die eigenen Namenskonventionen. Die finalen Variablen – Konstanten – sollten großgeschrieben werden. Das funktioniert bei den SystemColor-Objekten aber nicht, da es alle Bezeichnernamen schon in Großbuchstaben gibt, und zwar für Variablen vom Typ Byte, die Verweise in eine interne Tabelle darstellen.
[2] Die Seite http://www.devdaily.com/java/java-uimanager-color-keys-list liefert eine Auflistung der Schlüssel und ein Programm zur Anzeige.
Inselraus: Zeichensätze des Systems ermitteln
Um herauszufinden, welche Zeichensätze auf einem System installiert sind, liefert getAvailableFontFamilyNames() auf einem GraphicsEnvironment ein Feld mit Font-Objekten. Ein Objekt vom Typ GraphicsEnvironment beschreibt die Zeichensätze des Systems und liefert GraphicsDevice-Objekte. Ein GraphicsDevice ist eine Malfläche, also das, worauf das System zeichnen kann. Das kann der Bildschirm sein, aber auch ein Drucker oder eine Hintergrundgrafik. Die statische Fabrikmethode getLocalGraphicsEnvironment() liefert ein solches GraphicsEnvironment-Objekt.
Beispiel: Im folgenden Codesegment gibt eine Schleife alle Zeichensatznamen aus:
for ( String fonts : GraphicsEnvironment. getLocalGraphicsEnvironment().getAvailableFontFamilyNames() ) System.out.println( fonts );
Auf meinem System liefert die Schleife die folgenden Ausgaben:
Arial
Arial Black
Arial Narrow
…
Wingdings
Wingdings 2
Wingdings 3
Zur API:
abstract class java.awt.GraphicsEnvironment
- static GraphicsEnvironment getLocalGraphicsEnvironment()
Liefert das aktuelle GraphicsEnvironment-Objekt. - abstract Font[] getAllFonts()
Liefert ein Feld mit allen verfügbaren Font-Objekten in einer Größe von einem Punkt. - abstract String[] getAvailableFontFamilyNames()
Liefert ein Feld mit allen verfügbaren Zeichensatzfamilien. - abstract String[] getAvailableFontFamilyNames(Locale l)
Liefert ein Feld mit verfügbaren Zeichensatzfamilien, die zu einer Sprache l gehören.
Inselraus: n-Ecke zeichnen
In der Graphics-Klasse gibt es keine Methode, um regelmäßige n-Ecken zu zeichnen. Eine solche Methode ist aber leicht und schnell programmiert: Wir teilen dazu einfach einen Kreis in n Teile auf und berechnen die x- und y-Koordinaten der Punkte auf dem Kreis. Diese Punkte fügen wir einem Polygon-Objekt mittels der addPoint(…)-Methode hinzu. Eine eigene statische Methode drawVertex(…) übernimmt diese Polygon-Erstellung. Der letzte Parameter der Methode ist ein Wahrheitswert, der bestimmt, ob das n-Eck gefüllt werden soll oder nicht:
package com.tutego.insel.ui.graphics; import java.awt.*; import javax.swing.*; public class N_Vertex extends JPanel { private static final long serialVersionUID = -6314283966378303073L; @Override protected void paintComponent( Graphics g ) { VertexDrawer.drawVertex( g, getWidth() / 2, getHeight() / 2, 50, 6, true ); VertexDrawer.drawVertex( g, getWidth() / 2, getHeight() / 2, 60, 6, false ); } public static void main( String[] args ) { JFrame f = new JFrame(); f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); f.add( new N_Vertex() ); f.setSize( 200, 200 ); f.setVisible( true ); } } class VertexDrawer { public static void drawVertex( Graphics g, int x, int y, int r, int n, boolean filled ) { Polygon p = new Polygon(); for ( int i = 0; i < n; i++ ) p.addPoint( (int) (x + r * Math.cos( i * 2 * Math.PI / n )), (int) (y + r * Math.sin( i * 2 * Math.PI / n )) ); if ( filled ) g.fillPolygon( p ); else g.drawPolygon( p ); } }