Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

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


Download:

- Beispielprogramme, ca. 35,4 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 11 Lambda-Ausdrücke und funktionale Programmierung
Pfeil 11.1 Code = Daten
Pfeil 11.2 Funktionale Schnittstellen und Lambda-Ausdrücke im Detail
Pfeil 11.2.1 Funktionale Schnittstellen
Pfeil 11.2.2 Typ eines Lambda-Ausdrucks ergibt sich durch Zieltyp
Pfeil 11.2.3 Annotation @FunctionalInterface
Pfeil 11.2.4 Syntax für Lambda-Ausdrücke
Pfeil 11.2.5 Die Umgebung der Lambda-Ausdrücke und Variablenzugriffe
Pfeil 11.2.6 Ausnahmen in Lambda-Ausdrücken
Pfeil 11.2.7 Klassen mit einer abstrakten Methode als funktionale Schnittstelle? *
Pfeil 11.3 Methodenreferenz
Pfeil 11.3.1 Varianten von Methodenreferenzen
Pfeil 11.4 Konstruktorreferenz
Pfeil 11.4.1 Standard- und parametrisierte Konstruktoren
Pfeil 11.4.2 Nützliche vordefinierte Schnittstellen für Konstruktorreferenzen
Pfeil 11.5 Implementierung von Lambda-Ausdrücken *
Pfeil 11.6 Funktionale Programmierung mit Java
Pfeil 11.6.1 Programmierparadigmen: imperativ oder deklarativ
Pfeil 11.6.2 Funktionale Programmierung und funktionale Programmiersprachen
Pfeil 11.6.3 Funktionale Programmierung in Java am Beispiel vom Comparator
Pfeil 11.6.4 Lambda-Ausdrücke als Funktionen sehen
Pfeil 11.7 Funktionale Schnittstelle aus dem java.util.function-Paket
Pfeil 11.7.1 Blöcke mit Code und die funktionale Schnittstelle java.util.function.Consumer
Pfeil 11.7.2 Supplier
Pfeil 11.7.3 Prädikate und java.util.function.Predicate
Pfeil 11.7.4 Funktionen und die allgemeine funktionale Schnittstelle java.util.function.Function
Pfeil 11.7.5 Ein bisschen Bi …
Pfeil 11.7.6 Funktionale Schnittstellen mit Primitiven
Pfeil 11.8 Optional ist keine Nullnummer
Pfeil 11.8.1 Optional-Typ
Pfeil 11.8.2 Primitive optionale Typen
Pfeil 11.8.3 Erstmal funktional mit Optional
Pfeil 11.9 Was ist jetzt so funktional?
Pfeil 11.10 Zum Weiterlesen
 

Zum Seitenanfang

11.8Optional ist keine Nullnummer Zur vorigen ÜberschriftZur nächsten Überschrift

Java hat eine besondere Referenz, die Entwicklern die Haare zu Berge stehen lässt und die Grund für lange Debug-Stunden ist: die null-Referenz. Eigentlich sagt null nur aus: »nicht initialisiert«. Doch was null so problematisch macht, ist die NullPointerException, die durch referenzierte null-Ausdrücke ausgelöst wird.

[zB]Beispiel

Entwickler haben vergessen, das Attribut location mit einem Objekt zu initialisieren, sodass setLocation(…) fehlschlagen wird:

class Place {

private Point2D location;

public void setLocation( double longitude, double latitude ) {

location.setLocation( longitude, latitude ); // inline NullPointerException

}

}

Einsatz von null

Fehler dieser Art sind durch Tests relativ leicht aufzuspüren. Aber hier liegt nicht das Problem. Das eigentliche Problem ist, dass Entwickler allzu gerne die typenlose null[ 212 ](null instanceof Typ ist immer false . ) als magischen Sonderwert sehen, sodass sie neben »nicht initialisiert« noch etwas anderes bedeutet:

[zB]Beispiel 1

