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



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

Колекции


Да сумираме до тук: вашият пръв, най-ефективен начин за владеене на обекти е ма­сив, а и сте принудени да използвате масив ако владяното е примитиви. До края на главата ще разгледаме по-общия случай, когато не се знае към момента на написването на програмата колко обекта ще са нужни, та трябва по-услож­нен начин за владеенето на обектите. Java има три типа класове-колекции за ре­ша­ване на този проблем: List, Set и Map. Изненадващо много проблеми могат да се рашат с тези три инструмента.

Сред другите им характеристики – Set, например, може да съдържа само един обект с дадена стойност, а Map е асоциативен масив което позволява да се асо­циира обект с някой друг обект – е и тази, че колекторните класове на Java ав­томатично сами ще си променят дължината при нужда. По този начин може да се владеят произволен брой обекти и не е необходимо да се грижим за това по време на написването на програмите.


Неудобство: неизвестен тип


“Неудобството” да се използват колекциите на Java е, че когато сложите обект в тях губите информацията за типа му. Това става защото по времето на на­пис­ва­нето на класа-колекция програмистът не е имал представа за конкретния тип кла­сове, които ще образуват колекцията, а освен това ако се направи да се под­дър­жа само вашият тип колекцията няма вече да е инструмент за всеобща упо­тре­ба. Така че колекцията съдържа манипулратори на обекти от тип Object, ка­кви­то са разбира се всички обекти в Java, понеже това е кореновият клас. (Това не включва примитивните класове, понеже те не са наследени от нищо.) Това е мно­го добро решение, освен в следните случаи:

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

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

Откъм предимствата: Java не дава да се използват погрешно обектите които сте сло­жили в колекцията. Ако хвърлите куче в колекцията от котки, после минете през колекцията опитвайки се да третирате всичко като котки, ще получите из­клю­чение когато стигнете до кучето. В същия смисъл, ако се опитате да на­пра­ви­те каст на манипулатор към куче да стане на котка ще получите изключение по време на изпълнение.

Ето пример:

//: c08:CatsAndDogs.java

// Simple collection example

import java.util.*;
class Cat {

private int catNumber;

Cat(int i) {

catNumber = i;

}

void print() {



System.out.println("Cat #" + catNumber);

}

}


class Dog {

private int dogNumber;

Dog(int i) {

dogNumber = i;

}

void print() {



System.out.println("Dog #" + dogNumber);

}

}


public class CatsAndDogs {

public static void main(String[] args) {

ArrayList cats = new ArrayList();

for(int i = 0; i < 7; i++)

cats.add(new Cat(i));

// Not a problem to add a dog to cats:

cats.add(new Dog(7));

for(int i = 0; i < cats.size(); i++)

((Cat)cats.get(i)).print();

// Dog is detected only at run-time

}

} ///:~


Може да се види, че използването на ArrayList е праволинейно: създавате го, сла­гате обекти вътре чрез add( ), а по-късно ги вадите чрез get( ). (Забележете че Vector има метод size( ) за да може да видите колко елемента са прибавени и да не стъпите неочаквано извън края, предизвиквайки изключение по време на из­пълнението.)

Класовете Cat и Dog са различни – нямат нищо общо помежду си, освен че са Objects. (Ако не кажете явно от кой клас наследявате, наследявате от Object.) Кла­сът Vector който идва от java.util, държи Objects, така че не само може да сла­гате Cat в тази колекция използвайки метода на Vector add( ), но може също да добавите обекти Dog без оплаквания и грешки по време на изпълнение. Ко­га­то се опитате да извадите неща които мислите че са Cat обекти използвайки ме­тода на Vector get( ), получавате манипулатор към Object който трябва да се пре­върна в такъв към Cat. Тогава трябва да сложите целия израз в скоби за да ста­не превръщането проди да се използва методът print( ) на Cat, иначе ще по­лу­чите синтактична грешка. По време на изпълнение, когато се опитате да на­пра­вите каст на Dog обект към Cat, ще получите изключение.

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

Понякога работи правилно при всички случаи


Понякога изглежда че нещата стават както трябва без кастинг към оригиналния тип. Първият случай е доста специален: Класът String има малко по-голяма по­мощ от страна на компилатора за да работи гладко. Щом компилаторът очаква String обект и не получава такъв, той автоматично ще извика метода toString( ) кой­то е определен в Object и може да бъде подтиснат от всеки Java клас. Този ме­тод дава желания String обект, който после се използва където е нужно.

