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 Exceptions
7 Äußere.innere Klassen
8 Besondere Klassen der Java SE
9 Generics<T>
10 Architektur, Design und angewandte Objektorientierung
11 Die Klassenbibliothek
12 Einführung in die nebenläufige Programmierung
13 Einführung in Datenstrukturen und Algorithmen
14 Einführung in grafische Oberflächen
15 Einführung in Dateien und Datenströme
16 Einführung in die <XML>-Verarbeitung mit Java
17 Einführung ins Datenbankmanagement mit JDBC
18 Bits und Bytes und Mathematisches
19 Die Werkzeuge des JDK
A Die Klassenbibliothek
Stichwort

Download:
- Aufgaben, ca. 1,1 MB
- Programme, ca. 12,8 MB

Buch bestellen
Ihre Meinung?

Spacer
Java ist auch eine Insel von Christian Ullenboom
Das umfassende Handbuch
Buch: Java ist auch eine Insel

Java ist auch eine Insel
Galileo Computing
1308 S., 10., aktualisierte Auflage, geb., mit DVD
ca. 49,90 Euro, ISBN 978-3-8362-1802-3
Pfeil 9 Generics<T>
Pfeil 9.1 Einführung in Java Generics
Pfeil 9.1.1 Mensch versus Maschine: Typprüfung des Compilers und der Laufzeitumgebung
Pfeil 9.1.2 Taschen
Pfeil 9.1.3 Generische Typen deklarieren
Pfeil 9.1.4 Generics nutzen
Pfeil 9.1.5 Diamonds are forever
Pfeil 9.1.6 Generische Schnittstellen
Pfeil 9.1.7 Generische Methoden/Konstruktoren und Typ-Inferenz
Pfeil 9.2 Umsetzen der Generics, Typlöschung und Raw-Types
Pfeil 9.2.1 Realisierungsmöglichkeiten
Pfeil 9.2.2 Typlöschung (Type Erasure)
Pfeil 9.2.3 Probleme aus der Typlöschung
Pfeil 9.2.4 Raw-Type
Pfeil 9.3 Einschränken der Typen über Bounds
Pfeil 9.3.1 Einfache Einschränkungen mit extends
Pfeil 9.3.2 Weitere Obertypen mit &
Pfeil 9.4 Typparameter in der throws-Klausel *
Pfeil 9.4.1 Deklaration einer Klasse mit Typvariable <E extends Exception>
Pfeil 9.4.2 Parametrisierter Typ bei Typvariable <E extends Exception>
Pfeil 9.5 Generics und Vererbung, Invarianz
Pfeil 9.5.1 Arrays sind invariant
Pfeil 9.5.2 Generics sind kovariant
Pfeil 9.5.3 Wildcards mit ?
Pfeil 9.5.4 Bounded Wildcards
Pfeil 9.5.5 Bounded-Wildcard-Typen und Bounded-Typvariablen
Pfeil 9.5.6 Das LESS-Prinzip
Pfeil 9.5.7 Enum<E extends Enum<E>> *
Pfeil 9.6 Konsequenzen der Typlöschung: Typ-Token, Arrays und Brücken *
Pfeil 9.6.1 Typ-Token
Pfeil 9.6.2 Super-Type-Token
Pfeil 9.6.3 Generics und Arrays
Pfeil 9.6.4 Brückenmethoden

Rheinwerk Computing - Zum Seitenanfang

9.6 Konsequenzen der Typlöschung: Typ-Token, Arrays und Brücken *Zur nächsten Überschrift

Die Typlöschung ist im Allgemeinen kein so großes Problem, doch in speziellen Situationen ist es lästig, dass der Typ nicht zur Laufzeit vorliegt.


Rheinwerk Computing - Zum Seitenanfang

9.6.1 Typ-TokenZur nächsten ÜberschriftZur vorigen Überschrift

Wir haben zum Beispiel gesehen, dass, wenn eine Tasche mit der Typvariablen T deklariert wurde, dieses T nicht wirklich wie ein Makro durch den Typparameter ersetzt wird, sondern dass in der Regel nur einfach Object eingesetzt wird.

class Pocket<T>
{
T newPocketContent() { return new T(); } // Fehler Compilerfehler
}

