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



страница44/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   40   41   42   43   44   45   46   47   ...   73

Резюме


Полиморфизъм значи “различни форми.” В ООП имате същото лице (общия интер­фейс на базовия клас) и различни форми използващи това лице: раз­лич­ни­те версии на динамично свързваните методи.

Видяхте в тази глава че не е възможно да се разбере и даже да се създаде при­мер за полиморфизъм без използването на абстракция на данните и на­сле­дя­ване. Полиморфизмът е черта която не може да се разглежда изолирано (като switch оператора например), а работи само в концерт, като част от “голямата кар­тина” на зависимостите между класовете. Хората често се смущават от дру­ги, не ОО черти на Java, като претоварването на методи, които понякога биват пред­ставяни като обектно ориентирани. Не се лъжете: ако няма късно свърз­ва­не няма полиморфизъм.

За да се използва полиморфизъм и с това ОО черти ефективно във вашите про­гра­ми трябва да си разширите кръгозора не извън само методите и съоб­ще­ния­та на отделен клас, но също и общо между класовете и техните взаи­мо­за­ви­си­мо­сти. Въпреки че това изисква значителни усилия, тази борба си заслужава, по­неже дава по-бърза разработка на програмите, по-добра организация на кода, раз­ширяеми програми, по-лесна поддръжка на кода.

Упражнения


  1. Създайте йерархия на наследяване от Rodent: Mouse, Gerbil, Hamster и т.н.. В базовия клас дайте методи които са общи за Rodent-ите, подтиснете ги в из­влечените класове за да осигурите специфичното за дадения Rodent по­ве­де­ние. Създайте масив Rodentи, попълнете го със специфични типове Rodentи, извикайте методите на базовия клас да видите какво ще стане.

  2. Променете упражнение 1 така че Rodent да е interface.

  3. Оправете проблема в WindError.java.

  4. В GreenhouseControls.java добавете Event вътрешни класове които включ­ват и изключват вентилаторите.

8: Притежаване на обектите


Твърде проста е програма, която има фиксиран брой обекти с известни времена на живот.

Изобщо програмите ви ще създават обекти според критерии, които ще са из­вест­ни чак по време на изпълнение на програмите. Няма да знаете докато про­гра­мата се пусне броя и даже точния тип на нужните обекти. За да се реши об­щия програмен проблем трябва да може да създавате произволно число обекти, по всяко време, навсякъде. Така че не може да се разчита на създаване на име­ну­ван манипулатор за всеки от обектите:

MyObject myHandle;

понеже никога не се знае точно колко броя ще трябват.

За да се реши този доста основен проблем, Java има няколко основни начина за вла­деене на обекти (или по-скоро - на манипулатори). Вграденият тип е масив, кой­то беше коментиран преди и ще се спрем още на него в тази глава. Също по­мощ­ната библиотека на Java има класове-колекции (също известни като кон­тей­нерни класове, но терминът “контейнер” е използван от Swing GUI би­блио­те­ката така че тук ще се използва “колекция”) които дават по-съвършен начин за владеене и даже за манипулиране на обекти. Останалата част от тази глава ще се занимава с тези неща.

Масиви


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

Има две неща, които отличават масивите сред другите колекции: ефективността и типа. Масивът е най-ефикасния начин който Java дава за запомняне и достъп до обекти (фактически - до манипулатори). Масивът е проста линейна по­сле­до­ва­телност, което прави достъпа до елементи бърз, но се плаща за тази скорост: ко­гато създавате масив, дължината му е зададена и не може да се променя по вре­ме на живота му (той е обект - б.пр.). Може да искате да създадете масив с опре­делена дължина и после, ако не ви стигне мястото, да създадете нов и да пре­местите ванипулаторите от стария масив в новия. Това е поведението на кла­са ArrayList който ще бъде изучен по-късно в тази глава. Поради до­пъл­ни­тел­ната работа заради тази гъвкавост с дължината, обаче, ArrayList е за­бе­ле­жи­мо по-малко фективен от масивите.

