Java 有多种方式保存对象(应该说是对象的引用)。
数组是保存一组对象的最有效的方式。
ArrayList 当做“可以自动扩充自身尺寸的数组”。
@SuppressWarnings(“unchecked”) 表示只有有关“不受检查的异常”的警告信息应该被抑制。
原生 ArrayList 也就是没加泛型的,保存的是 Object,因此可以添加所有继承自 Object 的对象。
使用预定义泛型很简单,像是这样:
class Apple {}
public class AppleAnd {
public static void main(String[] args){
List<Apple> list = new ArrayList<>();
}
}
尖括号里是类型参数,通过使用泛型,就可以在编译期防止将错误类型的对象放置到容器中。而且在取出 list 中的元素时,也不用类型转换。
容器中的元素除了支持 get(index) 的形式来获取某个元素,也可以用 foreach 语法来选择 List 中的每个元素。
当指定了某个类型作为泛型参数时,除了可以放确切的该类型对象,向上转型也可以像作用于其他类型一样作用于泛型。
Java 容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
一般来说,编写的代码都是与接口打交道,方便修改实现。
class A {
List<Apple> apples = new ArrayList<>();
//List apples = new LinkedList<>();
}
当然,在 LinkedList 、TreeMap 中具有的而接口中不存在的方法,就不能通过转型来使用了。
Collection 接口概括了序列的概念——一种存放一组对象的方式。
add() 方法表明它是要将一个新元素放置到 Collection 中。
所有的 Collection 都可以使用 foreach 语法便利。
在 java.util 包中的 Arrays 和 Collections 类中都有很多实用方法。
Arrays.asList() 接受一个数组或是用逗号分割的元素列表(使用可变参数),将其转换为一个 List 对象。
Collections.addAll() 接受一个 Collection 对象,以及一个数组或用逗号分割的列表,将元素添加到 Collection 中。
传统的 addAll() 接受一个 Collection。所有 Collection 类型都包含该方法
Collection 的构造器可以接受另一个 Collection,但是 Collection.addAll() 方法运行起来要快的多,而且使用后者更方便,所以是优选方案。
Collection.addAll() 成员方法只能接受另一个 Collection 对象作为参数,没有上文讲到的两种方法更灵活。
注:可以直接使用 Arrays.asList() 的输出,将其当做 List ,但是这种情况下,其底层表示的是数组,不能调整尺寸,试图使用 add() 或 delete() 会得到“Unsupported Operation”错误。
Arrays.asList() 方法的限制是它对所产生的 List 的类型做出了最理想的假设,而并没有注意你对它会赋予什么样的类型
class Snow {
}
class Powder extends Snow{
}
class Light extends Powder{
}
class Heavy extends Powder{
}
class Crusty extends Snow{
}
class Slush extends Snow{
}
public class AsListTnference {
public static void main(String[] args) {
List<Snow> snow1 = Arrays.asList(
new Crusty(), new Slush(),new Powder());
//Won't compile:
//List snow2 = Arrays.asList(
// new Light(), new Heavy());
List<Snow> snow3 = new ArrayList<>();
Collections.addAll(snow3, new Light(), new Heavy());
List<Snow> snow4 = Arrays.<Snow>asList(
new Light(), new Heavy());
}
}
当试图创建 snow2 时,Arrays.asList() 中只有 powder 类型,因此它会创建 List
而不是List
Collections.addAll() 工作良好,因为它从第一个参数中了解到了目标类型是什么。
也可以像 snow4 那样,以告诉编译器对于 Arrays.asList() 你要产生的 List 类型,这称为 显示类型参数说明。
对于数组来说,只能使用 Arrays.toString(),多维用 Arrays.deepToString()。
而容器无需任何帮助。
Collection 在每个槽中保存一个元素。此类容器包括
ArrayList 和 LinkedList 都按照插入的顺序保存元素。
HashSet 、 TreeSet 和 LinkedHashSet 都是 Set 类型,每个相同的项只保存一次。
Map 在每个槽内保存了两个对象。可以用键来查找对象,所以,对于每一个键,Map 只接受存储一次。
Map.put(K,V) Map.get(K) 用来添加和获取。
有两种类型的 List
当确定一个元素是否属于某个 List ,发现某个元素的索引,以及从某个 List 中移除一个元素时,都会用到 equals() 方法。因此,必须要意识到 List 的行为根据 equals() 的行为而有所变化。
subList() 方法允许从大的列表创建出一个片段。containsAll() 不会因为顺序不同而产生不同的行为。
retainAll() 方法是一种有效的交集行为。所产生的行为依赖于 equals() 。
removeAll() 移除所有元素。
set() ,在指定的索引出,用第二个参数替换整个位置的元素。
对于 List ,有一个重载的 addAll() 方法可以在初始 List 的中间插入新的列表,而不仅仅是前文提到的两种方法追加到表尾。
toArray() 方法,将任意的 Collection 转换为一个数组。无参版本返回 Object 数组。如果传递目标类型的数据,在它能通过类型检查的情况下,它将产生指定数据类型的数据。如果参数数组太小,toArray() 方法将创建一个具有合适尺寸的数组。
迭代器是一个对象,它的的工作是遍历并选择序列中的对象。此外,迭代器通常被成为轻量级对象:创建它的代价小,因此,经常可以见到迭代器有些奇怪的限制;
例如,Java 的 Iterator 只能单向移动,这个 Iterator 只能用来:
如果只向前遍历 List ,使用 foreach 语法更加简介。
另外 Iterator 还可以移除由 next() 产生的最后一个元素,这意味着在调用 remove() 之前必须先调用 next()。
public class CrossContainerIteration {
public static void display(Iterator<Pet> it) {
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " " );
}
}
}
display() 方法不包含任何有关它所遍历的序列的类型信息,它将能够遍历序列的操作与序列底层的结构分离。
迭代器统一了对容器的访问方式。
ListIterator 是一个更加强大的 Iterator 的子类型,它只能用于各种 List 类的访问。Iterator 只能单向移动,而 ListIterator 可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用 set() 方法替换它访问过的最后一个元素。可以通过 ListIterator(n) 方法创建一个一开始就指向列表索引为 n 的元素处的 ListIterator 。
实现了 List 接口,一些优缺点之前已经提到过。
除此之外,LinkedList 还添加了可以使其用作栈、队列和双端队列的方法。这些方法有些彼此只是名称有些差异:
名称 | 功能 | 异常 |
---|---|---|
getFirst() | 返回列表第一个元素 | List 为空,抛 NoSuchElementException |
element() | 返回列表第一个元素 | List 为空,抛 NoSuchElementException |
peek() | 返回列表第一个元素 | List 为空,返回 null |
removeFirst() | 移除并返回列表的头 | List 为空,抛 NoSuchElementException |
remove() | 移除并返回列表的头 | List 为空,抛 NoSuchElementException |
pool() | 移除并返回列表的头 | List 为空,返回 null |
addFirst() | 将某个元素插入到列表的头部 | |
add() | 将某个元素插入到列表的尾部 | |
addLast() | 将某个元素插入到列表的尾部 | |
removeLast() | 移除并返回列表的最后一个元素 |
后进先出的容器。有时也被称为 叠加栈,最后压入栈的元素,第一个弹出栈。
LinkedList 具有能够直接实现栈的所有功能的方法,因此可以直接将 LinkedList 作为栈使用。
不过,有时一个真正的栈更能把事情讲清楚。
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push(T v) { storage.addFirst(v);}
public T peek() { return storage.getFirst(); }
public T pop() { return storage.removeFirst();}
public boolean empty() { return storage.isEmpty();}
public String toString() { return storage.toString(); }
Set 不保存重复元素。Set 中最常被使用的是测试归属性,可以很容易地访问某个对象是否在某个 Set 中,正因如此,查找成了 Set 中最重要的操作,所以通常都会选择一个 HashSet 实现,它专门对快速查找进行了优化。
Set “ is-a " Collection 也就是说 Set 具有 Collection 完全一样的接口,没有额外个功能。
一般用 containsAll() 来测试归属性。
将对象映射到其他对象的能力是一种解决编程问题的杀手锏。
如下一个程序统计 Random 产生数字的分布情况。
public class Statistics {
public static void main(String[] args) {
Random rand = new Random(47);
Map<Integer,Integer> m =
new HashMap<Integer, Integer>();
for(int i = 0; i < 10000; i++) {
int r = rand.nextInt(20);
Integer freq = m.get(r);
m.put(r, freq == null? 1 : freq + 1);
}
System.out.println(m);
}
}
Map 与数组和其他的 Collection 一样,可以很容易地扩展到多为,我们只需将其值设置为 Map。
例如 Map
Map 可以返回它的键的 Set ,它的值的 Collection,或者它的键值对的 Set。
KeySet() 方法产生所有键组成的 Set,它在 foreach 语句中被用来遍历该 Map。
典型的先进先出容器。队列在并发编程中特别重要。
LinkedList 提供了方法以支持队列的行为,并且它实现了 Queue 接口,因此 LinkedList 可以用作 Queue 的一种实现。
offer() 方法是 Queue 相关的方法之一,它在允许的情况下,将一个元素插入到队尾,或者返回 false。
peek() 和 element() 都在不移除的情况下返回对头,但是 peek() 在队列为空,返回 null,element() 会跑出异常。poll() 和 remove() 移除并返回对头,poll() 在队列为空返回 null ,remove() 抛出异常。
优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。
当调用 offer() 方法来插入一个对象时,这个对象会在队列中排序。默认的排序将使用对象在队列中的自然顺序,但是可以通过提供自己的 Comparator 来修改这个顺序。PriorityQueue 可以确保当你调用 peek() 、poll()和remove() 方法时,获取的元素将是队列中优先级最高的元素。
Collection 是描述所有序列容器的共性的根接口,他可能会被认为是一个附属接口,因为要表示其他若干个接口的共性而出现的接口。另外,java.util.AbstractCollection 类提供了 Collection 的默认实现,方便你可以创建 AbstractCollection 的子类型,而其中没有不必要的代码重复。
使用接口方便应用于更多的对象类型。如果编写的方法接受一个 Collection ,那么该方法就可以用于所有实现了 Collection 的类。
在Java中,Collection接口这种方式和迭代器这种方式绑定到了一起,意味着实现 Collection 就意味着需要提供 iterator() 方法
Collection 接口和 Iterator 都可以将display() 方法与底层容器的特定类型实现解耦。
事实上,Collection 要更方便一些,因为它是 Iterable 类型,所以可以使用 foreach 结构。
当要实现 Collection 的外部类时,由于它去实现 Collection 接口可能非常困难,因此使用 Iterator 就会变得简单,虽然可以通过继承 AbstractCollection 来实现,但是无论如何还是要实现 iterator() 和 size()。
所以在已经继承某个类之后,只能通过实现 Collection 的情况下,会非常吃力。此时,继承并提供创建迭代器的能力就会容易的多了。
public class CollectionSequence extends AbstractCollection<A> {
private A[] as = new A[] {
new A("dog"), new A("cat"), new A("bird"),new A("fish")
};
@Override
public Iterator<A> iterator() {
return new Iterator<A>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < as.length;
}
@Override
public A next() {
return as[index++];
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public int size() {
return as.length;
}
public static void main(String[] args) {
CollectionSequence c = new CollectionSequence();
InterfaceVsIterator.display(c);
InterfaceVsIterator.display(c.iterator());
}
}
下面是提供创建迭代器的方式:
Class PetSequence {
protected Pet[] pets = Pets.createArray(8);
}
public class NonCollectionSequence extends PetSequence {
public Iterator<Pet> iterator() {
return new Iterator<Pet>() {
private int index =0;
public boolean hasNext() {
return index < pet.length;
}
public Pet next() { return pets[index++];}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
foreach 语法主要用于数组,但是也可以应用于任何 Collection 对象。
之所以能够工作,是因为 Java SE5 引入了新的被称为 Iterable 的接口,该接口包含一个能够产生 Iterator 的 Iterator() 方法,并且 Iterable 接口被 foreach 用来在序列中移动。因此,如果创建了任何实现 Iterable 的类,都可以将它用于 foreach 语句中。
public class IterableClass implements Iterable<String> {
protected String[] words = ("And that is how" +
"we know the Earth to be banan-shaped.").split(" ");
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < words.length;
}
@Override
public String next() {
return words[index++];
}
};
}
public static void main(String[] args) {
for(String s : new IterableClass())
System.out.print(s + " ");
}
}
大量的类都是 Iterable 类型,主要包括所有的 Collection 类,但是不包括各种 Map。
尝试把数组当做一个 Iterable 参数传递会导致失败,说明不存在任何从数组到 Iterable 的自动转换,必须手动执行这种转换
public class ArrayIsNotIterable {
static <T> void test(Iterable<T> ib) {
for(T t : ib)
System.out.println(t + " ");
}
public static void main(String[] args) {
test(Arrays.asList(1, 2, 3));
String[] strings = {"A", "B", "C"};
//test(strings);
test(Arrays.asList(strings));
}
}
如果现有一个 Iterable 类,如果想要添加一种或多种在 foreach 语句中使用这个类的方法,直接继承并覆盖 iterator() 方法,显然不是一种好的方式。
一种解决方案是所谓 适配器方法的惯用法。提供特定的接口以满足 foreach 语句。当有一个接口并需要另一个接口时,编写适配器就可以解决问题。
package cn.iunote.learn02.chapter11.test29;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import cn.iunote.learn02.chapter11.test26.IterableClass;
public class MultiIterableClass extends IterableClass {
public Iterable<String> reversed() {
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
int current = words.length - 1;
@Override
public boolean hasNext() {
return current > -1;
}
@Override
public String next() {
return words[current--];
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
public Iterable<String> randomized(){
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
List<String> shuffled = new ArrayList<String>(Arrays.asList(words));
Collections.shuffle(shuffled, new Random(47));
return shuffled.iterator();
}
};
}
public static void main(String[] args) {
MultiIterableClass mic = new MultiIterableClass();
for(String s : mic.reversed())
System.out.print(s + " ");
System.out.println();
for(String s : mic.randomized())
System.out.print(s + " ");
}
}
如果直接将 ral 对象置于 foreach 语句中,将得到默认的前向迭代器。但是如果在该对象上调用 reversed() 方法,就会产生不同的行为。
如果用ArrayList 将 Arrays.asList() 方法的结果包装起来,就不会不该数组的顺序,如果直接用 Arrays.asList() 方法的结果,那么就会影响底层的数组。
Java 提供了大量持有对象的方式: