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


Използване на Collectionи



страница50/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   46   47   48   49   50   51   52   53   ...   73

Използване на Collectionи


Следващата таблица показва какво може да се направи с Collection, а по този начин и какво може да се направи със Set или List. (List има и друга функционалност.) Mapовете не са наследени от Collection и ще се разгледат отделно.

Boolean add(Object)

*Осигурява че Collection съдържа аргумента. Връща false ако не е добавило аргумента.

Boolean addAll(Collection)

*Добавя елементите в аргумента. Връща true ако има добавени елементи.

void clear( )

*Маха всички елементи в Collection.

Boolean contains(Object)

True ако Collection съдържа аргумента.

Boolean containsAll(Collection)

True ако Collection съдържа всичките елементи в аргумента.

Boolean isEmpty( )

True ако Collection няма елементи.

Iterator iterator( )

Връща Iterator който може да се използва за обикаляне елементите на Collection.

Boolean remove(Object)

*Ако аргументът е в Collection, маха се един негов екземпляр. Връща true ако е имало премахване.

Boolean removeAll(Collection)

*Маха всички елементи които се съдържат в аргумента. Връща true ако е имало махане.

Boolean retainAll(Collection)

*Връща само елементите които се съдържат в аргумента (“intersection” от теорията на множествата). Връща true ако е имало промяна.

int size( )

Връща броя на елементите в Collection.

Object[] toArray( )

Връща масив съдържащ всичките елементи на Collection.

Object[] toArray(Object[] a)

Връща масив съдържащ всички елементи на Collection, чийто тип съвпада с този на масива a а не е просто Object (необходимо е да се превърне масива в правилния тип).




*Това е метод “по избор” което значи че може и да не е реализиран от конкретна Collection. Ако не е, методът изхвърля UnsupportedOperationException. Изключенията са описани в глава 9.

Следващият пример демонстрира всички тези методи. Отново, те работят с вся­ко нещо наследено от Collection; ArrayList е използван като един вид “най-ма­лък общ знаменател”:

//: c08:newcollections:Collection1.java

// Things you can do with all Collections

package c08.newcollections;

import java.util.*;
public class Collection1 {

// Fill with 'size' elements, start

// counting at 'start':

public static Collection

fill(Collection c, int start, int size) {

for(int i = start; i < start + size; i++)

c.add(Integer.toString(i));

return c;

}

// Default to a "start" of 0:



public static Collection

fill(Collection c, int size) {

return fill(c, 0, size);

}

// Default to 10 elements:



public static Collection fill(Collection c) {

return fill(c, 0, 10);

}

// Create & upcast to Collection:



public static Collection newCollection() {

return fill(new ArrayList());

// ArrayList is used for simplicity, but it's

// only seen as a generic Collection

// everywhere else in the program.

}

// Fill a Collection with a range of values:



public static Collection

newCollection(int start, int size) {

return fill(new ArrayList(), start, size);

}

// Moving through a List with an iterator:



public static void print(Collection c) {

for(Iterator x = c.iterator(); x.hasNext();)

System.out.print(x.next() + " ");

System.out.println();

}

public static void main(String[] args) {



Collection c = newCollection();

c.add("ten");

c.add("eleven");

print(c);

// Make an array from the List:

Object[] array = c.toArray();

// Make a String array from the List:

String[] str =

(String[])c.toArray(new String[1]);

// Find max and min elements; this means

// different things depending on the way

// the Comparable interface is implemented:

System.out.println("Collections.max(c) = " +

Collections.max(c));

System.out.println("Collections.min(c) = " +

Collections.min(c));

// Add a Collection to another Collection

c.addAll(newCollection());

print(c);

c.remove("3"); // Removes the first one

print(c);

c.remove("3"); // Removes the second one

print(c);

// Remove all components that are in the

// argument collection:

c.removeAll(newCollection());

print(c);

c.addAll(newCollection());

print(c);

// Is an element in this Collection?

System.out.println(

"c.contains(\"4\") = " + c.contains("4"));

// Is a Collection in this Collection?

System.out.println(

"c.containsAll(newCollection()) = " +

c.containsAll(newCollection()));

Collection c2 = newCollection(5, 3);

// Keep all the elements that are in both

// c and c2 (an intersection of sets):

c.retainAll(c2);

print(c);

// Throw away all the elements in c that

// also appear in c2:

c.removeAll(c2);

System.out.println("c.isEmpty() = " +

c.isEmpty());

c = newCollection();

print(c);

c.clear(); // Remove all elements

System.out.println("after c.clear():");

print(c);

}

} ///:~



Първият метод дава начин да се запълни всяка Collection с тестови данни, в този случай int превърнати в String. Вторият метод често ще бъде използван по про­тежение на тази глава.

Двете версии на newCollection( ) създават ArrayListове съдържащи различни мно­жества от данни и ги връщат като Collection обекти, така че е ясно че не се из­ползва нищо освен интерфейса на Collection.

Методът print( ) също ще бъде използван в тази глава. Тъй като се придвижва през Collection чрез Iterator, който всяка Collection може да направи, той ще ра­боти с Listове и Setове и всяка Collection колекция създадена от Map.

main( ) използва прости задачи за показване на всичките методи на Collection.

Следващите секции дават сравнение на различните реализации на List, Set и Map и подсказват във всеки един случай (със звездичка) кое да бъде вашият из­бор по подразбиране. Ще видите че наследените класове Vector, Stack и Hashtable не са включени понеже във всички случаи те са предпочитаните кла­со­ве в Java 2 Collections.


Използване на Listове


List (interface)

Най-важната черта на един List е редът; той обещава да обработва елементите в конкретен ред. List добавя методи към Collection които позволямват вмъкване и махане на елементи и в средата на List. (Това се препоръчва само за LinkedList.) List ще направи ListIterator, а използвайки го може да извървите Listа в двете посоки, както и да вмъквате и махате елементи в средата на листа (отново, препоръчва се само за LinkedList).

ArrayList*

List подкрепен от масив. Използва се вместо Vector за обща употреба при владеенето на обекти. Позволява бърз достъп до произволен елемент, но е бавен когато се вмък­ват и изтриват елементи от средата. ListIterator трябва да се използва само за назад-напред разходки по ArrayList, не и за вмъкване и махане на елементи, което е скъпо срав­нено с LinkedList.

LinkedList