Aus new T() macht die Typlöschung also new Object(), und das ist nichts wert. Doch wie kann dennoch ein Typ erzeugt werden und der Typ T zur Laufzeit vorliegen?

Hier lässt es sich ein Trick nutzen, nämlich ein Class-Objekt für den Typ einsetzen.

Tabelle 9.12: Transfer der Typparameter durch Class-Objekte

Typparameter Class-Objekt repräsentiert Typparameter
String Class.String
Integer Class.Integer

Dieses Class-Objekt, das nun den Typ repräsentiert, heißt Typ-Token (engl. type token). Es kommt natürlich entgegen, dass Class selbst als generischer Typ deklariert ist und zwei interessante Methoden ebenfalls generifiziert wurden:

Class<String> clazz1 = String.class;
String newInstance = clazz1.newInstance();
Class< ? extends String> clazz2 = newInstance.getClass();
System.out.println( clazz1.equals( clazz2 ) ); // true

Zunächst ist da die Methode newInstance(). Sie erzeugt ein neues Exemplar mit Typ, den das Class-Objekt repräsentiert.

final class java.lang.Class<T>
implements Serializable, GenericDeclaration, Type, AnnotatedElement
  • public T newInstance()
    throws InstantiationException, IllegalAccessException

Mit einem gegebenen Objekt lässt sich mit getClass() das zugehörige Class-Objekt zur Klasse erfragen.

class java.lang.Object
  • public final native Class<?> getClass()
    Liefert Class-Objekt.
Hinweis

Die Rückgabe Class<?> bei getClass() ist unschön, insbesondere die allgemeine Wildcard. Sie verhindert, dass sich Folgendes schreiben lässt: Class<String> clazz = "ARTE".getClass(); // Fehler Type mismatch Compilerfehler Stattdessen muss es so heißen: Class<? extends String> clazz = "ARTE".getClass(); Da Object nicht generisch deklariert ist, ist es kein Wunder, dass getClass() keine genaueren Angaben machen kann.

Lösungen mit dem Typ-Token

Um das Typ-Token einzusetzen, muss das Class-Object mit als Argument in einem Konstruktor oder einer Methode übergeben werden. So lässt sich etwa eine newInstance()-Methode nachbauen, die die geprüften Exceptions fängt und im Fehlerfall als RuntimeException meldet. Gut zu sehen ist, wie sich der Typ des Class-Objekts auf die Rückgabe überträgt.

public static <T> T newInstance( Class<T> type )
{
try
{
return type.newInstance();
}
catch ( /*InstantiationException, IllegalAccessException*/ Exception e )
{
throw new RuntimeException( e );
}
}

PS: Seit Java 7 ist ReflectiveOperationException die Basisklasse für Reflection-Fehler.


Rheinwerk Computing - Zum Seitenanfang

9.6.2 Super-Type-TokenZur nächsten ÜberschriftZur vorigen Überschrift

Mit einem Class-Objekt lässt sich gut ein Typ repräsentieren, allerdings gibt es ein Problem. Das Class-Objekt kann selbst keine generischen Typen darstellen.

Tabelle 9.13: Ein Class-Objekt kann keinen generischen Typ beschreiben.

Typparameter Class-Objekt repräsentiert Typparameter
String Class.String
Integer Class.Integer
Pocket<String> Class.Pocket<String> Geht nicht!

Der wirkliche Typ lässt sich nur mit viel Getrickse bestimmen und festhalten. Hier kommt die Reflection-API zum Einsatz, sodass nur kurz die Klasse, ein Beispiel und Verweise auf weitere Literatur vorgestellt werden sollen. Hier die Klasse:

Listing 9.24: com/tutego/insel/generic/TypeRef, TypeRef

public abstract class TypeRef<T>
{
public final Type type;

protected TypeRef()
{
ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass();
type = superclass.getActualTypeArguments()[0];
}
}

Und ein Beispiel, das eine anonyme Unterklasse erzeugt und so den Typ zugänglich macht:

Listing 9.25: com/tutego/insel/generic/TypeRefDemo, main()

