Liigu peamise sisu juurde

Instantsid ja meetodid

Sissejuhatus

Klassi struktuuri lugemine on vaid üks pool sellest, mida reflektsioon võimaldab. Teine pool seisneb selliste klasside eksemplaride loomises, mille nime saad teada alles programmi töö ajal, ning nende meetodite väljakutsumises ilma nende nimedele oma koodis otseselt viitamata. See on mehhanism, mis võimaldab sõltuvuste süstimist suuremas mastaabis ning mis on ka kasutusel reaalsetes projektides (teadagi Spring oma tuumas on sõltuvuste süstimise raamistik).

Eksemplaride loomine

Igat konstruktorit esindab Constructor<?> objekt, mis saadakse klassilt.

Argumendita konstruktor

Kõige tavalisem juhtum on vaikimisi parameetriteta konstruktor:

Class<?> type = Class.forName("com.example.library.Book");

Constructor<?> ctor = type.getDeclaredConstructor();
Object book = ctor.newInstance();

getDeclaredConstructor() ilma argumentideta tagastab argumendita konstruktori või viskab NoSuchMethodException erindi, kui sellist konstruktorit ei eksisteeri. newInstance() kutsub selle välja ja tagastab uue objekti.

Kui argumendita konstruktor on private, saab kasutada sama setAccessible(true) nippi nagu väljade puhul:

Constructor<?> ctor = type.getDeclaredConstructor();
ctor.setAccessible(true);
Object instance = ctor.newInstance();

Parameetritega konstruktorid

Et leida konstruktor, mis võtab kindlat tüüpi parameetreid, tuleb need tüübid edasi anda meetodile getDeclaredConstructor:

Constructor<?> ctor = Book.class.getDeclaredConstructor(
String.class, String.class, int.class
);
Object book = ctor.newInstance("Clean Code", "Robert C. Martin", 2008);

getDeclaredConstructor meetodile antud klassiobjektid kirjeldavad konstruktori signatuuri. newInstance meetodile antud väärtused on tegelikud argumendid. Mõlemad loendid peavad vastama argumentide arvu, tüüpide ja järjekorra poolest.

nõuanne

Class klassil on olemas ka aegunud lühimeetod Class.newInstance(), mis kutsub otse välja argumendita konstruktori. Seda ei soovitata kasutada: selle erindite käsitlemise semantika on segane ning see märgiti aegunuks Java 9 versioonis. Kasuta selle asemel getDeclaredConstructor().newInstance().

Meetoditega seotud tegevused

Meetodeid esindab java.lang.reflect.Method klass. Selle hankimine sarnaneb väljadega:

Method method = Book.class.getDeclaredMethod("setYear", int.class);

Esimene argument on meetodi nimi, ülejäänud on parameetrite tüübid. Kui sellise täpse signatuuriga meetodit ei eksisteeri, viskab getDeclaredMethod erindi NoSuchMethodException.

Meetodi väljakutsumine

invoke käivitab meetodi etteantud sihtobjektil koos etteantud argumentide loendiga:

Book book = new Book("Clean Code", "Robert C. Martin", 2008);
Method setYear = Book.class.getDeclaredMethod("setYear", int.class);

setYear.invoke(book, 2009);

invoke tagastusväärtus on Object, void meetodite puhul on see alati null.

Method getTitle = Book.class.getDeclaredMethod("getTitle");
Object title = getTitle.invoke(book); // "Clean Code"

Staatiliste meetodite puhul tuleb anda sihtobjektina edasi null:

Method parseInt = Integer.class.getDeclaredMethod("parseInt", String.class);
Object result = parseInt.invoke(null, "42");

Meetodite loendamine

Klassis olevate meetodite avastamiseks saab kasutada getDeclaredMethods meetodi:

for (Method method : Book.class.getDeclaredMethods()) {
System.out.println(method.getName() + " " + Arrays.toString(method.getParameterTypes()));
}

Seda võtet kasutavad testiraamistikud, käskude vahendajad (command dispatchers) ja sarnased tööriistad, et leida neile olulised meetodid - tavaliselt koos annotatsioonidega, mida käsitleme järgmistes peatükkides.

InvocationTargetException ja IllegalAccessException

Meetodite või konstruktorite väljakutsumisel esineb sageli kaks erinditüüpi:

InvocationTargetException visatakse invoke ja newInstance poolt siis, kui sihtmeetod ise viskab erindi. Algne erind mähitakse selle sisse ning selle saab kätte meetodiga getCause():

try {
method.invoke(target);
} catch (InvocationTargetException e) {
Throwable original = e.getCause();
// handle the actual exception the method threw
}

Selline mähkimine on tüütu, kuid vajalik: invoke on reflektiivne väljakutse ning selle deklareeritud erindid kirjeldavad probleeme refleksioonimehhanismis endas. Kõik, mida väljakutsutav meetod ise viskab, on midagi muud ning seepärast mähitakse see ümber.

IllegalAccessException visatakse siis, kui meetod ei ole sellest kohast ligipääsetav, kust seda välja kutsutakse - selle tekkimisel on lahendus kasutada setAccessible(true) võtet.

ErindMillal
NoSuchMethodExceptionOtsitakse meetodit või konstruktorit, mida ei eksisteeri
IllegalAccessExceptionNähtavuskontroll blokeerib väljakutse
IllegalArgumentExceptionArgumentide loend ei vasta signatuurile
InvocationTargetExceptionMeetod ise viskas erindi
InstantiationExceptionnewInstance abstraktse klassi puhul

Praktiline näide

Nendest osadest piisab väikese käsuvahendaja (command dispatcher) ehitamiseks. Kui klassis on iga käsu jaoks eraldi meetod, saab õige meetodi käivitada sisendist saadud sõne põhjal:

public class GreetingCommands {
public void hello(String name) {
System.out.println("Hello, " + name);
}

public void bye(String name) {
System.out.println("Goodbye, " + name);
}
}
public static void run(Object target, String methodName, String arg) throws Exception {
Method m = target.getClass().getDeclaredMethod(methodName, String.class);
m.invoke(target, arg);
}
GreetingCommands commands = new GreetingCommands();
run(commands, "hello", "world"); // Hello, world
run(commands, "bye", "world"); // Goodbye, world

Vahendaja ei tea, millised käsud eksisteerivad - klassi saab lisada uusi käske ilma vahendajat muutmata. Selline „leia meetod nime järgi ja kutsu see välja” mehhanism on paljude raamistikute tuumikuks. Järgmistes peatükkides tutvume annotatsioonidega, et raamistik saaks meetodeid tuvastada mitte ainult nime, vaid ka eesmärgi järgi.