Dienstag, 3. September 2019

Tests in JUnit deaktivieren

Tests können in JUnit temporär deaktiviert werden, d.h. sie werden einfach nicht ausgeführt. Natürlich sollte man nicht wahllos irgendwelche Tests ausschalten, sondern sich schon etwas dabei denken. Ein Grund könnte z.B. sein, dass man über fehlgeschlagene Tests nicht mehr den Überblick behalten kann und einfach einige ausblende möchte, an denen man im Moment sowieso nicht arbeiten möchte.

Wichtig ist, dass die Tests dann auch irgendwann wieder eingeschaltet werden. JUnit 4 und JUnit 5 bieten dazu jeweils unterschiedliche Annotationen an. Das erste Code-Beispiel gilt für JUnit 4.

@Test
@Ignore("Dieser Test löst eine Endlosschleife aus")
public void test() {
    // Testcode
}


Die Annotation @Ignore sorgt dafür, dass JUnit den Test nicht ausführt. Allerdings sollten Sie immer eine Erklärung dazu schreiben, damit man sieht, warum der Test deaktiviert wurde. Schließlich sollen auch die deaktivierten Tests nicht in Vergessenheit geraten. Aber dafür sorgt auch die Darstellung von JUnit selbst. Dazu gleich. Sehen wir uns erst mal noch die Annotation in JUnit 5 an:

@Test
@Disabled("Dieser Test löst eine Endlosschleife aus")
public void test() {
    // Testcode
}
Im Screenshot sehen Sie, dass der Test nicht vollkommen verschwindet, sondern von JUnit immer noch angezeigt wird. So kann er nicht total und ganz vergessen werden.




Sonntag, 25. August 2019

Properties-Dateien lesen und schreiben

Was sind properties-Dateien?

Properties-Dateien in Java sind eine sehr nützliche Angelegenheit. Es sind einfache Textdateien, die einem bestimmten Aufbau folgen. Und zwar bestehen sie aus Schlüssel-Wert-Paaren. Es gibt also Strings, die sogenannten Schlüssel, denen andere Strings, die Werte, zugeordnet sind. So könnte eine properties-Datei aussehen:

language=de
resolution=850x1024
startfile=/usr/local/anyFile


Das Beispiel zeigt auch, wofür properties-Dateien gut eingesetzt werden können: zum Speichern von Konfigurationsdaten. Sie werden aber auch bspw. beim Internationalisierungsmechanismus von Java eingesetzt. Wenn ich wissen möchte, mit welcher Auflösung das Programm angezeigt werden soll, lese ich den Wert des Schlüssels resolution. Wenn der Benutzer die Auflösung verändert, ändert mein Programm den Wert des Schlüssels resolution.

Mehr Zauberei sind properties-Dateien nicht. Da stellt sich jetzt eine wichtige Frage: Wie lese und schreibe ich die Dinger? Natürlich können Sie die Mechanismen verwenden, mit denen Sie jede Textdatei lesen und schreiben. Aber Java bietet gerade für properties-Dateien eine Abkürzung.

Lesen von properties-Dateien


Der folgende Code zeigt wie eine properties-Datei gelesen wird.

    public static String findValueOfKey(String key) {
        Properties properties = new Properties();
        FileInputStream in = null;
        try {
            in = new FileInputStream("/usr/local/propertiesFile.properties");
            properties.load(in);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }


        String value = properties.getProperty(key);
        return value;
    }


Das wichtigste Konzept ist die Klasse Properties. Diese stellt die Schnittstelle zu den properties-Dateien dar. Wir lesen die Datei in einen FileInputStream, laden diesen in das Properties-Objekt und lassen uns dann den Wert zu dem übergebenen Schlüssel geben. Das war es auch schon. Wie immer bei Dateiverarbeitung mit Java ist leider einiges an Exception-Handling erforderlich.

Schreiben von properties-Dateien

Wenn wir Werte in die Schlüssel der properties-Datei schreiben möchten, ist das auch nicht mehr Aufwand. Wir programmieren eigentlich nur den umgekehrten Weg: von dem Properties-Objekt in die Datei.

