企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 集合概述 在前面的章节中介绍过在程序中可以通过数组来保存多个对象,但在某些情况下无法确定到底需要保存多少个对象,此时数组将不再适用,因为数组的长度不可变。例如,要保存一个学校的学生信息,由于不停有新生来报道,同时也有学员毕业离开学校,这时学生的数目很难确定。为了保存这些数目不确定的对象,JDK 中提供了一系列特殊的类,这些类可以存储任意类型的对象,并且长度可变,统称为集合。这些类都位于java.util包中,在使用时一定要注意导包的问题,否则会出现异常。集合按照其存储结构可以分为两大类,即单列集合Collection和双列集合Map,这两种集合的特点具体如下。 - Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是List和Set。其中,List的特点是元素有序、元素可重复。Set的特点是元素无序并且不可重复。List接口的主要实现类有ArrayList和LinkedList,Set接口的主要实现类有HashSet和TreeSet。 - Map:双列集合类的根接口,用于存储具有键(Key)、值(Value)映射关系的元素,每个元素都包含一对键值,在使用Map集合时可以通过指定的Key找到对应的Value,例如根据一个学生的学号就可以找到对应的学生。Map接口的主要实现类有HashMap和TreeMap。 从上面的描述可以看出JDK中提供了丰富的集合类库,为了便于初学者进行系统地学习,接下来通过一张图来描述整个集合类的继承体系。 ![](http://47.107.171.232/easily-j/images/20190107/a3aeb3c0-0123-4617-8e85-df6b8892b8fe.png) 图中列出了程序中常用的一些集合类,其中,虚线框里填写的都是接口类型,而实线框里填写的都是具体的实现类。 ## Collection接口 Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。 Collection接口的方法 ![](http://47.107.171.232/easily-j/images/20190107/d132d419-9050-4ed5-bc3b-6008f67f11ff.png) ## List接口 #### List接口简介 List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。 List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如表所示。 ![](http://47.107.171.232/easily-j/images/20190107/14e9d8a5-79eb-41f0-aff0-239269d17796.png) 表中列举了List集合中的常用方法,所有的List实现类都可以通过调用这些方法来对集合元素进行操作。 #### ArrayList集合 ArrayList是List接口的一个实现类,它是程序中最常见的一种集合。在ArrayList内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。 ArrayList集合中大部分方法都是从父类Collection和List继承过来的,其中add()方法和get()方法用于实现元素的存取,接下来通过一个案例来学习ArrayList集合如何存取元素,如例所示。 ```java import java.util.ArrayList; public class Example { public static void main(String[] args) { ArrayList list = new ArrayList(); // 创建ArrayList 集合 list.add("stu1"); // 向集合中添加元素 list.add("stu2"); list.add("stu3"); list.add("stu4"); System.out.println("集合的长度: " + list.size()); // 获取集合中元素的个数 System.out.println("第2个元素是: " + list.get(1)); // 取出并打印指定位置的元素 } } ``` 运行结果: ``` 集合的长度: 4 第2个元素是: stu2 ``` 例中,首先调用add(Object o)方法向ArrayList集合添加了4个元素,然后调用size()方法获取集合中元素个数,最后通过调用ArrayList的get(int index)方法取出指定索引位置的元素。从运行结果可以看出,索引位置为1的元素是集合中的第二个元素,这就说明集合和数组一样,索引的取值范围是从0开始的,最后一个索引是size-1,在访问元素时一定要注意索引不可超出此范围,否则会抛出角标越界异常IndexOutOfBoundsException。 由于ArrayList集合的底层是使用一个数组来保存元素,在增加或删除指定位置的元素时,会导致创建新的数组,效率比较低,因此不适合做大量的增删操作。但这种数组的结构允许程序通过索引的方式来访问元素,因此使用ArrayList集合查找元素很便捷。 #### LinkedList集合 ArrayList集合在查询元素时速度很快,但在增删元素时效率较低,为了克服这种局限性,可以使用List接口的另一个实现类LinkedList。该集合内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。当插入一个新元素时,只需要修改元素之间的这种引用关系即可,删除一个节点也是如此。正因为这样的存储结构,所以LinkedList集合对于元素的增删操作具有很高的效率,LinkedList集合添加元素和删除元素的过程如图所示。 ![](http://47.107.171.232/easily-j/images/20190107/a14986ea-1546-48a6-8be7-1322798470fe.png) 图中,通过两张图描述了LinkedList集合新增元素和删除元素的过程。其中,左图为新增一个元素,图中的元素1和元素2在集合中彼此为前后关系,在它们之间新增一个元素时,只需要让元素1记住它后面的元素是新元素,让元素2记住它前面的元素为新元素就可以了。右图为删除元素,要想删除元素1与元素2之间的元素3,只需要让元素1与元素2变成前后关系就可以了。 LinkedList集合除了具备增删元素效率高的特点,还专门针对元素的增删操作定义了一些特有的方法,如表所示。 ![](http://47.107.171.232/easily-j/images/20190107/d1626242-3fac-49a5-9709-4907f7ceddf1.png) 表中,列出的方法主要针对集合中的元素进行增加、删除和获取操作,接下来通过一个案例来学习这些方法的使用,如例所示。 ```java import java.util.LinkedList; public class Example { public static void main(String[] args) { LinkedList link = new LinkedList(); // 创建LinkedList 集合 link.add("stu1"); link.add("stu2"); link.add("stu3"); link.add("stu4"); System.out.println(link.toString()); // 取出并打印该集合中的元素 link.add(3, "Student"); // 向该集合中指定位置插入元素 link.addFirst("First"); // 向该集合第一个位置插入元素 System.out.println(link); System.out.println(link.getFirst()); // 取出该集合中第一个元素 link.remove(3); // 移除该集合中指定位置的元素 link.removeFirst(); // 移除该集合中第一个元素 System.out.println(link); } } ``` 运行结果: ``` [stu1, stu2, stu3, stu4] [First, stu1, stu2, stu3, Student, stu4] First [stu1, stu2, Student, stu4] ``` 例中,首先在LinkedList集合中存入4个元素,然后通过add(int index,Object o)和addFirst(Object o)方法分别在集合的指定位置和第一个位置(索引0位置)插入元素,最后使用remove(intindex)和removeFirst()方法将指定位置和集合中的第一个元素移除,这样就完成了元素的增删操作。由此可见,使用LinkedList对元素进行增删操作是非常便捷的。 #### Iterator接口 在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK 专门提供了一个接口Iterator。Iterator接口也是Java集合框架中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。 接下来通过一个案例来学习如何使用Iterator迭代集合中的元素,如例所示。 ```java import java.util.ArrayList; import java.util.Iterator; public class Example { public static void main(String[] args) { ArrayList list = new ArrayList(); // 创建ArrayList 集合 list.add("data_1"); // 向该集合中添加字符串 list.add("data_2"); list.add("data_3"); list.add("data_4"); Iterator it = list.iterator(); // 获取Iterator 对象 while (it.hasNext()) { // 判断ArrayList 集合中是否存在下一个元素 Object obj = it.next(); // 取出ArrayList 集合中的元素 System.out.println(obj); } } } ``` 运行结果: ``` data_1 data_2 data_3 data_4 ``` 例中演示的是Iterator遍历集合的整个过程。当遍历元素时,首先通过调用ArrayList集合的iterator()方法获得迭代器对象,然后使用hasNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。需要注意的是,在通过next()方法获取元素时,必须保证要获取的元素存在,否则,会抛出NoSuchElementException异常。 Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程,如图所示。 ![](http://47.107.171.232/easily-j/images/20190107/0d7ae828-a5b4-4de2-92ba-b851ffd0a363.png) 图中,在调用Iterator的next()方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next()方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next()方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext()方法返回false,表示到达了集合的末尾,终止对元素的遍历。 需要特别说明的是,当通过迭代器获取ArrayList集合中的元素时,都会将这些元素当作Object类型来看待,如果想得到特定类型的元素,则需要进行强制类型转换。 #### JDK5.0新特性 - foreach循环 虽然Iterator可以用来遍历集合中的元素,但写法上比较烦琐,为了简化书写,从JDK5.0开始,提供了foreach循环。foreach循环是一种更加简洁的for循环,也称增强for循环。foreach循环用于遍历数组或集合中的元素,其具体语法格式如下: ``` for(容器中元素类型临时变量: 容器变量) { 执行语句 } ``` 从上面的格式可以看出,与for循环相比,foreach循环不需要获得容器的长度,也不需要根据索引访问容器中的元素,但它会自动遍历容器中的每个元素。接下来通过一个案例对foreach循环进行详细讲解,如例所示。 ```java import java.util.ArrayList; public class Example { public static void main(String[] args) { ArrayList list = new ArrayList(); // 创建ArrayList 集合 list.add("Jack"); // 向ArrayList 集合中添加字符串元素 list.add("Rose"); list.add("Tom"); for (Object obj : list) { // 使用foreach 循环遍历ArrayList 对象 System.out.println(obj); // 取出并打印ArrayList 集合中的元素 } } } ``` 运行结果: ``` Jack Rose Tom ``` 通过例可以看出,foreach循环在遍历集合时语法非常简洁,没有循环条件,也没有迭代语句,所有这些工作都交给虚拟机去执行了。foreach循环的次数是由容器中元素的个数决定的,每次循环时,foreach中都通过变量将当前循环的元素记住,从而将集合中的元素分别打印出来。 #### ListIterator接口 上面讲解的Iterator迭代器提供了hasNext()方法和next()方法,通过这两个方法可以实现集合中元素的迭代,迭代的方向是从集合中的第一个元素向最后一个元素迭代,也就是所谓的正向迭代。为了使迭代方式更加多元化,JDK 中还定义了一个ListIterator迭代器,它是Iterator的子类,该类在父类的基础上增加了一些特有的方法,如表所示。 ![](http://47.107.171.232/easily-j/images/20190107/0b29727d-8f87-4bc2-a984-be47dc971047.png) 从表可以看出ListIterator中提供了hasPrevious()方法和previous()方法,通过这两个方法可以实现反向迭代元素,另外还提供了add()方法用于增加元素。接下来通过一个案例来学习ListIterator迭代器的使用,如例所示。 ```java import java.util.ArrayList; import java.util.ListIterator; public class Example { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add("data_1"); list.add("data_2"); list.add("data_3"); System.out.println(list); ListIterator it = list.listIterator(list.size()); // 获得ListIterator 对象 while (it.hasPrevious()) { // 判断该对象中是否有上一个元素 Object obj = it.previous(); // 迭代该对象的上一个元素 System.out.print(obj + " "); // 获取并打印该对象中的元素 } } } ``` 运行结果: ``` [data_1, data_2, data_3] data_3 data_2 data_1 ``` 例中,演示的是ListIterator从后向前遍历集合的过程。在使用listIterator(int index)方法获得ListIterator对象时,需要传递一个int类型的参数指定迭代的起始位置,本例中传入的是集合的长度,表示以集合的最后一个元素开始迭代,之后再使用hasPrevious()方法判断是否存在上一个元素,如果存在,则通过previous()方法将元素取出;否则,则表示到达了集合的末尾,没有要遍历的元素。在这个过程中,如果想增加元素同样不能调用集合对象的add()方法,此时需要使用ListIterator提供的add()方法,否则会出现并发修改异常ConcurrentModificationException。需要注意的是,ListIterator迭代器只能用于List集合。 #### Enumeration接口 前面我们讲过在遍历集合时可以使用Iterator接口,但在JDK1.2 以前还没有Iterator接口的时候,遍历集合需要使用Enumeration接口,它的用法和Iterator类似。由于很多程序中依然在使用Enumeration,因此了解该接口的用法是很有必要的。JDK中提供了一个Vector集合,该集合是List接口的一个实现类,用法与ArrayList完全相同,区别在于Vector集合是线程安全的,而ArrayList集合是线程不安全的。在Vector类中提供了一个elements()方法用于返回Enumeration对象,通过Enumeration对象就可以遍历该集合中的元素。接下来通过一个案例来演示如何使用Enumeration对象遍历Vector集合,如例所示。 ```java import java.util.Enumeration; import java.util.Vector; public class Example { public static void main(String[] args) { Vector v = new Vector(); // 创建Vector 对象 v.add("Jack"); // 向该Vector 对象中添加元素 v.add("Rose"); v.add("Tom"); Enumeration en = v.elements(); // 获得Enumeration 对象 while (en.hasMoreElements()) { // 判断该对象是否有更多元素 Object obj = en.nextElement(); // 取出该对象的下一个元素 System.out.println(obj); } } } ``` 运行结果: ``` Jack Rose Tom ``` 例中,首先创建了一个Vector集合并通过调用add()方法向集合添加三个元素,然后调用elements()方法返回一个Enumeration对象,例程中的第9~12行代码使用一个while循环对集合中的元素进行迭代,其过程与Iterator迭代的过程类似,通过hasMoreElements()方法循环判断是否存在下一个元素,如果存在,则通过nextElement()方法逐一取出每个元素。 ## Set接口 #### Set接口简介 Set接口和List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。 Set接口主要有两个实现类,分别是HashSet和TreeSet。其中,HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。TreeSet则是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序。接下来围绕Set集合的这两个实现类详细地进行讲解。 #### HashSet集合 HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的。当向HashSet集合中添加一个对象时,首先会调用该对象的hashCode()方法来确定元素的存储位置,然后再调用对象的equals()方法来确保该位置没有重复元素。Set集合与List集合存取元素的方式都一样,在此不再进行详细的讲解,接下来通过一个案例来演示HashSet集合的用法,如例所示。 ```java import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Vector; public class Example { public static void main(String[] args) { HashSet set = new HashSet(); // 创建HashSet 集合 set.add("Jack"); // 向该Set 集合中添加字符串 set.add("Eve"); set.add("Rose"); set.add("Rose"); // 向该Set 集合中添加重复元素 Iterator it = set.iterator(); // 获取Iterator 对象 while (it.hasNext()) { // 通过while 循环,判断集合中是否有元素 Object obj = it.next(); // 如果有元素,就通过迭代器的next()方法获取元素 System.out.println(obj); } } } ``` 运行结果: ``` Eve Rose Jack ``` 例中,首先通过add()方法向HashSet集合依次添加了四个字符串,然后通过Iterator迭代器遍历所有的元素并输出打印。从打印结果可以看出取出元素的顺序与添加元素的顺序并不一致,并且重复存入的字符串对象“Rose”被去除了,只添加了一次。 HashSet集合之所以能确保不出现重复的元素,是因为它在存入元素时做了很多工作。当调用HashSet集合的add()方法存入元素时,首先调用当前存入对象的hashCode()方法获得对象的哈希值,然后根据对象的哈希值计算出一个存储位置。如果该位置上没有元素,则直接将元素存入,如果该位置上有元素存在,则会调用equals()方法让当前存入的元素依次和该位置上的元素进行比较,如果返回的结果为false就将该元素存入集合,返回的结果为true则说明有重复元素,就将该元素舍弃。整个存储的流程如图所示。 ![](http://47.107.171.232/easily-j/images/20190107/d94b7825-46b9-4f7d-b952-ac3e15a91c94.png) 根据前面的分析不难看出,当向集合中存入元素时,为了保证HashSet正常工作,要求在存入对象时,重写该类中的hashCode()和equals()方法。例中将字符串存入HashSet时,String类已经重写了hashCode()和equals()方法。但是如果将Student对象存入HashSet,结果又如何呢? 接下来通过一个案例来进行演示,如例所示。 ```java import java.util.HashSet; class Student { String id; String name; public Student(String id, String name) { // 创建构造方法 this.id = id; this.name = name; } public String toString() { // 重写toString()方法 return id + ":" + name; } } public class Example { public static void main(String[] args) { HashSet hs = new HashSet(); // 创建HashSet 集合 Student stu1 = new Student("1", "Jack"); // 创建Student 对象 Student stu2 = new Student("2", "Rose"); Student stu3 = new Student("2", "Rose"); hs.add(stu1); hs.add(stu2); hs.add(stu3); System.out.println(hs); } } ``` 运行结果: ``` [2:Rose, 1:Jack, 2:Rose] ``` 在例中,向HashSet集合存入三个Student对象,并将这三个对象迭代输出。图所示的运行结果中出现了两个相同的学生信息“2:Rose”,这样的学生信息应该被视为重复元素,不允许同时出现在HashSet集合中。之所以没有去掉这样的重复元素是因为在定义Student类时没有重写hashCode()和equals()方法。接下来针对例中的Student类进行改写,假设id相同的学生就是同一个学生,改写后的代码如例所示。 ```java import java.util.HashSet; class Student { private String id; private String name; public Student(String id, String name) { this.id = id; this.name = name; } // 重写toString()方法 public String toString() { return id + ":" + name; } // 重写hashCode 方法 public int hashCode() { return id.hashCode(); // 返回id 属性的哈希值 } // 重写equals 方法 public boolean equals(Object obj) { if (this == obj) { // 判断是否是同一个对象 return true; // 如果是,直接返回true } if (!(obj instanceof Student)) { // 判断对象是为Student 类型 return false; // 如果对象不是Student 类型,返回false } Student stu = (Student) obj; // 将对象强转为Student 类型 boolean b = this.id.equals(stu.id); // 判断id 值是否相同 return b; // 返回判断结果 } } public class Example { public static void main(String[] args) { HashSet hs = new HashSet(); // 创建HashSet 对象 Student stu1 = new Student("1", "Jack"); // 创建Student 对象 Student stu2 = new Student("2", "Rose"); Student stu3 = new Student("2", "Rose"); hs.add(stu1); // 向集合存入对象 hs.add(stu2); hs.add(stu3); System.out.println(hs); // 打印集合中的元素 } } ``` 运行结果: ``` [1:Jack, 2:Rose] ``` 在例7-11 中,Student类重写了Object类的hashCode()和equals()方法。在hashCode()方法中返回id属性的哈希值,在equals()方法中比较对象的id属性是否相等,并返回结果。当调用HashSet集合的add()方法添加stu3对象时,发现它的哈希值与stu2对象相同,而且stu2.equals(stu3)返回true,HashSet集合认为两个对象相同,因此重复的Student对象被成功去除了。 #### TreeSet集合 TreeSet是Set接口的另一个实现类,它内部采用自平衡的排序二叉树来存储元素,这样的结构可以保证TreeSet集合中没有重复的元素,并且可以对元素进行排序。所谓二叉树就是说每个节点最多有两个子节点的有序树,每个节点及其子节点组成的树称为子树,通常左侧的子节点称为“左子树”,右侧的子节点称为“右子树”,二叉树中元素的存储结构如图所示。 ![](http://47.107.171.232/easily-j/images/20190107/849e8666-4752-4773-a8e6-f68a434eaaaa.png) 在实际应用中,二叉树分为很多种,如排序二叉树、平衡二叉树等。TreeSet集合内部使用的是自平衡的排序二叉树,它的特点是存储的元素会按照大小排序,并能去除重复元素。例如向一个二叉树中存入8个元素,依次为13、8、17、17、1、11、15、25,如果以排序二叉树的方式来存储,在集合中的存储结构会形成一个树状结构,如图所示。 ![](http://47.107.171.232/easily-j/images/20190107/7e0853d0-f516-4dc7-91f0-99da111d9845.png) 从图可以看出,在向TreeSet集合依次存入元素时,首先将第1个存入的元素放在二叉树的最顶端,之后存入的元素与第一个元素比较,如果小于第一个元素就将该元素放在左子树上,如果大于第1个元素,就将该元素放在右子树上,依此类推,按照左子树元素小于右子树元素的顺序进行排序。当二叉树中已经存入一个17的元素时,再向集合中存入一个为17的元素时,TreeSet会将把重复的元素去掉。 了解了二叉树存放元素的原理,接下来通过一个案例来演示TreeSet对元素的排序效果,如例所示。 ```java import java.util.Iterator; import java.util.TreeSet; public class Example { public static void main(String[] args) { TreeSet ts = new TreeSet(); // 创建TreeSet 集合 ts.add("Jack"); // 向TreeSet 集合中添加元素 ts.add("Helena"); ts.add("Helena"); ts.add("Eve"); Iterator it = ts.iterator(); // 获取Iterator 对象 while (it.hasNext()) { System.out.println(it.next()); } } } ``` 运行结果: ``` Eve Helena Jack ``` 从图可以看出,通过迭代器Iterator迭代出的字符串元素按照字母表的顺序打印了出来,这些元素之所以能够排序是因为每次向TreeSet集合中存入一个元素时,就会将该元素与其他元素进行比较,最后将它插入到有序的对象序列中。集合中的元素在进行比较时,都会调用compareTo()方法,该方法是Comparable接口中定义的,因此要想对集合中的元素进行排序,就必须实现Comparable接口。JDK 中大部分的类都实现Comparable接口,拥有了接口中的CompareTo()方法,如Integer、Double和String等。 在TreeSet集合中存放Student类型对象时,如果Student类没有实现Comparable接口,则Student类型的对象将不能进行比较,这时,TreeSet集合就不知道按照什么排序规则对Student对象进行排序,最终导致程序报错。因此,为了在TreeSet集合中存放Student对象,必须使Student类实现Comparable接口,如例所示。 ```java import java.util.Iterator; import java.util.TreeSet; class Student implements Comparable { // 定义Student 类实现Comparable 接口 String name; int age; public Student(String name, int age) {// 创建构造方法 this.name = name; this.age = age; } public String toString() { // 重写Object 类的toString()方法,返回描述信息 return name + ":" + age; } public int compareTo(Object obj) { // 重写Comparable 接口的compareTo 方法 Student s = (Student) obj; // 将比较对象强转为Student 类型 if (this.age - s.age > 0) { // 定义比较方式 return 1; } if (this.age - s.age == 0) { return this.name.compareTo(s.name); // 将比较结果返回 } return -1; } } public class Example { public static void main(String[] args) { TreeSet ts = new TreeSet(); // 创建TreeSet 集合 ts.add(new Student("Jack", 19)); // 向集合中添加元素 ts.add(new Student("Rose", 18)); ts.add(new Student("Tom", 19)); ts.add(new Student("Rose", 18)); Iterator it = ts.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } } ``` 运行结果: ``` Rose:18 Jack:19 Tom:19 ``` 例中,Student 类实现了Comparable 接口中的compareTo()方法。在compareTo()方法中,首先先针对age值进行比较,根据比较结果返回-1和1,当age相同时,再对name进行比较。因此,从运行结果可以看出,学生首先按照年龄排序,年龄相同时会按照姓名排序。 有时候,定义的类没有实现Comparable接口或者实现了Comparable接口而不想按照定义的compareTo()方法进行排序。例如,希望字符串可以按照长度来进行排序,这时,可以通过自定义比较器的方式对TreeSet集合中的元素排序,即实现Comparator接口,在创建TreeSet集合时指定比较器。接下来通过一个案例来实现TreeSet集合中字符串按照长度进行排序,如例所示。 ```java import java.util.Comparator; import java.util.Iterator; import java.util.TreeSet; class MyComparator implements Comparator { // 定义比较器实现Comparator 接口 public int compare(Object obj1, Object obj2) { // 实现比较方法 String s1 = (String) obj1; String s2 = (String) obj2; int temp = s1.length() - s2.length(); return temp; } } public class Example { public static void main(String[] args) { TreeSet ts = new TreeSet(new MyComparator()); // 创建TreeSet 对象时传入自定义比较器 ts.add("Jack"); // 向该Set 对象中添加字符串 ts.add("Helena"); ts.add("Eve"); Iterator it = ts.iterator(); // 获取Iterator 对象 // 通过while 循环,逐渐将集合中的元素打印出来 while (it.hasNext()) { // 如果Iterator 有元素进行迭代,则获取元素并进行打印 Object obj = it.next(); System.out.println(obj); } } } ``` 运行结果: ``` Eve Jack Helena ``` 例中,定义了一个MyComparator类实现了Comparator接口,在compare()方法中实现元素的比较,这就相当于定义了一个比较器。在创建TreeSet集合时,将MyComparator比较器对象传入,当向集合中添加元素时,比较器对象的compare()方法就会被自动调用,从而使存入TreeSet集合中的字符串按照长度进行排序。 ## Map接口 #### Map接口简介 在现实生活中,每个人都有唯一的身份证号,通过身份证号可以查询到这个人的信息,这两者是一对一的关系。在应用程序中,如果想存储这种具有对应关系的数据,则需要使用JDK中提供的Map接口。Map接口是一种双列集合,它的每个元素都包含一个键对象Key和一个值对象Value,键和值对象之间存在一种对应关系,称为映射。从Map集合中访问元素时,只要指定了Key,就能找到对应的Value。为了便于Map接口的学习,接下来首先了解一下Map接口中定义的一些通用方法。 ![](http://47.107.171.232/easily-j/images/20190108/7a74f083-8feb-4781-9738-97ac2dddd406.png) 表中,列出了一系列方法用于操作Map。其中,put(Object key,Object value)和get(Object key)方法分别用于向Map中存入元素和取出元素;containsKey(Object key)和containsValue(Object value)方法分别用于判断Map中是否包含某个指定的键或值;keySet()和values()方法分别用于获取Map中所有的键和值。 Map接口提供了大量的实现类,最常用的有HashMap和TreeMap,接下来针对这两个类进行详细地讲解。 #### HashMap集合 HashMap集合是Map接口的一个实现类,它用于存储键值映射关系,但必须保证不出现重复的键。接下来通过一个案例来学习HashMap的用法。 ```java import java.util.HashMap; import java.util.Map; public class Example { public static void main(String[] args) { Map map = new HashMap(); // 创建Map 对象 map.put("1", "Jack"); // 存储键和值 map.put("2", "Rose"); map.put("3", "Lucy"); System.out.println("1: " + map.get("1")); // 根据键获取值 System.out.println("2: " + map.get("2")); System.out.println("3: " + map.get("3")); } } ``` 运行结果: ``` 1: Jack 2: Rose 3: Lucy ``` 例中,首先通过Map的put(Object key,Object value)方法向集合中加入3个元素,然后通过Map的get(Object key)方法获取与键对应的值。前面讲过Map集合中的键具有唯一性,现在向Map集合中存储一个相同的键看看会出现什么情况。现对例进行修改,在第9行代码下面增加一行代码,如下所示: ```java map.put("3", "Mary"); ``` 运行结果: ``` 1: Jack 2: Rose 3: Mary ``` 从图中可以看出,Map中仍然只有3个元素,第二次添加的值“Mary”覆盖原来的值“Lucy”,因此证实了Map中的键必须是唯一的,不能重复。如果存储了相同的键,后存储的值则会覆盖原有的值,简而言之就是:键相同,值覆盖。在程序开发中,经常需要取出Map中所有的键和值,那么如何遍历Map中所有的键值对呢? 有两种方式可以实现,第一种方式就是先遍历Map集合中所有的键,再根据键获取相应的值,接下来就通过一个案例来演示这种遍历方式,如例所示。 ```java import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class Example { public static void main(String[] args) { Map map = new HashMap(); // 创建Map 集合 map.put("1", "Jack"); // 存储键和值 map.put("2", "Rose"); map.put("3", "Lucy"); Set keySet = map.keySet(); // 获取键的集合 Iterator it = keySet.iterator(); // 迭代键的集合 while (it.hasNext()) { Object key = it.next(); Object value = map.get(key); // 获取每个键所对应的值 System.out.println(key + ":" + value); } } } ``` 运行结果: ``` 1:Jack 2:Rose 3:Lucy ``` 例中,是第一种遍历Map的方式。首先调用Map对象的keySet()方法,获得存储Map中所有键的Set集合,然后通过Iterator迭代Set集合的每一个元素,即每一个键,最后通过调用get(Stringkey)方法,根据键获取对应的值。 Map集合的另外一种遍历方式是先获取集合中的所有的映射关系,然后从映射关系中取出键和值。接下来通过一个案例来演示这种遍历方式,如例所示。 ```java import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class Example { public static void main(String[] args) { Map map = new HashMap(); // 创建Map 集合 map.put("1", "Jack"); // 存储键和值 map.put("2", "Rose"); map.put("3", "Lucy"); Set entrySet = map.entrySet(); Iterator it = entrySet.iterator(); // 获取Iterator 对象 while (it.hasNext()) { Map.Entry entry = (Map.Entry) (it.next()); // 获取集合中键值对映射关系 Object key = entry.getKey(); // 获取Entry 中的键 Object value = entry.getValue(); // 获取Entry 中的值 System.out.println(key + ":" + value); } } } ``` 运行结果: ``` 1:Jack 2:Rose 3:Lucy ``` 例7-17中,是第二种遍历Map的方式。首先调用Map对象的entrySet()方法获得存储在Map中所有映射的Set集合,这个集合中存放了Map.Entry类型的元素(Entry是Map接口内部类),每个Map.Entry对象代表Map中的一个键值对,然后迭代Set集合,获得每一个映射对象,并分别调用映射对象的getKey()和getValue()方法获取键和值。 在Map中,还提供了一个values()方法,通过这个方法可以直接获取Map中存储所有值的Collection集合,接下来通过一个案例来演示,如例所示。 ```java import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class Example { public static void main(String[] args) { Map map = new HashMap(); // 创建Map 集合 map.put("1", "Jack"); // 存储键和值 map.put("2", "Rose"); map.put("3", "Lucy"); Collection values = map.values(); Iterator it = values.iterator(); while (it.hasNext()) { Object value = it.next(); System.out.println(value); } } } ``` 运行结果: ``` Jack Rose Lucy ``` 在例中,通过调用Map的values()方法获取包含Map中所有值的Collection集合,然后迭代出集合中的每一个值。 从上面的例子可以看出,HashMap集合迭代出来元素的顺序和存入的顺序是不一致的。如果想让这两个顺序一致,可以使用Java中提供的LinkedHashMap类,它是HashMap的子类,和LinkedList一样也使用双向链表来维护内部元素的关系,使Map元素迭代的顺序与存入的顺序一致。接下来通过一个案例来学习一下LinkedHashMap的用法,如例所示。 ```java import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; public class Example { public static void main(String[] args) { Map map = new LinkedHashMap(); // 创建Map 集合 map.put("1", "Jack"); // 存储键和值 map.put("2", "Rose"); map.put("3", "Lucy"); Set keySet = map.keySet(); Iterator it = keySet.iterator(); while (it.hasNext()) { Object key = it.next(); Object value = map.get(key); // 获取每个键所对应的值 System.out.println(key + ":" + value); } } } ``` 运行结果: ``` 1:Jack 2:Rose 3:Lucy ``` 在例中,首先创建了一个LinkedHashMap集合并存入了3个元素,然后使用迭代器将元素取出。从运行结果可以看出,元素迭代出来的顺序和存入的顺序是一致的。 #### TreeMap集合 在JDK中,Map接口还有一个常用的实现类TreeMap。TreeMap集合是用来存储键值映射关系的,其中不允许出现重复的键。在TreeMap中是通过二叉树的原理来保证键的唯一性,这与TreeSet集合存储的原理一样,因此TreeMap中所有的键是按照某种顺序排列的。接下来通过一个案例来了解TreeMap的具体用法,如例所示。 ```java import java.util.Iterator; import java.util.Set; import java.util.TreeMap; public class Example { public static void main(String[] args) { TreeMap tm = new TreeMap(); tm.put("1", "Jack"); tm.put("2", "Rose"); tm.put("3", "Lucy"); Set keySet = tm.keySet(); // 获取键的集合 Iterator it = keySet.iterator(); // 获取Iterator 对象 while (it.hasNext()) { // 判断是否存在下一个元素 Object key = it.next(); // 取出元素 Object value = tm.get(key); // 根据获取的键找到对应的值 System.out.println(key + ":" + value); } } } ``` 运行结果: ``` 1:Jack 2:Rose 3:Lucy ``` 在例中,使用put()方法将3个学生的信息存入TreeMap集合,其中学号作为键,姓名作为值,然后对学生信息进行遍历。从运行结果可以看出,取出的元素按照学号的自然顺序进行了排序,这是因为学号是String类型,String类实现了Comparable接口,因此默认会按照自然顺序进行排序。 在使用TreeMap集合时,也可以通过自定义比较器的方式对所有的键进行排序。接下来通过一个案例将学生对象按照学号由大到小的顺序进行排序,如例所示。 ```java import java.util.Comparator; import java.util.Iterator; import java.util.Set; import java.util.TreeMap; public class Example { public static void main(String[] args) { TreeMap tm = new TreeMap(new MyComparator()); // 传入一个自定义比较器 tm.put("1", "Jack"); // 向集合存入学生的学号和姓名 tm.put("2", "Rose"); tm.put("3", "Lucy"); Set keySet = tm.keySet(); // 获取键的集合 Iterator it = keySet.iterator(); // 获得迭代器对象 while (it.hasNext()) { Object key = it.next(); // 获得一个键 Object value = tm.get(key); // 获得键对应的值 System.out.println(key + ":" + value); } } } class MyComparator implements Comparator { // 自定义比较器 public int compare(Object obj1, Object obj2) { // 实现比较方法 String id1 = (String) obj1; // 将Object 类型的参数强转为String 类型 String id2 = (String) obj2; return id2.compareTo(id1); // 将比较之后的值返回 } } ``` 运行结果: ``` 3:Lucy 2:Rose 1:Jack ``` 例中定义了比较器MyComparator针对String类型的id进行比较,在实现compare()方法时,调用了String 对象的compareTo()方法。由于方法中返回的是“id2.compareTo(id1)”,因此最终输出结果中的id按照与字典顺序相反的顺序进行了排序。 #### Properties集合 Map接口中还有一个实现类Hashtable,它和HashMap 十分相似,区别在于Hashtable是线程安全的。Hashtable存取元素时速度很慢,目前基本上被HashMap类所取代,但Hashtable类有一个子类Properties在实际应用中非常重要,Properties主要用来存储字符串类型的键和值,在实际开发中,经常使用Properties集合来存取应用的配置项。假设有一个文本编辑工具,要求默认背景色是红色,字体大小为14px,语言为中文,其配置项应该如下: ``` Backgroup-color=red Font-size=14px Language=chinese ``` 在程序中可以使用Prorperties集合对这些配置项进行存取,接下来通过一个案例来学习,如例所示。 ```java import java.util.Enumeration; import java.util.Properties; public class Example { public static void main(String[] args) { Properties p = new Properties(); // 创建Properties 对象 p.setProperty("backgroup-color", "red"); p.setProperty("Font-size", "14px"); p.setProperty("Language", "chinese"); Enumeration names = p.propertyNames(); // 获取Enumeration 对象所有键的枚举 while (names.hasMoreElements()) { // 循环遍历所有的键 String key = (String) names.nextElement(); String value = p.getProperty(key); // 获取对应键的值 System.out.println(key + "=" + value); } } } ``` 运行结果: ``` backgroup-color=red Language=chinese Font-size=14px ``` 例的Properties类中,针对字符串的存取提供了两个专用的方法setProperty()和getProperty()。setProperty()方法用于将配置项的键和值添加到Properties集合当中。在第8行代码中通过调用Properties的propertyNames()方法得到一个包含所有键的Enumeration对象,然后在遍历所有的键时,通过调用getProperty()方法获得键所对应的值。 ## JDK5.0新特性 - 泛型 #### 为什么使用泛型 通过之前的学习,了解到集合可以存储任何类型的对象,但是当把一个对象存入集合后,集合会“忘记”这个对象的类型,将该对象从集合中取出时,这个对象的编译类型就变成了Object类型。换句话说,在程序中无法确定一个集合中的元素到底是什么类型的。那么在取出元素时,如果进行强制类型转换就很容易出错。接下来通过一个案例来演示这种情况,如例所示。 ```java import java.util.ArrayList; public class Example { public static void main(String[] args) { ArrayList list = new ArrayList(); // 创建ArrayList 集合 list.add("String"); // 添 加字符串对象 list.add("Collection"); list.add(1); // 添加Integer 对象 for (Object obj : list) { // 遍历集合 String str = (String) obj; // 强制转换成String 类型 } } } ``` 运行结果: ``` Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at Example.main(Example.java:10) ``` 例中,向List集合存入了3个元素,分别是两个字符串和一个整数。在取出这些元素时,都将它们强转为String类型,由于Integer对象无法转换为String类型,因此在程序运行时会出现如图7-33所示的错误。为了解决这个问题,在Java中引入了“参数化类型(parameterizedtype)”这个概念,即泛型。它可以限定方法操作的数据类型,在定义集合类时,使用“<参数化类型>”的方式指定该类中方法操作的数据类型,具体格式如下: ``` ArrayList<参数化类型> list = new ArrayList<参数化类型>(); ``` 接下来对例中的第4行代码进行修改,如下所示: ```java ArrayList<String> list = new ArrayList<String>(); //创建集合对象并指定泛型为String ``` 程序编译报错的原因是修改后的代码限定了集合元素的数据类型,ArrayList<String>这样的集合只能存储String类型的元素,程序在编译时,编译器检查出Integer类型的元素与List集合的规定类型不匹配,编译不通过,这样就可以在编译时解决错误,避免程序在运行时发生错误。 接下来使用泛型再次对例进行改写,如例所示。 ```java import java.util.ArrayList; public class Example { public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); // 创建ArrayList 集合,使用泛型 list.add("String"); // 添加字符串对象 list.add("Collection"); for (String str : list) { // 遍历集合 System.out.println(str); // 强制转换成String 类型 } } } ``` 运行结果: ``` String Collection ``` 例中,使用泛型规定了ArrayList集合只能存入String类型元素。之后向集合中存入了两个String类型元素,并对这个集合进行遍历,从运行结果可以看出该例程可以正常运行。需要注意的是,在使用泛型后每次遍历集合元素时,可以指定元素类型为String,而不是Object,这样就避免了在程序中进行强制类型转换。 #### 自定义泛型 上一小节讲解了在集合上如何使用泛型,那么在程序中是否能自定义泛型呢? 假设要实现一个简单的容器,用于缓存程序中的某个值,此时在这个容器类中势必要定义两个方法save()和get(),一个用于保存数据,另一个用于取出数据,这两个方法的定义如下: ``` void save(参数类型参数){……} 返回值参数类型get(){……} ``` 为了能存储任意类型的对象,save()方法的参数需要定义为Object类型,同样get()方法的返回值也需要是Object类型。但是当使用get()方法取出这个值时,有可能忘记当初存储的是什么类型的值,在取出时将其转换为String类型,这样程序就会发生错误。这种错误是很麻烦的,为了避免这个问题,就可以使用泛型。 如果在定义一个类CachePool时使用<T>声明参数类型(T 其实就是Type的缩写,这里也可以使用其他字符,为了方便理解都定义为T),将save()方法的参数类型和get()方法的返回值类型都声明为T,那么在存入元素时元素的类型就被限定了,容器中就只能存入这种T 类型的元素,在取出元素时就无须进行类型转换。接下来通过一个案例来看一下如何自定义泛型,如: ```java class cachePool<T> { // 在创建类时,声明参数类型为T T temp; public void save(T temp) { // 在创建save()方法时,指定参数类型为T this.temp = temp; } public T get() { // 在创建get()方法时,指定返回值类型为T return temp; } } public class Example { public static void main(String[] args) { // 在实例化对象时,传入参数为Integer 类型 cachePool<Integer> pool = new cachePool<Integer>(); pool.save(new Integer(1)); Integer temp = pool.get(); System.out.println(temp); } } ``` 运行结果: ``` 1 ``` 例中,在定义CachePool类时,声明了参数类型为T,在实例化对象时通过<Integer>将参数T 指定为Integer类型,同时在调用save()方法时传入的数据也是Integer类型,那么调用get()方法取出的数据自然就是Integer类型,这样做的好处是不需要进行类型转换。 ## Collections工具类 在程序中,针对集合的操作非常频繁,例如将集合中的元素排序、从集合中查找某个元素等。针对这些常见操作,JDK 提供了一个工具类专门用来操作集合,这个类就是Collections,它位于java.util包中。Collections类中提供了大量的方法用于对集合中的元素进行排序、查找和修改等操作,接下来对这些常用的方法进行介绍。 - 排序操作 Collections类中提供了一系列方法用于对List集合进行排序,如表所示。 ![](http://47.107.171.232/easily-j/images/20190108/4af93b4c-b7a3-42cf-8e4f-401c5b0d235e.png) 接下来通过一个案例针对表中的方法进行学习,如例所示。 ```java import java.util.ArrayList; import java.util.Collections; public class Example { public static void main(String[] args) { ArrayList list = new ArrayList(); Collections.addAll(list, "C", "Z", "B", "K"); // 添加元素 System.out.println("排序前: " + list); // 输出排序前的集合 Collections.reverse(list); // 反转集合 System.out.println("反转后: " + list); Collections.sort(list); // 按自然顺序排列 System.out.println("按自然顺序排序后: " + list); Collections.shuffle(list); System.out.println("洗牌后: " + list); } } ``` 运行结果: ``` 排序前: [C, Z, B, K] 反转后: [K, B, Z, C] 按自然顺序排序后: [B, C, K, Z] 洗牌后: [K, C, Z, B] ``` - 查找、替换操作 Collections类还提供了一些常用方法用于查找、替换集合中的元素,如表所示。 ![](http://47.107.171.232/easily-j/images/20190108/70f0e3d2-0f80-4424-a819-6d5c60b2be21.png) 接下来使用表中的方法通过一个案例演示如何查找、替换集合中的元素,如例所示。 ```java import java.util.ArrayList; import java.util.Collections; public class Example { public static void main(String[] args) { ArrayList list = new ArrayList(); Collections.addAll(list, -3, 2, 9, 5, 8); System.out.println("集合中的元素: " + list); System.out.println("集合中的最大元素: " + Collections.max(list)); System.out.println("集合中的最小元素: " + Collections.min(list)); Collections.replaceAll(list, 8, 0); // 将集合中的8 用0 替换掉 System.out.println("替换后的集合: " + list); } } ``` 运行结果: ``` 集合中的元素: [-3, 2, 9, 5, 8] 集合中的最大元素: 9 集合中的最小元素: -3 替换后的集合: [-3, 2, 9, 5, 0] ``` Collections类中还有一些其他方法,有兴趣的初学者,可以根据需要自学API帮助文档,这里就不再介绍了。 ## Arrays工具类 - **使用Arrays的sort()方法排序** 在前面学习数组时,要想对数组进行排序就需要自定义一个排序方法,其实也可以使用Arrays工具类中的静态方法sort()来实现这个功能。接下来通过一个案例来学习sort()方法的使用,如例所示。 ```java import java.util.Arrays; public class Example { public static void main(String[] args) { int[] arr = { 9, 8, 3, 5, 2 }; // 初始化一个数据 System.out.print("排序前: "); printArray(arr); // 打印原数组 Arrays.sort(arr); // 调用Arrays 的sort 方法排序 System.out.print("排序后: "); printArray(arr); } public static void printArray(int[] arr) { // 定义打印数组方法 System.out.print("["); for (int x = 0; x < arr.length; x++) { if (x != arr.length - 1) { System.out.print(arr[x] + ","); } else { System.out.println(arr[x] + "]"); } } } } ``` 运行结果: ``` 排序前: [9,8,3,5,2] 排序后: [2,3,5,8,9] ``` - **使用Arrays的binarySearch(Object[] a,Object key)方法查找元素** 程序开发中,经常会在数组中查找某些特定的元素,如果数组中元素较多时查找某个元素就会非常烦琐。为此,Arrays类中还提供了一个方法binarySearch(Object[] a, Object key)用于查找元素,接下来通过一个案例来学习该方法的使用,如例所示。 ```java import java.util.Arrays; public class Example { public static void main(String[] args) { int[] arr = { 9, 8, 3, 5, 2 }; Arrays.sort(arr); // 调用排序方法,对数组排序{2,3,5,8,9} int index = Arrays.binarySearch(arr, 3); // 查找指定元素3 System.out.println("数组排序后元素3 的索引是:" + index);// 输出打印元素所在的索引位置 } } ``` 运行结果: ``` 数组排序后元素3 的索引是:1 ``` 从运行结果可以看出,使用Arrays的binarySearch(Object[] a,Object key)方法查找出了3在数组中的索引为1。需要注意的是,binarySearch()方法只能针对排序后的数组进行元素的查找,因为该方法采用的是二分法查找。所谓二分法查找,就是每次将指定元素和数组中间位置的元素进行比较,从而排除掉其中的一半元素,这样的查找是非常高效的。接下来通过一个图例来演示二分法查找元素的过程,如图所示。 ![](http://47.107.171.232/easily-j/images/20190108/15ecb745-448a-4519-bbe2-7b68c740d5ec.png) 图中的start、end和mid(mid=(start+end)/2)分别代表在数组中查找区间的开始索引、结束索引和中间索引,假设查找的元素为key,接下来分步骤讲解元素的查找过程。 第一步,判断开始索引start和结束索引end,如果start<=end,则key和arr[mid]进行比较;如果两者相等,说明找到了该元素;如果不相等,则返回元素arr[mid]。 第二步,将key和arr[mid]继续进行比较,如果key<arr[mid],表示查找的值处于索引start和mid之间,这时执行第三步,否则表示要查找的值处于索引mid和end之间,执行第四步。 第三步,将查找区间的结束索引end置为mid-1,继续查找,直到start>end,表示查找的数组不存在,这时执行第五步。 第四步,将查找区间的开始索引start置为mid+1,结束索引不变,继续查找,直到start>end,表示查找的数组不存在,这时执行第五步。 第五步,返回“(插入点)-1”。这个“插入点”指的是大于key值的第一个元素在数组中的位置,如果数组中所有的元素值都小于要查找的对象,“插入点”就等于Arrays.size()。 - **使用Arrays的copyOfRange(int[] original, int from,int to)方法拷贝元素** 在程序开发中,经常需要在不破坏原数组的情况下使用数组中的部分元素,这时可以使用Arrays工具类的copyOfRange(int[] original, int from, int to)方法将数组中指定范围的元素复制到一个新的数组中,该方法中参数original表示被复制的数组,from 表示被复制元素的初始索引(包括),to表示被复制元素的最后索引(不包括),接下来通过一个案例来学习如何拷贝数组,如例所示。 ```java import java.util.Arrays; public class Example { public static void main(String[] args) { int[] arr = { 9, 8, 3, 5, 2 }; int[] copied = Arrays.copyOfRange(arr, 1, 7); for (int i = 0; i < copied.length; i++) { System.out.print(copied[i] + " "); } } } ``` 运行结果: ``` 8 3 5 2 0 0 ``` 例中,使用Arrays的copyOfRange(arr,1,7)方法将数组{9,8,3,5,2}中从arr[1](包括开始索引对应的元素)到arr[6](不包括结束索引对应的元素)这6个元素复制到新数组copied中,由于原数组arr的最大索引为4,因此只有arr[1]到arr[4]这四个元素8,3,5,2复制到了新数组copied中,另外两个元素放入了默认值0。 - **使用Arrays的fill(Object[] a,Object val)方法填充元素** 程序开发中,经常需要用一个值替换数组中的所有元素,这时可以使用Array的fill(Object[] a,Object val)方法,该方法可以将指定的值赋给数组中的每一个元素,接下来通过一个案例来演示如何填充元素,如例所示。 ```java import java.util.Arrays; public class Example { public static void main(String[] args) { int[] arr = { 1, 2, 3, 4 }; Arrays.fill(arr, 8); // 用8 替换数组中的每一个值 for (int i = 0; i < arr.length; i++) { System.out.println(i + ": " + arr[i]); } } } ``` 运行结果: ``` 0: 8 1: 8 2: 8 3: 8 ``` - **使用Arrays的toString(int[] arr)方法把数组转换为字符串** 在程序开发中,经常需要把数组以字符串的形式输出,这时就可以使用Arrays工具类的另一个方法toString(int[] arr)。需要注意的是,该方法并不是对Object类toString()方法的重写,只是用于返回指定数组的字符串形式。接下来通过一个案例来演示如何将数组转换为字符串,如例所示。 ```java import java.util.Arrays; public class Example { public static void main(String[] args) { int[] arr = { 9, 8, 3, 5, 2 }; String arrString = Arrays.toString(arr); // 使用toString()方法将数组转换为字符串 System.out.println(arrString); } } ``` 运行结果: ``` [9, 8, 3, 5, 2]