Дава оптимален последователен достъп, с нескъпи вмъквания и изтривания от средата на списъка. Относително бавен за произволен достъп. (Използвайте ArrayList вместо него.) Има също addFirst( ), addLast( ), getFirst( ), getLast( ), removeFirst( ) и removeLast( ) (които не са дефинирани в никой интерфейс или базов клас) за да поз­воли да се използва като стек, опашка и дек.

Всеки метод в следващия пример покрива различна група активности: такива каквито всеки списък може (basicTest( )), разходки наоколо с Iterator (iterMotion( )) versus промени чрез Iterator (iterManipulation( )), разглеждане на резултата от манипулирането на List (testVisual( )) и операции налични само за LinkedListове.

//: c08:newcollections:List1.java

// Things you can do with Lists

package c08.newcollections;

import java.util.*;
public class List1 {

// Wrap Collection1.fill() for convenience:

public static List fill(List a) {

return (List)Collection1.fill(a);

}

// You can use an Iterator, just as with a



// Collection, but you can also use random

// access with get():

public static void print(List a) {

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

System.out.print(a.get(i) + " ");

System.out.println();

}

static boolean b;



static Object o;

static int i;

static Iterator it;

static ListIterator lit;

public static void basicTest(List a) {

a.add(1, "x"); // Add at location 1

a.add("x"); // Add at end

// Add a collection:

a.addAll(fill(new ArrayList()));

// Add a collection starting at location 3:

a.addAll(3, fill(new ArrayList()));

b = a.contains("1"); // Is it in there?

// Is the entire collection in there?

b = a.containsAll(fill(new ArrayList()));

// Lists allow random access, which is cheap

// for ArrayList, expensive for LinkedList:

o = a.get(1); // Get object at location 1

i = a.indexOf("1"); // Tell index of object

b = a.isEmpty(); // Any elements inside?

it = a.iterator(); // Ordinary Iterator

lit = a.listIterator(); // ListIterator

lit = a.listIterator(3); // Start at loc 3

i = a.lastIndexOf("1"); // Last match

a.remove(1); // Remove location 1

a.remove("3"); // Remove this object

a.set(1, "y"); // Set location 1 to "y"

// Keep everything that's in the argument

// (the intersection of the two sets):

a.retainAll(fill(new ArrayList()));

// Remove everything that's in the argument:

a.removeAll(fill(new ArrayList()));

i = a.size(); // How big is it?

a.clear(); // Remove all elements

}

public static void iterMotion(List a) {



ListIterator it = a.listIterator();

b = it.hasNext();

b = it.hasPrevious();

o = it.next();

i = it.nextIndex();

o = it.previous();

i = it.previousIndex();

}

public static void iterManipulation(List a) {



ListIterator it = a.listIterator();

it.add("47");

// Must move to an element after add():

it.next();

// Remove the element that was just produced:

it.remove();

// Must move to an element after remove():

it.next();

// Change the element that was just produced:

it.set("47");

}

public static void testVisual(List a) {



print(a);

List b = new ArrayList();

fill(b);

System.out.print("b = ");

print(b);

a.addAll(b);

a.addAll(fill(new ArrayList()));

print(a);

// Insert, remove, and replace elements

// using a ListIterator:

ListIterator x = a.listIterator(a.size()/2);

x.add("one");

print(a);

System.out.println(x.next());

x.remove();

System.out.println(x.next());

x.set("47");

print(a);

// Traverse the list backwards:

x = a.listIterator(a.size());

while(x.hasPrevious())

System.out.print(x.previous() + " ");

System.out.println();

System.out.println("testVisual finished");

}

// There are some things that only



// LinkedLists can do:

public static void testLinkedList() {

LinkedList ll = new LinkedList();

Collection1.fill(ll, 5);

print(ll);

// Treat it like a stack, pushing:

ll.addFirst("one");

ll.addFirst("two");

print(ll);

// Like "peeking" at the top of a stack:

System.out.println(ll.getFirst());

// Like popping a stack:

System.out.println(ll.removeFirst());

System.out.println(ll.removeFirst());

// Treat it like a queue, pulling elements

// off the tail end:

System.out.println(ll.removeLast());

// With the above operations, it's a dequeue!

print(ll);

}

public static void main(String args[]) {



// Make and fill a new list each time:

basicTest(fill(new LinkedList()));

basicTest(fill(new ArrayList()));

iterMotion(fill(new LinkedList()));

iterMotion(fill(new ArrayList()));

iterManipulation(fill(new LinkedList()));

iterManipulation(fill(new ArrayList()));

testVisual(fill(new LinkedList()));

testLinkedList();

}

} ///:~



В basicTest( ) и iterMotion( ) виканията са сложени просто да се покаже пра­вил­ният синтаксис, а ако и да е хваната връщаната стойност, тя не се използва. В ня­кои случаи връщаната стойност не се хваща, понеже типично не се използва. Ще разгледате пълната документация за тези методи преди да ги използвате.

Използване на Setове


Set има точно същия интерфейс като Collection, така че няма допълнителна функ­ционалност както с двата различни Listа. Set е точно Collection, той има са­мо различно поведение. (Това е идеалната употреба на наследяването и поли­мор­физма: да се изрази различно поведение.) Set позволява само един екзем­пляр от всяка стойност на обект да съществува (което конституира “стой­ност­та” по-сложно, както ще видите).

Set (interface)

Всеки елемент който се добавя към Set трябва да бъде уникален; иначе Set не добавя дублициращия елемент. Обектите добавени към Set трябва да дефинират equals( ) за да има уникалност на обекти. Set има точно същия интерфейс като Collection. Интерфейсът на Set не гарантира че обектите ще бъдат в някакъв определен ред.

HashSet*

За Setове където бързото търсене е важно. Обектите трябва да дефинират също и hashCode( ).

TreeSet

Подреден Set подкрепен от червено-черно дърво. По този на­чин може да извлечете подредена последователност от Set.

Следния пример не показва всичко което може да се направи със Set, тъй като ин­терфейсът е като на Collection и беше изпитан в предишншя пример. Вместо то­ва се демонстрира поведението, което прави Set уникален:

//: c08:newcollections:Set1.java

// Things you can do with Sets

package c08.newcollections;

import java.util.*;
public class Set1 {

public static void testVisual(Set a) {

Collection1.fill(a);

Collection1.fill(a);

Collection1.fill(a);

Collection1.print(a); // No duplicates!

// Add another set to this one:

a.addAll(a);

a.add("one");

a.add("one");

a.add("one");

Collection1.print(a);

// Look something up:

System.out.println("a.contains(\"one\"): " +

a.contains("one"));

}

public static void main(String[] args) {



testVisual(new HashSet());

testVisual(new TreeSet());

}

} ///:~