TypeRef<Pocket<String>> ref1 = new TypeRef<Pocket<String>>(){};
System.out.println( ref1.type ); // com.tutego.insel.generic.Pocket<java.lang.String>
TypeRef<Pocket<Byte>> ref2 = new TypeRef<Pocket<Byte>>(){};
System.out.println( ref2.type ); // com.tutego.insel.generic.Pocket<java.lang.Byte>

Damit konnten wir den Typparameter über java.lang.reflect.Type festhalten, und ref1 unterscheidet sich eindeutig von ref2. Der Typ liegt jedoch nicht als Class-Objekt vor, und Operationen wie newInstance() sind auf Type nicht möglich – die Schnittstelle deklariert überhaupt keine Methoden, sondern repräsentiert nur Typen.

Auf den Webseiten http://gafter.blogspot.com/2006/12/super-type-tokens.html und http://www.artima.com/weblogs/viewpost.jsp?thread=206350 sind weitere Informationen und Einsatzgebiete zu finden. Super-Typ-Token kommen in der Java-Welt nicht besonders oft vor.


Rheinwerk Computing - Zum Seitenanfang

9.6.3 Generics und ArraysZur nächsten ÜberschriftZur vorigen Überschrift

Die Typlöschung ist der Grund dafür, dass Arrays nicht so umgesetzt werden können, wie es sich der Entwickler denkt.[174](Bei Sun (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4888066) ist dafür ein Bug gelistet. Suns Antwort auf die Bitte, ihn zu beheben, lautet lapidar: »Some day, perhaps, but not now.«) Folgendes ergibt einen Compilerfehler:

class TwoBox<T>
{
T[] array = new T[ 2 ]; // Fehler Cannot create a generic array of T
T[] getArray() { return array; }
}

Der Grund für diesen Fehler ist dann gut zu erkennen, wenn wir überlegen, zu welchem Programmcode die Typlöschung führen würde:

class TwoBox
{
Object[] array = new Object[ 2 ]; // (1)
Object[] getArray() { return array; }
}

Der Aufrufer würde nun die TwoBox parametrisiert verwenden wollen:

TwoBox<String> twoStrings = new TwoBox<String>();
String[] stringArray = twoStrings.getArray();

Denken wir an dieser Stelle wieder an die Typlöschung und an das, was der Compiler generiert:

TwoBox twoStrings = new TwoBox();
String[] stringArray = (String[]) twoStrings.getArray(); // (2)

Jetzt ist es auffällig: Während (1) ein Object-Array der Länge 2 aufbaut und auch getArray() dies als Object-Array nach außen gibt, castet (2) dieses Object-Array auf ein String-Array. Das geht aber nicht, denn diese beiden Typen sind nicht typkompatibel. Zwar kann natürlich ein Object-Array Strings referenzieren, aber das Feld selbst als Objekt ist eben ein Object[] und kein String[].

Reflection hilft

Die Java-API bietet über Reflection wieder eine Möglichkeit, Arrays eines Typs zu erzeugen:

T[] array = (T[]) Array.newInstance( clazz, 2 );

Allerdings muss der Class-Typ clazz bekannt sein und als zusätzlicher Parameter übergeben werden. Die Syntax T.class gibt einen Compilerfehler, denn über die Typlöschung wäre das ja sowieso immer Object.class, was den gleichen Fehler wie vorher ergibt und kein Fortschritt wäre.


Rheinwerk Computing - Zum Seitenanfang

9.6.4 BrückenmethodenZur nächsten ÜberschriftZur vorigen Überschrift

Aus der Tatsache, dass mit Generics übersetzte Klassen auf einer JVM ablauffähig sein müssen, die kein generisches Typsystem besitzt, folgen diverse Hacks, die der Compiler zur Erhaltung der heiligen Kompatibilität vornimmt. Er fügt neue Methoden ein, sogenannte Brückenmethoden, damit der Bytecode nach der Typlöschung auch von älteren Programmen genutzt werden kann.

Brückenmethode wegen Typvariablen in Parametern

Starten wir mit der Schnittstelle Comparable, die generisch deklariert wurde.

public interface Comparable<T> { public int compareTo( T o ); }

Die bekannte Klasse Integer implementiert zum Beispiel diese Schnittstelle und kann somit sagen, wie die Ordnung zu einem anderen Integer-Objekt ist:

