image

Wolfgang Rinser

2. Auflage

Objekte Tools Essentials

Band 1: Objekte

Softwarekonstruktion mit Java 9

Image

© 2017 Wolfgang Rinser

Verlag: tredition GmbH, Hamburg

978-3-7439-4057-4 (Paperback)

978-3-7439-4058-1 (Hardcover)

978-3-7439-4059-8 (e-Book)

Juli 2017:2. Auflage

Das Werk, einschließlich seiner Teile, ist urheberrechtlich geschützt. Jede Verwertung ohne Zustimmung des Autors ist unzulässig. Dies gilt insbesondere für die elektronische oder sonstige Vervielfältigung, Übersetzung, Verbreitung und öffentliche Zugänglichmachung.

Bibliografische Information der Deutschen Nationalbibliothek:

Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

Inhalt

ÜBERSICHT

Fahrplan und Rapid-Coding

Das erste Anliegen - Verständliche Texte

Konventionen und Verabredungen

DAS SOFTWARE-UNIVERSUM

1. Das Buch der unbrauchbaren Softwareteile

2. Ein Anfang – aber nicht mehr

EINE KLEINE JAVA-RUNDREISE – TEIL 1

1. Intro

2. 90 Prozent - Ein Plan und ein Stück Code - Was ein Entwickler wissen muss

3. Methoden

3.1. Die Signatur von Methoden

3.2. Werden Argumente und Returnwerte kopiert?

3.3. Returntyp und return Statement

3.4. Aufteilung von Code in Methoden

3.5. Variabel lange Argumentlisten

3.6. Argumentprüfung

4. Lokale Variable, Blöcke, Scope und final

5. Die Grundregel für zusammengesetzte Ausdrücke

6. Weitere elementare Regeln und Begriffe

7. Zuweisung

8. Kombinierte Zuweisungen

9. Inkrement und Dekrement

10. Stringverknüpfung (+)

11. Bedingung (if/else)

12. Verzweigung (switch)

13. Der new-Operator

14. Gleichheits- und Identitäts-Operator

15. Logische Operatoren, Vergleiche, Verknüpfung von Bedingungen

16. Der Conditional-Operator

17. Zusammenfassung der Operatoren

17.1. Auswertungsreihenfolge

17.2. Auflistung nach Priorität

17.3. Bit-Operatoren, Bit-Komplement-Operator und Bit-Shift-Operatoren

18. Iterationen

18.1. Übersicht

18.2. Bedingungen und Zähler

18.3. Ablaufschema

18.4. break und continue

18.5. Iteration und robuste Programmierung

18.6. for/in

19. Elementare Programmiertechnik anhand eines Beispiels

19.1. Die Aufgabe

19.2. Start ohne Skrupel

19.3. Übersichtlicher Code

19.4. Zerlegung in Methoden

19.5. Welche Anforderungen bestehen und wer überprüft sie?

19.6. Aufsammeln der Zahlen

19.7. Abspaltung der Benutzereingabe

20. Objekte*

20.1. Bausteine

20.2. Methodenaufrufe

20.3. Anatomie von Klassen und Objekten

20.4. Statische Elemente

20.5. Referenzen, Nirvana und this

21. Typen und Interfaces*

21.1. Ein leichtgewichtiges Tool

21.2. Ableitung und Vererbung

21.3. Das Verbergen und das Abgreifen von Information

21.4. Upcast und Downcast

21.5. Typen

21.6. Interfaces erweitern Interfaces

22. Allgemeine Vererbung*

23. Packages, Import und private Typen

23.1. Packages

23.2. Nicht-public Typen

23.3. Package-Sichtbarkeit

23.4. Import-Statements

23.5. Static Imports

24. Module*

25. Exceptions*

26. Elementare Typen und ihre Verpackung in Objekten – Wrappers

27. Zahlen und Zufallszahlen

27.1. Literale und Arithmetik

27.2. Große Zahlen

27.3. Der Zahlengenerator Random

28. Konstanten, Konstanz und Unveränderbarkeit

29. Enums*

30. Threads*

31. Datum und Uhrzeit

31.1. Zeitstempel und Zeitdauer

31.2. Zusammensetzen, Beschränken und Kopieren

31.3. Tageszeit - LocalTime

31.4. Datum – LocalDate

31.5. Zeitzonen – ZonedDateTime

31.6. Alt - Date und Calendar

31.7. Formatieren von Datums- und Zeitangaben

31.8. Parsen

32. Performancemessung

33. Strings*

34. Arrays

34.1. Basics

34.2. Initialisierung, elementare Elementtypen und Referenzelemente

34.3. Elementtyp, Vergleichbarkeit und das Tool Arrays

34.4. Mehrdimensionale Arrays

35. Collections*

36. Generische Klassen*

37. Generische Methoden*

38. Maps*

39. Essentials

40. Guidelines

RAPID-CODING EINSCHUB 1

1. Was ist Rapid-Coding

2. Der Zeitfaktor

3. Die Aufgabe

4. Zerlegung und Verteilung

5. Alt-Bekanntes

5.1. Iterationen

5.2. Argumentprüfungen

5.3. Exceptions werfen

5.4. Konstanten vereinbaren

5.5. Es gibt sehr viel mehr

6. Ein kleiner Schritt

7. Ein großer Schritt

OBJEKTE

1. Einige kurze Fakten zu Objekten, die das Verständnis erleichtern

1.1. Man kann Java auch ohne Objekte nutzen

1.2. Man kann Java mit Objekten und ohne Objektorientierung betreiben

1.3. Instanzen und Objekte sind dasselbe

1.4. Wir konstruieren keine Objekte, sondern Klassen

1.5. Auch mit Objekten kann man Mist bauen

1.6. Klassen sind Typen

1.7. ‚Abstrakt’ bedeutet ‚nicht implementiert’

2. Fange einfach irgendwo an

2.1. Straight-forward

2.2. Einsatz der üblichen Tools

