Da ich fürs Java-Programmieren des Öfteren die deutschen Umlaute und das ß durch entsprechende Unicodes ersetzen musste, hab ich mir eine Seite geschrieben, die das für mich macht. Diese habe ich jetzt unter Umlaute durch Unicodes ersetzen auf meine Homepage gestellt.
Eine Applikation wurde programmiert und deren Zwischenmeldungen zunächst nur über den
System.out.println
Befehl auf die Konsole ausgegeben. Jetzt soll die Funktionalität
auch einem Benutzer zugänglich gemacht werden. D.h. man braucht ein GUI und es wäre schön, wenn man die
Meldungen direkt in einem TextArea
anzeigen könnte.
Am einfachsten wäre es doch, wenn man alle bisherigen System.out.println
Befehle einfach
so lassen könnte und die Ausgabe einfach umleiten könnte. Den Hinweis für diese Vorgangsweise und wie
man das umsetzt habe ich
hier
gefunden.
OutputStream
vererbt und überschreibt die Methode
public void write(int b) throws IOException
mit folgendem Inhalt.
// redirects data to the text area m_pTextArea.append(String.valueOf((char)b)); // scrolls the text area to the end of data m_pTextArea.setCaretPosition(m_pTextArea.getDocument().getLength());(Meine Implementierung kann man hier sehen.)
myFunctionality(TextAreaOutputStream pOutputStream) { //1. redirect the output stream PrintStream pStandardOut = System.out; if (pOutputStream != null) { PrintStream pPrintStream = new PrintStream(pOutputStream); System.setOut(pPrintStream); }
//finally set back the output stream to the default System.setOut(pStandardOut); } // end of myFunctionality
Ab jetzt werden alle Ausgaben, die mit System.out
getätigt wurden, in das TextArea Element geschrieben.
Allerdings gibt es einen kleinen Schönheitsfehler: Die Funktionalität läuft voll durch bis die Ausgabe dann als ganzes
im TextArea angezeigt wird. Das ist vor allem mühselig, wenn es sich um längere Laufzeiten handelt und somit der
Benutzer glaubt, das Programm tut nichts.
Damit die Ausgabemeldungen direkt wie bei der Konsole im TextArea Element angezeigt werden,
verwende ich einen eigene Klasse, die von der Klasse SwingWorker
vererbt. Dabei sind folgende Dinge zu
implementieren:
doInBackground()
muss überschrieben werden und darin sollte die Funktionalität
aufgerufen werden, die das Logging in das TextArea Element durchführt. Zusätzlich überprüfe ich hier,
ob ein Fehler bei der Funktionalität aufgetreten ist. In diesem Fall wird ein dementsprechendes Flag gesetzt und
die Fehlermeldung im GUI ausgegeben.
done()
sollte überschrieben werden, weil ich dann im Falle der erfolgreichen
Ausführung (hier wird der Wert des Flags abgefragt) eine Erfolgsmeldung im GUI ausgebe.
Die Implementierung meiner Klasse kann man sich hier ansehen.
Ich hatte die Aufgabenstellung eine exe-Datei auf Windows mit verschiedenen Parametern abhängig von Daten in einer Datenbank aufzurufen. Dabei hat sich für den Aufruf folgender Code als praktisch erwiesen:
private void callExecutable(String pathToExecutable, boolean showOutputOfExecutable) { ProcessBuilder builder = new ProcessBuilder(pathToExecutable, "argument1", "argument2", ...); if (showOutputOfExecutable) { builder.inheritIO(); } builder.redirectErrorStream(true); Process p = builder.start(); p.waitFor(); System.out.println("Executable has done its work!"); }
Erstellung des ProcessBuilders
Mit Hilfe der Klasse ProcessBuilder
lässt sich wesentlich sicherer eine ausführbare Datei starten
als mit Runtime.getRuntime().exec("myCommand");
. Wichtig dabei ist, dass man jedes Argument als eigenen
Parameter im Konstruktor angibt. Würde man beispielsweise in der Commandline den Befehl
myExe.exe --logfile myLog.log
absetzen, so
müsste man dies im Javacode folgendermaßen machen:
ProcessBuilder builder = new ProcessBuilder("myExe.exe", "--logfile", "myLog.log");
Folgender Aufruf hingegen führt zu Fehlern:
ProcessBuilder builder = new ProcessBuilder("myExe.exe --logfile myLog.log");
Ausgabe der ausführbaren Datei im Java-Prozess sichtbar machen
Wenn die Ausgabe der ausführbaren Datei auch im Commandline Fenster, in dem die Java-Anwendung läuft, sichtbar sein soll, so muss man
die Methode inheritIO()
des ProcessBuilder
Objekts aufrufen. Sollen auch Fehler-Resultate der ausführbaren
Datei im Commandline Fenster ersichtlich sein, so muss man die Methode redirectErrorStream(true)
aufrufen.
Starten und Warten auf die ausführbare Datei
Schließlich startet man mit der Methode start() des ProcessBuilder
Objekts die ausführbare Datei und erhält ein
Process
Objekt zurück. Die ausführbare Datei kann nun parallel zum aktuellen Java-Programm laufen. Soll das Java-Programm
allerdings auf die Fertigstellung des gestarteten Prozesses warten, so muss man die Methode waitFor()
des Process
Objekts
aufrufen.
Die Aufgabenstellung war, eine ZIP-Datei einzulesen und deren Dateien als byte Arrays in der Datenbank abzuspeichern. Dafür verwendete
ich ursprünglich die Java-internen Klassen ZipInputStream
und ZipEntry
. Mit folgendem Code wurde die ZIP-Datei
ausgelesen:
public void parseZipFile(ZipInputStream zipInputStream) { try { ZipEntry zipEntry; while ((zipEntry = zipInputStream.getNextEntry()) != null) { if (zipEntry.isDirectory()) { continue; } //copy contents of zipEntry to byte array } } catch (Exception exception) { exception.printStackTrace(); } }
Dies funktionierte auch mit den ZIP-Dateien, die ich ursprünglich für meine Tests verwendete. Allerdings trat dann bei einigen
ZIP-Dateien der User der Fehler malformed input off : 11, length 1
auf. (In meinem Fall trat der Fehler in der
Methode decodeUTF8_UTF16
auf.)
Meine Online-Recherche zu diesem Fehler führte mich wie so oft zu
einem Stackoverflow-Artikel,
der mir die Verwendung der "API-kompatiblen" commons-compress Library empfahl.
Dadurch änderte sich mein Code zu folgendem:
public void parseZipFile(ZipArchiveInputStream zipInputStream) { try { ArchiveEntry zipEntry; while ((zipEntry = zipInputStream.getNextEntry()) != null) { if (zipEntry.isDirectory()) { continue; } //copy contents of zipEntry to byte array } } catch (Exception exception) { exception.printStackTrace(); } }
Mit dieser Änderung ließen sich dann auch ohne Probleme die ZIP-Dateien der User bearbeiten.
Mein Fazit war daher, dass die commons-compress Library vielfältigere ZIP-Formate unterstützt als die Java-interne API und dass man
wohl gut beraten ist, für solche Aufgaben diese Library zu verwenden.
Wenn man ZIP-Dateien einliest, dann will man früher oder später meist auch welche erstellen. In meinem Fall wollte ich aus einer Liste von byte Arrays eine ZIP-Datei mit je einem Dateieintrag pro byte Array erstellen. Und letztlich schrieb ich folgenden Code:
public byte[] createZipFile(List<String> fileNames, List<byte[]> files) throws Exception { try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ZipArchiveOutputStream zipOutputStream = new ZipArchiveOutputStream(byteArrayOutputStream)) { for (int i = 0; i < files.size(); i++) { ZipArchiveEntry entry = new ZipArchiveEntry(fileNames.get(i)); byte[] file = files.get(i); entry.setSize(file.length); zipOutputStream.putArchiveEntry(entry); zipOutputStream.write(file); zipOutputStream.closeArchiveEntry(); } zipOutputStream.finish(); zipOutputStream.close(); byteArrayOutputStream.close(); return byteArrayOutputStream.toByteArray(); } }
Dabei halfen mir folgende Stackoverflow-Artikel:
Dies ist eine kleine Sammlung von Best Practices bezüglich Java Coding, die ich im Laufe der Zeit für mich gesammelt habe.
toString
-Methode bei Objekten, die null
sein könntenWenn man ein toString()
auf ein Object
ausführt und dabei eine
NullPointerException
vermeiden will, kann man wie folgt vorgehen:
Object value = getObject(); String stringValue = value == null ? "NULL" : value.toString();
Seit Java 7 gibt es allerdings die Klasse Objects
, die das eleganter löst:
Object value = getObject(); String stringValue = Objects.toString(value, "NULL");
null
ist
Will man eine Exception werfen, wenn ein Objekt null
ist, kann man folgendes machen:
Object value = getObject(); if (value == null) { throw new Exception("My message!"); }
Mit der Einführung der Klasse Objects
in Java 7 kann man dies aber auch so machen:
Object value = getObject(); Objects.requireNonNull(value, "My message!");
Wenn man einen String mit einer Konstanten vergleicht, dann muss man ja immer davon ausgehen,
dass der String auch null
sein kann. Daher macht man das oft so:
if (myString != null && myString.equals(CONSTANT_STRING)) { ... }
Wenn man aber die Konstante an die erste Stelle des Vergleichs stellt, spart man sich aber die
separate Abprüfung auf null
:
if (CONSTANT_STRING.equals(myString)) { ... }
Will man eine Liste von Strings zu einem String verknüpfen, kann man folgende Schleife anwenden:
List<String> myStringList = getStringList(); StringBuilder stringBuilder = new StringBuilder(); if (myStringList != null) { for (int i = 0; i < myStringList.size(); i++) { stringBuilder.append(myStringList.get(i)); if (i < myStringList.size() - 1) { stringBuilder.append(";"); } } } return stringBuilder.toString();
Allerdings bietet die Klasse String mit der Methode join
seit Java 8 genau das, was man braucht.
return String.join(";", getStringList());
try
-with-resources verwenden anstatt von finally
-BlöckenWenn man beispielsweise Daten von Dateien eingelesen hat, bin ich meist folgendermaßen vorgegangen:
BufferedReader reader = new BufferedReader(new FileReader(fileName)); try { String line; while((line = reader.readLine()) != null) { System.out.println(line); } } catch (Exception e) { //handle exception } finally { reader.close(); }
Mit der Einführung des try
-with-resources Statements in Java 7 erspart man sich
den finally
Block und man kann die Resourcen genau für den try-Block angeben, wo sie gebraucht werden.
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) { String line; while((line = reader.readLine()) != null) { System.out.println(line); } } catch (Exception e) { //handle exception }
Mit Java 8 wurden Lambda Expressions eingeführt. Diese ermöglichen es, funktionale Parameter in Java in kurzer Schreibweise darzustellen. Am besten beginne ich mit einem Beispiel.
Angenommen es gibt folgendes Interface, das nur eine Methode namens performAction
ausführt.
interface Action<T> { void performAction(T object); }
Und angenommen man will diese Action dann für jedes Element einer Liste oder einer Collection
ausführen,
dann könnte man folgende Methode schreiben:
private <T> void performActionForAll(Collection <? extends T> objects, Action<T> action) { for (T object : objects) { action.performAction(object); } }
Dann könnte man diese Methode mit folgendem Aufruf durchführen
List<MyItem> myItems = getMyItems(); performActionForAll(myItems, new Action<MyItem>() { public void performAction(MyItem myItem) { System.out.println(myItem); } });
Mit Hilfe von Lambda Expressions kann man dies aber abkürzen wie folgt:
List<MyItem> myItems = getMyItems(); performActionForAll(myItems, myItem -> System.out.println(myItem));
Dies funktioniert allerdings nur, wenn das Interface Action
nur eine Methode beinhaltet.
Übernimmt diese Methode des Interfaces nur einen Parameter, so kann man die Schreibweise noch weiter abkürzen:
List<MyItem> myItems = getMyItems(); performActionForAll(myItems, System.out::println);
Würde die Methode performAction
mehrere Parameter übernehmen, so müsste man den
Aufruf mit Lambda Expression so gestalten:
List<MyItem> myItems = getMyItems(); performActionForAll(myItems, (myItem, param2, param3) -> System.out.println(myItem));
Streams ermöglichen das Bearbeiten von Objekten von Listen, Arrays oder anderen Collections mit
kurzen deklarativen Befehlen, die manchmal auch ein wenig an SQL erinnern. Man spart sich dabei vor allem
das Schreiben von eigenen Schleifen, was den Code einfacher und lesbarer macht.
Das Verwenden von
Streams teilt sich dabei in drei Teile:
Zur Erstellung eines Streams kann wie folgt vorgegangen werden:
myCollection.stream()
liefert einen Stream vom Typ der Collection mit deren Inhalten.
Arrays.stream(myArray)
liefert einen Stream vom Typ des Arrays mit dessen Inhalten.
IntStream.range(10, 100)
liefert einen Stream vom Typ Int mit Zahlen von 10 bis inklusive 99.
Wurde ein Stream erstellt, so können zahlreiche Operationen darauf angewandt werden. Hier sind nur einige Beispiele angeführt, um zu veranschaulichen, was man tun kann.
myStream.forEach(x -> System.out.println(getStringProperty()))
myStream.map(x -> getStringProperty().toUpperCase())
myStream.filter(x -> getIntProperty() < 10)
myStream.sorted()
myStream.distinct()
Nachdem auf dem Stream die notwendigen Operationen ausgeführt wurden, kann das Ergebnis mittels des Terminators in die richtige Form gebracht werden bzw. das eigentliche Ergebnis errechnet werden. Hier wieder nur einige Beispiele, um das zu veranschaulichen.
myStream.min()
(Es wird ein Optional des Typs zurückgegeben.)
myStream.max()
(Es wird ein Optional des Typs zurückgegeben.)
myStream.findFirst()
(Es wird ein Optional des Typs zurückgegeben.)
myStream.findAny()
(Es wird ein Optional des Typs zurückgegeben.)
myStream.collect(Colletors.toList())
myStream.collect(Colletors.toUnmodifiableList())
Map<String, String> map = myStream.collect(Collectors.toMap(x -> x.getStringProperty1(), x -> x.getStringProperty2()))
myStream.collect(Collectors.joining(", "));
List<MyItem> myItems = getMyItems(); MyItem myMinimumItem = myItems.stream() .min(Comparator.comparingDouble(MyItem::getDoubleValue)) .orElseThrow();
Dieser Code liefert das Objekt zurück, das den kleinsten double
-Wert bei der Methode
getDoubleValue()
aufweist. Da die Operation min
ein Optional zurückliefert muss man noch
eine Funktion anhängen, um das eigentliche Objekt zu erhalten. In diesem Fall wird orElseThrow
angewandt, um einen Fehler zu werfen, wenn kein Element vom min
-Terminator gefunden werden konnte.
List<MyItem> myItems = getMyItems(); Set<String> stringProperties = myItems.stream() .map(MyItem::getStringProperty1) .collect(Collectors.toSet());
Dieser Code ruft von allen Objekten eine String-Eigenschaft ab und sammelt diese in einem Set
.
List<MyItem> myItems = getMyItems(); Map<String, Double> properties = myItems.stream() .collect(Collectors.toMap(x -> x.getStringProperty1(), x -> x.getDoubleValue()));
Hier wird von den Objekten eine String-Eigenschaft als Key und ein double
-Wert als Value in einer
Map
abgespeichert.
List<MyItem> myItems = getMyItems(); List<MyItem> filteredItems = myItems.stream() .filter(x -> x.getDoubleValue() > 10) .collect(Collectors.toList());
In diesem Beispiel werden all jene Objekte deren Methode getDoubleValue()
einen Wert größer 10 zurückgibt,
in eine neue Liste gegeben.