Дублиращите стойности са добавени към Set, но когато се извежда ще видите че Set е приело само един екземпляр от всяка стойност.

Като пускате тази програма ще видите че редът поддържан от HashSet е раз­ли­чен от TreeSet, понеже всеки има различен начин на запомняне на елементи кои­то по-късно да бъдат търсени. (TreeSet ги държи сортирани, докато HashSet из­ползва хеш функция, което е нарочно за бързи търсения.) Като създавате соб­стве­ни типове бъдете предупредени че Set иска начин да поддържа подредба в па­метта, точно както с “groundhog” примерите показани по-рано в главата. За да се реализира сравняемост в Java 2 Collections, обаче, трябва да приложите Comparable интерфейс и да реализирате compareTo( ) метод (това ще се опише пъл­но по-ксно). Ето пример:

//: c08:newcollections:Set2.java

// Putting your own type in a Set

package c08.newcollections;

import java.util.*;


class MyType implements Comparable {

private int i;

public MyType(int n) { i = n; }

public boolean equals(Object o) {

return

(o instanceof MyType)



&& (i == ((MyType)o).i);

}

public int hashCode() { return i; }



public String toString() { return i + " "; }

public int compareTo(Object o) {

int i2 = ((MyType) o).i;

return (i2 < i ? -1 : (i2 == i ? 0 : 1));

}

}
public class Set2 {



public static Set fill(Set a, int size) {

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

a.add(new MyType(i));

return a;

}

public static Set fill(Set a) {



return fill(a, 10);

}

public static void test(Set a) {



fill(a);

fill(a); // Try to add duplicates

fill(a);

a.addAll(fill(new TreeSet()));

System.out.println(a);

}

public static void main(String[] args) {



test(new HashSet());

test(new TreeSet());

}

} ///:~


Дефинициите за equals( ) и hashCode( ) следват формата дадена в “groundhog” при­мерите. Трябва да дефинирате equals( ) в двата случая, но hashCode( ) е абсо­лютно необходим само ако класът ще се слага в HashSet (което е вероятно, поне­же това ще бъде първото ви предпочитание за реализация на Set). Обаче за програ­мистки стил винаги ще подтискате hashCode( ) когато подтискате equals( ).

В compareTo( ) забележете че не съм използвал “простата и очевидна форма” return i-i2. Макар и това да е честа програмна грешка, тя ще работи само ако i и i2 са unsigned int (ако Java имаше ключовата дума “unsigned” каквато той няма). Начинът не върви за целите със знак в Java които са недостатъчно го­ле­ми да представят разликата чежду две int. Ако i е голямо положително цяло а j е голямо отрицателно число, i-j ще препълни и ще върне отрицателна стойност, кое­то е грешка.


Използване на Mapове


Map (interface)

Поддържа ключ-стойност асоциации (pairs), така че може да намерите стойност по ключ.

HashMap*

Реализация основана на хеш таблица. (Използвайте го вместо Hashtable.) Дава константно време на изпълнение за вмъ­кване и изтриване на двойки. Скоростта може да бъде на­гласявана чрез конструктори които позволяват да се за­да­ват капацитет и фактор на натоварването за хеш таб­ли­ца­та.

TreeMap

Реализация, основана на червено-бяло дърво. Когато гле­да­те ключове или двойки, те ще бъдат сортирани (определено отComparable или Comparator, обсъждани по-късно). На­ме­рението на TreeMap е че вземате резултатите сор­тирани. TreeMapе единствен Map с метода subMap( ) кое­то поз­во­ля­ва да се върне поддърво.

Следващият пример съдържа две множества от данни и fill( ) метод който поз­во­лява да попълните всяка карта с двуразмерен масив от Objectи. Тези ин­стру­мен­ти ще се използват и в други Map примери също така.

//: c08:newcollections:Map1.java

// Things you can do with Maps

package c08.newcollections;

import java.util.*;
public class Map1 {

public final static String[][] testData1 = {

{ "Happy", "Cheerful disposition" },

{ "Sleepy", "Prefers dark, quiet places" },

{ "Grumpy", "Needs to work on attitude" },

{ "Doc", "Fantasizes about advanced degree"},

{ "Dopey", "'A' for effort" },

{ "Sneezy", "Struggles with allergies" },

{ "Bashful", "Needs self-esteem workshop"},

};

public final static String[][] testData2 = {



{ "Belligerent", "Disruptive influence" },

{ "Lazy", "Motivational problems" },

{ "Comatose", "Excellent behavior" }

};

public static Map fill(Map m, Object[][] o) {



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

m.put(o[i][0], o[i][1]);

return m;

}

// Producing a Set of the keys:



public static void printKeys(Map m) {

System.out.print("Size = " + m.size() +", ");

System.out.print("Keys: ");

Collection1.print(m.keySet());

}

// Producing a Collection of the values:



public static void printValues(Map m) {

System.out.print("Values: ");

Collection1.print(m.values());

}

// Iterating through Map.Entry objects (pairs):



public static void print(Map m) {

Collection entries = m.entrySet();

Iterator it = entries.iterator();

while(it.hasNext()) {

Map.Entry e = (Map.Entry)it.next();

System.out.println("Key = " + e.getKey() +

", Value = " + e.getValue());

}

}



public static void test(Map m) {

fill(m, testData1);

// Map has 'Set' behavior for keys:

fill(m, testData1);

printKeys(m);

printValues(m);

print(m);

String key = testData1[4][0];

String value = testData1[4][1];

System.out.println("m.containsKey(\"" + key +

"\"): " + m.containsKey(key));

System.out.println("m.get(\"" + key + "\"): "

+ m.get(key));

System.out.println("m.containsValue(\""

+ value + "\"): " +

m.containsValue(value));

Map m2 = fill(new TreeMap(), testData2);

m.putAll(m2);

printKeys(m);

m.remove(testData2[0][0]);

printKeys(m);

m.clear();

System.out.println("m.isEmpty(): "

+ m.isEmpty());

fill(m, testData1);

// Operations on the Set change the Map:

m.keySet().removeAll(m.keySet());

System.out.println("m.isEmpty(): "

+ m.isEmpty());

}

public static void main(String args[]) {



System.out.println("Testing HashMap");

test(new HashMap());

System.out.println("Testing TreeMap");

test(new TreeMap());

}

} ///:~


