Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 1 Java ist auch eine Sprache
Pfeil 1.1 Historischer Hintergrund
Pfeil 1.2 Warum Java populär ist – die zentralen Eigenschaften
Pfeil 1.2.1 Bytecode
Pfeil 1.2.2 Ausführung des Bytecodes durch eine virtuelle Maschine
Pfeil 1.2.3 Plattformunabhängigkeit
Pfeil 1.2.4 Java als Sprache, Laufzeitumgebung und Standardbibliothek
Pfeil 1.2.5 Objektorientierung in Java
Pfeil 1.2.6 Java ist verbreitet und bekannt
Pfeil 1.2.7 Java ist schnell – Optimierung und Just-in-time-Compilation
Pfeil 1.2.8 Das Java-Security-Modell
Pfeil 1.2.9 Zeiger und Referenzen
Pfeil 1.2.10 Bring den Müll raus, Garbage-Collector!
Pfeil 1.2.11 Ausnahmebehandlung
Pfeil 1.2.12 Das Angebot an Bibliotheken und Werkzeugen
Pfeil 1.2.13 Vergleichbar einfache Syntax
Pfeil 1.2.14 Java ist Open Source
Pfeil 1.2.15 Wofür sich Java weniger eignet
Pfeil 1.3 Java im Vergleich zu anderen Sprachen *
Pfeil 1.3.1 Java und C(++)
Pfeil 1.3.2 Java und JavaScript
Pfeil 1.3.3 Ein Wort zu Microsoft, Java und zu J++
Pfeil 1.3.4 Java und C#/.NET
Pfeil 1.4 Weiterentwicklung und Verluste
Pfeil 1.4.1 Die Entwicklung von Java und seine Zukunftsaussichten
Pfeil 1.4.2 Features, Enhancements (Erweiterungen) und ein JSR
Pfeil 1.4.3 Applets
Pfeil 1.4.4 JavaFX
Pfeil 1.5 Java-Plattformen: Java SE, Jakarta EE, Java ME, Java Card
Pfeil 1.5.1 Die Java SE-Plattform
Pfeil 1.5.2 Java ME: Java für die Kleinen
Pfeil 1.5.3 Java für die ganz, ganz Kleinen
Pfeil 1.5.4 Java für die Großen: Jakarta EE (ehemals Java EE)
Pfeil 1.5.5 Echtzeit-Java (Real-time Java)
Pfeil 1.6 Java SE-Implementierungen
Pfeil 1.6.1 OpenJDK
Pfeil 1.6.2 Oracle JDK
Pfeil 1.7 AdoptOpenJDK installieren
Pfeil 1.7.1 AdoptOpenJDK unter Windows installieren
Pfeil 1.8 Das erste Programm compilieren und testen
Pfeil 1.8.1 Ein Quadratzahlen-Programm
Pfeil 1.8.2 Der Compilerlauf
Pfeil 1.8.3 Die Laufzeitumgebung
Pfeil 1.8.4 Häufige Compiler- und Interpreter-Probleme
Pfeil 1.9 Entwicklungsumgebungen
Pfeil 1.9.1 Eclipse IDE
Pfeil 1.9.2 IntelliJ IDEA
Pfeil 1.9.3 NetBeans
Pfeil 1.10 Zum Weiterlesen
 

Zum Seitenanfang

1.2    Warum Java populär ist – die zentralen Eigenschaften Zur vorigen ÜberschriftZur nächsten Überschrift

Java ist eine objektorientierte Programmiersprache, die sich durch einige zentrale Eigenschaften auszeichnet. Diese machen sie universell einsetzbar und für die Industrie als robuste Programmiersprache interessant. Da Java objektorientiertes Programmieren ermöglicht, können Entwickler moderne und wiederverwertbare Softwarekomponenten entwickeln.

Zum Teil wirkt Java sehr konservativ, aber das liegt daran, dass die Sprachdesigner nicht sofort alles einbauen, was im Moment gerade hip ist (XML-Literale sind so ein Beispiel). Java nahm schon immer das in den Sprachkern auf, was sich in anderen Programmiersprachen als sinnvoll und gut herausgestellt hatte. Sun und später Oracle vermieden es aber, Dinge in die Sprache zu integrieren, die nur von sehr wenigen Entwicklern eingesetzt werden oder die öfter zu Fehlern führen. In den Anfängen stand C++ als Vorbild da, heute schielt Java auf C# und Skriptsprachen.

Einige der zentralen Eigenschaften wollen wir uns im Folgenden anschauen und dabei auch zentrale Begriffe und Funktionsweisen beleuchten.

 

Zum Seitenanfang

1.2.1    Bytecode Zur vorigen ÜberschriftZur nächsten Überschrift

Zunächst ist Java eine Programmiersprache wie jede andere. Doch im Gegensatz zu herkömmlichen Übersetzern einer Programmiersprache, die in der Regel Maschinencode für einen bestimmten Prozessor (zum Beispiel für x86er-Mikroprozessoren oder Prozessoren der ARM-Architektur) und eine spezielle Plattform (etwa Linux oder Windows) generieren, erzeugt der Java-Compiler aus den Quellcode-Dateien den sogenannten Bytecode. Dieser Programmcode ist binär und Ausgangspunkt für die virtuelle Maschine zur Ausführung. Bytecode ist vergleichbar mit Mikroprozessorcode für einen erdachten Prozessor, der Anweisungen wie arithmetische Operationen, Sprünge und Weiteres kennt.

 

Zum Seitenanfang

1.2.2    Ausführung des Bytecodes durch eine virtuelle Maschine Zur vorigen ÜberschriftZur nächsten Überschrift

