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

 << zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5
Java ist auch eine Insel

Java ist auch eine Insel
5., akt. und erw. Auflage
1454 S., mit CD, 49,90 Euro
Galileo Computing
ISBN 3-89842-747-1
gp Kapitel 21 Reflection und Annotationen
  gp 21.1 Metadaten
    gp 21.1.1 XDoclet
  gp 21.2 Mit dem Class-Objekt etwas über Klassen erfahren
    gp 21.2.1 An ein Class-Objekt kommen
    gp 21.2.2 Was das Class-Objekt beschreibt
    gp 21.2.3 Der Name der Klasse
    gp 21.2.4 Die Arbeit auf dem Feld
    gp 21.2.5 instanceof mit Class-Objekten
    gp 21.2.6 Oberklassen finden
    gp 21.2.7 Implementierte Interfaces einer Klasse oder eines Inferfaces
    gp 21.2.8 Modifizierer und die Klasse Modifier
    gp 21.2.9 Die Attribute einer Klasse
    gp 21.2.10 Methoden einer Klasse erfragen
    gp 21.2.11 Konstruktoren einer Klasse
    gp 21.2.12 Annotationen
  gp 21.3 Objekte manipulieren
    gp 21.3.1 Objekte erzeugen
    gp 21.3.2 Die Belegung der Variablen erfragen
    gp 21.3.3 Eine generische toString()-Funktion
    gp 21.3.4 Variablen setzen
    gp 21.3.5 Private Attribute ändern
  gp 21.4 Methoden aufrufen
    gp 21.4.1 Statische Methoden aufrufen
    gp 21.4.2 Dynamische Methodenaufrufe bei festen Methoden beschleunigen
  gp 21.5 Informationen und Identifizierung von Paketen
    gp 21.5.1 Geladene Pakete
  gp 21.6 Annotationen
    gp 21.6.1 Neue Annotationen definieren
    gp 21.6.2 Annotationen mit genau einem Element
    gp 21.6.3 Beliebige Schlüssel-/Werte-Paare
    gp 21.6.4 Vorbelegte Elemente
    gp 21.6.5 Annotieren von Annotations-Typen
    gp 21.6.6 Annotationen zur Laufzeit ausgelesen
    gp 21.6.7 Mögliche Nachteile von Annotationen


Galileo Computing

21.6 Annotationedowntop

Die vordefinierten Annotationen haben eine besondere Semantik, wie @Override oder @SuppressWarning gut zeigen. Anders als die von Sun definierten javaDoc-Tags für die API-Dokumentation, lassen sich ganz selbstverständlich eigene Annotations-Typen definieren. Neue Frameworks wie EJB 3 oder Bibliotheken wie WebServices machen davon großen Gebrauch, wie schon unser eingehendes Beispiel mit dem Annotations-Typ @WebMethod zeigte.


Galileo Computing

21.6.1 Neue Annotationen definieren  downtop

Ein Annotations-Typ (engl. annotation type) wird so definiert wie eine Schnittstelle, nur steht vor dem Schlüsselwort interface ein @-Zeichen. Die Definition von neuen Annotations-Typen und Schnittstellen ist so groß, dass in der Java Language Specification Annotationen auch im Kapitel über Schnittstellen behandelt werden. (Später erwahren wir den Grund dafür: Der Compiler übersetzt die Annotations-Typen in Schnittstellen.)


Beispiel   Um Elemente zur späteren Optimierung zu markieren, soll ein neuer Annotations-Typ Optimize entworfen werden.
public   @interface   Optimize
{
} 

Unseren neuen Annotations-Typ können wir nun festmachen an beliebigen Typdeklarationen (Objekt-/Klassen-/lokale Variablen, Parameter), Methoden oder Konstruktoren.


Beispiel   Annotiere eine Klasse
  @Optimize
  public class DatabaseDAO { }


Galileo Computing

21.6.2 Annotationen mit genau einem Element  downtop

Der Annotations-Typ konnte mit keinem zusätzlichen Element versehen werden, da er in der bisherigen Schreibweise eine Markierungs-Annotation ist. Erlaubt sind zwar ein Paar runde Klammern hinter dem Namen, und auch Kommentare, aber eben kein Wert.