Методите printKeys( ), printValues( ) и print( ) са не само полезни ютилита, те също демонстрират произвеждането на Collection изгледи на Map. Методът keySet( ) дава Set подкрепена от ключовете в Map; тук той се третира само ка­то Collection. Подобно е третирането на values( ), който дава List съдържащ всич­ките стойности в Map. (Забележете че ключовете трябва да са уникални, до­като стойностите могат да съдържат дубликати.) Тъй като тези Collectionи са под­държани от Map, всякакви промени в Collection ще рефлектират в асо­ции­ра­ния Map.

Методът print( ) грабва Iteratorа даден от entries и го използва за извеждане на клю­ча и стойността за всяка двойка. Останалата част от програмата дава про­сти приложения на всяка операция на Map и тества всеки тип Map.

Когато създавате собствени класове за използване като ключ в Map трябва да се справите със същите въпроси дискутирани по-рано във връзка със Setовете.

Избиране на реализация


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

За да разберете отговора трябва да знаете, че всяка реализация има своите соб­стве­ни черти, силни страни и слабости. Например може да видите на диа­гра­ма­та (не е показана-б.пр.) че “чертата” на Hashtable, Vector и Stack е че те са на­сле­дени класове, така че съществуващия код няма да се скапва. От друга страна най-добре е да не ги използвате за нов (Java 2) код.

Разликата между колекциите често изчезва от това, което изразяваме с ”под­кре­пяно от;” тоест структурите данни които физически реализират вашия interface. Това значи че, например, ArrayList, LinkedList и Vector (което е гру­ба еквивалентност на ArrayList) всички прилагат интерфейса на List така че ва­ша­та програма ще дава едни и същи резултати независимо какво използвате. Оба­че ArrayListVector) се подкрепят от масив, докато LinkedList е реа­ли­зи­рано по обикновения начин за двойно свързани листове, като отделни обекти съ­държащи данните заедно с полета с адресите на предишния и следващия еле­мент. Поради това ако имате да правите множество вмъквания и изтривания в сре­дата на списъка LinkedList е подходящия избор. (LinkedList има също до­пъл­нителни възможности свързани с AbstractSequentialList.) Ако не, ArrayList е вероятно по-бърз.

Като друг пример Set може да бъде реализиран чрез TreeSet или HashSet. TreeSet се подкрепя от TreeMap и е проектиран да поддържа постоянно сор­ти­ра­но множество. Обаче ако ще имате големи количества във вашия Set, ско­рост­та на добавянията на TreeSet ще стане малка. Когато пишете програма коя­то ще използва Set ще използвате HashSet по подразбиране, а ще преминете на TreeSet когато стане по-важно да имате постоянно сортирано множество.


Избор между Listове


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

//: c08:newcollections:ListPerformance.java

// Demonstrates performance differences in Lists

package c08.newcollections;

import java.util.*;
public class ListPerformance {

private static final int REPS = 100;

private abstract static class Tester {

String name;

int size; // Test quantity

Tester(String name, int size) {

this.name = name;

this.size = size;

}

abstract void test(List a);



}

private static Tester[] tests = {

new Tester("get", 300) {

void test(List a) {

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

for(int j = 0; j < a.size(); j++)

a.get(j);

}

}



},

new Tester("iteration", 300) {

void test(List a) {

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

Iterator it = a.iterator();

while(it.hasNext())

it.next();

}

}



},

new Tester("insert", 1000) {

void test(List a) {

int half = a.size()/2;

String s = "test";

ListIterator it = a.listIterator(half);

for(int i = 0; i < size * 10; i++)

it.add(s);

}

},

new Tester("remove", 5000) {



void test(List a) {

ListIterator it = a.listIterator(3);

while(it.hasNext()) {

it.next();

it.remove();

}

}



},

};

public static void test(List a) {



// A trick to print out the class name:

System.out.println("Testing " +

a.getClass().getName());

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

Collection1.fill(a, tests[i].size);

System.out.print(tests[i].name);

long t1 = System.currentTimeMillis();

tests[i].test(a);

long t2 = System.currentTimeMillis();

System.out.println(": " + (t2 - t1));

}

}

public static void main(String[] args) {



test(new ArrayList());

test(new LinkedList());

}

} ///:~


Вътрешния клас Tester е abstract, за да се получи базов клас за тестовете. Той съ­държа String за извеждане когато стартира тестът, size параметър за да бъде из­ползван за количество елементи или брой повторения на тестове, кон­струк­тор за инициализация на полетата и abstract метод test( ) който върши ра­бо­та­та. Всичките различни типове тестове са събрани на едно място, масива tests, кой­то се инициализира с анонимни вътрешни класове наследени от Tester. За да се добавят или махнат тестове просто добавете или махнете дефиниция на въ­тре­шен клас в масива, а всичко останало ще стане автоматично.

Listът предаден на test( ) първо се попълва с елементи, после всеки тест в tests се замерва. Резултатите ще се менят от машина на машина; те дават само срав­не­ние между скоростта на различните измервани неща. Ето резюме от едно пус­кане:

Type

Get

Iteration

Insert

Remove

ArrayList

110

490

3790

8730

LinkedList

1980

220

110

110

Може да се види, че произволния достъп (get( )) е евтин за ArrayList и скъп за LinkedList. (Наопаки, итерацията е по-бърза за LinkedList отколкото за ArrayList,което противоречи на интуицията.) От друга страна, вмъкванията и из­триванията в средата на списъка са драматично по-евтини за LinkedList от­кол­кото за ArrayList. Вероятно най-добрият подход е да се използва ArrayList пър­воначално и да се мине на LinkedList ако се натъкнете на проблеми със ско­рост­та дължащи се на множество вмъквания и изтривания.

Избор между Setовете


Имате избор между TreeSet и HashSet, в зависимост от дължината на Setа(ако ви трябва наредена последователност от Set, използвайте TreeSet). Следната те­сто­ва програма разглежда тези неща:

//: c08:newcollections:SetPerformance.java

package c08.newcollections;

import java.util.*;