Damit der Programmcode des virtuellen Prozessors ausgeführt werden kann, kümmert sich nach der Phase, in der der Programmcode in Bytecode übersetzt wird, eine Laufzeitumgebung, die sogenannte Java Virtual Machine (JVM), um den Bytecode.[ 7 ](Die Idee des Bytecodes (das Satzprogramm FrameMaker schlägt hier als Korrektur »Bote Gottes« vor) ist schon alt. Die Firma Datapoint schuf um 1970 die Programmiersprache PL/B, die Programme auf Bytecode abbildet. Auch verwendet die Originalimplementierung von UCSD-Pascal, die etwa Anfang 1980 entstand, einen Zwischencode – kurz p-code. ) Die Laufzeitumgebung (auch Runtime-Interpreter genannt) lädt den Bytecode, prüft ihn und führt ihn in einer kontrollierten Umgebung aus. Die JVM bietet eine ganze Reihe von Zusatzdiensten, z. B. eine automatische Speicherbereinigung (Garbage-Collector), die Speicher aufräumt, sowie eine starke Typprüfung unter einem klar definierten Speicher- und Threading-Modell.

 

Zum Seitenanfang

1.2.3    Plattformunabhängigkeit Zur vorigen ÜberschriftZur nächsten Überschrift

Eine zentrale Eigenschaft von Java ist seine Plattformunabhängigkeit bzw. Betriebssystemunabhängigkeit. Diese wird durch zwei zentrale Konzepte erreicht: Zum einen bindet sich Java nicht an einen bestimmten Prozessor oder eine bestimmte Architektur, sondern der Compiler generiert Bytecode, den eine Laufzeitumgebung dann abarbeitet. Zum anderen abstrahiert Java von den Eigenschaften eines konkreten Betriebssystems, schafft etwa eine Schnittstelle zum Ein-/Ausgabesystem oder eine API für grafische Oberflächen. Entwickler programmieren immer gegen eine Java-API, aber nie gegen die API der konkreten Plattform, etwa die Windows- oder Unix-API. Die Java-Laufzeitumgebung bildet Aufrufe etwa auf Dateien für das jeweilige System ab, ist also Vermittler zwischen den Java-Programmen und der eigentlichen Betriebssystem-API.

Plattformunabhängige Programmiersprachen und Laufzeitumgebungen sind heute Standard und Java ist nicht mehr außergewöhnlich. Die Top-Sprachen heute sind JavaScript, Python, Ruby, Scala, PHP, Kotlin, C# und alle haben eine Laufzeitumgebung, wobei einige Sprachen auch in nativen Code übersetzt werden können. Plattformunabhängigkeit ist schwer, denn die Programmiersprache und ein Bytecode produzierender Compiler sind nur ein Teil – der größere Teil sind die Laufzeitumgebung und eine umfangreiche API. Zwar ist auch C an sich eine portable Sprache, und ANSI-C-Programme lassen sich von jedem C-Compiler auf jedem Betriebssystem mit Compiler übersetzen, aber das Problem sind die Bibliotheken, die über ein paar simple Dateioperationen nicht hinauskommen.

In Java 7 änderte sich die Richtung etwas, was sich besonders an der API für die Dateisystemunterstützung ablesen lässt. Vor Java 7 war die Datei-Klasse so aufgebaut, dass die Semantik gewisser Operationen nicht ganz genau spezifiziert und auf diese Weise sehr plattformabhängig war. Es gibt aber in der Datei-Klasse keine Operation, die nur auf einer Plattform zur Verfügung steht und andere Plattformen ausschließt. Das Credo lautete immer: Was nicht auf allen Plattformen existiert, kommt nicht in die Bibliothek.[ 8 ](Es gibt sie durchaus, die Methoden, die zum Beispiel nur unter Windows zur Verfügung stehen. Aber dann liegen sie nicht in einem java- oder javax-Paket, sondern in einem internen Paket. ) Mit Java 7 gab es einen Wechsel: Nun sind plattformspezifische Dateieigenschaften zugänglich. Es bleibt abzuwarten, ob in Zukunft in anderen API-Bereichen – vielleicht bei grafischen Oberflächen – noch weitere Beispiele hinzukommen.

 

Zum Seitenanfang

1.2.4    Java als Sprache, Laufzeitumgebung und Standardbibliothek Zur vorigen ÜberschriftZur nächsten Überschrift

