Think in Java第四版 读书笔记5第11章

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/u011109881/article/details/90257023

第十一章 持有对象(主要讲容器类)

概要

通常程序中我们需要一些容器来存储对象或者对象的引用
在java中承担这一责任的是数组和容器类
数组VS容器类
数组存在一个缺陷:长度固定不够灵活
而容器类则没有这个缺陷,但是容器类需要的存储空间更大

11.1 泛型的作用

在Java SE5 之前,Java还没引入泛型,我们可以在List中放入任意类型的对象,看起来很方便是吧,但是在取出时要进行类型判断,如果转换错误还会报类型转换错误导致程序异常终止
泛型的优点
1.规定了容器类的存放类型
2.将类型转换错误从运行时转移到编译时
3.取出数据时不需要进行类型判断和转换了

11.2 容器的基本概念

容器的分类
1)Collection(集合类)存储独立元素,基本成员有List(按照顺序插入) Set(不能有相同元素) Queue(先进先出 FIFO)
2)Map 存储键值对 有点类似字典前面的索引与后面的具体内容的关系

容器类变量的创建:使用接口声明变量(Effective Java 2nd 52条)
此处唯一不可以这样用的情况是我们要使用的方法是具体类特有方法的时候

集合类添加元素与遍历的例子

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

public class SimpleCollection {

	public static void main(String[] args) {
		Collection<Integer> collection = new HashSet<Integer>();//Collection 是List Set Queue的父类,因此这里可以通用 这就体现了使用接口声明的效果了
		for (int i = 0; i < 10; i++) {
			collection.add(i);
		}
		for (Integer integer : collection) {
			System.out.println(integer + " ");
		}
	}

}

11.3 添加一组元素

添加一组元素的几种方法的例子

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class AddingGroups {
	public static void printCollection(Collection<Integer> collection) {
		System.out.println(collection);
	}
	
	
	public static void main(String[] args) {
		Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));
		printCollection(collection);//[1, 2, 3, 4, 5] 创建时传入一组数据
		
		Collection<Integer> collection2 = new ArrayList<Integer>(collection);
		printCollection(collection2);//[1, 2, 3, 4, 5] 创建时传入另一个Collection对象
		
		Integer [] moreInts = {6,7,8,9,10};
		collection.addAll(Arrays.asList(moreInts));//添加一组元素方式1 调用Collection.addAll(Collection<? extends E> c)
		printCollection(collection);//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
		
		//添加一组元素方式2 调用Collections.addAll(Collection<? super T> c, T... elements)
		//这样添加元素,运行速度明显快一些 但是无法使用这种方式进行构造(创建)Collection对象
		Collections.addAll(collection, 11,12,13,14,15);///可变参数的运用 //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
		printCollection(collection);
		
		
		Collections.addAll(collection, moreInts);//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 6, 7, 8, 9, 10]
		printCollection(collection);
		
		List<Integer> list = Arrays.asList(16,1,7,18,19);//Arrays.asList返回的是个定长数组 因此对它进行add delete操作都会报错UnsupportedOperationException
		printCollection(list);//[16, 1, 7, 18, 19]
		list.set(1, 99);
		printCollection(list);//[16, 99, 7, 18, 19]
		//list.add(21);//java.lang.UnsupportedOperationException
	}
}

Arrays.asList的局限性除了生成的List无法修改之外 在生成的类型上也可能出错 需要指定

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

class Snow{}
class Power extends Snow{}
class Light extends Power{}
class Heavy extends Power{}
class Crusty extends Snow{}
class Slush extends Snow{}

public class AsListInterface {

	public static void main(String[] args) {
		List<Snow> snow1 = Arrays.asList(new Crusty(),new Slush(),new Power());
		
		//List<Snow> snow2 = Arrays.asList(new Light(),new Heavy());
		//编译不通过 List<Snow>期望值为List<Power>
		
		List<Snow> snows3 = new ArrayList<Snow>();
		Collections.addAll(snows3, new Light(),new Heavy());//Collections.addAll不会有上面的问题
		
		//以下的写法可以 因为Arrays.<Snow>中告诉编译器对于Arrays.asList应该产生的List类型 这称为显示类型参数说明
		List<Snow> snow4 = Arrays.<Snow>asList(new Light(),new Heavy(),new Snow());
	}

}

另外注意区分Collection(接口)和Collections(工具类,专门提供排序遍历查找比较等方法 和Arrays类似,很好记,Collections和Arrays都是工具类,都带s)

容器的打印

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
//Java 中两种容器:Collection和Map 的简单介绍
//Collection每个“槽”只能保存一个元素 Collection包括List Set元素不能重复 Queue FIFO
//Map每个“槽”可以保存两个元素:键和值
public class PrintingContainers {
	static Collection<String> fill(Collection<String> collection) {
		collection.add("rat");
		collection.add("cat");
		collection.add("dog");
		collection.add("dog");
		return collection;
	}

	static Map<String, String> fill(Map<String, String> map) {
		map.put("rat", "1");
		map.put("cat", "2");
		map.put("dog", "3");
		map.put("dog", "4");
		return map;
	}

	public static void main(String[] args) {
		//各输出源自AbstractMap AbstractCollection对应的toString方法
		//ArrayList LinkedList输出一样 但是LinkedLis包含的操作多于ArrayList
		System.out.println(fill(new ArrayList<String>()));
		System.out.println(fill(new LinkedList<String>()));
		
		//以下三种属于Set类型 相同的数据只能保存一次
		//通常使用set时不关注存储顺序 需要关注顺序的话用 TreeSet和LinkedHashSet
		System.out.println(fill(new HashSet<String>()));
		System.out.println(fill(new TreeSet<String>()));
		System.out.println(fill(new LinkedHashSet<String>()));
		
		//三种map 特点是存储键值对 Map有很多特别的特性
		//对于其原理可以参考我之前的一篇文章 https://blog.csdn.net/u011109881/article/details/80379505
		System.out.println(fill(new HashMap<String, String>()));
		System.out.println(fill(new TreeMap<String, String>()));
		System.out.println(fill(new LinkedHashMap<String, String>()));
	}
}
/*
out put:
[rat, cat, dog, dog]
[rat, cat, dog, dog]
[rat, cat, dog]
[cat, dog, rat]
[rat, cat, dog]
{rat=1, cat=2, dog=4}
{cat=2, dog=4, rat=1}
{rat=1, cat=2, dog=4}
*/

11.5 List

List有两种经常使用的类型
ArrayList 擅长随机访问
LinkedList 擅长从中间插入元素
书中例子没有列出完整代码,这里就不写代码 代码无非演示了List
List有这些方法,可以直接在api看到详细介绍
在这里插入图片描述

1.6 迭代器

迭代器的存在方便了我们对集合类的访问
迭代器还可以进行一些元素的操作
迭代器的存在 将容器的访问与容器的底层结构相分离,我们不需要知道底层的具体存储类型就可以访问。
不过迭代器丢失了当前访问item的索引,如果想要索引 还是得使用传统的for循环来遍历

11.6.1 ListIterator

最大特点是可以指定cursor的index

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

//简单介绍ListIterator的例子(只能遍历List 无法用在set上)
public class ListIteration {

	public static void main(String[] args) {
		List<String> strings = new ArrayList<String>();
		strings.add("0");
		strings.add("1");
		strings.add("2");
		strings.add("3");
		strings.add("4");
		strings.add("5");
		strings.add("6");
		strings.add("7");
		ListIterator<String> it = strings.listIterator();
		while (it.hasNext()) {
			//获取前后索引index
			System.out.println(it.next()+","+it.nextIndex()+","+it.previousIndex());//注意next方法执行后cursor已经后移
			//同样it.previous也会导致cursor前移
		}
		System.out.println();
		while (it.hasPrevious()) {
			System.out.print(it.previous()+" ");//向前倒序访问
		}
		System.out.println();
		System.out.println(strings);//打印原始字符串
		it = strings.listIterator(3);//获取index为3的Iterator
		while (it.hasNext()) {
			String string = it.next();
			System.out.print(string+" ");
			it.set(""+(Integer.valueOf(string)+1));//set方法替换访问的最后一个元素
		}
		System.out.println();
		System.out.println(strings);
	}
}
/** out put
0,1,0
1,2,1
2,3,2
3,4,3
4,5,4
5,6,5
6,7,6
7,8,7

7 6 5 4 3 2 1 0 
[0, 1, 2, 3, 4, 5, 6, 7]
3 4 5 6 7 
[0, 1, 2, 4, 5, 6, 7, 8]
*/

11.7 LinkedList

import java.util.Arrays;
import java.util.LinkedList;

public class LinkedListFeatures {

	public static void main(String[] args) {
		LinkedList<String> strings = new LinkedList<String>(Arrays.asList("0","1","2","3","4","5","6","7","8"));
		System.out.println(strings);
		System.out.println(strings.getFirst());//取出头部(第一个)元素 但是不从List中删除 比下面两个方法多了null list时抛出NoSuchElementException的逻辑
		System.out.println(strings.element());//取出头部(第一个)元素 但是不从List中删除 element内部调用的getFirst方法
		System.out.println(strings.peek());//取出头部(第一个)元素 但是不从List中删除
		System.out.println(strings);
		System.out.println("------------");
		System.out.println(strings.remove());//取出头部(第一个)元素 并且从List中删除 内部调用的removeFirst
		System.out.println(strings.removeFirst());//取出头部(第一个)元素 并且从List中删除
		System.out.println(strings.poll());//取出头部(第一个)元素 并且从List中删除 和removeFirst的区别在于判空
//	    public E removeFirst() {
//	        final Node<E> f = first;
//	        if (f == null)
//	            throw new NoSuchElementException();
//	        return unlinkFirst(f);
//	    }
		
//	    public E poll() {
//	        final Node<E> f = first;
//	        return (f == null) ? null : unlinkFirst(f);
//	    }
		System.out.println(strings);
		System.out.println("----------------");
		strings.addFirst("addFirst");//在头部加数据
		strings.offer("offer");//在尾部加数据 内部调用add方法
		strings.add("add");//在尾部加数据
		strings.addLast("addLast");//在尾部加数据 和add方法实现 不过add方法有返回值
		System.out.println(strings);
		strings.removeLast();//从尾部删除
		System.out.println(strings);
		strings.remove(1);//删除指定下标
		System.out.println(strings);
	}
}
/** output
[0, 1, 2, 3, 4, 5, 6, 7, 8]
0
0
0
[0, 1, 2, 3, 4, 5, 6, 7, 8]
------------
0
1
2
[3, 4, 5, 6, 7, 8]
----------------
[addFirst, 3, 4, 5, 6, 7, 8, offer, add, addLast]
[addFirst, 3, 4, 5, 6, 7, 8, offer, add]
[addFirst, 4, 5, 6, 7, 8, offer, add]

 */

LinkedList本身可以模拟Queue

11.8 Stack

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
//Set不保存重复元素
//Set常用方法是查看某个元素是否在集合中
//因此查找是set的常用操作
//hashSet对查找进行了优化

//Set 继承自Collection接口并且没有添加新方法,因此Set的方法和Collection的方法完全一致
//但是Set的行为却不同 这就像Java的多态一样--表现不同的行为
public class SetOfInteger {

	public static void main(String[] args) {
		Random random = new Random(47);
		Set<Integer> integers = new HashSet<Integer>();//HashSet TreeSet都是顺序的 LinkedHashSet是乱序的
		//HashSet的输出结果书中的结论不一致 可能是因为在新版本Java中修改了hashSet的实现,此处不明确 如有错误请帮忙指出
		for (int i = 0; i < 10000; i++) {
			integers.add(random.nextInt(30));
			//调用add实际调用了10000次 但是只有30次成功了,其他失败
			//是因为add的数值已经存在
		}
		System.out.println(integers);
	}

}
//输出:
//[0, 1, 2, 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]
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

//测试Set的包含关系
public class SetOperations {

	public static void main(String[] args) {
		Set<String> set1 = new HashSet<String>();
		Collections.addAll(set1, "A B C D E F G H I J K L".split(" "));
		set1.add("M");
		System.out.println("H in set1 " + set1.contains("H"));
		System.out.println("N in set1 " + set1.contains("N"));

		Set<String> set2 = new HashSet<String>();
		Collections.addAll(set1, "H I J K L".split(" "));
		System.out.println("set2 in set1 " + set1.containsAll(set2));
		set1.remove("H");
		System.out.println("set1 " + set1);
		System.out.println("set2 in set1 " + set1.containsAll(set2));
		set1.removeAll(set2);
		System.out.println("after removeAll in set2" + set1);
		Collections.addAll(set1, "X Y Z".split(" "));
		System.out.println("XYZ add to set1 " + set1);
	}

}
/**
H in set1 true
N in set1 false
set2 in set1 true
set1 [A, B, C, D, E, F, G, I, J, K, L, M]
set2 in set1 true
after removeAll in set2[A, B, C, D, E, F, G, I, J, K, L, M]
XYZ add to set1 [A, B, C, D, E, F, G, I, J, K, L, M, X, Y, Z]
 */

书中下面的几个例子用到了Compare和书里提供的工具类,就略过了。

11.10 MAP

map是经常使用的数据存储工具,比如产生10000个[0,20)的随机数,map就是很好的选择,可以以[0,20)的数字为键,出现的次数为值,程序也很简单,就不列出了。
Map还有containsKey和containsValue方法来判断map是否包含指定的键或值
Map的值存储的东西可以是基本类型 也可以是其他对象,比如List Map等等,比如我们想要存储一些有多个宠物的人,那么我们可以用Map<Person,List< Pet >>来存储。
Map也可以返回它的键集合或者值集合,即keySet和values方法
Map是一个非常重要的存储工具

11.11 Queue

Queue是一种FIFO先进先出的容器,比如排队买票,自然是先排队的人先买到票然后退出队伍,还是很形象的。

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

//利用LinkedList模拟构建Queue,之所以可以模拟(通过向下转型),原因如下
//public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
//public interface Deque<E> extends Queue<E> 
//因此LinkedList间接实现了Queue 它满足Queue的所有行为
public class QueueDemo {
	public static void printQ(Queue queue) {
		while (queue.peek() != null) {
			System.out.print(queue.remove() + " ");// 从头部删除(取出)
		}
		System.out.println("|||||");
	}

	public static void main(String[] args) {
		Queue<Integer> queue = new LinkedList<Integer>();
		Random random = new Random(47);
		for (int i = 0; i < 10; i++) {
			queue.offer(random.nextInt(i + 10));// 从尾部添加
		}
		printQ(queue);
		Queue<Character> qc = new LinkedList<Character>();
		for (char c : "China".toCharArray()) {
			qc.offer(c);
		}
		printQ(qc);
	}

}

11.11.1 PriorityQueue

public class PriorityQueueDemo {

	public static void main(String[] args) {
		//PriorityQueue 优先级队列并不完全遵照FIFO的原则,它的元素还有一个权重(优先级)
		//例子1 PriorityQueue存放整型
		PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>();
		Random random = new Random(47);
		for (int i = 0; i < 10; i++) {
			priorityQueue.offer(random.nextInt(i+10));
		}
		QueueDemo.printQ(priorityQueue);
		
		//例子2 PriorityQueue存放整型
		List<Integer> ints = Arrays.asList(25,22,20,18,14,9,3,1,1,2,3,9,14,18,21,23,25);
		priorityQueue = new PriorityQueue<Integer>(ints);
		QueueDemo.printQ(priorityQueue);
		
		//例子3 PriorityQueue存放反序整型 第二个参数是Comparator 决定了元素的权重
		priorityQueue = new PriorityQueue<Integer>(ints.size(),Collections.reverseOrder());
		priorityQueue.addAll(ints);
		QueueDemo.printQ(priorityQueue);
		
		//例子4 PriorityQueue存放word 默认按照字典顺序分配权重
		String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";
		List<String> strings = Arrays.asList(fact.split(" "));
		PriorityQueue<String> stringPQ = new PriorityQueue<String>(strings);
		QueueDemo.printQ(stringPQ);
		
		//例子5 PriorityQueue存放word 默认按照字典反序分配权重
		stringPQ = new PriorityQueue<String>(strings.size(),Collections.reverseOrder());
		stringPQ.addAll(strings);
		QueueDemo.printQ(stringPQ);
		
		//例子6 PriorityQueue存放字符 改成Set存储 所以没有重复值
		Set<Character> charSet = new HashSet<Character>();
		for (char c : fact.toCharArray()) {
			charSet.add(c);
		}
		PriorityQueue<Character> characterPQ = new PriorityQueue<Character>(charSet);
		QueueDemo.printQ(characterPQ);
	}

}
/**
 * 输出:
0 1 1 1 1 1 3 5 8 14 |||||
1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25 |||||
25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1 |||||
EDUCATION ESCHEW OBFUSCATION SHOULD |||||
SHOULD OBFUSCATION ESCHEW EDUCATION |||||
  A B C D E F H I L N O S T U W |||||
 */

11.12 Collection 和 Iterator

public class InterfaceVSIterator {
	public static void display(Iterator<String> it){
		while (it.hasNext()) {
			System.out.print(it.next()+" ");
		}
		System.out.println();
	}
	
	public static void display(Collection<String> collection) {
		for(String string:collection){
			System.out.print(string+" ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		List<String> strings = Arrays.asList("My","Name","Is","AAA","Nice","To","Meet","You");//顺序存储
		Set<String> stringSet = new HashSet<String>(strings);//不存储相同字符 存储顺序按照散列顺序存储
		Map<String, String> stringMap = new LinkedHashMap<String, String>();
		String [] namesStrings = ("A,B,C,D,E,F,G,H").split(",");
		for (int i = 0; i < namesStrings.length; i++) {
			stringMap.put(namesStrings[i], strings.get(i));
		}
		
		display(strings);
		display(stringSet);
		
		display(strings.iterator());
		display(stringSet.iterator());
		
		System.out.println(stringMap);
		System.out.println(stringMap.keySet());
		
		display(stringMap.values());
		display(stringMap.values().iterator());
	}

}
/** 输出:(看起来iterator和collection没有区别)
My Name Is AAA Nice To Meet You 
AAA Meet Nice Is To My You Name 
My Name Is AAA Nice To Meet You 
AAA Meet Nice Is To My You Name 
{A=My, B=Name, C=Is, D=AAA, E=Nice, F=To, G=Meet, H=You}
[A, B, C, D, E, F, G, H]
My Name Is AAA Nice To Meet You 
My Name Is AAA Nice To Meet You 
 */

本例看起来collection 和 iterator功能基本一样,只不过一个利用foreach来遍历一个用next遍历,但是我们需要注意public interface Collection extends Iterable的类结构,因此iterator有时候比collection更轻便。

11.3 foreach 与迭代器(Iterator)

foreach不仅仅可以使用在list上 事实上只要实现了Iterable接口 就可以使用foreach进行遍历,比如collection
例子:实现自己的Iterable

import java.util.Iterator;
/**
 * 
 * Iterable和Iterator
 * 
 * Iterable(实现Iterable接口允许该对象使用for-each语法)
 * Implementing this interface allows an object to be the target of
 * the "for-each loop" statement.
 * 
 * Iterator
 * Iterator取代了原先的Java集合框架中的枚举,Iterator和枚举有两点不同
 * 1.Iterator在完善的语义语法定义下可以在遍历时删除元素
 * 2.方法名改进
 * Iterator是Java集合类的一个成员
 * An iterator over a collection. Iterator takes the place of ink Enumeration in the Java Collections Framework.  Iterators differ from enumerations in two ways:
 * 1.Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.
 * 2.Method names have been improved.
 * This interface is a member of the Java Collections Framework
 */

public class IterableClass implements Iterable<String> {
	IterableClass(){
		
	}
	protected String[] words = ("And that is how we know the Earth to be banana-shaped")
			.split(" ");

	public Iterator<String> iterator() {
		return new Iterator<String>() {
			private int index = 0;

			public boolean hasNext() {
				return index < words.length;
			}

			public String next() {
				return words[index++];
			}

			public void remove() {
				throw new UnsupportedOperationException("remove");
			}

		};
	}
	
	public static void main(String[] args) {
		for (String s: new IterableClass()) {
			System.out.print(s+" ");
		}
	}

}

通常自定义Iterable类时先实现Iterable,然后覆写Iterable中的iterator方法,其返回值是Iterator对象,上面的例子即是匿名内部类的使用
注意数组不是Iterable的

public class ArrayIsNotIterable {
	static <T> void test(Iterable<T> ib){
		for (T t :ib) {
			System.out.print(t+" ");
		}
	}
	
	public static void main(String [] args){
		test(Arrays.asList(1,2,3));
		String [] strings = {"A","B","C"};
		//test(strings); 编译不过 说明数组不是Iterable的
		//Arrays.asList将数组转换成list 而list是Iterable的 此时可以进行遍历
		test(Arrays.asList(strings));
	}

}

11.13.1 适配器模式与Iterator的结合
假设现在我们要进行一个单词表的正向或者方向遍历,Iterable接口的方法iterator只提供了一种遍历方式,如何实现这种需求呢?就要用到适配器了。

public class ReversibleArrayList<T> extends ArrayList<T> {
	public ReversibleArrayList(Collection<T> c) {
		super(c);// 沿用父类的构造方法
	}

	public Iterable<T> reversed() {//由于for-each使用的是Iterable对象,我们可以使用新的Iterable来实现新的输出
		//对于for each 使用返回不同的Iterable方法 ,个人决定这个更像策略模式?
		return new Iterable<T>() {
			public Iterator<T> iterator() {
				return new Iterator<T>() {
					int current = size() - 1;

					public boolean hasNext() {
						return current > -1;
					}

					public T next() {
						return get(current--);// 先返回对象 之后index--
					}

				};
			}
		};
	}
}
public class AdapterMethodIdiom {

	public static void main(String[] args) {
		//给for-each 传入不同的iterator对象则输出不同
		
		ReversibleArrayList<String> ral = new ReversibleArrayList<String>(Arrays.asList("To be or not to be ?".split(" ")));
		for (String string : ral) {//顺序输出
			System.out.print(string+" ");
		}
		System.out.println();
		
		for (String string : ral.reversed()) {//反序输出
			System.out.print(string+" ");
		}
		
	}
}
//备注Collections.shuffle(ral,new Random(47)); 是一个随机打乱List的方法

总结

1.数组长度不可变
2.collection存放单一元素 map存放键值对,collection和map都能自动调整size
3.Array list随机访问效率高 LinkedList进行元素插入效率高
4.LinkedList可以模拟stack和Queue
5.HashMap被设计进行快速访问,TreeMap的键需要保持有序,因此没有HashMap快,LinkedhashMap兼具有序和快速访问
6.Set存放非重复元素,HashSet访问速度很快 TreeSet有序 LinkedHashSet以插入顺序存放元素
7.常用集合类和接口
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/u011109881/article/details/90257023