10.1集合类概述
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java 提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。Java 所有的集合类都位于 java.util 包下,提供了一个表示和操作对象集合的统一构架,包含大量集合接口,以及这些接口的实现类和操作它们的算法。
集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量),而集合里只能保存对象(实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象)。
Java 集合类型分为 Collection 和 Map,它们是 Java 集合的根接口,这两个接口又包含了一些子接口或实现类。图 1 和图 2 分别为 Collection 和 Map 的子接口及其实现类。
对于 Set、List、Queue 和 Map 这 4 种集合,Java 最常用的实现类分别是 HashSet、TreeSet、ArrayList、ArrayDueue、LinkedList 和 HashMap、TreeMap 等。表 2 介绍了集合中这些常用的实现类。
10.2Collection接口
Collection 接口是 List、Set 和 Queue 接口的父接口,通常情况下不被直接使用。Collection 接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作。定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
注意:以上方法完全来自于 Java API 文档,读者可自行参考 API 文档来查阅这些方法的详细信息。读者无需硬性记忆这些方法,可以和实际生活结合记忆。集合类就像容器,现实生活中容器的功能,就是添加对象、删除对象、清空容器和判断容器是否为空等,集合类为这些功能都提供了对应的方法。
public static void main(String[] args) {
ArrayList list1 = new ArrayList(); // 创建集合 list1
ArrayList list2 = new ArrayList(); // 创建集合 list2
list1.add("one"); // 向 list1 添加一个元素
list1.add("two"); // 向 list1 添加一个元素
list2.addAll(list1); // 将 list1 的所有元素添加到 list2
list2.add("three"); // 向 list2 添加一个元素
System.out.println("list2 集合中的元素如下:");
Iterator it1 = list2.iterator();
while (it1.hasNext()) {
System.out.print(it1.next() + "、");
}
}
public static void main(String[] args) { ArrayList list1 = new ArrayList(); // 创建集合 list1 ArrayList list2 = new ArrayList(); // 创建集合 list2 list1.add("one"); list1.add("two"); list1.add("three"); System.out.println("list1 集合中的元素数量:" + list1.size()); // 输出list1中的元素数量 list2.add("two"); list2.add("four"); list2.add("six"); System.out.println("list2 集合中的元素数量:" + list2.size()); // 输出list2中的元素数量 list2.remove(2); // 删除第 3 个元素 System.out.println("\nremoveAll() 方法之后 list2 集合中的元素数量:" + list2.size()); System.out.println("list2 集合中的元素如下:"); Iterator it1 = list2.iterator(); while (it1.hasNext()) { System.out.print(it1.next() + "、"); } list1.removeAll(list2); System.out.println("\nremoveAll() 方法之后 list1 集合中的元素数量:" + list1.size()); System.out.println("list1 集合中的元素如下:"); Iterator it2 = list1.iterator(); while (it2.hasNext()) { System.out.print(it2.next() + "、");
注意:retainAll( ) 方法的作用与 removeAll( ) 方法相反,即保留两个集合中相同的元素,其他全部删除。
10.3List集合
List 是一个有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List 集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List 集合默认按元素的添加顺序设置元素的索引,第一个添加到 List 集合中的元素的索引为 0,第二个为 1,依此类推。
10.3.1List接口
10.3.2List接口的实现类
注意:当调用 List 的 set(int index, Object element) 方法来改变 List 集合指定索引处的元素时,指定的索引必须是 List 集合的有效索引。例如集合长度为 4,就不能指定替换索引为 4 处的元素,也就是说这个方法不会改变 List 集合的长度。
public class Test { public static void main(String[] args) { Product pd1 = new Product(4, "木糖醇", 10); Product pd2 = new Product(5, "洗发水", 12); Product pd3 = new Product(3, "热水壶", 49); List list = new ArrayList(); // 创建集合 list.add(pd1); list.add(pd2); list.add(pd3); System.out.println("*************** 商品信息 ***************"); for (int i = 0; i < list.size(); i++) { // 循环遍历集合,输出集合元素 Product product = (Product) list.get(i); System.out.println(product); }
public static void main(String[] args) { List list = new ArrayList(); list.add("One"); list.add("|"); list.add("Two"); list.add("|"); list.add("Three"); list.add("|"); list.add("Four"); System.out.println("list 集合中的元素数量:" + list.size()); System.out.println("list 集合中的元素如下:"); Iterator it = list.iterator(); while (it.hasNext()) { System.out.print(it.next() + "、"); } System.out.println("\n在 list 集合中'丨'第一次出现的位置是:" + list.indexOf("|")); System.out.println("在 list 集合中'丨'最后一次出现的位置是:" + list.lastIndexOf("|")); }
public static void main(String[] args) { List list = new ArrayList(); list.add("One"); list.add("Two"); list.add("Three"); list.add("Four"); list.add("Five"); list.add("Six"); list.add("Seven"); System.out.println("list 集合中的元素数量:" + list.size()); System.out.println("list 集合中的元素如下:"); Iterator it = list.iterator(); while (it.hasNext()) { System.out.print(it.next() + "、"); } List sublist = new ArrayList(); sublist = list.subList(2, 5); // 从list集合中截取索引2~5的元素,保存到sublist集合中 System.out.println("\nsublist 集合中元素数量:" + sublist.size()); System.out.println("sublist 集合中的元素如下:"); it = sublist.iterator(); while (it.hasNext()) { System.out.print(it.next() + "、"); } }
10.3.3Iterator
public class Test { public static void main(String[] args) { LinkedList<String> products = new LinkedList<String>(); // 创建集合对象 String p1 = new String("六角螺母"); String p2 = new String("10A 电缆线"); String p3 = new String("5M 卷尺"); String p4 = new String("4CM 原木方板"); products.add(p1); // 将 p1 对象添加到 LinkedList 集合中 products.add(p2); // 将 p2 对象添加到 LinkedList 集合中 products.add(p3); // 将 p3 对象添加到 LinkedList 集合中 products.add(p4); // 将 p4 对象添加到 LinkedList 集合中 String p5 = new String("标准文件夹小柜"); products.addLast(p5); // 向集合的末尾添加p5对象 System.out.print("*************** 商品信息 ***************"); System.out.println("\n目前商品有:"); for (int i = 0; i < products.size(); i++) { System.out.print(products.get(i) + "\t"); } System.out.println("\n第一个商品的名称为:" + products.getFirst()); System.out.println("最后一个商品的名称为:" + products.getLast()); products.removeLast(); // 删除最后一个元素 System.out.println("删除最后的元素,目前商品有:"); for (int i = 0; i < products.size(); i++) { System.out.print(products.get(i) + "\t"); }
10.4Set集合
Set 集合类似于一个罐子,程序可以依次把多个对象“丢进”Set 集合,而 Set 集合通常不能记住元素的添加顺序。也就是说 Set 集合中的对象不按特定的方式排序,只是简单地把对象加入集合。Set 集合中不能包含重复的对象,并且最多只允许包含一个 null 元素。
10.4.1Set接口
HashSet 具有以下特点:
不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
HashSet 不是同步的,如果多个线程同时访问或修改一个 HashSet,则必须通过代码来保证其同步。
集合元素值可以是 null。当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据该 hashCode 值决定该对象在 HashSet 中的存储位置。如果有两个元素通过 equals() 方法比较返回的结果为 true,但它们的 hashCode 不相等,HashSet 将会把它们存储在不同的位置,依然可以添加成功。
也就是说,两个对象的 hashCode 值相等且通过 equals() 方法比较返回结果为 true,则 HashSet 集合认为两个元素相等。
在 HashSet 类中实现了 Collection 接口中的所有方法。HashSet 类的常用构造方法重载形式如下。
HashSet():构造一个新的空的 Set 集合。
HashSet(Collection<? extends E>c):构造一个包含指定 Collection 集合元素的新 Set 集合。其中,“< >”中的 extends 表示 HashSet 的父类,即指明该 Set 集合中存放的集合元素类型。c 表示其中的元素将被存放在此 Set 集合中。
public static void main(String[] args) { HashSet<String> courseSet = new HashSet<String>(); // 创建一个空的 Set 集合 String course1 = new String("Java入门教程"); String course2 = new String("Python基础教程"); String course3 = new String("C语言学习教程"); String course4 = new String("Golang入门教程"); courseSet.add(course1); // 将 course1 存储到 Set 集合中 courseSet.add(course2); // 将 course2 存储到 Set 集合中 courseSet.add(course3); // 将 course3 存储到 Set 集合中 courseSet.add(course4); // 将 course4 存储到 Set 集合中 System.out.println("C语言中文网教程有:"); Iterator<String> it = courseSet.iterator(); while (it.hasNext()) { System.out.println("《" + (String) it.next() + "》"); // 输出 Set 集合中的元素 } System.out.println("有" + courseSet.size() + "套精彩教程!"); }
注意:在以上示例中,如果再向 CourseSet 集合中再添加一个名称为“Java入门教程”的 String 对象,则输出的结果与上述执行结果相同。也就是说,如果向 Set 集合中添加两个相同的元素,则后添加的会覆盖前面添加的元素,即在 Set 集合中不会出现相同的元素。
10.4.2Set接口的实现类
public class Test08 { public static void main(String[] args) { TreeSet<Double> scores = new TreeSet<Double>(); // 创建 TreeSet 集合 Scanner input = new Scanner(System.in); System.out.println("------------学生成绩管理系统-------------"); for (int i = 0; i < 5; i++) { System.out.println("第" + (i + 1) + "个学生成绩:"); double score = input.nextDouble(); // 将学生成绩转换为Double类型,添加到TreeSet集合中 scores.add(Double.valueOf(score)); } Iterator<Double> it = scores.iterator(); // 创建 Iterator 对象 System.out.println("学生成绩从低到高的排序为:"); while (it.hasNext()) { System.out.print(it.next() + "\t"); } System.out.println("\n请输入要查询的成绩:"); double searchScore = input.nextDouble(); if (scores.contains(searchScore)) { System.out.println("成绩为: " + searchScore + " 的学生存在!"); } else { System.out.println("成绩为: " + searchScore + " 的学生不存在!"); } // 查询不及格的学生成绩 SortedSet<Double> score1 = scores.headSet(60.0); System.out.println("\n不及格的成绩有:"); for (int i = 0; i < score1.toArray().length; i++) { System.out.print(score1.toArray()[i] + "\t"); } // 查询90分以上的学生成绩 SortedSet<Double> score2 = scores.tailSet(90.0); System.out.println("\n90 分以上的成绩有:"); for (int i = 0; i < score2.toArray().length; i++) { System.out.print(score2.toArray()[i] + "\t"); } } }
注意:在使用自然排序时只能向 TreeSet 集合中添加相同数据类型的对象,否则会抛出 ClassCastException 异常。如果向 TreeSet 集合中添加了一个 Double 类型的对象,则后面只能添加 Double 对象,不能再添加其他类型的对象,例如 String 对象等。
10.5Map集合
Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键(key)对象和一个值(value)对象。用于保存具有映射关系的数据。
Map 集合里保存着两组值,一组值用于保存 Map 里的 key,另外一组值用于保存 Map 里的 value,key 和 value 都可以是任何引用类型的数据。Map 的 key 不允许重复,value 可以重复,即同一个 Map 对象的任何两个 key 通过 equals 方法比较总是返回 false。
Map 中的 key 和 value 之间存在单向一对一关系,即通过指定的 key,总能找到唯一的、确定的 value。从 Map 中取出数据时,只要给出指定的 key,就可以取出对应的 value。
Map 接口主要有两个实现类:HashMap 类和 TreeMap 类。其中,HashMap 类按哈希算法来存取键对象,而 TreeMap 类可以对键对象进行排序。
10.5.1Map接口
Map 集合最典型的用法就是成对地添加、删除 key-value 对,接下来即可判断该 Map 中是否包含指定 key,也可以通过 Map 提供的 keySet() 方法获取所有 key 组成的集合,进而遍历 Map 中所有的 key-value 对。下面程序示范了 Map 的基本功能。
10.5.2Map接口的实现类public class Test09 { public static void main(String[] args) { HashMap users = new HashMap(); users.put("11", "张浩太"); // 将学生信息键值对存储到Map中 users.put("22", "刘思诚"); users.put("33", "王强文"); users.put("44", "李国量"); users.put("55", "王路路"); System.out.println("******** 学生列表 ********"); Iterator it = users.keySet().iterator(); while (it.hasNext()) { // 遍历 Map Object key = it.next(); Object val = users.get(key); System.out.println("学号:" + key + ",姓名:" + val); } Scanner input = new Scanner(System.in); System.out.println("请输入要删除的学号:"); int num = input.nextInt(); if (users.containsKey(String.valueOf(num))) { // 判断是否包含指定键 users.remove(String.valueOf(num)); // 如果包含就删除 } else { System.out.println("该学生不存在!"); } System.out.println("******** 学生列表 ********"); it = users.keySet().iterator(); while (it.hasNext()) { Object key = it.next(); Object val = users.get(key); System.out.println("学号:" + key + ",姓名:" + val); } } }
TreeMap 类的使用方法与 HashMap 类相同,唯一不同的是 TreeMap 类可以对键对象进行排序,这里不再赘述。
10.6集合的使用场合
Map 集合的遍历与 List 和 Set 集合不同。Map 有两组值,因此遍历时可以只遍历值的集合,也可以只遍历键的集合,也可以同时遍历。Map 以及实现 Map 的接口类(如 HashMap、TreeMap、LinkedHashMap、Hashtable 等)都可以用以下几种方式遍历。
1)在 for 循环中使用 entries 实现 Map 的遍历(最常见和最常用的)。
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
for (Map.Entry<String, String> entry : map.entrySet()) {
String mapKey = entry.getKey();
String mapValue = entry.getValue();
System.out.println(mapKey + ":" + mapValue);
}
}
2)使用 for-each 循环遍历 key 或者 values,一般适用于只需要 Map 中的 key 或者 value 时使用。性能上比 entrySet 较好。
Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
// 打印键集合
for (String key : map.keySet()) {
System.out.println(key);
}
// 打印值集合
for (String value : map.values()) {
System.out.println(value);
}
3)使用迭代器(Iterator)遍历
Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Entry<String, String> entry = entries.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + ":" + value);
}
4)通过键找值遍历,这种方式的效率比较低,因为本身从键取值是耗时的操作。
for(String key : map.keySet()){
String value = map.get(key);
System.out.println(key+":"+value);
}