@Optimize class DatabaseDAO                   // OK
@Optimize() class DatabaseDAO                 // OK
@Optimize("Schneller!") class DatabaseDAO     // Compilerfehler

Damit zusätzliche Informationen wie im dritten Beispiel erlaubt sind, werden im Annotations-Typ Definitionen eingesetzt, deren Schreibweise an Methoden in einer Java-Schnittstelle erinnert. (Aber die Methoden dürfen keinen Parameter definieren, die Rückgabe darf nicht void sein und kein throws haben.)

Soll eine Annotation mit genau einer Zusatzinformation belegt werden, ist eine Methode namens value() einzuführen.


Beispiel   Der Annotations-Typ Optimize muss mit einen String versehen werden.
public @interface Optimize
{
    String value();
  } 

Der Rückgabetyp der Methode bestimmt den Typ des Annotations-Elements und ist im begrenzten Rahmen wählbar. Ingesamt erlaubt die Definition:

gp  alle primitive Datentypen (byte, short, int, long, float, double, boolean), aber keine Wrapper
gp  String
gp  Class (inklusive Typ)
gp  Enum-Typen
gp  andere Annotationen (was zu geschachtelten Annotationen führt)

Felder von oben genannten Typen. Felder von Feldern (mehrdimensionale Felder) sind aber nicht gestattet:

Elemente, die so heißen wie die Methoden aus Object, sind nicht zugelassen.


Beispiel   Nutze den Annotations-Typ Optimize.
@Optimize(   "Schneller!"   )
public class DatabaseDAO
{
}

Die Elemente sind typisiert und notwendige Typkonvertierungen führt der Compiler automatisch durch. null ist als Argument nie erlaubt. Fehlt das erwartete Element, gibt es einen Compilerfehler.

@Optimize("Schneller!")      // OK
@Optimize()                  // Fehler
@Optimize(1)                 // Fehler
@Optimize("Wichtig: " + 1)   // OK
@Optimize(null)              // Fehler

Galileo Computing

21.6.3 Beliebige Schlüssel-/Werte-Paare  downtop

Wenn der Annotations-Typ ein Element value definiert, so muss keine Angabe über einen Schlüsselnamen gemacht werden, obwohl das möglich ist und schon das erste Beispiel für ein Schlüssel-/Werte-Paar darstellt:

@Optimize(   value = "Schneller!"   )

Eine Annotation lässt sich mit einer beliebigen Anzahl von Elementen definieren, und value ist nur dann nötig, wenn der Schlüssel nicht ausdrücklich genannt werden soll. Ist mehr als ein Element definiert, muss ohnehin der Elementname zusammen mit der Belegung genannt werden, und value könnte auch anders heißen.


Beispiel   Der Annotations-Typ Optimize soll zusätzlich zur Aufgabe auch denjenigen speichern, der die Aufgabe erledigen soll.
public @interface Optimize
{
  String   value  ();
  String   assignedTo  ();
}
Und in der Anwendung:
@Optimize(   value =   "schneller",   assignedTo =   "Christian" )
public class DatabaseDAO
{
} 

Unterschiedliche Typen der Elemente

String ist ein häufiger Datentyp für Elemente, doch auch die primitiven Datentypen – neben einigen anderen – sind erlaubt. Dazu einige Beispiele.


Beispiel   Die Optimierung hat eine bestimmte Priorität. Sie soll als int angegeben werden.
public @interface Optimize
{
  String value();
  String assignedTo();
    int    priority();
  }
In unserem Beispiel soll sie 5 sein:
@Optimize(
    value      = "schneller",
    assignedTo = "Christian",
      priority   = 5   )

Von den unterschiedlichen Element-Typen dürfen eindimensionale Fehler gebildet werden. Da es keine anderen Sammlungen gibt, stellt das Feld die einzige Möglichkeit dar, beliebig viele Elemente anzugeben.


Beispiel   Die Optimierungaufgabe soll nicht nur einer Person zugewiesen werden können, sonden mehrere. assignedTo muss eine Feld vom Typ String werden.
public @interface Optimize
{
  String   value();
    String[]   assignedTo();
  int      priority();
}
Interssanterweise muss die Zeile mit der Zuweisung an assignedTo nicht geändert werden, denn bei nur einem Element kann die Zuweisung so bleiben.
@Optimize(
    value      = "schneller",
      assignedTo = "Christian",
      priority   = 5 )
