读《java编程思想》11-持有对象

时间:2019-09-03
本文章向大家介绍读《java编程思想》11-持有对象,主要包括读《java编程思想》11-持有对象使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
程序需要在任何时刻和任意位置创建任意数量的对象,不能依靠创建命名引用来持有每一个对象。(一个变量一个?)
数组是保存一组对象引用最有效的方式,但是有固定尺寸,java中使用容器类来解决这个问题。
1、泛型和类型安全的容器
声明容器时,使用尖括号括起来的就是类型参数,他指定了容器实例可以保存的类型,通过泛型,就可以在编译期防止将错误类型的对象放置到容器中。
如:List<Pet> pets = new ArrayList<>();
 
2、基本概念
java容器类类库的用途是“保存对象”即持有对象,并将其分为两个不同的概念:
(1)Collection:一个独立元素的序列。(List、Set、Queue都是子类)
(2)Map:一组成对的“键值对”对象,允许使用键来查找值。
 
 
3、在一个Collection中添加一组元素
(1)Arrays.asList
参数为可变参数,返回值为Arrays$ArrayList,并不是java.util.ArrayList。它没有实现更改列表长度的方法,如果调用add或remove,则会调用到AbstractList中,抛出UnsupportedOperationException。
(2)Collection构造器中可以接受另一个Collection。
(3)Collections.addAll
注意无论哪种方式添加,操作的都是引用。如果改变对象属性,那么所有通过引用获取的属性都会改变。
不可变对象除外,如:String、基本类型的包装类、BigInteger和BigDecimal等。
 
4、容器的打印
Arrays.toString 打印数组。
Collection自带的toString 打印容器。
 
5、List
(1)ArrayList 擅长随机访问,插入或移除元素时较慢。
它内部是一个数组,通过下标随机访问数组速度快,插入和移除通过 copy数组扩大或者缩小数组容量,然后给新地址赋值完成。如果ArrayList非常大, 复制肯定会消耗不少时间。
(2)LinkedList 擅长插入或移除元素,随机访问较慢。
它内部是一个双向链表,Node节点里面有next prev 两种指针。插入和移除通过改变next 和 prev指向即可完成。速度快。如果要随机访问某个下标元素,则需要从第一个或者最后一个元素开始通过两种指针挨个数过去。如果节点数量很大,目标很靠后,那么这种整体扫描肯定会花不少时间。
 
练习7中先通过List.subList方法从父列表中得到子列表,然后删除父列表中的某个元素,最后打印sublist,会抛出ConcurrentModificationException,为什么呢?
使用subList方法获取的是ArrayList$SubList,并不是一个java.util.ArrayList。和Arrays.asList类似,但不是一个静态内部类,这里使用了外部类modCount属性。SubList所有的新增或修改的操作除了修改自己的modCount外,都会同步修改外部类ArrayList的modCount。并且SubList其他方法大都会先调用checkForComodification判断这两者modCount是否一致,如果不同则会抛出ConcurrentModificationException。因此不要通过外部ArrayList 删除或新增元素,可以通过sublist子列表修改。
 
6、迭代器
迭代器是一个对象,它可以遍历并选择序列中的对象。
Iterator 只能单项移动,Collection.iterator可以获取。
ListIterator 可以双向移动,List.listIterator可以获取。
 
7、LinkedList同时拥有List、Stack、Queue能力
(1)List实现之一
get、add、remove
 
(2)拥有栈的所有功能,并不是继承Stack。
a. java.util.Stack主要方法:
public E push(E item) 入栈
public synchronized E pop() 出栈
public synchronized E peek() 查看栈顶
 
b. 对应LinkedList方法:
public void push(E e)
public E pop()
public E peek()
 
c. 如果把LinkedList作为栈
LinkedList stack = new LinkedList();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.pop();
System.out.println(stack);
输出为[3, 2, 1], 那么最左边为栈顶3,最右边为栈底1。
push插入栈顶,为入栈,pop移出栈顶,为出栈。此证明栈为后进先出LIFO。
 
d. java.util.Stack 和 java.util.LinkedList 主要区别:
Stack内部实现是数组(和ArrayList内部一样,优缺点也相同),LinkedList则为双向链表。
Stack线程安全,性能不如LinkedList。
 
书中建议使用LinkedList替代Stack,应该也是考虑到性能原因,并且入栈出栈(插入删除)链表显然更适合。
 
(3)实现了双向队列接口Deque(继承Queue)
a. java.util.Queue方法:
boolean add(E e) 队尾添加
boolean offer(E e) 队尾添加,LinkedList实现直接调用的add方法
E remove() 获取并删除队头
E poll() 获取并删除队头,LinkedList实现都调用removeFirst方法
E element() 获取队头,如果队列为空,则抛异常NoSuchElementException
E peek() 获取队头,如果队列为空,则返回null
 
b. java.util.Deque方法:
void push(E e) 队头插入 (和栈类似)
Deque其他主要方法,都是在Queue方法后面添加First 和 Last (双向),如:
void addFirst(E e)
void addLast(E e)
等等。
 
注意LinkedList不是阻塞队列BlockingQueue,自然没有put 和take方法。
 
c. 如果把LinkedList作为队列
LinkedList queue = new LinkedList();
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
queue.poll();
System.out.println(queue);
输出为[2, 3, 4],那么最左边2为队头,最右边4位队尾。
offer插入队尾,为入队,poll移出队头,为出队。此证明了队列为先进先出FIFO。
 
(4)总结:LinkedList同时拥有列表、栈、队列功能,因此出现了很多功能相同名字不同的方法,如add offer等,我们使用时建议按照目的功能不同分开使用。
如:
列表 add、remove、 get
栈 push、 pop、peek
队列 offer、poll、peek,xxxFirst、xxxLast
 
 
8、Map 键值对(第17章再深入分析算法)
(1)TreeMap 根据key排序,使用红黑树实现,put插入元素时,先找到插入位置,再做自平衡。
注意,因为要对key排序,所以需要使用Comparator构造TreeMap,或者key对象实现Comparable,否则会出现异常。
cannot be cast to java.lang.Comparable
 
Tips:
Comparable、Comparator 相似,通过比较方法的参数可以看出区别:
Comparable
int compareTo(T o)
接口实现后,拥有比较能力,拿自己和别人比。
 
Comparator
int compare(Object o1, Object o2)
接口实现后,作为比较器,是一种算法对象。比较外部两个对象。
 
(2)HashMap 是链表散列结构,使用数组和链表结合的方式实现:
put插入元素主要流程为:
其中判断是否为已有节点从而进行替换(包括链表节点查找,树节点查找),都使用节点的key值进行==或equals比对。因此如果使用对象作为map的key,则不仅需要覆盖对象的hashCode方法,还需要覆盖equals方法,才能正确查找到对象。
 
(3)LinkedHashMap
a. 继承HashMap(节点为单向链表节点,只有next),保留数组结构,但使用双向链表节点(Entity代替Node,拥有before、after双向引用)
 
b. 新增头尾节点,
LinkedHashMap.Entry<K,V> head;
LinkedHashMap.Entry<K,V> tail;
插入元素时,覆盖afterNodeAccess方法(HashMap put方法中的一步,空实现),维护LinkedHashMap的head、tail引用,以及LinkedHashMap.Entry的before、afterl引用。
 
c. 总结:LinkedHashMap没有改变HashMap的put插入逻辑,只是在插入节点时维护before、after引用,当foreach遍历节点时,从head或tail开始遍历。就可以保证有序。
 
 
9、Set 不保存重复元素,查找是其最重要的工作
(1)TreeSet
内部使用TreeMap实现,key为set的元素,value为new Object()。
同TreeMap,因为要对元素排序,所以需要使用Comparator构造TreeSet,或者元素对象实现Comparable,否则会出现异常。
cannot be cast to java.lang.Comparable
 
(2)HashSet
内部使用HashMap实现,key为set元素,value为new Object()。
 
(3)LinkedHashSet
继承HashMap,内部使用LinkedHashMap实现,key为set元素,value为new Object()。(构造函数调用父类HashSet构造函数初始化LinkedHashMap)
 
总结:因为Map的key唯一,所以Set元素唯一。
 
10、PriorityQueue 优先级队列
其实就是可排序队列,内部使用数组存储,因为要对元素排序,所以需要使用Comparator构造PriorityQueue,或者元素对象实现Comparable,否则会出现异常。
cannot be cast to java.lang.Comparable
 
11、Collection,Iterator,Iterable三接口关系
(1)Collection继承Iterable接口。
(2)Iterable接口需要实现Iterator<T> iterator()方法返回迭代器。
(3)实现了Iterable接口可以支持foreach语法。(单向遍历)
(4)在设计类时提供返回不同Iterable或者iterator的方法,可以实现多种遍历方式:双向、乱序等。
 
因此Collection实现类List、Set等可以foreach,并通过iterator方法得到迭代器。

原文地址:https://www.cnblogs.com/shineup/p/11453092.html