Die mit Javadoc dokumentiere Methode getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits) in der Schnittstelle JavaCompiler ist so ein Beispiel:

  • out: »a writer for additional output from the compiler; use system.err if null«

  • fileManager: »a file manager; if null use the compiler’s standard filemanager«

  • diagnosticListener: »a diagnostic listener; if null use the compiler’s default method for reporting diagnostics«

  • options: »compiler options, null means no options«

  • classes: »names of classes to be processed by annotation processing, null means no class names«

  • compilationUnits: »the compilation units to compile, null means no compilation units«

Alle Argumente können null sein, getTask(null, null, null, null, null, null) ist ein korrekter Aufruf. Schön ist die API nicht, und besser wäre sie wohl mit einem Builder-Pattern gelöst.

[zB]Beispiel 2

Der BufferedReader erlaubt das zeilenweise Einlesen aus Datenquellen, und readLine() liefert null, wenn es keine Zeile mehr zu lesen gibt.

[zB]Beispiel 3

Viel Irritation gibt es mit der API vom Assoziativspeicher. Eine gewöhnliche HashMap kann als assoziierten Wert null bekommen, doch get(key) liefert auch dann null, wenn es keinen assoziierten Wert gibt. Das führt zu einer Mehrdeutigkeit, da die Rückgabe von get(…) nicht verrät, ob es eine Abbildung auf null gibt oder ob der Schlüssel nicht vorhanden ist.

Map<Integer,String> map = new HashMap<>();

map.put( 0, null );

System.out.println( map.containsKey( 0 ) ); // true

System.out.println( map.containsValue( null ) ); // true

System.out.println( map.get( 0 ) ); // null

System.out.println( map.get( 1 ) ); // null

Kann die Map null-Werte enthalten, muss es immer ein Paar der Art if(map.containsKey(key)), gefolgt von map.get(key) geben. Am besten verzichten Entwickler auf null in Datenstrukturen.

Da null so viele Einsatzfälle hat und das Lesen der API-Dokumentation gerne übersprungen wird, sollte es zu einigen null-Einsätzen Alternativen geben. Manches Mal ist das einfach, etwa wenn die Rückgabe Sammlungen sind. Dann gibt es mit einer leeren Sammlung eine gute Alternative zu null. Das ist ein Spezialfall des so genannten Null-Object-Patterns und wird in Kapitel 16, »Einführung in Datenstrukturen und Algorithmen«, näher beschrieben.

Fehler, die aufgrund einer NullPointerException entstehen, ließen sich natürlich komplett vermeiden, wenn immer ordentlich auf null-Referenzen getestet würde. Aber gerade die null-Prüfungen werden von Entwicklern gerne vergessen, da ihnen nicht bewusst ist oder sie nicht erwarten, dass eine Rückgabe null sein kann. Gewünscht ist ein Programmkonstrukt, bei dem explizit wird, dass ein Wert nicht vorhanden sein kann, sodass nicht null diese Rolle übernehmen muss. Wenn im Code lesbar ist, dass ein Wert optional ist, also vorhanden sein kann oder nicht, reduziert das Fehler.

 

Zum Seitenanfang

11.8.1Optional-Typ Zur vorigen ÜberschriftZur nächsten Überschrift

Die Java-Bibliothek bietet eine Art Container, der ein Element enthalten kann oder nicht. Wenn der Container ein Element enthält, ist es nie null. Dieser Container kann befragt werden, ob er ein Element enthält oder nicht. Eine null als Kennung ist somit überflüssig.

[zB]Beispiel

Optional<String> opt1 = Optional.of( "Aitazaz Hassan Bangash" );

System.out.println( opt1.isPresent() ); // true

System.out.println( opt1.get() ); // Aitazaz Hassan Bangash

Optional<String> opt2 = Optional.empty();

System.out.println( opt2.isPresent() ); // false

// opt2.get() -> java.util.NoSuchElementException: No value present

Optional<String> opt3 = Optional.ofNullable( "Malala" );

System.out.println( opt3.isPresent() ); // true

System.out.println( opt3.get() ); // Malala

