javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen

11 Introspektion


11.1 Læse klasseinformation for et objekt 152

11.1.1 Oversigt over pakken java.lang.reflect 152

11.1.2 Opremse metoderne i en klasse 153

11.2 Arbejde med objekter 153

11.3 Introspektion på javabønner 154

11.4 Generere nye klasser og indlæse dem 155

11.4.1 Kalde oversætteren som eksternt program 155

11.4.2 Bruge oversætteren internt 155

11.4.3 Indlæse klasser fra filsystemet 156

11.5 Videre læsning 157

Introspektion (eng.: Introspection eller reflection) handler om, hvordan man under kørslen af programmet kan inspicere et vilkårligt objekt, finde dets klasse, finde ud af, hvilke metoder og variabler klassen har, kalde metoderne og aflæse/sætte variablerne, oprette nye objekter fra klassen etc. etc.

Introspektion bruges sjældent i almindelige programmer, men det kan være nyttigt at kende til mulighederne for introspektion for at forstå, hvordan andre programmer, bl.a. udviklingsværktøj, fungerer.

11.1 Læse klasseinformation for et objekt

Stamklassen java.lang.Object, som alle objekter direkte eller indirekte arver fra, indeholder metoden getClass() - og ethvert objekt har derfor getClass(). Denne metode returnerer en repræsentation af objektets klasse (af typen java.lang.Class).

I eksemplet herunder opretter vi et Frame-objekt og henter dets klasse. Vi kigger derefter på superklasserne og udskriver dem.

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

public class UndersoegKlasse
{
  public static void main(String[] args)
  {
    Object o = new Frame();

    // Find klassen
    Class klasse = o.getClass();
    System.out.println("Klassen navn er: "+klasse.getName());

    // Find superklasserne
    Class superklasse = klasse.getSuperclass();
    while (superklasse != null)
    {
      System.out.println("... og den har superklasse: "+superklasse.getName());
      superklasse = superklasse.getSuperclass();
    }
  }
}

Klassen navn er: java.awt.Frame
... og den har superklasse: java.awt.Container
... og den har superklasse: java.awt.Component
... og den har superklasse: java.lang.Object

Man ser, at Frame er en Container, der igen arver fra Component, som arver fra Object.

11.1.1 Oversigt over pakken java.lang.reflect

Class-objekter har metoder til at spørge om alt, hvad der er værd at vide om klassen, herunder variabler, konstruktører og metoder (der bruges hjælpeklasserne Field, Constructor og Method). De vigtigste er opremset herunder:

Vigtigste metoder i Class (der repræsenterer en klasse)

String getName() giver en streng med klassens navn (og pakkenavn)

Class getSuperclass() giver Class-objektet, der repræsenterer superklassen

Class[] getInterfaces() giver et array med de interfaces, klassen implementerer

Field[] getFields() giver et array med de variabler, der er erklæret public i klassen

Constructor[] getConstructors() giver et array med de konstruktører, der er erklæret public

Method[] getMethods() giver et array med de metoder, der er erklæret public i klassen

Class[] getClasses() giver et array af de indre klasser (og interfaces), der er public i klassen

Ved normal introspektion af en klasse ses kun variabler, metoder og konstruktører, der er erklæret public. Vil man også se protected, pakke og private variabler/metoder/konstruktører på klassen skal man bruge nogle tilsvarende metoder, der starter med getDeclared, f.eks. getDeclaredFields(), getDeclaredConstructors() og getDeclaredMethods(). Disse metoder giver alle data, men der sker først et sikkerhedstjek for, om den kaldende tråd har tilladelse til at få disse oplysninger (det har f.eks. en applet ikke).

11.1.2 Opremse metoderne i en klasse

I eksemplet herunder får vi med getMethods() et array af alle metoder i klassen, der er erklæret public. Dette gennemløbes, og for hver metode udskrives retur- og parametertyper.

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

public class FindMetoder
{
  public static void main(String[] args)
  {
    Object o = new Button();

    // Find klassen
    Class klasse = o.getClass();
    System.out.println("Klassen navn er: "+klasse.getName());

    Method[] metoder = klasse.getMethods();
    for (int i=0; i<metoder.length; i++)
    {
      Method m = metoder[i];
      System.out.print("Metode "+m.getName());
      System.out.print(" har returtype: "+m.getReturnType().getName());
      Class[] parametertyper = m.getParameterTypes();
      System.out.print(" og parametertyper:");
      for (int j=0; j<parametertyper.length; j++)
        System.out.print(" " + parametertyper[j].getName());
    }
    System.out.println();
  }
}

Klassen navn er: java.awt.Button
...
Metode: notify har returtype: void
Metode: notifyAll har returtype: void
Metode: toString har returtype: java.lang.String
...
Metode: getLabel har returtype: java.lang.String
Metode: setLabel har returtype: void og parametertyper: java.lang.String
...

Programudskriften er ret lang, idet også superklassernes metoder udskrives.

11.2 Arbejde med objekter

Ud over at inspicere klasserne er der også mulighed for at arbejde aktivt med dem:

11.3 Introspektion på javabønner

Netop til javabønner findes der nogle klasser i pakken java.beans, der er specielt velegnede til at undersøge og manipulere med egenskaber (dvs. get- og set-metoder, se kapitel 4, Komponentbaseret programmering).

Man kan f.eks. opremse en bønnes egenskaber, udskrive en beskrivelse af hver egenskab og aflæse dens værdi:

import java.lang.reflect.*;
import javax.swing.*;
import java.beans.*;

public class Boenneintrospektion 
{
  public static void main(String[] args) throws Exception
  {
    Object objekt = new JButton();
    Class klasse = objekt.getClass();
    BeanInfo bønneinfo = Introspector.getBeanInfo(klasse);
    PropertyDescriptor egenskaber[] = bønneinfo.getPropertyDescriptors();

    for (int i=0; i<egenskaber.length; i++)
    {
      PropertyDescriptor e = egenskaber[i];

      System.out.print(e.getName()+": "+e.getShortDescription());

      Method læsemetode = e.getReadMethod();
      if (læsemetode != null) 
      {
        Object[] tomParameterliste = {};
        Object værdi = læsemetode.invoke(objekt,tomParameterliste);
        System.out.print(" (værdi="+værdi+")");
      }

      // sæt egenskaben til true, hvis den kan sættes og er af type boolean
      Method skrivemetode = e.getWriteMethod();
      if (skrivemetode!=null && e.getPropertyType()==java.lang.Boolean.TYPE)
      {
        Boolean[] parameterlisteMedTRUE = { Boolean.TRUE };
        skrivemetode.invoke(objekt, parameterlisteMedTRUE ); // sæt egenskab
      }
      System.out.println();
    }
  }
}

Herunder ses, hvad programmet skriver ud (nogle linjer er fjernet):

icon: The button's default icon (værdi=null)
inputMap: inputMap (værdi=javax.swing.InputMap@6e3d60)
insets: insets (værdi=java.awt.Insets[top=5,left=17,bottom=5,right=17])
alignmentY: The preferred vertical alignment of the component. (værdi=0.5)
alignmentX: The preferred horizontal alignment of the component. (værdi=0.0)
toolTipText: The text to display in a tool tip. (værdi=null)
mnemonic: the keyboard character mnemonic (værdi=0)
verticalAlignment: The vertical alignment of the icon and text. (værdi=0)
defaultButton: Whether or not this button is the default button (værdi=false)
rolloverEnabled: Whether rollover effects should be enabled. (værdi=false)
horizontalAlignment: The horizontal alignment of the icon and text. (værdi=0)
borderPainted: Whether the border should be painted. (værdi=true)

Metoden getShortDescription() henter en beskrivelse af egenskaben fra bønnens BeanInfo-klasse (ekstra information, der er beregnet til udviklingsværktøj, se afsnit 4.3.3). Kører du selv eksemplet, kan det være, at den ekstra information ikke er tilgængelig, og da giver getShortDescription() blot det korte navn.

11.4 Generere nye klasser og indlæse dem

Et spørgsmål relateret til introspektion er:

11.4.1 Kalde oversætteren som eksternt program

En mulighed for at oversætte en .java-fil til en .class-fil er at kalde oversætteren javac som et eksternt program, ligesom det gøres fra kommandolinjen, f.eks.:

public class KaldJavacEksternt
{
  public static void main(String[] args) throws Exception
  {
    Runtime r = Runtime.getRuntime();
    Process p = r.exec("javac UndersoegKlasse.java");
    p.waitFor();                               // vent på at processen er færdig
    System.out.println("færdig");
  }
}

færdig

Man skal være opmærksom på, at kommandoen 'javac' skal være tilgængelig fra kommandolinjen. Det kan f.eks. ske ved at ændre i stien (PATH-miljøvariablen).

11.4.2 Bruge oversætteren internt

Oversætteren er faktisk programmeret i Java og findes i klassen com.sun.tools.javac.Main. En anden mulighed er derfor at bruge denne klasse direkte:

public class KaldJavacInternt
{
  public static void main(String[] args)
  {
    com.sun.tools.javac.Main oversætter = new com.sun.tools.javac.Main();
    String[] filer = { "UndersoegKlasse.java" };
    oversætter.compile( filer );
    System.out.println("færdig");
  }
}

færdig

Skal man oversætte klasser mange gange, er denne måde langt hurtigere, idet oversætteren kører i den allerede eksisterende virtuelle maskine i stedet for at blive startet som et nyt program.

Man skal være opmærksom på, at filen lib/tools.jar fra ens Java-installation skal inkluderes i klassestien (CLASSPATH), sådan at programmet skal oversættes og køres f.eks. således:

javac -classpath /usr/local/jdk1.4/lib/tools.jar:. KaldJavacInternt.java
java -classpath /usr/local/jdk1.4/lib/tools.jar:. KaldJavacInternt

11.4.3 Indlæse klasser fra filsystemet

Hvis man vil indlæse klasser fra et andet sted end der, hvor systemet plejer at lede (f.eks. over netværket), skal man definere sin egen ClassLoader.

Eksemplet herunder er en ClassLoader, der kan indlæse klasser fra et bestemt katalog. Det får stien til kataloget overført i konstruktøren, hvorefter det opbygger en liste over de tilgængelige .class-filer. Denne liste kan hentes udefra ved at kalde tilgængeligeKlasser().

import java.io.*;
import java.util.*;

public class ClassLoaderFraKatalog extends ClassLoader {
  private Map klasser = new HashMap();

  public Set tilgængeligeKlasser()
  {
    return klasser.keySet();
  }

  public ClassLoaderFraKatalog(String sti)
  {
    File katalog = new File(sti);
    File[] filer = katalog.listFiles();

    for (int i=0; i<filer.length; i++)
    {
      File f = filer[i];
      System.out.println(f);
      String fn = f.getName();

      if (fn.endsWith(".class"))
      {
        String klassenavn = fn.substring(0,fn.length()-6); // fjern .class
        klasser.put(klassenavn,f);
      }
    }
  }

  public Class findClass(String navn) throws ClassNotFoundException
  {
    //System.out.println("findClass("+navn);
    try {
      File f = (File) klasser.get(navn);
      if (f==null) throw new IllegalArgumentException("Ukendt klasse: "+navn);

      byte[] b = new byte[(int) f.length()];
      FileInputStream fis = new FileInputStream(f);
      fis.read(b);
      return defineClass(navn, b, 0, b.length);
    } catch (Exception e) {
      System.out.println("findClass() fejl:"+e.getMessage());
      throw new ClassNotFoundException(e.getMessage());
    }
  }
}

Herunder et program, der benytter ClassLoaderFraKatalog:

import java.util.*;

public class BenytClassLoaderFraKatalog 
{
  public static void main(String[] args) throws Exception
  {
    // indlæs klasser fra det aktuelle katalog (.)
    ClassLoaderFraKatalog classLoader = new ClassLoaderFraKatalog(".");

    Set tilgængeligeKlasser = classLoader.tilgængeligeKlasser();
    System.out.println("tilgængeligeKlasser="+tilgængeligeKlasser);

    for (Iterator i=tilgængeligeKlasser.iterator(); i.hasNext(); )
    {
      try {
        String klassenavn = (String) i.next();
        Class klasse = classLoader.loadClass(klassenavn);
        Object objekt = klasse.newInstance();
        System.out.println("obj = " + objekt );
      } catch (Throwable e) { 
        e.printStackTrace();
      }
    }
  }
}

./FindMetoder.java
./ClassLoaderFraKatalog.java
./Boenneintrospektion.java
./FindMetoder.class
./KaldJavacEksternt.class
./KaldJavacInternt.java
./KaldJavacEksternt.java
./UndersoegKlasse.class
./BenytClassLoaderFraKatalog.class
./UndersoegKlasse.java
./BenytClassLoaderFraKatalog.java
./ClassLoaderFraKatalog.class
./Boenneintrospektion.class
./KaldJavacInternt.class
tilgængeligeKlasser=[FindMetoder, ClassLoaderFraKatalog, UndersoegKlasse, BenytClassLoaderFraKatalog, KaldJavacEksternt, Boenneintrospektion, KaldJavacInternt]
obj = FindMetoder@53c015
java.lang.InstantiationException: ClassLoaderFraKatalog
        at java.lang.Class.newInstance0(Native Method)
        at java.lang.Class.newInstance(Class.java:232)
        at BenytClassLoaderFraKatalog.main(BenytClassLoaderFraKatalog.java:18)
obj = UndersoegKlasse@680a59
obj = BenytClassLoaderFraKatalog@7f5ea7
obj = KaldJavacEksternt@13fac
obj = Boenneintrospektion@4672d0
obj = KaldJavacInternt@4abc9

Det ses, at det lykkes os at indlæse klasserne og oprette objekter fra dem.

Eneste undtagelse er indlæsningen af klassen ClassLoaderFraKatalog, hvor kaldet til newInstance() fejler, da klassen ikke har en konstruktør uden parametre.

11.5 Videre læsning

For en grundigere indføring end den herover se

javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen
http://javabog.dk/ - af Jacob Nordfalk.
Licens og kopiering under Åben Dokumentlicens (ÅDL) hvor intet andet er nævnt (71% af værket).

Ønsker du at se de sidste 29% af dette værk (362838 tegn) skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.