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

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
gp Kapitel 21 Reflection und Annotationen
  gp 21.1 MetaData
    gp 21.1.1 XDoclet
    gp 21.1.2 Annotationen
  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 Instanceof mit Class-Objekten
    gp 21.2.5 Oberklassen finden
    gp 21.2.6 Implementierte Interfaces einer Klasse oder eines Inferfaces
    gp 21.2.7 Modifizierer und die Klasse Modifier
    gp 21.2.8 Die Attribute einer Klasse
    gp 21.2.9 Methoden einer Klasse erfragen
    gp 21.2.10 Konstruktoren einer Klasse
  gp 21.3 Annotationen
  gp 21.4 Objekte manipulieren
    gp 21.4.1 Objekte erzeugen
    gp 21.4.2 Die Belegung der Variablen erfragen
    gp 21.4.3 Eine generische toString()-Funktion
    gp 21.4.4 Variablen setzen
    gp 21.4.5 Private Attribute ändern
  gp 21.5 Methoden aufrufen
    gp 21.5.1 Statische Methoden aufrufen
    gp 21.5.2 Dynamische Methodenaufrufe bei festen Methoden beschleunigen
  gp 21.6 Informationen und Identifizierung von Paketen
    gp 21.6.1 Geladene Pakete


Galileo Computing

21.4 Objekte manipulieredowntop

Nachdem wir nun genügend über das Ausfragen von Klassen-, Variablen-, Methoden- und Konstruktor-Objekten wissen, wollen wir nun aktiv eigene Objekte erzeugen, Werte von Variablen abfragen und verändern sowie Methoden dynamisch per Reflection aufrufen.


Galileo Computing

21.4.1 Objekte erzeugen  downtop

Der new-Operator erzeugt in Java zur Laufzeit ein Exemplar einer Klasse. Der Compiler muss dazu den Namen der Klasse wissen, so dass er einen passenden Konstruktor-Aufruf erzeugen kann. Kennen wir aber erst später zur Laufzeit den Namen der gewünschten Klasse für unser Objekt, so fällt die new-Operation flach, denn für diesen Spezialfall ist der new-Operator nicht gedacht.

Um Exemplare bestimmter Klassen dynamisch zu erzeugen, brauchen wir wieder ein passendes Class-Objekt. Nun holen wir uns mit getConstructor() ein Konstruktor-Objekt, das den gewünschten Konstruktor beschreibt. Jedes Konstruktor-Objekt kennt eine newInstance(Object[])-Methode, die ein neues Exemplar erschafft, indem sie den zugrunde liegenden Konstruktor aufruft. Der Parameter von newInstance() ist ein Feld von Werten, die an den echten Konstruktor gehen. Glücklicherweise kennt Java anonyme Arrays, so dass wenig zu schreiben bleibt. Bei einem parameterlosen Konstruktor können wir einfach newInstance(null) aufrufen.


Beispiel   Ein Reflection-Konstruktor erzeugt ein Point-Objekt mit den Koordinaten 10, 20.

Listing 21.9   CreateObject.java


import java.lang.reflect.*;
import java.awt.*;

public class CreateObject
{
  public static void main( String args[] )
  {
    try
    {
      Class pointClass = Point.class;

      Class argsClass[] = new Class[]{int.class, int.class};
      Constructor constructor =
        pointClass.getConstructor( argsClass );

      Object[] intArgs =
        new Object[]{new Integer(10), new Integer(20)};
      Point p = (Point) constructor.newInstance( intArgs );

      System.out.println( p );
    }
    catch ( InstantiationException e ) {
      System.err.println( e );
    }
    catch ( IllegalAccessException e ) {
      System.err.println( e );
    }
    catch ( IllegalArgumentException e ) {
      System.err.println( e );
    }
    catch ( InvocationTargetException e ) {
      System.err.println( e );
    }
    catch ( NoSuchMethodException e ) {
      System.err.println( e );
    }
  }
}


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

gp  Constructor<T> getConstructor( Class... parameterTypes ) throws NoSuchMethodException
Liefert den sichtbaren Konstruktor mit dem gewünschten Typ.


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