Optional<String> opt4 = Optional.ofNullable( null );

System.out.println( opt4.isPresent() ); // false

// opt4.get() -> java.util.NoSuchElementException: No value present
final class java.lang.Optional<T>
  • static <T> Optional<T> empty()

    Liefert ein leeres Optional-Objekt.

  • boolean isPresent()

    Liefert wahr, wenn dieses Optional einen Wert hat, sonst ist wie im Fall von empty() die Rückgabe false.

  • static <T> Optional<T> of(T value)

    Baut ein neues Optional mit einem Wert auf, der nicht null sein darf; andernfalls gibt es eine NullPointerException. null in das Optional hineinzubekommen, geht also nicht.

  • static <T> Optional<T> ofNullable(T value)

    Liefert ein Optional mit dem Wert, wenn dieser ungleich null ist, bei null ist die Rückgabe ein Optional.empty().

  • T get()

    Liefert den Wert. Enthält das Optional keinen Wert, weil kein Wert isPresent() ist, folgt eine NoSuchElementException.

  • T orElse(T other)

    Ist ein Wert isPresent(), liefere den Wert. Ist das Optional leer, liefere other.

Des Weiteren überschreibt Optional die Methoden equals(…), toString() und hashCode() – 0, wenn kein Wert gegeben ist, sonst Hashcode vom Element – und ein paar weitere Methoden, die wir uns später anschauen.

[»]Hinweis

Intern null zu verwenden hat zum Beispiel den Vorteil, dass die Objekte serialisiert werden können. Optional implementiert Serializable nicht, daher sind Optional-Attribute nicht serialisierbar, können also etwa nicht im Fall von Remote-Aufrufen mit RMI übertragen werden. Auch die Abbildung auf XML oder auf Datenbanken ist umständlicher, wenn nicht JavaBean-Properties herangezogen werden, sondern die internen Attribute.

Ehepartner oder nicht?

Optional wird also dazu verwendet, im Code explizit auszudrücken, ob ein Wert vorhanden ist oder nicht. Das gilt auf beiden Seiten: Der Erzeuger muss explizit ofXXX(…) aufrufen und der Nutzer explizit isPresent() oder get(). Beide Seiten sind sich bewusst, dass sie es mit einem Wert zu tun haben, der optional ist, also existieren kann oder nicht. Wir wollen das in einem Beispiel nutzen, und zwar für eine Person, die einen Ehepartner haben kann:

Listing 11.9com/tutego/insel/java/lang/Person.java, Person

public class Person {

private Person spouse;

public void setSpouse( Person spouse ) {

this.spouse = Objects.requireNonNull( spouse );

}

public void removeSpouse() {

spouse = null;

}

public Optional<Person> getSpouse() {

return Optional.ofNullable( spouse );

}

}

In diesem Beispiel ist null für die interne Referenz auf den Partner möglich; diese Kodierung soll aber nicht nach außen gelangen. Daher liefert getSpouse() nicht direkt die Referenz, sondern es kommt Optional zum Einsatz und drückt aus, ob eine Person einen Ehepartner hat oder nicht. Auch bei setSpouse(…) akzeptieren wir kein null, denn null-Argumente sollten so weit wie möglich vermieden werden. Ein Optional ist hier nicht angemessen, weil es ein Fehler ist, null zu übergeben. Zusätzlich sollte natürlich die Javadoc an setSpouse(…) dokumentieren, dass ein null-Argument zu einer NullPointerException führt. Daher passt Optional als Parametertyp nicht.

Listing 11.10com/tutego/insel/java/lang/OptionalDemo.java, main()

Person heinz = new Person();

System.out.println( heinz.getSpouse().isPresent() ); // false

Person eva = new Person();

heinz.setSpouse( eva );

System.out.println( heinz.getSpouse().isPresent() ); // true

System.out.println( heinz.getSpouse().get() ); // com/…/Person

heinz.removeSpouse();

System.out.println( heinz.getSpouse().isPresent() ); // false
 

