11 持有对象
通常,程序总是根据运行时才知道某些条件去创建新的对象。
不需要事先定义好创建的数量,可以根据需要创建任意数量的对象。
数组则必须实现指定数量,所以有所约束。
Java提供了容器类来解决这个问题,基本的类型是List、Set、Queue和Map。这些称之为集合类,且都是Collection类或Map类的子类。
Set不能保存重复值。 Map允许将对象与其他对象关联起来。
这些容器不需要事先指定大小。
11.1 泛型和类型安全的容器
在JavaSE5之前,可以像容器插入不正确的类型,例如创建一个Arraylist,既可以插入Apple类型,也可以插入Orange类型
通过add()方法插入对象,通过get方法访问这些数据。
(1)不使用泛型
因为没有使用泛型,所以会提示警告,可以使用@SuppressWarnings来抑制。
class Cat{ }
class Dog{ }
public class Test {
@SuppressWarnings("unchecked") //不提示警告
public static void main(String[] args) {
ArrayList dogs = new ArrayList();
dogs.add(new Cat());
dogs.add(new Dog());
}
}
当不指定类型时,默认是Object类。因为所有类都继承Object
(2)使用泛型
使用泛型,意味着你不可以插入其他类型的数据
class Cat{ }
class Dog{ }
public class Test {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
ArrayList<Dog> dogs = new ArrayList<Dog>();
// dogs.add(new Cat()); // 会报错,不可以插入
dogs.add(new Dog());
for (Dog dog : dogs) { //使用增强for循环进行遍历
System.out.println(dog);
}
}
}
(3)使用泛型,可以插入其子类
class Dog{ }
class Hasiqi extends Dog{} //哈士奇
class Keji extends Dog{} //柯基
public class Test {
public static void main(String[] args) {
ArrayList<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Hasiqi());
dogs.add(new Keji());
}
}
11.2 基本概念
Java容器的用途是"保存对象",并将其分为两个不同大类
(1)Collection。 包括List、Set、Queue等接口。
List表示数据是有顺序的,Set则不可以有重复元素,Queue则按照规则来确定顺序
(2)Map, 由一堆键值对组成,通过字典的形式将其关联起来。
List<Dog> dogs = new ArrayList<Dog>();
List<Dog> dogs = new LinkedList<Dog>();
// 这里的List是接口, 接口可以指向其子类。
import java.util.Collection;
Collection<String> c = new ArrayList<String>();
c.add("abc");
11.3 添加一组元素
Arrays.asList()方法接受一个数组或者用逗号分割的元素列表(可变参数),将其转换为List对象
Collections.addAll()方法接受一个Collection对象,或者可变参数,将其添加到Collection中
import java.util.*;
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5);
Integer[] ins = {5,6,7,8};
List<Integer> list2 = Arrays.asList(ins);
Collection<Integer> coll = new ArrayList<Integer>(
Arrays.asList(1,2,3,4,5) );
Collections.addAll(coll, 1,2,3,4,5);
Collections.addAll(coll,ins);
}
}
11.4 容器的打印
使用Arrays.toString()来进行打印
public class Test {
public static void main(String[] args) {
Collection coll = new ArrayList<Integer>();
coll.add("zhangsan");
coll.add("lisi");
System.out.println(coll);
Map map = new HashMap();
map.put("name","zhangsan");
map.put("age",18);
System.out.println(map);
}
}
// 输出结果
// [zhangsan, lisi]
// {name=zhangsan, age=18}
List : 以特定的顺序保存元素
Set:不能存储重复元素
Queue: 只允许一端插入,另一端移出
ArrayList和LinkedList都是List类型
HashSet、TreeSet和LinkedHashSet都是Set类型。 HashSet使用哈希表存储,查询时间快。
HashMap、TreeMap、LinkedHashMap都是Map类型。
11.5 List
List接口在Collection的基础上添加了大量的方法。
ArrayList,随机查询元素快,但是插入和删除速度慢
LinkedList,随机查询元素慢,但是插入和删除快。
import java.util.*;
class Dog{}
class Hasiqi extends Dog{}
public class Test {
public static void main(String[] args) {
List<Dog> dogs = Arrays.asList(new Dog(),new Dog());
Dog d = new Dog();
dogs.add(d);//添加一个dog数据
dogs.remove(d); //删除
Dog hashqi = new Hasiqi();
System.out.println(dogs.indexOf(hashqi)); //查看哈士奇的下标
dogs.get(1); //获取元素
dogs.add(1,new Dog()); //给指定位置添加
List<Dog> sub = dogs.subList(1, 2);//获得子串
dogs.containsAll(sub); //是否都包含
}
}
11.6 迭代器
为什么要使用迭代器?如果刚开始使用的是List编码,后来发现还需要应用于Set,那么就得重头开始编码。
使用迭代器(Iterable)则没有这个问题。
Java的Iterable只能单向移动。使用iterator()来获得迭代器对象,使用next()获得下一个元素,使用hashNext()检查是否还有下一个,使用remove()来删除元素。
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>(
Arrays.asList(1,2,3,4,5,6,7,8));
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
Integer temp = it.next();
System.out.println(temp);
}
}
}
11.6.1 ListIterator
ListIterator是一个更加强大的Iterator的子类。
Iterator只能向前移动,但是ListIterator可以双向移动。
可以通过listIterator(n)来指定起始位置
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>(
Arrays.asList(1,2,3,4,5,6,7,8));
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()) {
Integer t1 = it.next();
System.out.println(t1);
}
while(it.hasPrevious()) {
Integer t2 = it.previous();
System.out.println(t2);
}
}
}
11.7 LinkedList
LinkedList 在查询方面速度慢,但是插入和删除速度较快
LinkedList还添加了栈,队列,双端队列的方法
getFirst() 和 element()完全一样,返回第一个元素,若为空则抛出异常
peek() 返回第一个元素,为空则返回null
removeFirst() 和 remove() 完全一样,删除第一个元素,为空抛出异常
poll() 删除第一个元素,为空则返回null
addFirst() 与 add() 和 addList() ,将某个元素插入列表尾部
removeLast() 删除并返回最后一个元素
11.8 Stack
栈,先进后出。 FILO
push() 入栈
pop() 出栈
peek() 返回栈顶元素
empty() 是否为空
11.9 Set
保存不重复的元素
Set具有与Collection完全一样的接口,没有额外的功能。
public class Test {
public static void main(String[] args) {
Set<String> set1 = new HashSet<String>();
Collections.addAll(set1,"2","3");
set1.add("4");
System.out.println(set1);
}
}
11.10 Map
计算数字出现的次数
import java.util.*;
public class Test {
public static void main(String[] args) {
Random rand = new Random(5);
Map<Integer,Integer> map = new HashMap<Integer, Integer>();
for(int i=0;i<1000;i++) {
int t = rand.nextInt(66);//生成0-66的随机数
Integer f = map.get(t);
if (f == null) {
f = 0;
}else {
f += 1;
}
map.put(t,f);
}
System.out.println(map);
}
}
11.11 Queue
队列是一个先进先出(FIFO)的容器,从一段放入事物,从另一端取出。
LinkedList提供支持队列的行为,且实现了Queuue接口。
Queue<Integer> queue = new LinkedList<Integer>();
offer() 方法插入列表尾
poll() 和 remove() 方法 删除列表头
peek() 和 element() 方法 返回队头
11.11.1 PriorityQueue
优先级队列,优先弹出最需要的元素。
使用offer()方法插入一个对象时,会在队列中被排序。
可以通过自己的Comparator来修改这个顺序。
import java.util.*;
public class Test {
public static void main(String[] args) {
PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
for(int i =10;i>0;i--) {
pq.offer(i);
}
while(pq.peek() != null) {
Integer temp = pq.remove();
System.out.println(temp);
}
}
}
// 输出结果
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10
11.12 Collection和Iterator
Collection时描述所有序列容器共性的根接口。
java.util.AbstractCollection 提供了Collection的默认实现(抽象类中的非抽象方法),可以创建AbstractCollection的子类型。
import java.util.*;
class Seq extends AbstractCollection<Integer>{
private Integer[] int_list = {1,2,3,4,5};
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() { //匿名内部类, 实现 Interator<Integer>
private int index = 0;
@Override
public Integer next() { //下一个数据
return int_list[index++];
}
@Override
public boolean hasNext() { // 判断是否有下一个数据
return index < int_list.length;
}
};
}
@Override
public int size() {
return int_list.length;
}
}
public class Test {
public static void main(String[] args) {
Collection seq = new Seq();
Iterator<Integer> it = seq.iterator();
while(it.hasNext()) {
Integer temp = it.next();
System.out.println(temp);
}
}
}
11.13 Foreach与迭代器
(1)Foreach可以适用于任何Collection对象
import java.util.*;
public class Test {
public static void main(String[] args) {
Collection<String> cs = new ArrayList<String>();
Collections.addAll(cs, "long time no see".split(" "));//添加数据
for (String str : cs) {//增强for循环
System.out.println(str);
}
}
}
(2)继承Iterable之后,就可以使用Foreach增强for循环
import java.util.*;
class Hello implements Iterable<String>{
private String[] str_list = {"a","b","c"};
public Iterator<String> iterator(){
return new Iterator<String>() {
private int index =0;
@Override
public boolean hasNext() {
return index < str_list.length;
}
@Override
public String next() {
return str_list[index++];
}
};
}
}
public class Test {
public static void main(String[] args) {
for (String str : new Hello()) {
System.out.println(str);
}
}
}
(3)循环map数据
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("name", "zhangsan");
map.put("age","18");
for (Entry entry : map.entrySet()) {// entry是一个键值对, 对所有的键值对进行遍历
System.out.println(entry.getKey()+":"+entry.getValue());
}
}
}
11.13.1 适配器方法惯用法
如果想在Iterator的基础上,添加一个向后的方法,则可以使用适配器方法。
适配器设计模式,通过继承,在原有的基础上添加一个向前迭代的foreach循环。
import java.util.*;
class NewArray<T> extends ArrayList<T>{
public NewArray(Collection<T> c){//构造方法
super(c);
}
public Iterable<T> reversed(){
return new Iterable<T>() { //返回实现Iterable的匿名内部类
@Override
public Iterator<T> iterator() { //实现iterator方法
return new Iterator<T>() { //返回匿名内部类
int index = size()-1;
@Override
public boolean hasNext() {
return index>-1;
}
@Override
public T next() {
// TODO Auto-generated method stub
return get(index--);
}
};
} // public Iterator<T> iterator()
}; // return new Iterable<T>()
}//Iterable<T> reversed()
}
public class Test {
public static void main(String[] args) {
Collection<String> cs = new ArrayList<String>();
Collections.addAll(cs, "a b c d e".split(" "));
NewArray<String> n_list = new NewArray(cs);
for (String str : n_list) {
System.out.println(str);
}
for (String str : n_list.reversed()) {
System.out.println(str);
}
}
}
11.14 总结
(1)数组的容量是事先确定的,但是容器则不需要事先确定
(2)Collection保存单一元素,Map保存键值对。
(3)数组和List都是排好序的容器,List能够自动扩充容量
(4)随机访问,使用ArrayList, 经常在表中间插入和删除,则使用LinkedList
(5)LinkedList提供实现Queue和栈的行为
(6)HashMap快速访问数据,TreeMap保持key的排序状态,LinkedHashMap保持元素插入的顺序
(7)Set不允许元素重复, HashSet查询速度快,TreeSet保持排序状态,LinkedHashSet保持插入的顺序
(8)不使用过时的Vector,Hashtable和Stack
其关系为
- Collection(接口)
- List(接口)
- LinkedList
- ArrayList
- Set(接口)
- LinkedList
- HashSet
- LinkedHashSet
- TreeSet
- Queue(接口)
- LinkedList
- PriorityQueue
- List(接口)
- Map(接口)
- HashMap
- LinkedHashMap
- TreeMap
- HashMap
Collection可以生成Iterator
List可以生成ListIterator