Книга е още в много ранна фаза на написване



страница68/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   65   66   67   68   69   70   71   72   73

Синтаксис на RTTI


Java прави своето RTTI чрез обекта Class, даже ако правите нещо от рода на каст. Класът Class също има някои пътища за използване на RTTI.

Първо, трябва да се сдобиете с манипулатор към съответния Class обект. Единият начин за това, както беше показано в предишния пример, е да се из­полз­ва стринг и Class.forName( ) метода. Това е удобно понеже не е необходим обект от дадения тип за да се получи Class манипулатор. Обаче ако вече има обект от типа от който се интересувате, може да вземете Class манипулатора чрез викане на метод който е част от Object кореновия клас: getClass( ). Това връ­ща Class манипулатор представящ фактическия тип на обекта. Class има ня­кои интересни и понякога полезни методи, демонстрирани в следния пример:

//: c11:ToyTest.java

// Testing class Class


interface HasBatteries {}

interface Waterproof {}

interface ShootsThings {}

class Toy {

// Comment out the following default

// constructor to see

// NoSuchMethodError from (*1*)

Toy() {}

Toy(int i) {}

}
class FancyToy extends Toy

implements HasBatteries,

Waterproof, ShootsThings {

FancyToy() { super(1); }

}
public class ToyTest {

public static void main(String[] args) {

Class c = null;

try {

c = Class.forName("FancyToy");



} catch(ClassNotFoundException e) {}

printInfo(c);

Class[] faces = c.getInterfaces();

for(int i = 0; i < faces.length; i++)

printInfo(faces[i]);

Class cy = c.getSuperclass();

Object o = null;

try {


// Requires default constructor:

o = cy.newInstance(); // (*1*)

} catch(InstantiationException e) {}

catch(IllegalAccessException e) {}

printInfo(o.getClass());

}

static void printInfo(Class cc) {



System.out.println(

"Class name: " + cc.getName() +

" is interface? [" +

cc.isInterface() + "]");

}

} ///:~


Може да видите че class FancyToy е доста сложен, като наследява от Toy и implements interface-и на HasBatteries, Waterproof и ShootsThings. В main( ) се създава манипулатор на Class и се инициализира на FancyToy Class чрез forName( ) вътре в подходящ try блок.

Методът Class.getInterfaces( ) връща масив от Class обекти представящи ин­тер­фейсите които се съдържат в Class обекта от който се интересуваме.

Ако имате Class обект може също да го питате за директния му базов клас чрез getSuperclass( ). Това, разбира се, връща Class манипулатор който може да из­след­вате по-нататък. Това значи че, по време на изпълнение, може да получите ця­лата йерархия на класа.

Методът newInstance( ) на Class може, отначало, да изглежда просто друг начин да clone( ) обект. Обаче може да създадете нов обект чрез newInstance( ) без съществуващ обект, както се вижда тук, понеже няма обект Toy, само cy, кое­то е манипулатор към Class обекта на y. Това е начин да се реализира “вир­туа­лен конструктор,” който позволява да се каже “Не знам точно какъв тип си, но вземи че се създай правилно.” В примера по-горе, cy е точно Class ма­ни­пу­ла­тор без никаква допълнителна информация известна по време на компилация. И когато създавате нов екземпляр, получавате Object манипулатор. Но този ма­ни­пулатор сочи Toy обект. Разбира се, преди да може да изпращате съобщения различни от тези за Object, ще трябва да го изследвате малко и да направите ня­ка­къв кастинг. Освен това класът кайто се създава с newInstance( ) трябва да има конструктор по подразбиране. Няма начин да се използва newInstance( ) за съз­даване на обекти без конструктор по подразбиране, така че това може да бъде малко ограничаващо в Java 1. Обаче API на рефлексията в Java 1.1 (об­съж­дан в следващата секция) позволява динамично да се използва всякакъв кон­структор в един клас.

Последния метод в листинга е printInfo( ), който взема Class манипулатор и име­то с getName( ), после гледа дали е интерфейс с isInterface( ).

Изходът от тази програма е:

Class name: FancyToy is interface? [false]