public class SetPerformance {

private static final int REPS = 200;

private abstract static class Tester {

String name;

Tester(String name) { this.name = name; }

abstract void test(Set s, int size);

}

private static Tester[] tests = {



new Tester("add") {

void test(Set s, int size) {

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

s.clear();

Collection1.fill(s, size);

}

}



},

new Tester("contains") {

void test(Set s, int size) {

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

for(int j = 0; j < size; j++)

s.contains(Integer.toString(j));

}

},

new Tester("iteration") {



void test(Set s, int size) {

for(int i = 0; i < REPS * 10; i++) {

Iterator it = s.iterator();

while(it.hasNext())

it.next();

}

}



},

};

public static void test(Set s, int size) {



// A trick to print out the class name:

System.out.println("Testing " +

s.getClass().getName() + " size " + size);

Collection1.fill(s, size);

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

System.out.print(tests[i].name);

long t1 = System.currentTimeMillis();

tests[i].test(s, size);

long t2 = System.currentTimeMillis();

System.out.println(": " +

((double)(t2 - t1)/(double)size));

}

}



public static void main(String[] args) {

// Small:

test(new TreeSet(), 10);

test(new HashSet(), 10);

// Medium:

test(new TreeSet(), 100);

test(new HashSet(), 100);

// Large:

test(new HashSet(), 1000);

test(new TreeSet(), 1000);

}

} ///:~


Следната таблица показва резултатите от едно пускане (с Beta3 софтуер на една конкретна платформа; ще го пуснете и при себе си също):

Type

Test size

Add

Contains

Iteration




10

22.0

11.0

16.0

TreeSet

100

22.5

13.2

12.1




1000

31.1

18.7

11.8




10

5.0

6.0

27.0

HashSet

100

6.6

6.6

10.9




1000

7.4

6.6

9.5

HashSet изобщо превъзхожда TreeSet за всички операции, а скоростта е ефек­тив­но независима от дължината.

Избор между Mapове


Когато избираме измежду реализации на Map дължината на Map е нещото, кое­то най-мно­го засяга скоростта, а следната програма разработва това:

//: c08:newcollections:MapPerformance.java

// Demonstrates performance differences in Maps

package c08.newcollections;

import java.util.*;
public class MapPerformance {

private static final int REPS = 200;

public static Map fill(Map m, int size) {

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

String x = Integer.toString(i);

m.put(x, x);

}

return m;



}

private abstract static class Tester {

String name;

Tester(String name) { this.name = name; }

abstract void test(Map m, int size);

}

private static Tester[] tests = {



new Tester("put") {

void test(Map m, int size) {

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

m.clear();

fill(m, size);

}

}



},

new Tester("get") {

void test(Map m, int size) {

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

for(int j = 0; j < size; j++)

m.get(Integer.toString(j));

}

},

new Tester("iteration") {



void test(Map m, int size) {

for(int i = 0; i < REPS * 10; i++) {

Iterator it = m.entrySet().iterator();

while(it.hasNext())

it.next();

}

}



},

};

public static void test(Map m, int size) {



// A trick to print out the class name:

System.out.println("Testing " +

m.getClass().getName() + " size " + size);

fill(m, size);

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

System.out.print(tests[i].name);

long t1 = System.currentTimeMillis();

tests[i].test(m, size);

long t2 = System.currentTimeMillis();

System.out.println(": " +

((double)(t2 - t1)/(double)size));

}

}



public static void main(String[] args) {

// Small:

test(new Hashtable(), 10);

test(new HashMap(), 10);

test(new TreeMap(), 10);

// Medium:

test(new Hashtable(), 100);

test(new HashMap(), 100);

test(new TreeMap(), 100);

// Large:

test(new HashMap(), 1000);

test(new Hashtable(), 1000);

test(new TreeMap(), 1000);

}

} ///:~



Понеже дължината е гвоздеят, ще видите че тестовете делят времето на из­пъл­не­ние на дължината за нормализация на резултата. Ето едни резултати. (Вашите ве­роятно ще са различни.)

Type

Test size

Put

Get

Iteration



10

11.0

5.0

44.0

Hashtable

100

7.7

7.7

16.5




1000

8.0

8.0

14.4



10

16.0

11.0

22.0

TreeMap

100

25.8

15.4

13.2




1000

33.8

20.9

13.6



10

11.0

6.0

33.0

HashMap

100

8.2

7.7

13.7




1000

8.0

7.8

11.9

Както може да се очаква, скоростта на Hashtable е приблизително равна на тази на HashMap (може също да забележите че HashMap е изобщо малко по-бърза. Пом­нете че HashMap има да замества Hashtable). TreeMap е изобщо по-бавен от HashMap, та цащо въобще би се използвал? Не бихте могли да я използвате ка­то Map, а като начин за създаване на подреден списък. Поведението на дър­во­то е такова, че то винаги е подредено и няма нужда специално да се сортира. (На­чинът на сортиране ще се обсъди по-късно.) Веднъж като запълните TreeMap, може да викате keySet( ) за да вземете Set изглед на ключовете, toArray( ) за да създадете масив от ключовете. Може да използвате static ме­то­да Arrays.binarySearch( ) (обсъждан по-късно) за скоростно намиране на обек­ти в сортирания ви масив. Разбира се това ще се прави само ако по някаква при­чи­на поведението на HashMap е неприемливо, понеже HashMap е проектиран бър­зо да намира нещата. Накрая, като използвате Map вашият първи избор ще бъ­де HashMap, а само ако искате непрекъснато сортиран Map ще ви трябва TreeMap.

Има друг въпрос свързан със скоростта, който горната таблица не адресира, а имен­но скоростта на създаване. Следващата програма изпробва скоростта на съз­даване на Map:

//: c08:newcollections:MapCreation.java

// Demonstrates time differences in Map creation

package c08.newcollections;

import java.util.*;


public class MapCreation {

public static void main(String[] args) {

final long REPS = 100000;

long t1 = System.currentTimeMillis();

System.out.print("Hashtable");

for(long i = 0; i < REPS; i++)

new Hashtable();

long t2 = System.currentTimeMillis();

System.out.println(": " + (t2 - t1));

t1 = System.currentTimeMillis();

System.out.print("TreeMap");

for(long i = 0; i < REPS; i++)

new TreeMap();

t2 = System.currentTimeMillis();

System.out.println(": " + (t2 - t1));

t1 = System.currentTimeMillis();

System.out.print("HashMap");

for(long i = 0; i < REPS; i++)

new HashMap();

t2 = System.currentTimeMillis();

System.out.println(": " + (t2 - t1));

}

} ///:~