2.3. Behandlung der Eingabefehler

2.4. Kommentare

2.5. Der gesamte Code

2.6. Das prozedurale Denken

2.7. Zäher Code

3. Die Klarheit der Objekte

3.1. Spielerischer Einstieg

3.2. Details der Komponenten

3.3. Resümee

3.4. Die Lottostatistik

4. Die Basics

4.1. Der Blick auf das Ganze

4.2. Instanzdaten und Kapselung

4.3. Wege, um die Kapselung zu brechen

4.4. Klassen-Elemente

4.5. Interface-Elemente

4.6. Konstruktoren

4.7. Object

4.8. Probleme mit equals() und hashCode()

5. Implementierung der Lotto-Applikation

5.1. LottoTicket

5.2. Win

5.3. LottoDrawing

5.4. RealLottery

5.5. Zustandsprogrammierung bei RealLottery

5.6. Objekte als robuste Maschinen

6. Statische Elemente

6.1. Syntax und Übersicht

6.2. Baustein ohne Instanzen

6.3. Singleton

6.4. Verwaltung von Instanzen

7. Vererbung

7.1. Cube

7.2. Terminologie

7.3. Konstruktoren

7.4. Schwierigkeiten, die genaues Hinsehen erfordern

7.5. Eine Art von Superclass-Chaining

7.6. Den Typ einer Instanz kann man nicht verbergen

7.7. Overriding

7.8. Der Downcast

7.9. Konsequenzen von später Bindung

7.10. Aufbau der Instanzen

7.11. NationalLottery

7.12. Vererbung von Implementierung bricht abgeleitete Klassen

7.13. Ist ein Math ein Cube?

8. Erzwingen und Verhindern von Overriding

8.1. Abstrakte Methoden und abstrakte Klassen

8.2. final Methoden und Klassen

9. Cloneable

10. Comparable

10.1. Vergleichbarkeit und natürliche Ordnung

10.3. Comparatoren sortieren Elemente mit und ohne natürliche Ordnung

10.4. Comparatoren in der Lotto-Applikation

10.5. Nur einfache Anforderungen an Listener

11. Essentials

12. Guidelines

RAPID-CODING EINSCHUB 2

1. Das Beispiel

2. Das einfache Schema der Klassen

3. Konzept, Typ und Erfassbarkeit

4. Trennung von Typ und Implementierung

MODULE

1. Arbeiten mit und ohne Module

2. Gespräch über Module

3. Benannte Module und Kapselung

4. Freigaben

5. Module und Objekte

6. Im Kreis

7. Essentials

ANONYME KLASSEN UND LAMBDAS

1. Innere Typen

1.1. Intro

1.2. Wechselseitiger Zugriff

1.3. Noch eine neue Syntax: new

1.4. Wie der Compiler den freien wechselseitigen Zugriff sieht

1.5. Übersicht zu inneren Typen

2. Syntax und Einsatz von anonymen Klassen

2.1. Der definierende Typ

2.2. Drei oder vier Schritte zu einer Formel

2.3. Für anonyme Klassen können keine Konstruktoren geschrieben werden

2.4. Code für eine spätere Ausführung

2.5. Klasse oder Methode?

2.6. Fallbeispiel mit einer Defaultmethode

2.7. Adapter als simple Hilfsmittel

3. Essentials zu inneren Typen

4. Lambdas

4.1. Intro

4.2. Von den anonymen Klassen zu den Lambdas

4.3. Die Syntax der Lambdas

4.4. Lambda-Interfaces

4.5. Methodenreferenzen

4.6. Der nächste Schritt: Kombination von Lambdas

5. Intro zu Streams*

5.1. Was ist ein Stream?

5.2. Eine eigenartige Konzeption

5.3. Was enthält ein Stream?

5.4. Als Beispiel - Die Arbeits- und Funktionsweise von reduce()

6. Essentials zu Lambdas

7. Guidelines zu Lambdas

EXCEPTIONS

1. Wirf eine Exception

2. Eine einfache Entscheidung

3. Stack-Traces, Methodenlabyrinthe und Ariadne-Fäden

4. Die methodenübergreifende Struktur von try-catch

5. Der passende Exception-Handler

6. finally

7. Checked- und Unchecked-Exceptions

8. Mehrfach-Handler (Multi-Catch)

9. Wiederauswurf – Rethrow

10. Smarter Compiler

11. Ressourcen-Behandlung

11.2. Aufwendige Ressourcen-Freigabe mit finally

11.3. Die Automatismen von try-with-resources

12. Suppressed Exceptions

13. Selbst verfasste Exceptiontypen

14. Exceptions und Threads

15. UncaughtExceptionHandler

16. Exceptions tatsächlich behandeln

16.1. Die Wiederholung

16.2. Unterscheide Produktivzeit und Entwicklungszeit

16.3. Exceptions zur Entwicklungszeit verlässlich anzeigen

16.4. Die Unterscheidung von Checked- und Unchecked-Exceptions ist unnötig

16.5. Etwas suchen

16.6. Auf Bauteile verzichten

16.7. Jedes Bauteil braucht ein Fehlerkonzept

16.8. Bei allen Methoden Exceptions deklarieren und spezifizieren

17. Essentials

18. Guidelines

TEIL 2 DER JAVA-RUNDREISE – TOOLS, OHNE DIE ES NICHT GEHT

1. Class und Reflection

2. System-Properties

3. Logging

3.1. Der Standard-Logging-Framework

3.2. Austauschbare Implementierungen und Tag-basiertes Logging

3.3. Guidelines zum Logging

4. Intro zu Threads

4.1. Ein Ariadne Faden

4.2. Dem Faden folgen

4.3. Parallele Fäden

4.4. Threads sind leichtgewichtige Prozesse

4.5. Objektorientierung und parallele Abläufe sind zwei verschiedene Welten

4.6. Ausführung in nur einem Thread

4.7. Ausführung in zwei Threads

4.8. Threads erzeugen und starten

4.9. Auf einen Thread warten - join()

4.10. Thread-Zustände

4.11. Blockierte Threads

4.12. Eine kurze Exkursion zum Wait-Set

4.13. Einen Thread beenden

4.14. Dämonen

4.15. Zeitscheiben und kooperatives Verhalten

4.16. Priorität und Id

4.17. Auflistung der Threads

4.18. Timer

4.20. Essentials zu Threads

5. Streams

5.1. Intro

5.2. Streams verwenden

5.3. Einzelne Zeichen und Arrays

5.4. Streams können blockieren

5.5. Eingangs-Streams

5.6. Übersicht der Byte-Streams

5.7. Übersicht der Character-Streams

5.8. Ausgangs-Streams

5.9. Typische Stream-Verwendungen

5.10. Daten-Streams

5.11. Essentials zu Streams

6. Files

6.1. Pfade

6.2. Die Klasse File

6.3. Absolute und relative Pfade

6.4. Path

6.5. Files lesen

6.6. Files schreiben

6.7. Ressource-Pfade

6.8. Ein kleiner Test zu den Ressource-Pfaden

6.9. Standardcode: Files lesen

6.10. Standardcode: Files schreiben

6.11. Directory Iterator und list()

6.12. Mehr über Files: copy(), move(), find() und walk()

6.13. Files und das Visitor-Pattern

6.14. Übersicht der Files-Methoden

6.15. Essentials zu Files

ANHANG

1. Die erste Klasse

2. Java und der JDK

2.1. Sourcecode, Bytecode und Virtuelle Maschine

2.2. Java-Versionen

2.3. Download des JDK

2.4. Installation des JDK

2.5. Die Path-Umgebungsvariable

2.6. Einige Tools

2.7. Entwicklung ohne und mit Entwicklungsumgebung

3. Modulepath und Classpath

3.1. Eine Liste von Orten, um nach referenzierten Typen zu suchen

3.2. Modulepath und Modulesourcepath

3.3. Disassembler

3.4. Vermeintliche und tatsächliche Classpath-Probleme

4. Compiler

4.1. Einfache Aufrufe

4.2. Module

5. Archive

5.1. Übersicht

5.2. Modulare Jar-Files

6. Interpreter

7. Eclipse-Intro

8. Das Richtige tun

INDEX

Abbildungen

Abbildung 1: Java-Arrays und Indizes

Abbildung 2: Einige Oberflächenelemente der Lotto-Applikation

Abbildung 3: Die Idee des Drawing-Bausteins

Abbildung 4: Leistung des Drawing-Bausteins

Abbildung 5: Bausteine und ihr Zusammenwirken

Abbildung 6: Der Typ Gambler

Abbildung 7: Der Typ Person

Abbildung 8: Ein ClassRoom für BirthDayPersons

Abbildung 9: Der Basistyp steht per Konvention oben, der abgeleitete Typ unten

Abbildung 10: Drei Example-Instanzen mit der Instanz ihrer Klasse

Abbildung 11: Die Standard-Log-Handler

Abbildung 12: Logger und Handler sind die Hauptakteure beim Logging

Abbildung 13: Zwei Welten: Character-Rohre und Byte-Rohre

Abbildung 14: Vier unabhängige Grundtypen: InputStream, OutputStream, Reader und Writer

Abbildung 15: Die vier Stream-Grundtypen und zugeordnete Interfaces

Abbildung 16: Filter - Streams werden als Rohre ineinander gesteckt

Abbildung 17: Reader und InputStream

Abbildung 18: Die Byte-Klassen des Stream-Frameworks

Abbildung 19: ByteArrayInputStreams werden zu einer Sequenz von Streams verknüpft

Abbildung 20: Die Character-Klassen des Stream-Frameworks

Abbildung 21: Writer und OutputStream

Abbildung 22: DataInputStream und DataOutputStream

Übersicht

Fahrplan und Rapid-Coding

Gute Konstruktionen basieren auf gutem Coding. Das Anliegen dieses Buches heißt Coding. Wir wollen gute Lesbarkeit, Stabilität, Einfachheit, Effizienz und Schnelligkeit. Wir wollen, dass Code solide gebaut ist und nicht bei kleinsten Änderungen im Umfeld wieder nachgearbeitet werden muss. Wir wollen, dass sein Aufbau einfach zu verstehen ist und seine Strukturen und Prinzipien leicht zu erfassen sind. Und wir wollen einen Ansatz, der uns systematisch und auf Dauer gestattet, Code schnell und sehr effizient zu erstellen. Das Stichwort dafür lautet: Rapid-Coding.

Rapid Coding umfasst eine Reihe von Techniken und Vorgehensweisen, die ein rasches und verlässliches Erstellen von hochwertigem Code ermöglichen. Neben der Grundschnelligkeit der Code-Erstellung sind Lesbarkeit und Verständlichkeit weitere Punkte, die uns interessieren. Die Verteilung von Code auf Methoden gehört zu den ersten und elementarsten Hilfsmitteln für verständliche Formulierungen. Man kann diese und andere robuste Techniken, die zu gutem Coding führen, anhand von simplen Beispielen darstellen. Und man könnte sich lange und mit Vergnügen bei den einfachen Beispielen und ihren Implikationen für Coding-Technik) aufhalten. Man würde viel dabei erfahren, was andern Orts - mit dem Fokus auf bloße Syntax - zu kurz kommt.

Es wartet jedoch mit der Objektorientierung ein Ansatz auf uns, dessen Leistungsfähigkeit die der prozeduralen Programmierung bei Weitem übersteigt. Der Schritt in die objektorientierte Welt ändert unsere Sichtweise auf Software und Coding – und die Änderung ist radikal. Es ergeben sich sowohl für das Denken über Probleme und Modelle als auch bei Konzeptionen und Strukturen ganz andere Möglichkeiten. Objektorientierung und Rapid-Coding sind zudem keine Gegensätze, sondern verschmelzen zu einer wunderbaren Einheit.

Entwickler werden Lambdas und Exceptions mit unterschiedlicher Zuneigung betrachten. Wir haben aber nur bei den Lambdas die Wahl, ob wir sie in unseren Programmen einsetzen. Hat man sich jedoch einmal an sie gewöhnt, wird man sie nicht mehr missen wollen. Bei den Exceptions haben wir diese Wahl nicht. Ohne Fehler-Behandlung läuft kein Java-Programm. Die vermeintlichen Alternativen haben allesamt große Handicaps. Aus diesen Gründen werden beide Techniken - Lambdas und Exceptions – sehr ausführlich behandelt.

Das vorliegende Buch setzt den Fokus auf elementare Programmierung, objektorientierte Programmierung und die Lambdas. Das erste braucht man, weil alles andere darauf aufbaut, das zweite, weil die Objekte Konstruktionen und Modelle eigentlich erst greifbar machen, und die Lambdas braucht man, weil sie viele Implementierungsaufgaben vereinfachen. Dieses Grundmaterial wird ergänzt durch die Module, die Java 9 etwas brisant machen, die Behandlung der Exceptions, um die man nicht herumkommt, und die Vorstellung einer Reihe von Tools, um die man gleichfalls nicht herumkommt. Rapid-Coding wird in diesem Buch vorbereitet. Es gibt zwei kleine Einschübe dazu, aber zunächst nicht mehr. Stattdessen kommen Klarheit der Formulierung, Standards, Präzision und selbst Einfachheit als unterschwellige Themen immer wieder zur Sprache. Diese Punkte sind zentral im Rapid-Coding-Ansatz und treiben ihn voran.

Die erste Java-Rundreise

-Ein Plan und ein Stück Code - Was ein Entwickler wissen muss

-Aufteilung von Code in Methoden

-Iteration und robuste Programmierung

-Elementare Programmiertechnik anhand eines Beispiels

-Objekte, Referenzen, Nirvana und this

-Erste Übersicht zu Typen, Interfaces und Vererbung

-Die bekannten Tools: Exceptions, Enums, Threads, Strings, Arrays, Collections, Maps und Generics

Die erste Rundreise macht Leserinnen und Leser mit den Java-Grundlagen vertraut. Sie lernen dabei alles an Syntax kennen, was Sie für das Schreiben von prozeduralem Code brauchen. Unterstützt wird dieses Kapitel vom Anhang, der weitere Informationen zum JDK und zum allgemeinen Arbeiten mit Java bietet. Die erste Rundreise enthält neben der grundlegenden Syntax einführendes Material zu vielen Themenpunkten, die häufig benötigt werden, so dass die Einführung relativ breit angelegt ist. Leserinnen und Leser werden dabei auch mit Aspekten und Themen bekannt gemacht, die erst in späteren Kapiteln tiefer ausgeführt werden. Derartige Themen sind mit einem Stern (*) gekennzeichnet.

Objekte

-Warum Objekte Klarheit bringen und wie sie dies tun

-Die Aufgaben einer Klasse

-Der Sprung in eine andere Art von Software – Das Denken in Objekten

-Der in sich abgeschlossene Baustein

-Objektorientierung übersetzt Konzepte in Objekte

-Diesmal vertieft: Typen, Interfaces und Vererbung

Das Kapitel zur Objektorientierung führt in das Denken mit Bausteinen ein und behandelt die zugehörige Java-Syntax. Mit vorhandenen Bausteinen zu arbeiten, ist manchmal trivial. Bausteine richtig zu erstellen, ist weniger trivial, aber auch nicht unbedingt schwierig. Mit Bausteinen zu konstruieren, die noch nicht vorhanden sind, ist zunächst etwas eigen, passt aber gut in das typische objektorientierte Vorgehen. Das Kapitel zeigt die Eigenheiten dieser Konstruktions- und Denkform. Es erklärt die Grundlagen und vermittelt vor allem die mentale Haltung, welche die Objektorientierung prägt.

Module

-Module sind das zentrale Thema in Java 9. Markus und Sonja, ein Freak und eine eigenwillige Entwicklerin, erschließen die Bedeutung der Module in einigen Gesprächen

-Wozu brauchen wir Module?

-Von der Abschließung der Module bis zu starker Kapselung und deep Reflection

-Alles, was Sie über Module wissen müssen, zusammengefasst in den Essentials dieses Kapitels

Das Thema der Module rumort in Java und berührt viele Detailthemen. Ihre Einführung hat fundamentalere Auswirkungen als seinerzeit die Einführung der Generics in Java 5 oder die der Lambdas in Java 8. Auch die JDK-Tools wie Compiler, Archiv-Tool und Interpreter sind stark betroffen. Sie sollten in Java 9 anders bedient werden als in Java 8. Viele Infos zu den Modulen findet man deshalb auch im Anhang, in dem der JDK und seine Tools beschrieben werden.

Lambdas

-Lambdas sind anonyme Klassen mit einer einfacheren Verwendung als diese

-Lambda-Interfaces

-Functionals

-Methodenreferenzen

-Eine kleine Einführung in eine neue Welt: Die Behandlung von Mengen durch Streams

Das Teilkapitel über anonyme Klassen behandelt zunächst ein Thema, für das sich typische Java-Entwickler nicht wirklich interessieren. Dennoch wird die Syntax ausführlich erklärt. Das Kapitel erarbeitet einige wichtige und merkbare Aussagen und kommt dann zum eigentlichen Grund für die ganze Mühe, nämlich zu den Lambdas. Dort wird der Stoff aber keineswegs leichter – ganz im Gegenteil. Die Abhandlung der Lambdas ist für Leserinnen und Leser sicherlich anstrengend. Diejenigen unter Ihnen, die zu Anfang des Buches noch Java-Neulinge sind und das Buch ohne Praxisbegleitung bearbeiten, stehen an dieser Stelle möglicherweise vor einer großen Herausforderung. Das vorliegende Buch bietet jedoch genügend Anregungen und Codebeispiele, mit denen man selbständig weiterarbeiten und sich damit beliebig viel praktische Erfahrung verschaffen kann.