Class name: HasBatteries is interface? [true]

Class name: Waterproof is interface? [true]

Class name: ShootsThings is interface? [true]

Class name: Toy is interface? [false]

Така, с Class обекта може да намерите всичко което искате да знаете за обект.


Рефлексия: информация за клас по време на из­пъл­не­ние


Ако не знаете точния тип на обект, RTTI ще ви го каже. Обаче има ограничение: ти­път трябва да е известен по време на компилация за да може да използвате RTTI и да правите нещо полезно с информацията. С други думи казано, ком­пи­ла­торът трябва да знае всички класове с които работите за RTTI.

Отначало това не изглежда силно ограничение, но да предположим че имате ма­нипулатор който е извън вашето програмно пространство. Фактически кла­сът на този обект е недостъпен за вашата програма даже по време на ком­пи­ла­ция. Да предположим че сте взели байтове от дисков файл или мрежова връзка и знаете че те представляват клас. Понеже компилаторът не знае за класа когато ком­пилира кода, как би могъл да използува този клас?

В традиционните програмни среди това изглежда като изсмукан от пръсти сценарий. Но ако преминем към по-голям програмен свят виждаме важни слу­чаи когато това се случва. Първият е компонентно-базираното програмиране къ­дето строите проекти чрез Rapid Application Development (RAD) в ин­стру­мент за производство на приложения. Това е визуалният подход към правене на про­грами (които виждате на екрана като форми) чрез местене на икони които пред­ставят компоненти във формата. Тези компоненти после се конфигурират чрез поставяне на някои стойности по време на компилация. Тази кон­фигу­ра­ция по време на проектирането изисква от всеки компонент да може да се пра­ви екземпляр и той да показва част от себе си и да позволява стойностите му да бъ­дат четени и поставяни. Освен това компонентите които поддържат GUI съби­тията трябва да показват информация за методите така че RAD средата да мо­же да асистира на програмиста в подтискането на тези методи за обработка на събитията. Рефлексията дава механизъм за откриване на достъпните методи и имената им. Java 1.1 дава начин за компонентно програмиране посредством Java Beans (описани в глава 13).

Друга примамлива мотивация за намиране на информация по време на из­пъл­не­ние е възможността да се създават и активират обекти отдалечено и много плат­формено през мрежа. Това е наречено Remote Method Invocation (RMI­) и поз­волява на Java програма (версия 1.1 и по-висока) да има разпределени по мно­го машини обекти. Това разпределяне може да се появи по много причини: мо­же би правите задача с интензивни изчисления и искате да я накъсате и да да­де­те някои парчета на машини които циклят напразно за да ускорите нещата. В ня­кои ситуации може да искате кода който обработва някои конкретни типове за­дачи (напр. “Бизнес Правила” в многоредична клиент/сървър архитектура) да е на определена машина така че тя да стане общо хранилище за тези данни та да мо­же да се праменят лесно и да важат за всички потребители. (Това е интересно из­следване понеже машина съществува само за да прови промените лесни!) Пара­лелно с всичко това, разпределената обработка също поддържа спе­циа­ли­зи­ран хардуер който може да е добър за отделна задача – обръщане на ма­три­ци, например – но неподходящ и твърде скъп за програмни системи с общо пред­назначение.

В Java 1.1 класът Class (описан по-рано в тазци глава) е разширен за да под­дър­жа концепцията за рефлексия, та има допълнителна библиотека, java.lang.reflect, с класовете Field, Method и Constructor (всеки от които при­ла­га Member interface). Обекти от тези типове се създават от JVM по време на из­пълнение за да продставят членовете на непознат клас. После може да из­полз­вате Constructorите за създаване на нови обекти, методите get( ) и set( ) за че­тене и промяна на полетата асоциирани с Field обекти, метод invoke( ) за ви­ка­не на метод асоцииран с Method обект. Освен това може да викате методите за удобство getFields( ), getMethods( ), getConstructors( )и т.н., за да върнат ма­сиви от обекти представящи полета, методи и конструктори. (Може да на­ме­ри­те повече чрез разглеждане на Class във вашата онлайн документация.) Така ин­формацията за класа за анонимен обект може напълно да се извлече по време на изпълнение, не е необходимо нищо да се знае по време на компилация.

