Az Osztály Objektum
Ahhoz hogy megértsük hogy működik az RTTI Java-ban, ahhoz előszöt
tisztában kell lenni azzal,
hogy a típusinformáció hogyan ábárzolódik a futási
idő alatt. Ez egy különleges objektum segitségével
valósul meg, ez a osztály
objektum,ami információt tartalmaz az
osztályról. Valójában, az osztály objektumot
arra hasznájuk, hogy
létrehozzunk minden reguláris objektumot az osztályból.
Létezik egy osztály objektum minden osztályhoz ami a program része. Tehát,
Minden alkalommal amikor új osztályt hozunk létre, egy Osztály objektum is létrejön (és tárolódik,
értelemszerűen,
egy azonosító.class File-ban). Futtatáskor, amikor létre akarjuk hozni ezt a fajta
objektumot, a Java
Virtual Machine (JVM), ami végrahajtja a programot, először ellenőrzi, hogy létezik e Osztály objektum
a betöltött típushoz. Ha nem,
a JVM betölti, megkeresve a
.class filet az adott néven. Ráadásul, a
Java program nincs teljesen betöltve mielőtt elindul,
ami megkölönbözteti a legtöbb más
nyelvtől.
Amikor az osztály objktum az adott típushoz a memóriában van, minden
olyan típusu objektum létrehozására
használva lesz. Ha ez homályosnak tűnik , vagy
ha nem igazán hisszük el, itt egy bemutató program bizonyításképp:
//: c10:SweetShop.java
// Bemutató hogy működik az osztály betöltő
import com.bruceeckel.simpletest.*;
class Candy {
static {
System.out.println("Loading Candy");
}
}
class Gum {
static {
System.out.println("Loading Gum");
}
}
class Cookie {
static {
System.out.println("Loading Cookie");
}
}
public class SweetShop {
private static Test monitor = new Test();
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("After creating Candy");
try {
Class.forName("Gum");
} catch(ClassNotFoundException e) {
System.out.println("Couldn't find Gum");
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
monitor.expect(new String[] {
"inside main",
"Loading Candy",
"After creating Candy",
"Loading Gum",
"After Class.forName(\"Gum\")",
"Loading Cookie",
"After creating Cookie"
});
}
} ///:~
Mindhárom osztály, a Candy, Gum, és Cookie állandó kikötése hogy
az osztály végrehajtódik első futtatáskor. Információk
lesznek kiírva, hogy
tudjuk, mikor töltődnek be. A main(
) ben, az
objektum létrehozások megoszlanak a kiíratások közt, hogy a
folyamat nyomon
követhetőbb legyen.
Az output-ból láthatjuk, hogy minden Osztály objektum csak akkor töltődik be ha
szükséges, és az állandó inicializáció megtörténik
osztálybetöltéskor.
Egy különösen érdekes sor:
Class.forName("Gum");
Ez a módszer az állandó része
az Osztálynak (amihez minden Osztály tartozik).
Az Osztály objektum olyan mint bármeik másik objektum tehát így es lehet és
kell is kezelnünk(ezt teszi a betöltő is).
Az egyik út hogy
utaljunk az Osztály objektumra a forName( ), ami egy
String , ami tartalmazza a szöveges
nevét(Figyelve a betűtésre, és a kis és nagy
betükre!) a megadott osztálynak amire hivatkoztunk. Viszatérési értéke egy
Osztály hivatkozás, amivel
nem fogalkozunk ebben az esetben—a hívás forName(
)
ekészül mellékhatásként,ami elkezdi betölteni a Gum-ot ha az
nincs még betöltve. A betöltés során, a Gum állandó kikötése végrehajtódik.
A fenti példában, ha Class.forName(
) nem működik mert nem találja az
Osztályt amit be akarunk tölteni, egy ClassNotFoundException üzenetet küld.
(ideális esetben az exception neve tökéletesen informál a hiba
helyéről).
Itt, mi egyszerűen jelentjük a problémát és továbbmegyünk,de
Kifinomultabb programokban meg
kell próbálni kezelni a problémát
a kivételkezelővel.
Osztály literációk
A Java biztosít egy másik utat, hogy létrehozzunk visszajelzést az
Osztály objektumról,
osztály literációkat használva. A fenti
programban igy nézne ki:
Gum.class;
Ami nem csak egyszerűbb, hanem biztonságosabb is mivel fordításkor
ellenőrződik.
Mivel ez szükségtelenné teszi a módszerhívást, sokkal effektívebb
is.
Az osztály literácók működnek reguláris osztályokkal, interface-ekkel,
tömbökkel,és
primitív típusokkal. Ráadásul, van egy alapértelmezett mező, a TYPE ami
létezik minden primitivebb osztály számára. A TYPE mező létrehoz
egy hivatkozást az osztály objektumnak a szóbanforgó primitív
tipusra, valahogy igy:
… egyenlő vele …
boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE
Javaslom használjuk a “.class” verziót ha lehet, mert sokkal következetesebb a reguláris
osztályokkal.
Ellenőrzés cast előtt
Mostanra láttunk pár RTTI formátumot, mint például:
jó legyen a cast. Ez jelezni fog ClassCastException
–el ha rosz castot
csináltunk.
2. Az Osztály objektum képviseli a típusát az objektumnak.
Az Osztály
objektum lekérdezhető hasznos futási információkról.
C++ ban, a klasszikus cast “(Shape)” nem végez RTTI-t. Ez egyszerüen
közli a compilerel hogy uj tipusként kezelje. Java-ban, ami nem
végez
tipusellenőrzést, ez a cast gyakran nevezik “type safe downcast”-nak.
Az ok, hogy ez “downcast” egy történelmi megegyezés a class
hiearchia
diagrammban. Ha castolni egy Circle –t egy Shape –é egy upcast, akkor
castolni egy Shape -t Circle -é egy downcast. Egyebár tudjuk egy Circle
egyben a Shape,a fordito szabdon engedélyezi az upcast hozzárendelést,
de nem tudhatjuk hogy a Shape szükségszerüen Circle, tehát a fordito
nem engedéylezi a downcast hozzárendelést egy explicit cast használata
nélkül. Itt az RTTI harmadik formája Javaban. Ez a instanceof kulcsszó,
ami megmondja hogy ha egy objektum része e egy bizonyos
tipusnak.
Egy boolean al tér vissza, tehát ez egy kétesélyes kérdés, például:
if(x instanceof Dog)
((Dog)x).bark();
A fenti if sor ellenőrzi hogy az x objektum a Dog osztályhoz tartozik e
mielőtt x
et Doghoz cast olja. Fontos
használni a instanceof –ot mielőtt
downcastolunk, főleg mikor nincs semmi információnk az osztály
tipusára vonatkozóan; másképp könnyen végződhet ClassCastException el
a művelet.Általábn jó eséllyel
vadászni fogunk egy tipusra,de könnyedén átalakithaunk
minden objektumot az instanceof használatával.
Feltételezve hogy létezik család a Pet osztályra:
//: c10:Pet.java
package c10;
public class Pet {} ///:~
//: c10:Dog.java
package c10;
public class Dog extends Pet {} ///:~
//: c10:Pug.java
package c10;
public class Pug extends Dog {} ///:~
//: c10:Cat.java
package c10;
public class Cat extends Pet {} ///:~
//: c10:Rodent.java
package c10;
public class Rodent extends Pet {} ///:~
//: c10:Gerbil.java
package c10;
public class Gerbil extends Rodent {} ///:~
//: c10:Hamster.java
package c10;
public class Hamster extends Rodent {} ///:~
A következő példában követni akarjuk bármien tipusu Pet számát,
tehát kell egy osztály ami int tipusu. Elkézelhetjük egy másféle Integernek
//: c10:Counter.java
package c10;
public class Counter {
int i;
public String toString() { return Integer.toString(i); }
} ///:~
Következőképp kell valami ami összetart két dolgot: Egy Pet típus jelző,
és egy Counter
(számláló) ami
tárolja a pet menniységet. Tehát, képesek
akarunk lenni arra
hogy azt mondjuk “hány Gerbil
objektum van ott?” Egy közönséges tömb nem fog
működniebben az esetben, mert a tömbökben az objektumokra indexükkel
hivatkozunk.
Azt akarjuk hogy ebben az esetben a pet objektummukkal hivatkozzunk a tömb elemeire.
Társítani akarjuk a Counter objektumokat a Pet objektumokkal. Van egy standard
adattípus ami pont ezt csinálja, az asszociatív tömb.
Itt egy borzasztó egyszerű példa:
//: c10:AssociativeArray.java
// Kulcsok
és értékek megfeleltetése.
package
c10;
import
com.bruceeckel.simpletest.*;
public
class AssociativeArray {
private
static Test monitor = new Test();
private
Object[][] pairs;
private
int index;
public
AssociativeArray(int length) {
pairs
= new Object[length][2];
}
public
void put(Object key, Object value) {
if(index
>= pairs.length)
throw
new ArrayIndexOutOfBoundsException();
pairs[index++]
= new Object[] { key, value };
}
public
Object get(Object key) {
for(int
i = 0; i < index; i++)
if(key.equals(pairs[i][0]))
return
pairs[i][1];
throw
new RuntimeException("Failed to find key");
}
public
String toString() {
String
result = "";
for(int
i = 0; i < index; i++) {
result
+= pairs[i][0] + " : " + pairs[i][1];
if(i
< index - 1) result += "\n";
}
return
result;
}
public
static void main(String[] args) {
AssociativeArray
map = new AssociativeArray(6);
map.put("sky",
"blue");
map.put("grass",
"green");
map.put("ocean",
"dancing");
map.put("tree",
"tall");
map.put("earth",
"brown");
map.put("sun",
"warm");
try {
map.put("extra",
"object"); // Past the end
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Too
many objects!");
}
System.out.println(map);
System.out.println(map.get("ocean"));
monitor.expect(new String[] {
"Too many objects!",
"sky : blue",
"grass : green",
"ocean : dancing",
"tree : tall",
"earth : brown",
"sun : warm",
"dancing"
});
}
} ///:~
Első látásra ez egy teljesen átlagos célú eszköznek tűnhet, tehát
miért nem helyezünk el
benne egy csomagot, például com.bruceeckel.tools? Nos ez tényleg egy átlagos célú eszköz,
—nagyon hasznos, valójában, a java.util
tartalmazza az asszociatív tömbök számát
(amiket térképnek
is neveznek) ez egy kicsit többre képes mint az előbbi, és kicsit gyorsabban.
A 11. ik fejezet nagy része az asszociatív tömbökkel foglalkozik, de lényegesen
bonyolultabb,
ám ugyanakkor lényegesen egyszerűbbé teszi a dolgokat, ezt használva
lassan megbarátkozunk
velük, és felfedezzük az asszociatív tömbök értékét.
Egy asszociatív tömbben, az “indexer” –t kulcsnak nevezik és a
hozzárednelt objektum az érték
nevet viseli. Itt mi társitjuk az értékeket a a
kulcsokkal egy 2 tömb tömbjeiben, ami
itt a pairs.
Ez egy fix hosszuságu tömb, ami a szerkesztőben létrejön, tehát
indexelnünk kell hogy biztosan
ne fussunk ki belőle a végén.Amikor put( ) olunk
egy uj kulcs-érték párt, egy új 2-elemű tömb keletkezik
és behelyeződik a
következő lehetséges helyére a pároknak.Ha az index nagyobb vagy egyenlő
a párok hosszával, egy kivétel képződik.
Ahhoz hogy használjuk a get( ) módszert, megadjuk a kulcsot amit keresünk,és ez létrehozza a
hozzárendelt értéket eredményként vagy
kivétellel tér visza amennyiben nem találja meg. A get( ) módszer
a legeffektívebb módot használja az
érték megtalálására: a tömb
tetején kezdi, és equals( )-t használ a kulcsok
öszehasonlítására. De a lényeg itt az egyszerűség, nem a
hatékonyság,és az igazi térképek a 11 es
fejezetben megoldják a teljesítmény-problémát, tehát itt most nem kell
aggódnunk emiatt.
A legfontosabb módszerek egy asszociatív tömbben a put( ) és a get( ), de az egyszerű megjelenítésre
a toString(
) felül lett irva, hogy egy kulcs-érték párt adjon vissza.
Hogy megmutassuk hogyan is működik ez, main( )
betölti a AssociativeArray
-t egy
sztringpárba és megjelenyti az eredmény térképet, ezt követi egy get( ) az értékek egyikén.
Most hogy minden eszközünk megvan, használhatjuk az instanceof ot hogy megszámoljuk a Pet-t:
//: c10:PetCount.java
// instanceof használata.
package c10;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class PetCount {
private static Test monitor = new Test();
private static Random rand = new Random();
static String[] typenames = {
"Pet", "Dog", "Pug",
"Cat",
"Rodent", "Gerbil", "Hamster",
};
// Kivétellel visszatér a konzolra:
public static void main(String[] args) {
Object[] pets = new Object[15];
try {
Class[] petTypes = {
Class.forName("c10.Dog"),
Class.forName("c10.Pug"),
Class.forName("c10.Cat"),
Class.forName("c10.Rodent"),
Class.forName("c10.Gerbil"),
Class.forName("c10.Hamster"),
};
for(int i = 0; i < pets.length; i++)
pets[i] = petTypes[rand.nextInt(petTypes.length)]
.newInstance();
} catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
} catch(ClassNotFoundException e) {
System.out.println("Cannot find class");
System.exit(1);
}
AssociativeArray map =
new AssociativeArray(typenames.length);
for(int i = 0; i < typenames.length; i++)
map.put(typenames[i], new Counter());
for(int i = 0; i < pets.length; i++) {
Object o = pets[i];
if(o instanceof Pet)
((Counter)map.get("Pet")).i++;
if(o instanceof Dog)
((Counter)map.get("Dog")).i++;
if(o instanceof Pug)
((Counter)map.get("Pug")).i++;
if(o instanceof Cat)
((Counter)map.get("Cat")).i++;
if(o instanceof Rodent)
((Counter)map.get("Rodent")).i++;
if(o instanceof Gerbil)
((Counter)map.get("Gerbil")).i++;
if(o instanceof Hamster)
((Counter)map.get("Hamster")).i++;
}
// Minden egyes pet listázása:
for(int i = 0; i < pets.length; i++)
System.out.println(pets[i].getClass());
// A számok:
System.out.println(map);
monitor.expect(new Object[] {
new TestExpression("%% class c10\\."+
"(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
pets.length),
new TestExpression(
"%% (Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster)" +
" : \\d+", typenames.length)
});
}
} ///:~
A main( ) ben a tömb petTypes –a az Osztály objektum nak a Class.forName( ).
használatával jön létre. Mivel a Pet
objektumok a c09 es csomagban vannak,
a csomagnevet kell használni a az osztályok elnevezésekor.
Következőnek a pets tömböt feltöltjük véletlenszerüen indexelve a petTypes
körül és használva a kiválasztott Osztály objektumot, hogy
létrehozzon
egy uj instance-át az osztálynak a Class.newInstance( )-al, ami az
alapértelmezett
(no-arg) osztály létrehozót használja a megvalósításhoz.
Mind a forName( ) ,és a newInstance( ) kivételekt
generálhat, amik
kezelve lesznek a catch clauses –t követő try blokkban.Ismét,
a kivételek nevei remek magyarázatok, arra hogy miben van a hiba.
(IllegalAccessException azt jelenti, hogy a Java
biztonsági rendszere miatt van a hiba)
Miután létrehoztuk a AssociativeArray-t,feltöltjük a kulcs-érték párokkal
a pet nevek és Counter objektumokat felhasználva.Aztán
minden Pet a véletlenszerűen
generált tömbben tesztelv lesz és meg elsz számlálva
az instanceof által. A tömb
és a AssociativeArray megjelik, tehát öszehosnlítható lesz az eredmény.
Van egy apró megkötés az instanceof al kapcsoaltban: csak név típussal lehet
öszehasonlítani , és nem egy Osztály
objektummal. A fenti példában
ugy tűnhet hogy ki kéne irni minden instanceof kifejezést, és ez igaz is.
De nincs rá mód hogy ésszerűen automatizáljuk az instanceof -o azzal hogy létrehozunk
egy
tömböt az osztály objektumokbol és inkább ahhoz hasonlitjuk. (türelem—lesz
alternatíva).
Ez nem olyan jó kikötés, mint amiennek tűnik,mert meg fogjuk érteni, hogy
a terv valószíűleg hibába fog végződni sok instanceof kifejezéssel.
Persze a példa kitalált— valószínüleg mindenki tenne egy static mezőt minden
tipushoz, és a
szerkesztővel oldaná meg hogy nyomon legyen követve a számolás.
Valami iesmit tennénk ha a kezünkben lenne a forráskód. Csakhogy
ez nincs mindig igy, az RTTI
könnyen képbe jön tehát.
Osztály literációk használata
Érdekes látni hogy a PetCount.java
példa hogy néz ki ujrairva
Osztály literációkat használva.Az eredmény többféleképpen is
tisztább:
//: c10:PetCount2.java
// Osztály literációk használata.
package c10;
import com.bruceeckel.simpletest.*;
import
java.util.*;
public
class PetCount2 {
private
static Test monitor = new Test();
private
static Random rand = new Random();
public
static void main(String[] args) {
Object[]
pets = new Object[15];
Class[]
petTypes = {
// Osztály
literációk:
Pet.class,
Dog.class,
Pug.class,
Cat.class,
Rodent.class,
Gerbil.class,
Hamster.class,
};
try {
for(int
i = 0; i < pets.length; i++) {
// A
Pet.class kiiktatása:
int
rnd = 1 + rand.nextInt(petTypes.length - 1);
pets[i]
= petTypes[rnd].newInstance();
}
}
catch(InstantiationException e) {
System.out.println("Cannot
instantiate");
System.exit(1);
}
catch(IllegalAccessException e) {
System.out.println("Cannot
access");
System.exit(1);
}
AssociativeArray
map =
new
AssociativeArray(petTypes.length);
for(int
i = 0; i < petTypes.length; i++)
map.put(petTypes[i].toString(),
new Counter());
for(int
i = 0; i < pets.length; i++) {
Object
o = pets[i];
if(o
instanceof Pet)
((Counter)map.get("class
c10.Pet")).i++;
if(o
instanceof Dog)
((Counter)map.get("class
c10.Dog")).i++;
if(o
instanceof Pug)
((Counter)map.get("class
c10.Pug")).i++;
if(o
instanceof Cat)
((Counter)map.get("class
c10.Cat")).i++;
if(o instanceof Rodent)
((Counter)map.get("class c10.Rodent")).i++;
if(o instanceof Gerbil)
((Counter)map.get("class c10.Gerbil")).i++;
if(o instanceof Hamster)
((Counter)map.get("class c10.Hamster")).i++;
}
// Minden egyes pet listázása:
for(int i = 0; i < pets.length; i++)
System.out.println(pets[i].getClass());
// Számok:
System.out.println(map);
monitor.expect(new Object[] {
new TestExpression("%% class c10\\." +
"(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
pets.length),
new TestExpression("%% class c10\\." +
"(Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster) : \\d+",
petTypes.length)
});
}
} ///:~
Itt, a típusnevek tömb el lett
távolítva a típusnév sztring megszerzésének érdekében
az Osztály objektumból. Vegyük észre, hogy a rendszer különbséget tesz
osztály és
interface közt.
Azt is láthatjuk, hogy a petTypes
létrehozása nem kell hogy körül legyen véve
egy try blokkal, ugyanis ez kiértékelődik fordításkor és ráadásul
nem képez kivételt, ellentétben Class.forName(
) el.
Amikor egy Pet objektum dinamkiusan létrejön,láthatjuk azt, hogy
Véletlen számok lefoglalódnak egy és petTypes.length (hossza) között
a nulla kivételével. Azért mert a nulla nem értelmezhető Pet.class, és egy
elemi Pet objektum létrehozása nem érdekes. Habár a Pet.class része apetTypes nak ,
eredményképp mind meg lesz számolva.