- 第六章 集合类
- 6.1 Collection接口
- 6.1.1 ArrayList集合:(查询)
- 6.1.2 linkedList集合:(增删)
- 6.1.3 Iterator接口
- 6.1.4 foreach循环
- 6.1.5 HashSet接口
- 6.1.6 TreeSet集合
- 6.1.6.1 使用compareTo()方法实现对象元素的自然排序:
- 6.1.6.2 通过实现Comparator接口进行比较器排序
- 6.2 Map接口
- 6.2.1 HashMap集合
- 6.2.2 TreeMap集合
- 6.2.3 Properties集合
- 6.3.1 泛型概述
- 6.3.2 类型通配符
- 6.4 JDK8新特性-Lambda表达式
为了在程序中可以保存数目不确定的对象,Java提供了一系列特殊的类
这些类可以存储任意类型的对象,并且长度可变,这些类被统称为集合
集合类都位于java.util包中,使用时必须导包
集合按照其存储结构可以分为两大类,单列集合Collection和双列集合Map,这两种集合的特点具体如下:
● Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是List和Set。其中,List的特点是元素有序、元素可重复。Set的特点是元素无序,而且不可重复。List接口的主要实现类有ArrayList和linkedList,Set接口的主要实现类有HashSet和TreeSe
● Map:双列集合类的根接口,用于存储具有键(Key)、值(Value)映射关系的元素,每个元素都包含一对键值,其中键值不可重复并且每个键最多只能映射到一个值,在使用Map集合时可以通过指定的Key找到对应的Value。例如,根据一个学生的学号就可以找到对应的学生。Map接口的主要实现类有HashMap和TreeMap
集合类的继承体系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JVli2uNG-1635600530408)(Java/image-20211025202249977.png)]
6.1 Collection接口**概念:**Collection是所有单列集合的父接口,它定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。Collection接口的常用如下表
| 方法****声明 | 功能描述 |
|---|---|
| boolean add(Object o) | 向集合中添加一个元素 |
| boolean addAll(Collection c) | 将指定Collection中的所有元素添加到该集合中 |
| void clear() | 删除该集合中的所有元素 |
| boolean remove(Object o) | 删除该集合中指定的元素 |
| boolean removeAll(Collection c) | 删除指定集合中的所有元素 |
| boolean isEmpty**()** | 判断该集合是否为空 |
| boolean contains(Object o) | 判断该集合中是否包含某个元素 |
| boolean containsAll**(Collection c)** | 判断该集合中是否包含指定集合中的所有元素 |
| Iterator iterator() | 返回在该集合的元素上进行迭代的迭代器(Iterator),用于遍历该集合所有元素 |
| int size() | 获取该集合元素个数 |
List接口简介
List接口继承自Collection接口,其不但继承了Collection接口中的所有方法,还增加了一些根据元素索引操作集合的特有方法,是单列集合的一个重要分支:
-
List集合允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引访问List集合中的指定元素
-
另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致
ArrayList是List接口的一个实现类,它是程序中最常见的一种集合,在ArrayList内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组
语法:ArrayList Arr1 = new ArrayList<>();
构造方法:
ArrayList(); //构造一个向量空间,使其内部数据数组的大小为10,其标准容量增量为0
ArrayList(int a); //使用指定的初始容量和容量增量构造一个向量空间
增加元素:
add(E element); //将指定元素增加到末尾
add(int index,E element); //在指定位置插入指定元素
删除元素:
remove(int index); //移除指定位置的元素
clear(); //清空向量中所有的元素
修改元素:
set(int index,E element); //用指定的元素替代指定位置的元素
查找元素:
get(int index); //返回指定位置的元素
indexOf(Object o); //返回此向量中第一次出现指定元素的索引,若不存在指定元素则返回-1
lastIndexOf(Object o); //返回此向量中最后一次出现指定元素的索引,不存在返回-1
容器大小:
size(); //返回该容器中的组件数
判空:
isEmpty(); //判断该向量中是否包含组件
转化为数组:
toArray(); //返回一个数组,包含此向量中以恰当顺序存放的所有元素
转化为字符串:
toString(); //返回此向量的字符串表示形式,其中包括每个元素的String形式
6.1.2 linkedList集合:(增删)ArrayList集合在查询元素时速度很快,但在增删元素时效率较低
为了克服这种局限性,可以使用List接口的另一个实现类linkedList。linkedList集合内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。当插入一个新元素时,只需要修改元素之间的这种引用关系即可,删除一个节点也是如此。正因为这样的存储结构,所以linkedList集合对于元素的增删操作具有很高的效率
linkedList集合添加删除元素的过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VwPilRI-1635600530409)(Java/image-20211025203018998.png)]
| 方法声明 | 功能描述 |
|---|---|
| void add(int index, E element) | 在此列表中指定的位置插入指定的元素 |
| void addFirst**(Object o)** | 将指定元素插入此列表的开头 |
| void addLast**(Object o)** | 将指定元素添加到此列表的结尾 |
| Object getFirst() | 返回此列表的第一个元素 |
| Object getLast() | 返回此列表的最后一个元素 |
| Object removeFirst() | 移除并返回此列表的第一个元素 |
| Object removeLast() | 移除并返回此列表的最后一个元素 |
实例:
1 import java.util.*;
2 public class Example02 {
3 public static void main(String[] args) {
4 linkedList link = new linkedList(); // 创建linkedList集合
5 link.add("张三");
6 link.add("李四");
7 link.add("王五");
8 link.add("赵六");
9 System.out.println(link.toString()); // 取出并打印该集合中的元素
10 link.add(3, "Student"); // 向该集合中指定位置插入元素
11 link.addFirst("First"); // 向该集合第一个位置插入元素
12 System.out.println(link);
13 System.out.println(link.getFirst()); // 取出该集合中第一个元素
14 link.remove(3); // 移除该集合中指定位置的元素
15 link.removeFirst(); // 移除该集合中第一个元素
16 System.out.println(link);
17 }
18 }
6.1.3 Iterator接口
Iterator接口也是集合中的一员,但它与Collection、Map接口有所不同
Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器
实例:
1 import java.util.*;
2 public class Example03 {
3 public static void main(String[] args) {
4 ArrayList list = new ArrayList(); // 创建ArrayList集合
5 list.add("张三"); // 向该集合中添加字符串
6 list.add("李四");
7 list.add("王五");
8 list.add("赵六");
9 Iterator it = list.iterator(); // 获取Iterator对象
10 while (it.hasNext()) { // 判断ArrayList集合中是否存在下一个元素
11 Object obj = it.next(); // 取出ArrayList集合中的元素
12 System.out.println(obj);
13 }
14 }
15 }
核心代码:
Iterator it = list.iterator();
while(it.hasNext()){ //判断ArrayList是否存在下一个元素
Object obj = it.next(); //取出ArrayList集合中的元素
System.out.println(obj);
}
迭代原理:内部采用指针的方式来跟踪集合中的元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-snHAHpQ9-1635600530411)(Java/image-20211025203754373.png)]
通过迭代器获取ArrayList集合中的元素时,这些元素的类型都是Object类型,如果想获取到特定类型的元素,则需要进行对数据类型强制转换
Iterator迭代器适合用于元素的遍历,不便于元素的修改:
//加入要在一个学生元素集合里删除一个学生
1 import java.util.*;
2 public class Example04 {
3 public static void main(String[] args) {
4 ArrayList list = new ArrayList(); //创建ArrayList集合
5 list.add("张三");
6 list.add("李四");
7 list.add("王五");
8 Iterator it = list.iterator(); // 获得Iterator对象
9 while (it.hasNext()) { // 判断该集合是否有下一个元素
10 Object obj = it.next(); // 获取该集合中的元素
11 if ("张三".equals(obj)) { // 判断该集合中的元素是否为张三
12 list.remove(obj); // 删除该集合中的元素
13 }
14 }
15 System.out.println(list);
16 }
17 }
上述程序在运行时出现了并发修改异常ConcurrentModificationException。这个异常是迭代器对象抛出的,出现异常的原因是集合在迭代器运行期间删除了元素,会导致迭代器预期的迭代次数发生改变,导致迭代器的结果不准确
//第一种解决办法:
//在业务逻辑上只要将姓名为Annie的学生删除,后面的学生不必再进行迭代,只需找到该学生后跳出迭代即可,但是并未进行删除操作
if ("张三".equals(obj)) {
list.remove(obj);
break;
}
//第二种解决方式:
//如果需要在集合的迭代期间对集合中的元素进行删除,可以使用迭代器本身的删除方法
if ("张三".equals(obj)) {
it.remove(); //注意这里用到的是迭代器iterator的删除方法而非集合类list的删除方法
}
6.1.4 foreach循环
Iterator可以用来遍历集合中的元素,但写法上比较繁琐,为了简化书写,从JDK5开始,提供了foreach循环。foreach循环是一种更加简洁的for循环,也称增强for循环。foreach循环用于遍历数组或集合中的元素,具体语法格式如下:
for(容器中元素类型 临时变量 :容器变量) {
执行语句
}
与for循环相比,foreach循环不需要获得容器的长度,也不需要根据索引访问容器中的元素,但它会自动遍历容器中的每个元素
实例:
1 import java.util.*;
2 public class Example05 {
3 public static void main(String[] args) {
4 ArrayList list = new ArrayList();// 创建ArrayList集合
5 list.add("aaa"); // 向ArrayList集合中添加字符串元素
6 list.add("bbb");
7 list.add("ccc");
8 for (Object obj : list) { // 使用foreach循环遍历ArrayList对象
9 System.out.println(obj); // 取出并打印ArrayList集合中的元素
10 }
11 }
12 }
**缺陷:**foreach循环虽然书写起来很简洁,但在使用时也存在一定的局限性。当使用foreach循环遍历集合和数组时,只能访问集合中的元素,不能对其中的元素进行修改
Set接口简介
6.1.5 HashSet接口与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复
Set接口主要有两个实现类,分别是HashSet和TreeSet。其中,HashSet是根据对象的哈希值来确定元素在集合中的存储位置,具有良好的存取和查找性能
TreeSet则是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序
HashSet是Set接口的一个实现类
- 储存的元素不可重复
- 元素无序
实例:
1 import java.util.*;
2 public class Example07 {
3 public static void main(String[] args) {
4 HashSet set = new HashSet(); // 创建HashSet集合
5 set.add("张三"); // 向该Set集合中添加字符串
6 set.add("李四");
7 set.add("王五");
8 set.add("李四"); // 向该Set集合中添加重复元素
9 Iterator it = set.iterator(); // 获取Iterator对象
10 while (it.hasNext()) { // 通过while循环,判断集合中是否有元素
11 Object obj = it.next();// 如果有元素,就通过迭代器的next()方法获取元素
12 System.out.println(obj);
13 }
14 }
15 }
由于用HashSet实现类实例化的对象不能存储重复的元素,且元素无序:
所以打印输出结果中重复的“李四”被去除,且输出顺序与输入顺序不一致
**HashSet集合确保元素不重复的机理:**当调用HashSet集合的add()方法存入元素时,首先调用当前存入对象的hashCode()方法获得对象的哈希值,然后根据对象的哈希值计算出一个存储位置,如果这个存储位置上没有元素则直接存储,若有元素会调用equal()方法让当前存入的元素依次和该位置上的元素进行比较,如果返回值为false则将该元素存入,如果返回值为true说明有重复元素,就将该元素舍弃
图解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xjhT4ZoY-1635600530412)(Java/image-20211026102314417.png)]
1 import java.util.*;
2 class Student {
3 String id;
4 String name;
5 public Student(String id,String name) { // 创建构造方法
6 this.id=id;
7 this.name = name;
8 }
9 public String toString() { // 重写toString()方法
10 return id+":"+name;
11 }
12 }
13 public class Example08 {
14 public static void main(String[] args) {
15 HashSet hs = new HashSet(); // 创建HashSet集合
16 Student stu1 = new Student("1", "张三"); // 创建Student对象
17 Student stu2 = new Student("2", "李四");
18 Student stu3 = new Student("2", "李四");
19 hs.add(stu1);
20 hs.add(stu2);
21 hs.add(stu3);
22 System.out.println(hs);
23 }
24 }
解析:
- 要点1:HashSet集合里的元素可以是实例化的对象
- 要点2:使用System.out.println(hs); 将HashSet类的对象hs输出时,系统会自动调用其1元素的toString()方法,注意也就是Student类的toString()方法,这个时候我们为了改变输出形式会重写Student类默认的toString()方法
- 要点3:上述代码的输出结果中存在重复的“2,李四”,因为我们在定义Student类时没有重写hashCode()方法和equal()方法
改进:
1 import java.util.*;
2 class Student {
3 private String id;
4 private String name;
5 public Student(String id, String name) {
6 this.id = id;
7 this.name = name;
8 }
9 // 重写toString()方法
10 public String toString() {
11 return id + ":" + name;
12 }
13 // 重写hashCode方法
14 public int hashCode() {
15 return id.hashCode(); // 返回id属性的哈希值
16 }
17 // 重写equals方法
18 public boolean equals(Object obj) {
19 if (this == obj) { // 判断是否是同一个对象
20 return true; // 如果是,直接返回true
21 }
22 if (!(obj instanceof Student)) { // 判断对象是为Student类型
23 return false;
24 }
25 Student stu = (Student) obj; // 将对象强转为Student类型
26 boolean b = this.id.equals(stu.id); // 判断id值是否相同
27 return b; // 返回判断结果
28 }
29 }
30 public class Example09 {
31 public static void main(String[] args) {
32 HashSet hs = new HashSet(); // 创建HashSet对象
33 Student stu1 = new Student("1", "张三"); // 创建Student对象
34 Student stu2 = new Student("2", "李四");
35 Student stu3 = new Student("2", "李四");
36 hs.add(stu1); // 向集合存入对象
37 hs.add(stu2);
38 hs.add(stu3);
39 System.out.println(hs); // 打印集合中的元素
40 }
41 }
- 要点1:通过重写Student类的hashCode()方法和equal()方法可以解决出现重复集合元素的问题
- 要点2:重写hashCode()方法,因为HashSet集合在存储元素时首先要计算这个对象的哈希值,再通过哈希值计算出一个存储位置,而Student类有两个成员变量,hashCode()方法无法正常使用,需要将hashCode()方法修改为返回其中一个成员变量的哈希值,HasSet集合的存储操作才能正常通过对象的哈希值计算出存储位置
- 要点3:重写equal()方法,在重写equal()方法的过程中,因为在通过哈希值计算出存储位置后,要通过equal()让当前存入的元素依次与该位置的元素进行比较,在比对到第n个对象时,这个对象是类的实例化,其继承了类的equal()方法,所以即将存入的对象要和第n个对象比对,即调用第n个对象的equal()方法,1.如果第19行代码确定是同一个元素,就直接返回true,即确定有重复元素,将其舍弃即可,在确定完不是同一个对象后,2.在第22行代码判断这个要存入HashSet集合的元素是否是Student类的实例化对象,如果不是同一个类的实例化对象,就可以直接返回false让HashSet集合的存储操作将其通过哈希值计算出的存储位置将其存储,3.在第25行代码开始,已经确定该对象是Student类且与第n个对象不是同一个对象,就通过Student stu = Student(obj)将其强转为Student类,然后与第n个元素的id值进行equal()方法进行比对,如果id相同,由于哈希值是通过id变量计算存储位置,若id相同,则计算出的哈希值就相同,存储位置也就相同,HashSet集合不允许这样的清空存在,所以返回true直接将这个元素舍弃,如果id值不同,说明通过哈希值计算出的存储位置不同,可以返回false直接将其存储
HashSet集合存储的元素是无序的,如果想让元素的存取顺序一致,可以使用Java中提供的linkedHashSet类,linkedHashSet类是HashSet的子类,与linkedList一样,它也使用双向链表来维护内部元素的关系
6.1.6 TreeSet集合HashSet集合存储的元素是无序的和不可重复的,为了对集合中的元素进行排序,Set接口提供了另一个可以对HashSet集合中元素进行排序的类——TreeSet
使用方法:
1 import java.util.TreeSet;
2 public class Example11 {
3 public static void main(String[] args) {
4 TreeSet ts = new TreeSet();
5 ts.add(3);
6 ts.add(1);
7 ts.add(1);
8 ts.add(2);
9 ts.add(3);
10 System.out.println(ts);
11 }
12 }
**输出结果:**通过TreeSet实现类的add方法添加元素后,在输出结果中已经对结果进行了排序,并且重复出现的元素只会打印一次
6.1.6.1 使用compareTo()方法实现对象元素的自然排序:TreeSet集合对添加的元素进行排序的机理:元素的类可以实现Comparable接口(基本类型的包装类,String类都实现了该接口),Comparable接口强行对实现它的每个类的对象进行整体排序,这种排序方法称为自然排序。
Comparable接口的compareTo()方法被称为自然比较方法,如果将自己定义的Student对象存入TreeSet集合,TreeSet将不会对添加的元素进行排序,Student类必须实现Comparable接口并重写compareTo()方法实现对元素的顺序存取(接口必须通过子类实现,并且子类必须实现接口的所有抽象方法)
import java.util.TreeSet; class Student implements Comparable{ private String id; private String name; public Student(String id,String name){ this.id=id; this.name=name; } //重写toString的方法 public String toString(){ retutn id+":"+name; } @Override public int compareTo(Student o){ //return 0; //集合里只有一个元素 //return 1; //集合按照存入顺序输出 return -1; //集合按照存入顺序倒序输出 } } public class Example{ public static void main(String arsg[]){ TreeSet ts = new TreeSet(); ts.add(new Student("1","张三")); ts.add(new Student("2","李四")); ts.add(new Student("2","李四")); System.out.println(ts); } }
6.1.6.2 通过实现Comparator接口进行比较器排序
**要点1:**注意重写toString方法时,toString()方法的返回值类型是String
**要点2:**Student类要实现Comparable接口(基本类型的包装类),在实现接口时注意语法:
class Student implements Comparable
尖括号<>内的Student代表Comparable接口包装的数据类型
**比较器排序:**实现Comparator接口,重写compare()方法和equals()方法,但是由于所有的类默认继承Object,而Object中有equals()方法,所有自定义比较器排序时不用重写equals()方法,只需要重写compare()方法,这种排序方法称为比较器排序法
import java.util.Comparator;
import java.util.TreeSet;
class Student{
private String id;
private String name;
public Student(String id,String name){
this.id=id;
this.name=name;
}
//重写toString的方法
public String toString(){
retutn id+":"+name;
}
}
public class Example{
public static void main(String args[]){
TreeSet ts = new TreeSet(new Comparator(){
@Override
public int compare(Object o1,Object o2){
return -1;
}
});
ts.add(new Student("1","张三"));
ts.add(new Student("2","李四"));
ts.add(new Student("2","李四"));
System.out.println(ts);
}
第17~22行代码是声明了一个TreeSet集合并通过匿名内部类的方式实现了Comparator接口,然后重写了compare()方法
6.2 Map接口**Map接口简介:**Map接口是一种双列集合接口,它的每个元素都包含一个键对象Key和一个值对象Value,键和值对象之间存在一种关系,称为“映射”,从Map集合方法元素时,只要指定了Key就能找到相对应的Value
Map接口的常用方法:
| 方法声明 | 功能描述 |
|---|---|
| void put(Object key, Object value) | 将指定的值与此映射中的指定键关联(可选操作) |
| Object get(Object key) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回null |
| void clear() | 移除所有的键值对元素 |
| V remove(Object key) | 根据键删除对应的值,返回被删除的值 |
| int size() | 返回集合中的键值对的个数 |
| boolean containsKey(Object key) | 如果此映射包含指定键的映射关系,则返回 true。 |
| boolean containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回 true |
| Set keySet() | 返回此映射中包含的键的 Set 视图 |
| Collection values() | 返回此映射中包含的值的 Collection 视图 |
| Set | 返回此映射中包含的映射关系的Set视图 |
HashMap集合是Map接口的一个实现类,用于存储键值映射关系,但HashMap集合没有重复的键且键值无序
1 import java.util.*;
2 public class Example14 {
3 public static void main(String[] args) {
4 HashMap map = new HashMap(); // 创建Map对象
5 map.put("1", "张三"); // 存储键和值
6 map.put("2", "李四");
7 map.put("3", "王五");
8 System.out.println("1:" + map.get("1")); // 根据键获取值
9 System.out.println("2:" + map.get("2"));
10 System.out.println("3:" + map.get("3"));
11 }
12 }
Map中的键必须是唯一的,不能重复,如果存储了相同的键,后存储的值则会覆盖原有的值,简而言之就是:键相同,值覆盖
HashMap集合的遍历方法:
- 先遍历所有的键再根据键获取相应的值
public class example{
public static void main(String args[]){
HashMap hm = new HashMap();
hm.put("1","张三");
hm.put("2","李四");
hm.put("3","王五");
Set keySet = hm.keySet(); //获取此映射中键的Set视图
Iterator it = keySet.iterator(); //迭代键的集合
while(it.hasNext()){
Object key = it.next();
Object value = hm.get(key); //获取每个键对应的值
System.out.println(key+":"+value);
}
}
}
- **要点1:**Set keySet = hm.keySet() 即调用了Map接口的keySet方法,keySet()方法可以返回此映射中键的Set视图
- **要点2:**通过迭代器Iterator迭代Set集合中的每一个元素(即每一个键),再通过get(String key)方法,根据键获取对应的值
- 先获取集合中的所有映射关系,然后从映射关系中取出键和值
public class Example{
public static void main(String args[]){
HashMap hm = new HashMap(); //创建Map集合
hm.put("1","张三"); //存储键和值
hm.put("2","李四");
hm.put("3","王五");
Set entrySet = hm.entrySet(); //获取所有的键和值的映射关系
Iterator it = entrySet.iterator(); //获取lterator对象
while(it.hasNext()){
//获取集合中键值对映射关系
Map.Entry entry = (Map.entry)(it.next());
Object key = entry.getKey(); //获取Entry中的键
Object value = entry.getValue(key); //获取Entry中的值
System.out.println(key+":"+value);
}
}
}
- 要点1: Set entrySet = hm.entrySet(); 即调用entrySet()方法获取存储再Map中所有映射的Set集合,这个集合中存放了Map.Entry类型的元素(Entry是Map内部接口),每个Map.Entry对象代表Map中的一个键值对
- **要点2:**通过迭代器lterator迭代Set集合,获得每一个映射对象,并分别调用映射对象的getKey() 和 getValue()方法获取键和值
在Map集合中,还提供了一些操作集合的常用方法:
- values()方法用于得到map实例中所有的value,返回值类型为Collection
- size()方法获取Map集合类的大小
- containsKey()方法用于判断是否包含传入的键
- containsValue()方法用于判断是否包含传入的值
- remove()方法用于根据key移除map实例中与该key对应的value
案例:
1 import java.util.*;
2 public class Example17 {
3 public static void main(String[] args) {
4 HashMap map = new HashMap(); // 创建Map集合
5 map.put("1", "张三"); // 存储键和值
6 map.put("3", "李四");
7 map.put("2", "王五");
8 map.put("4", "赵六");
9 System.out.println("集合大小为:"+map.size());
10 System.out.println("判断是否包含传入的键:"+map.containsKey("2"));
11 System.out.println("判断是否包含传入的值:"+map.containsValue("王五"));
12 System.out.println("移除键为1的值是:"+map.remove("1"));
13 Collection values = map.values();
14 Iterator it = values.iterator();
15 while (it.hasNext()) {
16 Object value = it.next();
17 System.out.println(value);
18 }
19 }
20 }
从输出结果上看,HashMap集合迭代出来的元素与存入的顺序并不一致:
如果想让存取顺序一致,可以使用Java中提供的linkedHashMap类,它是HashMap的子类,与linkedList一样,也是用双向循环链表来维护内部元素的关系,使Map元素的存取顺序一致
linkedHashMap实例:
1 import java.util.*;
2 public class Example18 {
3 public static void main(String[] args) {
4 linkedHashMap map = new linkedHashMap(); // 创建Map集合
5 map.put("3", "李四"); // 存储键和值
6 map.put("2", "王五");
7 map.put("4", "赵六");
8 Set keySet = map.keySet();
9 Iterator it = keySet.iterator();
10 while (it.hasNext()) {
11 Object key = it.next();
12 Object value = map.get(key); // 获取每个键所对应的值
13 System.out.println(key + ":" + value);
14 }
15 }
16 }
6.2.2 TreeMap集合使用了先遍历所有的键,再根据键迭代值的迭代集合的方法
HashMap集合存储的元素的键值是无序的和不可重复的,为了对集合中的元素的键值进行排序,Map接口提供了另一个可以对集合中元素键值进行排序的类TreeMap
案例:
1 import java.util.Iterator;
2 import java.util.Set;
3 import java.util.TreeMap;
4 public class Example19 {
5 public static void main(String[] args) {
6 TreeMap map = new TreeMap(); // 创建Map集合
7 map.put(3, "李四");// 存储键和值
8 map.put(2, "王五");
9 map.put(4, "赵六");
10 map.put(3, "张三");
11 Set keySet = map.keySet();
12 Iterator it = keySet.iterator();
13 while (it.hasNext()) {
14 Object key = it.next();
15 Object value = map.get(key); // 获取每个键所对应的值
16 System.out.println(key+":"+value);
17 }
18 }
19 }
- **要点1:**使用了 Set keySet = map.keySet(); 方法先获得所有的键,再通过键获得对应的值
- **要点2:**使用Map里集合类的put()方法添加元素,但添加了两个键为“3”的值,第二个直接覆盖了第一个键为“3”的值,说明TreeMap中的键必须是唯一的,不能重复且有序
TreeMap对键值排序的机理:(TreeMap的排序与TreeSet一样也分自然排序和比较排序)
TreeMap的比较排序实例:
1 import java.util.*;
2 class Student {
3 private String name;
4 private int age;
5 public String getName() {
6 return name;
7 }
8 public void setName(String name) {
9 this.name = name;
10 }
11 public int getAge() {
12 return age;
13 }
14 public void setAge(int age) {
15 this.age = age;
16 }
17 public Student(String name, int age) {
18 super();
19 this.name = name;
20 this.age = age;
21 }
22 @Override
23 public String toString() {
24 return "Student [name=" + name + ", age=" + age + "]";
25 }
26 }
27 public class Example20 {
28 public static void main(String[] args) {
29 TreeMap tm = new TreeMap(new Comparator() {
30 @Override
31 public int compare(Student s1, Student s2) {
32 int num = s1.getName().compareTo(s2.getName());//按照姓名比较
33 return num == 0 ? num:s1.getAge() - s2.getAge();
34 }
35 });
36 tm.put(new Student("张三", 23), "北京");
37 tm.put(new Student("李四", 13), "上海");
38 tm.put(new Student("赵六", 43), "深圳");
39 tm.put(new Student("王五", 33), "广州");
40 Set keySet = tm.keySet();
41 Iterator it = keySet.iterator();
42 while (it.hasNext()) {
43 Object key = it.next();
44 Object value = tm.get(key); // 获取每个键所对应的值
45 System.out.println(key+":"+value);
46 }
47 }
48 }
6.2.3 Properties集合
- **要点1:**通过匿名内部类的方式实现了Comparator接口,然后重写了compare()方法,在compare()方法中通过三目运算符的方式自定义了排序方式为先按照年龄排序,年龄相同再按照姓名排序
- **要点2:**使用了 Set keySet = map.keySet(); 方法先获得所有的键,再通过键获得对应的值
Map接口还有一个实现类Hashtable,它与HashMap十分相似,区别在于Hashtable是线程安全的
(回顾字符串类StringBuffer也是线程安全的,而StringBuild相对于StringBuffer来说不安全)
Hashtable存取元素时的速度很慢,目前基本被HashMap类所取代,但Hashtable有一个重要的子类Properties在实际开发中非常重要
-
Properties主要用来存储字符串类型的键和值
-
在实际开发中,经常使用Properties集合来存取应用的配置项。假设有一个文本编辑工具,要求默认背景色是红色,字体大小为14px,语言为中文,其配置项如下面的代码:
Backgroup-color = red Font-size = 14px Language = chinese
实例:
public class Example{
public static void main(String args[]){
Properties p = new Properties(); //创建Properties对象
p.setProperty("Background-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);
}
}
}
6.3.1 泛型概述
- **要点1:**在上述的Properties类中,针对字符串的存取提供了两个专用的方法setProperty()和setProperty()
- **要点2:**Properties类的propertyNames()方法可以得到一个包含所有键的Enumeration对象name中,然后遍历所有的键时,通过调用geyProperty()方法获得键对应的值
泛型是指定一个表示类型的变量,即“参数化类型”,在编程中使用泛型来代替某个实际的类型,而后通过实际调用时传入或推导的类型来对泛型进行替换,以达到代码复用的效果
在使用泛型的过程中,操作数据类型被指定为一个参数,这种参数类型在类,接口和方法中分别称为泛型类,泛型接口,泛型方法
实例:
public class Box {
private String value;
public void set(String value) {
this.value = value;
}
public String get() {
return value;
}
}
定义了一个Box类,Box类中设置了一个 String 类型的数据。这时程序运行起来是没有问题的。但是,如果我们又需要一个能设置 Integer 类型数据的类,这个时候我们只能重新创建一个类,把 value 改为 Integer类型的。可是,随着业务不断增加,我们需要设置越来越多数据类型的类,这样会使得工程变得越来越“笨重”,并且安全性和重用性都非常低
泛型的使用
public class Box{ private T t; public void set(T t){ this.t=t; } public T get(){ return t; } }
Box类在定义时使用了“”的形式,T表示此类型是由外部调用本类时指定的。这样,在实例化类对象时可以传入除基础数据类型以外的任意类型数据,使类具有良好的通用性
泛型类:
[访问权限] class 类名称<泛型类型标识1,泛型类型标识2,…,泛型类型标识n>
[访问权限] 泛型类型标识 变量名称;
[访问权限] 泛型类型标识 方法名称;
[访问权限] 返回值类型声明 方法名称(泛型类型标识 变量名称){};
泛型对象:
类名称<参数化类型> 对象名称 = new 类名称<参数化类型>();
实例:
1 import java.util.*;
2 public class Example23 {
3 public static void main(String[] args) {
4 ArrayList list = new ArrayList();
5 list.add(1); // 添加字符串对象
6 list.add(2);
7 for (Integer str : list) { // 遍历集合
8 System.out.println(str);
9 }
10 }
11 }
**要点1:**使用泛型规定了ArrayList集合只能存入Integer类型元素,然后向集合中存入了两个Integer类型元素,并对这个集合进行foreach遍历
**要点2:**每次遍历集合元素时,可以指定元素类型为Integer,而不是Object,这样就避免了在程序中进行强制类型转换
泛型方法:
[访问权限] <泛型标识> 返回值类型 方法名称(泛型标识 参数名称)
1 public class Example24 {
2 public static void main(String[] args) {
3 //创建对象
4 Dog dog = new Dog();
5 //调用方法,传入的参数是什么类型,返回值就是什么类型
6 dog.show("hello");
7 dog.show(12);
8 dog.show(12.5);
9 }
10 }
11 class Dog{
12 String eat;
13 Integer age;
14 public void show(T t) {
15 System.out.println(t);
16 }
17 }
18 tool.show(12.5);
19 }
**要点:**定义了一个泛型方法show(),并将show()方法的参数类型和返回值类型规定为泛型,这样调用方法时,传入的参数是什么类型,返回值就是什么类型
泛型接口:
[访问权限] interface 接口名称<泛型标识> {}
泛型接口定义完成之后,就要定义此接口的子类,定义泛型接口的子类有两种方式
- 一种是直接在子类实现的接口中明确地给出泛型类型
当子类明确泛型类的类型参数变量时,外界使用子类的时候,需要传递类型参数变量进来,在实现类中需要定义出类型参数变量
public interface Inter{ public abstract void show(T t); } public class InterImpl implements Inter { @Override public void show(String s) { System.out.println(s); } } public class Example25 { public static void main(String[] args) { Inter inter = new InterImpl(); inter.show("hello"); } }
- **要点1:**定义了一个泛型接口Inter,在泛型接口子类InterImpl中实现了Inter接口,Inter inter = new InterImpl();
- **要点2:**InterImpl实现 Inter接口时,直接在实现的接口处制定了具体的泛型类型String,这样在重写Inter接口中的show()方法时直接指明类型为String即可
- 另一种是直接在子类后声明泛型
当子类不明确泛型类的类型参数变量,外界使用子类的时候,也需要传递类型参数变量进来,在实现类中也需要定义出类型参数变。接下来通过修改子类InterImpl和测试程序来学习这种情况的泛型接口定义
public interface Inter6.3.2 类型通配符{ public abstract void show(T t); } public class InterImpl implements Inter { @Override public void show(T t) { System.out.println(t); } } public class Example25 { public static void main(String[] args) { Inter inter = new InterImpl(); inter.show("hello"); Inter ii = new InterImpl<>(); ii.show(12); } }
在Java中,数组是可以协变的,例如,Dog extends Animal,那么Animal[]与dog[]是可以兼容的。而集合是不能协变的,也就是说List不是List的父类, 为了解决这个问题,Java泛型为我们提供了类型通配符 ?
实例:
1 import java.util.*;
2 public class Example28 {
3 public static void main(String[] args) {
4 //List集合装载的是Integer
5 List list = new ArrayList<>();
6 list.add(1);
7 list.add(2);
8 test(list);
9 }
10 public static void test(List> list) {
11 for(int i=0;i
需要注意的是,如果使用通配符“?”接收泛型对象,则通配符“?”修饰的对象只能接收,不能修改,也就是不能设置。错误的代码如下所示
class Test {
public static void main(String[] args) {
List> list = new ArrayList();
list.add("张三");
}
}
通配符表示可以匹配任意类型,任意的Java类都可以匹配, 但是当接收一个List集合时,它只能操作数字类型的元素(Float、Integer、Double、Byte等数字类型都行),而如果直接使用通配符的话,该集合就不是只能操作数字了。针对这类问题我们可以设定通配符的上限和下限
- 设定通配符上限代码如下所示:
List extends Number> - 设定通配符下限代码如下所示:
super Type>
6.4 JDK8新特性-Lambda表达式
Lambda表达式是JDK8的一个新特性,Lambda可以取代大部分的匿名内部类,写出更优雅的Java代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。JDK也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效
Lambda表达式由参数列表、箭头符号 -> 和函数体组成。函数体既可以是一个表达式,也可以是一个语句块。其中表达式会被执行然后返回执行结果;语句块中的语句会被依次执行,就像方法中的语句一样
Lambda表达式常用的语法格式如下表
语法格式 描述 ()-> System.out.println(“Hello Lambda!”); 无参数,无返回值 (x) -> System.out.println(x) 有一个参数,并且无返回值 x -> System.out.println(x) 若只有一个参数,小括号可以省略不写 Comparator com = (x, y) -> {System.out.println(“函数式接口”);returnInteger.compare(x, y); }; 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句 Comparator com = (x, y) -> Integer.compare(x, y); 若Lambda 体中只有一条语句,return 和大括号都可以省略不写 (Integer x, Integer y) -> Integer.compare(x, y); Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
实例:
1 import java.util.Arrays;
2 public class Example29 {
3 public static void main(String[] args) {
4 String[] arr = {"program", "creek", "is", "a", "java", "site"};
5 Arrays.sort(arr, (m, n) -> Integer.compare(m.length(), n.length()));
6 System.out.println("Lambda语句体中只有一条语句,参数类型可推断:"+
7 Arrays.toString(arr));
8 Arrays.sort(arr, (String m, String n) -> {
9 if (m.length() > n.length())
10 return -1;
11 else
12 return 0;
13 });
14 System.out.println("Lambda语句体中有多条语句:"+Arrays.toString(arr));
15 }
16 }
- **要点1:**使用Lambda表达式语法对字符串数组进行了排序



