Kapitel 7 Reflections

Reflections er en anderledes måde at kalde metoder på. Normalt skrives navnene på de metoder, der skal kaldes, direkte i kildeteksten, inden den kompileres. Det vil sige, at det på forhånd er lagt fast, hvilke metoder, der skal kaldes på hvilket tidspunkt, når programmet køres. Hvis man vil give brugeren mulighed for at vælge mellem flere forskellige gennemløb af programmet, kan man bruge if eller switch-case til at evaluere brugerens valg og herefter kalde en funktion. Ved hjælp af reflections er det imidlertid muligt at skrive programmer, der er mere dynamiske. For eksempel kan brugeren – mens programmer kører – indtaste navnet på en metode, og så få den udført.

Du læser en gammel bog
Den bog, du læser her, er fra 1998, og mange ting kan have ændret sig siden da.
Vi håber, at du stadig kan finde relevant information i den.
Hvis du vil læse aktuelle oplysninger om de avancerede dele af Java, anbefaler vi
bogen Core Java – Advanced Features

 

Fordelene ved denne fremgangsmåde er mange. Først og fremmest bliver man fri for en enorm mængde if-sætninger, der skal kontrollere, hvad brugeren har indtastet, og derefter kalde den pågældende funktion. Derudover bliver systemet nemmere at vedligeholde. Når man implementerer en ny funktion, er den omgående tilgængelig for brugeren – man behøver ikke først tilføje den til listen over gyldige kommandoer.

 

Der er imidlertid også en række problemer, der skal overvindes. For eksempel tager mange metoder en eller flere parametre. Det skal således være muligt for brugeren at indtaste navnet på metoden samt de parametre, der skal bruges, hvorefter metoden bliver kaldt med de parametre.

 

Reflections kan langt fra anvendes i ethvert projekt, men der er en del opgaver, der med fordel kan løses ved brug af reflections. Det gælder for eksempel netværkskommunikation, hvor reflections kan være et godt alternativ eller supplement til remote method invocation (RMI). Også ved implementering af scriptsprog har reflections en fordel.

 

Dynamisk metodekald

 

Dynamisk metodekald er nøglen til det hele, og derfor vil det blive gennemgået først. Da Java er objektorienteret kan metoderne ikke stå alene – de skal være tilknyttet en klasse. Man kan finde et objekts klasse ved at benytte funktionen objekt.getClass(). Denne funktion returnerer et objekt af typen java.lang.Class:

 

public final class Class extends Object

{

public static Class forName(String className) throws ClassNotFoundException;

public Object newInstance() throws InstantiationException, IllegalAccessException;

public boolean isInstance(Object obj);

public boolean isPrimitive();

 

public String getName();

public int getModifiers();

public ClassLoader getClassLoader();

public Class getSuperclass();

public Class[] getInterfaces();

public Field[] getFields()

throws SecurityException;

 

public Method[] getMethods() throws SecurityException;

public Constructor[] getConstructors();

public Method getMethod(String name, Class[] parameterTypes)

throws NoSuchMethodException, SecurityException;

public Constructor getConstructor(Class[] parameterTypes)

throws NoSuchMethodException, SecurityException;

}

 

Når man har fundet ud af, hvilken klasse, der er tale om, kan man bruge getMethod til at finde den metode, der skal kaldes. Det er imidlertid ikke så let, som det lyder. Java understøtter overloading, hvilket betyder at flere forskellige funktioner i samme klasse godt kan have det samme navn. Man kender i så fald forskel på dem på deres forskellige signatur – det vil sige returtype og antallet og/eller typen af parametre. getMethod tager derfor to parametre:

 

  • en String, der angiver metodens navn
  • et array af Class-objekter, der angiver typen af parametrene i den rækkefølge de forekommer i metodens signatur.

Indeholder klassen ikke en public metode, der svarer til den angivne signatur, opstår en NoSuchMethodException. Hvis metoden bliver fundet, bliver der returneret en reference til den i form af et java.lang.reflect.Method-objekt:

 

public final class Method extends Object implements Member

{

public String getName();

public int getModifiers();

public Class getReturnType();

public Class[] getParameterTypes();

public Class[] getExceptionTypes();

public Object invoke(Object obj, Object[] args)

throws NullPointerException, IllegalArgumentException,

IllegalAccessException, InvocationTargetException;

}

 

Det Method-objekt, der returneres fra getMethod, kan man kalde metoden invoke på – så bliver metoden udført. invoke tager to parametre:

 

  • Det objekt, metoden skal kaldes på. Det er dette objekt, metoden getClass blev kaldt på.
  • Et array af objekter, svarende til de aktuelle parametre i metodekaldet.

 

 