Bei mehreren Elementen wird ein paar geweifter Klammern verwendet.
    assignedTo =   { "Christian", "Jens" }  ,
Wenn kein Element aufgeführt wird, bleiben nur ein Paar Klammern für das leere Feld.
    assignedTo =   { }  ,

Neben Feldern sind Aufzählungen sehr nützlich. Sie machen Ganzzahlen als magische Zahlen für Konstanten überflüssig.

Definiert der Annotations-Typ ein Element vom Typ einer Aufzählung, so kann die Enum jede bekannte Aufzählung sein. Möglich ist auch, die Aufzählung innerhalb von @interface als inneren Typ zu definieren. Das ist besonders interessant, wenn die Aufzählung nicht allgemein ist, sondern ausschließlich Sinn mit einer Annotation ergibt.


Beispiel   Die Priorirät soll keine Ganzzahl, sondern eine innere Aufzählung sein.
public @interface Optimize
{
    enum Priority { LOW, NORM, HIGH }
    String   value();
  String[] assignedTo();
    Priority   priority();
}
Der innere Typ Priority ist über den äußeren Typ Optimize anzusprechen:
@Optimize(
    value      = "schneller",
    assignedTo = "Christian",
      priority   = Optimize.Priority.HIGH   )
Ist die Qualifizierung über die äußere Klasse nicht erwünscht, so ist eine import-Anweisung zu setzen. Etwa so:
import com.javatutor.insel.annotation. _saito_fett_ Optimize.Priority _saito_fettout_ ;
Das erlaubt priority = Priority.HIGH. Um auch noch Priority. einzusparen, muss ein statisches import gesetzt werden.

Der Element-Typ Class ist selten anzutreffen, doch mit den generischen Einschränkungen ermöglicht er eine präzise Klassenangabe. Häufiger ist hingegen eine Annotation in der Annotation zu finden, vor allem, weil keine beliebigen Typen als Element-Typen erlaubt sind. Die Annotation als Element-Typ löst dies auf schöne Weise.


Beispiel   Ein Annotations-Typ Name soll Vor- und Nachname speichern.

Listing 21.21   com/javatutor/insel/annotation/Name.java

package com.javatutor.insel.annotation;
public @interface Name
{
  String firstname();
  String lastname();
}
Der Annotations-Typ Author nutzt Name als Elementtyp für value.

Listing 21.22   com/javatutor/insel/annotation/Author.java

package com.javatutor.insel.annotation;
public @interface Author
{
    Name  [] value();
}
Vor Name steht nicht das @-Zeichen. Nur in der Anwendung:
@Author(   @Name( firstname = "Christian", lastname = "Ullenboom" )   )
Hätten wir das Element nicht value, sondern etwa name genannt, müsste die Angabe heißen:
name = @Name( firstname = "Christian", lastname = "Ullenboom" )
Und hätten wir mehrere Autoren, sprich Namen, angegeben, würden wir schreiben:
@Author(
    {
      @Name(firstname="Christian", lastname="Ullenboom"),
    @Name(firstname="Hansi", lastname="Hinterweltler")
    }   )


Galileo Computing

21.6.4 Vorbelegte Elemente  downtop

Im bisherigen Fall mussten alle Werte angegeben werden, und wir konnten kein Schlüssel-/Wertepaar auslassen. Die Annotations-Typen ermöglichen allerdings für Elemente Standardwerte, sodass ein Wert angeben werden kann, aber nicht muss. In der Syntax hält dafür das default her, was auch zu einer neuen Schreibweise führt, die von den Schnittstellen abweicht.


Beispiel   Für den Annotations-Typ Optimize sollen der Bearbeiter und die Priorität auf Standardwerte gesetzt werden.
package com.javatutor.insel.annotation;
public @interface Optimize
{
  enum Priority { LOW, NORM, HIGH }
  String   value();
  String[] assignedTo()   default ""  ;
  Priority priority()     default Priority.NORM  ;
}

Nachträgliche Änderung und die Sinnhaftigkeit von Standardwerten

Standardwerte sind für Annotationen ein sehr wichtiges Instrument, denn wenn einmal eine Annotation definiert wurde, ist eine Änderung nicht immer möglich; das Phänomen ist von Schnittstellen hinlänglich bekannt. Neben dem Hinzufügen neuer Elemente stellt bei Schnittstellen das Löschen von Operationen kein Problem dar – ganz im Unterschied zu Annotationen: Werden Elemente entfernt, gibt es Compilerfehler. Auch das Ändern von Element-Typen erntet im Allgemeinen Compilerfehler.

Werden neue Elemente in bestehende Annotations-Typen eingefügt, dann müssten alle existierenden konkreten Annotationen das neue Element setzen, was eine sehr große Änderung ist, vergleichbar einem neuen Element in einer Schnittstelle. Anders als bei Schnittstellen, lösen Default-Werte das Problem, da auf diese Weise für das neue Element immer gleich ein Wert vorhanden ist, der, sofern erwünscht, neu belegt werden kann. Ohne Probleme ist es möglich, einen Default-Wert hinzuzunehmen, während das Entfernen von Standardwerten kritisch ist.


Galileo Computing

21.6.5 Annotieren von Annotations-Typen  downtop

Von den in Java 5 eingeführten Annotationen haben wir die drei Typen aus dem Paket java.lang schon kennen gelernt. Die restlichen vier Annotationen aus dem Paket java.lang.annotation dienen dazu, Annotations-Typen zu annotieren. In diesem Fall wird von Meta-Annotationen gesprochen.


@Target Was lässt sich annotieren? Klasse, Methode, …?
@Retention Wo ist die Annotation sichtbar? Nur für den Compiler oder auch für die Laufzeitumgebung?
@Documented Der Wunsch wird angezeigt, die Annotation in der Dokumentation zu erwähnen.
@Inherited Macht deutlich, dass ein annotiertes Element auch in der Unterklasse annotiert ist.

@Target

Die Annotation @Target definiert, wo eine Annotation angeheftet werden kann. Die Aufzählung java.lang.annotation.ElementType definiert dazu die folgenden Ziele:

gp  ANNOTATION_TYPE
gp  CONSTRUCTOR
gp  FIELD
gp  LOCAL_VARIABLE
gp  METHOD
gp  PACKAGE
gp  PARAMETER
gp  TYPE

Ist kein ausdrückliches @Target gewählt, gilt es für alle Elemente.


Beispiel   Unsere Annotation Optimize soll vor beliebigen Typen, Methoden, Pakten und Konstruktoren erlaubt sein.
package com.javatutor.insel.annotation;
<span class="listing">import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
</span>  @Target( { TYPE, METHOD, CONSTRUCTOR, PACKAGE } )
  public @interface Optimize
{
  ...
}
Das erste import macht den Annotationstyp Target dem Compiler bekannt, und das zweite statische import macht die Aufzählung ElementType zugänglich.

Mit ElementType.TYPE ist die Annotation vor allen Typen – Klassen, Schnittstellen, Annotationen, Enums – erlaubt. Eine Einschränkung, etwa nur auf Klassen, ist nicht möglich. Interessant ist, dass eine Unterteilung für Methoden und Konstruktoren möglich ist und sogar lokale Variablen annotiert werden können.


Beispiel   Beim existierenden Annotations-Typ @Overrides ist die Annotation @Target schön zu erkennen.
  @Target( value=METHOD )
  public @interface Overrides
Die Idee der Meta-Annotation: Es gibt nur überschriebene Methoden.

@Retention

Die Annotation @Retention steuert, ob die Annotation nur für den Compiler oder ein Tool sichtbar ist, oder sich auch in der Klassendatei befinden soll. Die Unterscheidung haben die Java-Designer vorgesehen, da nicht automatisch jede Annotation zur Laufzeit verfügbar ist. (Eine Begründung ist, dass es sonst den Ressourcenverbrauch erhöhen würde.)

Es gibt drei Typen, die in der Aufzählung java.lang.annotation.RetentionPolicy genannt sind:

gp  SOURCE. Vom Compiler verworfen. Nützlich für Tools.
gp  CLASS. Abgebildet in der Klassendatei, aber von der JVM ignoriert.
gp  RUNTIME. Annotation wird gespeichert und ist für die JVM verfügbar.