gp  T newInstance( Object... initargs )
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
Erzeugt ein neues Exemplar, indem es den durch das Constructor-Objekt repräsentierten Konstruktor mit den im Array angegebenen Parametern aufruft. Auf einige Exceptions ist zu achten. IllegalAccessException: Auf den Konstruktor kann nicht zugegriffen werden (zum Beispiel, weil er privat ist). IllegalArgumentException: Die Anzahl der Parameter ist falsch beziehungsweise eine Konvertierung der Parameterwerte in die benötigten Typen ist nicht möglich. InstantiationException: Das Constructor-Objekt bezieht sich auf einen Konstruktor einer abstrakten Klasse. InvocationTargetException: Bei der Ausführung des angegebenen Konstruktors ist eine Exception aufgetreten.

Galileo Computing

21.4.2 Die Belegung der Variablen erfragen  downtop

Schreiben wir einen GUI-Builder oder einen Debugger, so reicht es nicht aus, nur die Namen und Datentypen der Variablen zu wissen. Wir wollen auch auf ihre Inhalte lesend und schreibend zugreifen. Das ist durch die verschiedenen getXXX()-Methoden für ein Field-Objekt leicht machbar. Der erste Schritt besteht also wieder darin, ein Class-Objekt zu erfragen. Dann müssen wir über das mittels getFields() besorgte Array von Attributbeschreibungen ein Field-Objekt für die interessierende Variable beschaffen. Die Field-Klasse hat einige spezielle getXXX()-Methoden, um besonders einfach an die Werte von Variablen primitiven Typs zu gelangen. So liefert getFloat() einen float-Wert und getInt() ein int. Für Variablen, die eine Objektreferenz enthalten, wird einfach die get()-Methode verwendet. Wir müssen daran denken, dass IllegalArgumentException und IllegalAccessException bei falschem Zugriff auftreten können.



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

gp  String getName()
Liefert den Namen der Variablen. Diese Methode ist Teil der Schnittstelle Member.
gp  int getModifiers()
Liefert die Modifizierer. Diese Methode ist Teil der Schnittstelle Member.
gp  Object get( Object obj )
boolean getBoolean( Object obj )
byte getByte( Object obj )
char getChar( Object obj )
double getDouble( Object obj )
float getFloat( Object obj )
int getInt( Object obj )
long getLong( Object obj )
short getShort( Object obj )
obj ist das Objekt, vom dem das Feld auszulesen ist. Im Fall eines statischen Feldes ist keine Angabe nötig.

Beispiel   Wir schreiben nun ein kleines Programm, welches ein Rectangle-Objekt mit einer Belegung für x, y, Höhe und Breite erzeugt. Anschließend erfragen wir mit getField(String) das Field-Objekt für eine Beschreibung der Variable mit dem gegebenen Namen. Das Field-Objekt gibt dann mit getXXX() den Inhalt der Variablen preis.
Um das Prinzip zu demonstrieren, wird die Höhe height über die get()-Methode erfragt, die ja ein Integer-Objekt zurückgibt. Für alle anderen Werte verwenden wir aber die spezialisierte Helferfunktion getInt(). Die Helferfunktionen sind nativ implementiert.

Listing 21.10   GetFieldElements.java


import java.lang.reflect.*;

class GetFieldElements
{
  public static void main( String args[] )
  {
    print( new java.awt.Rectangle(11,22,33,44) );
  }

  static void print( Object r )
  {
    Class c = r.getClass();

    try {
      Field heightField = c.getField( "height" ),
            widthField  = c.getField( "width" ),
            xField      = c.getField( "x" ),
            yField      = c.getField( "y" );

      Integer height = (Integer) heightField.get( r );
      int     width = widthField.getInt( r ),
              x = xField.getInt( r ),
              y = yField.getInt( r );

      String out = c.getName() + "[x=" + x + ",y=" + y +
                   ",width=" + width + ",height=" + height + "]";

      System.out.println( out );
      System.out.println( r.toString() );
    }
    catch ( NoSuchFieldException e ) {
      System.out.println( e );
    }
    catch ( SecurityException e ) {
      System.out.println( e );
    }
    catch ( IllegalAccessException e ) {
      System.out.println( e );
    }
  }
}