Exceptions

-Wirf eine Exception

-Stack-Traces, Methodenlabyrinthe und Ariadne-Fäden

-Ein Code-Wächter

-Checked- und Unchecked-Exceptions

-Try-With-Resources und Suppressed-Exceptions

-Exceptions tatsächlich behandeln

Die Exceptions sind Standard-Java-Stoff und sozusagen eine Pflichtveranstaltung. Das Thema ist nicht so kurzweilig wie die erste Rundreise, es ist nicht so interessant wie die Objektorientierung, nicht so aufregend wie Lambdas und nicht so neu wie die Module. Aber die Exceptions sind unumgänglich. Man muss da einfach durch. Und man muss da auch mit guter Aufmerksamkeit durch. Das Kapitel enthält einiges Material, wie man Code mit Exception-Handling schreibt. Da gibt es sehr viel mehr als nur Syntax. Man schreibt in der Praxis kaum einmal eine Seite Code, ohne mit Exceptions in Berührung zu kommen. Und sobald man es mit Exceptions tatsächlich zu tun hat, sollte man wissen, was es über sie zu wissen gibt und wie man sie handhabt.

Die zweite Java-Rundreise

-Kurze Intros zu Class, Reflection und System-Properties

-Sichtbarmachung von Abläufen mit Standard-Logging

-Das Notwendigste zu den Threads

-Ebenfalls unverzichtbare Tools: Die sehr eigene Welt der IO-Streams

-Files, Ressource-Pfade und das Standardvorgehen beim Lesen und Schreiben von Dateien

Die zweite Rundreise ist das, wonach es sich anhört: ein bequemer Ausflug. Mit Class und Reflection wird ein abseitiges Thema angeschnitten, weil man hin und wieder damit zu tun hat. Das Ganze bleibt aber ohne viel Tiefgang, weil Reflection (zum Glück) nicht zum essentiellen Konstruktionswissen zählt. Beim Thema Logging ist das anders, dies kann durchaus nützlich sein. Entsprechend gründlich wird es auch behandelt. Bei den Threads hingegen sind wir zufrieden, wenn wir hinterher ungefähr wissen, mit was man es da eigentlich zu tun hat. Demgemäß ist dieses Teilkapitel auch als ‚Intro’ betitelt. Anders ist es wiederum bei Streams und Files. Diese Themen werden gründlich und abschließend (wenn auch nicht erschöpfend) behandelt.

Anhang

-Falls alles andere versagt: Eine besondere Hilfestellung beim Schreiben der ersten Klasse

-Der JDK und seine Versionen – Eine Übersicht auf einer Seite

-Modulepath und Classpath

-Compiler, Interpreter und das Archiv-Tool

-Eine sehr kurze Intro zu Eclipse

Das erste Anliegen - Verständliche Texte

Betrachten Sie das folgende Stück Code. Auf seinen genauen Inhalt kommt es hier nicht so sehr an:

long number = … // Kommt von irgendwo her
boolean test = true;
if (number == 2)
test = true;
else if (number % 2 == 0)
test = false;
else
{
double squareRoot = Math.sqrt(number + 1);
long divider = 3;
for (; divider < squareRoot; divider += 2)
if (number % divider == 0)
test = false;
}

Dieses kleine Beispiel zeigt Java-Code. Um diesen zu schreiben, muss man einige Regeln beachten, mit denen wir uns befassen werden. Aber dieser Code ist kein besonders gut verständlicher Text. Man könnte sagen: Software sieht nun mal so aus. Doch das ist Nonsense. Wir können Software in völlig unlesbarer Form schreiben und wir können sie in gut verständlicher Form produzieren. Beides geht und das obige Beispiel ist keines von beiden. Wir sind damit nicht zufrieden. Denn wir wollen und brauchen grundsätzlich gut verständlichen Code. Wir wollen kleine und größere Systeme in Software erstellen. Und je umfangreicher die Systeme werden, desto größer ist die Notwendigkeit von verständlichen Texten. Wir brauchen Codeformulierungen, die super einfach lesbar und erstellbar sind. Etwa Code in dieser Art:

long number = … // Kommt irgendwoher. Wird getestet
boolean test = PrimeTester.isPrime(number); // Das ist im Prinzip verständlich

Ob die Bedeutung des Ausdruck: ‚PrimeTester.isPrime(number)’ tatsächlich jedermann zugänglich ist, ist hier nicht die richtige Fragestellung. Selbstverständlich setzen wir bei unseren Konstruktionen eine fachliche Voreingenommenheit voraus, die beispielsweise die Leistung von isPrime() einordnen kann. Aber darüber hinaus erheben wir beim Schreiben von Software grundsätzlich den Anspruch, dass wir mit lesbaren Texten operieren. Die Implementierung von isPrime() könnte dieses Aussehen haben:

public class PrimeTester
{
public static boolean isPrime(long number) throws NumberException
{
boolean result = false;
if (Eratosthenes.canHandle(number))
result = Eratosthenes.test(number);
else if (Atkin.canHandle(number))
result = Atkin.test(number);
else if (Fermat.canHandle(number))
result = Fermat.test(number);
else
throw new NumberException(“cannot handle: ” + number);
return result;
}
}

Die hypothetischen Komponenten Eratosthenes, Atkin und Fermat werden hier als gegeben vorausgesetzt. Sie sehen nun den Unterschied zum ersten Codebeispiel. Der Code von PrimeTester.isPrime() ist gut lesbar. Für jemand, der fachlich versiert ist, ist diese Implementierung wie ein offenes Buch. Es ist verständlicher Text, der in Java verfasst ist. Und der Leser muss noch nicht einmal Java können, um ihn zu verstehen. Uns ist klar, dass Code wie der im ersten Beispiel nicht immer vermieden werden kann. Aber prinzipiell kann jedes Stück Code entweder verständlich formuliert oder verständlich verpackt werden.

