L07-Java集合
Java集合框架
概述
一方面, 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中。
对于数组存储对象的弊端:
- 一旦创建,其长度不可变
- 真实的数组存放的对象的个数是不可知的
Java 集合类:可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
分类
Java 集合可分为 Collection 和 Map 两种体系。
Collection接口:
- Set:元素无序、不可重复的集合 — 类似高中的“集合”
- List:元素有序,可重复的集合 — “动态”数组
Map接口:具有映射关系“key-value对”的集合 — 类似于高中的“函数”
Collection接口继承树
Map接口继承树
Collection接口
概述
- Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
- JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。
- 在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 Java5 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
接口方法
size( )
返回集合中元素的个数。
add(Object obj)
向集合中添加一个元素。
addAll(Collection c)
将形参c中包含的所有元素添加到当前集合中。
isEmpty( )
判断集合是否为空。
clear( )
清空集合元素。
contains(Object obj)
判断此集合中是否包含指定的obj元素。
判断的依据:根据元素所在类的equals( )方法进行判断。
明确:如果存入集合中的元素是自定义类的对象,要求:自定义类要重写equals( )方法!
containsAll(Collection c)
判断当前集合中是否包含c中所有的元素。
retainAll(Collection c)
求当前集合与c中共有的元素,返回给当前集合。
remove(Object obj)
在当前元素中删除指定的obj元素。
removeAll(Collection c)
从当前集合中删除包含在c中的元素。
equals(Object obj)
判断两个集合中所有元素是否相同。
hashCode( )
返回此集合的哈希值。
toArray( )
将集合转化为数组。
toArray(T[] a)
将集合转化为数组。返回数组的运行时类型与指定数组的运行时类型相同。
iterator( )
返回在此集合的元素上进行迭代的迭代器。
1 | import java.util.ArrayList; |
遍历集合元素
Iterator接口遍历
概述
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
- 所有实现了Collection接口的集合类都有一个iterator( )方法,用以返回一个实现了Iterator接口的对象
- Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。
示例
1 | Iterator iterator = coll.iterator(); |
Iterator接口的方法
在调用it.next()
方法之前必须要调用it.hasNext()
进行检测。若不调用,且下一条记录无效,直接调用it.next()
会抛出NoSuchElementException
异常。
foreach循环遍历
概述
Java 5 提供了 foreach 循环迭代访问 Collection。
面试题
1 | public class TestFor { |
Collection子接口之一: List接口
概述
- List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- JDK API中List接口的实现类常用的有:ArrayList、LinkedList 和 Vector。
常用方法
List 集合里添加了一些根据索引来操作集合元素的方法:
void add(int index, Object ele)
boolean addAll(int index, Collection eles)
Object get(int index)
int indexOf(Object obj)
int lastIndexOf(Object obj)
Object remove(int index)
Object set(int index, Object ele)
List subList(;int fromIndex, int toIndex)
List实现类: ArrayList
- ArrayList 是 List 接口的典型实现类
- 本质上,ArrayList是对象引用的一个变长数组
- ArrayList 是线程不安全的,而 Vector 是线程安全的,即使为保证 List 集合线程安全,也不推荐使用Vector
- Arrays.asList(…) 方法返回的 List 集合既不是 ArrayList 实例,也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合
List实现类: LinkedList
- 对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
- 新增方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
List 实现类之三: Vector
- Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
- 在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。
- 新增方法:
void addElement(Object obj)
void insertElementAt(Object obj,int index)
void setElementAt(Object obj,int index)
void removeElement(Object obj)
void removeAllElements()
Collection子接口之二: Set接口
概述
- Set 接口是Collection的子接口,set接口没有提供额外的方法
- Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败
- Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals 方法
Set实现类之一: HashSet
特点
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。
HashSet 具有以下特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值决定该对象在 HashSet 中的存储位置。
HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
hashCode( ) 方法
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
- 对于存放在Set容器中的对象,对应的类一定要重写
equals()
和hashCode(Object obj)
方法,以实现对象相等规则。 - 重写 hashCode() 方法的基本原则
- 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值
- 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等
- 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值
Set实现类之二: LinkedHashSet
- LinkedHashSet 是 HashSet 的子类
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
- LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能
- LinkedHashSet 不允许集合元素重复
Set实现类之三: TreeSet
概述
- 向TreeSet中添加的元素必须是同一类型的
- 不允许元素重复
- 可以按照添加进集合中的元素的指定顺序遍历,例如String、包装类等默认按照从小到大的顺序遍历
- 当自定义类没有实现Comparable接口时,向TreeSet中添加自定义类对象时,会报ClassCastException异常
- TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
自然排序
自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序排列。
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。
实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。
Comparable 的典型实现:
- BigDecimal、BigInteger 以及所有的数值型对应的包装类:按它们对应的数值大小进行比较
- Character:按字符的 unicode值来进行比较
- Boolean:true 对应的包装类实例大于 false 对应的包装类实例
- String:按字符串中字符的 unicode 值进行比较
- Date、Time:后边的时间、日期比前面的时间、日期大
向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象
对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值
当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过 equals() 方法比较返回 true,则通过 compareTo(Object obj) 方法比较应返回 0
定制排序
TreeSet 的自然排序是根据集合元素的大小,进行元素升序排列。如果需要定制排序,比如降序排列,可通过Comparator接口的帮助,需要重写compare(T o1,T o2)
方法。
- 利用
int compare(T o1,T o2)
方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。 - 要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。
- 使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
练习
- 定义一个Employee类,该类包含:private成员变量name, age, birthday,其中 birthday 为 MyDate 类的对象;并为每一个属性定义 getter, setter 方法;并重写 toString 方法输出 name, age, birthday。
- MyDate类包含:private成员变量month, day, year;并为每一个属性定义 getter, setter 方法;
- 创建该类的 5 个对象,并把这些对象放入 TreeSet 集合中(下一章:TreeSet 需使用泛型来定义)。分别按以下两种方式对集合中的元素进行排序,并遍历输出:
- 使 Employee 实现 Comparable 接口,并按 name 排序
- 创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序。
提示:Employee类是否需要重写equals()方法?MyDate类呢?——需要
1 | import java.util.Objects; |
1 | import java.util.Objects; |
1 | import java.util.Objects; |
1 | import java.util.Comparator; |
Map接口
概述
Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
- Map 中的 key 和 value 都可以是任何引用类型的数据
- Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写
hashCode()
和equals()
方法 - 常用String类作为Map的“键”
- key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value
Map体系继承树
Map常用方法
添加、删除操作:
Object put(Object key,Object value)
Object remove(Object key)
void putAll(Map t)
void clear()
元视图操作的方法:
Set keySet()
Collection values()
Set entrySet()
元素查询的操作:
Object get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
boolean equals(Object obj)
Map实现类: HashMap
- Map接口的常用实现类:HashMap、TreeMap和Properties
- HashMap是 Map 接口使用频率最高的实现类
- 允许使用null键和null值,与HashSet一样,不保证映射的顺序
- HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等
- HashMap 判断两个 value 相等的标准是:两个 value 通过 equals() 方法返回 true
如何遍历Map
1 | import java.util.*; |
Map实现类: LinkedHashMap
- LinkedHashMap 是 HashMap 的子类
- 与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致
Map实现类: TreeMap
TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态
TreeMap 的 Key 的排序:
- 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
- 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
TreeMap判断两个key相等的标准:两个key通过
compareTo()
方法或者compare()
方法返回0若使用自定义类作为TreeMap的key,所属类需要重写
equals()
和hashCode()
方法,且equals()
方法返回true时,compareTo()
方法应返回0
Map实现类: Hashtable
- Hashtable是个古老的 Map 实现类,线程安全
- 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
- 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
- Hashtable判断两个key相等、两个value相等的标准,与hashMap一致
子类Properties
- Properties 类是 Hashtable 的子类,该对象用于处理属性文件
- 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
- 存取数据时,建议使用
setProperty(String key,String value)
方法和getProperty(String key)
方法
1 | import java.io.File; |
Collections工具类
Collections 是一个操作 Set、List 和 Map 等集合的工具类,Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
常用方法
排序操作:(均为static方法)
reverse(List)
:反转 List 中元素的顺序shuffle(List)
:对 List 集合元素进行随机排序sort(List)
:根据元素的自然顺序对指定 List 集合元素按升序排序sort(List,Comparator)
:根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List,int,int)
:将指定 list 集合中的 i 处元素和 j 处元素进行交换
查找、替换:
Object max(Collection)
:根据元素的自然顺序,返回给定集合中的最大元素Object max(Collection,Comparator)
:根据 Comparator 指定的顺序,返回给定集合中的最大元素Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object)
:返回指定集合中指定元素的出现次数void copy(List dest,List src)
:将src中的内容复制到dest中boolean replaceAll(List list,Object oldVal,Object newVal)
:使用新值替换 List 对象的所有旧值
同步控制
Collections 类中提供了多个 synchronizedXxx()
方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
Enumeration
Enumeration 接口是 Iterator 迭代器的 “古老版本”
1 | Enumeration stringEnum = new StringTokenizer("a-b*c-d-e-g", "-"); |
面试题
- Collection 和 Collections 的区别
答:Collection是集合类的上级接口,继承他的接口主要有 Set 和 List。Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
- Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别
答:Set里的元素是不能重复的,那么用iterator()
方法来区分重复与否。equals()
是判读两个Set是否相等,equals()
和==方法决定引用值是否指向同一对象,equals()
在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。
- List, Set, Map是否继承自Collection接口
答: List,Set是,Map不是
- 两个对象值相同
(x.equals(y) == true)
,但却可有不同的hash code,这句话对不对
答:不对,有相同的hash code
- 说出ArrayList,Vector,LinkedList的存储性能和特性
答:ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
- HashMap和Hashtable的区别
答:HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。
自己为它的方法实现同步,而 HashMap 就必须为之提供外同步。HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。 HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要
Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异
ArrayList和Vector的区别,HashMap和Hashtable的区别
答:就ArrayList与Vector主要从二方面来说:- 同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的
- 数据增长:当需要增长时,Vector默认增长为原来一倍,而ArrayList却是原来的一半
就HashMap与HashTable主要从三方面来说:
- 历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现
- 同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的
- 值:只有HashMap可以让你将空值作为一个表的条目的key或value