Egy másik késztetés az
osztályinformációk
kiderítésére futásidõben
képességet ad létrehozni és
végrehajtani objektumokat távoli platformon a
hálózaton keresztül. Ezt hívják távoli metódus hívásnak(RMI)
és ez biztosítja egy Java programnak az
objektumoknak a kiosztását
számítógépeken keresztül. Ez a
disztribució történhet számos okból:
például, amikor csinálsz egy intenzív
számítási feladatot és részekre
akarod tördelni, és darabokat adni egy
számítógépnek - amely kihasználatlan
-
a sebesség növelése céljából.
Néhány szituációban helyet kell foglalnod a
feladat egyéni típusainak (pl. "Üzleti
szabályok",
többszörös kliens/server
architekturáknál) speciális gépeken,
így ez a számítógép egy
közös tár lesz, és leírja a
tevékenységeket, és ezt bárki a rendszerből
könnyedén befolyásolhatja. (Ez egy érdekes
fejlesztés mióta a számítógép
létezik, a szoftverfejlesztés közben egyszerűen
módosíthatjuk a programot. Tovább ezen az
úton, az osztott számitás
speciális hardvert követel ami jó a
részfeladatokhoz-
például: Matrix inveziójának
előállításához- de alkalmatlan illetve
túl drága általános célú
programozáshoz.
A Class osztály (Az előző fejezetben leirt) támogatja a tükrözés koncepcióját, és ez egy kiegészítő könyvtár, java.lang.reflect, osztályok Mezőivel, Metódussal és Konstructorral (ezek implementálva vannak a Member Interface-ben).
Ezeknek az objektumoknak a típusait a JVM hozza létre
futási időben, hogy egyeztesse a megfelelő tagokat a
névtelen osztályban. Ezután
használhatóak a Konstruktorok új objektum létrehozásánál, a get() és set() metódusokat, hogy olvassuk, illetve modosítsuk azokat a mezőket, amelyek társítva vannak a Field objektummal, és az Invoke() metódust, hogy hívjunk egy metódust, amely társítva van egy Method objektummal. Ráadásul, hívhatod a megfelelő metódusokat a getFields(), getMethods(), getConstructors()
-al stb. és a visszatérési érték egy
tömb lesz, melyben az objektumok mezői, metódusai és
konstrutorai. (Többet is megtudhatsz ha utánanézel a
Class osztálynak a JDK
dokumentációban.) Az ismeretlen objektumok osztály
információi egyértelműen meghatározottak
futási időben és semmit nem szükséges tudni
róla fordításnál.
Fontos, hogy rájőjjünk, semmi mágikus nincs a
tükröződésben. Ha használod a
tükröződést egy névtelen típus egy objektuma egymásra hat, a JVM
egyszerűen megnézi az objektumot és látja, hogy
tartozik hozzá egy osztály (pont mint a megszokott
RTTI-nél) de ezután miután történne
bármi más a Class objektum betöltődik. Így a .class
fiájl, mint egy egyéni típus még
elérhetővé válik a JVM számára, a
helyi gépen vagy a hálózaton keresztül.
Tehát az igazi különbség az RTTI és
tükröződés között: RTTI-el a
fordító megnyitja és megvizsgálja a .class
fájl fordításkor. Egy másik mód, meg
kell hívnod az objektum összes metódusát a
"normális" úton. Tükröződéssel, a .class fájl elérhetetlen fordításkor; a futási környezet nyitja meg és ellenőrzi ezt.
Az osztály metódus kifejtő
Szüség lehet időközönként a reflection
eszközök közvetlen használatára; ezek egy
nyelvben vannak, hogy támogassák más Java jelleget
is úgy mint objektum sorozatát (12. fejezet) és
JavaBeans (14. fejezet). Van amikor ez egészen hasznos, hogy
dinamikusan is elő tud állni az információ egy
osztályról. Egy módfelett hasznos eszköz az
osztály metódus kifejtő. Ahogy az előbb
említettük, megnézi az osztály
definíció forrás kódot vagy JDK
dokumentációt és csak a metódusokat
mutatja, amelyek defináltak vagy túlterheltek, azok
osztály definícióik nélkül. De lehet
egy tucatnál is több elérhető számodra,
melyek még az alaposztályokból jönnek.
Ezeknek a megállapítása fáradságos
és időrabló¹. Szerencsére, reflection
gondoskodik az írásról egyszerűen, ami
automatikusan meg fogja mutatni a teljes interface-t. Itt egy
példa a használatára:
¹ Főleg a múltban. Sun nagyban javítja a HTML
Java dokumentációt , így ennek
segítségével könnyebb látni az alap
osztály metódusokat.
//: c10:ShowMethods.java
// Reflekció használata, hogy megnutassuk az összes metódusát az osztálynak,
// még ha azok az alap osztályban vannak definiálva is.
// {Args: ShowMethods}
import java.lang.reflect.*;
import java.util.regex.*;
public class ShowMethods {
private static final String usage =
"usage: \n" +
"ShowMethods pified.class.name\n" +
"To show all methods in class or: \n" +
"ShowMethods pified.class.name word\n" +
"To search for methods involving 'word'";
private static Pattern p = Pattern.compile("\\w+\\.");
public static void main(String[] args) {
if(args.length < 1) {
System.out.println(usage);
System.exit(0);
}
int lines = 0;
try {
Class c = Class.forName(args[0]);
Method[] m = c.getMethods();
Constructor[] ctor = c.getConstructors();
if(args.length == 1) {
for(int i = 0; i < m.length; i++)
System.out.println(
p.matcher(m[i].toString()).replaceAll(""));
for(int i = 0; i < ctor.length; i++)
System.out.println(
p.matcher(ctor[i].toString()).replaceAll(""));
lines = m.length + ctor.length;
} else {
for(int i = 0; i < m.length; i++)
if(m[i].toString().indexOf(args[1]) != -1) {
System.out.println(
p.matcher(m[i].toString()).replaceAll(""));
lines++;
}
for(int i = 0; i < ctor.length; i++)
if(ctor[i].toString().indexOf(args[1]) != -1) {
System.out.println(p.matcher(
ctor[i].toString()).replaceAll(""));
lines++;
}
}
} catch(ClassNotFoundException e) {
System.out.println("No such class: " + e);
}
}
} ///:~
A Class metódusok getMethod() és getConstructors() egy Metódus tömbbel és Konstructor
tömbbel térnek vissza. Ezeknek az osztályoknak a
távolabbi metódusait szétdarabolja a névre,
argumentumaira, és visszatér a metódusok
értékeivel, amiket feltüntet. De használhatod
a toString()-et is ahogyan a példában tették: előállít egy Stringet
a teljes Metódus aláírással. A
kód többi részét előállítja a
parancs sor információ, megállapítja, ha
egy egyéni aláírás illik a cél
stringre (használd: indexOf()), és leteszi a név módosítást.
A név darabja a "java.lang.String"-ben van, Java JDK 1.4 szabályos kifejezés
kínál egy hatékony és tömör
eszközt, amely már elérhető néhány
nyelvben sok éve. Láthattad a szabályos
kifejezések egyszerű kezelését a com.bruceekel.simple.Test osztály expect()
utasításában. A fenti példában,
láthattad az alap kódolási
lépéseket, szüségszerűen használtunk
előírás szerinti kifejezéseket a programjainkban.
A java.util.regex
importálása után, a
fordításnál a szabályos kifejezések
először a static Pattern.compile()
metódust használják, amely létrehozza a
Pattern objektumot a string argumentum használatával.
Ebben az esetben az argumentum:
"\\w+\\."
Ahhoz, hogy megértsük ezt vagy más reguláris
kifejezést meg kell néznünk a JDK
dokumentációt a java.util.regex.Pattern című résznél. az egyikben a '\w'
szerepelt, melynek jelentése "egy szó karakter:
[a-zA-Z_0-9]." a '+' jelentése " egy vagy több megelőző
kifejezés" - igy ebben az esetben, egy vagy több szó
karakterek - és a '\' létrehoz egy literális
periódust (inkább mint a periódus operátor
meynek jelentése "bármely karakter" a reguláris
kifejezésből). Így ez a kifejezés fogja
összekötni a szó karakterek egymást követő
tagjait a periódusban, amely pontosan az amire
szükségünk van a módosítások
rögzítéséhez.
Ezek után van neked egy lefordított Pattern objektumod, használhatod a matcher() metódus hívására, átadni a stringet amit keresni akarunk. A matcher() metódus készít egy Matcher objektumot,
melynek van egy halmaz operációja, melyeket
kiválaszthatod (az összeset láthatod a JDK
dokumentációban: java.util.regex.Mather). Itt a replaceAll() metódus
arra használatos, hogy felcserlje az összes match-et
üres stringre- lényegében törli a match-eket.
Egy még tömörebb alternativa, ha használod a
reguláris kifejezéseket, azokkal
elkészítheted a String osztályt. Például az előbb használt replaceAll() a fenti programban akár a következőket át is lehetett volna írni erről:
p.matcher(ctor[i].toString()).replaceAll("")
erre:
ctor[i].toString().replaceAll("\\w+\\.", "")
előfordítás nélkül a reguláris
kifejezés. Ez a forma jó az egyszerhasználatos
reguláris kifejezéseknél, de az
előfordított forma jelentősen hatékonyabb ha
szükséged van a reguláris kifejezés
többszörös használatára, ebben az esetben
jó ez a példa.
Ez a példa megmutatja a reflekciót működés közben, amióta az eredményt a Class.forName()
produkálja, nem tudhatjuk fordításkor azt,
és következésképpen az összes
metódus információ futási időben fejtődik
ki. Ha tanulmányozod a JDK dokumentációt a
reflekcóról akkor látni fogod: van
elég támogatás, hogy valóban
csináljon egy wgy metódus hívást egy
objektumon ami teljesen névtelen fordításkor (erre
lesz példa a későbbiekben a könyben). Bár
kezdetben ez olyasmi, amiről nem is gondolnád, hogy valaha is
szükséged lesz rá, ám a teljes
reflekció értéke egészen meglepő.
Egy rávilágító próba futtatni:
java ShowMethods ShowMethods
Ez produkál egy listát ami magába foglalja a publikus alapértelmezett
konstruktorokat, még a kódokat is láthatod,
melyeket nem a konstruktorok definiáltak. A konstruktor az egyik
amit a fordító automatikusan szintetizál. Ha
ezután csinálsz ShowMethods -ot egy nem publikus osztályon
(ez csomag hazzáférhető), a hozzáadott
alaértelmezett konstruktor nem mutat távolabbra a
kimenetben. A hozzáadott alaértelmezett konstruktor
automatikusan ugyanazt a hozzáférhetőséget adja,
mint az osztálynak.
Másik érdekes tapasztalat, hogy a java ShowMethods segítségül hívja a java.lang.String-et char, int, String, stb.
extra argumentumokkal. Ez az eszköz időmegtakarító
tud lenni, miközben programozol, amikor nem emlékszel egy
osztélynak az egyéni metódusára és
nem akarsz keresgélni az indexek vagy az osztály
hierarchia között a JDK dokumentációban vagy ha
nem tudod melyik osztállyal miket lehet csinálni.
Például: Color (szín) objektumok.
A 14. fejezet tartalmazza ennek a programnak a GUI
(felhasználói felület) verzióját
(testreszabható az információ kifejtése a
rugalmas komponenseknél) így el is hagyható
futáskor,
miközben írod a kódot, a gyorskeresés
engedélyezve van.
Összegzés
RTTI megengedi neked, hogy
kiderítsd a típus információkat ismeretlen
osztály hivatkozásokról. Ez elég
összetett, hogy a kezdők rosszul kezeljék, ezt kell
elsajátítani mielőtt polimorf metódusokat
hívnánk. Sok ember procedurális
háttérrel jön, nem olyan bonyolult megszervezni az ő programjaikat a switch
utasítás halmazaival. Elvégezhetik ezt az RTTI-vel
és így elveszthetik a polimorfizmus fontos
változóit a program fejlesztésénél
és a karbantartás. A Java szándéka,
hogy használjuk a polimorf metódus
hívásokat a kódon belül, és az RTTI-t
csak akkor használjuk, amikor muszály.
Azonban, a polimorf metódus hívások
használata megköveteli, hogy az alap-osztály
definíciókat ellenörzés alatt tartsuk, mert
néhány helyen meghosszabbíthatja a programod, fel
kell fedezned azt, hogy az alap osztályok nem foglalják
magukba a metódust, amelyre szüséged van. Ha az alap
osztály egy könyvtárban található vagy
másvalaki tarja irányítás alatt, akkor egy
megoldás a problémára az RTTI:
örökölhetsz egy új típust és
hoozáadhatod az extra metódusodat.
Máskülönben a kódban
megállapíthatod a helyét az egyéni
típusodnak és egy speciális metódus
segítségével meghívhatod. Ez nem
semmisíti meg a program polimorfizmusát és
kiterjeszthetőségét, mert új típus
hozzáadása nem fog megkövetelni keresést az
összekötött utasításokra a programodban.
Azonban, amikor hozzáadsz új kódot a
főrészhez, az megkövetel új tulajdonságot, Az
RTTI-t kell használnod, hogy megállapítsd az
egyéni típusod.
A tulajdonságok elhelyezése az alap osztályokban
egy egyéni osztály előnyére válhat, az
összes más osztályok származtathatók
az alap követelmény néhány semmitmondó
metódus csonkból. Ezt az interface csinálja,
kevésbé vlágos és bosszantó ha ezek
túlterhelt absztrakt metódusok és az alap
osztályból lettek származtatva.
Például: tekintsünk egy osztály
hierarchiát a hangszerek bemutatásával. Meg kell
tisztítani a hangszereket a zenekarban. Egy
választás ha beteszed a clearSpitValve() metódust az alap osztályban, amely az Instrument (hangszerek), de ez zavaró, mert magába foglalja az ütős -és Elektronikus
hangszereket szintén van fúvókája. RTTI
nyújt sokkal ésszerűbb bontást ebben az
esetben, mert lehet hely egy metódus számára a
különleges osztályban (FúvósHangszerek
ebben az esetben), itt ez helyénvaló. Azonban egy
még helyénvalóbb bontás is
elképzelhető, ha a prepareInstrument()
metódust beletesszük az alap osztályba, de nem
láthattad, amikor először próbáltad megoldani
a problémát, és lehet, hogy tévesen
használtad volna az RTTI-t.