Die Forderung, mit verständlichen Texten zu arbeiten, ist in diesem Buch für lange Zeit eine unserer Leitlinien und eine Vision, die uns führt. Oftmals ist sie nur implizit vorhanden, sozusagen versteckt in unserem Hinterkopf, denn wir sind auf weiten Strecken – im Grunde große Teile des Buches hindurch – auch mit anderen Themen befasst. Wir müssen lernen, wie man elementaren Java-Code schreibt. Dann müssen wir sehen, wie wir zu umfassenderen Konstruktionen kommen, ohne die Sicherheit und Stabilität von elementarem Code aufzugeben. Und gelegentlich arbeiten wir auch daran, wie man dies auf verlässliche und schnelle Art macht (Rapid Coding).

Wir werden bei der Verfolgung unserer Vision Schritt für Schritt Techniken und Strategien entfalten, die uns die großen Konstruktionen erschließen. Aber dabei geht es sehr langsam voran. Denn das elementare Coding steht immer wieder im Zentrum unserer Bemühungen. Dieses ist die Basis und muss besonders gut beherrscht werden. Wir lernen Techniken, die uns Sicherheit geben, mit denen wir verlässlich konstruieren können, die uns schnell (sehr schnell) machen und die uns nachts gut schlafen lassen. Aber das elementare Coding wird dabei im Fokus bleiben und ein wichtiges Fundament bilden.

Konventionen und Verabredungen

Guidelines, Essentials und Merksätze

Zu den meisten Themen gibt es Essentials. Diese stellen den Inhalt des vorangehenden Kapitels in Kurzform dar. Die Essentials haben zwei Zielsetzungen. Zum einen ist das die Zusammenfassung, zum anderen die Kürze. Eine Zusammenfassung (in anderen Worten) soll zudem die Wahrscheinlichkeit für den Leser erhöhen, eine verständliche Erklärung zu den gerade aktuellen Punkten zu finden. Die Essentials sind dafür gedacht, zu einem gegebenen Stoff Zusammenhänge zu rekapitulieren oder auf knappem Raum nach bestimmten Informationen suchen zu können.

In den Text sind zuweilen kurze und im Layout hervorgehobene Statements eingestreut. Diese kann man als Merker, Merksätze oder Grundaussagen auffassen. Ihr Zweck ist es, die Aufmerksamkeit auf einen Zusammenhang zu richten, der das Erfassen einer Thematik erleichtert. Merker sind auf das erste Kennenlernen eines Themas ausgerichtet. Sie wollen einen Pflock einschlagen und eine feste Aussage auf einem noch schwankenden Boden machen. Sie wollen die Aufmerksamkeit auf einen Punkt fokussieren. Bei wiederholtem Lesen werden die Merker - anders als die Essentials - überflüssig.

Zu einigen Themen gibt es Guidelines. Diese stehen wie die Essentials am Ende eines Kapitels und enthalten Ratschläge und Empfehlungen.

Die *-Kapitel

Hauptsächlich in der ersten Rundreise befinden sich Kapitel, die mit einem Stern (*) gekennzeichnet sind. Sie besprechen Themen, die zu einem späteren Zeitpunkt ausführlich behandelt werden. Verständnisprobleme in diesen Kapiteln sollte man deshalb nicht allzu ernst nehmen, sondern eher als Anregung betrachten. Aber auch sonst ist Geduld eine schöne Tugend. Gerade auf der ersten Rundreise treffen Sie öfters auf Syntax, die erst später genauer erklärt wird. Zwei Quellen für schnelle Erklärungen, die man bei Schwierigkeiten als erstes konsultieren sollte, sind allgemein das Glossar und im Speziellen die Essentials des betreffenden Kapitels. Zu einigen der mit einem * gekennzeichneten Kapitel gibt es die ausführliche Behandlung nicht in diesem Buch, sondern im dem Folgeband ‚Tools’.

Das Glossar

Im Anhang dieses Buches sollte sich ein relativ umfangreiches Glossar befinden. Das war der Plan. Der ursprüngliche einleitende Text zum Glossar war dieser:

„Der Leser kann dort Begriffe nachschlagen oder sich zu Stichworten weitere Informationen besorgen. Das Glossar ist zusammen mit den Essentials der einzelnen Kapitel eine Alternative zu erklärendem Text. Leserinnen und Leser sollen mit dem Glossar über eine Möglichkeit verfügen, zu allen neu auftauchenden Begriffen eine kurze erste Erklärung zu erhalten. Wenn man beispielsweise in den einführenden Abschnitten auf die Begriffe ‚abstrakt’, ‚Referenzvariable’ oder ‚Nirvana’ stößt und damit nicht viel anfangen kann, so ist das ein Fall für das Glossar.“

Dass es das Glossar in diesem Buch nun doch nicht gibt, hat vor allem Platzgründe. Das Glossar wird stattdessen als eigene Publikation parallel zu diesem Buch erscheinen.

Konventionen

In diesem Buch werden Methodennamen durchgehend mit runden Klammern geschrieben. ‚main’ beispielsweise kann eine Variable sein oder vielleicht auch ein Threadname. Mit ‚main’ ist jedoch keine Klasse gemeint, denn Typnamen werden durchgehend groß geschrieben (wie etwa Example oder String). Und mit ‚main’ ist auch keine Methode gemeint, denn die würde als main() geschrieben werden.

Eine kleinere Schwierigkeit beim Schreiben über Software besteht in der Unterscheidung zwischen Standardtypen (die mit dem JDK mitgeliefert werden) und den selbst-verfassten Typen des Autors. Aus dem Kontext heraus ist die Unterscheidung zumeist klar. Standardtypen werden in der Regel bei der ersten Erwähnung als solche bezeichnet. Das zweite Hilfsmittel der Unterscheidung ist die Angabe der Packages. Standardtypen residieren grundsätzlich in den Packages java oder javax. Selbst-verfassten Typen residieren niemals in diesen Packages.