Така всичко което трябва да направите за да извеждат вашите обекти е да под­тис­нете метода toString( ) както е показано в следващия пример:

//: c08:WorksAnyway.java

// In special cases, things just seem

// to work correctly.

import java.util.*;


class Mouse {

private int mouseNumber;

Mouse(int i) {

mouseNumber = i;

}

// Magic method:



public String toString() {

return "This is Mouse #" + mouseNumber;

}

void print(String msg) {



if(msg != null) System.out.println(msg);

System.out.println(

"Mouse number " + mouseNumber);

}

}


class MouseTrap {

static void caughtYa(Object m) {

Mouse mouse = (Mouse)m; // Cast from Object

mouse.print("Caught one!");

}

}
public class WorksAnyway {



public static void main(String[] args) {

ArrayList mice = new ArrayList();

for(int i = 0; i < 3; i++)

mice.add(new Mouse(i));

for(int i = 0; i < mice.size(); i++) {

// No cast necessary, automatic call

// to Object.toString():

System.out.println(

"Free mouse: " + mice.get(i));

MouseTrap.caughtYa(mice.get(i));

}

}

} ///:~



You can see the redefinition of toString( ) in Mouse. In the second for loop in main( ) you find the statement:

System.out.println("Free mouse: " + mice.get(i));

След знака ‘+’ компилаторът очаква да види String обект. get( ) дава Object, та­ка че за да получи желания String компилаторът неявно вика toString( ). За не­ща­стие тези магии стават само със String; няма ги за никой друг тип.

Вторият подход за скриване на кастинга е заложен в Mousetrap. Методът caughtYa( ) приема не-Mouse, но Object, който после превръща в Mouse. Това е твър­де самонадеяно, разбира се, понеже чрез приемане на Object нищо не би мо­гло да бъде подадено към метода. Ако обаче кастингът не е коректен – ако сте подали неправилен тип – ще получите изключение по време на изпълнение. То­ва не е толкова добре като проверката по време на компилация, но все още би­ва. Забележете че при използването на метода:

MouseTrap.caughtYa(mice.get(i));

Не е необходим каст.


Правене на Vector чувствителен към типа


Може да не искате да изоставите този въпрос точно сега. По-добро решение е да наследите Vector, така че да приема само вашия тип и да произвежда само ва­шия тип:

//: c08:GopherList.java

// A type-conscious ArrayList

import java.util.*;


class Gopher {

private int gopherNumber;

Gopher(int i) {

gopherNumber = i;

}

void print(String msg) {



if(msg != null) System.out.println(msg);

System.out.println(

"Gopher number " + gopherNumber);

}

}


class GopherTrap {

static void caughtYa(Gopher g) {

g.print("Caught one!");

}

}


class GopherList {

private ArrayList v = new ArrayList();

public void add(Gopher m) {

v.add(m);

}

public Gopher get(int index) {



return (Gopher)v.get(index);

}

public int size() { return v.size(); }



public static void main(String[] args) {

GopherList gophers = new GopherList();

for(int i = 0; i < 3; i++)

gophers.add(new Gopher(i));

for(int i = 0; i < gophers.size(); i++)

GopherTrap.caughtYa(gophers.get(i));

}

} ///:~


Това е като в предния пример, освен че новият клас GopherVector има private член от тип Vector (наследяването ат Vector има тенденция да разочарова, по при­чини които ще видите по-късно), и методи точно като Vector. Обаче той не прие­ма и не произвежда родови Objectи, само Gopher обекти.

Понеже GopherVector ще приема само Gopher, ако бяхте написали:

gophers.add(new Pigeon());

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

Забележете че не е необходим каст за използване на get( ) – то си е Gopher.

Параметризирани типове


Този вид проблеми не е изолиран – има множество случаи, когато трябва да по­лу­чите нови типове от налични типове, в които е необходимо да се разполага с ин­формация за типа по време на компилация. Това е концепцията за па­ра­ме­три­зи­ран тип. В C++ това се поддържа директно от езика с шаблон. В определен мо­мент Java резервира ключовата дума generic за да я използва някой ден за па­ра­метризираните типове, но не е сигурно, че това някога ще се случи.




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




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

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