完整的容器分类法
Java SE5新添加了:
1.Queue接口(LinkedList已经为实现该接口做了修改)及其实现PriorityQueue和各种风格的BlockingQueue。
2.ConcurrentMap接口及其实现ConcurrentHashMap,用于多线程机制
3.CopyOnWriteArrayList和CopyOnWriteArraySet,用于多线程机制
4.EnumSet和EnumMap,为使用enum而设计的set和map的特殊实现
5.在Collections类中的多个便利方法
虚线框表示abstract类,它们只是部分实现了特定接口的工具。例如,如果要创建自己的Set,那并不用从Set接口开始并实现其中全部方法,只需从AbstractSet继承,然后执行一些创建新类必须的工作。
填充容器
Collections类也有一些实用的static方法,其中包括fill,同Arrays一样只复制同一对象引用来填充整个容器的,并且只对List对象有用,但是所产生的列表可以传递给构造器或addAll方法:
class StringAddress {private String s;public StringAddress(String s) {this.s = s;}@Overridepublic String toString() {return super.toString() + " " + s;}}public class FillingLists {public static void main(String[] args) {List<StringAddress> list= new ArrayList<StringAddress>(Collections.nCopies(4, new StringAddress("Hello")));System.out.println(list);Collections.fill(list, new StringAddress("World!"));System.out.println(list);}} /*[StringAddress@82ba41 Hello, StringAddress@82ba41 Hello, StringAddress@82ba41 Hello, StringAddress@82ba41 Hello][StringAddress@923e30 World!, StringAddress@923e30 World!, StringAddress@923e30 World!, StringAddress@923e30 World!]*/
第一种是使用Collections.nCopies创建传递给构造器的List。所有引用都被设置为指向相同的对象,Collection.fill被调用之后也是如此,fill方法的用处更有限,因为他只能替换已经在List中存在的元素,而不能添加新的元素。
一种Generator解决方案
所有的Collection子类型都有一个接收另一个Collection对象的构造器,用所接收的Collection对象中的元素来填充新的容器。addAll是所有Collection子类型的一部分,可以用来组装现有的Collection。
构建接收Generator和quantity数值并将它们做为构造器参数的类:
public class CollectionData<T> extends ArrayList<T> {public CollectionData(Generator<T> gen, int quantity) {for(int i = 0; i < quantity; ++i) {this.add(gen.next());}}public static <T> CollectionData<T> list(Generator<T> gen, int quantity) {return new CollectionData(gen, quantity);}}
CollectionData是适配器设计模式的一个实例,它将Generator适配到Collection的构造器上。
class Government implements Generator<String> {String[] foundation = ("strange women lying in ponds " +"distributing swords is no basis for a system of " +"government").split(" ");private int index;@Overridepublic String next() {return foundation[index++];}}public class CollectionDataTest {public static void main(String[] args) {Set<String> set = new LinkedHashSet<String>(new CollectionData<String>(new Government(), 15));System.out.println(set);// 使用便利方法set.addAll(CollectionData.list(new Government(), 15));System.out.println(set);}} /*[strange, women, lying, in, ponds, distributing, swords, is, no, basis, for, a, system, of, government][strange, women, lying, in, ponds, distributing, swords, is, no, basis, for, a, system, of, government]*/
LinkedHashSet维护的是保持了插入顺序的链接列表。
数组章节中的操作符:
public class CollectionDataGeneration {public static void main(String[] args) {System.out.println(new ArrayList<String>(CollectionData.list( // 便利方法new RandomGenerator.String(9), 10)));System.out.println(new HashSet<Integer>(new CollectionData<Integer>(new RandomGenerator.Integer(), 10)));}} /*[YNzbrnyGc, FOWZnTcQr, GseGZMmJM, RoEsuEcUO, neOEdLsmw, HLGEahKcx, rEqUCBbkI, naMesbtWH, kjUrUkZPg, wsqPzDyCy][573, 4779, 871, 4367, 6090, 7882, , 8037, 3455, 299]*/
RandomGenerator.String所产生的String长度是通过构造器参数控制的。
Map生成器
Pair类,产生一个对象对(一个键和一个值):
public class Pair<K, V> {// 只读的数据传输对象public final K key;public final V value;public Pair(K k, V v) {this.key = k;this.value = v;}}
Map适配器现在可以使用各种不同的Generator、Iterator和常量值的组合来填充Map初始化对象:
public class MapData<K, V> extends LinkedHashMap<K, V> {// 使用单一的Generator<Pair<K,V>>public MapData(Generator<Pair<K, V>> gen, int quantity) {for(int i = 0; i < quantity; ++i) {Pair p = (Pair)gen.next();this.put(p.key, p.value);}}// 使用两个分离的Generatorpublic MapData(Generator<K> genK, Generator<V> genV, int quantity) {for(int i = 0; i < quantity; ++i) {this.put(genK.next(), genV.next());}}// 使用一个Generator和一个常量值public MapData(Generator<K> genK, V value, int quantity) {for(int i = 0; i < quantity; ++i) {this.put(genK.next(), value);}}// 使用一个Iterable和一个Generatorpublic MapData(Iterable<K> genK, Generator<V> genV) {Iterator var4 = genK.iterator();while(var4.hasNext()) {Object key = (Object)var4.next();this.put(key, genV.next());}}// 使用一个Iterable和一个单一值public MapData(Iterable<K> genK, V value) {Iterator var4 = genK.iterator();while(var4.hasNext()) {Object key = (Object)var4.next();this.put(key, value);}}public static <K, V> MapData<K, V> map(Generator<Pair<K, V>> gen, int quantity) {return new MapData(gen, quantity);}public static <K, V> MapData<K, V> map(Generator<K> genK, Generator<V> genV, int quantity) {return new MapData(genK, genV, quantity);}public static <K, V> MapData<K, V> map(Generator<K> genK, V value, int quantity) {return new MapData(genK, value, quantity);}public static <K, V> MapData<K, V> map(Iterable<K> genK, Generator<V> genV) {return new MapData(genK, genV);}public static <K, V> MapData<K, V> map(Iterable<K> genK, V value) {return new MapData(genK, value);}}
LettersGenerator通过产生一个Iterator还实现了Iterable:
class Letters implements Generator<Pair<Integer,String>>,Iterable<Integer> {private int size = 9;private int number = 1;private char letter = 'A';@Overridepublic Pair<Integer,String> next() {return new Pair<Integer,String>(number++, "" + letter++);}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {@Overridepublic Integer next() { return number++; }@Overridepublic boolean hasNext() { return number < size; }@Overridepublic void remove() {throw new UnsupportedOperationException();}};}}public class MapDataTest {public static void main(String[] args) {// Pair生成器print(MapData.map(new Letters(), 11));// 两个分开的生成器print(MapData.map(new CountingGenerator.Character(),new RandomGenerator.String(3), 8));// 使用一个Generator和一个常量值print(MapData.map(new CountingGenerator.Character(),"Value", 6));// 使用一个Iterable和一个Generatorprint(MapData.map(new Letters(),new RandomGenerator.String(3)));// 使用一个Iterable和一个单一值print(MapData.map(new Letters(), "Pop"));}} /*{1=A, 2=B, 3=C, 4=D, 5=E, 6=F, 7=G, 8=H, 9=I, 10=J, 11=K}{a=YNz, b=brn, c=yGc, d=FOW, e=ZnT, f=cQr, g=Gse, h=GZM}{a=Value, b=Value, c=Value, d=Value, e=Value, f=Value}{1=mJM, 2=RoE, 3=suE, 4=cUO, 5=neO, 6=EdL, 7=smw, 8=HLG}{1=Pop, 2=Pop, 3=Pop, 4=Pop, 5=Pop, 6=Pop, 7=Pop, 8=Pop}*/
可以使用工具来创建任何用于Map或Collection的生成数据集,然后通过构造器或Map.putAll和Collection.addAll方法来初始化Map和Collection。
使用Abstract类
每个java.util容器都有其自己的Abstract类,它们提供了该容器的部分实现,因此必须做的只是去实现那些产生想要的容器所必需的方法。
享元模式:可在普通解决方案需要过多对象,或者产生普通对象太占用空间时使用享元。享元模式使得对象的一部分可以被具体化,因此,与对象中的所有事物都包含在对象内部不同,可以在更加高效的外部表中查找对象的一部分或整体。
下面演示通过继承java.util.Abstract来创建定制的Map和Collection。为了创建只读的Map,可以继承Abstractmap并实现entrySet。为了创建只读的Set,可以继承AbstractSet并实现iterator和size。
public class Countries {public static final String[][] DATA = new String[][]{{"ALGERIA", "Algiers"}, {"ANGOLA", "Luanda"}, {"BENIN", "Porto-Novo"}, {"BOTSWANA", "Gaberone"}, {"BURKINA FASO", "Ouagadougou"}, {"BURUNDI", "Bujumbura"}, {"CAMEROON", "Yaounde"}, {"CAPE VERDE", "Praia"}, {"CENTRAL AFRICAN REPUBLIC", "Bangui"}, {"CHAD", "N\'djamena"}, {"COMOROS", "Moroni"}, {"CONGO", "Brazzaville"}, {"DJIBOUTI", "Dijibouti"}, {"EGYPT", "Cairo"}, {"EQUATORIAL GUINEA", "Malabo"}, {"ERITREA", "Asmara"}, {"ETHIOPIA", "Addis Ababa"}, {"GABON", "Libreville"}, {"THE GAMBIA", "Banjul"}, {"GHANA", "Accra"}, {"GUINEA", "Conakry"}, {"BISSAU", "Bissau"}, {"COTE D\'IVOIR (IVORY COAST)", "Yamoussoukro"}, {"KENYA", "Nairobi"}, {"LESOTHO", "Maseru"}, {"LIBERIA", "Monrovia"}, {"LIBYA", "Tripoli"}, {"MADAGASCAR", "Antananarivo"}, {"MALAWI", "Lilongwe"}, {"MALI", "Bamako"}, {"MAURITANIA", "Nouakchott"}, {"MAURITIUS", "Port Louis"}, {"MOROCCO", "Rabat"}, {"MOZAMBIQUE", "Maputo"}, {"NAMIBIA", "Windhoek"}, {"NIGER", "Niamey"}, {"NIGERIA", "Abuja"}, {"RWANDA", "Kigali"}, {"SAO TOME E PRINCIPE", "Sao Tome"}, {"SENEGAL", "Dakar"}, {"SEYCHELLES", "Victoria"}, {"SIERRA LEONE", "Freetown"}, {"SOMALIA", "Mogadishu"}, {"SOUTH AFRICA", "Pretoria/Cape Town"}, {"SUDAN", "Khartoum"}, {"SWAZILAND", "Mbabane"}, {"TANZANIA", "Dodoma"}, {"TOGO", "Lome"}, {"TUNISIA", "Tunis"}, {"UGANDA", "Kampala"}, {"DEMOCRATIC REPUBLIC OF THE CONGO (ZAIRE)", "Kinshasa"}, {"ZAMBIA", "Lusaka"}, {"ZIMBABWE", "Harare"}, {"AFGHANISTAN", "Kabul"}, {"BAHRAIN", "Manama"}, {"BANGLADESH", "Dhaka"}, {"BHUTAN", "Thimphu"}, {"BRUNEI", "Bandar Seri Begawan"}, {"CAMBODIA", "Phnom Penh"}, {"CHINA", "Beijing"}, {"CYPRUS", "Nicosia"}, {"INDIA", "New Delhi"}, {"INDONESIA", "Jakarta"}, {"IRAN", "Tehran"}, {"IRAQ", "Baghdad"}, {"ISRAEL", "Jerusalem"}, {"JAPAN", "Tokyo"}, {"JORDAN", "Amman"}, {"KUWAIT", "Kuwait City"}, {"LAOS", "Vientiane"}, {"LEBANON", "Beirut"}, {"MALAYSIA", "Kuala Lumpur"}, {"THE MALDIVES", "Male"}, {"MONGOLIA", "Ulan Bator"}, {"MYANMAR (BURMA)", "Rangoon"}, {"NEPAL", "Katmandu"}, {"NORTH KOREA", "P\'yongyang"}, {"OMAN", "Muscat"}, {"PAKISTAN", "Islamabad"}, {"PHILIPPINES", "Manila"}, {"QATAR", "Doha"}, {"SAUDI ARABIA", "Riyadh"}, {"SINGAPORE", "Singapore"}, {"SOUTH KOREA", "Seoul"}, {"SRI LANKA", "Colombo"}, {"SYRIA", "Damascus"}, {"TAIWAN (REPUBLIC OF CHINA)", "Taipei"}, {"THAILAND", "Bangkok"}, {"TURKEY", "Ankara"}, {"UNITED ARAB EMIRATES", "Abu Dhabi"}, {"VIETNAM", "Hanoi"}, {"YEMEN", "Sana\'a"}, {"AUSTRALIA", "Canberra"}, {"FIJI", "Suva"}, {"KIRIBATI", "Bairiki"}, {"MARSHALL ISLANDS", "Dalap-Uliga-Darrit"}, {"MICRONESIA", "Palikir"}, {"NAURU", "Yaren"}, {"NEW ZEALAND", "Wellington"}, {"PALAU", "Koror"}, {"PAPUA NEW GUINEA", "Port Moresby"}, {"SOLOMON ISLANDS", "Honaira"}, {"TONGA", "Nuku\'alofa"}, {"TUVALU", "Fongafale"}, {"VANUATU", "< Port-Vila"}, {"WESTERN SAMOA", "Apia"}, {"ARMENIA", "Yerevan"}, {"AZERBAIJAN", "Baku"}, {"BELARUS (BYELORUSSIA)", "Minsk"}, {"BULGARIA", "Sofia"}, {"GEORGIA", "Tbilisi"}, {"KAZAKSTAN", "Almaty"}, {"KYRGYZSTAN", "Alma-Ata"}, {"MOLDOVA", "Chisinau"}, {"RUSSIA", "Moscow"}, {"TAJIKISTAN", "Dushanbe"}, {"TURKMENISTAN", "Ashkabad"}, {"UKRAINE", "Kyiv"}, {"UZBEKISTAN", "Tashkent"}, {"ALBANIA", "Tirana"}, {"ANDORRA", "Andorra la Vella"}, {"AUSTRIA", "Vienna"}, {"BELGIUM", "Brussels"}, {"BOSNIA", "-"}, {"HERZEGOVINA", "Sarajevo"}, {"CROATIA", "Zagreb"}, {"CZECH REPUBLIC", "Prague"}, {"DENMARK", "Copenhagen"}, {"ESTONIA", "Tallinn"}, {"FINLAND", "Helsinki"}, {"FRANCE", "Paris"}, {"GERMANY", "Berlin"}, {"GREECE", "Athens"}, {"HUNGARY", "Budapest"}, {"ICELAND", "Reykjavik"}, {"IRELAND", "Dublin"}, {"ITALY", "Rome"}, {"LATVIA", "Riga"}, {"LIECHTENSTEIN", "Vaduz"}, {"LITHUANIA", "Vilnius"}, {"LUXEMBOURG", "Luxembourg"}, {"MACEDONIA", "Skopje"}, {"MALTA", "Valletta"}, {"MONACO", "Monaco"}, {"MONTENEGRO", "Podgorica"}, {"THE NETHERLANDS", "Amsterdam"}, {"NORWAY", "Oslo"}, {"POLAND", "Warsaw"}, {"PORTUGAL", "Lisbon"}, {"ROMANIA", "Bucharest"}, {"SAN MARINO", "San Marino"}, {"SERBIA", "Belgrade"}, {"SLOVAKIA", "Bratislava"}, {"SLOVENIA", "Ljuijana"}, {"SPAIN", "Madrid"}, {"SWEDEN", "Stockholm"}, {"SWITZERLAND", "Berne"}, {"UNITED KINGDOM", "London"}, {"VATICAN CITY", "---"}, {"ANTIGUA AND BARBUDA", "Saint John\'s"}, {"BAHAMAS", "Nassau"}, {"BARBADOS", "Bridgetown"}, {"BELIZE", "Belmopan"}, {"CANADA", "Ottawa"}, {"COSTA RICA", "San Jose"}, {"CUBA", "Havana"}, {"DOMINICA", "Roseau"}, {"DOMINICAN REPUBLIC", "Santo Domingo"}, {"EL SALVADOR", "San Salvador"}, {"GRENADA", "Saint George\'s"}, {"GUATEMALA", "Guatemala City"}, {"HAITI", "Port-au-Prince"}, {"HONDURAS", "Tegucigalpa"}, {"JAMAICA", "Kingston"}, {"MEXICO", "Mexico City"}, {"NICARAGUA", "Managua"}, {"PANAMA", "Panama City"}, {"ST. KITTS", "-"}, {"NEVIS", "Basseterre"}, {"ST. LUCIA", "Castries"}, {"ST. VINCENT AND THE GRENADINES", "Kingstown"}, {"UNITED STATES OF AMERICA", "Washington, D.C."}, {"ARGENTINA", "Buenos Aires"}, {"BOLIVIA", "Sucre (legal)/La Paz(administrative)"}, {"BRAZIL", "Brasilia"}, {"CHILE", "Santiago"}, {"COLOMBIA", "Bogota"}, {"ECUADOR", "Quito"}, {"GUYANA", "Georgetown"}, {"PARAGUAY", "Asuncion"}, {"PERU", "Lima"}, {"SURINAME", "Paramaribo"}, {"TRINIDAD AND TOBAGO", "Port of Spain"}, {"URUGUAY", "Montevideo"}, {"VENEZUELA", "Caracas"}};static Map<String, String> map = new Countries.FlyweightMap((Countries.FlyweightMap)null);static List<String> names;static {names = new ArrayList(map.keySet());}public Countries() {}static Map<String, String> select(final int size) {return new Countries.FlyweightMap((Countries.FlyweightMap)null, (Countries.FlyweightMap)null) {public Set<java.util.Map.Entry<String, String>> entrySet() {return new Countries.FlyweightMap.EntrySet(size);}};}public static Map<String, String> capitals() {return map;}public static Map<String, String> capitals(int size) {return select(size);}public static List<String> names() {return names;}public static List<String> names(int size) {return new ArrayList(select(size).keySet());}public static void main(String[] args) {Print.print(capitals(10));Print.print(names(10));Print.print(new HashMap(capitals(3)));Print.print(new LinkedHashMap(capitals(3)));Print.print(new TreeMap(capitals(3)));Print.print(new Hashtable(capitals(3)));Print.print(new HashSet(names(6)));Print.print(new LinkedHashSet(names(6)));Print.print(new TreeSet(names(6)));Print.print(new ArrayList(names(6)));Print.print(new LinkedList(names(6)));Print.print(capitals().get("BRAZIL"));}private static class FlyweightMap extends AbstractMap<String, String> {private static Set<java.util.Map.Entry<String, String>> entries;static {entries = new Countries.FlyweightMap.EntrySet(Countries.DATA.length);}private FlyweightMap() {}public Set<java.util.Map.Entry<String, String>> entrySet() {return entries;}private static class Entry implements java.util.Map.Entry<String, String> {int index;Entry(int index) {this.index = index;}public boolean equals(Object o) {return Countries.DATA[this.index][0].equals(o);}public String getKey() {return Countries.DATA[this.index][0];}public String getValue() {return Countries.DATA[this.index][1];}public String setValue(String value) {throw new UnsupportedOperationException();}public int hashCode() {return Countries.DATA[this.index][0].hashCode();}}static class EntrySet extends AbstractSet<java.util.Map.Entry<String, String>> {private int size;EntrySet(int size) {if(size < 0) {this.size = 0;} else if(size > Countries.DATA.length) {this.size = Countries.DATA.length;} else {this.size = size;}}public int size() {return this.size;}public Iterator<java.util.Map.Entry<String, String>> iterator() {return new Countries.FlyweightMap.EntrySet.Iter((Countries.FlyweightMap.EntrySet.Iter)null);}private class Iter implements Iterator<java.util.Map.Entry<String, String>> {private Countries.FlyweightMap.Entry entry;private Iter() {this.entry = new Countries.FlyweightMap.Entry(-1);}public boolean hasNext() {return this.entry.index < EntrySet.this.size - 1;}public java.util.Map.Entry<String, String> next() {++this.entry.index;return this.entry;}public void remove() {throw new UnsupportedOperationException();}}}}}
FlyweightMap必须实现entrySet方法,它需要定制set实现和定制Map.Entry类。这里正是享元部分:每个Map.Entry对象都只存储了它的索引,而不是实际的键和值。当你调用getKey和getValue时,它们会使用该索引来返回恰当的DATA元素。EntrySet可以确保它的size不会大于DATA。
可以在EntrySet.Iterator中看到享元其它部分的实现。与为DATA中的每个数据对都创建Map,Entry对象不同,每个迭代器只有一个Map.Entry。Entry对象被用作数据的视窗,它只包含在静态字符数组中的索引。每次调用迭代器的nest方法时,Entry中的index都会递增,使其指向下一个元素,然后从next返回该Iterator所持有的单一Entry对象。
select方法将产生一个包含制定尺寸的EntrySet的FlyweightMap,它会被用于重载过的capitals和names方法。
Countries的尺寸受限会成为问题。可以采用与产生定制容器相同的方法来解决,其中定制容器是经过初始化的,并且具有任意尺寸的数据集。下面的类是一个list,它可以具有任意尺寸,并且用Integer数据进行了预初始化:
public class CountingIntegerList extends AbstractList<Integer> {private int size;public CountingIntegerList(int size) {this.size = size < 0?0:size;}public Integer get(int index) {return Integer.valueOf(index);}public int size() {return this.size;}public static void main(String[] args) {System.out.println(new CountingIntegerList(30));}}/*[0,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29]*/
为了从AbstractList创建只读List,必须实现get和size,这里再次使用了享元解决方案:当你寻找值时,get将产生它,因此这个List实际上并不比组装。
下面是包含经过预初始化,并且都是唯一的Integer和String对的Map,可以具有任意尺寸:
public class CountingMapData extends AbstractMap<Integer, String> {private int size;private static String[] chars = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split(" ");public CountingMapData(int size) {if(size < 0) {this.size = 0;}this.size = size;}public Set<java.util.Map.Entry<Integer, String>> entrySet() {LinkedHashSet entries = new LinkedHashSet();for(int i = 0; i < this.size; ++i) {entries.add(new CountingMapData.Entry(i));}return entries;}public static void main(String[] args) {System.out.println(new CountingMapData(60));}private static class Entry implements java.util.Map.Entry<Integer, String> {int index;Entry(int index) {this.index = index;}public boolean equals(Object o) {return Integer.valueOf(this.index).equals(o);}public Integer getKey() {return Integer.valueOf(this.index);}public String getValue() {return CountingMapData.chars[this.index % CountingMapData.chars.length] + Integer.toString(this.index / CountingMapData.chars.length);}public String setValue(String value) {throw new UnsupportedOperationException();}public int hashCode() {return Integer.valueOf(this.index).hashCode();}}}/*[0=A0, 1=B0 ... 25=Z0,26=A1...59=H2]*/
这里使用的是LinkedHashSet,而不是定制的Set类,因此享元并未完全实现。
Collection的功能方法
其中不包括随机访问所选择元素的get方法,因为Collection包括Set,而Set是自己维护内部顺序的。
public class CollectionMethods {public static void main(String[] args) {Collection<String> c = new ArrayList<String>();c.addAll(Countries.names(6));c.add("ten");c.add("eleven");print(c);// 从list中生成一个数组Object[] array = c.toArray();// 从list中生成一个字符串数组String[] str = c.toArray(new String[0]);// 查找最大最小元素print("Collections.max(c) = " + Collections.max(c));print("Collections.min(c) = " + Collections.min(c));// 将一个集合添加到另一个集合Collection<String> c2 = new ArrayList<String>();c2.addAll(Countries.names(6));c.addAll(c2);print(c);c.remove(Countries.DATA[0][0]);print(c);c.remove(Countries.DATA[1][0]);print(c);// 移除集合中所有元素c.removeAll(c2);print(c);c.addAll(c2);print(c);// 是这个集合中的元素吗?String val = Countries.DATA[3][0];print("c.contains(" + val + ") = " + c.contains(val));// 集合中是否包含这个集合?print("c.containsAll(c2) = " + c.containsAll(c2));Collection<String> c3 = ((List<String>)c).subList(3, 5);// 交集c2.retainAll(c3);print(c2);// 移除c2中所有c3的元素c2.removeAll(c3);print("c2.isEmpty() = " + c2.isEmpty());c = new ArrayList<String>();c.addAll(Countries.names(6));print(c);c.clear(); // 移除所有元素print("after c.clear():" + c);}} /*[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, ten, eleven]Collections.max(c) = tenCollections.min(c) = ALGERIA[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, ten, eleven, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO][ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, ten, eleven, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO][BENIN, BOTSWANA, BULGARIA, BURKINA FASO, ten, eleven, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO][ten, eleven][ten, eleven, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO]c.contains(BOTSWANA) = truec.containsAll(c2) = true[ANGOLA, BENIN]c2.isEmpty() = true[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO]after c.clear():[]*/
可选操作
执行各种不同的添加和移除的方法在Collection接口中都是可选操作。这意味着实现类并不需要为这些方法提供功能定义。
可选操作声明调用某些方法将不会执行有意义的行为,相反,它们会抛出异常。如果一个操作是可选的,编译器仍旧会严格要求你只能调用该接口中的方法。
未获支持的操作
最常见的未获支持的操作,源于背后由固定尺寸的数据结构支持的容器。当使用Arrays.asList将数组转换为List时,就会得到这样的容器。还可以通过使用Collections类中不可修改的方法,选择创建任何会抛出UnsupportedOperationException的容器:
public class Unsupported {static void test(String msg, List<String> list) {System.out.println("--- " + msg + " ---");Collection<String> c = list;Collection<String> subList = list.subList(1,8);// 复制列表Collection<String> c2 = new ArrayList<String>(subList);try { c.retainAll(c2); } catch(Exception e) {System.out.println("retainAll(): " + e);}try { c.removeAll(c2); } catch(Exception e) {System.out.println("removeAll(): " + e);}try { c.clear(); } catch(Exception e) {System.out.println("clear(): " + e);}try { c.add("X"); } catch(Exception e) {System.out.println("add(): " + e);}try { c.addAll(c2); } catch(Exception e) {System.out.println("addAll(): " + e);}try { c.remove("C"); } catch(Exception e) {System.out.println("remove(): " + e);}// set()方法只改变值try {list.set(0, "X");} catch(Exception e) {System.out.println("List.set(): " + e);}}public static void main(String[] args) {List<String> list = Arrays.asList("A B C D E F G H I J K L".split(" "));test("Modifiable Copy", new ArrayList<String>(list));test("Arrays.asList()", list);test("unmodifiableList()",Collections.unmodifiableList(new ArrayList<String>(list)));}} /*--- Modifiable Copy ------ Arrays.asList() ---retainAll(): java.lang.UnsupportedOperationExceptionremoveAll(): java.lang.UnsupportedOperationExceptionclear(): java.lang.UnsupportedOperationExceptionadd(): java.lang.UnsupportedOperationExceptionaddAll(): java.lang.UnsupportedOperationExceptionremove(): java.lang.UnsupportedOperationException--- unmodifiableList() ---retainAll(): java.lang.UnsupportedOperationExceptionremoveAll(): java.lang.UnsupportedOperationExceptionclear(): java.lang.UnsupportedOperationExceptionadd(): java.lang.UnsupportedOperationExceptionaddAll(): java.lang.UnsupportedOperationExceptionremove(): java.lang.UnsupportedOperationExceptionList.set(): java.lang.UnsupportedOperationException*/
因为Arrays.asList会生成一个List,它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作。任何会引起对底层数据结构的尺寸进行修改的方法都会产生一个UnsupportedOperationException异常,以表示对未获支持操作的调用。
Arrays.asList产生固定尺寸的List,而Collections.unmodifiableList产生不可修改的列表。set方法可以在二者产生的对象之间有粒度上的变化。Arrays.asList可以修改元素,没有违反尺寸固定的特性。
List的功能方法
basicTest包含每个list都可以执行的操作;iterMotion使用Iterator遍历元素;对应的iterManipulation使用Iterator修改元素;testVisual用以查看List的操作效果;
public class Lists {private static boolean b;private static String s;private static int i;private static Iterator<String> it;private static ListIterator<String> lit;public static void basicTest(List<String> a) {a.add(1, "x"); // 在位置1添加a.add("x"); // 在结尾添加// 添加一个集合a.addAll(Countries.names(25));// 从3开始添加一个集合a.addAll(3, Countries.names(25));b = a.contains("1"); // 是否存在// 整个集合都在里面吗b = a.containsAll(Countries.names(25));// 随机访问列表, ArrayList简单, LinkedList昂贵s = a.get(1); // Get (typed) object at location 1i = a.indexOf("1"); // Tell index of objectb = a.isEmpty(); // Any elements inside?it = a.iterator(); // Ordinary Iteratorlit = a.listIterator(); // ListIteratorlit = a.listIterator(3); // Start at loc 3i = a.lastIndexOf("1"); // Last matcha.remove(1); // Remove location 1a.remove("3"); // Remove this objecta.set(1, "y"); // Set location 1 to "y"a.retainAll(Countries.names(25));a.removeAll(Countries.names(25));i = a.size(); a.clear();}public static void iterMotion(List<String> a) {ListIterator<String> it = a.listIterator();b = it.hasNext();b = it.hasPrevious();s = it.next();i = it.nextIndex();s = it.previous();i = it.previousIndex();}public static void iterManipulation(List<String> a) {ListIterator<String> it = a.listIterator();it.add("47");// Must move to an element after add():it.next();// Remove the element after the newly produced one:it.remove();// Must move to an element after remove():it.next();// Change the element after the deleted one:it.set("47");}public static void testVisual(List<String> a) {print(a);List<String> b = Countries.names(25);print("b = " + b);a.addAll(b);a.addAll(b);print(a);// Insert, remove, and replace elements// using a ListIterator:ListIterator<String> x = a.listIterator(a.size()/2);x.add("one");print(a);print(x.next());x.remove();print(x.next());x.set("47");print(a);// Traverse the list backwards:x = a.listIterator(a.size());while(x.hasPrevious()) {printnb(x.previous() + " ");}print();print("testVisual finished");}// There are some things that only LinkedLists can do:public static void testLinkedList() {LinkedList<String> ll = new LinkedList<String>();ll.addAll(Countries.names(25));print(ll);// Treat it like a stack, pushing:ll.addFirst("one");ll.addFirst("two");print(ll);// Like "peeking" at the top of a stack:print(ll.getFirst());// Like popping a stack:print(ll.removeFirst());print(ll.removeFirst());// Treat it like a queue, pulling elements// off the tail end:print(ll.removeLast());print(ll);}public static void main(String[] args) {// Make and fill a new list each time:basicTest(new LinkedList<String>(Countries.names(25)));basicTest(new ArrayList<String>(Countries.names(25)));iterMotion(new LinkedList<String>(Countries.names(25)));iterMotion(new ArrayList<String>(Countries.names(25)));iterManipulation(new LinkedList<String>(Countries.names(25)));iterManipulation(new ArrayList<String>(Countries.names(25)));testVisual(new LinkedList<String>(Countries.names(25)));testLinkedList();}}
Set和存储顺序
存储顺序不同的set实现不仅具有不同的行为,而且它们对于可以在特定的set中放置元素的类型也有不同的要求:
HashSet如果没有其他限制,应该默认选择,因为它对速度进行了优化。
必须为散列存储和树形储存都创建equals方法,但是hashCode只有在类被置于HashSet和LinkedHashSet时才必须。但都应该覆盖equals方法,总是同时覆盖hashCode方法。
class SetType {int i;public SetType(int n) { i = n; }public boolean equals(Object o) {return o instanceof SetType && (i == ((SetType)o).i);}public String toString() { return Integer.toString(i); }}class HashType extends SetType {public HashType(int n) { super(n); }public int hashCode() { return i; }}class TreeType extends SetTypeimplements Comparable<TreeType> {public TreeType(int n) { super(n); }public int compareTo(TreeType arg) {return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));}}public class TypesForSets {static <T> Set<T> fill(Set<T> set, Class<T> type) {try {for(int i = 0; i < 10; i++)set.add(type.getConstructor(int.class).newInstance(i));} catch(Exception e) {throw new RuntimeException(e);}return set;}static <T> void test(Set<T> set, Class<T> type) {fill(set, type);fill(set, type); // 尝试添加fill(set, type);System.out.println(set);}public static void main(String[] args) {test(new HashSet<HashType>(), HashType.class);test(new LinkedHashSet<HashType>(), HashType.class);test(new TreeSet<TreeType>(), TreeType.class);// Things that don't work:test(new HashSet<SetType>(), SetType.class);test(new HashSet<TreeType>(), TreeType.class);test(new LinkedHashSet<SetType>(), SetType.class);test(new LinkedHashSet<TreeType>(), TreeType.class);try {test(new TreeSet<SetType>(), SetType.class);} catch(Exception e) {System.out.println(e.getMessage());}try {test(new TreeSet<HashType>(), HashType.class);} catch(Exception e) {System.out.println(e.getMessage());}}} /*[2, 4, 9, 8, 6, 1, 3, 7, 5, 0][0, 1, 2, 3, 4, 5, 6, 7, 8, 9][9, 8, 7, 6, 5, 4, 3, 2, 1, 0][9, 9, 7, 5, 1, 2, 6, 3, 0, 7, 2, 4, 4, 7, 9, 1, 3, 6, 2, 4, 3, 0, 5, 0, 8, 8, 8, 6, 5, 1][0, 5, 5, 6, 5, 0, 3, 1, 9, 8, 4, 2, 3, 9, 7, 3, 4, 4, 0, 7, 1, 9, 6, 2, 1, 8, 2, 8, 6, 7][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9][0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]java.lang.ClassCastException: SetType cannot be cast to parablejava.lang.ClassCastException: HashType cannot be cast to parable*/
HashType继承自SetType,并且添加了hashCode方法,该方法对于放置到Set散列实现中的对象来说是必须的。
TreeType实现了Comparable接口,如果一个对象被用于任何种类的排序容器中,例如SortedSet(TreeSet是其唯一实现),那必须实现这个接口。
如果equals对于某个特定比较产生true,那么compareTo对于该比较应返回0,如果equals对于某个比较产生false,那么compareTo对于该比较应该返回非0。
从输出上看hashSet以某种神秘顺序保存所有元素,LinkedHashSet按照元素插入顺序保存元素,而TreeSet按照排序顺序维护元素。
SortedSet
SortedSet中的元素可以保证处于排序状态,SortedSet接口中的下列方法提供附加的功能:Comparator comparator返回当前Set使用的Comparator;或者返回null,表示以自然方式排序。
Object first返回容器中的第一个元素。
Object last返回容器中的最末一个元素。
SortedSet subSet(fromElement, toElement)生成此Set的子集,范围从fromElement(包含)到toElement(不包含)。
SortedSet headSet(toElement)生成此Set的子集,由小于toElement的元素组成
SortedSet tailSet(fromElement)生成此Set的子集,由大于或等于fromElement的元素组成
public class SortedSetDemo {public static void main(String[] args) {SortedSet<String> sortedSet = new TreeSet<String>();Collections.addAll(sortedSet, "one two three four five six seven eight".split(" "));print(sortedSet);String low = sortedSet.first();String high = sortedSet.last();print(low);print(high);Iterator<String> it = sortedSet.iterator();for(int i = 0; i <= 6; i++) {if(i == 3) { low = it.next(); }if(i == 6) { high = it.next(); }else { it.next(); }}print(low);print(high);print(sortedSet.subSet(low, high));print(sortedSet.headSet(high));print(sortedSet.tailSet(low));}} /*[eight, five, four, one, seven, six, three, two]eighttwoonetwo[one, seven, six, three][eight, five, four, one, seven, six, three][one, seven, six, three, two]*/
SortedSet的意思是按对象的比较函数对元素排序,而不是元素插入次序。插入顺序可以使用LinkedHashSet来保存。
队列
除了并发应用,Queue在Java SE5中仅有的两个实现是LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能。
public class QueueBehavior {private static int count = 10;static <T> void test(Queue<T> queue, Generator<T> gen) {for(int i = 0; i < count; i++) {queue.offer(gen.next());}while(queue.peek() != null) {System.out.print(queue.remove() + " ");}System.out.println();}static class Gen implements Generator<String> {String[] s = ("one two three four five six seven eight nine ten").split(" ");int i;@Overridepublic String next() {return s[i++];}}public static void main(String[] args) {test(new LinkedList<String>(), new Gen());test(new PriorityQueue<String>(), new Gen());test(new ArrayBlockingQueue<String>(count), new Gen());test(new ConcurrentLinkedQueue<String>(), new Gen());test(new LinkedBlockingQueue<String>(), new Gen());test(new PriorityBlockingQueue<String>(), new Gen());}} /*one two three four five six seven eight nine teneight five four nine one seven six ten three twoone two three four five six seven eight nine tenone two three four five six seven eight nine tenone two three four five six seven eight nine teneight five four nine one seven six ten three two*/
优先级队列
列表中的每个对象都包含一个字符串和一个主要的以及次要的优先级值,该列表的排序顺序也是通过实现Comparable而进行控制的:
class ToDoList extends PriorityQueue<ToDoList.ToDoItem> {static class ToDoItem implements Comparable<ToDoItem> {private char primary;private int secondary;private String item;public ToDoItem(String td, char pri, int sec) {primary = pri;secondary = sec;item = td;}@Overridepublic int compareTo(ToDoItem arg) {if(primary > arg.primary) {return +1;}if(primary == arg.primary) {if (secondary > arg.secondary) {return +1;} else if (secondary == arg.secondary) {return 0;}}return -1;}@Overridepublic String toString() {return Character.toString(primary) +secondary + ": " + item;}}public void add(String td, char pri, int sec) {super.add(new ToDoItem(td, pri, sec));}public static void main(String[] args) {ToDoList toDoList = new ToDoList();toDoList.add("Empty trash", 'C', 4);toDoList.add("Feed dog", 'A', 2);toDoList.add("Feed bird", 'B', 7);toDoList.add("Mow lawn", 'C', 3);toDoList.add("Water lawn", 'A', 1);toDoList.add("Feed cat", 'B', 1);while(!toDoList.isEmpty()) {System.out.println(toDoList.remove());}}} /*A1: Water lawnA2: Feed dogB1: Feed catB7: Feed birdC3: Mow lawnC4: Empty trash*/
双向队列
双向队列就像一个队列,但是可以在任意一段添加或移除元素。在LinkedList中包含支持双向队列的方法,但在Java标准类库中没有任何显式的用于双向队列的接口。因此,LinkedList无法去实现这样的接口,无法像前面转型到Queue那样向上转型为Deque。但是可以使用组合创建一个Deque类,并直接从LinkedList中暴露相关方法:
public class Deque<T> {private LinkedList<T> deque = new LinkedList();public Deque() {}public void addFirst(T e) {this.deque.addFirst(e);}public void addLast(T e) {this.deque.addLast(e);}public T getFirst() {return this.deque.getFirst();}public T getLast() {return this.deque.getLast();}public T removeFirst() {return this.deque.removeFirst();}public T removeLast() {return this.deque.removeLast();}public int size() {return this.deque.size();}public String toString() {return this.deque.toString();}}public class DequeTest {static void fillTest(Deque<Integer> deque) {for(int i = 20; i < 27; i++) {deque.addFirst(i);}for(int i = 50; i < 55; i++) {deque.addLast(i);}}public static void main(String[] args) {Deque<Integer> di = new Deque<Integer>();fillTest(di);print(di);while(di.size() != 0) {printnb(di.removeFirst() + " ");}print();fillTest(di);while(di.size() != 0) {printnb(di.removeLast() + " ");}}} /*[26, 25, 24, 23, 22, 21, 20, 50, 51, 52, 53, 54]26 25 24 23 22 21 20 50 51 52 53 5454 53 52 51 50 20 21 22 23 24 25 26*/
不太可能在两端都放入元素并抽取它们,因此,Deque不如Queue那样常用。
如果觉得《Java编程思想笔记——容器深入研究1》对你有帮助,请点赞、收藏,并留下你的观点哦!