Ved brug af invoke-metoden skal følgende bemærkes:

 

  • Primitive typer kan ikke overføres i et array af objekter, da de ikke er objekter. I stedet skal wrapper-klasserne bruges (Integer i stedet for int, Float i stedet for float, osv.). Konverteringen fra wrapper-typerne i kaldet til invoke til de primitive typer i det reelle funktionskald foretages automatisk.Hvis det argument, der forsøges konverteret til en primitiv type, er null opstår en NullPointerException.

    Hvis det argument, der forsøges konverteret til en primitiv type, ikke er en af Javas standard wrapper-klasser, opstår en IllegalArgumentException.

  • Hvis metoden returnerer en primitiv type, bliver returværdien automatisk konverteret til en wrapper-type. Det er herefter op til programmøren at konvertere den tilbage til en primitiv type, hvis nødvendigt.
  • Hvis den metode, der bliver kaldt er en klassemetode – det vil sige, at den er erklæret static – bruges den første parameter i kaldet til invoke ikke, og den kan være null.
  • Hvis metoden ikke er en klassemetode, og objektet angives som null opstår en NullPointerException.
  • Hvis objektet, der sendes med som parameter til invoke, ikke definerer den pågældende metode, opstår en IllegalArgumentException.
  • Hvis metodekaldet medfører adgangskontrol, og metoden ikke er tilgængelig, opstår en IllegalAccessException.
  • Hvis typerne af de aktuelle parametre angivet i kaldet til invoke ikke passer til de formelle parametre angivet i den pågældende metodes signatur, opstår en IllegalArgumentException.
  • Hvis metoden ikke tager nogen parametre, ses bort fra arrayet af parametre i kaldet til invoke. Parametrene kan i så fald angives som null
  • Hvis udførslen af metoden resulterer i en exception, bliver denne exception grebet af invoke-metoden. Herefter bliver den placeret i en InvokationTargetException, som invoke genererer og sender videre.
  • Hvis metoden udføres succesfuldt, returneres metodens returværdi af invoke – eventuelt konverteret til en wrapper-klasse. Hvis metoden returnerer void, returnerer invoke null.

Alt i alt skal der altså foretages to skridt for at udføre et dynamisk kald af en metode:

 

  1. Der skal findes en reference til den pågældende metode.
  2. Metoden skal kaldes.

 

Det lyder umiddelbart meget simpelt, men det er slet ikke så let at programmere. Det er for besværligt at skulle til at oprette et array af Class-objekter, hver gang man skal have en reference til en metode. Derefter skal man oprette et array af objekter, der indeholder de aktuelle parametre i metodekaldet. Det

 

ville være væsentligt nemmere, hvis man kunne opbevare argumenterne i en klasse, der både håndterede de aktuelle argumenter, men også deres klasser. Til at løse denne opgave kan en klasse ArgumentList oprettes. Et eksempel på en sådan klasses ses herunder:

 

import java.util.Vector;

 

public class ArgumentList

{

// Variabler til at håndtere henholdsvis klasser og objekter

protected Vector classes;

protected Vector objects;

 

// Constructor

public ArgumentList ()

{

classes = new Vector();

objects = new Vector();

}

 

// Funktioner til at tilføje argumenter

// Primitive typer konverteres til wrapper-klasser

 

public void addArg (boolean b)

{

addArg (new Boolean (b));

}

 

public void addArg (byte b)

{

addArg (new Byte (b));

}

 

public void addArg (char c)

{

addArg (new Character (c));

}

 

public void addArg (int i)

{

addArg (new Integer (i));

}

 

public void addArg (short s)

{

addArg (new Short (s));

}

 

public void addArg (long l)

{

addArg (new Long (l));

}

 

 

public void addArg (float f)

{

addArg (new Float (f));

}

 

public void addArg (double d)

{

addArg (new Double (d));

}

 

// Indsæt et objekt – ingen konvertering nødvendig

 

public void addArg (Object o)

{

objects.addElement (o);

classes.addElement (o.getClass());

}

 

// Funktioner til returnering af argumenter

 

public Class[] getClasses ()

{

classes.trimToSize();

int classCount = classes.size();

 

Class[] cl = new Class [classCount];

 

for (int i = 0; i < classCount; i++)

cl[i] = (Class) classes.elementAt (i);

 

return cl;

}

 

public Object[] getArguments ()

{

objects.trimToSize();

int argumentCount = objects.size();

 

Object[] obj = new Object [argumentCount];

 

for (int i = 0; i < argumentCount; i++)

obj[i] = objects.elementAt (i);

 

return obj;

}

}

 

Klassen gemmer de klasser og objekter, der siden skal bruges som argumenter, i en instans af den dynamiske klasse java.util.Vector. Til at tilføje argumenter til listen bruges en række overloadede metoder, der alle hedder addArg. Der findes en version af addArg til hver af de primitive typer. Disse metoder opretter et objekt af den pågældende primitive types wrapper-klasse og kalder videre til den udgave af addArg, der tager et objekt som parameter.

 

Denne udgave af metoden er den eneste, der foretager et virkeligt stykke arbejde. Den tilføjer objektet til listen over objekter, og tilføjer objektets klasse til listen over klasser. Som klassen er skitseret her, skal argumenterne tilføjes i samme rækkefølge som de forekommer i metodens signatur. En forbedring af klassen kan indeholde funktioner, der kan tilføje argumenter på en bestemt position.

 

Der findes to funktioner, der returnerer information om argumenterne. Det er getClasses og getArguments. getClasses returnerer et array af Class-objekter. Det sker ved at gennemløbe vektoren og konvertere hvert element til Class. getArguments foretager det samme for objects-vektoren – blot bliver elementerne her ikke konverteret, da de allerede er af typen Object.

 

Med ArgumentList på plads er det muligt at lave en generel funktion, der foretager et dynamisk metodekald:

 

import java.lang.reflect.Method;

import java.lang.Class;

 

public class Invoker

{

public static Object dynamicInvocation (Object obj, String methodName,

ArgumentList args) throws Exception

{

Method method = obj.getClass().getMethod (methodName, args.getClasses());

return method.invoke (obj, args.getArguments());

}

}

 

