为了在程序中可以保存数目不确定的对象,Java提供了一系列特殊类,这些类可以存储任意类型的对象,并且长度可变。这些类统称为集合。
集合按照其存储结构可以分为两大类,单列集合Collection和双列集合Map,这两种集合的特点具体如下:
List接口继承自Collection接口,是单列集合的一个重要分支。List集合允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引访问List集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引操作集合的特有方法。 List接口常用方法如下表。
ArrayList是List接口的一个实现类,它是程序中最常见的一种集合。在ArrayList内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。
ArrayList集合中大部分方法都是从父类Collection和List继承过来的,其中add()方法和get()方法分别用于实现元素的存入和取出。接下来通过一个案例学习ArrayList集合的元素存取。
1 import java.util.*;
2 public class Example01 {
3 public static void main(String[] args) {
4 ArrayList list = new ArrayList(); // 创建ArrayList集5合
5 list.add("张三"); // 向集合中添加元素
6 list.add("李四");
7 list.add("王五");
8 list.add("赵六");
9 // 获取集合中元素的个数
10 System.out.println("集合的长度:" + list.size());
11 // 取出并打印指定位置的元素
12 System.out.println("第2个元素是:" + list.get(1));
13 }
14 }
上述代码中,第4行代码创建了一个list对象,第5~8行代码使用list对象调用add(Object o)方法向ArrayList集合中添加了4个元素,第10行代码使用list对象调用size()方法获取集合中元素个数并输出打印,第12行代码使用list对象调用ArrayList的get(int index)方法取出指定索引位置的元素并输出打印。从运行结果可以看出,索引位置为1的元素是集合中的第二个元素,这就说明集合和数组一样,索引的取值范围是从0开始的,最后一个索引是size-1,在访问元素时一定要注意索引不可超出此范围,否则会抛出角标越界异常IndexOutOfBoundsException。
由于ArrayList集合的底层是使用一个数组来保存元素,在增加或删除指定位置的元素时,会导致创建新的数组,效率比较低,因此不适合做大量的增删操作。因为这种数组的结构允许程序通过索引的方式来访问元素,所以使用ArrayList集合查找元素很便捷。
Set接口和List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set接口主要有两个实现类,分别是HashSet和TreeSet。其中,HashSet是根据对象的哈希值来确定元素在集合中的存储位置,具有良好的存取和查找性能。TreeSet则是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序。
HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的。接下来通过一个案例演示HashSet集合的用法。
1 import java.util.*;
2 public class Example07 {
3 public static void main(String[] args) {
4 HashSet set = new HashSet(); // 创建HashSet集合
5 set.add("张三"); // 向该Set集合中添加字符串
6 set.add("李四");
7 set.add("王五");
8 set.add("李四"); // 向该Set集合中添加重复元素
9 Iterator it = set.iterator(); // 获取Iterator对象
10 while (it.hasNext()) { // 通过while循环,判断集合中是否有元素
11 Object obj = it.next();// 如果有元素,就通过迭代器的next()方法获取元素
12 System.out.println(obj);
13 }
14 }
15 }
上述代码中,第4-8行代码是声明了一个HashSet集合并通过add()方法向HashSet集合依次添加了四个字符串;第9行代码是声明了一个迭代器对象it,第10~13行代码是通过Iterator迭代器遍历所有的元素并输出。从打印结果可以看出,取出元素的顺序与添加元素的顺序并不一致,并且重复存入的字符串对象“李四”被去除了,只添加了一次。
HashSet集合之所以能确保不出现重复的元素,是因为它在存入元素时做了很多工作。当调用HashSet集合的add()方法存入元素时,首先调用当前存入对象的hashCode()方法获得对象的哈希值,然后根据对象的哈希值计算出一个存储位置。如果该位置上没有元素,则直接将元素存入,如果该位置上有元素存在,则会调用equals()方法让当前存入的元素依次和该位置上的元素进行比较,如果返回的结果为false就将该元素存入集合,返回的结果为true则说明有重复元素,就将该元素舍弃。
1 import java.util.*;
2 class Student {
3 String id;
4 String name;
5 public Student(String id,String name) { // 创建构造方法
6 this.id=id;
7 this.name = name;
8 }
9 public String toString() { // 重写toString()方法
10 return id+":"+name;
11 }
12 }
13 public class Example08 {
14 public static void main(String[] args) {
15 HashSet hs = new HashSet(); // 创建HashSet集合
16 Student stu1 = new Student("1", "张三"); // 创建Student对象
17 Student stu2 = new Student("2", "李四");
18 Student stu3 = new Student("2", "李四");
19 hs.add(stu1);
20 hs.add(stu2);
21 hs.add(stu3);
22 System.out.println(hs);
23 }
24 }
上述代码中,第15行代码声明了一个HashSet集合,第16-18行代码分别声明了3个Student对象,第19~22行代码是分别将3个Student对象存入HashSet集合中并输出。图6-18所示的运行结果中出现了两个相同的学生信息“2:李四”,这样的学生信息应该被视为重复元素,不允许同时出现在HashSet集合中。之所以没有去掉这样的重复元素,是因为在定义Student类时没有重写hashCode()和equals()方法。
HashSet集合存储的元素是无序的,如果想让元素的存取顺序一致,可以使用Java中提供的LinkedHashSet类,LinkedHashSet类是HashSet的子类,与LinkedList一样,它也使用双向链表来维护内部元素的关系。
Map接口是一种双列集合,它的每个元素都包含一个键对象Key和值对象Value,键和值对象之间存在一种对应关系,称为映射。从Map集合中访问元素时,只要指定了Key,就能找到对应的Value。
HashMap集合是Map接口的一个实现类,用于存储键值映射关系,但HashMap集合没有重复的键并且键值无序
1 import java.util.*;
2 public class Example14 {
3 public static void main(String[] args) {
4 HashMap map = new HashMap(); // 创建Map对象
5 map.put("1", "张三"); // 存储键和值
6 map.put("2", "李四");
7 map.put("3", "王五");
8 System.out.println("1:" + map.get("1")); // 根据键获取值
9 System.out.println("2:" + map.get("2"));
10 System.out.println("3:" + map.get("3"));
11 }
12 }
上述代码中,第4-7行代码声明了一个HashMap集合并通过Map的put(Object key,Object value)方法向集合中加入3个元素,第8~10行代码是通过Map的get(Object key)方法获取与键对应的值。
Map集合中的键具有唯一性,现在向Map集合中存储一个相同的键看看会出现什么情况,在第7行代码下面增加一行代码,如下所示:
map.put(“3”, “赵六”);
修改之后,再次运行程序,运行结果如下图。
Map中仍然只有3个元素,只是第二次添加的值“赵六”覆盖了原来的值“王五”,这也证实了Map中的键必须是唯一的,不能重复,如果存储了相同的键,后存储的值则会覆盖原有的值,简而言之就是:键相同,值覆盖。
在程序开发中,经常需要取出Map中所有的键和值,那么如何遍历Map中所有的键值对呢?有两种方式可以实现,第一种方式就是先遍历Map集合中所有的键,再根据键获取相应的值。
接下来通过一个案例来演示先遍历Map集合中所有的键,再根据键获取相应的值。
1 import java.util.*;
2 public class Example15 {
3 public static void main(String[] args) {
4 HashMap map = new HashMap(); // 创建Map集合
5 map.put("1", "张三"); // 存储键和值
6 map.put("2", "李四");
7 map.put("3", "王五");
8 Set keySet = map.keySet(); // 获取键的集合
9 Iterator it = keySet.iterator(); // 迭代键的集合
10 while (it.hasNext()) {
11 Object key = it.next();
12 Object value = map.get(key); // 获取每个键所对应的值
13 System.out.println(key + ":" + value);
14 }
15 }
16 }
上述代码中,第8~14行代码是第一种遍历Map的方式。首先调用Map对象的KeySet()方法,获得存储Map中所有键的Set集合,然后通过Iterator迭代Set集合的每一个元素,即每一个键,最后通过调用get(String key)方法,根据键获取对应的值。
Map中,还提供了一些操作集合的常用方法,例如,values()方法用于得到map实例中所有的value,返回值类型为Collection;size()方法获取map集合类的大小;containsKey()方法用于判断是否包含传入的键;containsValue()方法用于判断是否包含传入的值;remove()方法用于根据key移除map中的与该key对应的value等。
HashMap集合存储的元素的键值是无序的和不可重复的,为了对集合中的元素的键值进行排序,Map接口提供了另一个可以对集合中元素键值进行排序的类TreeMap。
1 import java.util.Iterator;
2 import java.util.Set;
3 import java.util.TreeMap;
4 public class Example19 {
5 public static void main(String[] args) {
6 TreeMap map = new TreeMap(); // 创建Map集合
7 map.put(3, "李四");// 存储键和值
8 map.put(2, "王五");
9 map.put(4, "赵六");
10 map.put(3, "张三");
11 Set keySet = map.keySet();
12 Iterator it = keySet.iterator();
13 while (it.hasNext()) {
14 Object key = it.next();
15 Object value = map.get(key); // 获取每个键所对应的值
16 System.out.println(key+":"+value);
17 }
18 }
19 }
上述代码中,第6-10行代码是通过Map的put(Object key,Object value)方法向集合中加入4个元素;第11~17行代码是使用迭代器遍历集合中的元素并通过元素的键获取对应的值,并打印。从运行结果可以看出,添加的元素已经自动排序,并且键值重复存入的整数3只有一个,只是后边添加的值“张三”覆盖了原来的值“李四”。这也证实了TreeMap中的键必须是唯一的,不能重复并且有序,如果存储了相同的键,后存储的值则会覆盖原有的值。
TreeMap集合之所以可以对添加的元素的键值进行排序,其实现同TreeSet一样,TreeMap的排序也分自然排序与比较排序两种。接下来通过一个案例演示比较排序法实现按键值排序,在该案例中,键是自定义类值是String类。
1 import java.util.*;
2 class Student {
3 private String name;
4 private int age;
5 public String getName() {
6 return name;
7 }
8 public void setName(String name) {
9 this.name = name;
10 }
11 public int getAge() {
12 return age;
13 }
14 public void setAge(int age) {
15 this.age = age;
16 }
17 public Student(String name, int age) {
18 super();
19 this.name = name;
20 this.age = age;
21 }
22 @Override
23 public String toString() {
24 return "Student [name=" + name + ", age=" + age + "]";
25 }
26 }
27 public class Example20 {
28 public static void main(String[] args) {
29 TreeMap tm = new TreeMap(new Comparator<Student>() {
30 @Override
31 public int compare(Student s1, Student s2) {
32 int num = s1.getName().compareTo(s2.getName());//按照姓名比较
33 return num == 0 ? num:s1.getAge() - s2.getAge();
34 }
35 });
36 tm.put(new Student("张三", 23), "北京");
37 tm.put(new Student("李四", 13), "上海");
38 tm.put(new Student("赵六", 43), "深圳");
39 tm.put(new Student("王五", 33), "广州");
40 Set keySet = tm.keySet();
41 Iterator it = keySet.iterator();
42 while (it.hasNext()) {
43 Object key = it.next();
44 Object value = tm.get(key); // 获取每个键所对应的值
45 System.out.println(key+":"+value);
46 }
47 }
48 }
上述代码中,第2-26行代码定义了一个Student类;第29-35行代码定义了一个TreeMap集合,并在该集合中通过匿名内部类的方式实现了Comparator接口,然后重写了compare()方法,在compare()方法中通过三目运算符的方式自定义了排序方式为先按照年龄排序,年龄相同再按照姓名排序。第36~46行代码是通过Map的put(Object key,Object value)方法向集合中加入4个键为Student对象值为String类型的元素,并使用迭代器将集合中元素打印输出。
Map接口中还有一个实现类Hashtable,它和HashMap十分相似,区别在于Hashtable是线程安全的。Hashtable存取元素时速度很慢,目前基本上被HashMap类所取代,但Hashtable类有一个子类Properties在实际应用中非常重要。
Properties主要用来存储字符串类型的键和值,在实际开发中,经常使用Properties集合来存取应用的配置项。假设有一个文本编辑工具,要求默认背景色是红色,字体大小为14px,语言为中文,其配置项如下面的代码:
Backgroup-color = red
Font-size = 14px
Language = chinese
在程序中可以使用Properties集合对这些配置项进行存取,接下来通过一个案例学习Properties集合的使用。
1 import java.util.*;
2 public class Example21 {
3 public static void main(String[] args) {
4 Properties p=new Properties(); // 创建Properties对象
5 p.setProperty("Backgroup-color", "red");
6 p.setProperty("Font-size", "14px");
7 p.setProperty("Language", "chinese");
8 Enumeration names = p.propertyNames();//获取Enumeration对象所有键枚举
9 while(names.hasMoreElements()){ // 循环遍历所有的键
10 String key=(String) names.nextElement();
11 String value=p.getProperty(key); // 获取对应键的值
12 System.out.println(key+" = "+value);
13 }
14 }
15 }
上述代码的Properties类中,针对字符串的存取提供了两个专用的方法setProperty()和getProperty()。setProperty()方法用于将配置项的键和值添加到Properties集合当中。在第8行代码中通过调用Properties的propertyNames()方法得到一个包含所有键的Enumeration对象,然后在遍历所有的键时,通过调用getProperty()方法获得键所对应的值。