Der Standard ist RetentionPolicy.CLASS.


Beispiel   Der Annotations-Typ @Deprecated ist nur für den Compiler von Interesse, und nicht zur Laufzeit.
@Retention(   value=SOURCE   )
public @interface Deprecated
Ist ein Element mit @Target annotiertet, so soll diese Information auch zur Laufzeit vorliegen.
@Retention(   value=RUNTIME   )
@Target( value=ANNOTATION_TYPE )
public @interface Target
Das Beispiel zeigt, dass die Anwendung auch rekursiv sein kann. (Natürlich auch indirekt rekursiv, denn nicht nur @Retention annotiert @Target, auch @Target annotiert @Retention.)

@Documented

Die Annotation @Documented zeigt an, dass die Annotation in der API-Dokumentation genannt werden soll. Alle Standard-Annotationen von Java werden so angezeigt, auch @Documented selbst. In der API-Dokumentation ist für die Annotationen ein neues Segment definiert.


Beispiel    @Documented ist selbst @Documented:
@Documented
@Target( value=ANNOTATION_TYPE )
public @interface Documented


Galileo Computing

21.6.6 Annotationen zur Laufzeit ausgelesen  downtop

Sollen Annotationen zur Laufzeit ausgelesen werden, so muss die @Retention mit RetentionPolicy.RUNTIME gesetzt sein. Damit ist unser Beispiel für den Annotations-Typ vollständig:


Beispiel    Eine mit @Optimize versehene Annotation soll zur Laufzeit erfragbar sein.

Listing 21.23   com/javatutor/insel/annotation/Optimize.java

package com.javatutor.insel.annotation;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Target( { TYPE, METHOD, CONSTRUCTOR, PACKAGE } )
  @Retention( RetentionPolicy.RUNTIME )
  public @interface Optimize
{
  enum Priority { LOW, NORM, HIGH }
  String   value();
  String[] assignedTo() default "";
  Priority priority()   default Priority.NORM;
}

Da nun unterschiedliche Dinge annotierbar sind, schreibt eine Schnittstelle AnnotatedElement für die Klasse Class, Constructor, Field, Method, Package und AccessibleObject folgende Methoden vor.


interface java.lang.reflect.  AnnotatedElement  

gp  <T extends Annotation> T getAnnotation( Class<T> annotationType ) Liefert die Annotation für einen bestimmten Typ. Ist sie nicht vorhanden, dann ist die Rückgabe null.
gp  boolean isAnnotationPresent( Class<? extends Annotation> annotationType ) Gibt es die angegebene Annotation?
gp  Annotation[] getAnnotations() Liefert die an dem Element festgemachten Annotationen. Gibt es keine Annotation, ist das Feld leer. Die Methode liefert auch Annotationen, die aus den Oberklassen kommen.
gp  Annotation[] getDeclaredAnnotations() Liefert die Annotationen, die exakt an diesem Element festgemacht sind.

Beispiel    Die Klasse DatabaseDAO ist nur mit @Optimize annotiert.
System.out.println( DatabaseDAO.class.isAnnotationPresent(Optimize.class) );    // true
System.out.println( DatabaseDAO.class.isAnnotationPresent(Documented.class) );  // false

Um auf die einzelnen Elemente einer Annotation zuzugreifen, müssen wir etwas mehr über die Umsetzung einer Annotation von Compiler und der JMV wissen. Übersetzt der Compiler einen Annotations-Typ, generiert er daraus eine Schnittstelle.


Beispiel    Für den Annotations-Typ Optimize generiert der Compiler:
public   interface Optimize   extends java.lang.annotation.Annotation
{
  public static final class Priority extends Enum { ... }
    String value();
  String[] assignedTo();
  Priority priority();
  }

Zur Laufzeit werden über java.lang.reflect.Proxy die Klassen gebaut, die die Annotationen repräsentieren. Rufen wir auf einem AnnotatedElement eine Methode wie getAnnotation() auf, bekommen wir das Proxy-Objekt, das vom Typ der Schnittstelle java.lang.annotation.Annotation ist.


