Liigu peamise sisu juurde

Väljad

Sissejuhatus

Kui sul on olemas Class<?> objekt, saad sellelt küsida, millised väljad on vastaval tüübil deklareeritud. Igat välja esindab java.lang.reflect.Field objekt - see on samuti väärtus, mida saab salvestada muutujasse, edasi anda ja mille meetodeid välja kutsuda.

Field objekt ei salvesta välja väärtust ennast. See kirjeldab välja - selle nime, tüüpi, modifikaatoreid ja annotatsioone - ning pakub meetodeid selle välja väärtuse lugemiseks või kirjutamiseks konkreetse objekti peal.

Väljade loetlemine

Toome näiteks sellise klassi:

public class Book {
public String title;
private String author;
private int year;
}

Selleks, et klassi välju loetleda, tuleks kasutada kas getDeclaredFields() või getFields() meetodit:

for (Field field : Book.class.getDeclaredFields()) {
System.out.println(field.getType().getSimpleName() + " " + field.getName());
}
// String title
// String author
// int year

getDeclaredFields() tagastab kõik klassis deklareeritud väljad sõltumata nende nähtavusest. getFields() samas tagastab ainult klassi public-väljad, kuid sisaldab ka ülemklassidest päritud välju. Enamiku reflektsiooni kasutusjuhtude puhul epaks piisama getDeclaredFields() meetodist.

Iga välja kohta saab teha mitmeid kasulikke päringuid:

field.getName();         // "author"
field.getType(); // Class<?> - String.class
field.getModifiers(); // int - bit flags, check with Modifier
field.getAnnotations(); // Annotation[]

Pane tähele, et getModifiers() tagastab täisarvu. Läbi Modifier abiklassi on võimalik seda tõlgendada inimloetavale kujule:

int mods = field.getModifiers();
Modifier.isPrivate(mods); // true
Modifier.isStatic(mods); // false
Modifier.isFinal(mods); // false

Välja väärtuse lugemine

Konkreetse objekti välja hetkeväärtuse lugemiseks saab kasutada field.get(obj) meetodit:

Book book = new Book("Clean Code", "Robert C. Martin", 2008);

Field titleField = Book.class.getDeclaredField("title");
Object value = titleField.get(book);

System.out.println(value); // Clean Code

Selle tagastustüüp on Object, sest välja tüüp ei pruugi kompileerimise ajal teada olla. Kui tüüp on teada, siis on võimalik läbi viia tüübiteisendust, kuid sellega kaob osa paindlikusest, mida reflektsioon algselt pakkus.

getDeclaredField otsib üles ühe konkreetse välja ette antud nime järgi. Kui välja ei eksisteeri, siis visatakse NoSuchFieldException erind.

Välja väärtuse muutmine

Väljale uue väärtuse kirjutamiseks saab kasutada field.set(obj, value) meetodit:

Field yearField = Book.class.getDeclaredField("year");
yearField.set(book, 2009);

System.out.println(book.year); // 2009

Väärtus peab olema väljaga ühilduvat tüüpi, vastasel juhul viskab meetod IllegalArgumentException erindi.

Ligipääs privaatsetele väljadele

Kui proovida ülaltoodud lugemis- või kirjutamisnäiteid private välja peal kasutada, siis tulemuseks tekib IllegalAccessException erind:

java.lang.IllegalAccessException: class X cannot access a member of class Book
with modifiers "private"

See on Java tavapärane nähtavuskontroll, mida rakendatakse ka refleksiooni kaudu juurdepääsul. Sellest möödahiilimiseks tuleb Field objekt kõigepealt ligipääsetavaks märkida:

Field authorField = Book.class.getDeclaredField("author");
authorField.setAccessible(true);

System.out.println(authorField.get(book)); // Robert C. Martin
authorField.set(book, "Uncle Bob");

authorField.setAccessible(false);

setAccessible(true) keelab nähtavuskontrolli ainult selle konkreetse Field objekti jaoks. See ei muuda välja ennast - muu kood näeb seda endiselt private väljana ega saa sellele otse ligi.

hoiatus

setAccessible(true) kasutamine raamistike või JDK klasside puhul on mõnikord vajalik, kuid see rikub kapseldamist, mille klassi autor on teadlikult loonud. Kui klassis on private väli, siis on otsustatud seda mitte avalikult eksponeerida. Väljastpoolt selle muutmine võib jätta objekti ebaselgesse olekusse. Kasuta seda võimalust ainult siis, kui see on tõesti vajalik - näiteks serialiseerimise, testimise või päris raamistikutaseme töö jaoks.

Staatilised väljad

Staatiline väli kuulub klassile, mitte konkreetsele objekti eksemplarile. Sellise välja lugemisel või muutmisel tuleb sihtobjektina anda null:

public class Constants {
public static int MAX_BOOKS = 1000;
}
Field max = Constants.class.getDeclaredField("MAX_BOOKS");
int value = (int) max.get(null); // 1000
max.set(null, 5000);

Praktiline näide

Toome näiteks koodi, mille eesmärk on kopeerida ühised väljad kahe erinevat tüüpi objekti vahel. Selline lähenemine on kasulik näiteks siis, kui andmeid teisendatakse andmebaasimudelist vaatemudeliks (view model) või ühest andmestruktuurist teise.

public static void copyCommonFields(Object source, Object target)
throws IllegalAccessException {
Class<?> targetType = target.getClass();
for (Field sourceField : source.getClass().getDeclaredFields()) {
try {
Field targetField = targetType.getDeclaredField(sourceField.getName());
if (targetField.getType().equals(sourceField.getType())) {
sourceField.setAccessible(true);
targetField.setAccessible(true);
targetField.set(target, sourceField.get(source));
}
} catch (NoSuchFieldException ignored) {
// target has no matching field — skip
}
}
}

Funktsioon ei maini ühtegi konkreetset tüüpi. See käib läbi lähteobjekti väljad, otsib sihtobjektilt üles sama nime ja tüübiga välja ning kopeerib väärtuse, kui selline väli leitakse. Sarnast lähenemist kasutavad paljud objektide kaardistamise (object mapping) teegid.

Field-iga seotud erandid

Field objektidega töötamisel tuleb arvestada kolme olulise erindiga, mida tuleb kas käsitleda või edasi visata:

ErandMillal tekib
NoSuchFieldExceptiongetDeclaredField("name") ei leia selle nimega välja
IllegalAccessExceptionNähtavuskontroll blokeerib lugemise või kirjutamise
IllegalArgumentExceptionset meetodile antud väärtus ei sobi välja tüübiga (kontrollimata erand / unchecked exception)

Väikestes näidetes kasutatakse sageli lihtsalt throws Exception või mähitakse erind RuntimeException-i sisse. Toodangus tasub aga teadlikult otsustada, mida iga selline tõrge tähendab: kas tegemist on valesti seadistatud klassiga, programmiveaga või taastatava sisendveaga.