Listing 9.26: java/lang/Integer.java, Ausschnitt

public final class Integer extends Number implements Comparable<Integer>
{
private final int value;

public Integer( int value ) { this.value = value; }

public int compareTo( Integer anotherInteger )
{
int thisVal = this.value;
int anotherVal = anotherInteger.value;
return ( thisVal < anotherVal ? –1 : (thisVal == anotherVal ? 0 : 1) );
}
...
}

Integer implementiert die Methode compareTo() mit dem Parametertyp Integer. Der Compiler wird also eine Methode mit der Signatur compareTo(Integer) erstellen. Doch damit beginnt ein Problem! Wir haben eine unbekannte Anzahl an Zeilen Quellcode, die sich auf eine Methode compareTo(Object) beziehen, denn vor Java 5 war die Signatur ja anders.

Damit es nicht zu Inkompatibilitäten kommt, setzt der Compiler einfach noch die Methode compareTo(Object) bei Integer dazu. Die Implementierung sieht so aus, dass sie einfach delegiert:

public int compareTo( Object anotherInteger )
{
return compareTo( (Integer) anotherInteger );
}

Brückenmethode wegen kovarianten Rückgabetypen

Wenn eine Methode überschrieben wird, so muss die Unterklasse die gleiche Signatur (also den gleichen Methodennamen und die gleiche Parameterliste) besitzen. Nehmen wir eine Klasse CloneableFont, die Font erweitert und die clone()-Methode aus Object überschreibt. Eine Klasse, die sich auch unter Java 1.4 übersetzen lässt, würde so aussehen:

Listing 9.27: com/tutego/insel/nongeneric/CloneableFont.java, CloneableFont

public class CloneableFont extends Font implements Cloneable
{
public CloneableFont( String name, int style, int size )
{
super( name, style, size );
}

@Override public Object clone()
{
return new Font( getAttributes() );
}
}

Im Bytecode der Klasse CloneableFont sind somit ein Konstruktor und eine Methode vermerkt.

Dazu kurz ein Blick auf die Ausgabe des Dienstprogramms javap, das die Signaturen anzeigt:

$ javap com.tutego.insel.nongeneric.CloneableFont
Compiled from "CloneableFont.java"
public class com.tutego.insel.nongeneric.CloneableFont extends java.awt.Font{
public com.tutego.insel.nongeneric.CloneableFont(java.lang.String, int, int);
public java.lang.Object clone();
}

Nehmen wir nun an, eine zweite Klasse ist Nutzer von CloneableFont:

Listing 9.28: com/tutego/insel/nongeneric/CloneableFontDemo.java, main()

CloneableFont font = new CloneableFont( "Arial", Font.BOLD, 12 );
Object font2 = font.clone();

Beim Aufruf font.clone() prüft der Compiler, ob die Methode clone() in CloneableFont aufrufbar ist, und trägt dann die exakte Signatur mit Rückgabe – das ist der entscheidende Punkt – im Bytecode ein. Die Anweisung font.clone() sieht im Bytecode von CloneableFontDemo.class etwa so aus (disassembliert mit javap):

invokevirtual #23;

Die #23 ist ein Verweis auf die clone()-Methode von CloneableFont, und invokevirtual ist der Bytecodebefehl zum Aufruf der Methode. Hinter der 23 steckt eine JVM-Methodenkennung, die von javap so ausgegeben wird:

com/tutego/insel/nongeneric/CloneableFont.clone:()Ljava/lang/Object;

Im Bytecode steht exakt ein Verweis auf die Methode clone() mir dem Rückgabetyp Object.

Seit Java 5 ist eine kleine Änderung beim Überschreiben hinzugekommen. Wenn eine Unterklasse eine Methode überschreibt, kann sie den Rückgabetyp auf einen Untertyp präzisieren – das nennt sich kovariantes Überschreiben. Also kann clone() statt Object jetzt Font zurückgeben.

Tabelle 9.14: Beispiel kovarianter Rückgabetypen

Gleicher Rückgabetyp wie die
überschriebene Methode
Konvarianter Rückgabetyp, seit Java 5

Listing 9.29: com/tutego/insel/nongeneric/CloneableFont.java, clone()

@Override public Object clone()
{
return new Font( getAttributes() );
}

