Резюме
RTTI позволява да се получи информация за типа от анонимен манипулатор на базов клас. Така то е готово за неправилна употреба от новака понеже може да има смисъл преди полиморфните методи. За много хора с процедурна ориентация, it’s difficult not to organize their programs into sets oе трудно да реорганизират програмите си в множества от switch оператори. Те биха направили това чрез RTTI и биха загубили по този начин важната черта полиморфизъм на кода и поддръжката. Намерението в Java е да се използват полиморфни извиквания на методи в кода, а да се използва RTTI само когато е необходимо.
Обаче използването на полиморфизма изисква сорса да е на разположение, понеже в някой момент се открива, че не разполагаме с някой необходим метод. Ако базовият клас идва от библиотека или въобще принадлежи на някой друг, решението на проблема е RTTI: Може да наследите нов тип и да добавите вашия допълнителен метод. На друго място в кода може да откриете точния тип и да извикате въпросния метод. Това не премахва полиморфизма или разширяемостта на кода на програмата понеже добавянето на нов тип няма да изисква излавянето на превключващи оператори в програмата. Обаче когато добавяте код в главното тяло на програмата който изисква вашата нова черта, трябва да използвате RTTI за откриване на точния тип.
Слагането на черта в базовия клас би могло да значи че, за употреба в някакъв специален слас, всички класове извлечени от въпросния клас трябва да имат някаква част от метода. Това прави интерфейса по-малко ясен и дразни с подтискането на абстрактните методи когато наследявате от този базов клас. Например да вземем класова йерархия представяща музикални инструменти. Да кажем че искате да настроите всички инструменти в оркестъра. Едната възможност е да използвате ClearSpitValve( ) метод в базовия клас Instrument, но това е смущаващо понеже предполага че Percussion и Electronic инструментите също се настройват с червячета. RTTI дава много по-смислено решение в този случай понеже можете да сложите метода в специфичен клас (Wind в този случай), където е подходящо. Обаче по-подходящо решение е да сложите prepareInstrument( ) метод в базовия клас, но може да не видите това когато се сблъскате с проблема и погрешно да мислите че трябва да се използва RTTI.
Накрая, RTTI понякога ще решава проблеми с ефективността. Ако вашят код използва масово полиморфизъм, но излезе че един от вашите обекти реагира на това с твърде ниска ефикасност, може да намерите типа с RTTI и да напишете код зависещ от случая за повишаване на ефективността.
Упражнения -
Напишете код който взема обект и рекурсивно печата всички класове в йерархията му.
-
В ToyTest.java, изкоментирайте конструктора по подразбиране на Toy и обаснете какво се случва.
-
Създайте нов тип колекция която използва ArrayList. Хванете типа на първия вкаран обект, а после позволете на потребителя да вкарва обекти от само него тип от този момент нататък.
-
Напишете програма да определи дали масив от char е примитивен тип или същински обект.
-
Реализирайте clearSpitValve( ) както е описано в тази глава.
-
Реализирайте rotate(Shape) метода описан в тази глава, така че да проверява дали върти Circle (и, ако да, не изпълнява операцията).
12: Подаване и връщане на обекти
Вече трябва да се чувствате достатъчно удобно с мисълта че когато “предавате” обект фактически предавате манипулатор.
В много програмни езици, ако не във всички, може да се използва “обичаен” начин да се предават обекти и повечето време той работи добре. Но винаги, изглежда, идва момент когато се налага да правите нещо необичайно и нещата стават малко по-сложни (в случая на C++, доста сложни). Java не е изключение, важно е да знаете какво точно става при предаването на обекти. Тази глава ще хвърли светлина върху този въпрос.
Друг начин да се постави въпроса, ако извате от съответно снабден език, е “Има ли в Java указатели?” Твърди се че указателите са сложни и пр. И затова лоши, а понеже Java е изцяло доброта и светлина and и ще премахне вашите земни програмистки тегла, той вероятно не може да има такива неща. Обаче по-точно е да се каже че в Java има указатели; разбира се всеки идентификатор на обект в Java (освен за примитивите) е един от тези указатели, но използването им е ограничавано и ковтролирано не само от компилатора, но и от операционната среда по време наизпълнение. Или с други думи казано, Java има указатели, но не аритметика с указателите. Те са които аз нарекох “манипулатори,” вие може да ги мислите като “безопасни указатели,” не много различно от безопасните ножици в основното училище - те не са остри и не може да се порежете без голямо усилие, но понякога могат да бъдат бавни и досадни.
Подаване на манипулатори
Когато подавате манипулатор на метад, още сочите към същия обект. Прост експеримент демонстрира това: (Виж стр. 89 ако имате проблеми с пускането на тази програма.)
//: c12:PassHandles.java
// Passing handles around
package c12;
public class PassHandles {
static void f(PassHandles h) {
System.out.println("h inside f(): " + h);
}
public static void main(String[] args) {
PassHandles p = new PassHandles();
System.out.println("p inside main(): " + p);
f(p);
}
} ///:~
Методът toString( ) е автоматично извикан в операторите за извеждане, а PassHandles наследява директно от Object без предефиниране на toString( ). Така, версията на toString( ) от Object се използва, което извежда класа на обекта следван от адреса на който се намира обекта (не манипулатора, а фактическия адрес в паметта). Изходът изглежда като този:
p inside main(): PassHandles@1653748
h inside f(): PassHandles@1653748
Може да видите че както p така и h се отнасят за един и същ обект. Това е много по-ефектимно от дублицирането на PassHandles обекта така че да можее да подадете аргумент на метод. Но това поражда важен въпрос.
Псевдоними
Псевдонимите означават, че повече от един указател сочи към един обект, както в горния пример. Проблемът с псевдонимите се случва когато някой пише в него обект. Ако собствениците на другите манипулатори към обекта не очакват той да се променя, те ще бъдат изненадани. Това може да бъде демонстрирано с прост пример:
//: c12:Alias1.java
// Aliasing two handles to one object
public class Alias1 {
int i;
Alias1(int ii) { i = ii; }
public static void main(String[] args) {
Alias1 x = new Alias1(7);
Alias1 y = x; // Assign the handle
System.out.println("x: " + x.i);
System.out.println("y: " + y.i);
System.out.println("Incrementing x");
x.i++;
System.out.println("x: " + x.i);
System.out.println("y: " + y.i);
}
} ///:~
В реда:
Alias1 y = x; // Assign the handle
нов Alias1 манипулатор се създава, но вместо да се насочи към пресния обект създаден с new, той се приравнява на съществуващия манипулатор. Така че съдържанието на манипулатора x, който е адрес на обекта x към който сочи, е приравнен на y, а с това и x и y сочат един и същ обект. Така че когато i-то на x се увеличава в оператора:
x.i++;
i-то на y също ще бъде засегжнато. Това може да бъде видяно в изхода:
x: 7
y: 7
Incrementing x
x: 8
y: 8
Едно добро решение на случая е просто той да не се състои: не правете алиасинг съзнателно в един обхват. Вашият код ще бъде по-лесен за четене и тестване. Обаче когато давате манипулатор като аргумент – което е начина по който се предполага да се работи в Java – понеже манипулаторът който автоматично се създава локално може да модифицира “външния обект” (обектът който е създаден извън обхвата на метода). Ето пример:
//: c12:Alias2.java
// Method calls implicitly alias their
// arguments.
public class Alias2 {
int i;
Alias2(int ii) { i = ii; }
static void f(Alias2 handle) {
handle.i++;
}
public static void main(String[] args) {
Alias2 x = new Alias2(7);
System.out.println("x: " + x.i);
System.out.println("Calling f(x)");
f(x);
System.out.println("x: " + x.i);
}
} ///:~
Изходът е:
x: 7
Calling f(x)
x: 8
Методът променя аргумента си, външния обект. Когато се случи нещо от този род, вие трябва да решите смислено ли е, дали потребителят го обаква и дали ще се създадат проблеми.
Изобщо, методи се викат за да върнат стойност и/или промяна на състоянието на обекта за който методът е извикан. (Методът е как “пращате съобщение” към него обект.) Много по-малко разпространено е да се вика метод заради промяна на аргументите му; това се нарича “викане на мутод заради странични ефекти.” Така, ако го правите, потребителят трябва да е добре инструктирани предупреден за възможни изненади. Поради смущението и капаните, много по-добре е да избягвате страничните ефекти.
Ако искате да промените аргумент по време на работа на метод и не възнамерявате да променяте външния аргумент, ще предпазите последния чрез създаване на копие в метода. Това е тема на по-голямата част от тази глава.
Сподели с приятели: |