Invoker-klassen indeholder kun en enkelt funktion. Det er den statiske dynamicInvocation, der tager tre parametre:

 

  • En reference til det objekt, metoden skal kaldes på.
  • Navnet på den metode, der skal kaldes.
  • De parametre, der skal overføres til funktionen, samlet i en ArgumentList.

 

Dynamisk instantiering af klasser

 

I de ovennævnte eksempler er klasserne blevet instantieret på den sædvanlige måde. Herefter er getClass-metoden blevet kaldt på objektet, for at få en reference til et Class­-objekt. Det er imidlertid også muligt at instantiere klasser dynamisk. Det gøres ved hjælp af den statiske funktion

 

public static Class forName(String className) throws ClassNotFoundException;

 

Denne funktion findes i klassen Class.

 

Metoden tager navnet på klassen, der skal oprettes en instans af, som parameter. Kan klassen ikke findes, opstår en ClassNotFoundException.

 

Når man har referencen til klassen, er man klar til at instantiere et objekt af den. Det gør man ved hjælp af metoden

 

public Object newInstance() throws InstantiationException,

IllegalAccessException;

 

Metoden returnerer et objekt, og returværdien skal derfor castes til den rette klasse. Nedenstående er et eksempel på brug af dynamisk instantiering:

 

import java.awt.Frame;

import java.awt.Label;

 

public class DynamicLabel extends Frame

{

public DynamicLabel ()

{

super (“DynamicLabel”);

}

 

public static final void main (String args[])

{

 

Class labelClass = null;

Label label = null;

 

try

{

labelClass = Class.forName (“java.awt.Label”);

}

catch (ClassNotFoundException cnfe)

{

System.err.println (cnfe.toString());

System.exit (1);

}

 

try

{

label = (Label) labelClass.newInstance();

}

catch (IllegalAccessException iae)

{

System.err.println (iae.toString());

System.exit (2);

}

catch (InstantiationException ie)

{

System.err.println (ie.toString());

System.exit (3);

}

 

ArgumentList argList = new ArgumentList();

argList.addArg (“Dette er min label”);

 

try

{

 

Invoker.dynamicInvocation (label, “setText”, argList);

}

catch (Exception e)

{

System.err.println (e.toString());

System.exit (4);

}

 

DynamicLabel me = new DynamicLabel();

 

me.add (“Center”, label);

 

me.setVisible(true);

}

}

 

Som det ses, skal man angive det komplette klassenavn for at få en reference til klassen. Det er med andre ord ikke nok at angive Label – man skal angive hele navnet java.awt.Label.

 

Eksemplet viser også, hvilke exceptions man skal være opmærksom på. Class.forName kan ende i en ClassNotFoundException, hvis klassen ikke kan findes. Instantieringen af klassen ved hjælp af newInstance kan også medføre exceptions. IllegalAccessException hvis man ikke har rettigheder til at instantiere klassen, og InstantiationException hvis man forsøger at instantiere en abstrakt klasse eller et interface, eller hvis man kalder funktionen på et objekt, der repræsenterer en primitiv type eller Void.

 

Efter instantieringen af objektet, bliver der gjort klar til at kalde metoden setText (String). Det gøres ved at oprette et ArgumentList-objekt, hvor teksten ”Dette er min label” tilføjes til listen over parametre. Dernæst bruges Invoker-klassens dynamicInvocation-metode, som beskrevet tidligere i kapitlet. Den metode kan resultere i en generel Exception, hvis kaldet til den underliggende metode mislykkes eller medfører en exception.

 

Resten af eksemplet sørger blot for at få labelen tilføjet til vinduet og få vinduet vist.

 

Som det ses, giver reflections væsentligt mere kode end almindelig, statisk programmering.

 

Indlæsning af klasser

 

Forrige afsnit forklarede, hvordan man kunne instantiere klasser dynamisk, men det sprang let og elegant over, hvordan klasserne blev indlæst. Til dette bruges en class loader. Normalt bliver klasser indlæst fra den lokale computer. I Windows og Unix bliver klasser indlæst fra de biblioteker, der er angivet i CLASSPATH-variablen. Sådan behøver det imidlertid ikke at være. Der er ikke noget, der hindrer, at man skriver sin egen class loader, der gør det muligt at indlæse klasser fra andre computere via et netværk. Først vil det nok være en god idé at se på, hvordan den almindelige class loader virker og bruges.

 

 

public abstract class ClassLoader extends Object

{

protected final java.lang.Class defineClass(java.lang.String, byte[] , int off, int len) throws java.lang.ClassFormatException.

public static java.lang.ClassLoader getBaseClassLoader ();

public java.lang.Class loadClass (java.lang.String) throws java.lang.ClassNotFoundException;

protected final void resolveClass(java.lang.Class)

 

}

 

Klassen ClassLoader er en abstrakt klasse, som andre klasser kan nedarve fra for at definere konkrete class loadere. Den vigtigste funktion i klassen er loadClass. Den bruges til at indlæse en klasse. Dernæst bliver metoden resolveClass kaldt – dette sker automatisk, hvis man angiver true som den anden parameter i kaldet til loadClass. resolveClass sørger for, at man kan instantiere klassen og kalde metoder på den.

 

Den mest interessante funktion i ClassLoader er imidlertid defineClass. Den konverterer et byte-array til en klasse. Det vil sige, at man kan indlæse en klasses binære kode gennem for eksempel en stream, og derefter konvertere dataene til en klasse. Det betyder, at man kan lave en class loader, der henter klasser på en anden computer ved at følge nedenstående råskitse til en plan:

 

  1. Opret en klasse, der nedarver fra ClassLoader.
  2. Definér metoden loadClass.

2.1.   Opret en stream til class-filen på den anden computer.

2.2.   Indlæs class-filen i et array af bytes.

2.3.   Konverter byte-arrayet til et Class-objekt ved hjælp af defineClass-metoden.

2.4.   Returnér det Class-objekt, kaldet til defineClass returnerede.

  1. Definér øvrige metoder for at gøre implementationen af ClassLoader konkret.

 

Ovenstående køreplan er – skal det ærligt indrømmes – temmelig overodnet, men i hovedtræk er det, det eneste man skal gøre, for at definere en ny class loader.

 

 

Andre muligheder

 

Man kan imidlertid andet med klasser end at instantiere dem og kalde funktioner på dem. Man kan bruge Class-klassens funktioner til at samle en masse informationer om et objekt. ClassInfo-klassen viser, hvordan man kan indsamle oplysninger om en klasse:

 

import java.lang.reflect.Modifier;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import java.lang.reflect.Constructor;

 

 

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

 

public class ClassInfo

{

private static String describeModifiers (int modifier)

{

String s;

 

if (Modifier.isPrivate(modifier))

s = “private “;

else

if (Modifier.isPublic(modifier))

s = “public “;

else

if (Modifier.isProtected(modifier))

s = “protected “;

else

s = “unspecified “;

 

if (Modifier.isStatic(modifier))

s = s + “static “;

 

if (Modifier.isFinal(modifier))

s = s + “final “;

 

if (Modifier.isSynchronized(modifier))

s = s + “synchronized “;

 

if (Modifier.isVolatile(modifier))

s = s + “volatile “;

 

if (Modifier.isTransient(modifier))

s = s + “transient “;

 

if (Modifier.isNative(modifier))

s = s + “native “;

 

return s;

}

 

public static void getClassInfo (String name) throws ClassNotFoundException

{

Class cl = Class.forName (name);

 

System.out.println (“Information om ” + name);

 

// Generel information om klassen

 

if (cl.isInterface())

System.out.println (name + ” er et interface.”);

 

else

if (cl.isArray())

System.out.println (name + ” er et array af typen ” + cl.getComponentType().getName());

else

if (cl.isPrimitive())

System.out.println (name + ” er en primitiv type.”);

 

// Modifier

 

System.out.println ();

 

int modifier = cl.getModifiers();

 

System.out.print (Modifier.isPublic(modifier) ? “X” : “O”);

System.out.println (” Public”);

 

System.out.print (Modifier.isPrivate(modifier) ? “X” : “O”);

System.out.println (” Private”);

 

System.out.print (Modifier.isProtected(modifier) ? “X” : “O”);

System.out.println (” Protected”);

 

System.out.print (Modifier.isStatic(modifier) ? “X” : “O”);

System.out.println (” Static”);

 

System.out.print (Modifier.isFinal(modifier) ? “X” : “O”);

System.out.println (” Final”);

 

System.out.print (Modifier.isAbstract(modifier) ? “X” : “O”);

System.out.println (” Abstract”);

 

 

// Nedarvning

 

System.out.println ();

System.out.println (“Information om nedarvning:”);

 

System.out.println (“Superklasse: ” + cl.getSuperclass());

 

Class[] interfaces = cl.getInterfaces();

System.out.print (“Interfaces: “);

 

for (int i = 0; i < interfaces.length; i++)

System.out.print (interfaces[i].getName() + ” “);

 

System.out.println();

 

 

// Variable

 

System.out.println ();

System.out.println (“Variable:”);

 

Field[] fields = cl.getFields();

 

for (int i = 0; i < fields.length; i++)

{

System.out.print (describeModifiers (fields[i].getModifiers()));

System.out.print (fields[i].getType().getName() + ” “);

System.out.print (fields[i].getName());

System.out.println ();

}

 

// Constructors

 

System.out.println ();

System.out.println (“Constructors”);

 

Constructor[] constructors = cl.getConstructors();

 

for (int i = 0; i < constructors.length; i++)

{

System.out.print (describeModifiers (constructors[i].getModifiers()));

System.out.print (constructors[i].getName() + “(“);

Class[] parameters = constructors[i].getParameterTypes();

for (int j = 0; j < parameters.length; j++)

{

System.out.print (parameters[j].getName());

if (j + 1 < parameters.length)

System.out.print (“, “);

}

 

System.out.print (“)”);

Class[] exceptions = constructors[i].getExceptionTypes();

 

if (exceptions.length != 0)

System.out.print (” throws “);

 

for (int j = 0; j < exceptions.length; j++)

{

System.out.print (exceptions[j].getName());

if (j + 1 < exceptions.length)

System.out.print (“, “);

}

 

System.out.println (“;”);

}

 

 

// Metoder

 

System.out.println ();

System.out.println (“Metoder:”);

 

Method[] methods = cl.getMethods();

 

for (int i = 0; i < methods.length; i++)

{

System.out.print (describeModifiers (methods[i].getModifiers()));

System.out.print (methods[i].getReturnType().getName() + ” ” + methods[i].getName() + ” (“);

Class[] parameters = methods[i].getParameterTypes();

for (int j = 0; j < parameters.length; j++)

{

System.out.print (parameters[j].getName());

if (j + 1 < parameters.length)

System.out.print (“, “);

}

 

Class[] exceptions = methods[i].getExceptionTypes();

 

System.out.print (“)”);

 

if (exceptions.length != 0)

System.out.print (” throws “);

 

for (int j = 0; j < exceptions.length; j++)

{

System.out.print (exceptions[j].getName());

if (j + 1 < exceptions.length)

System.out.print (“, “);

}

 

System.out.println (“;”);

}

}

 

public static final void main (String args[]) throws Exception

{

String className = “”;

try

{

System.out.println (“Indtast navnet på den klasse, der skal diagnosticeres”);

BufferedReader in = new BufferedReader (new InputStreamReader (System.in));

className = in.readLine();

}

catch (IOException ioe)

{

 

System.err.println (“Fejl under indlæsning!”);

System.err.println (ioe.toString());

System.exit (1);

}

 

try

{

getClassInfo (className);

}

catch (ClassNotFoundException cnfe)

{

System.err.println (“Klassen ” + className + ” kunne ikke findes.”);

System.err.println (cnfe.toString());

System.exit (2);

}

}

}

 

Klassen lader brugeren indtaste navnet på en klasse, og man vil så blive præsenteret for en række informationer om den pågældende klasse. Hvis man for eksempel indtaster java.lang.Integer, får man følgende output:

 

Information om java.lang.Integer

 

X Public

O Private

O Protected

O Static

X Final

O Abstract

 

Information om nedarvning:

Superklasse: class java.lang.Number

Interfaces: java.lang.Comparable

 

Variable:

public static final int MIN_VALUE

public static final int MAX_VALUE

public static final java.lang.Class TYPE

 

Constructors

public java.lang.Integer(int);

public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException;

 

Metoder:

public static java.lang.Integer decode (java.lang.String) throws java.lang.NumberFormatException;

public static java.lang.Integer getInteger (java.lang.String);

public static java.lang.Integer getInteger (java.lang.String, int);

public static java.lang.Integer getInteger (java.lang.String, java.lang.Integer);

 

public static int parseInt (java.lang.String) throws java.lang.NumberFormatException;

public static int parseInt (java.lang.String, int) throws java.lang.NumberFormatException;

public static java.lang.String toBinaryString (int);

public static java.lang.String toHexString (int);

public static java.lang.String toOctalString (int);

public static java.lang.String toString (int);

public static java.lang.String toString (int, int);

public static java.lang.Integer valueOf (java.lang.String) throws java.lang.NumberFormatException;

public static java.lang.Integer valueOf (java.lang.String, int) throws java.lang.NumberFormatException;

public boolean equals (java.lang.Object);

public final native java.lang.Class getClass ();

public int hashCode ();

public final native void notify ();

public final native void notifyAll ();

public java.lang.String toString ();

public final void wait () throws java.lang.InterruptedException;

public final native void wait (long) throws java.lang.InterruptedException;

public final void wait (long, int) throws java.lang.InterruptedException;

public byte byteValue ();

public double doubleValue ();

public float floatValue ();

public int intValue ();

public long longValue ();

public short shortValue ();

public int compareTo (java.lang.Integer);

public int compareTo (java.lang.Object);

 

Det første, der sker i metoden getClassInfo, er, at der oprettes en reference til den pågældende klasse. Herefter kaldes en række is-funktioner, der afgør om klassen er et interface, et array eller en primitiv type. Dernæst bruges getModifiers til at bestemme klassens modifiers. Disse angiver for eksempel om klassen er public, protected eller private. Modifiers returneres ikke – som man kunne tro – i en instans af Modifier-klassen, men som en int. Man kan herefter bruge Modifier-klassens statiske funktioner til at fortolke betydningen af værdien:

 

public class Modifier extends Object

{

public static boolean isAbstract(int);

public static boolean isFinal(int);

public static boolean isInterface(int);

public static boolean isNative(int);

public static boolean isPrivate(int);

public static boolean isProtected(int);

public static boolean isPublic(int);

 

public static boolean isStatic(int);

public static boolean isSynchronized(int);

public static boolean isTransient(int);

public static boolean isVolatile(int);

}

 

Som det ses af funktionslisten, er det de samme modifiers, der bruges ved metoder og variable. Efter udskrivning af modifiers udskriver programmet information om nedarvning. Det vil sige navnet på klassens superklasse samt navnene på eventuelt implementerede interfaces. Programmet udskriver kun navnet på den direkte superklasse, men superklassens eventuelle superklasser kan nemt udskrives ved at lave en løkke, der udskriver getSuperClass, indtil denne returnerer null:

 

Class c = cl;

while (c.getSuperClass() != null)

{

System.out.println (c.getSuperClass());

c = c.getSuperClass();

}

 

Selvom en klasse i Java kun kan nedarve fra én klasse, kan den implementere en lang række interfaces. Derfor returnerer funktionen getInterfaces et array af objekter af klassen Class. Interfaces er jo bare en klasse uden kode.

 

Herefter beskriver programmet klassens attributter. En liste over disse kan fås ved et kald til funktionen getFields, der returnerer et array af objekter af klassen Field:

 

public final class Field extends AccesibleObject implements Member

{

public int getModifiers ();

}

 

For hver variabel kaldes den private funktion describeModifiers, der laver en tekstuel beskrivelse af hver variabel.

 

Dernæst udskrives en oversigt over klassens constructors. Disse returneres af funktionen getConstructors, der returnerer et array af objekter af typen Constructor:

 

