Следващата таблица показва какво може да се направи с 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 така че вашата програма ще дава едни и същи резултати независимо какво използвате. Обаче ArrayList (и Vector) се подкрепят от масив, докато 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 библиотеката колекции: тя е лесна за учене и използване. За да работи подходът, обаче:
-
UnsupportedOperationException трябва да е рядък случай. Тоез за повечето случаи класовете трябва да работят, а само в специални случаи да има неподдържана операция. Това е вярно за библиотеката колекции на Java 2 тъй като класовете които използвате 99% от времето – ArrayList, LinkedList, HashSet и HashMap, както и другите конкретни реализации – поддържат всичките операции. Дизайнът дава “задна вратичка” ако искате да създадете нова Collection без да се предефинира всичко за Collection interface, и все пак да пасва на съществуващата библиотека.
-
Когато операцията е неподдържана, има достатъчна вероятност че 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” аспектът – той не се опитва да установи какъв е проблемът после чрез някакъв усложнен алгоритъм.
Сподели с приятели: |