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.2 Mit dem Class-Objekt etwas über Klassen erfahren  downtop

Angenommen, wir wollen einen Klassen-Browser schreiben. Dieser soll alle zum laufenden Programm gehörenden Klassen und darüber hinaus weitere Informationen wie Variablenbelegung, definierte Methoden, Konstruktoren und Informationen über die Vererbungshierarchie anzeigen. Dafür benötigen wir die Bibliotheksklasse Class. Exemplare der Klasse Class sind Objekte, die jeweils eine Java-Klasse repräsentieren. In diesem Punkt unterscheidet sich Java von vielen herkömmlichen Programmiersprachen wie C++, da bei Java Eigenschaften von Klassen vom gerade laufenden Programm mittels der Class-Objekte abgefragt werden können. Bei den Exemplaren von Class handelt es sich um eine eingeschränkte Form von Metaobjekten – die Beschreibung einer Java-Klasse, die aber nur ausgewählte Informationen preisgibt. Neben normalen Klassen werden auch Schnittstellen durch je ein Class-Objekt repräsentiert.


Galileo Computing

21.2.1 An ein Class-Objekt kommen  downtop

Zunächst müssen wir für eine bestimmte Klasse das zugehörige Class-Objekt in Erfahrung bringen. Dies ist auf mehreren Wegen möglich:

gp  Ist ein Exemplar der Klasse verfügbar, rufen wir die getClass()-Methode des Objekts auf, und erhalten das Class-Exemplar der zugehörigen Klasse. Dies ist praktisch, wenn der Typ des Objekts nicht genau bekannt ist.
gp  Haben wir bereits ein Class-Objekt, sind aber nicht an ihm, sondern an dessen Vorfahren interessiert, so können wir einfach mit getSuperclass() ein Class-Objekt für die Oberklasse erhalten.
gp  Jede Klasse enthält eine Klassenvariable mit Namen .class vom Typ Class, die auf das zugehörige Class-Exemplar verweist. Auch auf primitiven Datentypen ist das Ende .class erlaubt. Genauso ist es mit dem Zugriff auf die statische Variable TYPE der Wrapper-Klassen.
gp  Eine Klasse kann auch über ihren Namen über die Klassenmethode Class.forName(String) angesprochen werden. Dadurch wird eine Klasse mit dem angegebenen Namen geladen, und wir erhalten das zugehörige Class-Exemplar als Ergebnis.

Beispiel   Wie wir von einem bekannten Objekt ein Class-Objekt für dessen Klasse bekommen.
Class c = derGroßeUnbekannte.getClass();


class java.lang.  Object  

gp  final Class<? extends Object> getClass() Liefert zur Laufzeit das Class-Exemplar, das die Klasse des Objekts repräsentiert.

Wissen wir, dass die Klasse DerKleineBekannte im Paket mein.dein liegt, so nutzen wir die Endung .class am Klassennamen zum Erlangen des Klassenobjekts:

Class c = mein.dein.DerKleineBekannte.class;

Haben wir zur Laufzeit die Variable klassenName mit »java.util.Date« initialisiert, so erhalten wir das Class-Exemplar für die Datumsklasse durch

Class c = Class.forName( klassenName );

Dies ist oft nur dann sinnvoll, wenn der Klassenname bei der Übersetzung des Programms noch nicht feststand. Sonst ist die vorhergehende Technik eingängiger.


Beispiel   Drei Möglichkeiten, um an das Class-Objekt heranzukommen, werden aufgezeigt.

Listing 21.1   GetClassObject.java

public class GetClassObject
{
  public static void main( String[] args )
  {
    Class c1 = java.util.Date.class;
    Class c2 = new java.util.Date().getClass();
    Class c3 = null;
    try
    {
       c3 = Class.forName( "java.util.Date" );
    }
    catch ( ClassNotFoundException e ) { e.printStackTrace(); }
    System.out.println( c1 );
    System.out.println( c2 );
    System.out.println( c3 );
  }
}

Beispiel   Klassenobjekte für primitve Elemente liefert forName() nicht! Die Anweisungen:
Class.forName( "boolean" );
Class.forName( boolean.class.getName() );
führen zu einer java.lang.ClassNotFoundException.


final class java.lang.  Class<T>
  implements Serializable, GenericDeclaration, Type, AnnotatedElement

gp  static Class<?> forName( String className ) throws ClassNotFoundException Liefert das Class-Exemplar für die Klasse oder Schnittstelle mit dem angegebenen vollqualifizierten Namen. Die benötigte Klasse wird, falls sie bisher noch nicht vom Programm benötigt wurde, vom Klassenlader gesucht und geladen. Die Methode liefert niemals null zurück. Falls die Klasse nicht geladen und eingebunden werden konnte, gibt es eine ClassNotFoundException. Eine alternative Methode forName() ermöglicht auch das Laden mit einem gewünschten Klassenlader.