Важно е да се разбере че няма нищо магическо в рефлексията. Когато из­полз­ва­те рефлексията за работа с обект от неизвестен тип JVM просто ще погледне обек­та и ще види дали принадлежи към определен клас (точно както обик­но­ве­ния RTTI) но след това, преди да може да направи каквото и да е друго нещо, Class обектът трябва да бъде натоварен. Така .class файлът за конкретния тип тряб­ва все пак да бъде достъпен за JVM, било на локалната машина или през мре­жата. Така че истинската разлика между RTTI и рефлексията е че с RTTI ком­пилаторът отваря и разглежда .class файла по време на компилация. С други думи, може да викате всички методи на обект по “нормален” начин. С ре­флексията .class файлът е недостъпен по време на компилация; той се отваря и използва от програмната среда по време на изпълнение.


Екстрактор на методите


Рядко ще става нужда да се използват инструментите на рефлексията; те са в ези­ка за поддръжка на други черти на Java като сериализацията на обекти (описана в глава 10), Java Beans и RMI (описано по-късно в тази книга). Има оба­че случаи когато възможността динамично да се извлича информация за клас е твърде полезна. Един извънредно полезен инструмент е екстракторът на ин­фор­мация за методите. Както се спомена по-горе, гледането дефиницията на клас в сорса позволява да се видят само дефинираните или подтиснати методи са­мо в тази дефиниция. Но може да има дузини други които са дошли чрез ба­зо­вите класове. Да се намерят дефинициите им е както досадно, така и от­не­ма­що много време. За щастие рефлексията дава начин да се напише прост ин­стру­мент който автоматично ще показва целия интерфейс. Ето как работи той:

//: c11:ShowMethods.java

// Using Java 1.1 reflection to show all the

// methods of a class, even if the methods are

// defined in the base class.

import java.lang.reflect.*;


public class ShowMethods {

static final String usage =

"usage: \n" +

"ShowMethods qualified.class.name\n" +

"To show all methods in class or: \n" +

"ShowMethods qualified.class.name word\n" +

"To search for methods involving 'word'";

public static void main(String[] args) {

if(args.length < 1) {

System.out.println(usage);

System.exit(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(m[i].toString());

for (int i = 0; i < ctor.length; i++)

System.out.println(ctor[i].toString());

}

else {


for (int i = 0; i < m.length; i++)

if(m[i].toString()

.indexOf(args[1])!= -1)

System.out.println(m[i].toString());

for (int i = 0; i < ctor.length; i++)

if(ctor[i].toString()

.indexOf(args[1])!= -1)

System.out.println(ctor[i].toString());

}

} catch (ClassNotFoundException e) {



System.out.println("No such class: " + e);

}

}



} ///:~

Методите на Class getMethods( ) и getConstructors( ) връщат масив от Method и Constructor, респективно. Всеки от тези класове има методи за по-нататъшна ди­секция на имена, аргументи и връщани стойности на методите, които те пред­ставят. Но може също просто да използвате toString( ), както е направено тук, за да се получи String с цялата сигнатура на метода. TОстаналата част от ко­да е за извличане на информация от командния ред, определяне дали дадена сиг­натура совпада с вашия стринг (чрез indexOf( )) и извеждане на резултатите.

Това показва рефлексия в действие, понеже резултатът даван от Class.forName( ) не може да бъде известен по време на компилация и затова ця­ла­та информация за сигнатурите бива извличана по време на изпълнение. Ако из­следвате вашата онлайн документация за рефлексията, вие ще видите че има до­статъчна поддръжка за извикване на метод който е напълно непознат по вре­ме на компилация. Още веднъж, това е нещо което едва ли ще искате да правите са­ми – поддръжката е налице заради Java и така програмната среда може да ра­бо­ти с Java Beans – но е интересно.