Es gibt einige wenige Grafiken. Kursive Schrift in Grafiken für Typ- oder Methodennamen hat gemäß UML grundsätzlich die Bedeutung von ‚abstrakt’, also von nicht-implementiert.

Codebeispiele

Ein Buch über Software lebt von Codebeispielen. Bei deren Darstellung im Buch wird darauf geachtet, dass die seitenverschlingende Wiederholung von ähnlichen Codepassagen vermieden wird. Vor allem auf die nochmalige Darstellung eines präsentierten Beispiels als Ganzes am Kapitelende wird verzichtet. Dennoch kommt es vor, dass einander ähnlich sehende Codestücke gezeigt werden, um im Vergleich irgendwelche Besonderheiten hervorzuheben.

Bei der Darstellung von Code in diesem Buch werden Schlüsselwörter durch Fettdruck hervorgehoben. Schlüsselwörter sind Elemente der Sprache, die für den Compiler eine besondere Bedeutung haben und die deshalb als Bezeichner für Variable, Typnamen oder Methodennamen nicht verwendet werden dürfen.

Obwohl der Autor die Meinung vertritt, dass Leserinnen und Leser dann am meisten von den Beispielen profitieren, wenn Sie deren Code selbst schreiben, sind die größeren Beispiele dieses Buches in einem .zip-File verfügbar. Der Sourcecode kann auf der Seite www.rinser.info heruntergeladen werden siehe dort den Link „Downloads“). Das .zip-File entpacken Sie durch Doppelklick. Das .jar-File, das Sie dann erhalten, müssen Sie mit dem Archiv-Tool des JDK entpacken.

Wer soll dieses Buch lesen?

Jeder Entwickler, der schon einmal in irgendeiner Sprache Code geschrieben hat, sollte dieses Buch lesen können. Leute mit Java- und C/C++-Kenntnissen werden sich dabei leichter tun als andere.

Gefundene Fehler

Meldungen von gefundenen Fehlern sind sehr willkommen, egal ob es sich um Rechtschreibfehler, inhaltliche Fehler oder um Fehler im dargestellten Code handelt. Hilfreich ist eine Mail an java@rinser.info mit Angabe des Kapitels, der Seite und der Abschnittsnummer (vom Beginn der Seite an gezählt) und einer kurzen Beschreibung des Fehlers. Die Mail sollte idealerweise den Betreff: ‚Objekte’ enthalten und eine Klassifizierung nach ‚Tippfehler’, ‚Inhaltlicher Fehler’ oder Codefehler’.

Das Software-Universum

Legen Sie sich ein Tagebuch zu. Nennen Sie es das „Buch der unbrauchbaren Softwareteile“ (BUST). Notieren Sie darin jede Komponente, die Sie erstellt haben und die nicht mehrfach verwendbar ist. Und notieren Sie jeweils sorgfältig den Grund, warum Sie schon wieder ein Stück Code für den Müll geschrieben haben

Nicht-dokumentierte Software ist nicht wiederverwendbar

Software, von der man den Code lesen muss, um zu wissen, was sie tut, ist nicht wirklich wiederverwendbar

Vor allem müssen wir aufhören, in Lösungen zu denken. Lösungen sind nahezu niemals wiederverwendbar

1.Das Buch der unbrauchbaren Softwareteile

2.Ein Anfang – aber nicht mehr

 

1. Das Buch der unbrauchbaren Softwareteile

Software bietet tausend Möglichkeiten. Es gibt mannigfache Wege, etwas zu konstruieren. Die Freiheit, was wir bauen und wie wir es bauen, ist unbegrenzt. Wir können raffinierte Algorithmen ersinnen, um gegebene Probleme zu lösen oder um bestehende Lösungen zu verbessern. Wir können jeden gewünschten Aspekt eines gegebenen Systems in Software übertragen. Und wir können Systeme in Software erfinden, die es in der realen, fassbaren Welt nicht gibt. Die Vielfalt an Programmen, Maschinen, Bauteilen, Konstruktions-Ideen, Techniken und Vorgehensweisen ist kaum zu benennen.

Ob wir mittelalterliche Handschriften analysieren, die Dynamik von physikalischen Abläufen berechnen oder Verkehrsströme simulieren, wie auch immer die zu erstellenden Systeme aussehen, jeder Softwareentwickler, der fachliches Wissen mit technischem Geschick kombiniert, kann sie bauen. Dies geschieht selbstverständlich mit unterschiedlichem Erfolg, in allen Qualitätsabstufungen und mit sehr verschiedenem zeitlichen Aufwand. Aber es gibt für die Konstruktion eines beliebigen Systems in Software keine prinzipiellen Voraussetzungen. Man braucht dazu keine besonderen Maschinen und keine teuren Werkzeuge. Ein Entwickler, der eine bestimmte Idee hat, kann unmittelbar damit beginnen, sie umzusetzen. Und er muss dazu keineswegs die bekannten Techniken und Ideengebäude verwenden. Software ist beliebig formbar und konstruierbar.

Im September 1990 veröffentlichte die amerikanische Kolumnistin Marilyn vos Savant das Ziegenproblem:

„Stellen Sie sich vor, Sie müssten als Teilnehmer an einer Gameshow eine von drei Türen auswählen. Hinter einer Tür befindet sich der Gewinn, ein Auto, hinter den beiden anderen befinden sich dagegen Ziegen. Sie wählen Tür Nr. 1 und der Showmaster, welcher weiß, was sich hinter den jeweiligen Türen befindet, öffnet eine andere Tür, z. B. Tür Nr. 3, hinter der eine Ziege erscheint. Nun fragt er Sie, ob Sie bei Tür Nr. 1 bleiben oder ob Sie stattdessen Tür Nr. 2 wählen wollen. Sollte man besser wechseln“ Frei nach de.wikipedia.org, Ziegenproblem)