Es erzeugt nun nach dem Aufruf die Ausgabe


java.awt.Rectangle[x=11,y=22,width=33,height=44]
java.awt.Rectangle[x=11,y=22,width=33,height=44]

Galileo Computing

21.4.3 Eine generische toString()-Funktion  downtop

Die toString()-Funktion ist für viele Klassen nicht wirklich nötig, aber außerordentlich praktisch. Viel zu schade, dass hier Zeit für die Entwicklung aufgewendet wird. Toll wäre, wenn toString() ganz automatisch die Attribute und Belegungen herausfindet und sie ausgibt. Aber wer Allgemeines sucht, wird die Antwort in Reflection finden. Wir schreiben einfach eine statische Funktion toString(Object) in eine Hilfsklasse und erfragen dann alle Attribute und die Werte des zu untersuchenden Objekts. Etwas Programmieraufwand müssen wir noch in der Behandlung der Oberklasse investieren, denn Reflection auf einem Objekt zeigt nur die in der Klasse definierten Attribute an, nicht aber die geerbten Attribute. Die Lösung ergibt sich fast von selbst:

Listing 21.11   ToStringHelper.java


import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.ArrayList;

public class ToStringHelper
{
 public static String toString( Object o )
 {
  ArrayList<String> list = new ArrayList<String>();

  toString( o, o.getClass(), list );

  return o.getClass().getName().concat( list.toString() );
 }

 private static void toString( Object o, Class clazz, ArrayList<String> list )
 {
  Field f[] = clazz.getDeclaredFields();
  AccessibleObject.setAccessible( f, true );

  for ( int i = 0; i < f.length; i++ )
  {
   try
   {
    list.add( f[i].getName() + "=" + f[i].get(o) );
   }
   catch ( IllegalAccessException e ) { e.printStackTrace(); }
  }



  if ( clazz.getSuperclass().getSuperclass() != null )
    toString( o, clazz.getSuperclass(), list );
 }
}

Die private Funktion toString(Object, Class, ArrayList) dient eigentlich nur für den rekursiven Aufruf durch die Oberklassen. Falls es eine Oberklasse gibt, also clazz.getSuperclass().getSuperclass() ein Objekt liefert, dann müssen wir für die Oberklasse ebenfalls die Attribute ablaufen. Das machen wir rekursiv.

Testen wir anschließend ToStringHelper in einer Klasse ToStringHelperDemo, die von Ober abgeleitet ist. Damit bekommen wir zwei Attribute in der Oberklasse. Eines davon ist interessant (die Variable i), denn sie wird in der Unterklasse überdeckt. Dennoch findet toString() beide Belegungen. Wäre das nicht erwünscht, müssten wir einfach die Liste durchschauen und suchen, ob schon ein Attribut mit dem gleichen Namen vorhanden ist. Da erst die Unterklasse und dann die Oberklasse durchsucht wird, bekommen wir auch die Attribute in dem sichtbaren Bereich, wie sich auch der Benutzer sieht.

Listing 21.12   ToStringHelperTest.java


class Ober
{
 int i = 123;
 private double d = 3.1415;
}

public class ToStringHelperTest extends Ober
{
  String hello = "world";
  int i = 42;

  public static void main( String args[] )
  {
    Ober t = new ToStringHelperTest();

    System.out.println( ToStringHelper.toString(t) );
    // ToStringHelperTest[hello=world, i=42, i=123, d=3.1415]
  }
}

Galileo Computing

21.4.4 Variablen setzen  downtop

