Kapitel 1. Eine moderne Sprache

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Die größten Herausforderungen und aufregendsten Möglichkeiten für Softwareentwickler/innen liegen heute in der Nutzung der Leistungsfähigkeit von Netzwerken. Anwendungen, die heute entwickelt werden, werden unabhängig von ihrem Anwendungsbereich oder ihrer Zielgruppe mit ziemlicher Sicherheit auf Rechnern laufen, die durch ein globales Netzwerk von Rechenressourcen verbunden sind. Die zunehmende Bedeutung von Netzwerken stellt neue Anforderungen an bestehende Tools und fördert die Nachfrage nach einer schnell wachsenden Liste völlig neuer Arten von Anwendungen.

Als wollen wir Software, die überall und auf jeder Plattform zuverlässig funktioniert und gut mit anderen Anwendungen zusammenarbeitet. Wir wollen dynamische Anwendungen, die die Vorteile einer vernetzten Welt nutzen und auf unterschiedliche und verteilte Informationsquellen zugreifen können. Wir wollen eine wirklich verteilte Software, die sich nahtlos erweitern und aktualisieren lässt. Wir wollen intelligente Anwendungen, die für uns in der Cloud unterwegs sind, um Informationen aufzuspüren und als elektronische Boten zu dienen. Wir wissen schon seit einiger Zeit, welche Art von Software wir wollen, aber erst in den letzten Jahren haben wir damit begonnen, sie zu bekommen.

Das Problem war in der Vergangenheit, dass die Werkzeuge für die Entwicklung dieser Anwendungen nicht ausreichten. Die Anforderungen an Geschwindigkeit und Portabilität schlossen sich größtenteils gegenseitig aus, und die Sicherheit wurde weitgehend ignoriert oder missverstanden. In der Vergangenheit waren wirklich portable Sprachen sperrig, interpretiert und langsam. Diese Sprachen waren eher wegen ihrer High-Level-Funktionalität als wegen ihrer Portabilität beliebt. Schnelle Sprachen boten in der Regel Geschwindigkeit, indem sie sich an bestimmte Plattformen banden, so dass sie die Portabilitätsanforderung nur halbwegs erfüllten. Es gab sogar ein paar Sprachen, die Programmierer dazu anregten, besseren und sichereren Code zu schreiben, aber sie waren hauptsächlich Ableger der portablen Sprachen und litten unter denselben Problemen. Java ist eine moderne Sprache, die alle drei Bereiche abdeckt: Portabilität, Geschwindigkeit und Sicherheit. Deshalb ist sie auch fast drei Jahrzehnte nach ihrer Einführung noch immer die dominierende Sprache in der Welt der Programmierung.

Java eingeben

Die Programmiersprache Java wurde entwickelt, um eine maschinenunabhängige Programmiersprache zu sein, die sowohl sicher genug ist, um Netzwerke zu durchqueren, als auch leistungsfähig genug, um nativen ausführbaren Code zu ersetzen. Java geht auf die hier angesprochenen Probleme ein und spielte eine Hauptrolle bei der Entwicklung des Internets, die uns dorthin geführt hat, wo wir heute stehen.

Java hat sich zur führenden Plattform für webbasierte Anwendungen und Webdienste entwickelt. Diese Anwendungen nutzen Technologien wie die Java Servlet API, Java Web Services und viele beliebte Open Source und kommerzielle Java Application Server und Frameworks. Die Portabilität und Geschwindigkeit von Java machen es zur bevorzugten Plattform für moderne Geschäftsanwendungen. Java-Server, die auf Open-Source-Linux-Plattformen laufen, sind heute das Herzstück der Geschäfts- und Finanzwelt.

Ursprünglich konzentrierte sich die Begeisterung von für Java vor allem auf seine Möglichkeiten, eingebettete Anwendungen für das Web, sogenannte Applets, zu erstellen. Aber in den ersten Tagen waren Applets und andere in Java geschriebene grafische Benutzeroberflächen (GUIs) auf der Client-Seite begrenzt. Heute verfügt Java über Swing, ein ausgefeiltes Toolkit zur Erstellung von GUIs. Diese Entwicklung hat Java zu einer brauchbaren Plattform für die Entwicklung traditioneller clientseitiger Anwendungssoftware gemacht, auch wenn viele andere Anbieter in dieses überfüllte Feld eingetreten sind.

Dieses Buch zeigt dir, wie du mit Java Programmieraufgaben in der Praxis lösen kannst. In den folgenden Kapiteln stellen wir dir eine große Auswahl an Java-Funktionen vor, darunter Textverarbeitung, Netzwerke, Dateiverarbeitung und die Erstellung von Desktop-Anwendungen mit Swing.

Die Ursprünge von Java

Die Saat von Java wurde 1990 vom Patriarchen und Chefforscher von Sun Microsystems, Bill Joy, gepflanzt. Damals konkurrierte Sun auf einem relativ kleinen Markt für Workstations, während Microsoft begann, die Mainstream-PC-Welt auf Intel-Basis zu beherrschen. Als Sun den Anschluss an die PC-Revolution verpasste, zog sich Joy nach Aspen, Colorado, zurück, um dort an fortschrittlicher Forschung zu arbeiten. Er setzte sich für die Idee ein, komplexe Aufgaben mit einfacher Software zu bewältigen und gründete die Sun Aspen Smallworks mit dem treffenden Namen.

Von den ursprünglichen Mitgliedern des kleinen Teams von Programmierern, das Joy in Aspen zusammenstellte, wird man sich an James Gosling als den Vater von Java erinnern. Gosling machte sich in den frühen 1980er Jahren einen Namen als Autor von Gosling Emacs, der ersten Version des beliebten Emacs-Editors, der in C geschrieben wurde und unter Unix lief. Der Gosling Emacs wurde schon bald von einer freien Version, dem GNU Emacs, verdrängt, die vom ursprünglichen Entwickler des Emacs geschrieben wurde. Zu diesem Zeitpunkt hatte Gosling bereits das Network extensible Window System (NeWS) von Sun entwickelt, das 1987 kurzzeitig mit dem X Window System um die Kontrolle über den Unix-GUI-Desktop konkurrierte. Obwohl einige Leute behaupten, dass NeWS X überlegen war, verlor NeWS, weil Sun es proprietär hielt und den Quellcode nicht veröffentlichte, während die Hauptentwickler von X das X-Konsortium gründeten und den entgegengesetzten Ansatz verfolgten.

Bei der Entwicklung von NeWS lernte Gosling, wie wichtig die Integration einer ausdrucksstarken Sprache mit einer netzwerkfähigen grafischen Benutzeroberfläche ist. Es lehrte Sun auch, dass die Internet-Programmiergemeinde sich letztendlich weigern wird, proprietäre Standards zu akzeptieren, ganz gleich wie gut sie auch sein mögen. Das Scheitern von NeWS legte den Grundstein für das Java-Lizenzierungssystem und den offenen (wenn auch nicht ganz "Open Source") Code. Gosling brachte das, was er gelernt hatte, in Bill Joys entstehendes Aspen-Projekt ein. Die Arbeit an diesem Projekt führte 1992 zur Gründung der Sun-Tochter FirstPerson, Inc. Ihre Aufgabe war es, Sun in die Welt der Unterhaltungselektronik zu führen.

Das FirstPerson-Team arbeitete an der Entwicklung von Software für Informationsgeräte wie Mobiltelefone und Personal Digital Assistants (PDAs). Ziel war es, die Übertragung von Informationen und Echtzeitanwendungen über billige Infrarot- und traditionelle paketbasierte Netzwerke zu ermöglichen. Speicher- und Bandbreitenbeschränkungen erforderten einen kleinen, effizienten Code. Die Art der Anwendungen verlangte außerdem, dass sie sicher und robust waren. Gosling und seine Teamkollegen begannen mit der Programmierung in C++, aber schon bald stellten sie fest, dass diese Sprache für diese Aufgabe zu komplex, unhandlich und unsicher war. Sie beschlossen, von vorne anzufangen, und Gosling begann mit der Arbeit an etwas, das er "C++ minus minus" nannte.

Mit dem Scheitern des Apple Newton (Apples erstem Handheld-Computer) wurde klar, dass das Schiff der PDAs noch nicht eingelaufen war, also verlagerte Sun die Bemühungen von FirstPerson auf das interaktive Fernsehen (ITV). Die Programmiersprache der Wahl für ITV-Set-Top-Boxen sollte der nahe Vorläufer von Java sein, eine Sprache namens Oak. Trotz ihrer Eleganz und ihrer Fähigkeit, sichere Interaktivität zu bieten, konnte Oak die verlorene Sache von ITV nicht retten. Die Kunden wollten es nicht, und Sun gab das Konzept bald auf.

Damals setzten sich Joy und Gosling zusammen, um eine neue Strategie für ihre innovative Sprache zu entwickeln. Es war 1993, und das explosionsartige Interesse am Web bot eine neue Chance. Oak war klein, sicher, architekturunabhängig und objektorientiert. Zufälligerweise sind dies auch einige der Anforderungen an eine universelle, internetfähige Programmiersprache. Sun änderte schnell den Fokus und mit ein wenig Umrüstung wurde aus Oak Java.

Aufwachsen

Es wäre keine Übertreibung zu sagen, dass Java (und sein auf Entwickler ausgerichtetes Paket, das Java Development Kit, oder JDK) wie ein Lauffeuer einschlug. Schon vor der ersten offiziellen Veröffentlichung, als Java noch kein Produkt war, sprangen fast alle großen Industrieunternehmen auf den Java-Zug auf. Zu den Java-Lizenznehmern gehörten Microsoft, Intel, IBM und praktisch alle großen Hardware- und Softwarehersteller. Doch trotz all dieser Unterstützung musste Java in den ersten Jahren viele Rückschläge einstecken und hatte mit einigen Wachstumsschmerzen zu kämpfen.

Eine Reihe von Vertragsverletzungs- und Kartellrechtsklagen zwischen Sun und Microsoft über den Vertrieb von Java und seine Verwendung im Internet Explorer behinderten die Verbreitung von Java auf dem weltweit am meisten verbreiteten Desktop-Betriebssystem - Windows. Microsofts Beteiligung an Java wurde auch zu einem Schwerpunkt eines größeren Bundesgerichtsverfahrens wegen schwerwiegender wettbewerbswidriger Praktiken des Unternehmens. Aus den Zeugenaussagen vor Gericht ging hervor, dass der Softwareriese versucht hatte, Java durch die Einführung von Inkompatibilitäten in seiner Version der Sprache zu untergraben. In der Zwischenzeit führte Microsoft im Rahmen seiner .NET-Initiative seine eigene, von Java abgeleitete Sprache namens C# (C-sharp) ein und strich Java aus der Windows-Version. C# hat sich zu einer sehr guten eigenständigen Sprache entwickelt, die in den letzten Jahren mehr Innovationen hervorgebracht hat als Java.

