}
2)创建一个测试类,调用 Product 类的构造函数实例化三个对象,并将 Product 对象保存至 ArrayList 集合中。最后遍历该集合,输出商品信息。测试类的代码实现如下:
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);
}
}
}
该示例中的 ArrayList 集合中存放的是自定义类 Product 的对象,这与存储的 String 类的对象是相同的。与 Set 不同的是,List 集合中存在 get() 方法,该方法可以通过索引来获取所对应的值,获取的值为 Object 类,因此需要将该值转换为 Product 类,从而获取商品信息。
该程序的运行结果如下所示。
*************** 商品信息 ***************
商品编号:4,名称:木糖醇,价格:10.0
商品编号:5,名称:洗发水,价格:12.0
商品编号:3,名称:热水壶,价格:49.0
##### 例 2
在使用 List 集合时需要注意区分 indexOf() 方法和 lastIndexOf() 方法。前者是获得指定对象的最小索引位置,而后者是获得指定对象的最大索引位置。前提条件是指定的对象在 List 集合中有重复的对象,否则这两个方法获取的索引值相同。
下面的案例代码演示了 indexOf() 方法和 lastIndexOf() 方法的区别。
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(“|”));
}
上述代码创建一个 List 集合 list,然后添加了 7 个元素,由于索引从 0 开始,所以最后一个元素的索引为 6。输出结果如下:
list 集合中的元素数量:7
list 集合中的元素如下:
One、|、Two、|、Three、|、Four、
在 list 集合中’|‘第一次出现的位置是:1
在 list 集合中’|'最后一次出现的位置是:5
##### 例 3
使用 subList() 方法截取 List 集合中部分元素时要注意,新的集合中包含起始索引位置的元素,但是不包含结束索引位置的元素。例如,subList(1,4) 方法实际截取的是索引 1 到索引 3 的元素,并组成新的 List 集合。
下面的案例代码演示了 subList() 方法的具体用法。
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() + “、”);
}
}
输出结果如下:
list 集合中的元素数量:7
list 集合中的元素如下:
One、Two、Three、Four、Five、Six、Seven、
sublist 集合中元素数量:3
sublist 集合中的元素如下:
Three、Four、Five、
### LinkedList类
LinkedList 类采用链表结构保存对象,这种结构的优点是便于向集合中插入或者删除元素。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高,但是 LinkedList 类随机访问元素的速度则相对较慢。这里的随机访问是指检索集合中特定索引位置的元素。
LinkedList 类除了包含 Collection 接口和 List 接口中的所有方法之外,还特别提供了表 2 所示的方法。
| 方法名称 | 说明 |
| --- | --- |
| void addFirst(E e) | 将指定元素添加到此集合的开头 |
| void addLast(E e) | 将指定元素添加到此集合的末尾 |
| E getFirst() | 返回此集合的第一个元素 |
| E getLast() | 返回此集合的最后一个元素 |
| E removeFirst() | 删除此集合中的第一个元素 |
| E removeLast() | 删除此集合中的最后一个元素 |
##### 例 4
在仓库管理系统中要记录入库的商品名称,并且需要输出第一个录入的商品名称和最后—个商品名称。下面使用 LinkedList 集合来完成这些功能,实现代码如下:
public class Test {
public static void main(String[] args) {
LinkedList products = new LinkedList(); // 创建集合对象
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”);
}
}
}
如上述代码,首先创建了 5 个 String 对象,分别为 p1、p2、p3、p4 和 p5。同时将 p1、 p2、p3 和 p4 对象使用 add() 方法添加到 LinkedList 集合中,使用 addLast() 方法将 p5 对象添加到 LinkedList 集合中。分别调用 LinkedList 类中的 getFirst() 方法和 getLast() 方法获取第一个和最后一个商品名称。最后使用 removeLast() 方法将最后一个商品信息删除,并将剩余商品信息打印出来。
LinkedList 中的 是 [Java]( ) 中的泛型,用于指定集合中元素的数据类型,例如这里指定元素类型为 String,则该集合中不能添加非 String 类型的元素。
运行程序,执行结果如下:
*************** 商品信息 ***************
目前商品有:
六角螺母 10A 电缆线 5M 卷尺 4CM 原木方板 标准文件夹小柜
第一个商品的名称为:六角螺母
最后一个商品的名称为:标准文件夹小柜
删除最后的元素,目前商品有:
六角螺母 10A 电缆线 5M 卷尺 4CM 原木方板
### ArrayList 类和 LinkedList 类的区别
ArrayList 与 LinkedList 都是 List 接口的实现类,因此都实现了 List 的所有未实现的方法,只是实现的方式有所不同。
ArrayList 是基于动态数组[数据结构]( )的实现,访问元素速度优于 LinkedList。LinkedList 是基于链表数据结构的实现,占用的内存空间比较大,但在批量插入或删除数据时优于 ArrayList。
对于快速访问对象的需求,使用 ArrayList 实现执行效率上会比较好。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高。
不同的结构对应于不同的算法,有的考虑节省占用空间,有的考虑提高运行效率,对于程序员而言,它们就像是“熊掌”和“鱼肉”,不可兼得。高运行速度往往是以牺牲空间为代价的,而节省占用空间往往是以牺牲运行速度为代价的。
## Java Set集合:HashSet和TreeSet类
Set 集合类似于一个罐子,程序可以依次把多个对象“丢进”Set 集合,而 Set 集合通常不能记住元素的添加顺序。也就是说 Set 集合中的对象不按特定的方式排序,只是简单地把对象加入集合。Set 集合中不能包含重复的对象,并且最多只允许包含一个 null 元素。
Set 实现了 Collection 接口,它主要有两个常用的实现类:HashSet 类和 TreeSet类。
### HashSet 类
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时就是使用这个实现类。HashSet 是按照 Hash 算法来存储集合中的元素。因此具有很好的存取和查找性能。
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 集合中。
下面的代码演示了创建两种不同形式的 HashSet 对象。
HashSet hs = new HashSet(); // 调用无参的构造函数创建HashSet对象
HashSet hss = new HashSet(); // 创建泛型的 HashSet 集合对象
##### 例 1
编写一个 [Java]( ) 程序,使用 HashSet 创建一个 Set 集合,并向该集合中添加 4 套教程。具体实现代码如下:
public static void main(String[] args) {
HashSet courseSet = new HashSet(); // 创建一个空的 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 it = courseSet.iterator();
while (it.hasNext()) {
System.out.println(“《” + (String) it.next() + “》”); // 输出 Set 集合中的元素
}
System.out.println(“有” + courseSet.size() + “套精彩教程!”);
}
如上述代码,首先使用 HashSet 类的构造方法创建了一个 Set 集合,接着创建了 4 个 String 类型的对象,并将这些对象存储到 Set 集合中。使用 HashSet 类中的 iterator() 方法获取一个 Iterator 对象,并调用其 hasNext() 方法遍历集合元素,再将使用 next() 方法读取的元素强制转换为 String 类型。最后调用 HashSet 类中的 size() 方法获取集合元素个数。
运行该程序,输出的结果如下:
C语言中文网教程有:
《Java入门教程》
《C语言学习教程》
《Python基础教程》
《Golang入门教程》
有4套精彩教程!
注意:在以上示例中,如果再向 CourseSet 集合中再添加一个名称为“Java入门教程”的 String 对象,则输出的结果与上述执行结果相同。也就是说,如果向 Set 集合中添加两个相同的元素,则后添加的会覆盖前面添加的元素,即在 Set 集合中不会出现相同的元素。
### TreeSet 类
TreeSet 类同时实现了 Set 接口和 SortedSet 接口。SortedSet 接口是 Set 接口的子接口,可以实现对集合进行自然排序,因此使用 TreeSet 类实现的 Set 接口默认情况下是自然排序的,这里的自然排序指的是升序排序。
TreeSet 只能对实现了 Comparable 接口的类对象进行排序,因为 Comparable 接口中有一个 compareTo(Object o) 方法用于比较两个对象的大小。例如 a.compareTo(b),如果 a 和 b 相等,则该方法返回 0;如果 a 大于 b,则该方法返回大于 0 的值;如果 a 小于 b,则该方法返回小于 0 的值。
表 1 列举了 JDK 类库中实现 Comparable 接口的类,以及这些类对象的比较方式。
| 类 | 比较方式 |
| --- | --- |
| 包装类(BigDecimal、Biglnteger、 Byte、Double、 Float、Integer、Long 及 Short) | 按数字大小比较 |
| Character | 按字符的 Unicode 值的数字大小比较 |
| String | 按字符串中字符的 Unicode 值的数字大小比较 |
TreeSet 类除了实现 Collection 接口的所有方法之外,还提供了如表 2 所示的方法。
| 方法名称 | 说明 |
| --- | --- |
| E first() | 返回此集合中的第一个元素。其中,E 表示集合中元素的数据类型 |
| E last() | 返回此集合中的最后一个元素 |
| E poolFirst() | 获取并移除此集合中的第一个元素 |
| E poolLast() | 获取并移除此集合中的最后一个元素 |
| SortedSet subSet(E fromElement,E toElement) | 返回一个新的集合,新集合包含原集合中 fromElement 对象与 toElement 对象之间的所有对象。包含 fromElement 对象,不包含 toElement 对象 |
| SortedSet headSet<E toElement〉 | 返回一个新的集合,新集合包含原集合中 toElement 对象之前的所有对象。 不包含 toElement 对象 |
| SortedSet tailSet(E fromElement) | 返回一个新的集合,新集合包含原集合中 fromElement 对象之后的所有对 象。包含 fromElement 对象 |
注意:表面上看起来这些方法很多,其实很简单。因为 TreeSet 中的元素是有序的,所以增加了访问第一个、前一个、后一个、最后一个元素的方法,并提供了 3 个从 TreeSet 中截取子 TreeSet 的方法。
##### 例 2
本次有 5 名学生参加考试,当老师录入每名学生的成绩后,程序将按照从低到高的排列顺序显示学生成绩。此外,老师可以查询本次考试是否有满分的学生存在,不及格的成绩有哪些,90 分以上成绩的学生有几名。
下面使用 TreeSet 类来创建 Set 集合,完成学生成绩查询功能。具体的代码如下:
public class Test08 {
public static void main(String[] args) {
TreeSet scores = new TreeSet(); // 创建 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 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 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 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 集合对象 scores,并向该集合中添加 5 个 Double 对象。接着使用 while 循环遍历 scores 集合对象,输出该对象中的元素,然后调用 TreeSet 类中的 contains() 方法获取该集合中是否存在指定的元素。最后分别调用 TreeSet 类中的 headSet() 方法和 tailSet() 方法获取不及格的成绩和 90 分以上的成绩。
运行该程序,执行结果如下所示。
------------学生成绩管理系统-------------
第1个学生成绩:
53
第2个学生成绩:
48
第3个学生成绩:
85
第4个学生成绩:
98
第5个学生成绩:
68
学生成绩从低到高的排序为:
48.0 53.0 68.0 85.0 98.0
请输入要查询的成绩:
90
成绩为: 90.0 的学生不存在!
不及格的成绩有:
48.0 53.0
90 分以上的成绩有:
98.0
注意:在使用自然排序时只能向 TreeSet 集合中添加相同数据类型的对象,否则会抛出 ClassCastException 异常。如果向 TreeSet 集合中添加了一个 Double 类型的对象,则后面只能添加 Double 对象,不能再添加其他类型的对象,例如 String 对象等。
## Java Map集合详解
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 类可以对键对象进行排序。
Map 接口中提供的常用方法如表 1 所示。
| 方法名称 | 说明 |
| --- | --- |
| void clear() | 删除该 Map 对象中的所有 key-value 对。 |
| boolean containsKey(Object key) | 查询 Map 中是否包含指定的 key,如果包含则返回 true。 |
| boolean containsValue(Object value) | 查询 Map 中是否包含一个或多个 value,如果包含则返回 true。 |
| V get(Object key) | 返回 Map 集合中指定键对象所对应的值。V 表示值的数据类型 |
| V put(K key, V value) | 向 Map 集合中添加键-值对,如果当前 Map 中已有一个与该 key 相等的 key-value 对,则新的 key-value 对会覆盖原来的 key-value 对。 |
| void putAll(Map m) | 将指定 Map 中的 key-value 对复制到本 Map 中。 |
| V remove(Object key) | 从 Map 集合中删除 key 对应的键-值对,返回 key 对应的 value,如果该 key 不存在,则返回 null |
| boolean remove(Object key, Object value) | 这是 [Java]( ) 8 新增的方法,删除指定 key、value 所对应的 key-value 对。如果从该 Map 中成功地删除该 key-value 对,该方法返回 true,否则返回 false。 |
| Set entrySet() | 返回 Map 集合中所有键-值对的 Set 集合,此 Set 集合中元素的数据类型为 Map.Entry |
| Set keySet() | 返回 Map 集合中所有键对象的 Set 集合 |
| boolean isEmpty() | 查询该 Map 是否为空(即不包含任何 key-value 对),如果为空则返回 true。 |
| int size() | 返回该 Map 里 key-value 对的个数 |
| Collection values() | 返回该 Map 里所有 value 组成的 Collection |
Map 集合最典型的用法就是成对地添加、删除 key-value 对,接下来即可判断该 Map 中是否包含指定 key,也可以通过 Map 提供的 keySet() 方法获取所有 key 组成的集合,进而遍历 Map 中所有的 key-value 对。下面程序示范了 Map 的基本功能。
##### 例 1
每名学生都有属于自己的唯一编号,即学号。在毕业时需要将该学生的信息从系统中移除。
下面编写 Java 程序,使用 HashMap 来存储学生信息,其键为学生学号,值为姓名。毕业时,需要用户输入学生的学号,并根据学号进行删除操作。具体的实现代码如下:
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);
}
}
}
在该程序中,两次使用 while 循环遍历 HashMap 集合。当有学生毕业时,用户需要输入该学生的学号,根据学号使用 HashMap 类的 remove() 方法将对应的元素删除。程序运行结果如下所示。
******** 学生列表 ********
学号:44,姓名:李国量
学号:55,姓名:王路路
学号:22,姓名:刘思诚
学号:33,姓名:王强文
学号:11,姓名:张浩太
请输入要删除的学号:
22
******** 学生列表 ********
学号:44,姓名:李国量
学号:55,姓名:王路路
学号:33,姓名:王强文
学号:11,姓名:张浩太
******** 学生列表 ********
学号:44,姓名:李国量
学号:55,姓名:王路路
学号:22,姓名:刘思诚
学号:33,姓名:王强文
学号:11,姓名:张浩太
请输入要删除的学号:
44
******** 学生列表 ********
学号:55,姓名:王路路
学号:22,姓名:刘思诚
学号:33,姓名:王强文
学号:11,姓名:张浩太
注意:TreeMap 类的使用方法与 HashMap 类相同,唯一不同的是 TreeMap 类可以对键对象进行排序,这里不再赘述。
## Java遍历Map集合的四种方式
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);
}
## Java Collections类操作集合详解
Collections 类是 [Java]( ) 提供的一个操作 Set、List 和 Map 等集合的工具类。Collections 类提供了许多操作集合的静态方法,借助这些静态方法可以实现集合元素的排序、查找替换和复制等操作。下面介绍 Collections 类中操作集合的常用方法。
### 排序(正向和逆向)
Collections 提供了如下方法用于对 List 集合元素进行排序。
* void reverse(List list):对指定 List 集合元素进行逆向排序。
* void shuffle(List list):对 List 集合元素进行随机排序(shuffle 方法模拟了“洗牌”动作)。
* void sort(List list):根据元素的自然顺序对指定 List 集合的元素按升序进行排序。
* void sort(List list, Comparator c):根据指定 Comparator 产生的顺序对 List 集合元素进行排序。
* void swap(List list, int i, int j):将指定 List 集合中的 i 处元素和 j 处元素进行交换。
* void rotate(List list, int distance):当 distance 为正数时,将 list 集合的后 distance 个元素“整体”移到前面;当 distance 为负数时,将 list 集合的前 distance 个元素“整体”移到后面。该方法不会改变集合的长度。
下面程序简单示范了利用 Collections 工具类来操作 List 集合。
##### 例 1
编写一个程序,对用户输入的 5 个商品价格进行排序后输出。这里要求使用 Collections 类中 sort() 方法按从低到高的顺序对其进行排序,最后将排序后的成绩输出。
具体实现代码如下:
public class Test1 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List prices = new ArrayList();
for (int i = 0; i < 5; i++) {
System.out.println(“请输入第 " + (i + 1) + " 个商品的价格:”);
int p = input.nextInt();
prices.add(Integer.valueOf§); // 将录入的价格保存到List集合中
}
Collections.sort(prices); // 调用sort()方法对集合进行排序
System.out.println(“价格从低到高的排列为:”);
for (int i = 0; i < prices.size(); i++) {
System.out.print(prices.get(i) + “\t”);
}
}
}
如上述代码,循环录入 5 个价格,并将每个价格都存储到已定义好的 List 集合 prices 中,然后使用 Collections 类的 sort() 方法对该集合元素进行升序排序。最后使用 for 循环遍历 users 集合,输出该集合中的元素。
该程序的执行结果如下所示。
请输入第 1 个商品的价格:
85
请输入第 2 个商品的价格:
48
请输入第 3 个商品的价格:
66
请输入第 4 个商品的价格:
80
请输入第 5 个商品的价格:
18
价格从低到高的排列为:
18 48 66 80 85
##### 例 2
循环录入 5 个商品的名称,并按录入时间的先后顺序进行降序排序,即后录入的先输出。
下面编写程序,使用 Collections 类的 reverse() 方法对保存到 List 集合中的 5 个商品名称进行反转排序,并输出排序后的商品信息。具体的实现代码如下:
public class Test2 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List students = new ArrayList();
System.out.println(“******** 商品信息 ********”);
for (int i = 0; i < 5; i++) {
System.out.println(“请输入第 " + (i + 1) + " 个商品的名称:”);
String name = input.next();
students.add(name); // 将录入的商品名称存到List集合中
}
Collections.reverse(students); // 调用reverse()方法对集合元素进行反转排序
System.out.println(“按录入时间的先后顺序进行降序排列为:”);
for (int i = 0; i < 5; i++) {
System.out.print(students.get(i) + “\t”);
}
}
}
如上述代码,首先循环录入 5 个商品的名称,并将这些名称保存到 List 集合中,然后调用 Collections 类中的 reverse() 方法对该集合元素进行反转排序。最后使用 for 循环将排序后的集合元素输出。
执行该程序,输出结果如下所示。
******** 商品信息 ********
请输入第 1 个商品的名称:
果粒橙
请输入第 2 个商品的名称:
冰红茶
请输入第 3 个商品的名称:
矿泉水
请输入第 4 个商品的名称:
软面包
请输入第 5 个商品的名称:
巧克力
按录入时间的先后顺序进行降序排列为:
巧克力 软面包 矿泉水 冰红茶 果粒橙
### 查找、替换操作
Collections 还提供了如下常用的用于查找、替换集合元素的方法。
* int binarySearch(List list, Object key):使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List 集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态。
* Object max(Collection coll):根据元素的自然顺序,返回给定集合中的最大元素。
* Object max(Collection coll, Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最大元素。
* Object min(Collection coll):根据元素的自然顺序,返回给定集合中的最小元素。
* Object min(Collection coll, Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最小元素。
* void fill(List list, Object obj):使用指定元素 obj 替换指定 List 集合中的所有元素。
* int frequency(Collection c, Object o):返回指定集合中指定元素的出现次数。
* int indexOfSubList(List source, List target):返回子 List 对象在父 List 对象中第一次出现的位置索引;如果父 List 中没有出现这样的子 List,则返回 -1。
* int lastIndexOfSubList(List source, List target):返回子 List 对象在父 List 对象中最后一次出现的位置索引;如果父 List 中没有岀现这样的子 List,则返回 -1。
* boolean replaceAll(List list, Object oldVal, Object newVal):使用一个新值 newVal 替换 List 对象的所有旧值 oldVal。
下面程序简单示范了 Collections 工具类的用法。
##### 例 3
编写一个程序,要求用户输入 3 个商品名称,然后使用 Collections 类中的 fill() 方法对商品信息进行重置操作,即将所有名称都更改为“未填写”。具体的实现代码如下:
public class Test3 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List products = new ArrayList();
System.out.println(“******** 商品信息 ********”);
for (int i = 0; i < 3; i++) {
System.out.println(“请输入第 " + (i + 1) + " 个商品的名称:”);
String name = input.next();
products.add(name); // 将用户录入的商品名称保存到List集合中
}
System.out.println(“重置商品信息,将所有名称都更改为’未填写’”);
Collections.fill(products, “未填写”);
System.out.println(“重置后的商品信息为:”);
for (int i = 0; i < products.size(); i++) {
System.out.print(products.get(i) + “\t”);
}
}
}
如上述代码,首先循环录入 3 个商品名称,并将这些商品信息存储到 List 集合中,然后调用 Collections 类中的 fill() 方法将该集合中的所有元素值替换为“未填写”。最后使用 for 循环将替换后的集合元素输出。
运行该程序,执行结果如下所示。
******** 商品信息 ********
请输入第 1 个商品的名称:
苏打水
请输入第 2 个商品的名称:
矿泉水
请输入第 3 个商品的名称:
冰红茶
重置商品信息,将所有名称都更改为’未填写’
重置后的商品信息为:
未填写 未填写 未填写
##### 例 4
在一个集合中保存 4 个数据,分别输出最大最小元素和指定数据在集合中出现的次数。
public class Test4 {
public static void main(String[] args) {
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
System.out.println(nums); // 输出:[2, -5, 3, 0]
System.out.println(Collections.max(nums)); // 输出最大元素,将输出 3
System.out.println(Collections.min(nums)); // 输出最小元素,将输出-5
Collections.replaceAll(nums, 0, 1);// 将 nums中的 0 使用 1 来代替
System.out.println(nums); // 输出:[2, -5, 3, 1]
// 判断-5在List集合中出现的次数,返回1
System.out.println(Collections.frequency(nums, -5));
Collections.sort(nums); // 对 nums集合排序
System.out.println(nums); // 输出:[-5, 1, 2, 3]
// 只有排序后的List集合才可用二分法查询,输出3
System.out.println(Collections.binarySearch(nums, 3));
}
}
如上述代码,向 List 集合中添加 4 个数据,然后调用 Collections 类中的 max() 和 min() 方法输出集合中的最大最小元素,replaceAll() 替换元素,frequency() 判断指定数据在 List 集合中出现的次数,最后用 binarySearch() 进行二分法查询。
运行上述程序,执行结果如下:
[2, -5, 3, 0]
3
-5
[2, -5, 3, 1]
1
[-5, 1, 2, 3]
3
### 复制
Collections 类的 copy() 静态方法用于将指定集合中的所有元素复制到另一个集合中。执行 copy() 方法后,目标集合中每个已复制元素的索引将等同于源集合中该元素的索引。
copy() 方法的语法格式如下:
void copy(List <? super T> dest,List<? extends T> src)
其中,dest 表示目标集合对象,src 表示源集合对象。
注意:目标集合的长度至少和源集合的长度相同,如果目标集合的长度更长,则不影响目标集合中的其余元素。如果目标集合长度不够而无法包含整个源集合元素,程序将抛出 IndexOutOfBoundsException 异常。
##### 例 5
在一个集合中保存了 5 个商品名称,现在要使用 Collections 类中的 copy() 方法将其中的 3 个替换掉。具体实现的代码如下:
public class Test5 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List srcList = new ArrayList();
List destList = new ArrayList();
destList.add(“苏打水”);
destList.add(“木糖醇”);
destList.add(“方便面”);
destList.add(“火腿肠”);
destList.add(“冰红茶”);
System.out.println(“原有商品如下:”);
for (int i = 0; i < destList.size(); i++) {
System.out.println(destList.get(i));
}
System.out.println(“输入替换的商品名称:”);
for (int i = 0; i < 3; i++) {
System.out.println(“第 " + (i + 1) + " 个商品:”);
String name = input.next();
srcList.add(name);
}
// 调用copy()方法将当前商品信息复制到原有商品信息集合中
Collections.copy(destList, srcList);
System.out.println(“当前商品有:”);
for (int i = 0; i < destList.size(); i++) {
System.out.print(destList.get(i) + “\t”);
}
}
}
如上述代码,首先创建了两个 List 对象 srcList 和 destList,并向 destList 集合中添加了 5 个元素,向 srcList 集合中添加了 3 个元素,然后调用 Collections 类中 copy() 方法将 srcList 集合中的全部元素复制到 destList 集合中。由于 destList 集合中含有 5 个元素,故最后两个元素不会被覆盖。
运行该程序,具体的执行结果如下所示。
原有商品如下:
苏打水
木糖醇
方便面
火腿肠
冰红茶
输入替换的商品名称:
第 1 个商品:
燕麦片
第 2 个商品:
八宝粥
第 3 个商品:
软面包
当前商品有:
燕麦片 八宝粥 软面包 火腿肠 冰红茶
## Java Iterator(迭代器)遍历Collection集合元素
Iterator(迭代器)是一个接口,它的作用就是遍历容器的所有元素,也是 [Java]( ) 集合框架的成员,但它与 Collection 和 Map 系列的集合不一样,Collection 和 Map 系列集合主要用于盛装其他对象,而 Iterator 则主要用于遍历(即迭代访问)Collection 集合中的元素。
Iterator 接口隐藏了各种 Collection 实现类的底层细节,向应用程序提供了遍历 Collection 集合元素的统一编程接口。Iterator 接口里定义了如下 4 个方法。
* boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回 true。
* Object next():返回集合里的下一个元素。
* void remove():删除集合里上一次 next 方法返回的元素。
* void forEachRemaining(Consumer action):这是 Java 8 为 Iterator 新增的默认方法,该方法可使用 Lambda 表达式来遍历集合元素。
下面程序示范了通过 Iterator 接口来遍历集合元素。
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
// 创建一个集合
Collection objs = new HashSet();
objs.add(“C语言中文网Java教程”);
objs.add(“C语言中文网C语言教程”);
objs.add(“C语言中文网C++教程”);
// 调用forEach()方法遍历集合
// 获取books集合对应的迭代器
Iterator it = objs.iterator();
while (it.hasNext()) {
// it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
String obj = (String) it.next();
System.out.println(obj);
if (obj.equals(“C语言中文网C语言教程”)) {
// 从集合中删除上一次next()方法返回的元素
it.remove();
}
// 对book变量赋值,不会改变集合元素本身
obj = “C语言中文网Python语言教程”;
}
System.out.println(objs);
}
}
从上面代码中可以看出,Iterator 仅用于遍历集合,如果需要创建 Iterator 对象,则必须有一个被迭代的集合。没有集合的 Iterator 没有存在的价值。
注意:Iterator 必须依附于 Collection 对象,若有一个 Iterator 对象,则必然有一个与之关联的 Collection 对象。Iterator 提供了两个方法来迭代访问 Collection 集合里的元素,并可通过 remove() 方法来删除集合中上一次 next() 方法返回的集合元素。
上面程序中第 24 行代码对迭代变量 obj 进行赋值,但当再次输岀 objs 集合时,会看到集合里的元素没有任何改变。所以当使用 Iterator 对集合元素进行迭代时,Iterator 并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。
当使用 Iterator 迭代访问 Collection 集合元素时,Collection 集合里的元素不能被改变,只有通过 Iterator 的 remove() 方法删除上一次 next() 方法返回的集合元素才可以,否则将会引发“java.util.ConcurrentModificationException”异常。下面程序示范了这一点。
public class IteratorErrorTest {
public static void main(String[] args) {
// 创建一个集合
Collection objs = new HashSet();
objs.add(“C语言中文网Java教程”);
objs.add(“C语言中文网C语言教程”);
objs.add(“C语言中文网C++教程”);
// 获取books集合对应的迭代器
Iterator it = objs.iterator();
while (it.hasNext()) {
String obj = (String) it.next();
System.out.println(obj);
if (obj.equals(“C语言中文网C++教程”)) {
// 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
objs.remove(obj);
}
}
}
}
输出结果为:
C语言中文网C++教程
Exception in thread “main” java.util.ConcurrentModificationException
at java.util.HashMap
H
a
s
h
I
t
e
r
a
t
o
r
.
n
e
x
t
N
o
d
e
(
U
n
k
n
o
w
n
S
o
u
r
c
e
)
a
t
j
a
v
a
.
u
t
i
l
.
H
a
s
h
M
a
p
HashIterator.nextNode(Unknown Source) at java.util.HashMap
HashIterator.nextNode(UnknownSource)atjava.util.HashMapKeyIterator.next(Unknown Source)
at IteratorErrorTest.main(IteratorErrorTest.java:15)
上面程序中第 15 行代码位于 Iterator 迭代块内,也就是在 Iterator 迭代 Collection 集合过程中修改了 Collection 集合,所以程序将在运行时引发异常。
Iterator 迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发 ConcurrentModificationException 异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。
>
> 快速失败(fail-fast)机制,是 Java Collection 集合中的一种错误检测机制。
>
>
>
注意:上面程序如果改为删除“C语言中文网C语言教程”字符串,则不会引发异常。这样可能有些读者会“心存侥幸”地想,在迭代时好像也可以删除集合元素啊。实际上这是一种危险的行为。对于 HashSet 以及后面的 ArrayList 等,迭代时删除元素都会导致异常。只有在删除集合中的某个特定元素时才不会抛出异常,这是由集合类的实现代码决定的,程序员不应该这么做。
## Java使用foreach循环遍历Collection集合
《[Java Iterator遍历Collection集合元素]( )》一节中主要讲解如何使用 Iterator 接口迭代访问 Collection 集合里的元素,除了这个方法之外,我们还可以使用 [Java]( ) 5 提供的 foreach 循环迭代访问集合元素,而且更加便捷。如下程序示范了使用 foreach 循环来迭代访问集合元素。
public class ForeachTest {
public static void main(String[] args) {
// 创建一个集合
Collection objs = new HashSet();
objs.add(“C语言中文网Java教程”);
objs.add(“C语言中文网C语言教程”);
objs.add(“C语言中文网C++教程”);
for (Object obj : objs) {
// 此处的obj变量也不是集合元素本身
String obj1 = (String) obj;
System.out.println(obj1);
if (obj1.equals(“C语言中文网Java教程”)) {
// 下面代码会引发 ConcurrentModificationException 异常
objs.remove(obj);
}
}
System.out.println(objs);
}
}
输出结果为:
C语言中文网C++教程
C语言中文网C语言教程
C语言中文网Java教程
[C语言中文网C++教程, C语言中文网C语言教程]
上面代码使用 foreach 循环来迭代访问 Collection 集合里的元素更加简洁,这正是 JDK 1.5 的 foreach 循环带来的优势。与使用 Iterator 接口迭代访问集合元素类似的是,foreach 循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量,因此在 foreach 循环中修改迭代变量的值也没有任何实际意义。
同样,当使用 foreach 循环迭代访问集合元素时,该集合也不能被改变,否则将引发 ConcurrentModificationException 异常。所以上面程序中第 14 行代码处将引发该异常。
## Java泛型简明教程
前面我们提到 [Java]( ) 集合有个缺点,就是把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了 Object 类型(其运行时类型没变)。
Java 集合之所以被设计成这样,是因为集合的设计者不知道我们会用集合来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要求具有很好的通用性,但这样做带来如下两个问题:
1. 集合对元素类型没有任何限制,这样可能引发一些问题。例如,想创建一个只能保存 Dog 对象的集合,但程序也可以轻易地将 Cat 对象“丢”进去,所以可能引发异常。
2. 由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是 Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既增加了编程的复杂度,也可能引发 ClassCastException 异常。
所以为了解决上述问题,从 Java 1.5 开始提供了泛型。泛型可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率。本节将详细介绍 Java 中泛型的使用。
### 泛型集合
泛型本质上是提供类型的“类型参数”,也就是参数化类型。我们可以为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全。
##### 例 1
下面将结合泛型与集合编写一个案例实现图书信息输出。
1)首先需要创建一个表示图书的实体类 Book,其中包括的图书信息有图书编号、图书名称和价格。Book 类的具体代码如下:
public class Book {
private int Id; // 图书编号
private String Name; // 图书名称
private int Price; // 图书价格
public Book(int id, String name, int price) { // 构造方法
this.Id = id;
this.Name = name;
this.Price = price;
}
public String toString() { // 重写 toString()方法
return this.Id + ", " + this.Name + “,” + this.Price;
}
}
2)使用 Book 作为类型创建 Map 和 List 两个泛型集合,然后向集合中添加图书元素,最后输出集合中的内容。具体代码如下:
public class Test14 {
public static void main(String[] args) {
// 创建3个Book对象
Book book1 = new Book(1, “唐诗三百首”, 8);
Book book2 = new Book(2, “小星星”, 12);
Book book3 = new Book(3, “成语大全”, 22);
Map<Integer, Book> books = new HashMap<Integer, Book>(); // 定义泛型 Map 集合
books.put(1001, book1); // 将第一个 Book 对象存储到 Map 中
books.put(1002, book2); // 将第二个 Book 对象存储到 Map 中
books.put(1003, book3); // 将第三个 Book 对象存储到 Map 中
System.out.println(“泛型Map存储的图书信息如下:”);
for (Integer id : books.keySet()) {
// 遍历键
System.out.print(id + “——”);
System.out.println(books.get(id)); // 不需要类型转换
}
List bookList = new ArrayList(); // 定义泛型的 List 集合
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);
System.out.println(“泛型List存储的图书信息如下:”);
for (int i = 0; i < bookList.size(); i++) {
System.out.println(bookList.get(i)); // 这里不需要类型转换
}
}
}
在该示例中,第 7 行代码创建了一个键类型为 Integer、值类型为 Book 的泛型集合,即指明了该 Map 集合中存放的键必须是 Integer 类型、值必须为 Book 类型,否则编译出错。在获取 Map 集合中的元素时,不需要将`books.get(id);`获取的值强制转换为 Book 类型,程序会隐式转换。在创建 List 集合时,同样使用了泛型,因此在获取集合中的元素时也不需要将`bookList.get(i)`代码强制转换为 Book 类型,程序会隐式转换。
执行结果如下:
泛型Map存储的图书信息如下:
1001——1, 唐诗三百首,8
1003——3, 成语大全,22
1002——2, 小星星,12
泛型List存储的图书信息如下:
1, 唐诗三百首,8
2, 小星星,12
3, 成语大全,22
### 泛型类
除了可以定义泛型集合之外,还可以直接限定泛型类的类型参数。语法格式如下:
public class class_name<data_type1,data_type2,…>{}
其中,class\_name 表示类的名称,data\_ type1 等表示类型参数。Java 泛型支持声明一个以上的类型参数,只需要将类型用逗号隔开即可。
泛型类一般用于类中的属性类型不确定的情况下。在声明属性时,使用下面的语句:
private data_type1 property_name1;
private data_type2 property_name2;
该语句中的 data\_type1 与类声明中的 data\_type1 表示的是同一种数据类型。
##### 例 2
在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。例如,下面的示例代码创建了一个表示学生的泛型类,该类中包括 3 个属性,分别是姓名、年龄和性别。
public class Stu<N, A, S> {
private N name; // 姓名
private A age; // 年龄
private S sex; // 性别
// 创建类的构造函数
public Stu(N name, A age, S sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 下面是上面3个属性的setter/getter方法
public N getName() {
return name;
}
public void setName(N name) {
this.name = name;
}
public A getAge() {
return age;
}
public void setAge(A age) {
this.age = age;
}
public S getSex() {
return sex;
}
public void setSex(S sex) {
this.sex = sex;
}
}
接着创建测试类。在测试类中调用 Stu 类的构造方法实例化 Stu 对象,并给该类中的 3 个属性赋予初始值,最终需要输出学生信息。测试类的代码实现如下:
public class Test14 {
public static void main(String[] args) {
Stu<String, Integer, Character> stu = new Stu<String, Integer, Character>(“张晓玲”, 28, ‘女’);
String name = stu.getName();
Integer age = stu.getAge();
Character sex = stu.getSex();
System.out.println(“学生信息如下:”);
System.out.println(“学生姓名:” + name + “,年龄:” + age + “,性别:” + sex);
}
}
该程序的运行结果如下:
学生信息如下:
学生姓名:张晓玲,年龄:28,性别:女
在该程序的 Stu 类中,定义了 3 个类型参数,分别使用 N、A 和 S 来代替,同时实现了这 3 个属性的 setter/getter 方法。在主类中,调用 Stu 类的构造函数创建了 Stu 类的对象,同时指定 3 个类型参数,分别为 String、Integer 和 Character。在获取学生姓名、年龄和性别时,不需要类型转换,程序隐式地将 Object 类型的数据转换为相应的数据类型。
### 泛型方法
到目前为止,我们所使用的泛型都是应用于整个类上。泛型同样可以在类中包含参数化的方法,而方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是不是泛型没有关系。
泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。另外,对一个 static 的方法而言,无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。
定义泛型方法的语法格式如下:
[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表])
例如:
public static List find(Class cs,int userId){}
一般来说编写 Java 泛型方法,其返回值类型至少有一个参数类型应该是泛型,而且类型应该是一致的,如果只有返回值类型或参数类型之一使用了泛型,那么这个泛型方法的使用就被限制了。下面就来定义一个泛型方法,具体介绍泛型方法的创建和使用。
##### 例 3
使用泛型方法打印图书信息。定义泛型方法,参数类型使用“T”来代替。在方法的主体中打印出图书信息。代码的实现如下:
public class Test16 {
public static void List(T book) { // 定义泛型方法
if (book != null) {
System.out.println(book);
}
}
public static void main(String[] args) {
Book stu = new Book(1, “细学 Java 编程”, 28);
List(stu); // 调用泛型方法
}
}
该程序中的 Book 类为前面示例中使用到的 Book 类。在该程序中定义了一个名称为 List 的方法,该方法的返回值类型为 void,类型参数使用“T”来代替。在调用该泛型方法时,将一个 Book 对象作为参数传递到该方法中,相当于指明了该泛型方法的参数类型为 Book。
该程序的运行结果如下:
1, 细学 Java 编程,28
### 泛型的高级用法
泛型的用法非常灵活,除在集合、类和方法中使用外,本节将从三个方面介绍泛型的高级用法,包括限制泛型可用类型、使用类型通配符、继承泛型类和实现泛型接口。
##### 1. 限制泛型可用类型
在 Java 中默认可以使用任何类型来实例化一个泛型类对象。当然也可以对泛型类实例的类型进行限制,语法格式如下:
class 类名称
其中,anyClass 指某个接口或类。使用泛型限制后,泛型类的类型必须实现或继承 anyClass 这个接口或类。无论 anyClass 是接口还是类,在进行泛型限制时都必须使用 extends 关键字。
例如,在下面的示例代码中创建了一个 ListClass 类,并对该类的类型限制为只能是实现 List 接口的类。
// 限制ListClass的泛型类型必须实现List接口
public class ListClass {
public static void main(String[] args) {
// 实例化使用ArrayList的泛型类ListClass,正确
ListClass lc1 = new ListClass();
// 实例化使用LinkedList的泛型类LlstClass,正确
ListClass lc2 = new ListClass();
// 实例化使用HashMap的泛型类ListClass,错误,因为HasMap没有实现List接口
// ListClass lc3=new ListClass();
}
}
在上述代码中,定义 ListClass 类时设置泛型类型必须实现 List 接口。例如,ArrayList 和 LinkedList 都实现了 List 接口,所以可以实例化 ListClass 类。而 HashMap 没有实现 List 接口,所以在实例化 ListClass 类时会报错。
当没有使用 extends 关键字限制泛型类型时,其实是默认使用 Object 类作为泛型类型。因此,Object 类下的所有子类都可以实例化泛型类对象,如图 1 所示的这两种情况。
![img](https://img-blog.csdnimg.cn/img_convert/e7e50d8399484a3c9132e732635bf849.png)
图1 两个等价的泛型类
##### 2. 使用类型通配符
Java 中的泛型还支持使用类型通配符,它的作用是在创建一个泛型类对象时限制这个泛型类的类型必须实现或继承某个接口或类。
使用泛型类型通配符的语法格式如下:
泛型类名称<? extends List>a = null;
其中,“<? extends List>”作为一个整体表示类型未知,当需要使用泛型对象时,可以单独实例化。
例如,下面的示例代码演示了类型通配符的使用。
A<? extends List>a = null;
a = new A (); // 正确
b = new A (); // 正确
c = new A (); // 错误
在上述代码中,同样由于 HashMap 类没有实现 List 接口,所以在编译时会报错。
##### 3. 继承泛型类和实现泛型接口
定义为泛型的类和接口也可以被继承和实现。例如下面的示例代码演示了如何继承泛型类。
public class FatherClass{}
public class SonClass<T1,T2,T3> extents FatherClass{}
如果要在 SonClass 类继承 FatherClass 类时保留父类的泛型类型,需要在继承时指定,否则直接使用 extends FatherClass 语句进行继承操作,此时 T1、T2 和 T3 都会自动变为 Object,所以一般情况下都将父类的泛型类型保留。
下面的示例代码演示了如何在泛型中实现接口。
interface interface1{}
interface SubClass<T1,T2,T3> implements
Interface1{}
## Java图书信息查询
前面详细介绍了 [Java]( ) 中各集合的使用,像 [Set 集合]( )和 [List 集合]( )等,另外,还结合泛型讲解了一些高级应用。在实际开发中,[泛型集合]( )是较常用的,一般定义集合都会使用泛型的形式来定义。本节将使用泛型集合来模拟实现某图书管理系统的查询功能。
在图书管理系统中为了方便管理图书,将图书划分为几个类别。每个类别下有很多图书,每本图书都有相对应的类别,这就具备了一对多的关系映射,即一个类别对应多本图书。
在这种情况下就可以使用 Map 映射来存储类别和图书信息,其键为 Category(类别)类型,值为 List 类型(Book 类为图书类),然后使用嵌套循环遍历输出每个类别所对应的多个图书信息。具体的实现步骤如下。
1)创建表示图书类别的 Category 类,在该类中有两个属性:id 和 name,分别表示编号和类别名称,并实现了它们的 setXxx() 和 getXxx() 方法,具体内容如下:
public class Category {
private int id; // 类别编号
private String name; // 类别名称
public Category(int id, String name) {
this.id = id;
this.name = name;
}
public String toString() {
return “所属分类:” + this.name;
}
// 上面两个属性的setXxx()和getXxx()方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2)创建表示图书明细信息的 BookInfo 类,在该类中包含 5 个属性:id、name、price、author 和 startTime,分别表示图书编号、名称、价格、作者和出版时间,同样实现了它们的 setXxx() 和 getXxx() 方法,具体内容如下:
public class BookInfo {
private int id; // 编号
private String name; // 名称
private int price; // 价格
private String author; // 作者
private String startTime; // 出版时间
public BookInfo(int id, String name, int price, String author, String startTime) {
this.id = id;
this.name = name;
this.price = price;
this.author = author;
this.startTime = startTime;
}
public String toString() {
return this.id + “\t\t” + this.name + “\t\t” + this.price + “\t\t” + this.author + “\t\t” + this.startTime;
}
// 上面5个属性的 setXxx() 和 getXxx() 方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.id = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
}
3)创建 CategoryDao 类,在该类中定义一个泛型的 Map 映射,其键为 Category 类型的对象,值为 List 类型的对象,并定义 printCategoryInfo() 方法,用于打印类别和图书明细信息。具体代码如下:
public class CategoryDao {
// 定义泛型Map,存储图书信息
public static Map<Category, List> categoryMap = new HashMap<Category, List>();
public static void printDeptmentInfo() {
for (Category cate : categoryMap.keySet()) {
System.out.println(“所属类别:” + cate.getName());
List books = categoryMap.get(cate);
System.out.println(“图书编号\t\t图书名称\t\t图书价格\t\t图书作者\t\t出版时间”);
for (int i = 0; i < books.size(); i++) {
BookInfo b = books.get(i); // 获取图书
System.out.println(b.getId() + “\t\t” + b.getName() + “\t\t” + b.getPrice() + “\t\t” + b.getAuthor()
+ “\t\t” + b.getStartTime());
}
System.out.println();
}
}
}
4)创建测试类 Test17,在该类中定义 4 个 Deptment 对象和 8 个 People 对象,并将 8 个 People 对象分成 4 组,存储到 4 个 List 集合中,然后将 4 个 Deptment 对象和 4 个 List 集合按照——对应的关系存储到 DeptmentDao 类中的 peoplesMap 映射中。最后调用 DeptmentDao 类中的 printDeptmentInfo() 方法打印类别及对应的图书信息。具体的代码如下:
public class Test17 {
public static void main(String[] args) {
Category category1 = new Category(1, “数据库”); // 创建类别信息
Category category2 = new Category(2, “程序设计”); // 创建类别信息
Category category3 = new Category(3, “平面设计”); // 创建类别信息
BookInfo book1 = new BookInfo(1, “细说 Java 编程”, 25, “张晓玲”, “2012-01-01”); // 创建图书信息
BookInfo book2 = new BookInfo(2, “影视后期处理宝典”, 78, “刘水波”, “2012-10-05”); // 创建图书信息
BookInfo book3 = new BookInfo(3, “MySQL 从入门到精通”, 41, “王志亮”, “2012-3-2”); // 创建图书信息
BookInfo book4 = new BookInfo(4, “Java 从入门到精通”, 27, “陈奚静”, “2012-11-01”); // 创建图书信息
BookInfo book5 = new BookInfo(5, “SQL Server 一百例”, 68, “张晓玲”, “2012-01-01”); // 创建图书信息
List pList1 = new ArrayList(); // 向类别 1 添加图书
pList1.add(book1);
pList1.add(book4);
List pList2 = new ArrayList(); // 向类别 2 添加图书
pList2.add(book3);
pList2.add(book5);
List pList3 = new ArrayList(); // 向类别 3 添加图书
pList3.add(book2);
CategoryDao.categoryMap.put(category1, pList1);
CategoryDao.categoryMap.put(category2, pList2);
CategoryDao.categoryMap.put(category3, pList3);
CategoryDao.printDeptmentInfo();
}
}
在该程序中,使用了泛型 List 和泛型 Map 分别存储图书类别和特定类别下的图书明细信息。从中可以看出使用泛型不仅减少了代码的编写量,也提高了类型的安全性。
运行该程序,输出的结果如下所示。
所属类别:平面设计
图书编号 图书名称 图书价格 图书作者 出版时间
2 影视后期处理宝典 78 刘水波 2012-10-05
所属类别:数据库
图书编号 图书名称 图书价格 图书作者 出版时间
1 细说 Java 编程 25 张晓玲 2012-01-01
4 Java 从入门到精通 27 陈奚静 2012-11-01
所属类别:程序设计
图书编号 图书名称 图书价格 图书作者 出版时间
3 MySQL 从入门到精通 41 王志亮 2012-3-2
5 SQL Server 一百例 68 张晓玲 2012-01-01
## Java枚举(enum)详解:Java声明枚举类型、枚举(enum)类、EnumMap 与 EnumSet
枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。
在 JDK 1.5 之前没有枚举类型,那时候一般用接口常量来替代。而使用 [Java]( ) 枚举类型 enum 可以更贴近地表示这种常量。
### 声明枚举
声明枚举时必须使用 enum 关键字,然后定义枚举的名称、可访问性、基础类型和成员等。枚举声明的语法如下:
enum-modifiers enum enumname:enum-base {
enum-body,
}
其中,enum-modifiers 表示枚举的修饰符主要包括 public、private 和 internal;enumname 表示声明的枚举名称;enum-base 表示基础类型;enum-body 表示枚举的成员,它是枚举类型的命名常数。
任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔。
**提示:如果没有显式地声明基础类型的枚举,那么意味着它所对应的基础类型是 int。**
##### 例 1
下面代码定义了一个表示性别的枚举类型 SexEnum 和一个表示颜色的枚举类型 Color。
public enum SexEnum {
male,female;
}
public enum Color {
RED,BLUE,GREEN,BLACK;
}
之后便可以通过枚举类型名直接引用常量,如 SexEnum.male、Color.RED。
使用枚举还可以使 switch 语句的可读性更强,例如以下示例代码:
enum Signal {
// 定义一个枚举类型
GREEN,YELLOW,RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch(color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
### 枚举类
Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public, static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。
所有枚举实例都可以调用 Enum 类的方法,常用方法如表 1 所示。
| 方法名称 | 描述 |
| --- | --- |
| values() | 以数组形式返回枚举类型的所有成员 |
| valueOf() | 将普通字符串转换为枚举实例 |
| compareTo() | 比较两个枚举成员在定义时的顺序 |
| ordinal() | 获取枚举成员的索引位置 |
##### 例 2
通过调用枚举类型实例的 `values( ) 方法`可以将枚举的所有成员以数组形式返回,也可以通过该方法获取枚举类型的成员。
下面的示例创建一个包含 3 个成员的枚举类型 Signal,然后调用 values() 方法输出这些成员。
enum Signal {
// 定义一个枚举类型
GREEN,YELLOW,RED;
}
public static void main(String[] args) {
for(int i = 0;i < Signal.values().length;i++) {
System.out.println(“枚举成员:”+Signal.values()[i]);
}
}
输出结果如下:
枚举成员:GREEN
枚举成员:YELLOW
枚举成员:RED
##### 例 3
创建一个示例,调用`valueOf() 方法`获取枚举的一个成员,再调用 compareTo() 方法进行比较,并输出结果。具体实现代码如下:
public class TestEnum {
public enum Sex {
// 定义一个枚举
male,female;
}
public static void main(String[] args) {
compare(Sex.valueOf(“male”)); // 比较
}
public static void compare(Sex s) {
for(int i = 0;i < Sex.values().length;i++) {
System.out.println(s + “与” + Sex.values()[i] + “的比较结果是:” + s.compareTo(Sex.values()[i]));
}
}
}
上述代码中使用 Sex.valueOf(“male”) 取出枚举成员 male 对应的值,再将该值与其他枚举成员进行比较。最终输出结果如下:
male与male的比较结果是:0
male与female的比较结果是:-1
##### 例 4
通过调用枚举类型实例的`ordinal() 方法`可以获取一个成员在枚举中的索引位置。下面的示例创建一个包含 3 个成员的枚举类型 Signal,然后调用 ordinal() 方法输出成员及对应索引位置。
具体实现代码如下:
public class TestEnum1 {
enum Signal {
// 定义一个枚举类型
GREEN,YELLOW,RED;
}
public static void main(String[] args) {
for(int i = 0;i < Signal.values().length;i++) {
System.out.println(“索引” + Signal.values()[i].ordinal()+“,值:” + Signal.values()[i]);
}
}
}