Интересен експеримент е да се пусне java ShowMethods ShowMethods. Това дава листинг който включва public конструктор по подразбиране, даже и да сте ви­дели от сорса че не е дефиниран такъв. Конструкторът който виждате е този ав­томатично конструиран от компилатора. Ако после направите ShowMethods не-public клас (тоест, приятелски), констлукторът по подразбиране вече не се по­казва в изходния листинг. На синтезирания конструктор по подразбиране ав­то­матично е даден достъп като на класа.

Изходът за ShowMethods е все още малко уморителен. Например има една част произведена чрез извикване на java ShowMethods java.lang.String:

public boolean

java.lang.String.startsWith(java.lang.String,int)

public boolean

java.lang.String.startsWith(java.lang.String)

public boolean


java.lang.String.endsWith(java.lang.String)

Би било даже по-добре квалификатори като java.lang да можеха да се изрязват. Кла­сът StreamTokenizer въведен в предишната глава може да помогне да се ре­ши този проблем:

//: c11:ShowMethodsClean.java

// ShowMethods with the qualifiers stripped

// to make the results easier to read

import java.lang.reflect.*;

import java.io.*;
public class ShowMethodsClean {

static final String usage =

"usage: \n" +

"ShowMethodsClean qualified.class.name\n" +

"To show all methods in class or: \n" +

"ShowMethodsClean qualif.class.name word\n" +

"To search for methods involving 'word'";

public static void main(String[] args) {

if(args.length < 1) {

System.out.println(usage);

System.exit(0);

}

try {



Class c = Class.forName(args[0]);

Method[] m = c.getMethods();

Constructor[] ctor = c.getConstructors();

// Convert to an array of cleaned Strings:

String[] n =

new String[m.length + ctor.length];

for(int i = 0; i < m.length; i++) {

String s = m[i].toString();

n[i] = StripQualifiers.strip(s);

}

for(int i = 0; i < ctor.length; i++) {



String s = ctor[i].toString();

n[i + m.length] =

StripQualifiers.strip(s);

}

if(args.length == 1)



for (int i = 0; i < n.length; i++)

System.out.println(n[i]);

else

for (int i = 0; i < n.length; i++)



if(n[i].indexOf(args[1])!= -1)

System.out.println(n[i]);

} catch (ClassNotFoundException e) {

System.out.println("No such class: " + e);

}

}

}


class StripQualifiers {

private StreamTokenizer st;

public StripQualifiers(String qualified) {

st = new StreamTokenizer(

new StringReader(qualified));

st.ordinaryChar(' '); // Keep the spaces

}

public String getNext() {



String s = null;

try {


if(st.nextToken() !=

StreamTokenizer.TT_EOF) {

switch(st.ttype) {

case StreamTokenizer.TT_EOL:

s = null;

break;


case StreamTokenizer.TT_NUMBER:

s = Double.toString(st.nval);

break;

case StreamTokenizer.TT_WORD:



s = new String(st.sval);

break;


default: // single character in ttype

s = String.valueOf((char)st.ttype);

}

}

} catch(IOException e) {



System.out.println(e);

}

return s;



}

public static String strip(String qualified) {

StripQualifiers sq =

new StripQualifiers(qualified);

String s = "", si;

while((si = sq.getNext()) != null) {

int lastDot = si.lastIndexOf('.');

if(lastDot != -1)

si = si.substring(lastDot + 1);

s += si;


}

return s;

}

} ///:~


Класът ShowMethodsClean е доста приличен на ShowMethods, освен че взема масиви от Method и Constructor и ги превръща в единствен масив от String. Все­ки от тези String обекти после се дава на StripQualifiers.Strip( ) за махане на всичката квалификация на метода. Както може да видите, това използва StreamTokenizer и String манипулация за свършване на тази работа.

Този инструмент може реално да спести време докато програмирате, когато се ин­тересувате от конкретен член и не щете да се разхождате в документацията на класовата йерархия, или ако не знаете дали класът може да прави нещо с, да ка­жем, Color обекти.

Глава 13 съдържа GUI версия на тази програма, така че може да я оставите пус­на­та докато пишете програми, за бърз оглед.




Сподели с приятели:
1   ...   65   66   67   68   69   70   71   72   73




©obuch.info 2024
отнасят до администрацията

    Начална страница