Aber Java verbreitet sich weiterhin auf einer Vielzahl von Plattformen. Wenn wir uns die Java-Architektur ansehen, wirst du sehen, dass ein Großteil der Faszination von Java von der in sich geschlossenen virtuellen Maschinenumgebung ausgeht, in der Java-Anwendungen laufen. Java wurde sorgfältig so konzipiert, dass diese unterstützende Architektur entweder in Software für bestehende Computerplattformen oder in angepasster Hardware implementiert werden kann. Hardware-Implementierungen von Java werden in einigen Smart Cards und anderen eingebetteten Systemen verwendet. Du kannst sogar "tragbare" Geräte wie Ringe und Hundemarken kaufen, in denen Java-Interpreter integriert sind. Software-Implementierungen von Java gibt es für alle modernen Computerplattformen, bis hin zu tragbaren Computern. Ein Ableger der Java-Plattform bildet heute die Grundlage für das Google-Betriebssystem Android, das auf Milliarden von Handys und anderen mobilen Geräten läuft.

Im Jahr 2010 kaufte die Oracle Corporation Sun Microsystems und wurde zum Verwalter der Java-Sprache. Nach einem etwas holprigen Start verklagte Oracle Google wegen der Verwendung der Java-Sprache in Android und verlor. Im Juli 2011 veröffentlichte Oracle die Java Standard Edition 7,1 eine wichtige Java-Version, die ein neues E/A-Paket enthielt. Im Jahr 2017 wurden mit Java 9 Module eingeführt, die einige seit langem bestehende Probleme mit der Art und Weise, wie Java-Anwendungen kompiliert, verteilt und ausgeführt werden, beheben. Mit Java 9 wurde auch ein schneller Aktualisierungsprozess eingeleitet, der dazu führte, dass einige Java-Versionen als "Langzeitunterstützung" und die übrigen als kurzfristige Standardversionen eingestuft wurden. (Mehr zu diesen und anderen Versionen in der "Java-Roadmap".) Oracle ist nach wie vor führend in der Java-Entwicklung, hat aber auch die Java-Welt gespalten, indem es die Haupt-Java-Entwicklungsumgebung in eine teure kommerzielle Lizenz umgewandelt hat, während es gleichzeitig eine kostenlose OpenJDK-Option anbietet, die die Zugänglichkeit beibehält, die viele Entwickler/innen lieben und erwarten.

Eine virtuelle Maschine

Bevor wir weitermachen, ist es nützlich, ein wenig mehr über die Umgebung zu wissen Java braucht, um seine Arbeit zu verrichten. Es ist in Ordnung, wenn du nicht alles verstehst, was wir in den nächsten Abschnitten ansprechen. Jeder unbekannte Begriff, der dir begegnet, wird in späteren Kapiteln erklärt. Wir wollen dir lediglich einen Überblick über das Ökosystem von Java geben. Das Herzstück dieses Ökosystems ist die Java Virtual Machine (JVM).

Java ist sowohl eine kompilierte als auch eine interpretierte Sprache. Der Java-Quellcode wird in einfache Binäranweisungen umgewandelt, ähnlich wie gewöhnlicher Mikroprozessor-Maschinencode. Während der C- oder C++-Quellcode jedoch auf native Anweisungen für ein bestimmtes Prozessormodell reduziert wird, wird der Java-Quellcode in ein universelles Format kompiliert - Anweisungen für die virtuelle Maschine, den sogenannten Bytecode.

Der Java Bytecode wird von einem Java-Laufzeitinterpreter ausgeführt. Das Laufzeitsystem führt alle normalen Aktivitäten eines Hardware-Prozessors aus, allerdings in einer sicheren, virtuellen Umgebung. Es führt einen stapelbasierten Befehlssatz aus und verwaltet den Speicher wie ein Betriebssystem. Es erstellt und manipuliert primitive Datentypen und lädt und ruft neu referenzierte Codeblöcke auf. Das Wichtigste ist jedoch, dass sie all dies in Übereinstimmung mit einer streng definierten offenen Spezifikation tut, die von jedem implementiert werden kann, der eine Java-konforme virtuelle Maschine erstellen möchte. Zusammen bilden die virtuelle Maschine und die Sprachdefinition eine vollständige Spezifikation. Es gibt keine Merkmale der Java-Basissprache, die undefiniert oder von der Implementierung abhängig sind. Zum Beispiel legt Java die Größen und mathematischen Eigenschaften aller primitiven Datentypen fest und überlässt dies nicht der Implementierung der Plattform.

Der Java-Interpreter ist relativ leichtgewichtig und klein; er kann in jeder Form implementiert werden, die für eine bestimmte Plattform wünschenswert ist. Der Interpreter kann als eigenständige Anwendung ausgeführt oder in eine andere Software, z. B. einen Webbrowser, eingebettet werden. Zusammengefasst bedeutet dies, dass Java-Code implizit portabel ist. Derselbe Java-Anwendungsbytecode kann auf jeder Plattform ausgeführt werden, die eine Java-Laufzeitumgebung bietet (siehe Abbildung 1-1). Du musst keine alternativen Versionen deiner Anwendung für verschiedene Plattformen erstellen und du musst den Quellcode nicht an die Endbenutzer weitergeben.

ljv6 0101
Abbildung 1-1. Die Java-Laufzeitumgebung

Die grundlegende Einheit von Java-Code ist die Klasse. Wie in anderen objektorientierten Sprachen sind Klassen kleine, modulare Anwendungskomponenten, die ausführbaren Code und Daten enthalten. Kompilierte Java-Klassen werden in einem universellen Binärformat verteilt, das Java-Bytecode und andere Klasseninformationen enthält. Die Klassen können diskret verwaltet und in Dateien oder Archiven lokal oder auf einem Netzwerkserver gespeichert werden. Die Klassen werden zur Laufzeit dynamisch gefunden und geladen, wenn sie von einer Anwendung benötigt werden.

Neben dem plattformspezifischen Laufzeitsystem verfügt Java über eine Reihe von Basisklassen, die architekturabhängige Methoden enthalten. Diese nativen Methoden dienen als Schnittstelle zwischen der virtuellen Java-Maschine und der realen Welt. Sie sind in einer nativ kompilierten Sprache auf der Host-Plattform implementiert und ermöglichen den Low-Level-Zugriff auf Ressourcen wie das Netzwerk, das Windowing-System und das Host-Dateisystem. Der überwiegende Teil von Java ist jedoch in Java selbst geschrieben - aus diesen grundlegenden Teilen gebootstrapped - und daher portabel. Dazu gehören auch wichtige Java-Tools wie der Java-Compiler, der ebenfalls in Java geschrieben wurde und daher auf allen Java-Plattformen auf die gleiche Weise ohne Portierung verfügbar ist.

In der Vergangenheit galten Interpreter als langsam, aber Java ist keine traditionelle interpretierte Sprache. Neben der Kompilierung des Quellcodes in portablen Bytecode wurde Java auch so konzipiert, dass Softwareimplementierungen des Laufzeitsystems ihre Leistung weiter optimieren können, indem sie den Bytecode im laufenden Betrieb in nativen Maschinencode kompilieren. Dies wird als dynamische oder Just-in-Time (JIT)-Kompilierung bezeichnet. Mit der JIT-Kompilierung kann Java-Code so schnell wie nativer Code ausgeführt werden und behält dabei seine Transportfähigkeit und Sicherheit.

Diese JIT-Funktion ist ein oft missverstandener Punkt unter denjenigen, die die Leistung von Sprachen vergleichen wollen. Es gibt nur eine einzige Leistungseinbuße, die kompilierter Java-Code zur Laufzeit aus Gründen der Sicherheit und des Designs der virtuellen Maschine hinnehmen muss: die Überprüfung von Array-Bounds. Alles andere kann in nativem Code optimiert werden, genau wie bei einer statisch kompilierten Sprache. Darüber hinaus enthält die Java-Sprache mehr Strukturinformationen als viele andere Sprachen, was mehr Optimierungsmöglichkeiten bietet. Erinnere dich auch daran, dass diese Optimierungen zur Laufzeit vorgenommen werden können, wobei das tatsächliche Verhalten und die Eigenschaften der Anwendung berücksichtigt werden. Was kann zur Kompilierzeit getan werden, was zur Laufzeit nicht besser gemacht werden kann? Nun, es gibt einen Kompromiss: Zeit.

Das Problem bei einer herkömmlichen JIT-Kompilierung ist, dass die Optimierung des Codes Zeit braucht. Ein JIT-Compiler kann zwar gute Ergebnisse liefern, aber beim Starten der Anwendung kann es zu erheblichen Latenzen kommen. Das ist in der Regel kein Problem für langlaufende serverseitige Anwendungen, aber es ist ein ernstes Problem für clientseitige Software und Anwendungen, die auf kleineren Geräten mit begrenzten Möglichkeiten laufen. Um dieses Problem zu lösen, verwendet die Java-Compilertechnologie namens HotSpot einen Trick, der adaptive Kompilierung genannt wird. Wenn du dir ansiehst, womit Programme tatsächlich ihre Zeit verbringen, stellt sich heraus, dass sie fast ihre gesamte Zeit damit verbringen, einen relativ kleinen Teil des Codes immer wieder auszuführen. Der Teil des Codes, der immer wieder ausgeführt wird, macht zwar nur einen kleinen Teil des gesamten Programms aus, aber sein Verhalten bestimmt die Gesamtleistung des Programms. Durch die adaptive Kompilierung kann die Java-Laufzeitumgebung neue Arten von Optimierungen nutzen, die in einer statisch kompilierten Sprache einfach nicht möglich sind - daher die Behauptung, dass Java-Code in manchen Fällen schneller als C/C++ laufen kann.

Um diese Anpassungsfähigkeit zu nutzen, beginnt HotSpot wie ein normaler Java-Bytecode-Interpreter, aber mit einem Unterschied: Er misst (profiliert) den Code während der Ausführung, um zu sehen, welche Teile wiederholt ausgeführt werden. Sobald er weiß, welche Teile des Codes für die Leistung entscheidend sind, kompiliert HotSpot diese Abschnitte in optimalen nativen Maschinencode. Da nur ein kleiner Teil des Programms in Maschinencode kompiliert wird, kann es sich die Zeit leisten, die für die Optimierung dieser Teile notwendig ist. Der Rest des Programms muss möglicherweise gar nicht kompiliert, sondern nur interpretiert werden, was Speicher und Zeit spart. Die Java VM kann in einem von zwei Modi laufen: Client und Server. Diese Modi bestimmen, ob der Schwerpunkt auf einer schnellen Startzeit und Speicherplatzersparnis oder auf einer hohen Leistung liegt. Ab Java 9 kannst du auch die AOT-Kompilierung (AOT = ahead of time ) nutzen, wenn die Minimierung der Startzeit deiner Anwendung wirklich wichtig ist.