Listing 9.30: com/tutego/insel/generic/CloneableFont.java, clone()

@Override public Font clone()
{
return new Font( getAttributes() );
}

Da der Rückgabetyp der überschriebenen Methode nun nicht mehr Object, sondern Font ist, ändert sich auch der Bytecode von CloneableFont. Die Datei CloneableFont.class ist ohne konvariante Rückgabe 593 Byte groß und mit konvarianter Rückgabe 739 Byte. (Warum dieser satte Unterschied? Dazu später mehr.)

Stellen wir uns nun Folgendes vor: Die erste Version von CloneableFont wurde lange vor Java 5 implementiert und konnte so noch kein kovariantes Überschreiben nutzen. Die Klasse CloneableFont ist unglaublich populär, und die Methode clone() – die Object liefert – wird von vielen Stellen aufgerufen. Im Bytecode der nutzenden Klassen gibt es also immer einen Bezug auf die Methode mit der JVM-Signatur:

com/tutego/insel/nongeneric/CloneableFont.clone:()Ljava/lang/Object;

Bei einem Refactoring geht der Autor der Klasse CloneableFont über seine Klasse und sieht, dass er bei clone() die kovariante Rückgabe nutzen kann. Er korrigiert die Methode und setzt statt Object den Typ Font ein. Er kompiliert die Klasse und setzt sie wieder in die Öffentlichkeit.

Nun stellt sich die Frage, was mit den Nutzern ist, also Klassen wie CloneableFontDemo, die nicht neu übersetzt werden. Denn sie suchen eine Methode mit dieser JVM-Signatur:

com/tutego/insel/nongeneric/CloneableFont.clone:()Ljava/lang/Object;

Da clone() in CloneableFont in der aktuell Version nun Font zurückgibt, müsste die JVM einen Fehler auslösen, denn der Bytecode ist ja anders, und die gefragte Methode mit der Rückgabe Object ist nicht mehr da. Das wäre ein riesiges Problem, denn so würden durch die Änderung des Autors alle nutzenden Klassen illegal, und die Projekte mit diesen Klassen ließen sich alle nicht mehr übersetzen.

Doch es gibt kein Anlass zur Panik. Es kommt nicht zu einem Compilerfehler, da der Compiler eine Hilfsmethode einfügt, die in der JVM-Signatur mit der Java 1.4-Variante von clone(), also mit der Rückgabe Object, übereinstimmt. Der Disassember javap zeigt das gut:

$ javap com.tutego.insel.generic.CloneableFont
Compiled from "CloneableFont.java"
public class com.tutego.insel.generic.CloneableFont extends java.awt.Font{
public com.tutego.insel.generic.CloneableFont(java.lang.String, int, int);
public java.awt.Font clone();
public java.lang.Object clone() throws java.lang.CloneNotSupportedException;
}

Die clone()-Methode gibt es also zweimal! Interessant ist die Besonderheit, dass Dinge im Bytecode erlaubt sind, die im Java-Programmcode nicht möglich sind. In Java gehört der Rückgabetyp nicht zur Signatur, und der Java-Compiler erlaubt nicht zwei Methoden mit der gleichen Signatur. Im Bytecode allerdings gehört der Rückgabetyp schon dazu, und daher sind die Methoden dort erlaubt, da sie klar unterscheidbar sind.

Übersetzt ein Java 5-Compiler die Klasse CloneableFont mit dem kovarianten Rückgabetyp bei clone(), so setzt er automatisch die Brückenmethode ein. Das erklärt auch, warum der Bytecode der Klassen mit konvarianten Rückgabetypen größer ist. So finden auch die alten Klassen, die auf die ursprüngliche clone()-Methode mit der Rückgabe Object compiliert sind, diese Methode, und es gibt keinen Laufzeitfehler.

Als Letztes muss noch geklärt werden, was der Compiler eigentlich genau für eine Brückenmethode generiert. Das ist einfach, denn in die Brückenmethode setzt der Compiler eine Weiterleitung.

@Override public Font clone()
{
return new Font( getAttributes() );
}
@Override public Object clone()
{
return (Font) clone();
}

Bleibt festzuhalten, dass auf Ebene der JVM kovariante Rückgabetypen nicht möglich sind und im Bytecode immer die Methode inklusive Rückgabetyp referenziert wird.