Beispiel    Nutzen wir die Methode getAnnotations() vom DatabaseDAO.class-Objekt, bekommen wir das Proxy-Objekt, das in der toString()-Methode die Werte zeigt.
for ( Annotation a : Optimize.class.getAnnotations() )
 out.println( a ); // @com.javatutor.insel.annotation.Optimize(assignedTo=[Christian], 
                      priority=HIGH, value=Schneller!)
Default-Werte werden zur Laufzeit gesetzt. Eine Schleife über Documented.class.getAnnotations() liefert
@java.lang.annotation.Documented()
@java.lang.annotation.Retention(value=RUNTIME)
@java.lang.annotation.Target(value=[ANNOTATION_TYPE])

Völlig generisch lassen sich von der Annotations-Schnittstelle die Methoden aufzählen, doch im Allgemeinen dürfte der erwartete Typ bekannt sein. Die Typisierung von getAnnotation() ist dabei besonders hilfreich. Zur Wiederholung:

gp  <A extends Annotation> A getAnnotation( Class<A> annotationClass )

Das Argument ist ein Class-Objekt, welches den Annotations-Typ repräsentiert. Die Rückgabe ist genau die konkrete Annotation für das annotierte Element.


Beispiel    Die typisierte getAnnotation() liefert ein konkretes Annotations-Objekt:
  Optimize   annotation = DatabaseDAO.class.getAnnotation(  Optimize.class  );
System.out.println(   annotation.value()   );  // Schneller!

Die anderen Methoden getAnnotations() und getDeclaredAnnotations() liefern nicht so präzise Typen und nur ein Feld von Annotation-Objekten; hier muss eine Typanpassung bei der Auslesung helfen.


Beispiel    Teste die Annotationen auf einem Class-Objekt clazz. Im Fall der Optimize-Annotation sollen die drei assoziierten Elemente ausgegeben werden.

Listing 21.24   com/javatutor/insel/annotation/ReadAnnotations.java

for ( Annotation a : clazz.getAnnotations() )
    if ( a instanceof Optimize )
    {
      Optimize oa = (Optimize) a;
      out.println(   oa.value()   );                       // Schneller!
    out.println( Arrays.toString(  oa.assignedTo()  ) ); // [Christian]
    out.println(   oa.priority()   );                    // HIGH
  }

Die Annotation ist zur Laufzeit ein Proxy-Objekt und kann keine eigene Klasse erweitern und auch keine anderen eigenen Schnittstellen implementieren. Eine Annotation kann auch keine andere Annotation erweitern. Ebenso kann eine eigene Klasse zwar die Schnittstelle java.lang.annotation.Annotation implementieren, wird dadurch aber zu keiner Annotation, was eine eigene Implementierung sinnlos macht.


Galileo Computing

21.6.7 Mögliche Nachteile von Annotationen  toptop

Annotationen sind eine gewaltige Neuerung und sicherlich die wichtigste seit vielen Java-Jahren. Auch wenn die Generics auf den ersten Blick bedeutsam erscheinen, sind die Annotationen ein ganz neuer Schritt in die deklarative Programmierung, wie sie Frameworks heute schon aufzeigen. Völlig kritiklos sind Annotationen allerdings nicht. Der erste Problempunkt: Die Annotationen sind sehr stark mit dem Quellcode verbunden, können also auch nur dort geändert werden. Ist der Original-Quellcode einmal nicht verfügbar, etwa weil der Auftraggeber ihn geschlossen hält, ist eine Änderung der Werte nahezu unmöglich. Doch wenn Annotationen nach der Übersetzung nicht mehr geändert werden können, ist das bei externen Konfigurationsdateien kein Problem. Externe Dateien haben auch noch den Vorteil, dass die relevanten Informationen auf einem Blick erfassbar sind. In einer Konfigurationsdatei können Daten zentral stehen, die sich sonst redundant auf unterschiedliche Java-Klassen verteilten könnten.

Ein Problem gibt es allerdings bei finalen statischen Variablen (Konstanten), das bei den Default-Werten der Annotationen nicht vorkommt: Weil die Default-Werte zur Laufzeit gesetzt werden, lassen sie sich in der Definition vom Annotations-Typ leicht ändern, und eine Neuübersetzung des Projekts kann somit unterbleiben.

 << zurück




Copyright © Galileo Press GmbH 2005
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.


[Galileo Computing]

Galileo Press GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de