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:

1. A klasszikus cast; e.g., “(Shape),” ami RTTI –t használ hogy biztosan
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.