1、集合接口
1.1 将集合的接口与实现分离
队列:队列接口指出可以在队列的尾部添加元素,在队列的头部删除元素,并且可以查找队列中元素的个数。当需要收集对象,并按照“先进先出” 的规则检索对象时就应该使用队列。
队列通常有两种实现方式:循环数组、队列
每一个实现都可以通过一个实现了Queue接口的类表示。
如果需要一个循环数组队列,就可以使用ArrayDeque类,如果需要一个链表队列,就直接使用LinkedList类,这个类实现了Queue接口。
循环数组要比链表更高效。循环数组是一个有界集合,即容量有限。
如果想实现自己的队列类,可以实现一种例如AbstractQueue的抽象队列类。
1.2 Java类库中的集合接口和迭代器接口
在Java类库中,集合类的接口是Collection接口,这个接口有两个基本方法:
public interface Collection{
boolean add(E element);
Iterator<E> iterator();
...
}
iterator方法用于返回一个实现了Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素。
1.迭代器
Iterator接口包含三个方法:
public interface Iterator<E>{
E next();
boolean hasNext();
void remove();
}
通过反复调用next方法,可以逐个访问集合中的每个元素。因此需要在调用next方法之前调用hasNext方法,判断集合中是否还有下一个元素。eg:
Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while(iter.hasNext()){
String element = iter.next();
...
}
Collection接口扩展了Iterator接口。因此,对于标准类库中的任何接口都可以使用“for each”循环。
元素被访问的顺序取决于集合类型。如果对ArrayList进行迭代,迭代器将从索引0开始,每迭代一次,索引值加1。然而,如果访问HashSet中的元素,每个元素将会按照某种随机的次序出现。虽然可以确定在迭代过程中能够遍历到集合中的多有元素,但却无法预知元素被访问的次序。
2.删除元素
Iterator接口的remove方法将会删除上次调用next方法时返回的元素。
2、具体的集合
除了以Map结尾的类之外,其他类都实现了Collection接口。而以Map结尾的类实现了Map接口。
集合类型 | 描述 |
---|---|
ArrayList | 一种可以动态增长和缩减的索引序列 |
LinkedList | 一种可以在任何位置进行高效地插入和删除操作的有序序列 |
ArrayDeque | 一种用循环数组实现的双端队列 |
HashSet | 一种没有重复元素的无序集合 |
TreeSet | 一种有序集 |
EnumSet | 一种包含枚举类型值的集 |
LinkedHashSet | 一种可以记住元素插入次序的集 |
PriorityQueue | 一种允许搞笑删除最小元素的集合 |
HashMap | 一种存储键、值关联的数据结构 |
TreeMap | 一种键值有序排列的映射表 |
EnumMap | 一种键值属于枚举类型的映射表 |
LinkedHashMap | 一种可以记住键值项添加次序的映射表 |
WeakHashMap | 一种其值无用武之地后可以被垃圾回收期回收的映射表 |
IdentityHashMap | 一种用==而不用equals比较键值的映射表 |
2.1 链表
数组和数组列表都有一个重大的缺陷,就是从数组中间位置删除一个元素要付出很大的代价,其原因是数组中处于被删除元素之后的所有元素都要向数组的前端移动。在数组中间位置插入一个元素也是如此。
针对这种情况,我们就可以使用----链表。链表是将每个对象存放在独立的结点中。每个结点存放着序列中下一个结点的引用。在JAVA语言中,所有链表实际上都是双向链接的——即每个结点还存放着指向前驱结点的引用。
链表与泛型集合之间有一个重要的区别:链表是一个有序集合。LinkedList.add方法将对象添加到链表的尾部。但是,当我们需要将元素添加到链表中间时,就要使用迭代器的add方法。只有对自然有序的集合使用迭代器添加元素才有实际意义。集合类库还提供了了子接口ListIterator,其中也包含了add方法,与Collection.add不同,这个方法不返回boolean类型的值。另外,ListInterator还提供了了反向遍历链表的方法。
interface ListInterator<E> extends Iterator<E>{
void add(E element);
E previous();
boolean hasPrevious();
}
链表迭代器的设计使它能够检测到集合的修改,如果迭代器发现它的集合被另一个迭代器修改了,或是被该集合自身的方法修改了,就会抛出一个Concurrent ModificatioException异常。
List<String> list = ...;
ListIterator<String> iter1 = list.listIterator();
ListIterator<String> iter2 = list.listIterator();
iter1.next();
iter1.remove();
iter2.next(); //抛出Concurrent ModificatioException异常
链表不支持快速的随机访问。如果要查看链表中第n个元素,就必须从头开始,越过n-1个元素。
示例代码:
package com.java01.day09;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* @author: ju
* @date: 2020-05-18 11:06
*/
public class LinkedListTest {
public static void main(String[] args){
List<String> a = new LinkedList<>();
a.add("Atim");
a.add("Apop");
a.add("Atest");
List<String> b = new LinkedList<>();
b.add("Bbob");
b.add("Biop");
b.add("Bwoe");
b.add("Byunny");
ListIterator<String> aIter = a.listIterator();
Iterator<String> bIter = b.iterator();
while (bIter.hasNext()){
if (aIter.hasNext()){
aIter.next();
}
aIter.add(bIter.next());
}
System.out.println(a);
while (bIter.hasNext()){
bIter.next();
if (bIter.hasNext()){
bIter.next();
bIter.remove();
}
}
System.out.println(b);
a.removeAll(b);
System.out.println(a);
}
}
运行结果:
2.2 数组列表
List接口用于描述一个有序集合,并且集合中每个元素的位置十分重要。有两种访问元素的协议:一种是用迭代器,另一种是用get和set方法随机的访问每个元素。后者不适用于链表,但对数组确实很有用的。集合类库提供了一种ArrayList类,这个类也实现了List接口,ArrayList封装了一个动态再分配的对象数组。
在需要动态数组时,也可以使用Vector类,而Vector类的所有方法都是同步的,而ArrayList方法不是同步的。
2.3 散列集
链表和数组可以按照人们的意愿排列元素的次序。而散列表,可以快速的查找所需要的对象,但是无法控制元素出现的次序,散列表为每个对象计算一个整数,称为散列码(hash code)。
在Java中,散列表用链表数组实现。每个列表被称为桶。要想查找表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的的结果就是保存这个元素的桶的索引。例如,如果某个对象的散列码是76268,并且有128个桶,对象应该保存在第108号桶中。有时候会遇到桶被占满的情况,这也是不可避免的。这种现象被称为散列冲突。这时,需要用新对象与桶中的所有对象进行比较,查看这个对象是否已经存在。
如果想更多地控制散列表的运行性能,就要指定一个初始的桶数。桶数是指用于收集具有相同散列值的桶的数目。如果要插入到散列表中的元素太多,就会增加冲突的可能性,降低运行性能。
如果散列表太满,就需要再散列。如果要对散列表再散列,就需要创建一个桶数更多的表,并将所有元素插入到这个新表中,然后丢弃原来的表。
散列表可以用来实现几个重要的数据结构。其中有set类型。set是没有重复元素的元素集合。set的add方法首先在集中查找要添加的对象,如果不存在,就将这个对象添加进去。
java集合类库提供了一个HashSet类,它实现了基于散列表的集。
示例代码
package com.java01.day09;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
/**
* @author: ju
* @date: 2020-05-18 13:27
*/
public class SetTest {
public static void main(String[] args){
Set<String> words = new HashSet<>();
long totalTime = 0;
Scanner in = new Scanner(System.in);
while (in.hasNext()){
String word = in.next();
long callTime = System.currentTimeMillis();
words.add(word);
totalTime += callTime;
}
Iterator<String> iter = words.iterator();
for (int i = 0; i<=20 && iter.hasNext(); i++){
System.out.println(iter.next());
}
System.out.println("-----");
System.out.println(words.size() + "distinct words." + totalTime + "milliseconds.");
}
}
运行结果:
2.4 树集
TreeSet与散列集十分类似,不过,它比散列集有所改进。树集是一个有序集合。可以以任意顺序将元素插入到集合中。在对集合进行遍历时,每个值将自动地按照排序后的顺序呈现。(排序是通过红黑树实现的)
将一个元素添加到树中要比添加到散列表中慢,但是与将元素添加到数组或链表的正确文职上相比还是快很多的。如果树中包含n个元素,查找新元素的正确位置平均需要log2 n次比较。不过,TreeSet可以自动地对元素进行排序。
2.5 对象的比较
TreeSet如何知道希望元素怎样排序呢?在默认情况下,树集假定插入的元素实现了Comparable接口。这个接口定义了一个方法:
public interface Comparable<T>{
int compareTo(T other);
}
如果想要插入自定义的对象,就必须通过实现Comparable接口自定义排序顺序。
然而,使用Comparable接口定义排序顺序显然尤其局限性。对于一个给定的类,只能够实现这个接口一次。如果两次先要排序的条件不一样怎么办呢,如果自定义排序的对象所属的类并没有实现Comparable接口怎么办呢,针对这种情况,我们可以通过将Comparator对象传递给TreeSet构造器来告诉树集使用不同的比较方法。
SortedSet<Item> sortByDescription = new TreeSet<>(new Comparator<Item>(){
public int compare(Item a, Item b){
String descrA = a.getDescription();
String descrB = b.getDescription();
return descrA,compareTo(descrB);
}
});
示例代码:
package com.java01.day09;
import java.util.Objects;
/**
* @author: ju
* @date: 2020-05-18 14:38
*/
public class Item implements Comparable<Item> {
private String description;
private int partNumber;
public Item() {
}
public Item(String description, int partNumber) {
this.description = description;
this.partNumber = partNumber;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getPartNumber() {
return partNumber;
}
public void setPartNumber(int partNumber) {
this.partNumber = partNumber;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Item)) {
return false;
}
Item item = (Item) o;
return getPartNumber() == item.getPartNumber() &&
Objects.equals(getDescription(), item.getDescription());
}
@Override
public int hashCode() {
return Objects.hash(getDescription(), getPartNumber());
}
@Override
public String toString() {
return "Item{" +
"description='" + description + '\'' +
", partNumber=" + partNumber +
'}';
}
@Override
public int compareTo(Item other) {
return Integer.compare(partNumber, other.partNumber);
}
}
package com.java01.day09;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* @author: ju
* @date: 2020-05-18 14:37
*/
public class TreeSetTest {
public static void main(String[] args){
SortedSet<Item> parts = new TreeSet<>();
parts.add(new Item("张三", 9087));
parts.add(new Item("李四", 4567));
parts.add(new Item("王五", 1234));
System.out.println(parts);
SortedSet<Item> sortByDescription = new TreeSet<>(new Comparator<Item>() {
@Override
public int compare(Item o1, Item o2) {
String o1Description = o1.getDescription();
String o2Description = o2.getDescription();
return o1Description.compareTo(o2Description);
}
});
sortByDescription.addAll(parts);
System.out.println(sortByDescription);
}
}
运行结果
2.6 队列与双端队列
队列可以让人们有效地在尾部添加一个元素,在头部删除一个元素。有两个端头的队列,即双端队列,可以让人么有效地在头部和尾部同时添加或删除元素。不支持在队列中间添加元素。在Java SE6 中引入了Deque接口,并由ArrayDeque和LinkedList类实现。这两个类都提供了双端队列,并且再必要时可以增加队列的长度。
2.7 优先级队列
优先级队列中的元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索。也就是说,无论何时调用remove方法,总会获得当前优先级队列中最小的元素。然而,优先级队列并没有对所有的元素进行排序。优先级队列使用了 堆 的数据结构。堆是一个可以自我调整的二叉树,对树执行添加、删除操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。
和TreeSet一样,一个优先级队列既可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供比较器的对象。
使用优先级队列的典型示例是任务调度。每一个任务有一个优先级,任务以随机顺序添加到队列中。每当启动一个新的任务时,都将优先级最高的任务从队列中删除(由于习惯上将“1”设置为“最高”优先级,所以会将最小的元素删除)。
示例代码:
package com.java01.day09;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.PriorityQueue;
/**
* @author: ju
* @date: 2020-05-18 14:59
*/
public class PriorityQueueTest {
public static void main(String[] args){
PriorityQueue<GregorianCalendar> pq = new PriorityQueue<>();
pq.add(new GregorianCalendar(1906, Calendar.DECEMBER, 23));
pq.add(new GregorianCalendar(1978, Calendar.MARCH, 12));
pq.add(new GregorianCalendar(1836, Calendar.APRIL, 24));
pq.add(new GregorianCalendar(2008, Calendar.JANUARY, 16));
for (GregorianCalendar data : pq){
System.out.println(data.get(Calendar.YEAR));
}
System.out.println("remove....");
while (!pq.isEmpty()){
System.out.println(pq.remove().get(Calendar.YEAR));
}
}
}
运行结果:
1836
1978
1906
2008
remove....
1836
1906
1978
2008
2.8 映射表
映射表用来存放键值对。如果提供了键,就能查找到值。
Java类库为映射表提供了两个通用的实现:HashMap和TreeMap。这两个类都实现了Map接口。
散列映射表对键进行散列,树映射表用键的整体排序对元素进行排序,并将其组织成搜索树。散列或比较函数只能作用于键。与键关联的值不能进行散列或比较。
与集一样,散列稍微快一些,如果不需要按照排序顺序访问键,就最好选择散列。
键必须是唯一的。不能对同一个键存放两个值。如果对同一个键两次调用put方法,第二个值就会取代第一个值。实际上,put将返回用这个键参数存储的上一个值。
映射表的视图:键集、值集合、键值对集
下列方法将返回这三个视图,静态内部类Map.Entry:
Set< K> keySet()
Collection< K> values()
Set< Map.Entry< K, V>> entrySet()
注意,keySet既不是HashSet,也不是TreeSet,而是实现了Set接口的某个其他类的对象。Set接口扩展了Collection接口。因此,可以与使用任何集合一样使用KeySet。
示例代码:
package com.java01.day09;
import com.java01.day02.Employee;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author: ju
* @date: 2020-05-18 16:05
*/
public class MapTest {
public static void main(String[] args){
Map<String, Employee> map = new HashMap<>();
map.put("001", new Employee("张三", 13, new Date()));
map.put("002", new Employee("李四", 14, new Date()));
map.put("003", new Employee("王五", 15, new Date()));
map.put("004", new Employee("赵柳", 16, new Date()));
System.out.println(map);
map.remove("002");
map.put("004", new Employee("qiqi", 17, new Date()));
System.out.println(map.get("004"));
for (Map.Entry<String, Employee> entry : map.entrySet()){
String key = entry.getKey();
Employee value = entry.getValue();
System.out.println(key + " --- " + value);
}
}
}
执行结果:
{001=Employee{name='张三', age=13, birthDay=Mon May 18 16:39:44 GMT+08:00 2020}, 002=Employee{name='李四', age=14, birthDay=Mon May 18 16:39:44 GMT+08:00 2020}, 003=Employee{name='王五', age=15, birthDay=Mon May 18 16:39:44 GMT+08:00 2020}, 004=Employee{name='赵柳', age=16, birthDay=Mon May 18 16:39:44 GMT+08:00 2020}}
Employee{name='qiqi', age=17, birthDay=Mon May 18 16:39:44 GMT+08:00 2020}
001 --- Employee{name='张三', age=13, birthDay=Mon May 18 16:39:44 GMT+08:00 2020}
003 --- Employee{name='王五', age=15, birthDay=Mon May 18 16:39:44 GMT+08:00 2020}
004 --- Employee{name='qiqi', age=17, birthDay=Mon May 18 16:39:44 GMT+08:00 2020}
2.9 专用集与映射表类
1、弱散列映射表
设计WeakHashMap 类是为了解决一个有趣的问题。假定对某个键的最后一次引用已经消亡,不再有任何途径引用这个值的对象了。但是,由于在程序中的任何部分没有再出现这个键,所以这个键值对无法从映射表中删除。
为什么垃圾回收机制不回收它呢?垃圾回收器跟踪活动的对象。只要映射表对象是活动的,其中的所有桶也是活动的,它们不能被回收。所以我们就要使用WeakHashMap完成这件事情。
WeakHashMap 使用弱引用保存键。WeakReference对象将引用保存到另外一个对象中,在这里,就是散列表键。对于这种类型的对象,垃圾回收器用一种特定的方式进行处理。通常,如果垃圾回收器发现某个特定的对象已经没有他人引用了,就将其回收。然而,如果某个对象只能由WeakReference引用,垃圾回收器让然回收它,但要将引用这个对象的弱引用放入队列中。WeakHashMap 将周期性地检查队列,以便找出新添加的弱引用。一个弱引用进入队列意味着这个键不再被他人使用,并且已经被收集起来。于是,WeakHashMap 将删除对应的条目。
2、链接散列集和链接映射表
LinkedHashSet和LinkedHashMap,用来记住插入元素项的顺序。这样就可以避免在散列表中的项从表面上看是随机排列的。
链接散列映射表将用访问顺序,而不是插入顺序,对映射表条目进行迭代。
访问顺序对于实现高速缓存的“最近最少使用”原则十分重要。
3、枚举集与映射表
EnumSet是一个枚举类型元素集的高效实现。由于枚举类型只有有限个实例,所以EnumSet内部用位序列实现。
EnumSet类没有公共的构造器。可以使用静态工厂方法构造这个集:
eg:
enum Weekday{MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
EnumSet<Weekday> always = EnumSet.allOf(Weekday.class);
EnumSet<Weekday> never= EnumSet.noneOf(Weekday.class);
EnumSet<Weekday> workday= EnumSet.range(Weekday.MONDAY, Weekday.FRIDAY);
EnumSet<Weekday> mwf= EnumSet.of(Weekday.MONDAY, Weekday.WEDNESDAY, Weekday.FRIDAY);
可以使用Set接口的常用方法来修改EnumSet。
EnumMap是一个键类型为枚举类型的映射表。它可以直接且高效地用一个值数组实现。在使用时,需要在构造器中指定键类型:
Enum<Weekday, Employee> personInCharge = new EnumMap<>(Weekday.class);
4、标识散列映射表
IdentityHashMap,这个类中,键的散列值不是用hashCode函数计算的,而是用System.identityHashCode方法计算的。这是Object.hashCode方法根据对象的内存地址来计算单列吗时所使用的的方法。而且,在对两个对象进行比较时,IdentityHashMap类使用==,而不使用equals。
在实现对象遍历算法时,这个类非常有用,可以用来跟踪每个对象的遍历状况。
3、集合框架
集合框架的接口:
List是一个有序集合。元素可以添加到容器中某个特定的位置。
Set接口与Collection接口是一样的,只是其方法的行为有着更加严谨的定义。集的add方法拒绝添加重复的元素。
SortedSet和SortedMap接口暴露了用于排序的比较器对象,并且定义的方法可以获得集合的子集试图。
NavigableSet和NavigableMap,其中包含了几个用于在有序集合映射表中查找和遍历的方法。TreeSet和TreeMap类实现了这几个接口。
集合框架中的类:
3.1 视图与包装器
通过使用视图可以获得其他的实现了集合接口和映射表接口的对象。映射表的keySet方法就是一个这样的示例。keySet方法返回一个实现了Set接口的类对象,这个类的方法对原映射表进行操作。这种集合成为视图。
1.轻量级集包装器
Arrays类的静态方法asList将返回一个包装了普通Java数组的List包装器。这个方法可以将数组传递给一个期望得到列表或集合变元的方法。例如:
Card[] cardDeck = new Card[52];
...
List<Card> cardList = Arrays.asList(cardDeck);
返回的对象不是ArrayList。它是一个视图对象,带有访问底层数组的get和set方法。改变数组大小的所有方法都会抛出一个UnsupportedOperationException异常。
从Java SE 5.0开始,asList方法声明为一个具有可变数量参数的方法。除了可以传递一个数组之外,还可以将各个元素直接传递给这个方法。例如:List<String> names = Arrays.asList("Amy", "Bob", "Carl");
这个方法调用Collections.nCopies(n, anObject)
将返回一个实现了List接口的不可修改的对象,并给人一种包含n个元素,每个元素都像是一个anObject的错觉。
例如:下面的调用将创建一个包含100个字符床的List,每个串都被设置为“DEFAULT”:
List<String> setting = Collections.nCopies(100, "DEFANLT");
如果调用Collections.singleton(anObject)
则将返回一个视图对象。这个对象实现了Set接口。返回的对象实现了一个不可修改的单元素集,而不需要付出建立数据结构的开销。singletonList方法与singletonMap方法类似。
2.子范围
可以使用subList方法来获得一个列表的子范围视图。
List group2 = arr.subList(10, 20);
第一个索引包含在内,第二个索引则不包含在内。
3.不可修改的视图
Collections还有几个方法,用于产生集合的不可修改视图。这些视图对现有集合增加了一个运行时的检查。如果发现视图对集合进行修改,就抛出一个异常,同时这个集合将保持未修改的状态。可以使用下面6种方法获得不可修改的视图:(每个方法都定义于一个接口)
Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableSortedSet
Collections.unmodifiableMap
Collections.unmodifiableSortedMap
不可修改视图并不是集合本身不可修改。仍然可以通过集合的原始引用对集合进行修改。并且仍然可以让集合的元素调用更改器方法。
由于视图只是包装了接口而不是实际的集合对象,所以只能访问接口中定义的方法。
4.同步视图
如果由多个线程访问集合,就必须确保集不会被意外地破坏。
类库的设计者使用视图机制来确保常规集合的线程安全,而不是实现线程安全的集合类。例如,Collections类的静态synchronizedMap方法可以将任何一个映射表转换为具有同步访问方法的Map。
5.检查视图
检查视图,用来对泛型类型发生问题时提供调试支持。
eg:List<String> safeStrings =
Collections.checkedList(strings, String.class);
视图的add方法将检测插入的对象是否属于给定的类。如果不属于给定的类,就立即抛出一个ClassCastException。
3.2 批操作
可以使用类库中的批操作避免频繁的使用迭代器。
3.3 集合与数组之间的转换
如果有一个数组需要转换为集合。Arrays.asList的包装器就可以实现这个目的。
将集合装换为数组可以使用toArray方法。
由toArray方法返回的数组是一个Object[]数组,无法改变其类型。相反,必须使用另外一种toArray方法,并将其设计为所希望的元素类型且长度为0的数组。随后返回的数组将与所创建的数组一样:
String[] values = staff.toArray(new String[0]);
如果愿意的话,可以构造一个指定大小的数组:
staff.toArray(new String[staff.size()]);
在这种情况下,没有创建任何新数组。
4、算法
4.1 排序与混排
Collections类中的sort方法可以对实现了List接口的集合进行排序。
实际上,可以使用归并排序对列表进行高效的排序。然而,Java程序并不是这样实现的。它直接将所有元素转入一个数组,并使用一种归并排序的变体对数组进行排序,然后,再将排序后的序列复制回列表。
集合类库中使用的归并排序算法比快速排序要慢一些,快速排序是通过排序算法的传统选择。但是,归并排序有一个主要的优点:稳定,即不需要交换相同的元素。
有关的术语定义:
- 如果列表支持set方法,则使可修改的。
- 如果列表支持add和remove方法,则是可改变大小的。
Collections类有一个算法shuffle,其功能与排序刚好相反,即随机地混排列表中元素的顺序。例如:
ArrayList<Card> cards = ... ;
Collections.shuffle(cards);
如果提供的列表没有实现RandomAccess接口,shuffle方法将元素复制到数组中,然后打乱数组元素的顺序,最后再将打乱顺序后的元素复制回列表。
示例代码:
package com.java01.day09;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author: ju
* @date: 2020-05-19 09:26
*/
public class ShuffleTest {
public static void main(String[] args){
List<Integer> numbers = new ArrayList<>();
for (int i=0; i<=49; i++){
numbers.add(i);
}
Collections.shuffle(numbers);
System.out.println(numbers);
List<Integer> winningCombination = numbers.subList(0, 6);
Collections.sort(winningCombination);
System.out.println(winningCombination);
}
}
运行结果:
[26, 32, 36, 10, 8, 20, 4, 17, 39, 11, 40, 33, 23, 47, 13, 1, 37, 34, 27, 29, 21, 30, 12, 28, 6, 15, 9, 2, 43, 46, 48, 3, 0, 5, 19, 49, 31, 25, 42, 16, 44, 45, 22, 24, 35, 41, 38, 14, 7, 18]
[8, 10, 20, 26, 32, 36]
4.2 二分查找
Collections类的binarySearch方法实现了这个算法。注意,集合必须是排好序的,否则泛将返回错误的答案。要想查找某个元素,必须提供集合
4.3 编写自己的算法
如果要编写自己的算法(实际上,是以集合作为参数的任何方法),应该尽可能地使用接口,而不要使用具体的实现。
eg:
void fillMenue(JMenu menu, Collection<JMenuItem> item){
for(JMenuItem item : items){
menu.add(item);
}
}
5、遗留的集合
5.1 Hashtable类
Hashtable类与HashMap类的作用一样,实际上,它们拥有相同的接口。Hashtable的方法是同步的。
5.2 枚举
遗留集合使用Enumeration接口对元素序列进行遍历。Enumeration接口有两个方法,即hasMoreElements和nextElements。Hashtable类的elements方法将产生一个用于描述表中各个枚举值的对象。
Enumeration<Employee> e = staff.elements();
while(e.hasMoreElements()){
Employee e = e.nextElement();
...
}
5.3 属性映射表
属性映射表是一个类型非常特殊的映射表结构。它有下面3个特性:
- 键与值都是字符串
- 表可以保存到一个文件中,也可以从文件中加载。
- 使用一个默认的辅助表
实现属性映射表的Java平台类成为Properties。