Stellen Sie sich weiter vor, dass Sie sich mit Ihren Kolleginnen und Kollegen über die Beantwortung der Frage und die korrekte Lösung des Problems nicht einig werden. Eine Ihrer möglichen Optionen besteht darin, das Problem durch Software zu lösen. Man verfährt dabei nach dem Monte-Carlo-Verfahren. Man macht Tausend oder auch eine Million Versuche und verteilt Fahrzeug und Ziegen jedes Mal zufällig hinter den drei Türen. Dann zählt man die Treffer (für den Gewinn des Fahrzeugs), die man hat, wenn man von Tür 1 wechselt, und die Treffer, wenn man nicht wechselt. Teilt man die Zahl der Treffer ohne Türwechsel durch die Zahl der Versuche, so erhält man eine Quote von etwa 33 Prozent, wenn nur die Zahl der Versuche groß genug ist. Für die Trefferquote bei einem Wechsel der Tür liefert ein Durchlauf der Simulation diese Daten:

Doors: 3 trials: 1000000000 winRate: 0.66666967 Duration: 35.431s

Der springende Punkt ist hier nicht das Ergebnis des Tests. Der springende Punkt ist, dass wir im Handumdrehen zu einer Fragestellung ein kleines Programm schreiben können. Und so wie wir in relativ kurzer Zeit zu diesem Problem ein Stück Software erstellen, so können wir zu jedem Thema, das uns interessiert, Software schreiben, die etwas behandelt, berechnet, analysiert oder einfach nur grafisch darstellt.

Software ist ein eigenes Universum und mit Software werden eigene Welten geschaffen. Software ist ein Betätigungsfeld mit einem extremen Potential. Wir haben darin alle Freiheiten. Wir können nahezu alles in Software konstruieren, was uns einfällt, und wir können es auf tausend verschiedene Weisen tun. Diesen unglaublichen Möglichkeiten, die Software auf der einen Seite jedem einzelnen Entwickler bietet, steht auf der anderen Seite ein gewisser sanfter aber dennoch nachdrücklicher und dauerhafter Zwang gegenüber. Nämlich der Zwang, Software möglichst rational und effizient zu entwickeln. Dabei handelt es sich nicht wirklich um einen Gegensatz. Die potentielle Freiheit, beliebige Systeme bauen zu können, wird erst dann zu einer tatsächlichen Möglichkeit, wenn wir es schaffen, unsere Konstruktionen auch in begrenzter Zeit umzusetzen. Der Traum von den unbegrenzten Möglichkeiten von Software ist eng an die Bedingung geknüpft, dass wir für die Realisierung einer Konstruktion nicht beliebig lange brauchen. Um Spielräume und Bewegungsfreiheit zu haben, brauchen wir eine bestimmte Effizienz in der Umsetzung unserer Ideen und Konstrukte. Um im Software-Universum Freiräume und Gestaltungsmöglichkeiten zu haben, muss man Implementierungen mit hoher Produktivität erstellen können. Wenn man sich im Schneckentempo bewegt, ist Bewegungsfreiheit nicht wirklich gegeben.

Es liegt auf der Hand, dass uns erst die Geschwindigkeit der Umsetzung die Potentiale von Software eigentlich erschließt. Effizienz und Produktivität sind Schlüsselfaktoren in der Softwareentwicklung. Dabei ist es nicht so, dass wir bei der Erstellung von Code keine Zeit haben. Es geht nicht darum, permanent mit Höchstgeschwindigkeit unterwegs zu sein. Im Gegenteil: Gelassenheit ist in der Erstellung von Software ein guter Begleiter. Es geht um die Grundschnelligkeit, mit der man sich bewegt.

Und es gibt weitere Faktoren, die eine Rolle spielen. Wenn Sie beispielsweise zum Ziegenproblem nicht nur eine einfache Simulation wollen, sondern auch eine Benutzeroberfläche, um etwa Gesetzmäßigkeiten anschaulich zu machen, oder um zu zeigen, wie die Wahrscheinlichkeit sich mit der Zahl der Türen verändert, brauchen Sie dazu einen Oberflächenbaukasten. Dabei reden wir nicht von Swing oder JavaFX oder einer der etablierten Browser-basierten Oberflächentechniken. Wir reden von einem Baukasten, der Ihnen das Anlegen der gewünschten Oberfläche in wirklich kurzer Zeit erlaubt. Denn als halbwegs erfahrene Entwickler wissen wir, dass Sie keine Lust mehr auf eine anschauliche Darstellung des Ziegenproblems haben, wenn Sie diese auf der Basis von Swing oder JavaFX von Grund auf neu erstellen müssen. Denn die Erfahrung lehrt uns, dass wir damit Tage oder Wochen beschäftigt sind. Was wir brauchen, ist eine Art generelle Wiederverwendung. Wenn wir Software mit hoher Produktivität erstellen wollen, müssen wir als erstes damit aufhören, Software zu bauen, die nur einmal verwendet wird - beziehungsweise nur einmal verwendbar ist.

Wenn Ihnen das Thema Effizienz wichtig erscheint, dann kaufen Sie sich ein Notizbuch von nicht ganz kleinem Umfang und notieren Sie in diesem zukünftig jedes Stück Software, das Sie für den Müll produziert haben, das also nicht mehrfach verwendbar ist. Das ist ein ernst gemeinter Vorschlag. Vielleicht ist es hilfreich, wenn Sie sich systematisch damit befassen, wann und warum Sie Software auf eine einmalige Nutzung hin erstellen.

Legen Sie sich ein Tagebuch zu. Nennen Sie es das „Buch der unbrauchbaren Softwareteile“ (BUST). Notieren Sie darin jede Komponente, die Sie erstellt haben und die nicht mehrfach verwendbar ist. Und notieren Sie jeweils sorgfältig den Grund, warum Sie schon wieder ein Stück Code für den Müll geschrieben haben