Zum Seitenanfang

11.8.2Primitive optionale Typen Zur vorigen ÜberschriftZur nächsten Überschrift

Während Referenzen null sein können und auf diese Weise das Nichtvorhandensein anzeigen, ist das bei primitiven Datentypen nicht so einfach. Wenn eine Methode ein boolean zurückgibt, bleibt neben true und false nicht viel übrig, und ein »nicht zugewiesen« wird dann doch gerne wieder über einen Boolean verpackt und auf null getestet. Gerade bei Ganzzahlen gibt es immer wieder Rückgaben wie –1.[ 215 ](Unter http://docs.oracle.com/javase/8/docs/api/constant-values.html lassen sich alle Konstantendeklarationen einsehen. ) Das ist bei den folgenden Beispielen der Fall:

  • Wenn bei InputStreams read(…) keine Eingaben mehr kommen, wird -1 zurückgegeben.

  • indexOf(Object) von List liefert -1, wenn das gesuchte Objekt nicht in der Liste ist und folglich auch keine Position vorhanden ist.

  • Bei einer unbekannten Bytelänge einer MIDI-Datei (Typ MidiFileFormat) hat getByteLength() als Rückgabe -1.

Diese magischen Werte sollten vermieden werden, und daher kann auch der optionale Typ wieder erscheinen.

Als generischer Typ kann Optional beliebige Typen kapseln, und primitive Werte könnten in Wrapper verpackt werden. Allerdings bietet Java für drei primitive Typen spezielle Optional-Typen an: OptionalInt, OptionalLong, OptionalDouble:

Optional<T>

OptionalInt

OptionalLong

OptionalDouble

static <T>

Optional<T>

empty()

static

OptionalInt

empty()

static

OptionalLong

empty()

static

Optional-Double

empty()

T get()

int getAsInt()

long getAsLong()

double getAsDouble()

boolean isPresent()

static <T>

Optional<T>

of(T value)

static OptionalInt

of(int value)

static OptionalLong

of(long value)

static OptionalDouble

of(double value)

static <T>

Optional<T>

ofNullable(T

value)

nicht übertragbar

T orElse(T other)

int orElse(int other)

long orElse(long other)

double orElse(

double other)

boolean equals(Object obj)

int hashCode()

String toString()

Tabelle 11.12Methodenvergleich zwischen den vier OptionalXXX-Klassen

Die Optional-Methode ofNullable(…) fällt in den primitiven Optional-Klassen natürlich raus. Die optionalen Typen für die drei primitiven Typen haben insgesamt weniger Methoden, und die obere Tabelle ist nicht ganz vollständig. Wir kommen im Rahmen der funktionalen Programmierung in Java noch auf die verbleibenden Methoden wie isPresent(…) zurück.

Best Practice

OptionalXXX-Typen eignen sich hervorragend als Rückgabetyp, sind als Parametertyp denkbar, doch wenig attraktiv für interne Attribute. Intern ist null eine akzeptable Wahl, der »Typ« ist schnell und speicherschonend.

 

Zum Seitenanfang

11.8.3Erstmal funktional mit Optional Zur vorigen ÜberschriftZur nächsten Überschrift

Neben den vorgestellten Methoden wie ofXXX(…) und isPresent() gibt es weitere, die auf funktionale Schnittstellen zurückgreifen:

final class java.lang.Optional<T>
  • void ifPresent(Consumer<? super T> consumer)

    Repräsentiert das Optional einen Wert, rufe den Consumer mit diesem Wert auf, andernfalls mache nichts.

  • Optional<T> filter(Predicate<? super T> predicate)

    Enthält das Optional einen Wert und ist das Prädikat predicate auf dem Wert wahr, ist die Rückgabe das eigene Optional (also this), sonst ist die Rückgabe Optional.empty().

  • <U> Optional<U> map(Function<? super T,? extends U> mapper)

    Repräsentiert das Optional einen Wert, dann wende die Funktion an und verpacke das Ergebnis (wenn es ungleich null ist) wieder in ein Optional. Ist das Optional ohne Wert, dann ist die Rückgabe Optional.empty(), genauso, wenn die Funktion null liefert.

  • <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)

    Wie map(…), nur dass die Funktion ein Optional statt eines direkten Werts gibt. Liefert die Funktion mapper ein leeres Optional, so ist das Ergebnis von flatMap(…) auch Optional. empty().

  • T orElseGet(Supplier<? extends T> other)

    Repräsentiert das Optional einen Wert, so liefere ihn; ist das Optional leer, so beziehe den Alternativwert aus dem Supplier.

  • <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)

    Repräsentiert das Optional einen Wert, so liefere ihn, andernfalls hole mit Supplier das Ausnahme-Objekt, und löse es aus.