По времето когато тази програма беше писана TreeMap беше драматично по-бърз от другите два типа. Това, заедно с приемливата и смислена скорост на put( ) от TreeMap, дава възможна стратегия в случай че създавате множество Mapове, а чак по-късно в програмата си правите много търсения: Да се съз­да­дат и запълнят TreeMaps, а после когато започнете търсенията, да се превърнат важ­ните TreeMapове в HashMapове чрез HashMap(Map) конструктора. Пак да подчертая, това ще се прави само ако има доказано тясно място и зна­чи­тел­но намаление на скоростта. (“Първо го направи да върви, после го прави да бъ­де бързо – ако трябва.”)

Неподдържани операции


Възможно е да се превърне масив в List със статичния метод static Arrays.asList( ):

//: c08:newcollections:Unsupported.java

// Sometimes methods defined in the Collection

// interfaces don't work!

package c08.newcollections;

import java.util.*;


public class Unsupported {

private static String[] s = {

"one", "two", "three", "four", "five",

"six", "seven", "eight", "nine", "ten",

};

static List a = Arrays.asList(s);



static List a2 = Arrays.asList(

new String[] { s[3], s[4], s[5] });

public static void main(String[] args) {

Collection1.print(a); // Iteration

System.out.println(

"a.contains(" + s[0] + ") = " +

a.contains(s[0]));

System.out.println(

"a.containsAll(a2) = " +

a.containsAll(a2));

System.out.println("a.isEmpty() = " +

a.isEmpty());

System.out.println(

"a.indexOf(" + s[5] + ") = " +

a.indexOf(s[5]));

// Traverse backwards:

ListIterator lit = a.listIterator(a.size());

while(lit.hasPrevious())

System.out.print(lit.previous());

System.out.println();

// Set the elements to different values:

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

a.set(i, "47");

Collection1.print(a);

// Compiles, but won't run:

lit.add("X"); // Unsupported operation

a.clear(); // Unsupported

a.add("eleven"); // Unsupported

a.addAll(a2); // Unsupported

a.retainAll(a2); // Unsupported

a.remove(s[0]); // Unsupported

a.removeAll(a2); // Unsupported

}

} ///:~


Ще откриете че само част от интерфейсите на Collection и List са в дей­стви­тел­ност реализирани. Останалите методи довеждат до неприятната поява на неща, на­речено UnsupportedOperationException. Ще научите всичко за из­клю­че­ния­та в следващата глава, но накъсо историята е, че Collection interface, както и ня­кои други interfaceи в библиотеката Колекции на Java 2, съдържа методи “по же­лание”, които могат да бъдат или да не бъдат “поддържани” в конкретен клас кой­то implements този interface. Извикването на неподдържан метод предиз­вик­ва UnsupportedOperationException за да се индицира програмна грешка.

“Какво?!?” казвате вие скептично. “Цялата работа на interfaceите и базовите кла­сове е че те обещават техните методи да направят нещо полезно! Това на­ру­ша­ва обещанието – значи не само че ще извикаме методи които ги няма, но ще спре и програмата! Безопасността на типовете просто е изхвърлена през про­зо­ре­ца!” Това не е чак така лошо. С Collection, List, Set и Map компилаторът про­дъл­жава да ви ограничава да викате методи само в рамките на този interface, та­ка че не е като в Smalltalk (където може да се извика всеки метод на всеки обект, но да се разбере какво прави може чак по време на изпълнение).В до­бав­ка, повечето методи каито взимат Collection като аргумент само четат тази Collection –всичките “четящи” методи в Collection не са по желание.

Този подход предотвратява експлозията на интерфейсите в дизайна. Други раз­ра­ботки на библиотеки от колекции винаги свършват в блато от интерфейси кои­то да описват всичките вариации по темата и са трудни за изучаване. Даже е не­възможно да се проследят различните специални класове и interfaceи, понеже ня­кой винаги би могър да наследи някой interface. С одхода на “не­под­дър­жа­на­та операция” се достига важна цел на Java 2 библиотеката колекции: тя е лесна за учене и използване. За да работи подходът, обаче:


  1. UnsupportedOperationException трябва да е рядък случай. Тоез за повечето случаи класовете трябва да работят, а само в специални случаи да има неподдържана операция. Това е вярно за библиотеката колекции на Java 2 тъй като класовете които използвате 99% от времето – ArrayList, LinkedList, HashSet и HashMap, както и другите конкретни реализации – под­държат всичките операции. Дизайнът дава “задна вратичка” ако искате да създадете нова Collection без да се предефинира всичко за Collection interface, и все пак да пасва на съществуващата библиотека.

  2. Когато операцията е неподдържана, има достатъчна вероятност че UnsupportedOperationException ще се появи по време на разработката, а не когато доставите програмата на потребителя. Най-после, това индицира про­грамна грешка: използвали сте клас некоректно. Това е по-малко си­гур­но и излиза когато експерименталната натура на този проект излиза на пре­ден план. Само с течение на времето ще видим дали работи добре.

В горния пример Arrays.asList( ) дава List който е подкрепен от масив с фик­си­рана дължина. Това прави смислено само поддържаните операции да са тези, кои­то не променят дължината на масива. Ако, от друга страна, нов interface бе­ше необходим за да се изрази тази нова черта на поведението (наречен, да ка­жем, “FixedSizeList”), щеше да се отвори вратата за усложняване и скоро ня­ма­ше да знаете от къде да започнете, ако искате да използвате библиотеката.

Документацията за метод който взима Collection, List, Set или Map трябва да по­казва какви методи по желания трябва да се реализират. Например сор­ти­ра­не­то изисква set( ) и Iterator.set( ) методите но не и add( ) и remove( ).


Сортиране и търсене


Java 2 дава помощ при търсене и сортиране в Listове. Това са static методи от два нови класа: Arrays за сортиране и търсене с масиви и Collections за сор­ти­ра­не и търсене в Listове.

Arrays


Класът Arrays има претоварен sort( ) и binarySearch( ) за масиви от всичките при­митивни типове, както и за String и Object. Ето пример който показва тър­се­не и сортиране на масив от byte (всички останали примитиви изглеждат по съ­щия начин) и масив от String:

//: c08:newcollections:Array1.java

// Testing the sorting & searching in Arrays

package c08.newcollections;

import java.util.*;
public class Array1 {

static Random r = new Random();

static String ssource =

"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +

"abcdefghijklmnopqrstuvwxyz";

static char[] src = ssource.toCharArray();

// Create a random String

public static String randString(int length) {

char[] buf = new char[length];

int rnd;


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

rnd = Math.abs(r.nextInt()) % src.length;

buf[i] = src[rnd];

}

return new String(buf);



}