public static void writeValueOfKey(String key, String value) {
    Properties properties = new Properties();
    properties.setProperty(key, value);  
    FileOutputStream out;
    try {
        out = new FileOutputStream("/usr/local/propertiesFile.properties");
        properties.store(out, null);
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}


Der Code sieht wieder sehr ähnlich aus zum Lesen. Wir arbeiten wie gehabt mit dem Properties-Objekt und setzen den Wert des Schlüssels. Dann speichern wir die ganze Geschichte mit store() in der Datei.

Sie sehen, dass die Bearbeitung von properties-Dateien ziemlich einfach ist. Und deswegen sind properties-Dateien ein so nützliches Werkzeug, um Informationen schnell auf die Festplatte zu kriegen, vorausgesetzt natürlich, man kann sie in Schlüssel-Wert-Paare pressen.

Freitag, 23. August 2019

Textdateien Zeile für Zeile lesen

Einfache Textdateien mögen auf den ersten Blick etwas mittealterlich wirken, aber sie werden dennoch häufig bei der Programmentwicklung verwendet. Es ist eben leicht, einfachen Text in diesen Dateien unterzukriegen und deswegen sollte man sie nicht unterschätzen. Eine besondere Form, die properties-Dateien, stelle ich in einem anderen Beitrag vor.

Die Frage ist jetzt wie wir in eine Textdatei schreiben und wieder aus ihr lesen. In diesem Beitrag beschäftigen wir uns mit dem Lesen.

Die ganze Datei in den Arbeitsspeicher laden

Für kleine Dateien ist dies eine ganz gute Methode: Wir klatschen einfach den Inhalt der kompletten Textdatei in den Arbeitsspeicher. Das sieht dann so aus wie im folgenden Beispiel zu sehen:

public static void printTextFileOnScreen(String fileName) {
    Path path = Paths.get(fileName);
    List<String> lines = Files.readAllLines(path);
    for (String line : lines) {
       System.out.println(line);
    }

}

Dieser Code lädt die gesamte Textdatei in den Arbeitsspeicher und die Zeilen in ein String-Array. Dann wird das Array durchlaufen und jede Zeile der Textdatei wird ausgegeben. Der Code ist einfach und knackig. Der Nachteil liegt auch auf der Hand und wurde schon erwähnt: Für größere Dateien ist diese Vorgehensweise absolut ungeeignet, da wir die komplette Datei in den Arbeitsspeicher laden.

BufferedInputReader


Obwohl die vorherige Methode für kleine Dateien durchaus anwendbar ist, möchte ich dennoch empfehlen, den im folgenden vorgestellten BufferedInputReader immer zu verwenden. Denn wie genau definiert sich eine Datei als "klein"? Aber sehen wir uns erst einmal ein bisschen Code an.

public static void printFileOnScreen(String filename) {
    BufferedReader bufferedReader = null;
   
    try {
        bufferedReader = new BufferedReader(new FileReader(filename));
        String line = null;
       
        while ((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
        }
    } catch (FileNotFoundException ex) {
        ex.printStackTrace();
    } catch (IOException ex) {
        ex.printStackTrace();
    } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}


Als erstes fällt auf, dass wir erheblich mehr schreiben müssen. In einem anderen Beitrag werde ich noch eine Möglichkeit vorstellen, wie man diese Menge an Boilerplate-Code etwas reduzieren kann. Was geschieht hier? Wir erzeugen als erstes ein FileReader-Objekt, mit dem wir auf die Datei zugreifen. FileReader stellt nur die absoluten Basisfunktionen zur Verfügung und aus diesem Grund stecken wir ihn in einen BufferedReader, dessen Interface umfangreicher ist. Dann laufen wir in einer Schleife durch die Datei. In jedem Schleifendurchlauf holen wir uns mit readLine() die aktuelle Zeile.

Im Gegensatz zu der vorherigen Lösung haben wir also immer nur die aktuelle Zeile im Arbeitsspeicher. Die Schleife läuft, solange wir noch Zeilen lesen. Wenn wir die letzte Zeile erreicht haben, gibt readLine() null zurück und die Schleife wird beendet. Vergessen Sie nicht, das BufferedReader-Objekt am Ende wieder brav zu schließen. 

Die geheimnisvolle SerialVersionUUID

Sie kennen das bestimmt: Sie programmieren mit einer IDE wie z.B. Eclipse und schreiben eine Klasse, die das Interface Serializable implementiert. Und auf einmal erhalten Sie von der IDE eine Warnung, die gerne eine SerialVersionUUID hätte. Was hat es denn damit auf sich?

Beim Serialisieren und Deserialisieren kann es zu folgendem Problem kommen: Sie serialisieren ein Objekt z.B. in eine Datei. Dann verändern Sie das Programm und möchten das Objekt wieder deserialisieren. Aber der aktuelle Stand des deserialisierten Objekts passt nicht mehr zu dem neuen Stand der Klasse in dem Programm, weil sie bspw. ein Attribut entfernt haben. Das Problem ist, dass beliebig viel Zeit zwischen dem Serialisieren und dem Deserialisieren liegen kann.

Die SerialVersionUUID ist ein Sicherheitsmechanismus, mit dem solche Probleme vermieden werden sollen. Beim Schreiben, also beim Serialisieren, wird die UUID mit gespeichert. Und beim Lesen, also beim Deserialisieren, überprüft Java, ob die gelesene UUID zu der aktuellen passt. Falls nicht, wird die Deserialisierung verweigert.

Standardmäßig berechnet sich diese UUID als Hashwert aus dem Inhalt der Klasse. Also wenn Sie das Interface Serializable implementieren und dann nichts weiter tun, wird Java einen Hashwert verwenden. Nun ist ein Hashwert nicht ohne Probleme. Manchmal gibt es Änderungen, die sich im Hashwert und somit in der UUID widerspiegeln, aber auf die Serialisierung keine Auswirkung haben. Dennoch würde gelesene Wert nicht mehr zum berechneten passen und Java würde die Deserialisierung verweigern.

Ein weiteres Problem ist, dass die Berechnung der UUID abhängig ist vom Compiler. Und somit könnte sie sich von Umgebung zu Umgebung unterscheiden, obwohl es eine Äderungen an der betroffenen Klasse gegeben hat. 

Und an dieser Stelle kommt die Warnung der IDE ins Spiel: Sie macht darauf aufmerksam, dass man am besten eine eigene SerialVersionUUID definiert. Dazu genügt es, einen privaten long-Wert zu definieren mit einem beliebigen Versions-Wert. Die meisten IDEs bieten an, einen Wert zu generieren. Diese Möglichkeit sollte man dann auch nutzen.

Also zusammenfassend lässt sich sagen: Sie müssen keine eigene SerialVersionUUID definieren, aber Sie sind sicherer und frustfreier unterwegs, wenn Sie es tun. Nutzen Sie dazu einfach die Möglichkeiten, die Ihnen die IDE anbietet und Sie haben damit keinerlei Arbeit mehr.

Serialisieren und Deserialisieren

Ein wichtiges Konzept in Java ist die Serialisierung und Deserialisierung. Dabei werden Objekte so umgewandelt, dass sie in eine Datei auf die Festplatte geschrieben werden können. Ein Objekt speichern ist ja im ersten Moment gar nicht so trivial wie es klingt: Die Daten des Objekts müssen irgendwie in eine Folge aus Bits umgewandelt werden. Und das ist eigentlich die korrektere Aufgabe der Serialisierung: Umwandlung von Objekten in Bits.

Das braucht man natürlich, um Objekte in Dateien zu speichern, aber die Serialisierung wird auch beim Senden von Objekten über ein Netzwerk benötigt (auch als Bit-Strom) oder in Datenbanken.

Das Schöne an Java ist: Zwar ist die Serialisierung als solche nicht trivial, aber die Benutzung des Serialisierungsmechanismus in Java ist sehr einfach. Es genügt, wenn eine Klasse das Interface java.io.Serializable implementiert wie im nächsten Beispiel zu sehen.

public class MySerializableClass implements Serializable {
    private int aValue;
    private Person person;
}



Serializable ist ein reines Marker-Interface, d.h. es markiert einfach eine Klasse für eine bestimmte Aufgabe. Wir müssen also keine Methoden implementieren. Java kümmert sich um den Rest. Ihre Klassen müssen auch keine weiteren Voraussetzungen erfüllen. Eine leichtere Benutzung ist kaum denkbar.

Im Beispiel oben fällt Ihnen auf, dass die Klasse eine Referenz auf ein Objekt enthält. Aber auch das ist kein Problem. Solange diese Klasse das Interface Serializable implementiert, kann Java damit umgehen.

Transiente Attribute

Manchmal gibt es Attribute, die nicht mit serialisiert werden sollen. Das kann verschiedee Gründe haben. Wenn wir über Netzwerk Daten übertragen, möchten wir z.B. Bandbreite sparen.  Oder wenn wir das Objekt in einer Datei speichern möchten, möchten wir vielleicht bestimmte Informationen nicht speichern. Aus welchen Gründen auch immer: Es gibt eine einfache Möglichkeit, einzelne Attribute von der Serialisierung auszuschließen. Das nächste Beispiel zeigt den Code.

public class MySerializableClass implements Serializable {
    private int aValue;
    private transient Person person;
}


Das Attribut person ist mit dem Schlüsselwort trasient versehen worden. Das heißt, dass es von der Serialisierung ausgeschlossen wird.

Überprüfungen auf null mit requiresNonNull

Ein bekanntes Phänomen in Java-Programmen sind die unseligen Überprüfungen auf null:

if (myObject == null) {
    throw new MyException();

}

Diese Überprüfungen sind nicht nur unpraktisch. Sie blähen den Code auch sehr auf. So hat man dann schnell mal mehrere if-Anweisungen hinteinander oder stark ineinander verschachtelte ifs. Beides trägt nicht unbedingt zur Lesbarkeit des Codes bei. Natürlich ist es auch keine Alternative, diese Überprüfungen auf null-Werte wegzulassen.

Nicht viele Programmierer wissen, dass Java noch einen weiteren Weg bietet mithilfe der Klasse Objects und deren statischer Methode requireNonNull(). Diese Methode existiert in drei Varianten, die wir uns jetzt alle einmal kurz ansehen möchten.

Die Grundform ist in dem folgenden Listing zu sehen:

String myString = null;       
Objects.requireNonNull(myString);


In diesem Fall wird eine NullPointerException geworfen. Es gibt keinen Unterschied zu einem Methoden-Zugriff auf ein null-Objekt.

Eine Alternative ist im folgenden Beispiel zu sehen:

String myString = null;       
Objects.requireNonNull(myString, "String ist null");


Wir können der Methode requireNonNull() einen String als zweiten Parameter mitgeben. Dieser enthält eine Fehlermeldung, die der NullPointerException mitgegeben wird.

Die dritte Möglichkeit sehen Sie im folgenden Code-Ausschnitt:

String myString = null;
Objects.requireNonNull(myString, () -> "Sie haben null mitgegeben");


Statt einem String kann der Methode auch ein Supplier-Objekt mittels eines Lambda-Ausdrucks mitgegeben werden. Dieser Supplier gibt die Fehlermeldung zurück, die zusammen mit der Exception ausgegeben werden soll. Diese letzte Variante kann etwas schneller sein als die vorherige, da die Fehlermeldung nur erzeugt wird, wenn sie tatsächlich benötigt wird. Wobei der Geschwindigkeitsunterschied in den meisten Fällen wohl kaum bemerkbar sein dürfte.

Letztendlich ermöglichen es diese drei Methoden, die drei Zeilen umfassende if-Anweisung von oben auf eine Zeile zu reduzieren. Etwas unschön ist, dass sie NullPointerExceptions wirft und man deswegen unter Umständen auch NullPointerExceptions abfangen muss, was ja nicht unbedingt als schöner Stil gilt. Es bleibt Ihnen überlassen wie Sie auf null prüfen, aber Sie sollten die hier vorgestellte Methode auf jeden Fall kennen, weil sie eingesetzt wird, z.B. finden Sie sie häufig im Code des JDK.

Veränderliche und unveränderliche Objekte

Die meisten Objekte in Java sind veränderlich (eng. mutable). Damit ist gemeint, dass der Status der Objekte, also die Werte ihrer Attribute, verändert werden kann. Veränderliche Objekte haben den Vorteil, dass wir beliebig mit ihnen verfahren und arbeiten können, weil sie veändert werden können. Sie haben allerdings den Nachteil, dass wir beliebig mit ihnen verfahren und arbeiten können, weil sie veändert werden können. Sie sehen hier, dass ein Vorteil auch zu einem Nachteil werden kann. Warum ist das so?

Manchmal möchte man keine Objekte haben, deren Status sich verändern kann. Die Werte der Attribute sollen immer konstant bleiben. Ein erster naiver Ansatz, um dies zu erreichen, könnte der folgende sein:

final Person meinePerson = new Person("Ali", "Baba");

Mit final können Konstanten definiert werden. Also liegt irgendwo der Gedanke nahe, dass nun auch das Objekt konstant ist und damit auch seine Attribute. Dieser Eindruck täuscht. Was hier konstant ist, ist die Referenz. Der folgende Code wird also vom Compiler nicht akzeptiert werden:

final Person meinePerson = new Person("Ali", "Baba");
final Person meineAnderePerson = new Person("Elle", "Fant");
meinePerson = meineAnderePerson;


Wir verändern in der dritten Zeile die Referenz und genau das wird durch das Schlüsselwort final verhindert. Was aber weiterhin funktionieren wird und was wir eigentlich verhindern wollten, zeigt der folgende Code-Ausschnitt:

final Person meinePerson = new Person("Ali", "Baba");
meinePerson.setVorname("Elle");
meinePerson.setNachname("Fant");


Also dieses schöne Schlüsselwort final führt uns nicht in die richtige Richtung. Schade. Um ein Objekt wirklich unveränderlich hinzubekommen, müssen wir uns etwas mehr Arbeit gönnen. Ein Objekt ist unveränderlich, wenn die Werte seiner Attribute nicht verändert werden können. Die Klasse muss also die folgenden Regeln erfüllen:

  1. Die Klasse darf keine Methoden anbieten, die schreibende Zugriffe auf die Attribute ermöglichen wie z.B. Setter.
  2. Alle Attribute sind privat und final.
  3. Wenn die Klasse Referenzen auf andere Objekte enthält, müssen diese Objekte natürlich auch unveränderlich sein.
  4. Wenn die Klasse eine Collection enthält, darf es keine Methoden geben, die das Verändern der Elemente dieser Collection ermöglichen, also keine Methoden zum Löschen, Hinzufügen usw.
  5. Die Klasse selbst ist als final deklariert. Damit können keine Unterklassen gebildet werden, die eventuell unerwünschtes Verhalten in die Objekte einschleusen.

Beispiele für unveränderliche Objekte im JDK sind die Wrapperklassen wie Integer. Sie bieten keinerlei Möglichkeit an, ihre Werte noch einmal zu verändern, nachdem die Objekte einmal erzeugt wurden.

Welche Voteile haben diese unveränderlichen Objekte jetzt?

  1. Ihre Werte können nicht aus Versehen verändert werden. Da in Java Objekte nur als Referenzen durch das Programm geschoben werden, ist manchmal nicht leicht zu sehen, dass man tatsächlich ein Objekt verändert. Dazu gleich noch mehr.
  2. Sie sind leichter zu entwickeln, da Klassen-Invarianten nur einmal überprüft werden müssen. Wenn man im Konstruktor prüft, ob die übergebenen Attributwerte korrekt sind, muss man keine weitere Überprüfungen mehr einbauen.
  3. Der interne Status bleibt konsistent, auch nachdem eine Exception geworfen wurde. Dies ist nicht bei allen veränderlichen Objekten der Fall.
  4. Unveränderliche Objekte sind automatisch thread-sicher, so dass man sich um Synchronisierung keine Gedanken machen muss.

Was meine ich nun damit, dass Programmierfehler vermieden werden können? Sehen Sie sich mal das nächste Code-Beispiel an.

public Person getMyPerson() {
    // Hier wird eine Person erzeugt und irgendwas tolles wird mit der Person getan.
    return person;
}

public void tueEtwasMitPerson(Person person) {
    person.setVorname("Elle");
    person.setNachame("Fant");

}

In Java wird oft übersehen, dass Objekte immer Referenzen sind. Die Methode getMyPerson() gibt also eine Referenz auf das Originalobjekt zurück. Die Methode tueEtwasMitPerson() erhält eine Referenz auf ein Objekt und wir verändern das Originalobjekt. Das kann erwünscht sein; das kann aber auch unerwünscht sein. Und genau an dieser Stelle greifen die unveränderlichen Objekte. Wenn Sie sicherstellen möchten, dass es keine unerwünschten Veränderungen geben kann, verwenden Sie unveränderliche Objekte.

Nun haben die unveränderlichen Objekte in Java einen immensen Nachteil: Klassen müssen von vornherein so konzipiert werden, dass ihre Objekte unveränderlich sind. Es ist nicht möglich, ein Objekt a der Klasse A veränderlich zu definieren und ein Objekt b der Klasse A veränderlich. Sie müssen die Klasse mit den oben beschriebenen Maßnahmen unveränderlich konzipieren. Daher kann es in manchen Fällen sinnvoll sein, zwei Klassen in seinem Programm zu haben wie z.B. Person und PersonImmutable.

Dieser Nachteil löst sich aber auch wieder in einen Vorteil auf: Sie können in Java natürlich auch nur teil-veränderliche Klassen entwickeln, was man sowieso tut, wenn man Attribute anständig kapselt.

Mit diesem Beitrag wollte ich nicht sagen, dass Objekte unveränderlich sein sollten. In vielen Fällen möchte man Attribute ja verändern können. Dennoch sollten Sie die Tatsache, was veränderlich bedeutet, unbedingt im Kopf behalten. Da sind schon besseren Programmieren mit Java schwere Fehler unterlaufen. Die Problematik ist wichtig und manchmal kann es wirklich ganz sinnvoll sein, vollkommen unveränderliche Objekte zu definieren.