public final class Constructor extends AccessibleObject implements Member

{

public java.lang.Class[] getExceptionTypes ();

public int getModifiers ();

public java.lang.String getName ();

public java.lang.Class[] getParameterTypes ();

public native java.lang.Object newInstance (java.lang.Object[]) throws

java.lang.InstantiationException, java.lang.IllegalAccessException,

java.lang.IllegalArgumentException,

java.lang.reflect.InvocationTargetException;

}

 

 

 

For at finde ud af, hvilke parametre en constructor tager, kan man kalde funktionen getParameterTypes, der returnerer et array af objekter af klassen Class. Ligeledes kan funktionen getExceptionTypes bruges til at finde ud af, hvilke exceptions kaldet af constructoren kan medføre. En exception er som bekendt også en klasse, og derfor bliver listen over mulige exceptions også returneret i et array af Class-objekter.

 

Klassens metoder behandles på stort set samme måde. En liste over dem hentes ved hjælp af funktionen getMethods, der returnerer metoderne i et array af Method-objekter:

 

public class Method extends AccessibleObject implements Member {

public java.lang.Class[] getExceptionTypes ();

public int getModifiers ();

public java.lang.String getName ();

public java.lang.Class[] getParameterTypes ();

public java.lang.Class getReturnType ();

public native java.lang.Object invoke (java.lang.Object, java.lang.Object[])

throws java.lang.IllegalAccessException,

java.lang.IllegalArgumentException,

java.lang.reflect.InvocationTargetException;

}

 

Bortset fra funktionen getReturnType, der returnerer et Class-objekt angivende funktionens returværdi, er funktionerne de samme som ved Constructor-klassen. Dog skal det nævnes, at metoder aktiveres ved hjælp af invoke, mens constructors kaldes med newInstance.


 

Specielle anvendelser af reflections

 

Reflections bliver flittigt brugt i virtuelle Java-maskiner. Hvis man skriver en virtuel Java-maskine (JVM), er det imidlertid ikke nok at kunne instantiere objekter og kalde metoder. Garbage collection er en vigtig del af Java-sproget. Fra og med version 1.2 indeholder Java også en række klasser, der gør det lettere at håndtere garbage collection. Disse ligger i pakken java.lang.ref.

 

En programstarter

En af de opgaver, en virtuel Java-maskine har, er at starte programmer. Da det vil være for omfattende at gennemgå en hel virtuel maskine her, vil vi nøjes med et eksempel på, hvordan man kan skrive et program, der kan starte andre Java-programmer. Hvert program startes i sin egen tråd, og det er således muligt at lade programmet starte flere programmer samtidigt. Gennem en grafisk brugergrænseflade er det muligt at angive, hvilket program, der skal startes, samt hvilke parametre, programmet skal startes med.

 

Programmet, der kan bruges til at starte de andre programmer, kaldes RunMachine.

 

 

public class RunMachine extends Frame

{

private static RunMachine me;

 

private Label classLabel;

public TextField classText;

 

private Label argLabel;

public TextField argText;

 

private Button start;

private Button stop;

 

public RunMachine()

{

super (“Run Machine”);

 

setLayout (new GridLayout(3,2));

classLabel = new Label (“Klasse:”);

classText = new TextField(20);

 

argLabel = new Label (“Argumenter:”);

argText = new TextField(20);

 

start = new Button (“Start”);

start.addActionListener (new StartListener(this));

 

stop = new Button (“Afslut”);

stop.addActionListener (new StopListener());

 

add(classLabel);

add(classText);

 

add(argLabel);

add(argText);

 

add(start);

add(stop);

 

pack();

}

 

public static final void main(String args[])

{

me = new RunMachine();

me.show();

}

}

 

Denne del af programmet opretter blot den grafiske brugergrænseflade. Et billede af denne kan ses i figur 7-1.

 

 

Figur 7-1: Brugergrænsefladen i Run Machine-programmet.

 

Som det ses tilknyttes en action listener til hver af de to knapper. StopListener er den simpleste af de to – den sørger blot for at stoppe programmet, når der trykkes på Afslut-knappen.

 

class StopListener implements ActionListener

{

public void actionPerformed (ActionEvent e)

{

System.exit(0);

}

}

 

Mere interessant er klassen StartListener, der håndterer tryk på Start-knappen:

 

class StartListener implements ActionListener

{

private RunMachine parent;

 

public StartListener(RunMachine parent)

{

this.parent = parent;

}

 

public void actionPerformed (ActionEvent e)

{

String cl = parent.classText.getText();

String args = parent.argText.getText();

 

if (cl.equals(“”))

{

System.err.println (“Ingen klasse angivet.”);

return;

}

 

// Kopier parametre til array

 

String parameters[] = new String[10];

int i = 0;

int pos = 0;

 

while (args.indexOf(” “, pos) != -1)

{

parameters[i] = args.substring (pos, args.indexOf(” “, pos));

pos = args.indexOf (” “, pos);

i++;

 

}

parameters[i] = args.substring(pos, args.length());

 

// Opret tråd, der udfører programmet

 

RunThread thread = new RunThread (cl, parameters);

thread.start();

}

}

 

Klassens constructor modtager en reference til RunMachine-klassen, så den kan læse de oplysninger, brugeren indtaster. Dette gøres i actionPerformed-metoden. Her kopieres først klassen og strengen indeholdende parametrene til lokale variable. Herefter kontrolleres det, om der er indtastet en klasse – er dette ikke tilfældet, udskrives en fejlmeddelelse.

 