Hinweis

In Java sind alle Methoden einer Klasse, die sich auch im Bytecode befinden, über Reflection zugänglich. Lästig wäre es nun, wenn Tools Methoden sehen, die vom Compiler eingeführt werden, weil dieser herumtricksen und Beschränkungen umschiffen muss. Die Brückenmethoden werden daher mit einem speziellen Flag markiert und als synthetische Methoden (engl. synthetic method) gekennzeichnet. Das Flag lässt sich über Reflection mit isSynthetic() an den Field-Objekten erfragen.

Brückenmethode wegen einer Typvariable in der Rückgabe

Werfen wir einen Blick auf ein ähnliches Szenario, bei dem der Rückgabetyp durch eine Typvariable einer generisch deklarierten Klasse bestimmt wird, und sehen wir uns an, welche Konsequenzen sich im Bytecode daraus ergeben.

Die Schnittstelle Iterator dient im Wesentlichen dazu, Datensammlungen nach ihren Daten zu fragen. Die beiden Spalten zeigen die Deklaration der Iterator-Schnittstelle unter Java 1.4 und Java 5.

Tabelle 9.15: Veränderung der Implementierung der Schnittstelle Iterator

Java 1.4 Java 5

public interface Iterator
{
boolean hasNext();
Object next();
void remove();
}

public interface Iterator<E>
{
boolean hasNext();
E next();
void remove();
}

So soll hasNext() immer true ergeben, wenn der Iterator weitere Daten liefern kann, und next() liefert schlussendlich das Datum selbst. In der Variante unter Java 5 ist der Rückgabetyp von next() durch die Typvariable bestimmt.

Warum Brückenmethoden benötigt werden, zeigt wieder ein Beispiel, in dem Entwickler mit Java 1.4 begannen und später ihr Programm mit Generics verfeinern. Trotz der Änderung muss eine alte, mit Java 1.4 compilierte Version noch funktionieren.

Üblicherweise liefern Iteratoren alle Elemente einer Datenstruktur. Doch anstatt durch eine Datenstruktur zu laufen, soll unser Iterator, ein EndlessRandomIterator, unendlich viele Zufallszahlen liefen. Wir interessieren uns besonders für die Implementierung der Schnittstelle Iterator. Ohne Generics unter Java 1.4 sieht das so aus:

Listing 9.31: com/tutego/insel/nongeneric/EndlessRandomIterator.java, EndlessRandomIterator

public class EndlessRandomIterator implements Iterator
{
public boolean hasNext() { return true; }
public Object next() { return Double.valueOf( Math.random() ); }
public void remove() { throw new UnsupportedOperationException(); }
}

Ein Programm, das den EndlessRandomIterator nutzt, empfängt bei next() nun ein Double. Aber durch den Ausschluss der Konvarianz in Java 1.4 kann durch die Deklaration von Object next() in Iterator in unserer Klasse EndlessRandomIterator auch nur Object next() stehen. Ein Nutzer von next() wird also auf jeden Fall die Methode next() mit dem Rückgabetyp Object erwarten und so im Bytecode vermerken – die Begründung hatten wir schon bei der Konvarianz im vorangehenden Unterkapitel aufgeführt.

Wird unter Java 5 der EndlessRandomIterator überarbeitet, ändern sich drei Dinge. Erstens wird implements Iterator zu implements Iterator<Double>. Dann wird Object next() zu Double next(), und drittens kann es im Rumpf von next() durch das Autoboxing etwas kürzer werden, nämlich return Math.random(). Der wichtige Punkt ist aber, dass sich der Rückgabetyp von next() ändert, also von Object auf Double geht. An der Stelle muss aber der Compiler mit einer Brückenmethode eingreifen, sodass im Bytecode wieder zwei next()-Methoden stehen: Einmal Object next() und dann Double next(). Denn ohne Double next() würde der alte Programmcode, der Object next() erwartet, plötzlich nicht mehr laufen, und das wäre ein Bruch zu der Abwärtskompatibilität.



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 Bibliotheken






 Java SE Bibliotheken


Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Einstieg in Eclipse






 Einstieg in
 Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in
 Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2011
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