// Create a random array of Strings:

public static

String[] randStrings(int length, int size) {

String[] s = new String[size];

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

s[i] = randString(length);

return s;

}

public static void print(byte[] b) {



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

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

System.out.println();

}

public static void print(String[] s) {



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

System.out.print(s[i] + " ");

System.out.println();

}

public static void main(String[] args) {



byte[] b = new byte[15];

r.nextBytes(b); // Fill with random bytes

print(b);

Arrays.sort(b);

print(b);

int loc = Arrays.binarySearch(b, b[10]);

System.out.println("Location of " + b[10] +

" = " + loc);

// Test String sort & search:

String[] s = randStrings(4, 10);

print(s);

Arrays.sort(s);

print(s);

loc = Arrays.binarySearch(s, s[4]);

System.out.println("Location of " + s[4] +

" = " + loc);

}

} ///:~


Първата част от класа съдържа ютилита за създаване на случайни String обекти из­ползвайки масив от букви, от които случайно може да се избира. randString( ) връща стринг с произволна дължина, а randStrings( ) създава ма­сив от случайни Stringове, ако му се даде дължината на Stringа и желаната дъл­жи­на на масива. Двата print( ) метода опростяват извеждането на примерните ма­сиви. В main( ) Random.nextBytes( ) пълни масива-аргумент със случайно из­бра­ни byteове. (Няма съответни Random методи за създаване на масиви от дру­ги­те примитивни типове.) Като получите масива, ще видите че има единствено из­викване на метода sort( ) или binarySearch( ). Има важно предупреждение за­ся­гащо binarySearch( ): Ако не викате sort( ) преди изпълнение на binarySearch( ), може да се получи непредсказуемо поведение, включително без­крайни цикли.

Сортиране и търсене при Stringовете става по същия начин, но когато пуснете програмата ще забележите нещо интересно: сортирането е лексикографично, та­ка че големите букви предхождат малките в знаковия набор. Така всички глав­ни букви са в началото на списъка, следвани от малките, така че ‘Z’ пред­хож­да ‘a’. Види се даже телефонните указатели са сортирани по този начин.


Comparable и Comparator


А ако това не е желаното? Например индексът в тази книга не би бил твърде по­ле­зен ако трябваше да гледате на две различни места за всичко което започва с ‘A’ и ‘a’ респективно.

Когато искате да сортирате масив от Object има проблем. Какво определя на­ред­бата на два Objectа? За нещастие първите проектанти на Java не смятаха то­ва за важен проблем, или той се е появил в кореновия клас Object. Като ре­зул­тат наредбата трябва да се внесе отвън за Objectи, а Java 2 библиотеката Ко­лек­ции дава стандартен начин да се прави това (което е почти толкова добро като да се прави в кореновия Object).

Има sort( ) за масиви от Object String, разбира се, е Object) който приема вто­ри аргумент: обект който прилага Comparator интерфейс (част от би­блио­те­ка­та Колекции на Java 2) и изпълнява сравнения с неговия единствен compare( ) ме­тод. Този метод взема като аргументи двата обекта които ще се сравняват и връ­ща отрицателно цяло ако първият е по-малък, нула ако са равни и по­ло­жи­тел­но цяло ако пъървият е по-голям. С това знание String частта от горния при­мер може да бъде пренаписана за да дава азбучно сортиране:

//: c08:newcollections:AlphaComp.java

// Using Comparator to perform an alphabetic sort

package c08.newcollections;

import java.util.*;
public class AlphaComp implements Comparator {

public int compare(Object o1, Object o2) {

// Assume it's used only for Strings...

String s1 = ((String)o1).toLowerCase();

String s2 = ((String)o2).toLowerCase();

return s1.compareTo(s2);

}

public static void main(String[] args) {



String[] s = Array1.randStrings(4, 10);

Array1.print(s);

AlphaComp ac = new AlphaComp();

Arrays.sort(s, ac);

Array1.print(s);

// Must use the Comparator to search, also:

int loc = Arrays.binarySearch(s, s[3], ac);

System.out.println("Location of " + s[3] +

" = " + loc);

}

} ///:~



Чрез превръщане към String методът compare( ) неявно проверява да е из­полз­ван само със String обекти – системата ще хване всякакви несъответствия по вре­мето на изпълнение. След превръщане на двата Stringа към малки букви, ме­тодът String.compareTo( ) дава желаните резултати.

Когато използвате ваш собствен Comparator за да изпъълните sort( ), трябва да из­ползвате същия Comparator когато използвате binarySearch( ).

Класът Arrays има друг sort( ) който взема единствен аргумент: масив от Object, но без Comparator. Този sort( ) също трябва да има начин да сравни два Objectа. Той използва естествения начин на сравнение който е вграден в класа чрез Comparable interface. Този interface има единствен метод, compareTo( ), кой­то сравнява обекта с аргумента си и връща отрицателно цяло, нула или по­ло­жително цяло ако обектът е по-малък, равен или по-голям от аргумента. Прост пример демонстрира това:

//: c08:newcollections:CompClass.java

// A class that implements Comparable

package c08.newcollections;

import java.util.*;
public class CompClass implements Comparable {

private int i;

public CompClass(int ii) { i = ii; }

public int compareTo(Object o) {

// Implicitly tests for correct type:

int argi = ((CompClass)o).i;

if(i == argi) return 0;

if(i < argi) return -1;

return 1;

}

public static void print(Object[] a) {



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

System.out.print(a[i] + " ");

System.out.println();

}

public String toString() { return i + ""; }



public static void main(String[] args) {

CompClass[] a = new CompClass[20];

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

a[i] = new CompClass(

(int)(Math.random() *100));

print(a);

Arrays.sort(a);

print(a);

int loc = Arrays.binarySearch(a, a[3]);

System.out.println("Location of " + a[3] +

" = " + loc);

}

} ///:~



Разбира се, вашият compareTo( ) може да бъде толкова сложен, колкото е необходимо.

Listове


List може да бъде сортиран и претърсван по същия начин както масив. static ме­тодите за сортировка и претърсване на List се съдържат в класа Collections, но имат подобни сигнатури на тези в Arrays: sort(List) за сортиране на List от обек­ти който прилага Comparable, binarySearch(List, Object) за намиране на обект в списъка, sort(List, Comparator) за сортиране на List чрез Comparator, binarySearch(List, Object, Comparator) за намиране на обект в него лист.4 Този при­мер използва дефинирания преди CompClass и AlphaComp за де­мон­стри­ра­не на инструменти за сортиране в Collections:

//: c08:newcollections:ListSort.java

// Sorting and searching Lists with 'Collections'

package c08.newcollections;

import java.util.*;
public class ListSort {

public static void main(String[] args) {

final int SZ = 20;

// Using "natural comparison method":

List a = new ArrayList();

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

a.add(new CompClass(

(int)(Math.random() *100)));

Collection1.print(a);

Collections.sort(a);

Collection1.print(a);

Object find = a.get(SZ/2);

int loc = Collections.binarySearch(a, find);

System.out.println("Location of " + find +

" = " + loc);

// Using a Comparator:

List b = new ArrayList();

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

b.add(Array1.randString(4));

Collection1.print(b);

AlphaComp ac = new AlphaComp();

Collections.sort(b, ac);

Collection1.print(b);

find = b.get(SZ/2);

// Must use the Comparator to search, also:

loc = Collections.binarySearch(b, find, ac);

System.out.println("Location of " + find +

" = " + loc);

}

} ///:~


Използването на тези методи е идентично на това от Arrays, използва се List вместо масив.

TreeMap трябва също да подреди обектите си според Comparable или Comparator.

Ютилита


Има определен брой полезни ютилита в класа Collections:

enumeration(Collection)

Дава Enumeration по стария стил за аргумента.

max(Collection)

min(Collection)

Дава максималния или минималния елемент на аргумента използвайки натуралното за Collection сравняване.

max(Collection, Comparator)

min(Collection, Comparator)

Дава максималния или минималния елемент за Collection чрез Comparator.

nCopies(int n, Object o)

Връща неизменяем лист List с дъл­жина n чиито манипулатори сочат o.

subList(List, int min, int max)

Връща нов List подкрепян от посо­чения за аргумент List който е прозорец в който този аргумент ще индексира започвайки от min и завършвайки точно преди max.

Забележете че min( ) и max( ) работят с обекти Collection, не с Listове, така че ня­ма нужда да се тревожите дали Collection е сортиран или не. (Както се спо­ме­на по-рано, трябва да сортирате sort( ) List или масив преди изпълнение на binarySearch( ).)

Правене на Collection или Map неизменяеми


Често е удобно да се създаде версия само за четене на Collection или Map. Класът Collections позволява да се направи това чрез подаване на оригиналната ко­лекция на метод, който връща версия само за четене. Има четири вариации на този метод, по езва за Collection (ако не искате да третирате Collection като по-специфичен тип), List, Set и Map. Този пример показва правилният начин за по­строяване на версия само за четене от всеки:

//: c08:newcollections:ReadOnly.java

// Using the Collections.unmodifiable methods

package c08.newcollections;

import java.util.*;
public class ReadOnly {

public static void main(String[] args) {

Collection c = new ArrayList();

Collection1.fill(c); // Insert useful data

c = Collections.unmodifiableCollection(c);

Collection1.print(c); // Reading is OK

//! c.add("one"); // Can't change it

List a = new ArrayList();

Collection1.fill(a);

a = Collections.unmodifiableList(a);

ListIterator lit = a.listIterator();

System.out.println(lit.next()); // Reading OK

//! lit.add("one"); // Can't change it
Set s = new HashSet();

Collection1.fill(s);

s = Collections.unmodifiableSet(s);

Collection1.print(s); // Reading OK

//! s.add("one"); // Can't change it

Map m = new HashMap();

Map1.fill(m, Map1.testData1);

m = Collections.unmodifiableMap(m);

Map1.print(m); // Reading OK

//! m.put("Ralph", "Howdy!");

}

} ///:~


Във всеки отделен случай трябва да попълните контейнера със значещи данни преди да го направите само за четене. Като веднъж е натоварен, най-добрият по­дход е да заместите манипулатора с този, получен от “непроменимото” из­вик­ване.По този начин се избягва рискът да се промени нежелано съ­дър­жа­ние­то след като е направено непроменимо. От друга страна, този инструмент също поз­волява да задържите променимия контейнер като private в клас и да връ­ща­те манипулатор "само за четене" чрез извикване на метод. Така че може да пра­ви­те промени от извътре на класа но всеки друг може само да чете.

Извикване на “непроменимия” метод за конкретен тип не предизвиква про­вер­ка по време на компилация, но веднъж като е станала трансформацията, ка­кво­то и извикване да е на метод който променя съдържанието на конкретен кон­тей­нер предизвиква UnsupportedOperationException.


Синхронизиране на Collection или Map


Ключовата дума synchronized е мажен момент от предмета на multithreading (многонишковост-б.пр.), по-сложна тема която няма да бъде въведена до глава 14. Тук ще отбележа само че класът Collections съдържа начин автоматично да се синхронизира целия контейнер. Синтаксисът е подобен на “непроменимите” ме­тоди:

//: c08:newcollections:Synchronization.java

// Using the Collections.synchronized methods

package c08.newcollections;

import java.util.*;
public class Synchronization {

public static void main(String[] args) {

Collection c =

Collections.synchronizedCollection(

new ArrayList());

List list = Collections.synchronizedList(

new ArrayList());

Set s = Collections.synchronizedSet(

new HashSet());

Map m = Collections.synchronizedMap(

new HashMap());

}

} ///:~



В този случай непосредствено се подава нов контейнер чрез подходящ “синхронизиран” метод; така няма начин непреднамерено да се покаже на света не­синхронизирана версия.

Колекциите на Java 2 имат също механизъм да предотвратят повече от един про­цес едновременно да променя съдържанието на контейнер. Проблемът се поя­вава когато правите итерации в даден контейнер и друг процес вмъква, маха или променя обект в същия контейнер. Може би вече се преминали този обект, мо­же той да не е достигнат още, може дължината на контейнера да се увеличи след извикването от вас на size( ) – има много сценарии за ката строфа. Ко­лек­ци­ите на Java 2 библиотеките включват fail fast механизъм който внимава за про­мени, направени не от вашия процес който отговаря за тях. Ако се установи про­мяна направена от нещо друго еднага се изхвърля ConcurrentModificationException. Това е “fail-fast” аспектът – той не се опитва да установи какъв е проблемът после чрез някакъв усложнен алгоритъм.





Сподели с приятели:
1   ...   46   47   48   49   50   51   52   53   ...   73




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

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