An dieser Stelle stellt sich natürlich die Frage, warum all diese guten Profiling-Informationen jedes Mal weggeworfen werden, wenn eine Anwendung heruntergefahren wird? Nun, Sun hat dieses Thema mit der Veröffentlichung von Java 5.0 durch die Verwendung von gemeinsam genutzten, schreibgeschützten Klassen, die dauerhaft in optimierter Form gespeichert werden, teilweise aufgegriffen. Dadurch wurden sowohl die Startzeit als auch der Overhead beim Ausführen vieler Java-Anwendungen auf einem bestimmten Rechner erheblich reduziert. Die Technologie dafür ist komplex, aber die Idee ist einfach: Optimiere die Teile des Programms, die schnell laufen müssen, und kümmere dich nicht um den Rest.

Natürlich enthält der "Rest" auch Code, der weiter optimiert werden könnte. Im Jahr 2022 startete das OpenJDK-Projekt Leyden mit dem Ziel, die Startzeit weiter zu verkürzen, die Größe von Java-Anwendungen zu minimieren und die Zeit zu verkürzen, die benötigt wird, bis alle zuvor genannten Optimierungen ihre volle Wirkung entfalten. Die vom Projekt Leyden vorgeschlagenen Mechanismen sind ziemlich komplex, deshalb werden wir sie in diesem Buch nicht besprechen. Wir wollen aber die kontinuierliche Arbeit an der Entwicklung und Verbesserung von Java und seinem Ökosystem hervorheben. Auch rund 30 Jahre nach seinem Debüt ist Java eine moderne Sprache.

Java im Vergleich zu anderen Sprachen

Die Entwickler von Java haben bei der Auswahl der Funktionen auf viele Jahre Programmiererfahrung mit anderen Sprachen zurückgegriffen. Es lohnt sich, Java auf hohem Niveau mit einigen dieser Sprachen zu vergleichen, sowohl für diejenigen unter euch, die bereits über Programmiererfahrung verfügen, als auch für die Neulinge, die die Dinge in einen Kontext stellen müssen. Dieses Buch setzt zwar voraus, dass du dich mit Computern und Softwareanwendungen im allgemeinen Sinne auskennst, aber wir erwarten nicht, dass du eine bestimmte Programmiersprache kennst. Wenn wir zum Vergleich auf andere Sprachen verweisen, hoffen wir, dass die Kommentare selbsterklärend sind.

Mindestens drei Säulen sind notwendig, um heute eine universelle Programmiersprache zu unterstützen: Portabilität, Geschwindigkeit und Sicherheit. Abbildung 1-2 zeigt, wie Java im Vergleich zu einigen der Sprachen abschneidet, die bei seiner Entwicklung beliebt waren.

ljv6 0102
Abbildung 1-2. Programmiersprachen im Vergleich

Du hast vielleicht schon gehört, dass Java sehr ähnlich ist wie C oder C++, aber das stimmt nur oberflächlich betrachtet wirklich nicht. Wenn du dir Java-Code zum ersten Mal ansiehst, wirst du feststellen, dass die grundlegende Syntax wie C oder C++ aussieht. Aber da enden die Ähnlichkeiten auch schon. Java ist keineswegs ein direkter Nachfahre von C oder die nächste Generation von C++. Wenn du die Sprachmerkmale vergleichst, wirst du feststellen, dass Java mehr mit hochdynamischen Sprachen wie Smalltalk und Lisp gemeinsam hat. Tatsächlich ist die Java-Implementierung so weit von nativem C entfernt, wie du es dir nur vorstellen kannst.

Wenn du mit der aktuellen Sprachlandschaft vertraut bist, wirst du feststellen, dass C#, eine beliebte Sprache, in diesem Vergleich fehlt. C# ist größtenteils Microsofts Antwort auf Java, wenn auch mit einer Reihe von Feinheiten darüber. Angesichts der gemeinsamen Entwicklungsziele und -ansätze (z. B. die Verwendung einer virtuellen Maschine, von Bytecode und einer Sandbox) unterscheiden sich die Plattformen in Bezug auf ihre Geschwindigkeit und ihre Sicherheitseigenschaften nicht wesentlich. C# ist mehr oder weniger so portabel wie Java. Wie Java lehnt sich auch C# stark an die C-Syntax an, ist aber eigentlich ein engerer Verwandter der dynamischen Sprachen. Die meisten Java-Entwickler/innen finden es relativ einfach, C# zu erlernen und umgekehrt. Der größte Teil der Zeit, die du für den Wechsel von der einen zur anderen Sprache benötigst, wird auf das Erlernen der Standardbibliothek entfallen.

Die oberflächlichen Ähnlichkeiten zu diesen Sprachen sind jedoch erwähnenswert. Java lehnt sich stark an die C- und C++-Syntax an, du wirst also knappe Sprachkonstrukte sehen, darunter eine Fülle von geschweiften Klammern und Semikolons. Java folgt der C-Philosophie, dass eine gute Sprache kompakt sein sollte, d. h. sie sollte so klein und regelmäßig sein, dass ein/e Programmierer/in all ihre Fähigkeiten auf einmal im Kopf behalten kann. Genauso wie C mit Bibliotheken erweiterbar ist, können in Java Pakete mit Klassen zu den Kernkomponenten der Sprache hinzugefügt werden, um den Wortschatz zu erweitern.

C war erfolgreich, weil es eine Programmierumgebung mit vielen Funktionen, hoher Leistung und einem akzeptablen Maß an Portabilität bietet. Auch Java versucht, Funktionalität, Geschwindigkeit und Portabilität unter einen Hut zu bringen, allerdings auf eine ganz andere Art und Weise. C tauscht Funktionalität gegen Portabilität, während Java ursprünglich Geschwindigkeit gegen Portabilität tauschte. Java geht auch auf Sicherheitsprobleme ein, die C nicht hat (obwohl in modernen Systemen viele dieser Probleme inzwischen durch das Betriebssystem und die Hardware gelöst werden).

Skriptsprachen wie Perl, Python und Ruby sind nach wie vor beliebt. Es gibt keinen Grund, warum eine Skriptsprache nicht für sichere, vernetzte Anwendungen geeignet sein kann. Aber die meisten Skriptsprachen eignen sich nicht für eine ernsthafte, umfangreiche Programmierung. Der Reiz von Skriptsprachen liegt darin, dass sie dynamisch sind; sie sind leistungsstarke Werkzeuge für eine schnelle Entwicklung. Einige Skriptsprachen, wie z. B. Tcl (zur Zeit der Java-Entwicklung sehr beliebt), helfen Programmierern auch bei der Bewältigung spezieller Aufgaben, wie z. B. der schnellen Erstellung von grafischen Oberflächen, die mit allgemeineren Sprachen nur schwer zu bewältigen sind. Skriptsprachen sind außerdem in hohem Maße portabel, wenn auch nur auf der Ebene des Quellcodes.