Med det på plads kan man koncentrere sig om de vigtigere ting. For eksempel at konvertere de parametre, der skal overføres til klassens main-metode fra en tekststreng til et array af Strings. Det gøres i de efterfølgende linier. Bemærk, at der her er indsat den begrænsning, at der maksimalt kan indtastes 10 parametre. Dette kan naturligvis ændres efter behov, eller programmeres dynamisk ved at anvende Vector-klassen. Det sidste, actionPerformed foretager, er at oprette en tråd – RunThread – som sørger for at starte programmet. RunThread er defineret således:

 

class RunThread extends Thread

{

private String cl;

private String parameters[];

 

private Method mainMethod = null;

 

public RunThread (String cl, String parameters[])

{

this.cl = cl;

this.parameters = parameters;

}

 

public void start()

{

Class thisClass = null;

 

try

{

thisClass = Class.forName(cl);

}

catch (ClassNotFoundException cnfe)

{

System.err.println (“Klassen kunne ikke findes: ” + cl);

System.err.println (“(” + cl.toString() + “)”);

}

 

Class arguments[] = new Class[1];

 

arguments[0] = parameters.getClass();

 

try

{

mainMethod = thisClass.getMethod(“main”, arguments);

}

catch (NoSuchMethodException nsme)

{

System.err.println (“Klassen ” + cl + ” indeholder ikke metoden main.”);

System.err.println (“(” + nsme.toString() + “)”);

}

run();

}

 

public void run()

{

Object args[] = new Object[1];

args[0] = parameters;

 

try

{

mainMethod.invoke (null, args);

}

catch (IllegalAccessException iae)

{

System.err.println (“main-metoden er ikke tilgængelig.”);

System.err.println (“(” + iae.toString() + “)”);

}

catch (IllegalArgumentException iax)

{

// Denne exception opstår aldrig, da første parameter i main.invoke er null

}

catch (NullPointerException npe)

{

System.err.println (“main-metoden er ikke defineret som statisk.”);

System.err.println (“(” + npe.toString() + “)”);

}

catch (InvocationTargetException ite)

{

System.err.println (“main-metoden er defineret forkert.”);

System.err.println (“(” + ite.toString() + “)”);

}

 

}

}

 

Constructoren i RunThread-klassen modtager både navnet på den klasse, der skal startes, samt det array, der indeholder de parametre, der skal overføres til klassens main-metode.

 

 

RunThread er – som navnet antyder – en tråd, og når denne startes, udføres dens start-metode. I denne oprettes der først en reference til et Class-objekt, der svarer til den klasse, der skal startes. Hvis klassen ikke kunne findes, opstår en ClassNotFoundException.

 

Herefter oprettes et array – arguments – der skal indeholde Class-objekter svarende til de typer, metoden bruger som parametre. I dette tilfælde ved vi, at main-metoden kun tager én parameter, nemlig et array af Strings. Variablen parameters er netop et String-array, og den kan således bruges til at skaffe en reference til et Class-objekt, der angiver et array af Strings. Dette gøres ved at kalde getClass-metoden på parameters-objektet.

 

Det er herefter simpelt at opnå en reference til selve main-metoden. Det sker ved hjælp af getMethod-metoden. Findes main­-metoden ikke, opstår en NoSuchMethodException, og man kan konludere, at klassen ikke er ment som et selvstændigt Java-program.

 

Når disse indledende manøvrer er udført, overgår kontrollen til run-metoden.

 

Denne metode starter med at oprette et array af objekter, som skal bruges til at holde de parametre, der skal sendes til main-metoden. Igen ved vi, at det kun drejer sig om et enkelt parameter, og det er derfor sikkert at angive størrelsen af args-arrayet statisk. Man skal passe på, at man ikke forfalder til blot at sende parameters-arrayet med som parametre. Dette vil ikke medføre en fejl ved kompilering, da et array af Strings sagtens kan konverteres til et array af Objects. Til gengæld vil det medføre en fejl, når programmet udføres, fordi en passende main-metode ikke kan findes. Angiver man for eksempel to parametre til main-funktionen vil programmet lede efter en metode med signaturen main (String, String) og ikke main (String[]), som jo er den rigtige main-metode. Derfor er det vigtigt at oprette et Object-array og lægge parametrene heri.

 

Herefter skal main-metoden kaldes. Dette gøres ved at kalde invoke på det mainMethod-objekt, som blev fundet i start-metoden. Det første parameter i invoke angiver normalt det objekt, metoden skal udføres på, men da main-metoden altid skal være erklæret statisk, kan man her blot angive null. Det betyder, at man er fri for at instantiere objektet, og fri for at håndtere de exceptions, udførslen af constructoren kan medføre. Selvom invoke-kaldet er simpelt, kan der opstå en række exceptions.

 

IllegalAccessException opstår, hvis main-metoden ikke er public. IllegalArgumentException kan ikke opstå i dette tilfælde, men den skal alligevel inkluderes i catch-blokken, fordi den er en del af invoke’s throws-blok. Denne exception opstår normalt, når metoden ikke eksisterer på det objekt, man forsøger at kalde den på. Her angives imidlertid intet objekt – metoden er jo statisk – og derfor kan denne exception aldrig forekomme. Til gengæld kan NullPointerException opstå. Det sker, hvis main-metoden ikke er defineret statisk som forventet. I så fald vil programmet forsøge at udføre main-metoden på null, og det medfører naturligvis en NullPointerException. InvocationTargetException opstår, hvis de aktuelle og formelle parametre ikke passer sammen. Dette kan kun ske, hvis main-metoden er defineret forkert.

 


