【Java】8、Java 集合框架

集合框架

集合概述

在前面的章节中介绍过在程序中可以通过数组来保存多个对象,但在某些情况下无法确定到底需要保存多少个对象,此时数组将不再适用,因为数组的长度不可变。例如,要保存一个学校的学生信息,由于不停有新生来报道,同时也有学员毕业离开学校,这时学生的数目很难确定。为了保存这些数目不确定的对象,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 中提供了丰富的集合类库,为了便于初学者进行系统地学习,接下来通过一张图来描述整个集合类的继承体系。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UXFI5IVS-1642058884085)(http://47.107.171.232/easily-j/images/20190107/a3aeb3c0-0123-4617-8e85-df6b8892b8fe.png)]

图中列出了程序中常用的一些集合类,其中,虚线框里填写的都是接口类型,而实线框里填写的都是具体的实现类。

Collection 接口

Collection 是所有单列集合的父接口,因此在 Collection 中定义了单列集合(List 和 Set)通用的一些方法,这些方法可用于操作所有的单列集合。

Collection 接口的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0lQteq6b-1642058884086)(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 接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XRm2e1nJ-1642058884086)(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 集合如何存取元素,如例所示。

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 集合添加元素和删除元素的过程如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zClQFza8-1642058884087)(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 集合除了具备增删元素效率高的特点,还专门针对元素的增删操作定义了一些特有的方法,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GushePBS-1642058884088)(http://47.107.171.232/easily-j/images/20190107/d1626242-3fac-49a5-9709-4907f7ceddf1.png)]

表中,列出的方法主要针对集合中的元素进行增加、删除和获取操作,接下来通过一个案例来学习这些方法的使用,如例所示。

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 迭代集合中的元素,如例所示。

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 对象迭代元素的过程,如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wfQPeK2K-1642058884089)(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 循环进行详细讲解,如例所示。

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 的子类,该类在父类的基础上增加了一些特有的方法,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HwTH2SpA-1642058884089)(http://47.107.171.232/easily-j/images/20190107/0b29727d-8f87-4bc2-a984-be47dc971047.png)]

从表可以看出 ListIterator 中提供了 hasPrevious()方法和 previous()方法,通过这两个方法可以实现反向迭代元素,另外还提供了 add()方法用于增加元素。接下来通过一个案例来学习 ListIterator 迭代器的使用,如例所示。

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 集合,如例所示。

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 集合的用法,如例所示。

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 则说明有重复元素,就将该元素舍弃。整个存储的流程如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UqsJuM4q-1642058884091)(http://47.107.171.232/easily-j/images/20190107/d94b7825-46b9-4f7d-b952-ac3e15a91c94.png)]

根据前面的分析不难看出,当向集合中存入元素时,为了保证 HashSet 正常工作,要求在存入对象时,重写该类中的 hashCode()和 equals()方法。例中将字符串存入 HashSet 时,String 类已经重写了 hashCode()和 equals()方法。但是如果将 Student 对象存入 HashSet,结果又如何呢? 接下来通过一个案例来进行演示,如例所示。

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 相同的学生就是同一个学生,改写后的代码如例所示。

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 集合中没有重复的元素,并且可以对元素进行排序。所谓二叉树就是说每个节点最多有两个子节点的有序树,每个节点及其子节点组成的树称为子树,通常左侧的子节点称为“左子树”,右侧的子节点称为“右子树”,二叉树中元素的存储结构如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34uI5v3w-1642058884092)(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,如果以排序二叉树的方式来存储,在集合中的存储结构会形成一个树状结构,如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6zZh9Q2Z-1642058884093)(http://47.107.171.232/easily-j/images/20190107/7e0853d0-f516-4dc7-91f0-99da111d9845.png)]

从图可以看出,在向 TreeSet 集合依次存入元素时,首先将第 1 个存入的元素放在二叉树的最顶端,之后存入的元素与第一个元素比较,如果小于第一个元素就将该元素放在左子树上,如果大于第 1 个元素,就将该元素放在右子树上,依此类推,按照左子树元素小于右子树元素的顺序进行排序。当二叉树中已经存入一个 17 的元素时,再向集合中存入一个为 17 的元素时,TreeSet 会将把重复的元素去掉。

了解了二叉树存放元素的原理,接下来通过一个案例来演示 TreeSet 对元素的排序效果,如例所示。

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 接口,如例所示。

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 集合中字符串按照长度进行排序,如例所示。

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 接口中定义的一些通用方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Iyud3GU-1642058884095)(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 的用法。

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 行代码下面增加一行代码,如下所示:

map.put("3""Mary");

运行结果:

1: Jack
2: Rose
3: Mary

从图中可以看出,Map 中仍然只有 3 个元素,第二次添加的值“Mary”覆盖原来的值“Lucy”,因此证实了 Map 中的键必须是唯一的,不能重复。如果存储了相同的键,后存储的值则会覆盖原有的值,简而言之就是:键相同,值覆盖。在程序开发中,经常需要取出 Map 中所有的键和值,那么如何遍历 Map 中所有的键值对呢? 有两种方式可以实现,第一种方式就是先遍历 Map 集合中所有的键,再根据键获取相应的值,接下来就通过一个案例来演示这种遍历方式,如例所示。

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 集合的另外一种遍历方式是先获取集合中的所有的映射关系,然后从映射关系中取出键和值。接下来通过一个案例来演示这种遍历方式,如例所示。

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 集合,接下来通过一个案例来演示,如例所示。

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 的用法,如例所示。

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 的具体用法,如例所示。

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 集合时,也可以通过自定义比较器的方式对所有的键进行排序。接下来通过一个案例将学生对象按照学号由大到小的顺序进行排序,如例所示。

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 集合对这些配置项进行存取,接下来通过一个案例来学习,如例所示。

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 类型。换句话说,在程序中无法确定一个集合中的元素到底是什么类型的。那么在取出元素时,如果进行强制类型转换就很容易出错。接下来通过一个案例来演示这种情况,如例所示。

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 行代码进行修改,如下所示:

ArrayList<String> list = new ArrayList<String>(); //创建集合对象并指定泛型为String

程序编译报错的原因是修改后的代码限定了集合元素的数据类型,ArrayList这样的集合只能存储 String 类型的元素,程序在编译时,编译器检查出 Integer 类型的元素与 List 集合的规定类型不匹配,编译不通过,这样就可以在编译时解决错误,避免程序在运行时发生错误。

接下来使用泛型再次对例进行改写,如例所示。

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 其实就是 Type 的缩写,这里也可以使用其他字符,为了方便理解都定义为 T),将 save()方法的参数类型和 get()方法的返回值类型都声明为 T,那么在存入元素时元素的类型就被限定了,容器中就只能存入这种 T 类型的元素,在取出元素时就无须进行类型转换。接下来通过一个案例来看一下如何自定义泛型,如:

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,在实例化对象时通过将参数 T 指定为 Integer 类型,同时在调用 save()方法时传入的数据也是 Integer 类型,那么调用 get()方法取出的数据自然就是 Integer 类型,这样做的好处是不需要进行类型转换。

Collections 工具类

在程序中,针对集合的操作非常频繁,例如将集合中的元素排序、从集合中查找某个元素等。针对这些常见操作,JDK 提供了一个工具类专门用来操作集合,这个类就是 Collections,它位于 java.util 包中。Collections 类中提供了大量的方法用于对集合中的元素进行排序、查找和修改等操作,接下来对这些常用的方法进行介绍。

  • 排序操作

Collections 类中提供了一系列方法用于对 List 集合进行排序,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWYMWmm5-1642058884096)(http://47.107.171.232/easily-j/images/20190108/4af93b4c-b7a3-42cf-8e4f-401c5b0d235e.png)]

接下来通过一个案例针对表中的方法进行学习,如例所示。

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 类还提供了一些常用方法用于查找、替换集合中的元素,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VJhgKJjq-1642058884096)(http://47.107.171.232/easily-j/images/20190108/70f0e3d2-0f80-4424-a819-6d5c60b2be21.png)]

接下来使用表中的方法通过一个案例演示如何查找、替换集合中的元素,如例所示。

import java.util.ArrayList;
import java.util.Collections;

public class Example {
	public static void main(String[] args) {
		ArrayList list = new ArrayList();
		Collections.addAll(list, -32958);
		System.out.println("集合中的元素: " + list);
		System.out.println("集合中的最大元素: " + Collections.max(list));
		System.out.println("集合中的最小元素: " + Collections.min(list));
		Collections.replaceAll(list, 80); // 将集合中的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()方法的使用,如例所示。

import java.util.Arrays;

public class Example {
	public static void main(String[] args) {
		int[] arr = { 98352 }; // 初始化一个数据
		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)用于查找元素,接下来通过一个案例来学习该方法的使用,如例所示。

import java.util.Arrays;

public class Example {
	public static void main(String[] args) {
		int[] arr = { 98352 };
		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()方法只能针对排序后的数组进行元素的查找,因为该方法采用的是二分法查找。所谓二分法查找,就是每次将指定元素和数组中间位置的元素进行比较,从而排除掉其中的一半元素,这样的查找是非常高效的。接下来通过一个图例来演示二分法查找元素的过程,如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEZIPK8J-1642058884098)(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 表示被复制元素的最后索引(不包括),接下来通过一个案例来学习如何拷贝数组,如例所示。

import java.util.Arrays;

public class Example {
	public static void main(String[] args) {
		int[] arr = { 98352 };
		int[] copied = Arrays.copyOfRange(arr, 17);
		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}中从 arr1到 arr6这 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)方法,该方法可以将指定的值赋给数组中的每一个元素,接下来通过一个案例来演示如何填充元素,如例所示。

import java.util.Arrays;

public class Example {
	public static void main(String[] args) {
		int[] arr = { 1234 };
		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()方法的重写,只是用于返回指定数组的字符串形式。接下来通过一个案例来演示如何将数组转换为字符串,如例所示。

import java.util.Arrays;

public class Example {
	public static void main(String[] args) {
		int[] arr = { 98352 };
		String arrString = Arrays.toString(arr); // 使用toString()方法将数组转换为字符串
		System.out.println(arrString);
	}
}

运行结果:

[9, 8, 3, 5, 2]

微信公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tellsea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值