Probleme für Obfuscator

Dass der Compiler automatisch Bytecode gemäß dieses veränderten Quellcodes erzeugt, führt nur dann zu unerwarteten Problemen, wenn wir einen Obfuscator über den Programmtext laufen lassen, der nachträglich den Bytecode modifiziert und damit die Bedeutung des Programms beziehungsweise des Bytecodes verschleiert und dabei Klassen umbenennt. Offensichtlich darf ein Obfuscator Klassen, deren Class-Exemplare abgefragt werden, nicht umbenennen; oder der Obfuscator müsste die entsprechenden Zeichenketten ebenfalls korrekt ersetzen (aber natürlich nicht alle Zeichenketten, die zufällig mit Namen von Klassen übereinstimmen).


Galileo Computing

21.2.2 Was das Class-Objekt beschreibt  downtop

Ein Class-Exemplar kann eine Schnittstelle, eine Klasse, einen primitiven Datentyp oder auch einen Array-Typ beschreiben. Dies lässt sich durch die drei Methoden isInterface(), isPrimitive() und isArray() herausfinden. Wenn keine der drei Methoden für ein Class-Exemplar true liefert, repräsentiert das Objekt eine gewöhnliche Klasse.

Dass es auch Class-Exemplare gibt, die die primitiven Datentypen von Java beschreiben, erstaunt zunächst. Damit ist es jedoch möglich, die Parameter- und Ergebnistypen beliebiger Java-Methoden einheitlich durch Class-Exemplare zu beschreiben. Dazu kodiert jede der acht Wrapper-Klassen, die zu den Datentypen boolean, byte, char, short, int, long, float und double gehören, und die spezielle Klasse für den Typ void eine Konstante TYPE. Benötigen wir ein Class-Objekt für den primitiven Typ int, so greifen wir mit Integer.TYPE (oder alternativ mit int.class) darauf zu. Alle Class-Exemplare für primitive Datentypen werden automatisch von der JVM erzeugt. Die Methode isPrimitve() gibt genau für diese neun besonderen Class-Exemplare true zurück, sodass sie von Repräsentanten für echte Klassen unterschieden werden können.


Hinweis   Obwohl void kein Typ ist, meldet isPrimitive() dies.
System.out.println( void.class.isPrimitive() );  // true

Das folgende Programmstück testet die Attribute von Class-Objekten systematisch durch. Wir benutzen die Methode getName(), um den Namen des Class-Objekts auszugeben. Im nächsten Abschnitt mehr dazu. Das Class-Objekte für Felder setzt sich aus dem Basistyp und Paaren von eckigen Klammern zusammen, etwa double[][].class.

Listing 21.2   CheckClassType.java

import java.util.*;
class CheckClassType
{
  public static void main( String[] args )
  {
    Class observer = Observer.class;
    Class observable = Observable.class;
    Class array = (new int[2][3][4]).getClass();
    Class primitive = Integer.TYPE;
    checkClassType( observer );
    checkClassType( observable );
    checkClassType( array );
    checkClassType( primitive );
  }
  static void checkClassType( Class c )
  {
    if (   c.isArray()   )
      System.out.println(   c.getName()   + " ist ein Array." );
    else if (   c.isPrimitive()   )
      System.out.println( c + " ist ein primitiver Datentyp.");
    else if (   c.isInterface()   )
      System.out.println( c.getName() + " ist eine Schnittstelle." );
    else
      System.out.println( c.getName()+ " ist eine Klasse." );
   }
}

Die Ausgabe des Programms ist nun:

java.util.Observer ist eine Schnittstelle.
java.util.Observable ist eine Klasse.
[[[I ist ein Array.
int ist ein primitiver Datentyp.

final class java.lang.  Class<T>
  implements Serializable, GenericDeclaration, Type, AnnotatedElement

gp  boolean isInterface() Liefert true, wenn das Class-Objekt eine Schnittstelle beschreibt.
gp  boolean isArray() Liefert true, wenn das Class-Objekt einen Array-Typ beschreibt.
gp  boolean isPrimitive() Testet, ob das Class-Objekt einen primitiven Datentyp beschreibt.

Komponententyp bei Feldern

Die Methode getComponentType() liefert bei Feldern den Typ der Elemente.

System.out.println( double[].class.getComponentType() );    // double
System.out.println( double[][].class.getComponentType() );  // class [D
System.out.println( double.class.getComponentType() );      // null

Galileo Computing

21.2.3 Der Name der Klasse  downtop

Liegt zu einer Klasse das Class-Objekt vor, so können wir zur Laufzeit ihren voll qualifizierten Namen über die Methode getName() ausgeben. Da jede Klasse und auch jede Schnittstelle über einen Namen verfügt, führt diese Funktion also jedes Mal zum Ziel. Das nachfolgende Programm macht sich dies zunutze und erzeugt die Ausgabe java.util.Date.

Listing 21.3   SampleName.java

import java.util.Date;
class NameOfTheClass
{
  public static void main( String[] args )
  {
    Date d   = new Date();
    Class c  = d.getClass();
    String s = c.  getName()  ;
    System.out.println( s );    // java.util.Date
  }
}

In dem Beispiel ist der voll qualifizierte Name java.util.Date noch einfach wiederzuerkennen.

Kodierung von Feldern

Schwieriger wird die Kodierung bei Array-Typen, die ja eine besondere Form von Klassen sind. getName() kodiert sie mit einem führenden »[«. Jede Klammer steht dabei für eine Dimension des Array-Typs. Nach den Klammern folgt in einer kodierten Form der Typ der Array-Elemente. So liefert

System.out.println( int[][][].class.getName() );                // [[[I
System.out.println( (new int[2][3][4]).getClass().getName() );  // [[[I

den String »[[[I«, also einen dreidimensionalen Array-Typ mit Array-Elementen vom primitiven Typ int. Der Elementtyp wird wie folgt kodiert.


Tabelle 21.1   Kodierung der Elementtypen

Kürzel Datentyp
B Byte
C Char
D Double
F Float
I Int
J Long
LElement-Typ; Klasse oder Schnittstelle
S Short
Z Boolean

Nimmt das Array Objektreferenzen auf, wird deren Typ in der Form »LKlassenname;« kodiert. So liefert (new Object[3]).getClass().getName() den String »[Ljava.lang.Object;«. Der Klassen- beziehungsweise Schnittstellenname ist dabei wieder voll qualifiziert.

Der String ist auch für Class.forName() von Bedeutung. Im Fall von Arrays liefert die Methode ein Class-Objekt für den Elementtyp. Die ersten Versuche, um ein Class-Objekt für Felder zu beziehen, scheitern an einer ClassNotFoundException:

Class.forName( "String[]" );
Class.forName( "java.lang.String[]" );

Im der ersten Anweisung in der Klassenname nicht voll qualifiziert und auch dann, in der zweiten Anweisung, ist der String falsch aufgebaut.

System.out.println( Class.forName("[Ljava.lang.String;") ); // class [Ljava.lang.String;

Steht die Frage an, ob ein Class-Objekt für ein Feld von Objekten steht oder für ein primitives Feld, lässt sich das Ergebnis von getName() auswerten.

public static boolean isObjectArray( Class clazz )
{
  if ( clazz != null && clazz.isArray() )
    return clazz.getName().startsWith( "[L" );
  return false;
}

So liefert

System.out.println( isObjectArray( Object[].class ) );       // true
System.out.println( isObjectArray( int[].class ) );          // false
System.out.println( isObjectArray( Object.class ) );         // false

toString()

Auch eine zweite Methode ist uns bekannt, um Class-Exemplare für Menschen lesbar auszugeben: die Methode toString(). Sie basiert im Kern auf getName(), fügt aber zusätzlich die Art der repräsentierten Klasse (normale Klasse, Schnittstelle oder primitiver Datentyp) ein:

public String toString() {
  return (isInterface() ? "interface " :
     (isPrimitive() ? "" : "class ")) + getName();
}

final class java.lang.  Class<T>
  implements Serializable, GenericDeclaration, Type, AnnotatedElement

gp  String getName() Liefert für ein Class-Exemplar als String den voll qualifizierten Namen der repräsentierten Klasse oder Schnittstelle beziehungsweise des repräsentierten Array-Typs oder primitiven Datentyps.
gp  String toString() Liefert eine für Menschen lesbare String-Repräsentation des Class-Objekts.

Galileo Computing

21.2.4 Die Arbeit auf dem Feld  downtop

Die Utility-Klasse java.lang.reflect.Array stellt Methoden bereit, um auf Array-Objekten generisch zu arbeiten. Elemente lassen sich erfragen und setzen und auch Arrays mit einem gewünschten Typ anlegen.

Object array =   Array.newInstance  ( int.class20 );
System.out.println(   Array.getLength  (array) );  // 20
  Array.setInt  ( array0, –1 );
System.out.println(   Array.getInt  (array0) );     // –1

Bei newInstance() ist der Typ int.class, und nicht int[].class!

Eine allgemeine Funktion set() und get() arbeitet für Objekte, wobei auch Wrapper für primitive Felder verwendet werden können.

  Array.set  ( array0Integer.valueOf(-1) );
System.out.println(   Array.get  (array0) );     // –1

Für mehrdimensionale Felder lässt sich bei newInstance() ein Feld von Größen angeben:

Object array =   Array.newInstance  ( int.class,   new int[] {22}   );
((int[][])array)[0][0] = 1;
((int[][])array)[1][1] = 1;
System.out.println( Arrays.deepToString( (int[][])array ) ); // [[1, 0], [0, 1]]

Galileo Computing

21.2.5 instanceof mit Class-Objekten  downtop

Wir konnten den binären Operator instanceof nutzen, um zu testen, ob ein Objekt Exemplar einer Klasse ist und einen bestimmten Typus besitzt. Die Klasse muss jedoch zur Übersetzungszeit bekannt sein und kann nicht dynamisch festgelegt werden. Um zu testen, ob ein Objekt Exemplar eines bekannten Class-Objekts ist, lässt sich eine Methode vom Class-Objekt nutzen: isAssignableFrom(Class).

public static boolean dynInstanceof( Object oClass c )
{
  return c.isAssignableFrom( o.getClass() );
}

Die Methode befragt genau genommen gar nicht die Hierarchie zwischen Objekt und Klasse, sondern die zwischen zwei Klassen. Unsere Funktion dynInstanceof() nutzt dies jedoch, um zu testen, ob ein Objekt einen bestimmten Typ annimmt.

JButton b = new JButton();
System.out.println( dynInstanceof( bJButton.class)  );                        // true
System.out.println( dynInstanceof( bObject.class)  );                         // true
System.out.println( dynInstanceof( bClass.forName("java.awt.Component") ) );  // true
System.out.println( dynInstanceof( bString.class)  );                         // false

Solange der Klassenname zur Übersetzungszeit bekannt ist, ist instanceof die beste Lösung. Doch wenn die Klasse nur durch ein Class-Objekt gegeben ist, bleibt immer noch isAssignableFrom().


Galileo Computing

21.2.6 Oberklassen finden  downtop

Das Class-Exemplar für eine Klasse speichert die Position der Klasse in der Vererbungshierarchie, die Sichtbarkeitsstufe der Klasse und weitere Informationen. Um die Oberklasse zu ermitteln, wird getSuperclass() verwendet. Die Methode gibt null zurück, falls das Class-Objekt eine Schnittstelle repräsentiert oder wir schon am oberen Ende der Hierarchie sind, also bei dem Class-Objekt für die Wurzelklasse Object. Das folgende Programm findet alle Oberklassen einer Klasse durch den wiederholten Aufruf der Methode getSuperclass().

Listing 21.4   ShowSuperclasses.java

class ShowSuperclasses
{
  public static void main( String[] args )
  {
    printSuperclasses( new javax.swing.JButton() );
  }
  static void printSuperclasses( Object o )
  {
    Class<?> subclass   = o.getClass();
    Class    superclass = subclass.  getSuperclass()  ;
    while ( superclass != null ) {
      String className = superclass.getName();
      System.out.println( className );
      subclass = superclass;
      superclass = subclass.  getSuperclass()  ;
    }
  }
}

Wahrscheinlich wäre eine rekursive Variante noch eleganter, aber darauf kommt es jetzt nicht an.

javax.swing.AbstractButton
javax.swing.JComponent
java.awt.Container
java.awt.Component
java.lang.Object

final class java.lang.  Class<T>
  implements Serializable, GenericDeclaration, Type, AnnotatedElement

gp  Class<? super T> getSuperclass() Liefert ein Class-Exemplar für die Oberklasse der durch das aufrufende Class-Objekt repräsentierten Klasse. Falls wir schon oben auf der Vererbungshierarchie bei Object sind oder nach der Oberklasse einer Schnittstelle fragen, liefert die Funktion null.

Galileo Computing

21.2.7 Implementierte Interfaces einer Klasse oder eines Inferfaces  downtop

Klassen stehen zum einen in einer Vererbungsbeziehung zu einer Oberklasse und können zum anderen mehrere Schnittstellen implementieren. Schnittstellen können ihrerseits wiederum andere Schnittstellen erweitern. In einer Klassendefinition folgt direkt hinter dem Schlüsselwort implements eine Auflistung der implementierten Schnittstellen. So implementiert die Klasse RandomAccessFile die Schnittstellen DataOutput, DataInput und Closeable.

public class RandomAccessFile implements DataOutputDataInputCloseable

Um zu einem vorhandenen Class-Objekt die Schnittstellen aufzulisten, rufen wir getInterfaces() auf, die uns ein Array von Class-Objekten liefert. Von hier aus kennen wir den Weg zum Namen. Der Aufruf von getName() liefert den String für den Namen der Schnittstelle. Wir bleiben bei unserem Beispiel und entwickeln ein kleines Programm, das die implementierten Schnittstellen ausgibt.

Listing 21.5   ShowInterfaces.java

public class ShowInterfaces
{
  public static void main( String[] args )
  {
    printInterfaceNames( java.io.RandomAccessFile.class );
  }
  static void printInterfaceNames( Class clazz )
  {
    for ( Class theInterface: clazz.  getInterfaces()   )
      System.out.println( theInterface.getName() );
  }
}

Die Ausgabe ist dann:

java.io.DataOutput
java.io.DataInput
java.io.Closeable

Galileo Computing

21.2.8 Modifizierer und die Klasse Modifier  downtop

Eine Klassendeklaration kann Modifizierer, also Schlüsselwörter, die zum Beispiel die Sichtbarkeit bestimmen, enthalten. Unter anderem sind dies public, protected, private und final. Sie stehen etwa in der Klassendeklaration vor dem Schlüsselwort class oder auch vor Methoden. Die Modifizierer können auch kombiniert werden: so ist die Klasse Class selbst public final. Um an die Modifizierer zu gelangen, wird die Methode getModifiers() verwendet, dann sind die Sichtbarkeiten als Ganzzahl im Rückgabewert verschlüsselt.

System.out.println( Modifier.class.getModifiers() );                    // 1
System.out.println( Modifier.toString(Modifier.class.getModifiers()) ); // public

final class java.lang.  Class<T>
  implements Serializable, GenericDeclaration, Type, AnnotatedElement

gp  int getModifiers() Liefert die Modifizierer für eine Klasse oder eine Schnittstelle.

Damit wir uns bei der Entschlüsselung nicht mit magischen Zahlenwerten der JVM herumschlagen müssen, gibt es in der Klasse java.lang.reflect.Modifier einige statische Methoden, die diese Ganzzahl testen. Zudem werden Konstanten definiert (wie Modifier.PUBLIC), mit denen dann dieser Integerwert kaum verglichen werden kann. Da die Ganzzahl potenziell eine Kombination mehrerer Modifizierer kodiert, ist die gezielte Abfrage allerdings mit den isXXX()-Methoden einfacher. Obwohl eine Klasse nicht transient, synchronized, nativ sein kann, listen wir hier alle Methoden auf, da wir diese Modifizierer später auch für die Untersuchung von Methoden und Objekt- beziehungsweise Klassenvariablen per Reflection einsetzen. Jede dieser Testmethoden liefert true, wenn der gefragte Modifizierer in dem kodierten Ganzzahlwert enthalten ist. Alle Methoden sind static und liefern ein boolean-Ergebnis, außer toString().


class java.lang.reflect.  Modifier  

gp  static boolean isAbstract( int mod )
gp  static boolean isFinal( int mod )
gp  static boolean isInterface( int mod )
gp  static boolean isNative( int mod )
gp  static boolean isPrivate( int mod )
gp  static boolean isProtected( int mod )
gp  static boolean isPublic( int mod )
gp  static boolean isStatic( int mod )
gp  static boolean isSynchronized( int mod )
gp  static boolean isTransient( int mod )
gp  static boolean isVolatile( int mod )

Beispiel   Wir betrachten die toString()-Methode der Klasse Modifier. Dort finden wir eine Liste aller möglichen Modifizierer mit den Konstanten.

public static String toString( int mod )
{
  StringBuffer sb = new StringBuffer();
  int len;
  if ((mod & PUBLIC) != 0) sb.append("public ");
  if ((mod & PRIVATE) != 0) sb.append("private ");
  if ((mod & PROTECTED) != 0) sb.append("protected ");
  /* Canonical order */
  if ((mod & ABSTRACT) != 0) sb.append("abstract ");
  if ((mod & STATIC) != 0) sb.append("static ");
  if ((mod & FINAL) != 0) sb.append("final ");
  if ((mod & TRANSIENT) != 0) sb.append("transient ");
  if ((mod & VOLATILE) != 0) sb.append("volatile ");
  if ((mod & NATIVE) != 0) sb.append("native ");
  if ((mod & SYNCHRONIZED) != 0) sb.append("synchronized ");
  if ((mod & INTERFACE) != 0) sb.append("interface ");
  if ((len = sb.length()) > 0)/* trim trailing space */
    return sb.toString().substring(0len-1);
  return "";
}

Hinweis    Schnittstellen, wie java.io.Serializable, tragen den Modifier »abstract«.
out.println( Serializable.class.getModifiers()); // 1537
out.println( Modifier.toString(Serializable.class.getModifiers())); // public abstract interface


Galileo Computing

21.2.9 Die Attribute einer Klasse  downtop

Besonders bei Klassen-Browsern oder GUI-Buildern wird es interessant, auf die Variablen eines Objekts zuzugreifen, das heißt, ihre Werte auszulesen und zu verändern. Damit wir an beschreibende Objekte für die in einer Klasse definierten beziehungsweise aus Oberklassen geerbten Variablen gelangen, rufen wir die Methode getFields() für das Class-Objekt der interessierenden Klasse auf. Als Ergebnis erhalten wir ein Array von Field-Objekten. Jeder Array-Eintrag beschreibt eine Objekt- oder Klassenvariable, auf die wir zugreifen dürfen. Nur auf öffentliche, also public-Elemente, haben wir per (gewöhnlicher) Reflection Zugriff. (Auf eine privilegierte Reflection gehen wir hier nicht ein.) Schnittstellen definieren ja bekanntlich nur Konstanten. Somit ist der schreibende Zugriff, den wir später näher betrachten wollen, nur auf in Klassen definierte Variablen beschränkt. Lesen ist natürlich bei Konstanten und Variablen gleichermaßen erlaubt. Beim Zugriff auf die Attribute mittels getFields() müssen wir aufpassen, dass wir uns keine SecurityException einfangen. Das kann uns aber bei vielen Methoden passieren, und weil SecurityException eine RuntimeException ist, muss sie auch nicht extra aufgefangen werden. In der Dokumentation ist sie daher nicht angegeben.


final class java.lang.  Class<T>
  implements Serializable, GenericDeclaration, Type, AnnotatedElement

gp  Field[] getFields() Liefert ein Array mit Field-Objekten. Die Einträge sind unsortiert. Das Array hat die Länge 0, wenn die Klasse beziehungsweise Schnittstelle keine öffentlichen Variablen definiert oder erbt. getFields() liefert automatisch auch Einträge für die aus Oberklassen beziehungsweise Schnittstellen geerbten öffentlichen Variablen.
gp  Field getField( String name ) throws NoSuchFieldException Erfragt ein bestimmtes Feld.

Die Klasse Field implementiert im Übrigen das Interface Member und ist eine Erweiterung von AccessibleObject. AccessibleObject ist die Basisklasse für Field-, Method- und Constructor-Objekte. Auch Method und Constructor implementieren das Interface Member, welches zur Identifikation über getName() oder getModifiers() dient. Zusätzlich liefert getDeclaringClass() das Class-Objekt, das tatsächlich eine Variable oder Methode definiert. Da geerbte Elemente in der Aufzählung mit auftauchen, ist dies der einzige Weg, um die Position der Definition in der Vererbungshierarchie exakt zu bestimmen.

Das Field-Objekt lässt sich vieles fragen: nach dem Namen des Attributs, nach dem Datentyp und auch wieder nach den deklarierten Modifizierern. Werfen wir einen Blick auf die toString()-Methode der Klasse Field:

public String toString() {
  int mod = getModifiers();
  return (((mod == 0) ? "" : (Modifier.toString(mod) + " "))
      + getTypeName(getType()) + " "
      + getTypeName(getDeclaringClass()) + "."
      + getName());
}

Abbildung
Hier klicken, um das Bild zu Vergrößern


final class java.lang.reflect.  Field
  extends AccessibleObject
implements Member

gp  Class<?> getDeclaringClass() Liefert das Class-Exemplar für die Klasse oder die Schnittstelle, in der die Variable definiert wurde. Diese Methode ist Teil der Schnittstelle Member.
gp  int getModifiers() Liefert die deklarierten Modifizierer für die Variable.
gp  String getName() Liefert den Namen der Variablen. Diese Methode ist Teil der Schnittstelle Member.
gp  Class<?> getType() Liefert ein Class-Objekt, das dem Datentyp der Variablen entspricht.
gp  String toString() Liefert eine String-Repräsentation. Zuerst wird der Zugriffsmodifizierer (public, protected oder private) mit weiteren Modifizierern (static, final, transient, volatile) ausgegeben. Dann kommt der Datentyp, gefolgt vom voll qualifizierten Namen der definierenden Klasse und schließlich der Name der Variablen.

Um für eine Klasse alle Objekt- und Klassenvariablen mit ihren Datentypen herauszufinden, müssen wir lediglich eine Schleife über das Attribut-Array laufen lassen. Die Namen der Variablen finden sich leicht mit getName(). Wir haben aber den zugehörigen Datentyp noch nicht. Dazu müssen wir erst mit getType() ein Class-Objekt für den Typ ermitteln, und dann liefert uns getName() eine String-Repräsentation des Typs.

Listing 21.6   ShowFields.java

import java.lang.reflect.*;
class ShowFields
{
  public static void main( String[] args )
  {
    printFieldNames( new java.text.SimpleDateFormat() );
  }
  static void printFieldNames( Object o )
  {
    System.out.println( "class " + o.getClass().getName() + " {" );
    for ( Field publicField : o.getClass().getFields() )
    {
      String fieldName = publicField.getName();
      Class typeClass = publicField.getType();
      String fieldType = typeClass.getName();
      System.out.println( "  " + fieldType + " " + fieldName + ";" );
//      System.out.println( "  " + publicField );
    }
    System.out.println( "}" );
  }
}

Dies ergibt die (gekürzte) Ausgabe:

class java.text.SimpleDateFormat {
  int ERA_FIELD;
  int YEAR_FIELD;
  ...
  int SHORT;
  int DEFAULT;
}

Kürzer und noch ausführlicher geht es mit der toString()-Methode. So liefert

for ( Field publicField : o.getClass().getFields() )
  System.out.println( "  " + publicFields );

etwa

class java.text.SimpleDateFormat {
  public static final int java.text.DateFormat.ERA_FIELD
  public static final int java.text.DateFormat.YEAR_FIELD
  ..
  public static final int java.text.DateFormat.SHORT
  public static final int java.text.DateFormat.DEFAULT
}

Galileo Computing

21.2.10 Methoden einer Klasse erfragen  downtop

Um herauszufinden, über welche Methoden eine Klasse verfügt, wenden wir eine ähnliche Vorgehensweise an wie bei den Variablen: getMethods(). Diese Methode liefert ein Array mit Method-Objekten. Über ein Method-Objekt lassen sich Methodenname, Ergebnistyp, Parametertypen, Modifizierer und eventuell resultierende Exceptions erfragen. Wir werden später sehen, dass sich die durch ein Method-Exemplar repräsentierte Methode über invoke() aufrufen lässt.


Hinweis   Auch wenn zwei Klassen die gleiche Methode besitzten, muss doch ein Method-Objekt immer für jede Klasse erfragt werden. Method-Objekte sind immer mit dem Class-Objekt verbunden.


final class java.lang.  Class<T>
  implements Serializable, GenericDeclaration, Type, AnnotatedElement

gp  Method[] getMethods() Gibt ein Array von Method-Objekten zurück, die alle öffentlichen Methoden der Klasse/Schnittstelle beschreiben. Geerbte Methoden werden mit in die Liste übernommen. Die Elemente sind nicht sortiert, und die Länge des Arrays ist null, wenn es keine öffentlichen Methoden gibt.
gp  Method getMethod( String name, Class... parameterTypes ) throws NoSuchMethodException Liefert zu einem Methodennamen und Parameterliste das passende Method-Objekt oder löst eine NoSuchMethodException aus. Besitzt die Methode keine Parameter –wie eine übliche getXXX()-Methode –, ist das Argument null und wird wegen der Varags auf Class[] angepasst.

Nachdem wir nun mittels getMethods() ein Array von Method-Objekten erhalten haben, lassen die Method-Objekte verschiedene Abfragen zu. So liefert getName() den Namen der Methode, getReturnType() den Ergebnistyp, und getParameterTypes() erzeugt ein Array von Class-Objekten, das die Typen der Methodenparameter widerspiegelt. Wir kennen dies schon von den Attributen.

Abbildung
Hier klicken, um das Bild zu Vergrößern


final class java.lang.reflect.  Method
  extends AccessibleObject
implements GenericDeclaration, Member

gp  Class<?> getDeclaringClass() Liefert das Class-Exemplar für die Klasse oder die Schnittstelle, in der die Methode definiert wurde. Diese Methode ist Teil der Schnittstelle Member.
gp  String getName() Liefert den Namen der Methode. Diese Methode ist Teil der Schnittstelle Member.
gp  int getModifiers() Liefert die Modifizierer. Diese Methode ist Teil der Schnittstelle Member.
gp  Class<?> getReturnType() Gibt ein Class-Objekt zurück, das den Ergebnistyp beschreibt.
gp  Class<?>[] getParameterTypes() Liefert ein Array von Class-Objekten, die die Typen der Parameter beschreiben. Die Reihenfolge entspricht der deklarierten Parameterliste. Das Array hat die Länge null, wenn die Methode keine Parameter erwartet.
gp  Class<?>[] getExceptionTypes() Liefert ein Array von Class-Objekten, die mögliche Exceptions beschreiben. Das Array hat die Länge null, wenn die Methode keine solchen Exceptions mittels throws deklariert. Das Feld spiegelt nur die throws-Klausel wider. Sie kann prinzipiell auch zu viele Exceptions enthalten, bei einer Funktion foo() throws RuntimeException, NullPointerException etwa genau die beiden Ausnahmen.
gp  String toString() Liefert eine String-Repräsentation der Methode, ähnlich dem Methodenkopf in einer Deklaration.

Wir wollen nun ein Programm schreiben, das zusätzlich zu den Parametertypen die Namen erfragt.

Listing 21.7   ShowMethods

import java.lang.reflect.*;
class ShowMethods
{
  public static void main( String[] args )
  {
    showMethods( java.awt.Color.BLACK );
  }
  static void showMethods( Object o )
  {
    for ( Method method : o.getClass().getMethods() )
    {
      // Rückgabetyp
      String returnString = method.getReturnType().getName();
      // Methodenname
      String methodString = method.getName();
      System.out.print( returnString + " " + methodString + "(" );
      // Parameter
      Class[] parameterTypes = method.getParameterTypes();
      for ( int k = 0; k < parameterTypes.length; k++ ) {
        String parameterString = parameterTypes[k].getName();
        System.out.print( " " + parameterString );
        if ( k < parameterTypes.length – 1 )
          System.out.print( ", " );
      }
      System.out.print( " )" );
      // Exceptions
      Class[] exceptions = method.getExceptionTypes();
      if ( exceptions.length > 0 ) {
        System.out.print( " throws " );
        for ( int k = 0; k < exceptions.length; k++ ) {
          System.out.print( exceptions[k].getName() );
          if ( k < exceptions.length – 1 )
            System.out.print( ", " );
        }
      }
      System.out.println();
    }
  }
}

Die Ausgabe sieht gekürzt so aus:

int hashCode( )
boolean equals( java.lang.Object )
java.lang.String toString( )
...
[F getRGBColorComponents( [F )
...
void wait( long ) throws java.lang.InterruptedException
void notify( )
void notifyAll( )

Wir bemerken an einigen Stellen eine kryptische Notation, wie etwa »[F«. Dies ist aber lediglich wieder die schon erwähnte Kodierung für Array-Typen. So gibt getRGB-Components() ein float-Array zurück und erwartet ein float-Array als Argument.


Galileo Computing

21.2.11 Konstruktoren einer Klasse  downtop

Konstruktoren und Methoden haben einige Gemeinsamkeiten, unterscheiden sich aber insofern, als Konstruktoren keinen Rückgabewert haben. Die Ähnlichkeit zeigt sich auch in der Methode getConstructors(), die ein Array von Constructor-Objekten zurückgibt. Über dieses Array lassen sich dann wieder Name, Modifizierer, Parameter und Exceptions der Konstruktoren einer Klasse erfragen. Wie wir an einer späteren Stelle sehen werden, lassen sich auch über die Methode newInstance() neue Objekte erzeugen. Wegen der weitgehenden Ähnlichkeit der Klassen Constructor und Method sind die folgenden Methoden hier nicht näher beschrieben.


final class java.lang.  Class<T>
  implements Serializable, GenericDeclaration, Type, AnnotatedElement

gp  Constructor[] getConstructors() Liefert ein Feld mit Constructor-Objekten.
gp  Constructor<T> getConstructor(Class... parameterTypes)   throws NoSuchMethodException Liefert ein ausgwähltes Constructor-Objekt.

final class java.lang.reflect.  Constructor<T>
  extends AccessibleObject
implements GenericDeclaration, Member

gp  Class<T> getDeclaringClass() Eine ziemlich langweilige Funktion, da Konstruktoren nicht vererbt werden. Somit wird immer nur jene Klasse ausgegeben, vor der das Class-Objekt kommt. Das ist ein wichtiger Unterschied zwischen Methoden und Konstruktoren, der bei dieser Methode deutlich auffällt.
gp  Class[] getExceptionTypes()
gp  int getModifiers()
gp  String getName()
gp  Class[] getParameterTypes()

Abbildung
Hier klicken, um das Bild zu Vergrößern

Wegen der Ähnlichkeit zu getMethods() verwenden wir als Beispiel die sehr gesprächige Methode toString() zum Auflisten aller Konstruktoren.

Listing 21.8   ShowConstructors.java

import java.lang.reflect.*;
public class ShowConstructors
{
  public static void main( String[] args )
  {
    for ( Constructor c : java.awt.Color.class.  getConstructors()   )
      System.out.println( c );
  }
}

Nach dem Aufruf erhalten wir:

public java.awt.Color(float,float,float,float)
public java.awt.Color(int)
public java.awt.Color(int,int,int)
public java.awt.Color(int,int,int,int)
public java.awt.Color(java.awt.color.ColorSpace,float[],float)
public java.awt.Color(int,boolean)
public java.awt.Color(float,float,float)

Galileo Computing

21.2.12 Annotationetoptop

Ob Annotationen zur Laufzeit vorhanden sind, erfragen Methoden der Schnittstelle AnnotatedElement, die unter anderem Class, Constructor, Field, Method, Package implementieren. Ein Blick in AnnotatedElement verrät, wie an die Annotationen heranzukommen ist:


interface java.lang.reflect.  AnnotatedElement  

gp  Annotation[] getAnnotations() Liefert alle an diesem Element assoziierten Annotationen.
gp  Annotation[] getDeclaredAnnotations() Liefert alle an diesem Element definierten Annotationen. Vererbte Annotationen werden ignoriert.
gp  boolean isAnnotationPresent( Class<? extends Annotation> annotationType ) Erfragt, ob das Element eine bestimmte Annotation besitzt.
gp  <T extends Annotation> T getAnnotation( Class<T> annotationType ) Liefert die Annotationen eines gewünschten Typs.



1  Echte Metaklassen wären Klassen, deren jeweils einziges Exemplar die normale Java-Klasse ist. Dann wären etwa die normalen Klassenvariablen in Wahrheit Objektvariablen in der Metaklasse.

 << 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