33,50 €
Ein Buch für Leute, die Spaß am Coding haben. Es basiert auf Java 9 und erklärt Syntax und Konzepte der Kernthemen. Es zeigt zudem einen Ansatz, den man als 'rationale Vorgehensweise' verstehen kann. Dieser wird als Rapid-Coding bezeichnet. Rapid-Coding umfasst eine Reihe von Techniken, die ein rasches und verlässliches Erstellen von hochwertigem Code ermöglichen. Die Objektorientierung selbst dient hier als Beispiel. Das Buch erklärt wie bei anderen Themen Syntax und involvierte Konstrukte, also alles, was ein Java-Entwickler in diesem Themenumfeld braucht. Es zeigt anhand einer schon bearbeiteten Aufga-benstellung, welche Tools die Aufgabe vereinfachen. Es demonstriert die Lösung der Aufgabe und ihre Kürze bei Verwendung dieser Tools. Sodann werden die Tools entwickelt. Das ist sozusagen Objektorientierung live. Aber der eigentliche Punkt ist die systematische Effizienz und Produktivität, die hier sichtbar werden und womit Softwareentwicklung allgemein betrieben werden kann. Der potentielle Leser sei allerdings gewarnt. In großen Teilen wird lediglich solide Entwicklungstechnik behandelt. Aber genau dies ist der Ausgangspunkt von Rapid-Coding. AUS DEM INHALT *** Eine kleine Java-Rundreise: Schritt-für-Schritt-Einführung der wichtigsten Sprachelemente und Syntaxkonstrukte *** Intro zu Rapid-Coding: Die Verteilung von Code, das Potential von Routine, die Trennung von Typ und Implementierung *** Objektorientierung: Das Denken in Objekten und das Konstruieren mit Objekten *** Module: Alles, was man darüber wissen muss und noch etwas mehr *** Lambdas: Von den anonymen Klassen zu Functionals und Methodenreferenzen *** Tools, ohne die es nicht geht: Exceptions, Threads, IO-Streams, Files.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 1355
Veröffentlichungsjahr: 2017
Wolfgang Rinser
2. Auflage
Softwarekonstruktion mit Java 9
© 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.
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
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
Betrachten Sie das folgende Stück Code. Auf seinen genauen Inhalt kommt es hier nicht so sehr an:
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:
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:
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.
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 [email protected] 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’.
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
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
Dieses Tagebuch wird Ihnen vermutlich zeigen, warum so viel Software nicht wiederverwendbar ist. Der allererste Grund ist fehlende Qualität. Damit ein Stück Software ein Bauteil wird, das man ohne Bauchschmerzen wieder einsetzen kann, muss es über eine gewisse Mindestqualität verfügen. Aber selbst wenn wir nun anfangen, unsere Softwareteile mit hoher Sorgfalt zu schreiben (was den Zeitaufwand möglicherweise erhöht, vielleicht aber auch nicht), sind wir von einer praktikablen Wiederverwendbarkeit noch weit entfernt. Damit wir ein Stück Code, das wir irgendwann einmal verfasst haben, wiederverwenden können, müssen wir es erst einmal wiederfinden. Dazu muss es a) geeignet abgelegt und b) geeignet beschrieben sein.
Nicht-dokumentierte Software ist nicht wiederverwendbar
An dieser Stelle eröffnet sich nun ein noch größeres Problemfeld. Denn welche Softwareteile haben eine vernünftige Dokumentation, vor allem eine solche, die wir glauben, selbst wenn es unsere eigene ist? Haben Sie tatsächlich schon öfters Softwareteile gesehen, die nicht zu offiziell vertriebenen Bibliotheken gehören und für die eine brauchbare Beschreibung existiert? In der Regel sind Dokumentationen zu Softwareteilen unbrauchbar, wenn diese nicht aus anerkannten Bibliotheken stammen. Denn selbst wenn Beschreibungen existieren, gibt es zwei Seiten: Das, was in der Beschreibung steht, und das, was tatsächlich realisiert ist. Um Software, die nicht aus anerkannten Bibliotheken stammt, wiederverwenden zu können, muss man sich oftmals zuerst mit dem Sourcecode befassen, oder die Software erproben – was ebenfalls mühsam ist. Das heißt, wir müssen Code lesen, um ihn verwenden zu können, weil Beschreibungen entweder nicht existieren, oder nicht präzis genug sind, oder mit der Software, die sie beschreiben, nicht wirklich viel zu tun haben. Auch das ist ein Qualitätsproblem und wir alle wissen, dass es sich dabei um ein real existierendes und häufiges Qualitätsproblem handelt.
Software, von der man den Code lesen muss, um zu wissen, was sie tut, ist nicht wirklich wiederverwendbar
Wenn Sie sich nun aus purem Spaß an der Sache entschließen, trotz des hohen Aufwands eine Benutzeroberfläche für das Ziegenproblem zu schreiben, werden Sie anschließend einige Einträge in Ihr Buch der unbrauchbaren Softwareteile stellen. Denn die Oberfläche, die Sie da schreiben, ist höchstwahrscheinlich nicht wiederverwendbar. Sie haben sich nun zwar bemüht und haben eine Dokumentation verfasst. Und das war wirklich Aufwand. Wenn Sie es einmal konsequent durchgezogen haben, wissen Sie, von was wir hier reden. Zudem mussten Sie die Dokumentation bei jeder Änderung der Software mit anpassen. Doch am Ende folgt die Erkenntnis, dass auch das für den Müll ist. Denn Sie können diese Oberfläche nicht wiederverwenden. Sie ist auf das Ziegenproblem zugeschnitten und ist für andere Zwecke nicht nutzbar.
Treten Sie Ihr Buch der unbrauchbaren Softwareteile deshalb nicht gleich ebenfalls in den Eimer. Es ist nützlich und wird Ihnen noch gute Dienste erweisen. Um Software vernünftig zu erstellen, brauchen wir nicht nur eine funktionierende Wiederverwendung, sondern auch eine andere Haltung zu der Art, wie wir Software konstruieren. Ihr Problem beim Erstellen einer Benutzeroberfläche für das Ziegenproblem ist, dass Sie Lösungen verfassen und in Lösungen denken. Um Software ‚vernünftig’ zu erstellen und um dieser Idee von einem Software-Universum näher zu kommen, in dem wir Bauteile, Maschinen, Applikationen und Konstrukte unseres Erfindungsreichtums mit einer gewissen Leichtigkeit und Grundschnelligkeit umsetzen, brauchen wir vor allem eine entsprechende mentale Haltung.
Vor allem müssen wir aufhören, in Lösungen zu denken. Lösungen sind nahezu niemals wiederverwendbar
Der rote Faden in dem umfassenden Plan, in dem das vorliegende Buch ein Anfang ist, ist die Idee von einem allgemeinen Konstruktionsansatz, mit dem wir erdachte Systeme und Konstruktionen in akzeptabler Zeit verfassen können, ohne das Buch der unbrauchbaren Softwareteile erheblich zu erweitern. Einige der Themen dieses Buches werden dabei eine Rolle spielen und sich letztlich als einfach erweisen, obwohl sie nicht von Anfang an so erscheinen. Auf andere Punkte, wie etwa auf das überragend wichtige Konstruktionsthema von Einfachheit selbst, trifft dies jedoch nicht zu.
Das Buch ist mit dem Ziel geschrieben, zu zeigen, wie man sich dem Software-Universum annähern kann. Es ist jedoch keine Eintrittskarte – der Titel lautet nicht: Ingenieur in 21 Tagen. Es wird ganz im Gegenteil die Auffassung vertreten, dass das Erlernen und der Erwerb von Konstruktionstechniken sehr lange dauert. Aber es gibt diese Konstruktionstechniken und es gibt diese Vorgehensweisen, die es erlauben, Software mit relativ hoher Produktivität und hoher Qualität zu schreiben. Diese Techniken muss man sich erschließen. Man kann sie erlernen. Das vorliegende Buch ist dazu ein Anfang – aber auch nur ein Anfang. Die einzelnen Themen werden uns in der konkreten Ausführung voranbringen, weil im Konkreten immer die Möglichkeit von Zugang und Verstehen liegt. Aber unsere Themenauswahl ist in erster Linie durch naheliegende Coding-Aspekte wie elementare Syntax, Exceptions oder Streams bestimmt, die in Java zum notwendigen Handwerk gehören. Es wird sich in dem ein oder anderen Beispiel die Möglichkeit ergeben, mehr zu sehen. Und je weiter wir fortschreiten, desto klarer wird die Bedeutung von Konstruktion und ihren speziellen Prinzipien hervortreten.
Es wird im Übrigen kein Unterschied zwischen dem ‚Schreiben’ und dem ‚Konstruieren’ von Software gemacht. Das Wort Konstruieren betont einen Gegenpol zu einer verbreiteten Art der Erstellung, die ich als ‚Bastelei’ bezeichne. Wenn wir an Lösungen ‚herumbasteln’, ist die Qualität oft schlecht, die Produktivität lausig und der Zeitdruck hoch. Wenn wir Software erstellen, sollten wir nicht basteln. Wir können experimentieren, wenn wir etwas erproben wollen oder uns etwas erschließen müssen. Aber die legitime Tätigkeit des Experimentierens sollte vom Konstruieren von Software und ihren Bausteinen unterschieden werden.
Stellt man die wesentlichen Faktoren, die gerade genannt wurden, zusammen, dann geht es um Effizienz, Qualität, Einfachheit und Wiederverwendbarkeit. Diese Faktoren sind bestimmend, sie interessieren und es ist offensichtlich, dass sie zum Teil auch voneinander abhängen. Und es kommen weitere Faktoren hinzu, die im großen Plan nicht ganz so stark im Vordergrund stehen, aber dennoch Wirkung haben, wie Lesbarkeit, Robustheit, Stabilität und Kürze. Wegen dieser Faktoren und Punkte wurde das vorliegende Buch geschrieben. Aber dieses Buch handelt in erster Linie und bewusst vom Coding und nicht von Qualität oder Einfachheit. Die genannten Themen werden nebenbei aufgegriffen – zwanglos, nicht häufig, und auch nur dann, wenn das Coding-Material dazu gute Gelegenheit bietet. Das ist nicht sehr oft der Fall. Wir befassen uns mit elementarem Code, mit anspruchsvollerem Code, mit größeren Konstruktionen, mit Konstruktionsprinzipien und Vorgehensweisen. Und hin und wieder bemerkt man, dass man gerade mit einer Schlüsselsituation zu tun hat und dass der betreffende Code (oder die Konstruktionsidee) mehr beinhaltet als nur eine Illustration der aktuellen Syntax.
Es ist nicht so – wenn wir kurz den Punkt Effizienz herausgreifen – dass man zuerst lernt, was Effizienz im Coding bedeutet, und im nächsten Schritt dann Coding selbst erlernt. Es geht selbstverständlich umgekehrt. Man lernt, wie man mit gesundem Menschenverstand an Programmierung herangeht. Und auf diesem Weg kann man ab und zu auf die ein oder andere Einsicht hinweisen, die nicht ganz offensichtlich ist. Das vorliegende Buch ist ein Buch über Coding. An einigen Stellen wird man eventuell bemerken, dass es mit anderen Hintergedanken geschrieben wurde.
Gut geschriebener Code besteht aus einer Vielzahl kleiner und kleinster Methoden
Entwickler müssen sich hin und wieder klar machen, welchen Spielraum sie durch die Gestaltung der Methoden und Methodenköpfe haben, in die sie den Code aufteilen. Die Methodenköpfe bestimmen das Vokabular, mit dem Code formuliert wird
Jeder Teilausdruck und jeder Methodenaufruf wird im Programmablauf durch seinen Ergebniswert ersetzt
Die Wirkung einer Operation und der Ergebniswert einer Operation sind nicht notwendigerweise dasselbe
‚Handgeschriebene’ Iterationen, also solche Iterationen, in denen der Entwickler die Schleifenbedingung und die Inkrementierung selbst verfasst, sind eine vermeidbare Fehlerquelle
Code erhält Struktur und Erklärung durch Aufteilung in Methoden. Methoden sind sozusagen die kleinen Bausteine, aus denen wir Code aufbauen, beziehungsweise in die wir Code zerlegen
Methodenaufrufe für Objekte haben immer dieses Standardaussehen: object.perform(). Dies liest man in dieser Weise: Schicke die Nachricht perform() an object. Das Objekt entscheidet dann, was es mit der Nachricht anfängt
Alle Objekte, die keinen elementaren Typ haben, leben im Nirvana
Traue niemals einer Aussage zur Performance. Messe grundsätzlich selbst
1.Intro
2.90 Prozent - Ein Plan und ein Stück Code - Was ein Entwickler wissen muss
3.Methoden
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
18.Iterationen
19.Elementare Programmiertechnik anhand eines Beispiels
20.Objekte *
21.Typen und Interfaces *
22.Allgemeine Vererbung *
23.Packages, Import und private Typen
24.Module *
25.Exceptions *
26.Elementare Typen und ihre Verpackung in Objekten - Wrappers
27.Zahlen und Zufallszahlen
28.Konstanten, Konstanz und Unveränderbarkeit
29.Enums *
30.Threads *
31.Datum und Uhrzeit
32.Performancemessung
33.Strings *
34.Arrays
35.Collections *
36.Generische Klassen *
37.Generische Methoden *
38.Maps *
39.Essentials
40.Guidelines
Dieses Kapitel zeigt Statements, Operatoren und Ausdrücke, die in einer durchschnittlichen Anwendungsprogrammierung häufig vorkommen. So begegnen uns etwa kombinierte Zuweisungen, Inkrement (c++) und Dekrement (c--) oder auch Stringverknüpfungen auf Schritt und Tritt. Dieses Kapitel zeigt und erklärt all die Java-Elemente, die für das Schreiben von einfachem prozeduralen Code typisch sind. Einige Entwickler sehen die genannten Operatoren skeptisch, weil es sich um verkürzende Schreibweisen handelt. Das vorliegende Buch möchte dagegen zeigen, dass kompakte Syntax und lesbarer Code gut zusammenpassen.
Umständlich geschriebener Code erscheint vielleicht Anfängern leichter fassbar, aber er ist für Übersichtlichkeit, Lesbarkeit und klare Strukturen schädlich. Lesbarkeit von Code wird nicht mit Ausführlichkeit und einem hohen Anteil von Leerzeilen erreicht, sondern durch die beständige Nutzung von immer gleichen Redewendungen. Diese werden sozusagen als elementarer Wortschatz in diesem Kapitel behandelt. Wenn sich ein Java-Neuling mit den häufigsten Redewendungen vertraut macht, hat er vermutlich einen einfacheren Einstieg, als wenn er sich durch alle Syntaxelemente mit gleichmäßiger Anstrengung hindurcharbeitet.
Der erste Teil der Java-Rundreise ist nur wenig mehr als ein Umherschweifen in hügeligem Gelände, um die Grundausrüstung kennenzulernen. Der zweite Teil erkundet dann ein größeres Gebiet und ist entsprechend anstrengender. Dazwischen erschließen wir uns angenehme Features wie die Lambdas, schwierige Tools wie die unvermeidbaren Exceptions, und schließlich auch die Philosophie, die Seele im Code, nämlich die Objektorientierung.
Wenn ein Entwickler einen Plan hat, kann er beginnen, Code zu schreiben. Die Pläne des Entwicklers im Kleinen betreffen dabei immer diese beiden Aufgaben:
Das Organisieren von Code in Methoden
Das Schreiben des Codes einer einzelnen Methode.
Die Rede ist von Funktionen. Funktionen heißen in Java Methoden. Beide Bezeichnungen werden weitgehend synonym verwendet. Das Denken eines Entwicklers dreht sich um Methoden. Jeglicher Code in Java läuft in Methoden ab. Und jede Methode ist Teil einer Klasse. Um Code zu schreiben, muss ein Entwickler eine Klasse bereitstellen und in dieser oder einer anderen Klasse eine main()-Funktion. Denn um ein Programm in Java auszuführen, braucht man eine main()-Funktion. Mit deren Aufruf wird das Programm gestartet. Ein Kopfdruck an der richtigen Stelle in der Entwicklungsumgebung und die Eingabe des Namens ‚Experiment’ in einen Dialog der Entwicklungsumgebung erzeugen eine Klasse mit der gewünschten main()-Methode (siehe den ersten Abschnitt im Anhang, wenn Sie hier Unterstützung brauchen):
public class
Experiment{
// Dieser Bezeichner ist beliebig
public static void
main(String[] args){}
// main() jedoch muss es immer geben
}
Ein weiterer Knopfdruck bringt die Klasse zur Ausführung. Das lässt uns natürlich kalt, denn sie tut ja nichts und folglich sehen wir auch nichts. Sollten Sie diese Klasse nicht mit einer Entwicklungsumgebung erzeugen, sondern vollständig selbst erstellen, dann achten Sie darauf, dass die Klasse in einem File mit dem Namen der Klasse und der Namenserweiterung .java steht. Die Klasse Experiment muss in dem Java-File Experiment.java gespeichert sein. Jede öffentliche Klasse muss in einem eigenen File ihres Namens stehen. Sie sollten zudem darauf achten, dass die Kopfzeile der main()-Funktion genau das gezeigte Aussehen hat. Wenn Sie vom Ablauf der Klasse etwas sehen wollen, dann stellen Sie Ausgabe-Statements wie das folgende in die main()-Funktion:
System.out.println(“Hello World”);
Den Button, dessen Betätigung dieses erste Programm zum Ablauf bringt, finden Sie ebenfalls in Ihrer Entwicklungsumgebung. In Eclipse ist dies: Run → Run As → Java Application. Wenn Sie nicht mit einer Entwicklungsumgebung arbeiten, müssen Sie das File Experiment.java zuerst in der Konsole übersetzen (javac Experiment.java). Dann können Sie den Code vom Interpreter ausführen lassen (java Experiment). Informationen über Compiler und Interpreter finden Sie im Anhang. Um sicherzustellen, dass Sie in der Lage sind, eine Klasse zu erstellen, gibt es im Anhang zudem ein kurzes Kapitel, das sich den möglichen Schwierigkeiten bei der ersten Klasse widmet. Denn ohne eigene Experimente, mit denen Sie die hier vorgestellten Beispiele und Codefragmente erproben, macht das Lesen dieses Buches wenig Sinn.
Ein Entwickler, der sich eine bevorstehende Codierung zurechtlegt, denkt entweder darüber nach, wie er den Code in Methoden aufteilt, oder wie er den Code einer einzelnen Methode schreibt. Im ersten Fall hat er etwas in dieser Art vor Augen:
Wir sehen hier ganz gut, wie der Entwickler sich die Sache denkt. Sein Plan nimmt Gestalt an und man kann den Plan lesen. Man muss sich das so vorstellen, dass wir hier zwar nur eine Klasse zeigen, dass in der Regel aber viele Klassen beteiligt sind, die allesamt sprechende Namen haben, und dass es in jeder dieser vielen Klassen viele Methoden mit ebenfalls sprechenden Namen gibt. Der Witz bei der Organisation von Code in Methoden liegt darin, dass der Code so aufgeteilt wird, dass die zu lösende Aufgabe in jeder einzelnen Methode möglichst übersichtlich und der Plan als Ganzes möglichst gut lesbar und zu verstehen ist.
Der ‚Plan als Ganzes’ ist dabei eine schöne Idee. Entwickler verfassen Pläne, wenn sie Software schreiben. Man sieht jedoch bald, dass es nicht ganz einfach ist, einen Plan zu machen und ihn aufzuschreiben. Die Schwierigkeiten beginnen bereits mit der Frage, wie ein aufgeschriebener Plan denn aussehen oder wann man ihn verfassen soll. Einen funktionierenden Plan zu erstellen, ist bedeutend schwieriger als die anschließende Umsetzung in Code. Planung und Codierung haben aber andererseits eine Menge miteinander zu tun. Sie verhalten sich etwa so wie Denken und Sprache. Das eine geht nicht ohne das andere. Niemand kann ohne fundierte Codekenntnisse einen vernünftigen Plan machen und umgekehrt ist eine Codierung ohne Plan genau das, wonach es manchmal aussieht – eben planlos.
In diesem Buch geht es sehr viel um Denken und Sprache und um Plan und Code. Man könnte auch sagen, es geht darum, wie man Software konstruiert. Für Konstruktionen braucht man mehr als die Syntax einiger Java-Statements. Wir brauchen dazu die Fähigkeit der Konzeption, das ist Planung, und wir brauchen die Fähigkeit der Umsetzung, das ist Coding. Und zudem brauchen wir eine gewisse Grundschnelligkeit. Es geht nicht nur darum, irgendwann einmal nach langer Zeit und vielen Anläufen alles in schöne Ordnung gebracht zu haben. Es geht auch um die Fähigkeit, mit einer hohen Produktivität zu konstruieren, also robuste Konstruktionen in kurzer Zeit zu erstellen. Planung und Coding und ihre Verbindung müssen so aufgesetzt sein, dass Konstruktionen rational und damit schnell, sehr schnell, durchgeführt werden können.
In diesem Hauptkapitel geht es in erster Linie um Coding. Rationalität beginnt jedoch - in kleinen Ansätzen - bereits hier. Wir müssen nicht jedes Syntaxelement in gleicher Breite beschreiben. Wir konzentrieren uns bei den Coding-Themen auf diejenige Auswahl von Syntax und Redewendungen, die geschätzte 90 Prozent des ‚durchschnittlichen’ Codes ausmachen. Wir betrachten dazu Tools wie Strings, Collections und Enums und eben elementare Syntax. Die 90-%-Regel gilt für elementare Syntax und Tools gleichermaßen. Mit einer Handvoll Tools erledigt man 90 Prozent der anfallenden Aufgaben in einer ‚durchschnittlichen’ Entwicklung. Mit der Beherrschung der wichtigsten Sprachmittel und einer guten Kombination aus Planung und Coding beginnt das minimale Konstruktionswissen, über das ein Entwickler verfügen sollte.
Sieht man nochmals auf die obige Klasse Experiment mit ihren Methoden, so verschwimmt die Grenze zwischen Planung und Code. Ist die Aufteilung in Methoden ein Mittel der Planung oder ein Mittel der Codierung? Tatsache ist, dass der Methodenaufruf eines der wichtigsten Elemente ist, um den Programmablauf zu kontrollieren. Und die Organisation von Code in Methoden ist ein elementares und zugleich wichtiges Engineering-Mittel. Wir behandeln Methoden und ihre Aufrufe deshalb als erstes. Der Java-Entwickler hat daneben aber auch alle anderen gängigen Sprachmittel zur Verfügung, die man in modernen prozeduralen und objektorientierten Sprachen kennt. Unter anderem sind das diese:
Methodenaufrufe. Um Leistungen an andere Methoden zu delegieren:
Bedingungen. Um Prüfungen durchzuführen:
Logische Operatoren. Um Bedingungen zu verknüpfen:
Verzweigungen. Um mehrwertige Bedingungen zu behandeln:
Iterationen. Um Elemente einer Menge zu durchlaufen:
Einige vorkommende Methoden wie getResults() oder getArray() sind hier nicht wesentlich und werden deshalb nicht weiter erläutert. In der Praxis müssen natürlich alle Methoden, die aufgerufen werden, an anderer Stelle vereinbart sein. Im weiteren Verlauf des Kapitels greifen wir die gezeigte Basissyntax auf, erläutern Regeln, auf die es ankommt, und zeigen an Standard-Situationen, die man häufig antrifft, was man tun und was man vermeiden sollte. Wir gehen dabei davon aus, dass die Leserinnen und Leser mit der grundsätzlichen Wirkungsweise einer Programmiersprache bereits halbwegs vertraut sind. Ein Java-Neuling, der von einer anderen Sprache her kommt, wie etwa Pascal, C oder C++, sollte mit dem gewählten Niveau gut zurecht kommen.
Die Grundlage von elementarem Java sind Methoden. Methoden sind ein erster Ansatz von Engineering. Sie dienen dazu, Code zu organisieren und die zu erbringende Leistung in benennbare Einheiten aufzuteilen. Eine Methode besteht aus Methodenkopf und Methodenkörper:
public static int
printLongArray(
long
[] array, String separator)
// Methodenkopf
{
// Ab hier Methodenkörper
…
// Die Klasse, zu der diese Methode
}
// gehört, ist nicht dargestellt
Der Methodenkopf enthält alle formalen Deklarationen zu einer Methode und legt Ein- und Ausgang fest. Er bestimmt die Sichtbarkeit (private oder public), den Returntyp und die Argumente. Die Sichtbarkeit von printLongArray() ist public, was bedeutet, dass die Methode auch außerhalb ihrer Klasse gesehen und zugegriffen werden kann. printLongArray() hat zwei Argumente, nämlich array und separator mit den Standardtypen long[] und String. Die Argumente sind der Eingang einer Methode. Sie legen fest, was an eine Methode bei ihrem Aufruf übergeben wird. Das Gegenstück dazu ist der Returnwert. Dieser ist das Ergebnis, das eine Methode nach ihrer Ausführung an den Aufrufer zurückliefert. Der Methodenkopf von printLongArray() legt fest, dass der Returnwert den elementaren Typ int hat. Die geordnete Liste der Argumente mit ihren Typen zusammen mit Methodenname, Returntyp und Sichtbarkeit nennt man auch die Signatur einer Methode.
Eine jede Methode ist Teil einer Klasse. Auch printLongArray() muss in irgendeiner Klasse vereinbart sein. Nehmen wir an, dass es sich dabei um die Klasse Experiment handelt, die wir bereits kennen. Wie die anderen Experiment-Methoden hat auch printLongArray() den Qualifier static. Java-Methoden werden nicht grundsätzlich als static vereinbart. Bis wir die Instanzmethoden kennenlernen, die nicht statisch sind (siehe dazu im Kapitel Objekte), sollten Sie Ihre Experimente jedoch mit statischen Methoden ausführen. Die Reihenfolge der Schlüsselworte public und static spielt übrigens keine Rolle. Beide Qualifier müssen aber vor dem Returntyp stehen. Wenn eine (statische) Methode von beliebigen anderen Klassen aus aufgerufen werden soll, so muss sie dazu public sein und der Methodenaufruf muss dabei durch den Klassennamen qualifiziert werden:
Experiment.printLongArray(…);
// Aufruf über Klassengrenzen hinweg
Um eine statische Methode der eigenen Klasse aufzurufen, genügt es, den Namen der Methode zu nennen. Der Aufruf einer Methode muss in jedem Fall ihrer Signatur entsprechen. Dies bedeutet, dass alle Argumente mit dem richtigen Typ gegeben und in der richtigen Reihenfolge präsentiert werden müssen. Der Returnwert kann entgegen genommen werden oder auch nicht. Im ersteren Fall muss auch für ihn eine Variable mit dem richtigen Typ bereitgestellt werden. Hier ist eine kleine Reihe von Beispielstatements, welche die Aufrufregeln illustrieren:
Derartige im Kommentar markierte Fehler sind immer harmlos. Denn es handelt sich um Fehler, die der Compiler bemerkt. Der Compiler lehnt dann die Übersetzung des betreffenden Codes ab. Ein Fehler, den der Compiler bemerkt, ist deshalb harmlos, weil er vom Entwickler unmittelbar erkannt wird und behoben werden kann. Ein nicht-harmloser Fehler ist ein Fehler, der erst zur Laufzeit auftritt, alle Test übersteht, nur sporadisch auftritt und das erste Mal in Erscheinung tritt, wenn die Applikation gerade an 1000 Anwender verteilt worden ist.
Das Beispiel zeigt, dass beim Aufruf einer Methode die übergebenen Argumente in ihren Typen auf die deklarierten Argumente passen müssen. Ebenso muss der Returntyp zum Typ der aufnehmenden Variable passen. Dies ist in Java elementar. Der Compiler kann geringfügige Anpassungen automatisch vornehmen, etwa wenn durch den Typ eines Arguments ein long gefordert und beim Aufruf irgendein anderer ganzzahliger Typ wie short oder byte übergeben wird. Denn diese Typen lassen sich ohne Informationsverlust in ein long umwandeln (siehe unter Cast). Wenn beim Methodenaufruf die Erwartung an die Typen der Argumente und ihre Anzahl nicht erfüllt wird, lehnt der Compiler ab und übersetzt nicht. Da der Name der Methode nicht das einzige entscheidende Kriterium bei ihrem Aufruf ist, können verschiedene Methoden mit gleichem Namen vereinbart werden, wenn sie sich in Zahl, Reihenfolge oder Typ der Argumente unterscheiden. Zwei Methoden gleichen Namens in einer Klasse, die sich nur im Returntyp unterscheiden, sind dagegen nicht erlaubt.
Verschiedene Methoden können den gleichen Namen tragen, wenn sich ihre Argumente durch Anzahl, Typ oder Reihenfolge unterscheiden
Wenn mehrere Methoden mit gleichem Namen innerhalb einer Klasse vereinbart werden, so nennt man dies Overloading. Overloading (Überladen) ist etwas sehr Triviales. Gäbe es für Instanzmethoden nicht einen wichtigen ähnlichen Mechanismus, nämlich Overriding, so würde der Term ‚Overloading’ hier gar nicht erwähnt werden. Das folgende Beispiel zeigt einige mögliche Überladungen in der Klasse Experiment:
public class
Experiment{
public static void
main(String[] args) {… }
public static int
printLongArray(
long
[] array, String separator) {… }
public static int
printLongArray(
long
[] array, String separator,
boolean
useSingleLine) {… }
public static void
printLongArray(
long
[] array, String separator,
short
itemsPerLine) {… }
public static int
printLongArray(
int
[] array, String separator) { … }
}
Wir sehen einfache Überladungen, die dadurch entstehen, dass die Zahl der Argumente variiert wird oder dass ein Argument einen anderen Typ erhält. Natürlich kann man Überladungen auch dadurch erreichen, dass man die Reihenfolge der Argumente vertauscht. Allerdings sollte für den Anwender der Methoden ohne Weiteres ersichtlich sein, was diese tun und worin ihr Unterschied liegt. Im obigen Beispiel gibt es da keinerlei Schwierigkeiten. Im folgenden Beispiel ist dagegen von außen nicht zu erkennen, was die beiden printLongArray()-Methoden unterscheidet:
public class
Experiment{
// Problematische Überladung
public static void
main(String[] args) {… }
public static int
printLongArray(
int
[] array, String separator) { … }
public static int
printLongArray(String separator,
int
[] array) { … }
}
Die Möglichkeit, mehrere Methoden in einer Klasse mit gleichem Namen zu verfassen, ist nicht nur theoretisch gegeben, sondern wird in der Praxis ausgiebig genutzt. Es ist sehr hilfreich, nicht beständig neue Methodennamen erfinden zu müssen, wenn man mehrere Methoden mit einer ähnlichen Leistung anbieten möchte. Welche Namen würde man für die Menge der printLongArray()-Methoden im obigen Beispiel wählen, wenn Overloading nicht zulässig wäre. Einige von uns würden es sicher mit printLongArray1(), printLongArray2() und so fort versuchen. Bei der Anwendung dieser Methoden würde man dann den unangenehmen Effekt entdecken, dass man bei jedem Methodenaufruf erst mühsam herausfinden muss, welche Zahl, beziehungsweise welcher Name zu welcher Argumentkombination gehört. Von daher ist Overloading zwar kein großes Feature, aber sehr praktisch.
In einer der obigen Versionen von printLongArray() sind drei Argumente gegeben: ein Array, ein String und ein boolean. Was passiert mit diesen Argumenten beim Methodenaufruf? Werden die Argumente kopiert? Oder sieht die gerufene Methode das Original? Ist es für den Aufrufer relevant, wenn die gerufene Methode ein Argument ändert? Die Antwort auf diese Fragen liegt in dem Unterschied zwischen elementaren Typen wie boolean und wirklichen Objekten (Instanzen) wie String und Array. Wir beginnen diese wichtige Erläuterung mit einer Nonsense-Implementierung von printLongArray():
Möglicherweise sind Sie ein ungeduldiger Mensch und möchten sofort wissen, was denn in Java ein boolean ist oder ein long oder was elementare Typen sind. Wenn Sie öfters von dieser Ungeduld getrieben werden, so gibt es für Sie zwei einfache Optionen. Diese heißen Glossar und Essentials. Beide sind Orte der Erklärung und Teil dieses Buches und befinden sich somit in Ihrem unmittelbaren Zugriff. Die genannten Fragen haben mit dem Typsystem von Java zu tun, das in einem späteren Teil dieses Buches behandelt wird. Eine kurze Übersicht ist hier hilfreich.