Sammenfatning

 

FAQ

 

Hvordan bruger man primitive typer i forbindelse med reflections?

Primitive typer – som for eksempel int – er ikke klasser, og man kan således ikke oprette et Class-objekt, der refererer til typen. Til hver primitiv type hører en wrapper-klasse, der er en klasse, der indeholder værdier af samme type som den primitive type. Hver af disse klasser ­– Boolean, Byte, Character, Double, Float, Integer, Long  og Short – har en statisk konstant ved navn TYPE. Denne indeholder et Class-objekt, der svarer til den primitive type, klassen er en wrapper for.

 

Hvordan opretter man et array dynamisk?

java.lang.reflect.Array indeholder de funktioner, der skal bruges til at oprette et array dynamisk. Klassen indeholder to metoder til formålet:

 

public static Object newInstance (Class componentType, int length) throws

NegativeArraySizeException;

 

og

 

public static Object newInstance (Class componentType, int[] dimensions) throws

IllegalArgumentException, NegativeArraySizeException;

 

Den første bruges, hvis man vil oprette et en-dimensionalt array. Hvis man vil oprette et array med plads til 10 Strings, skriver man altså:

 

public static final void main (String args[]) throws

NegativeArraySizeException, ClassNotFoundException

{

Object o = java.lang.reflect.Array.newInstance (

Class.forName (“java.lang.String”),10);

}

 

Hvis man vil oprette et fler-dimensionalt array, skal man huske på, at hver dimension ikke behøver at have den samme længde. Derfor skal man først oprette et array af int¸ der angiver længden af hver dimension. Almindeligvis kan man gøre det på denne måde:

 

public static final void main (String args[])

{

String array[][] = new String[3][];

array[0] = new String [4];

array[1] = new String [3];

 

array[2] = new String [2];

}

 

Skal det samme opnås ved hjælp af reflections, skal det gøres på denne måde:

 

public static final void main (String args[]) throws

IllegalArgumentException, NegativeArraySizeException,

ClassNotFoundException

{

int arraySize[] = {4,3,2};

Object array = java.lang.reflect.Array.newInstance (Class.forName

(“java.lang.String”), arraySize);

 

}

Hvordan tilgås elementer i et array?

Som det ses af nedenstående signatur indeholder Array-klassen en række set- og get-funktioner, der kan bruges til at læse og ændre indholdet af et element i et array.

 

public final class Array extends Object

{

public static native byte getByte (java.lang.Object, int) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native char getChar (java.lang.Object, int) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

 

public static native double getDouble (java.lang.Object, int) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native float getFloat (java.lang.Object, int) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native int getInt (java.lang.Object, int) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native int getLength (java.lang.Object) throws java.lang.IllegalArgumentException;

public static native long getLong (java.lang.Object, int) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native short getShort (java.lang.Object, int) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native void setBoolean (java.lang.Object, int, boolean) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native void setByte (java.lang.Object, int, byte) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native void setChar (java.lang.Object, int, char) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native void setDouble (java.lang.Object, int, double) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native void setFloat (java.lang.Object, int, float) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native void setInt (java.lang.Object, int, int) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native void setLong (java.lang.Object, int, long) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

public static native void setShort (java.lang.Object, int, short) throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException;

}

 

Alle metoderne er statiske, og de skal således ikke kaldes på selve arrayet – i stedet skal arrayet sendes med som parameter. Det andet parameter i metodekaldende er det indeks i arrayet, der skal læses eller ændres. Kald til metoderne giver mulighed for at tre forskellige exceptions kan opstå. NullPointerException opstår, hvis det array, der angives som parameter, er null. IllegalArgumentException opstår, hvis den første parameter i metodekaldet ikke er et array, mens ArrayIndexOutOfBoundsException opstår, hvis indeks angives med en negativ værdi, eller hvis indeks er større end størrelsen af arrayet. Nedenstående eksempel viser, hvordan et int-array opretets, og hvordan en værdi indsættes i arrayet.

 

Object array = java.lang.reflect.Array.newInstance (Integer.TYPE, 5);

java.lang.reflect.Array.setInt(array, 3, 10);

 

Eksemplet opretter først et array med 5 pladser. Herefter sættes den fjerde plads i arrayet – den med indeks 3 – til værdien 10.

 

Hvis en klasses constructor er overloaded, kan man så kalde constructor.newInstance på en tilfældig constructor i klassen, og ved hjælp af parametrene i newInstance-kaldet angive, hvilken constructor, der skal kaldes?

 

Nej. Man skal kalde newInstance på den constructor, der skal udføres. Man vælger den rigtige constructor ved at foretage et kald til Class.getConstructor().

 

Det samme gør sig i øvrigt gældende for metoder, hvor Method.Invoke heller ikke kan bruges til at vælge mellem flere forskellige overloadede metoder.

Du læser en gammel bog
Den bog, du læser her, er fra 1998, og mange ting kan have ændret sig siden da.
Vi håber, at du stadig kan finde relevant information i den.
Hvis du vil læse aktuelle oplysninger om de avancerede dele af Java, anbefaler vi
bogen Core Java – Advanced Features

Comments are closed.