Класът vector в C++ знае типа на обектите които съдържа, но това е до­пъл­ни­те­лен недостатък в сравнение с масивите в Java: Операторът на vector в C++ operator[] не прави проверка за излизане от обхвата, така че може да минете зад края. (Възможно е, обаче, да питате колко е голям vector и методът at( ) пра­ви проверка да не се излиза извън границите.) В Java има проверка на гра­ни­ци­те независимо дали работите с масив или колекция – ще се получи RuntimeException ако излезете от границите. Както ще научите в глава 9, този тип изключение индицира програмистка грешка и затова не е необходимо да про­верявате за него в програмата. Впрочем, причината в C++ vector да не про­ве­рява за прескачане на границите при всеки достъп е скоростта – в Java имаме по­стоянни допълнителни разходи на ресурси както за масивите, така и за колек­ции­те.

Другите родови класове за колекции, които ще се изучават в тази глава, List, Set и Map, работят с обектите като че последните нямат специфичен тип. Тоест те ги третират като да са Object, кореновият клас на класовете в Java. Това ра­бо­ти чудесно от една гледна точка: необходимо е да построите само една ко­лек­ция, който и да е Java обект ще е подходящ за нея. (Освен примитивите – те мо­гат да бъдат слагани като константи в колекциите чрез обгръщащите класове в Java или като променяеми величини като ги включите в свой клас.) Това е вто­ро­то място където масивът е най-добър за родови колекции: когато създавате ма­сив го създавате да съдържа определен тип. Това значи че имате проверка по вре­ме на компилация да не сложите в него неправилен тип, или да не сбъркато ти­па който извлимчате. Разбира се, Java няма да ви позволи да изпротите не­под­ходящо съобщение към обект, както (ще провери типа-б.пр.) по време на ком­пилация, така и по време на изпълнение. Така че едното не е по-рисковано от другото; просто едното е по-бързо, по-добре е компилаторът да се обади, то­гава и вероятността крайният потребител да получи изключение е по-малка.

Заради ефективността и проверката на типовете е най-добре да използвате ма­си­ви ако е възможно. Ако обаче имате да решавате по-общ проблем масивите са твърде ограничаващи. След погледа към масивите останалата част от тази гла­ва ще бъде посветена на колекциите, доставяни с Java.

Масивите са първокласни обекти


Независимо от типа на масива с който работите идентификаторът на масива е фак­тически манипулатор към обект създаден на хийпа. Обектът в хийпа може да бъде създаден или имплицитно, като част от синтаксиса на инициализацията, или явно с new израз. Част от тобект на хийпа (фактически единственото поле или метод който е достъпен) е членът само за четене length който казва колко еле­мента могат да бъдат запомнени. Синтаксисът ‘[]’ е единственият друг до­стъп който маже да има до елементите на масив.

Следващият пример показва различни начини за инициализиране на масиви и как манипулаторите на масиви могат да бъдат присвоявани на други масиви-обек­ти. Също се показва че масивите от примитиви и масивите от обекти са поч­ти еднакви откъм използване. Единствената разлка е че часивите от обекти съ­държат манипулатори докато тези от примитиви съдържат направо стой­но­сти. (Виж стр. 89 ако има проблеми с изпълнението на тази програма.)

//: c08:ArraySize.java

// Initialization & re-assignment of arrays

