Java Programme an die Windows 10 Taskleiste anpinnen

Damit ich meine Java Programme auf Windows 10 schnell ausführen kann, pinne ich sie gerne an die Taskleiste.

Deutsche Umlaute

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.

Konsolenausgabe in TextArea umleiten mit Hilfe von SwingWorker

Szenario

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.

Lösungsansatz

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.

  1. Man schreibt eine Klasse, die von der Klasse 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.)
  2. Beim Start der Funktionalität, die in das TextArea-Element geloggt werden soll, muss der Standard-Output verändert werden. Man sollte sich aber die Standardwerte in einer Variable abspeichern, damit man das wieder zurücksetzen kann.
              myFunctionality(TextAreaOutputStream pOutputStream) {
                //1. redirect the output stream
                PrintStream pStandardOut = System.out;
                if (pOutputStream != null) {
                  PrintStream pPrintStream = new PrintStream(pOutputStream);
                  System.setOut(pPrintStream);
                }
              
  3. Am Ende der Funktionalität sollte man die Standard Einstellungen wieder herstellen.
                //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.

Verbesserung

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:

  1. Die Methode 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.
  2. Die Methode 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.

Aufruf von ausführbaren Dateien aus einem Java-Programm heraus

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!");
      }
    

Erklärungen zum Code

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.

Lesen und Erstellen von ZIP-Dateien

Auslesen einer ZIP-Datei

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.

Erstellen einer ZIP-Datei

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:

Eine Sammlung von Best Practices für Java Coding

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önnten

Wenn 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");
    

Exceptions werfen, wenn ein Objekt 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!");
    

String-Vergleich auf Konstanten

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)) {
        ...
      }
    

Schleifen bei String-Concatenation vermeiden

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öcken

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

Lambda Expressions und Streams

Eine kurze Einführung in Lambda Expressions

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.

Beispiel und Aufruf ohne Lambda Expression

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

Aufruf mit Lambda Expression

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.

Aufruf mit Lambda Expression mit nur einem Parameter

Ü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);
    

Aufruf mit Lambda Expression mit mehreren Parametern

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

Eine kurze Einführung zu Streams

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:

Erstellen von Streams

Zur Erstellung eines Streams kann wie folgt vorgegangen werden:

Operationen

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.

Terminatoren

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.

Beispiele für Anwendungen von Streams

Ermittlung des Objekts mit einem Minimum-Wert

      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.

Abfragen einer bestimmten Eigenschaft der Objekte

      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.

Abfragen von zwei Eigenschaften der Objekte

      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.

Filtern von Objekten

      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.