[zB]Beispiel

Wenn das Optional keinen Wert hat, soll eine NullPointerException statt der NoSuchElementException ausgelöst werden.

String s = optionalString.orElseThrow( NullPointerException::new );

Beispiel für NullPointerException-sichere Kaskadierung von Aufrufen mit Optional

Die beiden XXXmap(…)-Methoden sind besonders interessant und ermöglichen einen ganz neuen Programmierstil. Warum, soll ein Beispiel zeigen.

Der folgende Zweizeiler gibt auf meinem System »MICROSOFT KERNELDEBUGGER-NETZWERKADAPTER« aus:

String s = NetworkInterface.getByIndex( 2 ).getDisplayName().toUpperCase();

System.out.println( s );

Allerdings ist der Programmcode alles andere als gut, denn NetworkInterface.getByIndex(int) kann null zurückgeben und getDisplayName() auch. Um ohne eine NullPointerException um die Klippen zu schiffen, müssen wir schreiben:

NetworkInterface networkInterface = NetworkInterface.getByIndex( 2 );

if ( networkInterface != null ) {

String displayName = networkInterface.getDisplayName();

if ( displayName != null )

System.out.println( displayName.toUpperCase() );

}

Von der Eleganz des Zweizeilers ist nicht mehr viel geblieben. Integrieren wir Optional (was ja eigentlich ein toller Rückgabetyp für getByIndex() und getDisplayName() wäre):

Optional<NetworkInterface> networkInterface = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) );

if ( networkInterface.isPresent() ) {

Optional<String> name = Optional.ofNullable( networkInterface.get().getDisplayName() );

if ( name.isPresent() )

System.out.println( name.get().toUpperCase() );

}

Mit Optional wird es nicht sofort besser, doch statt if können wir einen Lambda-Ausdruck nehmen und bei ifPresent(…) einsetzen:

Optional<NetworkInterface> networkInterface = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) );

networkInterface.ifPresent( ni -> {

Optional<String> displayName = Optional.ofNullable( ni.getDisplayName() );

displayName.ifPresent( name -> {

System.out.println( name.toUpperCase() );

} );

} );

Wenn wir die lokalen Variablen networkInterface und displayName entfernen, landen wir bei:

Optional.ofNullable( NetworkInterface.getByIndex( 2 ) ).ifPresent( ni -> {

Optional.ofNullable( ni.getDisplayName() ).ifPresent( name -> {

System.out.println( name.toUpperCase() );

} );

} );

Von der Struktur her ist das mit der if-Abfrage identisch und über die Einrückungen auch zu erkennen. Fallunterscheidungen mit Optional und ifPresent(…) umzuschreiben bringt keinen Vorteil.

In Fallunterscheidungen zu denken hilft hier nicht weiter. Was wir uns bei NetworkInterface.getByIndex( 2 ).getDisplayName().toUpperCase() vor Augen halten müssen, ist eine Kette von Abbildungen. NetworkInterface.getByIndex(int) bildet auf NetworkInterface ab, getDisplayName() von NetworkInterface bildet auf String ab, und toUpperCase() bildet von einem String auf einen anderen String ab. Wir verketten drei Abbildungen und müssten ausdrücken können: Wenn eine Abbildung fehlschlägt, dann höre mit der Abbildung auf. Und genau hier kommen Optional und map(…) ins Spiel. In Code:

Optional<String> s = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) )

.map( ni -> ni.getDisplayName() )

.map( name -> name.toUpperCase() );

s.ifPresent( System.out::println );

Die Klasse Optional hilft uns bei zwei Dingen: Erstens wird map(…) beim Empfangen einer null-Referenz auf ein Optional.empty() abbilden, und zweitens ist das Verketten von leeren Optionals kein Problem, es passiert einfach nichts – Optional.empty().map(…) führt nichts aus, und die Rückgabe ist einfach nur ein leeres Optional. Am Ende der Kette steht nicht mehr String (wie am Anfang des Beispiels), sondern Optional<String>.

Umgeschrieben mit Methodenreferenzen und weiter verkürzt ist der Code sehr gut lesbar und Null-Pointer-Exception-sicher:

Optional.ofNullable( NetworkInterface.getByIndex( 2 ) )

.map( NetworkInterface::getDisplayName )

.map( String::toUpperCase )

.ifPresent( System.out::println );

Die Logik kommt ohne externe Fallunterscheidungen aus und arbeitet nur mit optionalen Abbildungen. Das ist ein schönes Beispiel für funktionale Programmierung.

Primitiv-Optionales

Die eigentliche Optional-Klasse ist generisch und kapselt jeden Referenztyp. Auch für die primitiven Typen int, long und double gibt es in drei speziellen Klassen OptionalInt, OptionalLong, OptionalDouble Methoden zur funktionalen Programmierung. Stellen wir die Methoden der vier OptionalXXX-Klassen gegenüber:

Optional<T>

OptionalInt

OptionalLong

OptionalDouble

static <T>

Optional<T>

empty()

static

OptionalInt

empty()

static

OptionalLong

empty()

static

OptionalDouble

empty()

T get()

int getAsInt()

long getAsLong()

double

getAsDouble()

boolean isPresent()

static <T>

Optional<T> of(T value)

static OptionalInt

of(int value)

static OptionalLong

of(long value)

static Optional Double

of(double value)

static <T>

Optional<T>

ofNullable(T value)

nicht übertragbar

T orElse(T other)

int orElse(int other)

long orElse(long other)

double orElse(double other)

boolean equals(Object obj)

int hashCode()

String toString()

void ifPresent(

Consumer<? super T> consumer)

void ifPresent(

IntConsumer

consumer)

void ifPresent(

LongConsumer

consumer)

void ifPresent(

DoubleConsumer consumer)

T orElseGet(Supplier<? extends T> other)

int orElseGet(IntSupplier other)

long orElseGet(LongSupplier other)

double orElseGet(DoubleSupplier other)

<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)

<X extends Throwable> int orElseThrow(Supplier<X> exceptionSupplier)

<X extends Throwable> long orElseThrow(Supplier<X> exceptionSupplier)

<X extends Throwable> double

orElseThrow(

Supplier<X>

exceptionSupplier)

Optional<T> filter(Predicate<? super T> predicate)

nicht vorhanden

<U> Optional<U> flatMap(Function <? super T,Optional<U>> mapper)

<U> Optional<U> map(Function<? super T,? extends U> mapper)

Tabelle 11.13Vergleich von Optional mit den primitiven OptionalXXX-Klassen

 


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
 Zum Katalog
Zum Katalog: Java ist auch eine Insel Java ist auch eine Insel

Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?

Ihre Meinung



 Buchempfehlungen
Zum Katalog: Java ist auch eine Insel

Java ist auch eine Insel




Zum Katalog: Java SE 9-Standard-Bibliothek

Java SE 9-Standard-Bibliothek




Zum Katalog: Professionell entwickeln mit Java EE 8

Professionell entwickeln mit Java EE 8




Zum Katalog: Entwurfsmuster

Entwurfsmuster




Zum Katalog: IT-Projektmanagement

IT-Projektmanagement




 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2017

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