package c08;
class Weeble {} // A small mythical creature
public class ArraySize {

public static void main(String[] args) {

// Arrays of objects:

Weeble[] a; // Null handle

Weeble[] b = new Weeble[5]; // Null handles

Weeble[] c = new Weeble[4];

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

c[i] = new Weeble();

Weeble[] d = {

new Weeble(), new Weeble(), new Weeble()

};

// Compile error: variable a not initialized:



//!System.out.println("a.length=" + a.length);

System.out.println("b.length = " + b.length);

// The handles inside the array are

// automatically initialized to null:

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

System.out.println("b[" + i + "]=" + b[i]);

System.out.println("c.length = " + c.length);

System.out.println("d.length = " + d.length);

a = d;

System.out.println("a.length = " + a.length);



// Java 1.1 initialization syntax:

a = new Weeble[] {

new Weeble(), new Weeble()

};

System.out.println("a.length = " + a.length);


// Arrays of primitives:

int[] e; // Null handle

int[] f = new int[5];

int[] g = new int[4];

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

g[i] = i*i;

int[] h = { 11, 47, 93 };

// Compile error: variable e not initialized:

//!System.out.println("e.length=" + e.length);

System.out.println("f.length = " + f.length);

// The primitives inside the array are

// automatically initialized to zero:

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

System.out.println("f[" + i + "]=" + f[i]);

System.out.println("g.length = " + g.length);

System.out.println("h.length = " + h.length);

e = h;

System.out.println("e.length = " + e.length);



// Java 1.1 initialization syntax:

e = new int[] { 1, 2 };

System.out.println("e.length = " + e.length);

}

} ///:~



Ето изхода от тази програма:

b.length = 5

b[0]=null

b[1]=null

b[2]=null

b[3]=null

b[4]=null

c.length = 4

d.length = 3

a.length = 3

a.length = 2

f.length = 5

f[0]=0

f[1]=0


f[2]=0

f[3]=0


f[4]=0

g.length = 4

h.length = 3

e.length = 3

e.length = 2

Масивът а a е просто манипулатор сочещ null и компилаторът предотвратява опи­ти да се прави с него нещо преди да е провилно инициалициран. Масивът b се инициалицира да сочи масив от Weeble манипулатори, но никога не се слагат там Weeble обекти. Може обаче да питате колко е дължината на масива, тъй ка­то b сочи законен обект. Това извежда наяве малък недостатък: не може да на­ме­­рите колко елемента има в масива, понеже length казва само колко елемента мо­же да бъдат сложени в масива; тоест, дължината на обекта-масив, не колко еле­мента съдържа. Обаче когато се създава обект неговия манипулатор се ини­циа­лизира с null така че можете да видите дали даден конкретен масив има еле­мен­ти като проверите дали манипулаторът му сочи null. По подобен начин все­ки масив от примитиви се инициализира с нула за числените типове, null за char и false за boolean.

Масивът c показва създаване на масив от обекти и присвояването на Weeble обек­ти на всичките слотове на масива. Масивът d показва синтаксиса на “агре­ги­раната инициализация” с който се получава създадане на обект-масив (неявно с new на хийпа, точно като масива c) и инициализиране с обекти Weeble, всич­ко­то в един ред.

Изразът


a = d;

как може да се вземе манипулатор присъединен към един масив-обект и да ме се присвои друг обект-масив, точно както може да се направи с всеки един ма­ни­пулатор на обект. Сега както a така и d сочат един и същ масив върху хийпа.

Java 1.1 добавя нов синтаксис за инициализация на масиви, която може да си пред­ставим като “динамична агрегатна инициализация.” Агрегатната ини­циа­ли­за­ция от Java 1.0 използвана за d трябва да бъде използвана в точката на де­фи­ни­ция на d, но със синтаксиса на Java 1.1 може да създавате и инициализирате ма­сиви навсякъде. Например да предположим че hide( ) е метод който приема ма­сив от Weeble обекти. Бихте могли да напишете:

hide(d);


но в Java 1.1 може също динамично да създадете масива който ще дадете като ар­гумент:

hide(new Weeble[] { new Weeble(), new Weeble() });

Този нов синтаксис дава по-удобен начин за писане на кода в някои ситуации.

Втората част от примера показва че масив от примитиви работи точно както ма­сив от обекти освен че масивът от примитиви съдържа директно стойности.


Колекции от примитиви