Bei Debuggern oder grafischen Editoren ist es nur eine Seite der Medaille, die Werte von Variablen anzuzeigen. Dazu kommt noch das Setzen der Werte von Variablen. Dies ist aber genauso einfach wie das Abfragen. An Stelle der getXXX()-Methoden kommen nun verschiedene setXXX()-Methoden zum Einsatz. So trägt setBoolean() einen Wahrheitswert oder setFloat() eine Fließkommazahl in eine Variable ein. Eine allgemeine set()-Methode wird für Objektreferenzen verwendet. Die Funktion set() kann jedoch auch mit dem Exemplar einer Wrapper-Klasse für Variablen von primitiven Datentypen benutzt werden. Die folgenden Funktionen set<Typ>() setzen daher alle »ihren« Datentyp. Wir müssen aber dafür sorgen, dass die Variable existiert und wir Zugriff darauf haben. In allen Fällen muss auf IllegalArgumentException und IllegalAccessException geachtet werden.



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

gp  void set( Object obj, Object value )
Setzt das Attribut des Objekts obj, das durch dieses Field-Objekt repräsentiert wird, auf den neuen Wert value.
gp  void setBoolean( Object obj, boolean z )
void setByte( Object obj, byte b )
void setChar( Object obj, char c )
void setDouble( Object obj, double d )
void setFloat( Object obj, float f )
void setInt( Object obj, int i )
void setLong( Object obj, long l )
void setShort( Object obj, short s )
Belegt das Feld eines Objektes obj mit einem primitives Element.

Das nachfolgende Programm erzeugt ein Rectangle-Objekt mit dem Konstruktor, der width und height setzt. Anschließend rufen wir mit modify() eine Methode auf, die eine Variable mit dem übergebenen Namen verändert. Der neue Variablenwert wird modify() ebenfalls mitgegeben. Die Veränderung der Variablen erfolgt anschließend mit der set()-Methode. Da wir primitive Datentypen übergeben, wickeln wir sie für die modify()-Methode in ein Integer-Objekt ein. Andernfalls müssten wir nicht die allgemeine set()-, sondern die spezialisierte setInt()-Methode verwenden.

Listing 21.13   SetFieldElements.java


import java.lang.reflect.*;
import java.awt.*;

class SetFieldElements
{
  public static void main( String args[] )
  {
    Rectangle r = new Rectangle( 11, 22 );
    System.out.println( r );

    modify( r, "width", new Integer(1111) );
    modify( r, "height", new Integer(2222) );

    System.out.println( r );
  }

  static void modify( Object o, String name, Integer param )
  {
    Field field;
    Integer value;

    Class c = o.getClass();

    try
    {
      field = c.getField( name );
      field.set( o, param );
    }
    catch ( NoSuchFieldException e ) {
      System.err.println( e );
    }
    catch ( IllegalAccessException e ) {
      System.err.println( e );
    }
  }
}

Die Ausgabe des Programms zeigt uns, wie erwartet, eine Veränderung der Breite und Höhe:


java.awt.Rectangle[x=0,y=0,width=11,height=22]
java.awt.Rectangle[x=0,y=0,width=1111,height=2222]

Galileo Computing

21.4.5 Private Attribute ändern  toptop

Sofern es der Sicherheitsmanager zulässt, können auch private- oder protected-Attribute geändert werden. Schlüsselfigur in diesem Spiel ist die Klasse java.lang.reflect.AccessibleObject, die den Klassen Constructor, Field und Method die Methode field.setAccessible(boolean) vererbt. Ist das Argument true, lässt sich ein geschütztes Element lesen und schreiben.

Listing 21.14   ReadPrivate.java


import java.lang.reflect.Field;

public class ReadPrivate
{
  private String privateKey = "Schnuppelhase";

  public static void main( String args[] ) throws Exception
  {
    ReadPrivate key = new ReadPrivate();
    Class c = key.getClass();

    Field field = c.getDeclaredField( "privateKey" );

    field.setAccessible( true );

    System.out.println( field.get(key) );  // Schnuppelhase
    field.set( key, "Schnuckibutzihasidrachelchen");

    System.out.println( field.get(key) ); // Schnuckibutzihasidrachelchen
  }
}




Copyright © Galileo Press GmbH 2004
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, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de