Nicht zu verwechseln mit Java, JavaScript ist eine objektbasierte Skriptsprache, die ursprünglich von Netscape für den Webbrowser entwickelt wurde. Sie dient als browserinterne Sprache für dynamische, interaktive, webbasierte Anwendungen. JavaScript hat seinen Namen von der Integration und den Ähnlichkeiten mit Java, aber damit endet der Vergleich auch schon. Es gibt jedoch auch bedeutende Anwendungen von JavaScript außerhalb des Browsers, wie z. B. Node.js,2 und erfreut sich bei Entwicklern in den verschiedensten Bereichen immer größerer Beliebtheit. Weitere Informationen zu JavaScript findest du in JavaScript: The Definitive Guide von David Flanagan (O'Reilly).

Das Problem mit Skriptsprachen ist, dass sie die Programmstruktur und die Datentypisierung eher lässig handhaben. Sie haben vereinfachte Typensysteme und bieten in der Regel kein ausgefeiltes Scoping von Variablen und Funktionen. Diese Eigenschaften machen sie weniger geeignet für die Entwicklung großer, modularer Anwendungen. Ein weiteres Problem bei Skriptsprachen ist die Geschwindigkeit: Da diese Sprachen auf hoher Ebene arbeiten und in der Regel quelltextinterpretiert werden, sind sie oft recht langsam.

Befürworter einzelner Skriptsprachen würden gegen einige dieser Verallgemeinerungen Einspruch erheben, und zweifellos hätten sie in einigen Fällen Recht. Skriptsprachen haben sich in den letzten Jahren verbessert - vor allem JavaScript, in dessen Leistung enorm viel Forschung geflossen ist. Aber der grundsätzliche Kompromiss ist unbestreitbar: Skriptsprachen wurden als lockere, weniger strukturierte Alternativen zu Systemprogrammiersprachen entwickelt und sind aus einer Vielzahl von Gründen nicht ideal für große oder komplexe Projekte.

Java bietet einige der wesentlichen Vorteile einer Skriptsprache: Sie ist hochdynamisch und hat die zusätzlichen Vorteile einer Sprache auf niedrigerem Niveau. Java verfügt über ein leistungsstarkes Paket für reguläre Ausdrücke, das bei der Arbeit mit Text mit Perl konkurriert. Außerdem verfügt die Sprache über Funktionen, die die Programmierung mit Sammlungen, Variablenargumentlisten, statischen Methodenimporten und anderen syntaktischen Zuckern, die die Sprache übersichtlicher machen, vereinfachen.

Die inkrementelle Entwicklung mit objektorientierten Komponenten in Kombination mit der Einfachheit von Java ermöglicht es, Anwendungen schnell zu entwickeln und sie leicht zu ändern. Studien haben ergeben, dass die Entwicklung in Java schneller ist als in C oder C++, wenn man nur die Sprachfunktionen betrachtet.3 Java verfügt außerdem über eine große Anzahl von Standard-Kernklassen für gängige Aufgaben wie die Erstellung von GUIs und die Netzwerkkommunikation. Maven Central ist eine externe Ressource mit einer riesigen Auswahl an Bibliotheken und Paketen, die du schnell in deine Umgebung einbinden kannst, um alle Arten von neuen Programmierproblemen zu bewältigen. Neben diesen Funktionen bietet Java die Skalierbarkeit und die Vorteile der Softwareentwicklung, die statischere Sprachen bieten. Sie bietet eine sichere Struktur, auf der du höherwertige Frameworks (und sogar andere Sprachen) aufbauen kannst.

Wie wir bereits gesagt haben, ist Java ähnlich aufgebaut wie Sprachen wie Smalltalk und Lisp. Diese Sprachen wurden jedoch hauptsächlich als Forschungsinstrumente und nicht für die Entwicklung großer Systeme eingesetzt. Ein Grund dafür ist, dass diese Sprachen nie eine standardmäßige, portable Anbindung an Betriebssystemdienste entwickelt haben, wie z. B. die C-Standardbibliothek oder die Java-Kernklassen. Smalltalk wird in einem interpretierten Bytecode-Format kompiliert und kann genau wie Java dynamisch in nativen Code übersetzt werden. Aber Java verbessert das Design, indem es einen Bytecode-Verifizierer einsetzt, um die Korrektheit des kompilierten Java-Codes sicherzustellen. Dieser Verifier verschafft Java einen Leistungsvorteil gegenüber Smalltalk, weil Java-Code weniger Laufzeitprüfungen benötigt. Der Bytecode-Verifizierer von Java hilft auch bei Sicherheitsfragen, etwas, das Smalltalk nicht anspricht.

Im weiteren Verlauf dieses Kapitels werden wir die Sprache Java aus der Vogelperspektive betrachten. Wir erklären, was an Java neu ist und was nicht so neu ist und warum.

Sicherheit der Konstruktion

Du hast sicherlich schon viel darüber gehört, dass Java eine sichere Sprache sein soll. Aber was verstehen wir unter sicher? Sicher vor was oder wem? Die Sicherheitsfunktionen von Java, die die meiste Aufmerksamkeit auf sich ziehen, sind diejenigen, die neue Arten von dynamisch portabler Software ermöglichen. Java bietet mehrere Ebenen des Schutzes vor gefährlich fehlerhaftem Code und auch vor bösartigeren Dingen wie Viren und Trojanern. Im nächsten Abschnitt sehen wir uns an, wie die Architektur der virtuellen Maschine von Java die Sicherheit des Codes bewertet, bevor er ausgeführt wird, und wie der Java Class Loader (der Bytecode-Lademechanismus des Java-Interpreters) eine Mauer um nicht vertrauenswürdige Klassen errichtet. Diese Funktionen bilden die Grundlage für hochrangige Sicherheitsrichtlinien, die verschiedene Aktivitäten für jede einzelne Anwendung erlauben oder verbieten können.

In diesem Abschnitt werden wir uns jedoch einige allgemeine Funktionen der Programmiersprache Java ansehen. Vielleicht noch wichtiger als die spezifischen Sicherheitsfunktionen, auch wenn sie oft übersehen werden, ist die Sicherheit, die Java bietet, indem es gängige Design- und Programmierprobleme angeht. Java soll so sicher wie möglich vor den einfachen Fehlern sein, die wir Programmierer/innen selbst machen, und auch vor denen, die wir von älterer Software übernehmen. Das Ziel von Java war es, die Sprache einfach zu halten, Werkzeuge bereitzustellen, die sich als nützlich erwiesen haben, und es den Benutzern zu ermöglichen, bei Bedarf kompliziertere Funktionen auf die Sprache aufzusetzen.

Vereinfachen, Vereinfachen, Vereinfachen...

In Java regiert die Einfachheit . Da Java von Grund auf neu entwickelt wurde, wurden Funktionen vermieden, die sich in anderen Sprachen als chaotisch oder umstritten erwiesen haben. Java erlaubt zum Beispiel keine programmgesteuerte Überladung von Operatoren (die es Programmierern in anderen Sprachen ermöglicht, die Bedeutung von grundlegenden Symbolen wie + und - neu zu definieren). Java hat keinen Quellcode-Präprozessor und verfügt daher nicht über Makros, #define Anweisungen oder bedingte Quellcodekompilierung. Diese Konstrukte gibt es in anderen Sprachen in erster Linie, um Plattformabhängigkeiten zu unterstützen, so dass sie in Java nicht benötigt werden. Die bedingte Kompilierung wird auch häufig zum Debuggen verwendet, aber die ausgefeilten Laufzeitoptimierungen von Java und Funktionen wie Assertions lösen das Problem eleganter.4

Java bietet eine gut definierte Paketstruktur für die Organisation von Klassendateien. Das Paketsystem ermöglicht es dem Compiler, einen Teil der Funktionen des traditionellen make-Dienstprogramms (ein Werkzeug zum Erstellen ausführbarer Dateien aus dem Quellcode) zu übernehmen. Der Compiler kann auch direkt mit kompilierten Java-Klassen arbeiten, da alle Typinformationen erhalten bleiben; es sind keine überflüssigen "Header"-Dateien wie in C/C++ erforderlich. All dies bedeutet, dass Java-Code weniger Kontext zum Lesen benötigt. Manchmal ist es sogar schneller, einen Blick in den Java-Quellcode zu werfen, als in die Klassendokumentation zu schauen.

Java verfolgt auch einen anderen Ansatz bei einigen strukturellen Merkmalen, die in anderen Sprachen problematisch waren. Java unterstützt zum Beispiel nur eine einzige Vererbungshierarchie (jede Klasse kann nur eine "Elternklasse" haben), erlaubt aber die Mehrfachvererbung von Schnittstellen. Eine Schnittstelle legt, wie eine abstrakte Klasse in C++, das Verhalten eines Objekts fest, ohne seine Implementierung zu definieren. Sie ist ein sehr leistungsfähiger Mechanismus, der es dem Entwickler ermöglicht, einen "Vertrag" für das Verhalten eines Objekts zu definieren, der unabhängig von einer bestimmten Objektimplementierung verwendet und herangezogen werden kann. Schnittstellen in Java machen die Mehrfachvererbung von Klassen und die damit verbundenen Probleme überflüssig.

Wie du in Kapitel 4 sehen wirst, ist Java eine ziemlich einfache und elegante Programmiersprache, und das macht immer noch einen großen Teil ihres Reizes aus.

Typ Sicherheit und Methode Bindung

Ein Merkmal einer Sprache ist die Art der Typüberprüfung, die sie verwendet. Im Allgemeinen werden Sprachen als statisch oder dynamisch kategorisiert, was sich auf die Menge an Informationen über Variablen bezieht, die zur Kompilierungszeit bekannt sind, im Gegensatz zu den Informationen, die bekannt sind, während die Anwendung läuft.

In einer streng statisch typisierten Sprache wie C oder C++ sind die Datentypen in Stein gemeißelt, wenn der Quellcode kompiliert wird. Der Compiler profitiert davon, weil er genug Informationen hat, um viele Fehler zu erkennen, bevor der Code ausgeführt wird. Der Compiler würde zum Beispiel nicht zulassen, dass du einen Fließkommawert in einer Ganzzahlvariablen speicherst. Der Code muss dann nicht zur Laufzeit typgeprüft werden und kann so kompiliert werden, dass er klein und schnell ist. Aber statisch typisierte Sprachen sind unflexibel. Sie unterstützen Sammlungen nicht so natürlich wie Sprachen mit dynamischer Typprüfung und machen es einer Anwendung unmöglich, neue Datentypen sicher zu importieren, während sie läuft.

Im Gegensatz dazu verfügt eine dynamische Sprache wie Smalltalk oder Lisp über ein Laufzeitsystem, das die Objekttypen verwaltet und die notwendigen Typprüfungen durchführt, während eine Anwendung ausgeführt wird. Diese Arten von Sprachen ermöglichen ein komplexeres Verhalten und sind in vielerlei Hinsicht leistungsfähiger. Allerdings sind sie im Allgemeinen auch langsamer, weniger sicher und schwieriger zu debuggen.

Die Unterschiede zwischen den Sprachen wurden mit den Unterschieden zwischen den verschiedenen Autotypen verglichen.5 Statisch typisierte Sprachen wie C++ sind mit einem Sportwagen vergleichbar: ziemlich sicher und schnell, aber nur nützlich, wenn du auf einer gut asphaltierten Straße fährst. Hochdynamische Sprachen wie Smalltalk sind eher wie ein Geländewagen: Sie bieten dir mehr Freiheit, können aber auch etwas unhandlich sein. Es kann Spaß machen (und manchmal auch schneller sein), durch die Hinterwälder zu brausen, aber du könntest auch im Graben stecken bleiben oder von Bären zerfleischt werden.

Eine weitere Eigenschaft einer Sprache ist die Art und Weise, wie sie Methodenaufrufe an ihre Definitionen bindet. In einer statischen Sprache wie C oder C++ werden die Definitionen von Methoden normalerweise zur Kompilierzeit gebunden, es sei denn, der Programmierer legt etwas anderes fest. Sprachen wie Smalltalk hingegen werden als Late Binding bezeichnet, weil sie die Definitionen der Methoden dynamisch zur Laufzeit festlegen. Die frühe Bindung ist aus Leistungsgründen wichtig; sie ermöglicht es, dass eine Anwendung ohne den Overhead läuft, der durch die Suche nach Methoden zur Laufzeit entsteht. Die späte Bindung ist jedoch flexibler. Sie ist auch in einer objektorientierten Sprache notwendig, in der neue Typen dynamisch geladen werden können und nur das Laufzeitsystem bestimmen kann, welche Methode ausgeführt werden soll.

Java bietet einige der Vorteile von C++ und Smalltalk: Es ist eine statisch typisierte, spätbindende Sprache. Jedes Objekt in Java hat einen wohldefinierten Typ, der zur Kompilierzeit bekannt ist. Das bedeutet, dass der Java-Compiler die gleiche Art von statischer Typüberprüfung und Verwendungsanalyse durchführen kann wie C++. Das bedeutet, dass du ein Objekt nicht dem falschen Variablentyp zuweisen oder nicht existierende Methoden für ein Objekt aufrufen kannst. Der Java-Compiler geht sogar noch weiter und verhindert, dass du uninitialisierte Variablen verwendest und unerreichbare Anweisungen erstellst (siehe Kapitel 4).

Java ist aber auch vollständig laufzeittypisiert. Das Java-Laufzeitsystem behält den Überblick über alle Objekte und ermöglicht es, ihre Typen und Beziehungen während der Ausführung zu bestimmen. Das bedeutet, dass du ein Objekt zur Laufzeit untersuchen kannst, um festzustellen, was es ist. Im Gegensatz zu C oder C++ prüft das Java-Laufzeitsystem die Umwandlung von einem Objekttyp in einen anderen, und es ist möglich, neue Arten von dynamisch geladenen Objekten mit einem gewissen Maß an Typsicherheit zu verwenden. Und weil Java Late Binding verwendet, ist es möglich, Code zu schreiben, der einige Methodendefinitionen zur Laufzeit ersetzt.

Inkrementelle Entwicklung

Java trägt alle Informationen über Datentypen und Methodensignaturen vom Quellcode bis zum kompilierten Bytecode mit sich. Das bedeutet, dass Java-Klassen inkrementell entwickelt werden können. Dein eigener Java-Quellcode kann auch sicher mit Klassen aus anderen Quellen kompiliert werden, die dein Compiler noch nie gesehen hat. Mit anderen Worten: Du kannst neuen Code schreiben, der auf binäre Klassendateien verweist, ohne die Typsicherheit zu verlieren, die du durch den Quellcode erhältst.

Java leidet nicht unter dem Problem der "fragilen Basisklasse". In Sprachen wie C++ kann die Implementierung einer Basisklasse praktisch eingefroren werden, weil sie viele abgeleitete Klassen hat; eine Änderung der Basisklasse kann die Neukompilierung aller abgeleiteten Klassen erfordern. Dies ist ein besonders schwieriges Problem für Entwickler von Klassenbibliotheken. Java umgeht dieses Problem, indem es Felder innerhalb von Klassen dynamisch platziert. Solange eine Klasse eine gültige Form ihrer ursprünglichen Struktur beibehält, kann sie sich weiterentwickeln, ohne dass andere Klassen, die von ihr abgeleitet sind oder sie verwenden, kaputt gehen.

Dynamische Speicherverwaltung

Einige der wichtigsten Unterschiede zwischen Java und niedrigeren Sprachen (wie C oder C++) betreffen die Art und Weise, wie Java den Speicher verwaltet. Java beseitigt Ad-hoc-Verweise auf beliebige Speicherbereiche(Zeiger, in anderen Sprachen) und fügt der Sprache einige Datenstrukturen auf hoher Ebene hinzu. Außerdem bereinigt Java ungenutzte Objekte (ein Prozess, der als Speicherbereinigung bekannt ist) effizient und automatisch. Diese Funktionen beseitigen viele sonst unüberwindbare Probleme mit Sicherheit, Portabilität und Optimierung.

Allein die Speicherbereinigung hat unzählige Programmierer vor der größten Fehlerquelle in C oder C++ bewahrt: der expliziten Speicherzuweisung und -freigabe. Das Java-Laufzeitsystem verwaltet nicht nur Objekte im Speicher, sondern behält auch alle Referenzen auf diese Objekte im Auge. Wenn ein Objekt nicht mehr verwendet wird, entfernt Java es automatisch aus dem Speicher. Du kannst Objekte, die du nicht mehr verwendest, in den meisten Fällen einfach ignorieren und darauf vertrauen, dass der Interpreter sie zu einem geeigneten Zeitpunkt wieder löscht.

Java verwendet eine ausgeklügelte Speicherbereinigung, die im Hintergrund läuft. Das bedeutet, dass die meisten Speicherbereinigungen während der Leerlaufzeiten stattfinden: zwischen E/A-Pausen, Mausklicks oder Tastatureingaben. Einige Laufzeitsysteme wie HotSpot verfügen über eine fortschrittlichere Speicherbereinigung, die das Nutzungsverhalten von Objekten (z. B. kurzlebig versus langlebig) unterscheiden und deren Sammlung optimieren kann. Die Java-Laufzeitumgebung kann sich nun automatisch auf die optimale Verteilung des Arbeitsspeichers für verschiedene Arten von Anwendungen einstellen, je nach deren Verhalten. Mit dieser Art von Laufzeitprofilen kann die automatische Speicherverwaltung viel schneller sein als die vom Programmierer sorgfältig verwalteten Ressourcen, was einige Programmierer der alten Schule immer noch kaum glauben können.

Wir haben gesagt, dass Java keine Zeiger hat. Streng genommen ist diese Aussage wahr, aber sie ist auch irreführend. Was Java bietet, sind Referenzen - einesicherere Art von Zeigern. Eine Referenz ist ein stark typisierter Handle für ein Objekt. Auf alle Objekte in Java, mit Ausnahme der primitiven numerischen Typen, wird über Referenzen zugegriffen. Mit Referenzen kannst du alle normalen Datenstrukturen aufbauen, die ein C-Programmierer mit Zeigern erstellt, z. B. verknüpfte Listen, Bäume und so weiter. Der einzige Unterschied ist, dass du bei Referenzen auf eine typsichere Weise vorgehen musst.

Referenzen in Java können nicht auf dieselbe Weise geändert werden wie Zeiger in Sprachen wie C. Eine Referenz ist ein atomares Ding; du kannst den Wert einer Referenz nicht verändern, außer indem du ihn einem Objekt zuweist. Referenzen werden wertmäßig weitergegeben, und du kannst ein Objekt nur über eine einzige Ebene der Umleitung referenzieren. Der Schutz von Referenzen ist einer der wichtigsten Aspekte der Java-Sicherheit. Das bedeutet, dass sich Java-Code an die Regeln halten muss; er kann nicht in Bereiche hineinschauen, in die er nicht sollte, um diese Regeln zu umgehen.

Schließlich sollten wir noch erwähnen, dass Arrays (im Wesentlichen indizierte Listen) in Java echte Objekte erster Klasse sind. Sie können wie andere Objekte dynamisch zugewiesen und zugeordnet werden. Arrays kennen ihre eigene Größe und ihren eigenen Typ. Obwohl du Array-Klassen nicht direkt definieren oder unterordnen kannst, haben sie eine klar definierte Vererbungsbeziehung, die auf der Beziehung zwischen ihren Basistypen basiert. Echte Arrays in der Sprache machen die Zeigerarithmetik, wie sie in C oder C++ verwendet wird, weitgehend überflüssig.

Fehlerbehandlung

Die Wurzeln von Java liegen in vernetzten Geräten und eingebetteten Systemen. Für diese Anwendungen ist es wichtig, ein robustes und intelligentes Fehlermanagement zu haben. Java verfügt über einen leistungsfähigen Mechanismus für die Behandlung von Ausnahmen, ähnlich wie in neueren Implementierungen von C++. Ausnahmen bieten eine natürlichere und elegantere Möglichkeit, mit Fehlern umzugehen. Mit Ausnahmen kannst du den Code für die Fehlerbehandlung vom normalen Code trennen, was die Anwendungen sauberer und besser lesbar macht.

Wenn eine Ausnahme auftritt, führt sie dazu, dass der Fluss der Programmausführung in einen vorher festgelegten "Catch"-Block übergeht. Die Ausnahme enthält ein Objekt, das Informationen über die Situation enthält, die das Problem verursacht hat. Der Java-Compiler verlangt, dass eine Methode entweder die Ausnahmen deklariert, die sie erzeugen kann, oder sie selbst abfängt und behandelt. Damit erhalten die Fehlerinformationen den gleichen Stellenwert wie Argumente und Rückgabetypen für Methoden. Als Java-Programmierer weißt du genau, mit welchen Ausnahmen du umgehen musst, und der Compiler hilft dir dabei, korrekte Software zu schreiben, die sie nicht unbehandelt lässt.

Themen

Moderne Anwendungen erfordern ein hohes Maß an Parallelität. Selbst eine sehr zielstrebige Anwendung kann eine komplexe Benutzeroberfläche haben, die gleichzeitige Aktivitäten erfordert. Je schneller die Rechner werden, desto weniger Geduld haben die Benutzer mit unzusammenhängenden Aufgaben, die ihre Zeit in Anspruch nehmen. Threads ermöglichen effizientes Multiprocessing und die Verteilung von Aufgaben sowohl für Client- als auch für Serveranwendungen. Java macht die Verwendung von Threads einfach, weil die Unterstützung dafür in die Sprache integriert ist.

Gleichzeitigkeit ist schön, aber es gehört mehr dazu, mit Threads zu programmieren, als nur mehrere Aufgaben gleichzeitig auszuführen. In den meisten Fällen müssen Threads synchronisiert (koordiniert) werden, was ohne explizite Sprachunterstützung schwierig sein kann. Java unterstützt die Synchronisierung auf der Grundlage des Monitor-Modells - eine Art Schloss- und Schlüsselsystem für den Zugriff auf Ressourcen. Das Schlüsselwort synchronized kennzeichnet Methoden und Codeblöcke für den sicheren, serialisierten Zugriff innerhalb eines Objekts. Es gibt auch einfache, primitive Methoden für explizites Warten und Signalisierung zwischen Threads, die sich für dasselbe Objekt interessieren.

Java verfügt über ein High-Level-Concurrency-Paket, das leistungsstarke Hilfsprogramme für gängige Muster in der Multithread-Programmierung bietet, wie z. B. Thread-Pools, Koordination von Aufgaben und ausgeklügelte Sperren. Mit dem Concurrency Package und den dazugehörigen Hilfsprogrammen bietet Java einige der fortschrittlichsten Thread-bezogenen Hilfsprogramme aller Sprachen. Und wenn du viele, viele Threads brauchst, kannst du in die Welt der virtuellen Threads von Project Loom eintauchen, die in Java 19 als Vorschaufunktion eingeführt werden.

Auch wenn manche Entwickler/innen vielleicht nie Multithread-Code schreiben müssen, ist das Erlernen der Programmierung mit Threads ein wichtiger Bestandteil der Programmierung in Java und etwas, das alle Entwickler/innen beherrschen sollten. In Kapitel 9 wird dieses Thema behandelt. In "Virtuelle Threads" werden virtuelle Threads vorgestellt und einige ihrer Leistungsvorteile hervorgehoben.

Skalierbarkeit

Wie wir bereits erwähnt haben, bestehen Java-Programme hauptsächlich aus Klassen. Über den Klassen bietet Java Pakete, eine Strukturebene, die Klassen in funktionale Einheiten gruppiert. Pakete bieten eine Namenskonvention für die Organisation von Klassen und eine zweite Ebene der organisatorischen Kontrolle über die Sichtbarkeit von Variablen und Methoden in Java-Anwendungen.

Innerhalb eines Pakets ist eine Klasse entweder öffentlich sichtbar oder vor dem Zugriff von außen geschützt. Pakete bilden eine weitere Art von Geltungsbereich, der näher an der Anwendungsebene liegt. Sie eignen sich für den Aufbau wiederverwendbarer Komponenten, die in einem System zusammenarbeiten. Pakete helfen auch dabei, eine skalierbare Anwendung zu entwickeln, die wachsen kann, ohne zu einem Vogelnest aus eng gekoppeltem Code zu werden. Der Aspekt der Wiederverwendung und der Skalierbarkeit wird erst durch das in Java 9 eingeführte Modulsystem wirklich verstärkt.6

Sicherheit der Umsetzung

Es ist eine Sache, eine Sprache zu entwickeln, die dich davor bewahrt, dir selbst in den Fuß zu schießen; eine ganz andere ist es, eine Sprache zu entwickeln, die andere davor bewahrt, dir in den Fuß zu schießen .

Kapselung ist das Konzept, bei dem Daten und Verhalten innerhalb einer Klasse versteckt werden; es ist ein wichtiger Teil des objektorientierten Designs. Sie hilft dir, saubere, modulare Software zu schreiben. In den meisten Sprachen ist die Sichtbarkeit von Datenelementen jedoch einfach Teil der Beziehung zwischen dem Programmierer und dem Compiler. Es ist eine Frage der Semantik und keine Aussage über die tatsächliche Sicherheit der Daten im Kontext der Programmumgebung.

Als Bjarne Stroustrup, der Schöpfer von C++, das Schlüsselwort private wählte, um versteckte Mitglieder von Klassen in C++ zu kennzeichnen, dachte er wahrscheinlich daran, einen Entwickler vor den schmutzigen Details des Codes eines anderen Entwicklers zu schützen, und nicht daran, die Klassen und Objekte dieses Entwicklers vor Angriffen durch Viren und Trojaner eines anderen zu schützen. Willkürliches Casting und Zeigerarithmetik in C oder C++ machen es trivial, Zugriffsrechte auf Klassen zu verletzen, ohne gegen die Regeln der Sprache zu verstoßen. Betrachte den folgenden Code:

// C++ code
class Finances {
    private:
        char creditCardNumber[16];
        // ...
};

main() {
    Finances finances;

    // Forge a pointer to peek inside the class
    char *cardno = (char *)&finances;
    printf("Card Number = %.16s\n", cardno);
}

In diesem kleinen C++-Drama haben wir einen Code geschrieben, der die Kapselung der Klasse Finances verletzt und einige geheime Informationen ausspäht. Diese Art von Betrug - der Missbrauch eines nicht typisierten Zeigers - ist in Java nicht möglich. Wenn dir dieses Beispiel unrealistisch erscheint, bedenke, wie wichtig es ist, die Basisklassen (Systemklassen) der Laufzeitumgebung vor ähnlichen Angriffen zu schützen. Wenn nicht vertrauenswürdiger Code die Komponenten beschädigen kann, die den Zugriff auf echte Ressourcen wie das Dateisystem, das Netzwerk oder das Fenstersystem ermöglichen, hat er sicherlich auch eine Chance, deine Kreditkartennummern zu stehlen.

Java ist mit dem Internet aufgewachsen - und mit all den nicht vertrauenswürdigen Quellen, die es dort gibt. Früher brauchte es mehr Sicherheit als heute, aber es hat einige Sicherheitsfunktionen beibehalten: Ein Class Loader kümmert sich um das Laden von Klassen aus der lokalen Speicherung oder dem Netzwerk, und darunter liegt die gesamte Systemsicherheit letztlich beim Java Verifier, der die Integrität der eingehenden Klassen garantiert.

Der Java bytecode verifier ist ein spezielles Modul und ein fester Bestandteil des Java-Laufzeitsystems. Klassenlader hingegen sind Komponenten, die von verschiedenen Anwendungen, wie Servern oder Webbrowsern, unterschiedlich implementiert werden können. Um die Sicherheit in der Java-Umgebung zu gewährleisten, müssen alle diese Komponenten richtig funktionieren.

Der Überprüfer

Die erste Verteidigungslinie von Java ist der Bytecode-Verifizierer. Der Verifier liest den Bytecode, bevor er ausgeführt wird, und stellt sicher, dass er brav ist und die Grundregeln der Java-Bytecode-Spezifikation befolgt. Ein vertrauenswürdiger Java-Compiler wird keinen Code erzeugen, der etwas anderes tut. Es ist jedoch möglich, dass eine böswillige Person absichtlich schlechten Java-Bytecode zusammenstellt. Es ist die Aufgabe des Überprüfers, dies zu erkennen.

Sobald der Code verifiziert wurde, gilt er als sicher vor bestimmten unbeabsichtigten oder bösartigen Fehlern. Verifizierter Code kann zum Beispiel keine Referenzen fälschen oder Zugriffsrechte auf Objekte verletzen (wie in unserem Kreditkartenbeispiel). Er kann keine illegalen Casts durchführen oder Objekte auf ungewollte Weise verwenden. Er kann nicht einmal bestimmte Arten von internen Fehlern verursachen, wie z. B. das Überlaufen oder Unterlaufen des internen Stacks. Diese grundlegenden Garantien sind die Basis für die gesamte Sicherheit von Java.

Du fragst dich vielleicht, ob diese Art von Sicherheit nicht in vielen interpretierten Sprachen implizit vorhanden ist? Nun, es stimmt zwar, dass es nicht möglich sein sollte, einen BASIC-Interpreter mit einer gefälschten BASIC-Codezeile zu beschädigen, aber denk daran, dass der Schutz in den meisten interpretierten Sprachen auf einer höheren Ebene stattfindet. Diese Sprachen haben in der Regel schwergewichtige Interpreter, die einen großen Teil der Laufzeitarbeit erledigen und daher zwangsläufig langsamer und umständlicher sind.

Im Vergleich dazu ist der Java-Bytecode ein relativ einfacher Befehlssatz auf niedriger Ebene. Durch die Möglichkeit, den Java-Bytecode vor der Ausführung statisch zu überprüfen, kann der Java-Interpreter später mit voller Geschwindigkeit und Sicherheit laufen, ohne teure Laufzeitprüfungen. Dies war eine der grundlegenden Innovationen in Java.

Der Verifier ist eine Art mathematischer "Theorembeweiser". Er geht durch den Java-Bytecode und wendet einfache, induktive Regeln an, um bestimmte Aspekte des Verhaltens des Bytecodes zu bestimmen. Diese Art der Analyse ist möglich, weil kompilierter Java-Bytecode viel mehr Typinformationen enthält als der Objektcode anderer Sprachen dieser Art. Der Bytecode muss außerdem ein paar zusätzliche Regeln befolgen, die sein Verhalten vereinfachen. Erstens operieren die meisten Bytecode-Befehle nur auf einzelnen Datentypen. Bei Stapeloperationen gibt es zum Beispiel separate Anweisungen für Objektreferenzen und für jeden der numerischen Typen in Java. Auch für das Verschieben von Werten in und aus einer lokalen Variablen gibt es für jeden Typ eine eigene Anweisung.

Zweitens ist der Typ des Objekts, das aus einer Operation resultiert, immer im Voraus bekannt. Keine Bytecode-Operation verbraucht Werte und erzeugt mehr als eine mögliche Art von Wert als Ausgabe. Daher ist es immer möglich, sich die nächste Anweisung und ihre Operanden anzusehen und den Typ des Wertes zu kennen, der sich daraus ergibt.

Da eine Operation immer einen bekannten Typ erzeugt, ist es möglich, die Typen aller Elemente auf dem Stack und in den lokalen Variablen zu einem beliebigen Zeitpunkt in der Zukunft zu bestimmen, indem man sich den Ausgangszustand ansieht. Die Sammlung all dieser Typinformationen zu einem bestimmten Zeitpunkt wird als Typzustand des Stacks bezeichnet. Das ist es, was Java zu analysieren versucht, bevor es eine Anwendung ausführt. Java weiß zu diesem Zeitpunkt nichts über die tatsächlichen Werte der Stack- und Variablenelemente; es weiß nur, um welche Art von Elementen es sich handelt. Diese Informationen reichen jedoch aus, um die Sicherheitsregeln durchzusetzen und sicherzustellen, dass Objekte nicht unrechtmäßig manipuliert werden.

Damit es möglich ist, den Typenzustand des Stacks zu analysieren, gibt es in Java eine zusätzliche Einschränkung für die Ausführung der Bytecode-Anweisungen: Alle Pfade zum gleichen Punkt im Code müssen mit genau dem gleichen Typenzustand ankommen.

Klasse Lader

Java fügt mit dem Klassenlader eine zweite Sicherheitsebene hinzu. Ein Klassenlader ist dafür verantwortlich, den Bytecode für Java-Klassen in den Interpreter zu bringen. Jede Anwendung, die Klassen aus dem Netzwerk lädt, muss einen Class Loader verwenden, um diese Aufgabe zu erledigen.

Nachdem eine Klasse geladen wurde und den Verifier durchlaufen hat, bleibt sie mit ihrem Class Loader verbunden. Daher sind die Klassen je nach ihrer Herkunft in verschiedene Namensräume unterteilt. Wenn eine geladene Klasse einen anderen Klassennamen referenziert, wird der Ort der neuen Klasse vom ursprünglichen Class Loader angegeben. Das bedeutet, dass Klassen, die von einer bestimmten Quelle geladen werden, nur mit anderen Klassen interagieren können, die von der gleichen Quelle geladen werden. Ein Java-fähiger Webbrowser kann z. B. einen Klassenlader verwenden, um einen separaten Bereich für alle Klassen zu erstellen, die von einer bestimmten URL geladen werden. Ausgefeilte Sicherheitsmechanismen, die auf kryptografisch signierten Klassen basieren, können ebenfalls mit Klassenladern implementiert werden.

Die Suche nach Klassen beginnt immer mit den eingebauten Java-Systemklassen. Diese Klassen werden von den Orten geladen, die im Klassenpfad des Java-Interpreters angegeben sind (siehe Kapitel 3). Die Klassen im Klassenpfad werden vom System nur einmal geladen und können nicht ersetzt werden. Das bedeutet, dass es für eine Anwendung unmöglich ist, grundlegende Systemklassen durch eigene Versionen zu ersetzen, die deren Funktionalität verändern.

Sicherheit auf Anwendungs- und Benutzerebene

Es gibt einen schmalen Grat zwischen genügend Macht, um etwas Nützliches zu tun, und der Macht, alles zu tun, was du willst. Java bietet die Grundlage für eine sichere Umgebung, in der nicht vertrauenswürdiger Code unter Quarantäne gestellt, verwaltet und sicher ausgeführt werden kann. Wenn du dich jedoch nicht damit zufrieden gibst, diesen Code in einer kleinen Blackbox zu halten und ihn nur zu seinem eigenen Vorteil auszuführen, musst du ihm zumindest Zugriff auf einige Systemressourcen gewähren, damit er nützlich sein kann. Jede Art von Zugriff bringt bestimmte Risiken und Vorteile mit sich. In der Umgebung von Cloud-Diensten hat es zum Beispiel den Vorteil, dass ein nicht vertrauenswürdiger (unbekannter) Code Zugriff auf das Dateisystem des Cloud-Servers erhält, weil er große Dateien schneller finden und verarbeiten kann, als du sie herunterladen und lokal verarbeiten könntest. Die damit verbundenen Risiken bestehen darin, dass der Code stattdessen auf dem Cloud-Server herumschleichen und möglicherweise sensible Informationen entdecken kann, die er nicht sehen sollte.

Auf der einen Seite wird einer Anwendung durch die bloße Ausführung eine Ressource zugewiesen -Rechenzeit -, die sie entweder sinnvoll nutzen oder leichtfertig verbrauchen kann. Es ist schwierig, eine nicht vertrauenswürdige Anwendung daran zu hindern, deine Zeit zu verschwenden oder gar einen "Denial of Service"-Angriff zu starten. Auf der anderen Seite kann eine leistungsstarke, vertrauenswürdige Anwendung berechtigten Anspruch auf den Zugriff auf alle möglichen Systemressourcen haben (z. B. das Dateisystem, die Erstellung von Prozessen oder Netzwerkschnittstellen); eine böswillige Anwendung könnte mit diesen Ressourcen Chaos anrichten. Die Botschaft hier ist, dass du wichtige und manchmal komplexe Sicherheitsfragen in deinen Programmen berücksichtigen musst.

In manchen Situationen kann es akzeptabel sein, den Benutzer einfach zu bitten, Anfragen zu genehmigen. Die Sprache Java bietet die Werkzeuge, um alle gewünschten Sicherheitsrichtlinien zu implementieren. Für welche Richtlinien du dich entscheidest, hängt letztlich davon ab, ob du der Identität und Integrität des betreffenden Codes vertraust oder nicht. An dieser Stelle kommen digitale Signaturen ins Spiel.

Digitale Signaturen sind zusammen mit Zertifikaten Techniken, mit denen überprüft werden kann, ob die Daten wirklich von der Quelle stammen, die sie vorgibt zu sein, und nicht unterwegs verändert wurden. Wenn die Bank of Boofa ihre Scheckbuchanwendung signiert, kannst du überprüfen, ob die Anwendung tatsächlich von der Bank stammt und nicht von einem Betrüger und ob sie nicht verändert wurde. Deshalb kannst du deinem System sagen, dass es dem Code mit der Signatur der Bank of Boofa vertrauen soll .

Eine Java-Roadmap

Bei den ständigen Aktualisierungen von Java ist es schwer, den Überblick darüber zu behalten, welche Funktionen jetzt verfügbar sind, was versprochen wurde und was es schon seit einiger Zeit gibt. Die folgenden Abschnitte stellen eine Roadmap dar, die eine gewisse Ordnung in die Vergangenheit, Gegenwart und Zukunft von Java bringt. In den Release Notes von Oracle findest du gute Zusammenfassungen der Java-Versionen mit Links zu weiteren Details. Wenn du mit älteren Versionen arbeitest, solltest du dir die Dokumente zu den Technologieressourcen von Oracle durchlesen.

Die Vergangenheit: Java 1.0-Java 20

Java 1.0 bot das Grundgerüst für die Java-Entwicklung: die Sprache selbst sowie Pakete, mit denen du Applets und einfache Anwendungen schreiben kannst. Obwohl 1.0 offiziell veraltet ist, gibt es immer noch einige Applets, die mit seiner API kompatibel sind.

Java 1.1 löste 1.0 ab und enthielt wichtige Verbesserungen im Abstract Window Toolkit (AWT) Paket (Javas ursprüngliche GUI-Funktionalität), ein neues Event Pattern, neue Sprachfunktionen wie Reflection und innere Klassen und viele andere wichtige Funktionen. Java 1.1 ist die Version, die viele Jahre lang von den meisten Versionen von Netscape und Microsoft Internet Explorer unterstützt wurde. Aus verschiedenen politischen Gründen war die Browserwelt lange Zeit in diesem Zustand eingefroren.

Java 1.2, von Sun "Java 2" genannt, war eine wichtige Veröffentlichung im Dezember 1998. Sie bot viele Verbesserungen und Ergänzungen, vor allem in Bezug auf die APIs, die in den Standarddistributionen enthalten waren. Die bemerkenswertesten Neuerungen waren die Aufnahme des Swing GUI-Pakets als Kern-API und eine neue, vollwertige 2D-Zeichen-API. Swing ist das fortschrittliche UI-Toolkit von Java, dessen Fähigkeiten die des alten AWT weit übertreffen. (Swing, AWT und einige andere Pakete werden auch als JFC oder Java Foundation Classes bezeichnet). Java 1.2 fügte auch eine richtige Sammlungs-API zu Java hinzu.

Java 1.3, das Anfang 2000 veröffentlicht wurde, fügte kleinere Funktionen hinzu, konzentrierte sich aber hauptsächlich auf die Leistung. Mit der Version 1.3 wurde Java auf vielen Plattformen deutlich schneller, und Swing erhielt viele Fehlerkorrekturen. In dieser Zeit reiften auch die Java Enterprise APIs wie Servlets und Enterprise JavaBeans.

Java 1.4, das 2002 veröffentlicht wurde, enthielt eine Reihe von neuen APIs und viele lang erwartete Funktionen. Dazu gehörten Sprachassertions, reguläre Ausdrücke, Einstellungsund Protokollierungs-APIs, ein neues E/A-System für hochvolumige Anwendungen, Standardunterstützung für XML, grundlegende Verbesserungen in AWT und Swing und eine stark ausgereifte Java Servlets API für Webanwendungen.

Java 5, das 2004 veröffentlicht wurde, war eine wichtige Version, die viele lang erwartete Verbesserungen der Sprachsyntax einführte, darunter Generics, typsichere Aufzählungen, die verbesserte for-Schleife, Variablenargumentlisten, statische Importe, Autoboxing und Unboxing von Primitiven sowie erweiterte Metadaten für Klassen. Eine neue Gleichzeitigkeits-API bietet leistungsstarke Threading-Fähigkeiten, und es wurden APIs für formatiertes Drucken und Parsing ähnlich denen in C hinzugefügt. Remote Method Invocation (RMI) wurde ebenfalls überarbeitet, um die Notwendigkeit von kompilierten Stubs und Skeletons zu beseitigen. Auch bei den Standard-XML-APIs gab es wichtige Ergänzungen.

Java 6, das Ende 2006 veröffentlicht wurde, war eine relativ unbedeutende Version, die der Java-Sprache keine neuen syntaktischen Funktionen hinzufügte, aber neue Erweiterungs-APIs wie die für XML und Webservices mitbrachte.

Java 7, das 2011 veröffentlicht wurde, war ein ziemlich großes Update. Fünf Jahre nach der Veröffentlichung von Java 6 wurden einige kleine Verbesserungen an der Sprache vorgenommen, wie z. B. die Möglichkeit, Strings in switch Anweisungen zu verwenden (mehr zu diesen beiden Dingen später!), sowie wichtige Ergänzungen wie die neue I/O-Bibliothek java.nio.

Java 8, das 2014 veröffentlicht wurde, vervollständigte einige Funktionen wie Lambdas und Standardmethoden, die in Java 7 gestrichen worden waren, da das Veröffentlichungsdatum dieser Version immer wieder verschoben wurde. In dieser Version wurde auch an der Unterstützung von Datum und Uhrzeit gearbeitet, einschließlich der Möglichkeit, unveränderliche Datumsobjekte zu erstellen, was für die Verwendung in den nun unterstützten Lambdas praktisch ist.

Mit Java 9, das nach einigen Verzögerungen im Jahr 2017 veröffentlicht wurde, wurden das Modulsystem (Projekt Jigsaw) und eine Read-Evaluate-Print-Schleife (REPL) für Java eingeführt: jshell. Wir werden jshell im weiteren Verlauf dieses Buches für unsere schnellen Erkundungen vieler Java-Funktionen verwenden. Mit Java 9 wurde auch JavaDB aus dem JDK entfernt.

Java 10, das kurz nach Java 9 Anfang 2018 veröffentlicht wurde, aktualisierte die Speicherbereinigung und brachte andere Funktionen wie Root-Zertifikate in die OpenJDK-Builds. Die Unterstützung für unveränderbare Sammlungen wurde hinzugefügt und die Unterstützung für alte Look-and-Feel-Pakete (wie Apples Aqua) wurde entfernt.

Java 11, das Ende 2018 veröffentlicht wurde, fügte einen Standard-HTTP-Client und Transport Layer Security (TLS) 1.3 hinzu. Die Module JavaFX und Java EE wurden entfernt. (JavaFX wurde umgestaltet, um als eigenständige Bibliothek weiterzuleben.) Java-Applets wurden ebenfalls entfernt. Zusammen mit Java 8 ist Java 11 Teil von Oracles Langzeitunterstützung (LTS). Bestimmte Versionen - Java 8, Java 11, Java 17 und Java 21 - werden über einen längeren Zeitraum hinweg gepflegt. Oracle versucht, die Art und Weise zu ändern, wie Kunden und Entwickler mit neuen Versionen umgehen, aber es gibt immer noch gute Gründe, bei bekannten Versionen zu bleiben. Du kannst mehr über Oracles Gedanken und Pläne für LTS- und Nicht-LTS-Versionen in der Oracle Java SE Support Roadmap des Oracle Technology Network lesen.

Java 12, das Anfang 2019 veröffentlicht wurde, enthält kleinere Verbesserungen der Sprachsyntax, wie z.B. eine Vorschau für Switch-Ausdrücke.

Java 13, das im September 2019 veröffentlicht wird, enthält eine Vorschau auf weitere Sprachfeatures, wie z. B. Textblöcke, sowie eine umfassende Neuimplementierung der Sockets-API. Laut den offiziellen Design-Dokumenten bietet diese beeindruckende Leistung "eine einfachere und modernere Implementierung, die leicht zu warten und zu debuggen ist".

Java 14, das im März 2020 veröffentlicht wurde, fügte weitere Vorschauen für Sprachsyntaxverbesserungen wie Datensätze hinzu, aktualisierte die Speicherbereinigung und entfernte die Pack200-Tools und die API. Außerdem wurde der Switch-Ausdruck, der erstmals in Java 12 vorgestellt wurde, aus dem Vorschaustatus in die Standardsprache übernommen.

Mit Java 15, das im September 2020 veröffentlicht wurde, wurde die Unterstützung für Textblöcke (mehrzeilige Strings) aus der Vorschau herausgenommen und es wurden sowohl versteckte als auch versiegelte Klassen hinzugefügt, die neue Möglichkeiten bieten, den Zugriff auf bestimmten Code zu beschränken. (Versiegelte Klassen wurden als Vorschaufunktion beibehalten.) Die Unterstützung für Textkodierung wurde außerdem auf Unicode 13.0 aktualisiert.

Java 16, das im März 2021 veröffentlicht wurde, behielt versiegelte Klassen in der Vorschau, verschob aber Datensätze aus der Vorschau. Die Netzwerk-APIs wurden um Unix Domain Sockets erweitert. Außerdem wurde die Streams-API um eine Option zur Listenausgabe erweitert.

Java 17, das im September 2021 mit LTS veröffentlicht wurde, hat versiegelte Klassen zu einem regulären Feature der Sprache gemacht. Eine Vorschau auf den Musterabgleich für switch Anweisungen wurde zusammen mit mehreren Verbesserungen auf macOS hinzugefügt. Datagram Sockets können jetzt verwendet werden, um Multicast-Gruppen beizutreten.

Mit Java 18, das im März 2022 veröffentlicht wurde, wurde UTF-8 endlich zum Standardzeichensatz für Java SE APIs. Es führte einen einfachen, statischen Webserver ein, der sich für Prototypen oder Tests eignet, und erweiterte die Optionen für die Auflösung von IP-Adressen.

Java 19, das im September 2022 veröffentlicht wurde, enthielt eine Vorschau auf virtuelle Threads, strukturierte Gleichzeitigkeit und Datensatzmuster. Die Unicode-Unterstützung wurde auf Version 14.0 erweitert und einige zusätzliche Datums- und Zeitformate wurden hinzugefügt.

Java 20, das im März 2023 veröffentlicht wurde, entfernte schließlich mehrere Threading-Operationen (Stop/Pause/Resume), die mehr als 20 Jahre zuvor im JDK 1.2 als unsicher eingestuft worden waren. Das Parsen von Zeichenketten wurde verbessert und unterstützt nun auch Grapheme, wie z. B. zusammengesetzte Emoji-Symbole.

Die Gegenwart: Java 21

Dieses Buch enthält alle neuesten und besten Verbesserungen bis zur Veröffentlichung von Java 21 im September 2023. Da es einen sechsmonatigen Veröffentlichungsrhythmus gibt, werden neuere Versionen des JDK mit ziemlicher Sicherheit schon verfügbar sein, wenn du dieses Buch liest. Wie bereits erwähnt, möchte Oracle, dass Entwickler/innen diese Versionen als Feature-Updates betrachten. Mit Ausnahme der Beispiele, die sich mit virtuellen Threads befassen, ist Java 17 für die Arbeit mit dem Code in diesem Buch ausreichend. In den seltenen Fällen, in denen wir ein neueres Feature verwenden, geben wir die erforderliche Mindestversion an. Du musst beim Lesen nicht "auf dem Laufenden bleiben", aber wenn du Java für veröffentlichte Projekte verwendest, solltest du dir die offizielle Roadmap von Oracle ansehen, um zu sehen, ob es sinnvoll ist, auf dem neuesten Stand zu bleiben.

Feature-Übersicht

Hier ist ein kurzer Überblick über die wichtigsten Funktionen der aktuellen Core Java API, die außerhalb der Standardbibliothek liegen:

Java Database Connectivity (JDBC)

Eine allgemeine Möglichkeit, mit Datenbanken zu interagieren (eingeführt in Java 1.1).

Remote Method Invocation (RMI)

Das verteilte Objektsystem von Java. Mit RMI kannst du Methoden für Objekte aufrufen, die auf einem Server irgendwo im Netzwerk laufen (eingeführt in Java 1.1).

Java Sicherheit

Eine Funktion zur Kontrolle des Zugriffs auf Systemressourcen, kombiniert mit einer einheitlichen Schnittstelle zur Kryptografie. Java Security ist die Grundlage für signierte Klassen.

Java Desktop

Ein Sammelbegriff für eine große Anzahl von Funktionen ab Java 9, darunter die Swing UI-Komponenten, das "pluggable look and feel", mit dem du die gesamte UI selbst anpassen und thematisieren kannst, Drag and Drop, 2D-Grafiken, Drucken, Bild- und Soundanzeige, -wiedergabe und -manipulation sowie Zugänglichkeitsfunktionen, die sich mit spezieller Software und Hardware für Menschen mit Seh- oder anderen Beeinträchtigungen integrieren lassen.

Internationalisierung

Die Fähigkeit, Programme zu schreiben, die sich an die Sprache und das Gebietsschema anpassen, die der Benutzer verwenden möchte. Das Programm zeigt den Text automatisch in der entsprechenden Sprache an (eingeführt in Java 1.1).

Java Naming and Directory Interface (JNDI)

Ein allgemeiner Dienst zum Nachschlagen von Ressourcen. JNDI vereinheitlicht den Zugang zu Verzeichnisdiensten wie LDAP, Novell's NDS und anderen.

Bei den folgenden APIs handelt es sich um "Standard-Erweiterungen". Einige, wie z. B. die für die Arbeit mit XML und Webservices, sind in der Standardausgabe von Java enthalten; andere müssen separat heruntergeladen und mit deiner Anwendung oder deinem Server bereitgestellt werden:

JavaMail

Eine einheitliche API zum Schreiben von E-Mail-Software.

Java Media Framework

Ein weiterer Sammelbegriff für die Koordinierung der Darstellung vieler verschiedener Medien. Dazu gehören Java 2D, Java 3D, Java Speech (für Spracherkennung und -synthese), Java Sound (qualitativ hochwertiges Audio), Java TV (für interaktives Fernsehen und ähnliche Anwendungen) und andere.

Java Servlets

Eine Funktion, mit der du serverseitige Webanwendungen in Java schreiben kannst.

Java Kryptographie

Aktuelle Implementierungen von kryptografischen Algorithmen. (Dieses Paket wurde aus rechtlichen Gründen von Java Security getrennt).

eXtensible Markup Language/eXtensible Stylesheet Language (XML/XSL)

Werkzeuge zur Erstellung und Bearbeitung von XML-Dokumenten, zur Validierung, zum Mapping auf und von Java-Objekten und zur Umwandlung mit Stylesheets.

Wir werden versuchen, auf einige dieser Funktionen einzugehen. Leider (aber zum Glück für Java-Softwareentwickler) ist die Java-Umgebung so umfangreich geworden, dass es unmöglich ist, alles in einem einzigen Buch zu behandeln. Wir werden auf andere Bücher und Ressourcen hinweisen, die Themen abdecken, die wir nicht ausführlich behandeln können.

Die Zukunft

Java ist heutzutage sicherlich nicht mehr das neue Kind im Stall, aber es ist nach wie vor eine der beliebtesten Plattformen für die Web- und Anwendungsentwicklung. Das gilt besonders für die Bereiche Webservices, Webanwendungs-Frameworks und XML-Tools. Auch wenn Java die mobilen Plattformen nicht so dominiert hat, wie es eigentlich vorgesehen war, kannst du die Java-Sprache und die Kern-APIs nutzen, um für Googles mobiles Betriebssystem Android zu programmieren, das auf Milliarden von Geräten auf der ganzen Welt eingesetzt wird. Im Microsoft-Lager hat die von Java abgeleitete Sprache C# einen Großteil der .NET-Entwicklung übernommen und die Java-Syntax und -Muster auf diese Plattformen gebracht.

Auch die JVM selbst ist ein interessanter Bereich der Erforschung und des Wachstums. Neue Sprachen tauchen auf, um die Vorteile des Funktionsumfangs und der Allgegenwärtigkeit der JVM zu nutzen. Clojure ist eine robuste funktionale Sprache mit einer wachsenden Fangemeinde, die von Hobbyanwendern bis hin zu den großen Kaufhäusern reicht. Und Kotlin ist eine Allzwecksprache, die mit Begeisterung die Android-Entwicklung erobert. Sie gewinnt in neuen Umgebungen an Zugkraft und ist gleichzeitig gut mit Java interoperabel.

Die aufregendsten Veränderungen in Java finden sich heute in den Trends zu leichteren, einfacheren Frameworks für Unternehmen und zur Integration der Java-Plattform mit dynamischen Sprachen für das Skripting von Webseiten und Erweiterungen. Es gibt noch viel mehr interessante Arbeit, die auf uns zukommt.

Du hast mehrere Möglichkeiten für Java-Entwicklungsumgebungen und -Laufzeitsysteme. Das Java Development Kit von Oracle ist für macOS, Windows und Linux erhältlich. Auf der Java-Website von Oracle findest du weitere Informationen über das neueste offizielle JDK.

Seit 2017 unterstützt Oracle offiziell Updates für das Open Source OpenJDK. Für Einzelpersonen und kleine (oder sogar mittlere) Unternehmen ist diese kostenlose Version möglicherweise ausreichend. Die Versionen hinken dem kommerziellen JDK hinterher und bieten keinen technischen Support von Oracle, aber Oracle hat sich fest vorgenommen, den freien und offenen Zugang zu Java aufrechtzuerhalten. Alle Beispiele in diesem Buch wurden mit dem OpenJDK geschrieben und getestet. Auf der OpenJDK-Website erfährst du mehr Details direkt aus erster Hand (von Oracle?).

Für die schnelle Installation einer kostenlosen Version von Java 19 (die für fast alle Beispiele in diesem Buch ausreicht, auch wenn wir einige Sprachfeatures aus späteren Versionen erwähnen), bietet Amazon seine Corretto-Distribution online mit freundlichen, vertrauten Installationsprogrammen für alle drei großen Plattformen an. Kapitel 2 führt dich durch die grundlegende Corretto-Installation auf Windows, macOS und Linux.

Es gibt auch eine Reihe beliebter integrierter Entwicklungsumgebungen (IDEs) für Java. Eine davon stellen wir in diesem Buch vor: die kostenlose Community Edition von IntelliJ IDEA von JetBrains. Mit dieser All-in-One-Entwicklungsumgebung kannst du Software schreiben, testen und verpacken und hast dabei fortschrittliche Werkzeuge zur Hand.

Übungen

Am Ende jedes Kapitels findest du ein paar Fragen und Codeübungen, die du überprüfen kannst. Die Antworten auf die Fragen findest du in Anhang B. Die Lösungen zu den Code-Übungen findest du zusammen mit den anderen Code-Beispielen auf GitHub.(In Anhang A findest du Informationen zum Herunterladen und Verwenden des Codes für dieses Buch). Wir ermutigen dich, die Fragen zu beantworten und die Übungen auszuprobieren. Mach dir keine Sorgen, wenn du in ein Kapitel zurückgehen und etwas mehr lesen musst, um eine Antwort zu finden oder einen Methodennamen nachzuschlagen. Das ist der Sinn der Sache! Wenn du lernst, dieses Buch als Nachschlagewerk zu benutzen, wird es dir später sehr nützlich sein.

  1. Welches Unternehmen unterhält derzeit Java?

  2. Wie lautet der Name des Open-Source-Entwicklungskits für Java?

  3. Nenne die zwei Hauptkomponenten, die bei Javas Ansatz zur sicheren Ausführung von Bytecode eine Rolle spielen.

1 Der Name Standard Edition (SE) tauchte schon früh in der Geschichte von Java auf, als Sun die J2EE-Plattform, die Java 2 Enterprise Edition, veröffentlichte. Die Enterprise Edition trägt jetzt den Namen "Jakarta EE".

2 Wenn du neugierig auf Node.js bist, schau dir Andrew Meads Learning Node.js Development und Shelley Powers' Learning Node auf der O'Reilly-Seite an.

3 Siehe z. B. G. Phipps, "Comparing Observed Bug and Productivity Rates for Java and C++", Software-Practice & Experience, Band 29, 1999.

4 Assertions gehen über den Rahmen dieses Buches hinaus, aber sie sind ein lohnenswertes Thema, mit dem du dich befassen kannst, wenn du in Java besser Fuß gefasst hast. Einige grundlegende Details findest du in der Oracle Java SE Dokumentation.

5 Die Analogie mit dem Auto stammt von Marshall P. Cline, dem Autor von C++ FAQ.

6 Module gehen über den Rahmen dieses Buches hinaus, aber sie sind der einzige Schwerpunkt von Java 9 Modularity von Paul Bakker und Sander Mak (O'Reilly).

Get Java lernen, 6. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.