Колекцийните класове могат да съдържат само манипулатори към обекти. Ма­сив, обаче, може да бъде създаден да съдържа директно стойности, точно както мо­же да съдържа и манипулатори към масиви. Възможно е да се използват “обхващащи” класове като Integer, Double и т.н. за да се сложат стойности на при­митиви вътре в колекция, но както ще видите по-късно в тази глава в при­ме­ра WordCount.java обгръщащите класове са малко полезни в масиви. Дали да сло­жите примитиви в масив или да ги обгърнете в класове и да ги сложите в ко­лек­ции е въпрос на ефективност. Много по-ефективно е да сложите стойностите в масив отколкото класове в колекция.

Раз­бира се, ако вашата колекция трябва сама да може да се разширява, макар и да е от примитиви, трябва да използвате обгръщащите класове. Може да си мис­лите че трябва да има специализиран тип Vector за всеки от примитивните ти­пове данни, но Java не прави това за нас. Някакъв вид шаблони могат да да­дат един ден по-добър начина за решаване на този проблем в Java.1


Връщане на масив


Да предположим, че пишете метод и искате да върнете не едно нещо, а цял куп не­ща. Езици като C и C++ не улесняват това, защото не може да се върне на­пра­во масив, само указател към масив. Това докарва проблеми защото е трудно да се следи времето на живот на масив, което лесно води до изтичания на памет.

Java приема подобен подход, но просто се “връща масив.” В действителност, раз­бира се, се връща манипулатор към масив, но с Java нямате отговорност за то­зи масив – той ще бъде налице колкото ви трябва, а боклучарят ще почисти ко­гато му дойде времето.

Като пример да вземем връщането на масив от тип String:

//: c08:IceCream.java

// Returning arrays from methods
public class IceCream {

static String[] flav = {

"Chocolate", "Strawberry",

"Vanilla Fudge Swirl", "Mint Chip",

"Mocha Almond Fudge", "Rum Raisin",

"Praline Cream", "Mud Pie"

};

static String[] flavorSet(int n) {



// Force it to be positive & within bounds:

n = Math.abs(n) % (flav.length + 1);

String[] results = new String[n];

boolean[] picked =

new boolean[flav.length];

for (int i = 0; i < n; i++) {

int t;

do


t = (int)(Math.random() * flav.length);

while (picked[t]);

results[i] = flav[t];

picked[t] = true;

}

return results;



}

public static void main(String[] args) {

for(int i = 0; i < 20; i++) {

System.out.println(

"flavorSet(" + i + ") = ");

String[] fl = flavorSet(flav.length);

for(int j = 0; j < fl.length; j++)

System.out.println("\t" + fl[j]);

}

}

} ///:~



Методът flavorSet( ) създава масив от String наречен results. Дължината на то­зи масив е n, определена от аргумента който се дава на метода. После про­дъл­жава случайното избиране измежду flav и слагането им в results, което накрая се връща. Връщането на масив е точно като връщането на всеки един обект – връ­ща се манипулатор. Не е важно че масивът бе създаден в flavorSet( ), нито ако е бил създаден където и да е другаде, в този смисъл. Боклучарят се грижи за ма­хането на масива когато вече не е нужен, а той ще стои докато е нужен.

Меж­ду другото, забележете че flavorSet( ) избира по случаен начин като оси­гу­ря­ва да няма избиране два пъти на един елемент. Това се прави в do цикъл кой­то продължава избора докато намери че последното избрано не присъства в ма­си­ва picked. (Разбира се, сравняването на String също би могло да бъде из­полз­ва­но, но сравняването на String е неефективно.) Ако изборът е успешен, добавя се. (i се инкрементира).



main( ) извежда 20 пълни множества вкус, така че може да видите че flavorSet( ) използва случайно избиране всеки път. Това е по-лесно да се на­блю­да­ва ако се пренасочи изходът към файл. И, докато гледате файла, помнете, не сте наистина гладни. (Вие само искате сладоледа, вие не се нуждаете от него в дей­ствителност.)



Сподели с приятели:
1   ...   40   41   42   43   44   45   46   47   ...   73




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

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