Java ist nicht nur eine Programmiersprache, sondern ebenso ein Laufzeitsystem, was Oracle durch den Begriff Java Platform verdeutlichen will. So gibt es neben der Programmiersprache Java durchaus andere Sprachen, die eine Java-Laufzeitumgebung ausführen, etwa diverse Skriptsprachen wie Groovy (http://groovy-lang.org), JRuby (http://jruby.org), Jython (http://www.jython.org), Kotlin (http://kotlinlang.org) oder Scala (http://www.scala-lang.org). Skriptsprachen auf der Java-Plattform werden immer populärer; sie etablieren eine andere Syntax, nutzen aber die JVM und die Bibliotheken.

Zu der Programmiersprache und JVM kommt ein Satz von Standardbibliotheken für Datenstrukturen, Zeichenkettenverarbeitung, Datum/Zeit-Verarbeitung, grafische Oberflächen, Ein-/Ausgabe, Netzwerkoperationen und mehr. Das bildet die Basis für höherwertige Dienste wie Datenbankanbindungen oder Webservices. Integraler Bestandteil der Standardbibliothek seit Java 1.0 sind weiterhin Threads. Sie sind leicht zu erzeugende Ausführungsstränge, die unabhängig voneinander arbeiten können. Mittlerweile unterstützen alle populären Betriebssysteme diese »leichtgewichtigen Prozesse« von Haus aus, sodass die JVM diese parallelen Programmteile nicht nachbilden muss, sondern auf das Betriebssystem verweisen kann. Auf den neuen Multi-Core-Prozessoren sorgt das Betriebssystem für eine optimale Ausnutzung der Rechenleistung, da Threads wirklich nebenläufig arbeiten können.

 

Zum Seitenanfang

1.2.5    Objektorientierung in Java Zur vorigen ÜberschriftZur nächsten Überschrift

Java ist als Sprache entworfen worden, die es einfach machen sollte, große, fehlerfreie Anwendungen zu schreiben. In C-Programmen erwartet uns statistisch gesehen alle 55 Programmzeilen ein Fehler. Selbst in großen Softwarepaketen (ab einer Million Codezeilen) findet sich, unabhängig von der zugrunde liegenden Programmiersprache, im Schnitt alle 200 Programmzeilen ein Fehler. Selbstverständlich gilt es, diese Fehler zu beheben, obwohl bis heute noch keine umfassende Strategie für die Softwareentwicklung im Großen gefunden wurde. Viele Arbeiten der Informatik beschäftigen sich mit der Frage, wie Tausende Programmierer über Jahrzehnte miteinander arbeiten und Software entwerfen können. Dieses Problem ist nicht einfach zu lösen und wurde im Zuge der Softwarekrise Mitte der 1960er-Jahre heftig diskutiert.

Eine Laufzeitumgebung eliminiert viele Probleme technischer Natur. Objektorientierte Programmierung versucht, die Komplexität des Softwareproblems besser zu modellieren. Die Philosophie ist, dass Menschen objektorientiert denken und eine Programmierumgebung diese menschliche Denkweise abbilden sollte. Genauso wie Objekte in der realen Welt verbunden sind und kommunizieren, so muss dies auch in der Softwarewelt möglich sein. Objekte bestehen aus Eigenschaften; das sind Dinge, die ein Objekt »hat« und »kann«. Ein Auto »hat« Räder und einen Sitz und »kann« beschleunigen und bremsen. Objekte entstehen aus Klassen, das sind Beschreibungen für den Aufbau von Objekten.

Die Sprache Java ist nicht bis zur letzten Konsequenz objektorientiert, so wie Smalltalk es vorbildlich demonstriert. Primitive Datentypen existieren für numerische Zahlen oder Unicode-Zeichen und werden nicht als Objekte verwaltet. Als Grund für dieses Design wird genannt, dass der Compiler und die Laufzeitumgebung mit der Trennung besser in der Lage waren, die Programme zu optimieren. Allerdings zeigt die virtuelle Maschine von Microsoft für die .NET-Plattform und andere moderne Programmiersprachen, dass auch ohne die Trennung eine gute Performance möglich ist.

 

Zum Seitenanfang

1.2.6    Java ist verbreitet und bekannt Zur vorigen ÜberschriftZur nächsten Überschrift

Unabhängig von der Leistungsfähigkeit einer Sprache zählen am Ende doch nur betriebswirtschaftliche Faktoren: Wie schnell und billig lässt sich ein vom Kunden gewünschtes System bauen, und wie stabil und änderungsfreundlich ist es? Dazu kommen Fragen wie: Wie sieht der Literaturmarkt aus, wie die Ausbildungswege, woher bekommt ein Team einen Entwickler oder Consultant, wenn es brennt? Dies sind nicht unbedingt Punkte, die Informatiker beim Sprachvergleich an die erste Stelle setzen, sie sind aber letztendlich für den Erfolg einer Softwareplattform entscheidend. Fast jede Universität lehrt Java, und mit Java ist ein Job sicher. Konferenzen stellen neue Trends vor und schaffen Trends. Diese Kette ist nicht zu durchbrechen, und selbst wenn heute eine neue Supersprache mit dem Namen »Bali« auftauchen würde, würde es Jahre dauern, bis ein vergleichbares System geschaffen wäre. Wohlgemerkt: Das sagt nichts über die Innovations- oder Leistungsfähigkeit aus, nur über die Marktsättigung, aber dadurch wird Java eben für so viele interessant.

Heute ist Java die Basis sehr erfolgreicher Produkte; viele von ihnen laufen auf der Serverseite. Dazu zählen Facebook, LinkedIn, Twitter, Amazon und eBay. Auf der Client-Seite ist Java seltener zu finden, das Spiel Minecraft ist eher eine Ausnahme.

[»]  Java-Entwickler sind glücklich

Andrew Vos hat sich Kommentare angeschaut, mit denen Entwickler ihre Programme in die Versionsverwaltung einpflegen.[ 9 ](http://tutego.de/go/profanity) Dabei zählte er, wie viele »böse« Wörter wie »shit«, »omg«, »wtf« beim Check-in vorkommen. Seine Herangehensweise ist zwar statistisch nicht ganz ordentlich, aber bei seinen untersuchten Projekten stehen Java-Entwickler recht gut da und haben wenig zu fluchen. Die Kommentare sind amüsant zu lesen und geben unterschiedliche Erklärungen, etwa dass JavaScript-Programmierer eigentlich nur über den IE fluchen, aber nicht über die Sprache JavaScript an sich, und dass Python-Programmierer zum Fluchen zu anständig sind.

 

Zum Seitenanfang

1.2.7    Java ist schnell – Optimierung und Just-in-time-Compilation Zur vorigen ÜberschriftZur nächsten Überschrift

Die Laufzeitumgebung von Java 1.0 startete mit einer puren Interpretation des Bytecodes. Das bereitete massive Geschwindigkeitsprobleme, denn beim Interpretieren muss die Arbeit eines Prozessors – das Erkennen, Dekodieren und Ausführen eines Befehls – noch einmal in Software wiederholt werden; das kostet viel Zeit. Java-Programme der ersten Stunde waren daher deutlich langsamer als übersetzte C(++)-Programme und brachten Java den Ruf ein, eine langsame Sprache zu sein.

Die Technik der Just-in-time-(JIT-)Compiler[ 10 ](Diese Idee ist auch schon alt: HP hatte um 1970 JIT-Compiler für BASIC-Maschinen. ) war der erste Schritt, das Problem anzugehen. Ein JIT-Compiler beschleunigt die Ausführung der Programme, indem er zur Laufzeit den Bytecode, also die Programmanweisungen der virtuellen Maschine, in Maschinencode der jeweiligen Plattform übersetzt. Eine Übersetzung zur Laufzeit hat enormes Optimierungspotenzial, da die JVM natürlich weiß, was für eine CPU eingesetzt ist, und für die CPU bestmöglichen Maschinencode erzeugen kann. Ein Beispiel ist die Befehlssatzerweiterung SSE2 (Streaming SIMD Extensions 2) für x86-Prozessoren: Findet die JVM diesen Prozessortyp vor, kann SSE2-Code erzeugt werden, andernfalls eben nicht.

Nach der Übersetzung steht ein an die Architektur angepasstes Programm im Speicher, das der physikalische Prozessor ohne Interpretation schnell ausführt. Mit dieser Technik entspricht die Geschwindigkeit der von anderen übersetzten Sprachen. Jedoch übersetzt ein guter JIT nicht alles, sondern versucht über diverse Heuristiken herauszufinden, ob sich eine Übersetzung – die ja selbst Zeit kostet – überhaupt lohnt. Die JVM beginnt daher immer mit einer Interpretation und wechselt dann in einen Compilermodus, wenn es nötig wird. Somit ist Java im Grunde eine compilierte, aber auch interpretierte Programmiersprache – von der Ausführung durch Hardware einmal abgesehen. Vermutlich ist der Java-Compiler in der JVM der am häufigsten laufende Compiler überhaupt. Und das Ergebnis ist sehr gut, was sich daran ablesen lässt, wie sich Java im Vergleich zu anderen Sprachen schlägt.[ 11 ](https://benchmarksgame-team.pages.debian.net/benchmarksgame/which-programs-are-fastest.html)

Der JIT-Compiler von Sun wurde immer besser und entwickelte sich weiter zu einer Familie von virtuellen Maschinen, die heute unter dem Namen HotSpot bekannt sind. Das Besondere ist, dass HotSpot die Ausführung zur Laufzeit überwacht und »heiße« (sprich: kritische) Stellen findet, etwa Schleifen mit vielen Wiederholungen – daher auch der Name »HotSpot«. Daraufhin steuert die JVM ganz gezielt Übersetzungen und Optimierungen. Zu den Optimierungen gehören Klassiker, wie das Zusammenfassen von Ausdrücken, aber auch viele dynamische Optimierungen fallen in diesen Bereich, zu denen ein statischer C++-Compiler nicht in der Lage wäre, weil ihm der Kontext fehlt.[ 12 ](Dynamische Methodenaufrufe sind in der Regel sehr schnell, weil die JVM die Typhierarchie kennt und sehr aggressiv optimieren kann. Sie kann aber Optimierungen auch wieder zurücknehmen, wenn sich die Typhierarchie etwa durch nachgeladene Klassen ändert. Auch weiß die JVM, welche Programmteile nebenläufig ausgeführt werden und gesichert werden müssen oder ob die Synchronisation entfallen kann. ) Zudem kann die JVM zu jeder Zeit Bytecode nachladen, der wie alle schon geladenen Teile genauso optimiert wird. Der neu eingeführte Programmcode kann sogar alte Optimierungen und Maschinencode ungültig machen, den dann die JVM neu übersetzt.

Traditioneller Compiler und Java-Compiler mit Laufzeitumgebung

Abbildung 1.2    Traditioneller Compiler und Java-Compiler mit Laufzeitumgebung

HotSpot steht genauso wie das Laufzeitsystem unter der freien GPL-Lizenz und ist für jeden einsehbar. Die JVM ist hauptsächlich in C++ programmiert, aber aus Performance-Gründen befinden sich dort auch Teile in Maschinencode, was die Portierung nicht ganz einfach macht. Das Zero-Assembler Project (http://openjdk.java.net/projects/zero) hat sich zum Ziel gesetzt, HotSpot ohne Maschinencode zu realisieren, sodass eine Portierung einfach ist. Die HotSpot-VM hat eine eigene Entwicklung und Versionsnummer.

Ganz neue Wege zeigen Lösungen wie GraalVM (http://www.graalvm.org/), die neben Java auch JavaScript, Ruby, R und Python mischen können.

 

Zum Seitenanfang

1.2.8    Das Java-Security-Modell Zur vorigen ÜberschriftZur nächsten Überschrift

Das Java-Security-Modell gewährleistet den sicheren Programmablauf auf den verschiedensten Ebenen. Der Verifier liest Code und überprüft die strukturelle Korrektheit und Typsicherheit. Weist der Bytecode schon Fehler auf, kommt der Programmcode erst gar nicht zur Ausführung. Die Prüfung ist wichtig, denn ein Klassenlader (engl. class loader) kann Klassendateien von überall her laden. Während vielleicht dem Bytecode aus dem lokalen Laufwerk vertraut werden kann, gilt das mitunter nicht für Code, der über ein ungesichertes Netzwerk übertragen wurde, wo ein Dritter plötzlich Schadcode einfügt (Man-in-the-Middle-Angriff). Ist der Bytecode korrekt in der virtuellen Maschine angemeldet, folgen weitere Prüfungen. So sind etwa (mit entsprechender Anpassung) keine Lese-/Schreibzugriffe auf private Variablen möglich. Treten Sicherheitsprobleme auf, werden sie durch Exceptions zur Laufzeit gemeldet – so kommt es etwa zu keinen Pufferüberläufen. Auf der Programmebene überwacht ein Security-Manager Zugriffe auf das Dateisystem, die Netzwerk-Ports, externe Prozesse und weitere Systemressourcen. Das Sicherheitsmodell kann vom Programmierer erweitert und über Konfigurationsdateien einfach konfiguriert werden.

Java kämpfte Anfang 2013 mit einigen bösen Sicherheitsproblemen. Applets, also Java-Programme, die in Webseiten eingebettet sind, konnten sich Zugriff zum System verschaffen, Anwendungen starten und so Blödsinn anstellen. Die Debatte kochte so hoch, dass auch Massenmedien[ 13 ](Ja, selbst in der BILD-Zeitung war es angekommen: http://www.bild.de/digital/internet/datenschutz/java-sicherheitsluecke-25899768.bild.html) die Meldung aufgriffen und zur Deinstallation von Java rieten. Zwei Dinge müssen jedoch berücksichtigt werden: Erstens betraf das Problem nur Applets, die fast vollständig aus dem Netz verschwunden sind. Auf der Serverseite, wo Java in der Regel zu Hause ist, gefährden diese Angriffe die Sicherheit nicht. Zweitens – allerdings ist das kein wirklicher Trost[ 14 ](Und die Frage ist auch, was peinlicher ist: von einer gut gemachten Attacke betroffen zu sein oder bei der Eingabe von »File:///« im Editor mit Rechtschreibkorrektur abzustürzen. ) – kann es durch Programmierfehler der Sandbox immer wieder zu Ausbrüchen aus der Umgebung kommen. Vergleichbare Meldungen vom Acrobat Reader, der eingebettete Skripte ausführt, sind nicht selten. Da ist es gut, dass Java eine so hohe Verbreitung hat, dass Fehler schnell gefunden und gefixt werden. Leider machte Oracle hier aber keine sehr gute Figur, und es dauerte eine paar Tage, bis der gefährliche Zero-Day-Exploit[ 15 ](Ein Fehler, der nach dem Entdecken sofort ausgenutzt wird, um Schadsoftware in Systeme einzuschleusen ) gefixt wurde. Selbst Facebook-Mitarbeiter waren von der Java-Sicherheitslücke betroffen.[ 16 ](http://www.facebook.com/notes/facebook-security/protecting-people-on-facebook/10151249208250766)

 

Zum Seitenanfang

1.2.9    Zeiger und Referenzen Zur vorigen ÜberschriftZur nächsten Überschrift

In Java gibt es keine Zeiger (engl. pointer) auf Speicherbereiche, wie sie aus anderen Programmiersprachen bekannt und gefürchtet sind. Da eine objektorientierte Programmiersprache ohne Verweise aber nicht funktioniert, führte Java Referenzen ein. Eine Referenz repräsentiert ein Objekt, und eine Variable speichert diese Referenz – sie wird Referenzvariable genannt. Während Programmierer nur mit Referenzen arbeiten, verbindet die JVM die Referenz mit einem Speicherbereich; der Zugriff, Dereferenzierung genannt, ist indirekt. Referenz und Speicherblock sind also getrennt. Das ist sehr flexibel, da Java das Objekt im Speicher bewegen kann.

Das im Speicher aufgebaute Objekt hat einen Typ, der sich nicht ändern kann. Wir sprechen hier vom Objekttyp: Ein Auto bleibt ein Auto und ist kein Laminiersystem. Eine Referenz unter Java kann verschiedene Typen annehmen; wir nennen das Referenztyp: Ein Java-Programm kann ein Auto auch als Fortbewegungsmittel ansehen.

[zB]  Beispiel *

Das folgende Programm zeigt, dass das Pfuschen in C++ leicht möglich ist und wir über eine Zeigerarithmetik Zugriff auf private Elemente bekommen können.[ 17 ](Auch ohne Compiler lässt sich das online prima testen: http://ideone.com. ) Für uns Java-Programmierer ist dies ein abschreckendes Beispiel:

#include <cstring>

#include <iostream>



using namespace std;



class VeryUnsafe {

public:

VeryUnsafe() { strcpy( password, "HaL9124f/aa" ); }

private:

char password[ 100 ];

};

int main() {

VeryUnsafe badguy;

char *pass = reinterpret_cast<char*>( & badguy );

cout << "Password: " << pass << endl;

}

Dieses Beispiel demonstriert, wie problematisch der Einsatz von Zeigern sein kann. Der zunächst als Referenz auf die Klasse VeryUnsafe gedachte Zeiger badguy mutiert durch die explizite Typumwandlung zu einem Char-Pointer pass. Problemlos können über diesen die Zeichen byteweise aus dem Speicher ausgelesen werden. Dies erlaubt auch einen indirekten Zugriff auf die privaten Daten.

In Java ist es nicht möglich, auf beliebige Teile des Speichers zuzugreifen. Auch sind private Variablen erst einmal sicher.[ 18 ](Ganz stimmt das allerdings nicht. Mit Reflection lässt sich da schon etwas machen, wenn die Sicherheitseinstellungen das nicht verhindern. ) Der Compiler bricht mit einer Fehlermeldung ab – bzw. das Laufzeitsystem löst eine Ausnahme (Exception) aus –, wenn das Programm einen Zugriff auf eine private Variable versucht.

 

Zum Seitenanfang

1.2.10    Bring den Müll raus, Garbage-Collector! Zur vorigen ÜberschriftZur nächsten Überschrift

In Programmiersprachen wie C(++) lässt sich etwa die Hälfte der Fehler auf falsche Speicherallokation zurückführen. Mit Objekten und Strukturen zu arbeiten, bedeutet unweigerlich, sie anzulegen und zu löschen. Die Java-Laufzeitumgebung kümmert sich jedoch selbstständig um die Verwaltung dieser Objekte – die Konsequenz: Sie müssen nicht freigegeben werden, die automatische Speicherfreigabe von Java (engl. garbage collector, kurz GC) entfernt sie. Nach dem expliziten Aufbauen eines Objekts überwacht das Laufzeitsystem von Java permanent, ob das Objekt noch gebraucht, also referenziert wird. Umgekehrt bedeutet das aber auch: Wenn auf einem Objekt vielleicht noch ein heimlicher Verweis liegt, kann der GC das Objekt nicht löschen. Diese sogenannten hängenden Referenzen sind ein Ärgernis und zum Teil nur durch längere Debugging-Sitzungen zu finden.

Der GC ist ein nebenläufiger Thread im Hintergrund, der nicht referenzierte Objekte findet, markiert und dann von Zeit zu Zeit entfernt. Damit macht der Garbage-Collector die Funktionen free(…) aus C oder delete(…) aus C++ überflüssig. Wir können uns über diese Technik freuen, da durch sie viele Probleme verschwunden sind. Nicht freigegebene Speicherbereiche gibt es in jedem größeren Programm, und falsche Destruktoren sind vielfach dafür verantwortlich. An dieser Stelle sollte nicht verschwiegen werden, dass es auch ähnliche Techniken für C(++) gibt.[ 19 ](Ein bekannter Garbage-Collector stammt von Hans-J. Boehm, Alan J. Demers und Mark Weiser. Der Algorithmus arbeitet jedoch konservativ. Das heißt, er findet nicht garantiert alle unerreichbaren Speicherbereiche, sondern nur einige. Eingesetzt wird der Boehm-Demers-Weiser-GC unter anderem in der X11-Bibliothek. Dort wurden die Funktionen malloc(…) und free(…) einfach durch neue Methoden ausgetauscht. )

Eine automatische Speicherbereinigung ist nicht ganz unproblematisch, da sie immer dann zuschlagen kann, wenn das System gerade etwas ganz Zeitkritisches machen möchte und Unterbrechungen nicht gelegen kommen. Doch die modernen Garbage-Collectors sind gut darin, wenig aktive Zyklen zu erkennen und die Arbeit gleichmäßig und auch auf mehrere Prozessorkerne zu verteilen. Doch automatische Speicherbereinigung ist nichts Neues, und die Verfahren sind lange erprobt. Schon frühere Programmiersprachen wie LISP (1958) und Smalltalk (1972) brachten einen Garbage-Collector mit, und alle modernen Programmiersprachen (bzw. deren Laufzeitumgebungen) besitzen eine automatische Speicherbereinigung.

 

Zum Seitenanfang

1.2.11    Ausnahmebehandlung Zur vorigen ÜberschriftZur nächsten Überschrift

Es gibt immer Dinge, die Entwickler nicht planen können. Mal bricht die Netzwerkverbindung zusammen, mal verschwindet eine Datei, mal ist der Teiler einer ganzzahligen Division 0. Java bietet Ausnahmen (engl. exceptions) an, um mit Laufzeitfehlern umzugehen. Ausnahmen sind Fehlerobjekte, die zur Laufzeit generiert werden, einen Fehler anzeigen und den allgemeinen Programmfluss unterbrechen. Diese Problemstellen können durch Programmkonstrukte gekapselt werden. Die Lösung ist in vielen Fällen sauberer als die mit Rückgabewerten und unleserlichen Ausdrücken im Programmfluss.

Aus Geschwindigkeitsgründen überprüft C(++)[ 20 ](In C++ ließe sich eine Variante mit einem überladenen Operator lösen. ) die Array-Grenzen (engl. range checking) standardmäßig nicht, was ein Grund für viele Sicherheitsprobleme ist. Ein fehlerhafter Zugriff auf das Element n + 1 eines Arrays der Größe n kann zweierlei bewirken: Ein Zugriffsfehler tritt auf, oder – viel schlimmer – andere Daten werden beim Schreibzugriff überschrieben, und der Fehler ist nicht mehr nachvollziehbar.

Das Laufzeitsystem von Java überprüft automatisch die Grenzen eines Arrays. Diese Überwachungen können auch nicht abgeschaltet werden, wie es Compiler anderer Programmiersprachen mitunter erlauben. Eine clevere Laufzeitumgebung findet heraus, ob keine Überschreitung möglich ist, und optimiert diese Abfrage dann weg; Array-Überprüfungen kosten daher nicht mehr die Welt und machen sich nicht automatisch in einer schlechteren Performance bemerkbar.

 

Zum Seitenanfang

1.2.12    Das Angebot an Bibliotheken und Werkzeugen Zur vorigen ÜberschriftZur nächsten Überschrift

Java ist mittlerweile schon so lange im Einsatz, dass es eine große Anzahl von Werkzeugen gibt, angefangen bei den Entwicklungsumgebungen mit unterstützenden Editoren über gute Debugger bis hin zu Werkzeugen zum Build-Management. Neben den Standardbibliotheken kommen weitere kommerzielle oder quelloffene Bibliotheken hinzu. Egal, ob es darum geht, PDF-Dokumente zu schreiben, Excel-Dokumente zu lesen, in SAP Daten zu übertragen oder bei einem Wincor-Bankautomaten den Geldauswurf zu steuern – für all das gibt es Java-Bibliotheken.

Neue Programmiersprachen haben es schwer, da mitzuhalten. Die Anzahl der Programmiersprachen ist in den letzten Jahren explodiert, doch so richtig können sie die breite Masse nicht anziehen, da ihnen eben Werkzeuge und Bibliotheken fehlen. Interessanter sind da schon die neuen Sprachen auf der Basis der JVM, denn das erlaubt weiterhin die Nutzung der Werkzeuge und Bibliotheken mit einer neuen Programmiersprache.

Werkzeuge und Bibliotheken sind es aber auch manchmal, die Teams vom Wechsel auf Java abhalten. Das trifft besonders die Spieleentwickler, die viel Erfahrung in C(++) haben und jahrelang Energie in Spiele-Frameworks gesteckt haben – dort sind die Tools einfach vorhanden, in Java (noch) nicht.

 

Zum Seitenanfang

1.2.13    Vergleichbar einfache Syntax Zur vorigen ÜberschriftZur nächsten Überschrift

Die Syntax von Java ist im Vergleich zu Sprachen wie C++ oder Perl eher einfach und strotzt nicht vor Operatoren oder Komplexität. Java erbte eine einfache und grundlegende Syntax wie die geschweiften Klammern von C, vermied es aber, die Syntax mit allen möglichen Dingen zu überladen. Da Programme häufiger gelesen als geschrieben werden, muss eine Syntax klar und durchgängig sein – je leichter Entwickler auf den ersten Blick sehen, was passiert, desto besser ist es.

»Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.« – John Woods

Es hat keinen Wert an sich, wenn Programme einfach nur kompakt sind, aber die Zeit, die ein Mensch zum Verstehen braucht, exponentiell steigt – je kompakter der Code, desto länger benötigt die menschliche Dekodierung. Auch wenn das folgende Perl-Beispiel aus »The Fifth Obfuscated Perl Contest Results«[ 21 ](https://de.wikipedia.org/wiki/Obfuscated_Perl_Contest) ganz bewusst als unleserliches Programm entworfen wurde, so wird jedem Betrachter schon beim Anblick schwummerig:

#:: ::-| ::-| .-. :||-:: 0-| .-| ::||-| .:|-. :||

open(Q,$0);while(<Q>){if(/^#(.*)$/){for(split('-',$1)){$q=0;for(split){s/\|

/:.:/xg;s/:/../g;$Q=$_?length:$_;$q+=$q?$Q:$Q*20;}print chr($q);}}}print"\n";

#.: ::||-| .||-| :|||-| ::||-| ||-:: :|||-| .:|

Eine einfachere Syntax lässt sich sowohl als Fluch (mehr Schreibarbeit) als auch als Segen (in der Regel leichter verständlich) auffassen. Java macht vieles richtig, aber es gibt ganz klar Stellen, an denen es auch hätte einfacher sein können (Stichwort Generics). Dennoch haben die Java-Entwickler gut daran getan, auf Konstrukte zu verzichten. Zwei aus C(++) ausgelassene Fähigkeiten werden exemplarisch in den beiden folgenden Abschnitten vorgestellt.

In Java gibt es keine benutzerdefinierten überladenen Operatoren

Wenn wir einen Operator wie das Pluszeichen verwenden und damit Ausdrücke addieren, tun wir dies meistens mit bekannten Rechengrößen wie Fließkommazahlen (Gleitkommazahlen) oder Ganzzahlen. Da das gleiche Operatorzeichen auf unterschiedlichen Datentypen gültig ist, nennt sich so ein Operator überladen. Operatoren wie +, -, *, / sind für Zahlen ebenso überladen wie die Operatoren Oder, Und oder XOR für Ganzzahlen und boolesche Werte. Die Vergleichsoperatoren == und != sind ebenfalls überladen, denn sie lassen sich bei allen Zahlen, aber auch bei Wahrheitswerten oder Objektverweisen verwenden. Ein auffälliger überladener Operator ist das Pluszeichen bei Zeichenketten. Strings können damit leicht zusammengesetzt werden. Programmierer nutzen in diesem Zusammenhang das Wort Konkatenation (selten Katenation). Bei den Strings "Hallo" + " " + "du da" ist "Hallo du da" die Konkatenation der Zeichenketten.

In Java ist es nicht möglich, vorhandene Operatoren mit neuer Bedeutung zu versehen. Andere Programmiersprachen erlauben dies, unter ihnen Python, C++, C# und selbst ALGOL von 1968. So kann zum Beispiel das Pluszeichen dafür genutzt werden kann, geometrische Punktobjekte zu addieren, Brüche zu teilen oder eine Zeile in eine Datei zu schreiben. Repräsentieren die Objekte mathematische Konstrukte, ist es ganz praktisch, wenn Operationen über kurze Operatorzeichen benannt werden – ein matrix1.add(matrix2) ist mit längerem Methodennamen sperriger als ein matrix1 + matrix2. Obwohl benutzerdefinierte überladene Operatoren zuweilen ganz praktisch sind, verführte die Möglichkeit oft zu unsinnigem Gebrauch. Daher haben die Sprachdesigner das für Java nicht vorgesehen, aber einige alternative Sprachen auf der JVM, z. B. Kotlin oder Scala, ermöglichen es, denn es ist eine Sprachbeschränkung und keine Beschränkung der virtuellen Maschine.

Kein Präprozessor für Textersetzungen *

Viele C(++)-Programme enthalten Präprozessor-Direktiven wie #define, #include oder #if zum Einbinden von Prototyp-Definitionen oder zur bedingten Compilierung. Einen solchen Präprozessor gibt es in Java nicht. Ohne Präprozessor ist auch die bedingte Compilierung mit #ifdef nicht möglich. Innerhalb von Anweisungsblöcken können wir uns in Java damit behelfen, Bedingungen der Art if (true) oder if (false) zu formulieren; über den Schalter -D auf der Kommandozeile lassen sich Variablen einführen, die dann eine if-Anweisung über System.getProperty(…) zur Laufzeit prüfen kann.[ 22 ](Da besonders bei mobilen Endgeräten Präprozessor-Anweisungen für unterschiedliche Geräte praktisch sind, gibt es Hersteller-Erweiterungen wie die von NetBeans (http://tutego.de/go/nbpreprocessor). )

Neue Entwicklungen

Auch Java geht mit der Zeit und verändert sich. Das führt dazu, dass die Syntax erweitert wird und Entwickler über die Jahre Neues lernen müssen. Bisher gibt es keine überladenen Operatoren, aber vielleicht gibt es sie in zehn Jahren? Und da die Anforderung an statische Typsicherheit steigt, genauso wie der Wunsch, im Bereich funktionaler Programmierung aufzuholen, ist einiges passiert. Java war schon immer ein bisschen »geschwätzig«: Die Symboldichte nahm ab Java 5 immer mehr zu und erreichte ihren (vorläufigen) Höhepunkt in Java 8. Bei der Weiterentwicklung versuchen die Sprachdesigner, die Programmiersprachen dennoch einfach zu halten, und sie haben bei der Syntax immer »den Java-Weg« im Blick, dass sich alle neuen Spracheigenschaften nahtlos einfügen.

 

Zum Seitenanfang

1.2.14    Java ist Open Source Zur vorigen ÜberschriftZur nächsten Überschrift

Schon seit Java 1.0 steht der Quellcode der Standardbibliotheken zur Verfügung (falls er beim JDK mitinstalliert wurde, befindet er sich im Wurzelverzeichnis unter dem Namen src.zip), und jeder Interessierte konnte einen Blick auf die Implementierung werfen. Zwar legte Sun damals die Implementierungen offen, doch weder die Laufzeitumgebung noch der Compiler oder die Bibliotheken standen unter einer akzeptierten Open-Source-Lizenz. Zehn Jahre seit der ersten Freigabe von Java gab es Forderungen an Sun, die gesamte Java-Plattform unter eine bekanntere Lizenzform wie die GNU General Public License (GPL) oder die BSD-Lizenz zu stellen. Dabei deutete Jonathan Schwartz in San Francisco bei der JavaOne-Konferenz 2006 schon an: »It’s not a question of whether we’ll open source Java, now the question is how.« War die Frage also statt des »Ob« ein »Wie«, kündigte Rich Green bei der Eröffnungsrede der JavaOne-Konferenz im Mai 2007 die endgültige Freigabe von Java als OpenJDK[ 23 ](http://openjdk.java.net) unter der Open-Source-Lizenz GPL 2 an. Dem war Ende 2006 die Freigabe des Compilers und der virtuellen Maschine vorausgegangen.

Ein paar Statistiken zeigt http://www.openhub.net/p/openjdk an:

  • rund 11 Millionen Zeilen Code insgesamt

  • > 57.000 Commits in die Versionsverwaltung insgesamt seit dem Bestehen vom OpenJDK

  • Rund 70 % vom OpenJDK ist Java-Code, ca. 20 % C und C++.

  • Dem COCOMO-Model (Constructive Cost Model) zufolge stecken über 3600 Jahre Entwicklungsarbeit in dem Code.

Im Prinzip kann mit dem OpenJDK jeder Entwickler sein eigenes Java zusammenstellen und beliebige Erweiterungen veröffentlichen. Mit der Lizenzform GPL kann Java auf Linux-Distributionen Platz finden, die Java vorher aus Lizenzgründen nicht integrieren wollten.

 

Zum Seitenanfang

1.2.15    Wofür sich Java weniger eignet Zur vorigen ÜberschriftZur nächsten Überschrift

Java wurde als Programmiersprache für allgemeine Probleme entworfen und deckt große Anwendungsgebiete ab (General-Purpose Language). Das heißt aber auch, dass es für ausreichend viele Anwendungsfälle deutlich bessere Programmiersprachen gibt, etwa im Bereich Skripting, wo die Eigenschaft, dass jedes Java-Programm mindestens eine Klasse und eine Methode benötigt, eher störend ist, oder im Bereich von automatisierter Textverarbeitung, wo andere Programmiersprachen eleganter mit regulären Ausdrücken arbeiten können.

Auch dann, wenn extrem maschinen- und plattformabhängige Anforderungen bestehen, wird es in Java umständlich. Java wurde plattformunabhängig entworfen, sodass alle Methoden auf allen Systemen lauffähig sein sollten. Sehr systemnahe Eigenschaften wie die Taktfrequenz sind nicht sichtbar, und sicherheitsproblematische Manipulationen wie der Zugriff auf bestimmte Speicherzellen (das PEEK und POKE) sind ebenso untersagt. Hier ist eine bei Weitem unvollständige Aufzählung von Dingen, die Java standardmäßig nicht kann:

  • Bildschirm auf der Textkonsole löschen, Cursor positionieren und Farben setzen

  • auf niedrige Netzwerkprotokolle wie ICMP zugreifen

  • Microsoft Office fernsteuern

  • Bilder einer Kamera einlesen

  • Zugriff auf USB[ 24 ](Eigentlich sollte es Unterstützung für den Universal Serial Bus geben, doch Sun hat hier – wie leider auch an anderer Stelle – das Projekt JSR-80: Java USB API nicht weiterverfolgt. ) oder Firewire

Aus den genannten Nachteilen, dass Java nicht auf die Hardware zugreifen kann, folgt, dass die Sprache nicht so ohne Weiteres für die Systemprogrammierung eingesetzt werden kann. Treibersoftware, die Grafik-, Sound- oder Netzwerkkarten anspricht, lässt sich in Java nur über Umwege realisieren. Genau das Gleiche gilt für den Zugriff auf die allgemeinen Funktionen des Betriebssystems, die Windows, Linux oder ein anderes System bereitstellt. Typische System-Programmiersprachen sind C(++) oder Go. Da außerdem die JVM eine gewisse Größe hat, ist Java für Microcontroller bisher keine Option.

Aus diesen Beschränkungen ergibt sich, dass Java eine hardwarenahe Sprache nicht ersetzen kann. Doch das muss die Sprache auch nicht! Jede Sprache hat ihr bevorzugtes Terrain, und Java ist eine allgemeine Applikationsprogrammiersprache; C(++) darf immer noch für Hardwaretreiber, eingebettete Systeme und virtuelle Maschinen herhalten. Die Standard-JVM ist (bisher noch) in C++ geschrieben und wird vom GCC-Compiler bzw. Microsoft Visual Studio und XCode übersetzt. C und C++ werden nie verschwinden; diese Sprachen sind wie Mikroben im Vulkangas – sie überdauern alles Leben. Und so wie am Vulkan die Mikroorganismen die Nahrungsgrundlage für andere Organismen bilden, so werden wir auch nicht ohne die systemnahen Sprachen C(++) oder Go auskommen.

Soll ein Java-Programm trotzdem systemnahe Eigenschaften nutzen – und das kann es mit entsprechenden Bibliotheken ohne Probleme –, bietet sich zum Beispiel der native Aufruf einer Systemfunktion an. Native Methoden sind Unterprogramme, die nicht in Java implementiert werden, sondern in einer anderen Programmiersprache, häufig in C(++). In manchen Fällen lässt sich auch ein externes Programm aufrufen und so etwa die Windows-Registry manipulieren oder Dateirechte setzen. Es läuft aber immer darauf hinaus, dass die Lösung für jede Plattform immer neu implementiert werden muss.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern