类集设置的目的(重点)
普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为java 对数据结构的实现。
类集中最大的几个操作接口:Collection、Map、Iterator
Collection 接口(重点)
Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。
接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。
public boolean add(E e) | 向集合中插入一个元素 |
---|---|
public boolean addAll(Collection<? extends E> c) | 向集合中插入一组元素 |
public void clear() | 清空集合中的元素 |
public boolean contains(Object o) | 查找一个元素是否存在 |
public boolean containsAll(Collection<?> c) | 查找一组元素是否存在 |
public boolean isEmpty() | 判断集合是否为空 |
public Iterator iterator() | 为 Iterator 接口实例化 |
public boolean remove(Object o) | 从集合中删除一个对象 |
boolean removeAll(Collection<?> c) | 从集合中删除一组对象 |
boolean retainAll(Collection<?> c) | 集合中只保留传入集合与自身相同的元素,有元素被删除返回true |
public int size() | 求出集合中元素的个数 |
public Object[] toArray() | 以对象数组的形式返回集合中的全部内容 |
<T> T[] toArray(T[] a) | 指定操作的泛型类型,并把内容返回 |
public boolean equals(Object o) | 从 Object 类中覆写而来 |
public int hashCode() | 从 Object 类中覆写而来 |
List 接口(重点)
在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。
扩充方法:
public void add(int index,E element) | 在指定位置处增加元素 |
---|---|
boolean addAll(int index,Collection<? extends E> c) | 在指定位置处增加一组元素 |
public E get(int index) | 根据索引位置取出每一个元素 |
public int indexOf(Object o) | 根据对象查找指定的位置,找不到返回-1 |
public int lastIndexOf(Object o) | 从后面向前查找位置,找不到返回-1 |
public ListIterator<E> listIterator() | 返回 ListIterator 接口的实例 |
public ListIterator<E> listIterator(int index) | 返回从指定位置的 ListIterator |
public E remove(int index) | 删除指定位置的内容 |
**public E set(int index,E element) ** | 修改指定位置的内容 |
List<E> subList(int fromIndex, int toIndex) | 返回子集合 |
List常用实现类有三个:
ArrayList(95%)、Vector(4%)、LinkedList(1%)
ArrayList和Vector都是基于存储元素的Object[] array来实现的,它们会在内存中开辟一块连续的空间来存储,因为数据存储是连续的,所以它们支持用下标来访问元素,索引数据的速度比较快。
-
ArrayList和Vector都有一个初始化的容量大小,当里面存储的元素超过初始的大小时就需要动态地扩充它们的存储空间,Vector默认扩充为原来的两倍(每次扩充空间的大小可以设置),ArrayList默认扩充为原来的1.5倍
-
ArrayList和Vector最大的区别就是synchronization的使用,ArrayList所有的方法是非同步的(非线程安全),而Vector的绝大多数方法(如add,insert,remove,set,equals,hashcode)都是直接或间接同步的,所以Vector是线程安全的,正由于提供了线程安全的机制,其性能上稍逊与ArrayList。
-
LinkedList是采用双向链表来实现的,对数据的索引需要从列表头开始遍历,因此用于随机访问则效率比较低,但是插入元素时不需要对数据进行移动,因此插入效率较高。LinkedList是非线程安全的容器。
-
对容器的选择:
当对数据的主要操作是索引或只在集合的末端增加、删除元素时,使用ArrayList或Vector效率比较高,当对数据的操作主要为指定位置或删除操作时,使用LikedList效率比较高;当在多线程中使用容器时,选用Vector较为安全。
示例代码
List<String> list = new ArrayList<>();
list.add("hello"); // 增加内容,此方法从Collection接口继承而来
list.add(1,"你好"); // 增加内容,此方法是List接口单独定义
list.add("安宁哈撒呦"); // 增加内容,此方法从Collection接口继承而来
list.remove("你好"); // 删除内容
list.remove(0); // 删除内容
for(int i=0; i<list.size(); i++){
System.out.println(list.get(i));
}
-
LinkedList
此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:
public boolean add(E e) 增加元素,如果有容量限制,并且已满,则抛出异 常 public E element() 取得,但是不删除当前元素,如果对列为空则抛出异常 boolean offer(E e) 添加,如果有容量限制,并且已满,只是无法添加, 但不抛出异常,返回 false E peek() 取得头元素,但是不删除,如果队列为空,则返回null E poll() 取得头元素,但是删除, 如果队列为空,则返回null E remove() 删除当前元素, 如果对列为空则抛出异常 示例代码:
Queue<String> queue = new LinkedList<String>(); queue.offer("111"); queue.offer("222"); queue.offer("333"); System.out.println(queue.peek()); // 111(取出第一个元素,不删除) System.out.println(queue.poll()); // 111(取出第一个元素,且删除) queue.remove("222"); // 删除第二个元素 System.out.println(queue.peek()); // 333 (取出第三个元素,且删除)
Set 接口(重点)
Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。有两个常用的子类:HashSet、TreeSet,使用的方法全部都是重写父接口Collection的方法。
-
散列存放:HashSet(重点)
HashSet 属于散列的存放类集,里面的内容是无序存放的
Set<String> set = new HashSet<String>(); set.add("A"); set.add("B"); System.out.println(set.add("C")); // true System.out.println(set.add("C")); // false(已存在存入失败返回false) set.add("D"); set.add("E"); set.remove("A"); // 删除A Object[] strArr = set.toArray(); // 转成数组 for(Object o : strArr) System.out.print(o.toString() + " "); // B C D E
-
排序的子类:TreeSet(重点)
与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:
在使用TreeSet实现类时,存入的对象要实现Comparable这个类,重写compareTo方法
传入的对象是否相同,是通过compareTo判断,因此重写compareTo方法时,应具有区分元素是否相同的能力
@Override public int compareTo(Person o) { if(this.getAge() > o.getAge()){ return 1; }else if(this.getAge() < o.getAge()){ return -1; }else{ return this.getName().compareTo(o.getName()); } }
注意:不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,hashSet存入对象时,也需要复写Object的equals和hashCode方法,才能去除重复值。
集合输出(重点)
输出集合使用如下几种方式:
-
Iterator 迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)
-
Iterator(绝对重点)
Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。行由前向后的单向输出
Collection 接口中提供了iterator()方法,可以用于为 Iterator 接口进行实例化操作。
此接口规定了以下三个方法:
boolean hasNext() 是否有下一个元素 E next() 取出内容 void remove() 删除当前内容 Iterator 中的操作指针是在第一条元素之上,当调用 next()方 法的时候,获取当前指针指向的值并向下移动,使用 hasNext()可以检查序列中是否还有元素。
示例代码:
Iterator<Person> it = set.iterator(); while(it.hasNext()){ Person p = it.next(); System.out.println(p.toString()); }
注意:但是在使用 Iterator 输出的时候有一点必须注意,在进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator 接口中的 remove()方法,而不能使用集合中的 remove()方法。否则将出现未知的错误。
-
ListIterator(理解)
ListIterator 是可以进行双向输出的迭代接口
操作方法
void add(E e) 增加元素 boolean hasPrevious() 判断是否有前一个元素 E previous() 取出前一个元素 void set(E e) 修改元素的内容 int previousIndex() 前一个索引位置 int nextIndex() 下一个索引位置 有一点需要注意的是,如果要想进行由后向前的输出,则首先必须先进行由前向后的输出。
-
新的支持:foreach(理解)
foreach可以用来输出数组的内容,也可以输出集合中的内容
for(Person p : set){ System.out.println(p.toString()); }
要注意的是,里面的操作泛型要指定具体的类型,这样在输出的时候才会更加有针对性。
Map 接口(重点)
Map保存的所有内容都按照 key→value 的形式保存,也称为二元偶对象。
此接口与 Collection 接口没有任何的关系,是第二大的集合操作接口。此接口常用方法如下:
void clear() 清空 Map 集合中的内容 boolean containsKey(Object key) 判断集合中是否存在指定的 key boolean containsValue(Object value) 判断集合中是否存在指定的 value Set<Map.Entry<K, V>> entrySet() 将 Map 接口变为 Set 集合 V get(Object key) 根据 key 找到其对应的 value boolean isEmpty() 判断是否为空 Set<K> keySet() 将全部的 key 变为 Set 集合 Collection<V> values() 将全部的 value 变为 Collection 集合 V put(K key,V value) 向集合中增加内容 void putAll(Map<? extends K, ? extends V> m) 增加一组集合 V remove(Object key) 根据 key 删除内容
Map 本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable
-
HashMap(重点)
HashMap 是 Map 的子类,此类继承了 AbstractMap 类,同时可以被克隆,可以被序列化下来。
示例代码:
Map<Integer, String> map = new HashMap<Integer, String>(); System.out.println(map.put(1, "A")); // 返回之前的value,没存之前时:null System.out.println(map.put(1, "B")); // 新的内容替换掉旧的内容,返回之前的value:A System.out.println(map.put(1, "B")); // 新的内容替换掉旧的内容,返回之前的value:B map.put(2, "C"); map.put(3, "D"); // 将 Map 二元偶对象变为 Set 集合 Set<Map.Entry<Integer, String>> setMap = map.entrySet(); // 迭代器迭代输出 Iterator<Map.Entry<Integer, String>> it = setMap.iterator(); while(it.hasNext()){ Map.Entry<Integer, String> map2 = it.next(); System.out.println(map2.getKey() + "→" + map2.getValue()); }
-
旧的子类:Hashtable(重点)
Hashtable 是一个最早的 keyvalue 的操作类,本身是在 JDK 1.0 的时候推出的。其基本操作与 HashMap 是类似的。
注意:Hashtable 中是不能向集合中插入 null 值的
-
HashMap 与 Hashtable 的区别(重点)
区别点 HashMap Hashtable 推出时间 JDK 1.2 之后推出的,新的操作类 JDK 1.0 时推出的,旧的操作类 性能 异步处理,性能较高 同步处理,性能较低 null 允许设置为null 不允许设置,否则将出现空指向异常 -
排序的子类:TreeMap(理解)
TreeMap 子类是允许 key 进行排序的操作子类,其本身在操作的时候将按照 key 进行排序,key 中的内容可以 为任意的对象,但是要求对象所在的类必须实现 Comparable 接口。
关于 Map 集合的输出
- Map中keySet()方法将key转成一个Set集合,再用Iterator进行输出
- Map中values()方法将value转成一个Collection集合,再用Iterator进行输出
- Map中entrySet()方法将Map.Entry转成一个Set集合,用Iterator遍历,再通Map.Entry进行key和value的分离后输出
-
Map.Entry
Map.Entry 本身是一个接口。此接口是定义在 Map 接口内部的。
实际上来讲,对于每一个存放到 Map 集合中的 key 和 value 都是将其变为了 Map.Entry 并且将 Map.Entry 保存在了 Map 集合之中。
常用方法:
K getKey() 的到key V getValue() 得到value
Collections 类(理解)
Collections 实际上是一个集合的操作类,与 Collection 接口没有任何的关系。
从实际考虑,使用此类操作并不是很方便,最好的做法就是使用各个接口的直接操作的方法完成。此类只是 一个集合的操作类。
分析 equals、hashCode 与内存泄露(理解)
-
重写 equals()方法时,必须遵守的准则
- 对称性:如果 x.equals(y)返回是“true”,那么 y.equals(x)也应该返回是“true”。
- 反射性:x.equals(x)必须返回是“true”。
- 类推性:如果 x.equals(y)返回是“true”,而且 y.equals(z)返回是“true”,那么 z.equals(x)也应该返回是“true”。
- 还有一致性:如果 x.equals(y)返回是“true”,只要 x 和 y 内容一直不变,不管你重复 x.equals(y)多少次,返回都是“true”。
- 任何情况下,x.equals(null),永远返回是“false”;x.equals(和 x 不同类型的对象)永远返回是“false”。
-
java.lnag.Object 中对 hashCode 的约定(很重要):
- 一致性:在一个应用程序执行期间,如果一个对象的 equals 方法做比较所用到的信息没有被修改的话,则对该对象调用 hashCode 方法多次,它必须始终如一地返回同一个整数。
- 如果两个对象根据 equals(Object o)方法是相等的,则调用这两个对象中任一对象的 hashCode 方法必须产生相同的整 数结果。
- 如果两个对象根据 equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的 hashCode 方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。(hashCode的到的整数结果不同,对象一定不同)
-
在 java 的集合中,判断两个对象是否相等的规则是:
(1)判断两个对象的 hashCode 是否相等如果不相等,认为两个对象也不相等,完毕如果相等,转入第二步骤。
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。后面会重点讲到这个问题。)
(2)判断两个对象用 equals 运算是否相等如果不相等,认为两个对象也不相等如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键) -
提示贴: 当一个对象被存进 HashSet 集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈希值与最初存储进 HashSet 集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用作为 的参数去 HashSet 集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet 集合中删除当前对象,从而 造成内存泄露。