十、集合、泛型

一、集合

:::tip

  1. 单列集合:其中的元素都是单个的;
    1. Collection 接口有两个重要的子接口 List、Set,他们实现子类都是单列集合
  2. 双列集合:其中的元素是以键值对的形式出现的;
    1. Map 接口实现的子类 是双列集合
      :::

1. Collection 接口

:::tip
Collection 接口实现类的特点:

  1. Collection 实现子类可以存放多个元素,每个元素可以是 object;
  2. 有些实现类可以存放重复的元素,有些不可以;
  3. 有些实现类是有序的(List),有些是无序的(Set);
  4. Collection 接口没收直接实现子类,它是通过子接口 List 和 set 来实现的;
    :::

Collection 接口常用方法

add添加单个元素
remove删除指定元素
contains查找元素是否存在
size获取元素个数
isEmpty判断是否为空
clear清空
addAll添加多个元素
contains查找多个元素是否都存在
removeAll删除多个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Collection 常用方法
public class Collection01 {
@SuppressWarnings({"all"}) // 消除警告
public static void main(String[] args) {
// 添加元素
List list = new ArrayList();
list.add("西游记");
list.add("水浒传");
System.out.println(list);

// 批量添加元素
List list1 = new ArrayList();
list1.add("射雕英雄传");
list1.add("神雕侠侣");
list.addAll(list1);
System.out.println(list);

// 查找元素
System.out.println(list.contains("三国演义"));

// 批量查找
System.out.println(list.containsAll(list1));

// 判断集合是否为空
System.out.println(list.isEmpty());

// 获取元素个数
System.out.println(list.size());

// 删除单个元素
list.remove("神雕侠侣");
System.out.println(list);

// 删除多个元素
list.removeAll(list1);
System.out.println(list);

// 清空集合
list.clear();
System.out.println(list);
}
}

Collection 遍历元素的方式

1. 使用 Iterator (迭代器)

:::tip

  • Iterator 对象成为迭代器,主要用于遍历 Collection 集合中的元素;
  • 所有实现 Collection 接口的集合类都有一个 iteration 方法;
    • 用以返回一个实现了 iteration 接口的对象,即迭代器
  • Iterator 仅用于遍历集合,其本身并不存放对象;
  • 使用快捷方式 itit, 快速创建遍历循环
    :::

:::danger
得到集合的迭代器 :Iterator iterator = 集合.iterator();
hasNext() : 判断是否还有下一个元素
nest() :下移,将移动后集合位置上的元素返回
如果需要再次遍历,需要 重置迭代器(即,重新生成迭代器并赋值给之前的变量)
:::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 迭代器 的使用方法
public class Iterator01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Book("西游记","罗贯中",100));
list.add(new Book("红楼梦","不知道",130));
list.add(new Book("水浒传","施耐庵",99));

// 得到 集合list 对应的 迭代器
Iterator iterator = list.iterator();

// 使用快捷方式 itit, 快速创建遍历循环
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);

// 再次遍历时,需要对迭代器进行重置
iterator = list.iterator();

// 使用快捷方式 itit, 快速创建遍历循环
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);

}
}
}

class Book{
private String name;
private String author;
private double price;

public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return name + author + price + "";
}
}
2. 使用增强 for 循环迭代

增强 for 循环是简化版的 iterator,只能用于遍历集合或数组;
其本质底层还是使用的迭代器;

1
2
3
for(元素类型 元素名:集合名/数组名){
访问元素
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 增强 for 循环
public class Iterator01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Book("西游记","罗贯中",100));
list.add(new Book("红楼梦","不知道",130));
list.add(new Book("水浒传","施耐庵",99));

// 增强 for 循环
for (Object obj:list) {
System.out.println(obj);
}
}
}

1. List 接口

:::tip
List 接口是 Collection 接口的子接口

  • List 集合类中的元素是有序的,且可以重复;
    • 元素顺序就是添加时的顺序;
    • 如: [tom, jack, menar, jack]
  • List 集合类中的每个元素都有其对应的 索引;
    • 索引从 0 开始;
  • List 容器中的元素可以根据序号存取
    :::
list 接口的常用方法
void add(int index, Object ele):在 index 位置插入 ele 元素
boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
Object get(int index):获取 index 位置的元素
int indexOf(Object obj):返回 obj 在集合中首次出现的位置
int lastIndexOf(Object obj):返回 obj 在集合中 末次出现的位置
Object remove(int index):移除 index 位置的元素,并返回此元素
Object set(int index,Object ele):对 index 位置的元素 重新赋值
List subList(int formIndex, int toIndex):返回 从 fromIndex 到 toIndex 位置的子集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// List 接口常用方法 - 案例
public class List01 {
@SuppressWarnings({"ALL"})
public static void main(String[] args) {
List list = new ArrayList();
// void add(int index, Object ele): 在 index 位置插入 ele 元素
list.add(0,"钢铁侠");
list.add(1,"蜘蛛侠");
list.add(2,"绿巨人");
System.out.println(list);

// boolean addAll(int index, Collection eles): 从 index 位置开始将 eles 中的所有元素添加进来
List list1 = new ArrayList();
list1.add(0,"黑寡妇");
list1.add(1,"美国队长");
list1.add(2,"黑豹");
list1.add(3,"蜘蛛侠");
list.addAll(3,list1);
System.out.println(list1);

// Object get(int index): 获取 index 位置的元素
System.out.println(list.get(4));

// int indexOf(Object obj): 返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("蜘蛛侠"));

// int lastIndexOf(Object obj): 返回 obj 在集合中 末次出现的位置
System.out.println(list.lastIndexOf("蜘蛛侠"));

// Object remove(int index): 移除 index 位置的元素,并返回此元素
System.out.println(list.remove(6));
System.out.println(list.remove("黑寡妇"));

// Object set(int index,Object ele): 对 index 位置的元素 重新赋值
list.set(2,"版纳博士");
System.out.println(list);

// List subList(int formIndex, int toIndex): 返回 从 fromIndex 到 toIndex 位置的子集合
System.out.println(list.subList(3,list.size()));
}
}

// 执行结果----------------------------------------
[钢铁侠, 蜘蛛侠, 绿巨人]
[黑寡妇, 美国队长, 黑豹, 蜘蛛侠]
美国队长
1
6
蜘蛛侠
true
[钢铁侠, 蜘蛛侠, 版纳博士, 美国队长, 黑豹]
[美国队长, 黑豹]

List [ArrayList, LinkedList, Vector]的三种遍历方式:

  1. 使用 iterator
  2. 加强 for 循环
  3. 使用普通的 for 循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//  书本排序 案例
public class List03 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(0,new Book("西游记","吴承恩",200));
arrayList.add(1,new Book("红楼梦","曹雪芹",150));
arrayList.add(2,new Book("三国志","罗贯中",220));
System.out.println(arrayList);

sort(arrayList);
for (Object o:arrayList){
System.out.println(o);
}

}
// 价格从大到小排序
public static void sort(ArrayList list){
int size = list.size();
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1 - i; j++) {
Book book1 = (Book) (list.get(j));
Book book2 = (Book) (list.get(j + 1));
if (book1.getPrice() < book2.getPrice()){
list.set(j, book2);
list.set(j+1, book1);
}

}
}
}
}

class Book{
private String name;
private String author;
private double price;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}

@Override
public String toString() {
return String.format("名称: %s\t\t 价格: %.2f\t\t 作者: %s",name,price,author);
}
}

ArrayList

:::danger
ArrayList 注意事项:

  1. ArrayList 可以存放任何元素,包括 null ,不限制个数;
  2. ArrayList 是由数组来实现数据存储的;
  3. ArrayList 基本等同于 Vector,执行效率高;
    1. ArrayList 是线程不安全的(没有 synchronized 修饰),多线程时,不建议使用;
    2. synchronized 表示线程互斥,起到线程安全的作用
      :::

:::warning
ArrayList 底层源码分析:

  1. ArrayList 中维护了一个 Object 类型的数组 elementData
    1. transient Object[] elementData;
  2. 当创建 ArrayList 对象时,如果使用的是无参构造器,则初始 elementData 容量为 0;
    1. 第一次添加,则扩容 elementData 为 10;
    2. 如需再次扩容,则扩容 elementData 为 1.5 倍;
  3. 如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小;
    1. 如果需要扩容,则直接扩容 elementData 为 1.5 倍
      :::
Vector

Vector 和 ArrayList 比较:

底层结构版本线程同步扩容倍数
ArrayList可变数组jdk1.2不安全,效率高有参构造默认为 15,以后以 1.5 倍扩容;无参构造,第一次为 10,以后以 1.5 倍扩
Vector可变数组jdk1.0安全,效率不高无参构造,默认为 10,以后以 2 倍扩容;如果指定大小,以后以 2 倍扩容;

:::warning
Vector 底层源码分析:

  1. Vector 底层也是一个对象数组;
    1. protected Object[] elementD
  2. Vector 是线程同步的,即线程安全,操作方法带有 synchronized
  3. 在开发中,需要使用线程同步安全时,使用 Vector
  4. 当创建 Vector 对象时,如果使用的是无参构造器,则初始 elementData 容量为 0;
    1. 第一次添加,则扩容 elementData 为 10;
    2. 如需再次扩容,则扩容 elementData 为 2 倍;
  5. 如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小;
    1. 如果需要扩容,则直接扩容 elementData 为 2 倍
      :::
LinkedList

LinkedList 和 ArrayList 比较:

底层结构增删效率改查效率线程安全
ArrayList可变数组较低,数组扩容较高不安全
LinkedList双向链表较高,通过链表追加较低不安全
  • 如果我们改查的操作多,选择 ArrayList;
  • 如果我们增删的操作多,选择 LinkedList;
  • 大部分情况下都是查询操作,所以一般会选择 ArrayList;
  • 也可以一个模块使用 ArrayList,一个模块使用 LinkedList;

:::warning
LinkedList 底层机制:

  • LinkedList 底层维护了一个双向链表;
  • LinkedList 中维护了两个属性:
    • first 和 last 分别指向 首节点 和 尾节点;
    • 每个节点(Node 对象)里面又维护了 prev、next、item 三个属性,最终实现双向链表;
      • 通过 prev 指向前一个;
      • 通过 next 指向后一个;

  • LinkedList 的元素增删 不是用过数组完成的,所以效率较高;
  • 可以添加任意元素(元素可以重复),包括 null;
  • 线程不安全,没有实现同步;
    :::

2. Set 接口

:::tip

  • 无序(添加和去除的顺序不一致),没有索引;
    • 不能使用 索引 的方式来获取元素;
  • 不允许重复元素,所有最多包含一个 null;
  • 可以使用迭代器 和 增强for 循环进行遍历;
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Set - 案例
public class Set01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet hashSet = new HashSet();

// 添加元素
for (int i = 0; i < 5; i++) {
hashSet.add("set - " + i);
}
System.out.println(hashSet);

// 遍历元素
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}

// 删除元素
hashSet.remove("set - 2");
System.out.println(hashSet);
}
}
HashSet

:::note

  • HashSet 实现了 Set 接口,实际上是 HashMap,底层维护的是一个 数组 + 单向链表;
  • 只能存放一个 null;
  • HashSet 是无序且不重复的;
    :::

:::warning
HashSet 底层原理:

  1. HashSet 底层是 HashMap;
  2. 添加一个元素时,先得到该元素的 hash 值,然后转换为 索引值;
  3. 找到存储数据表 table,查看索引位置是否存在元素;
    1. 如果不存在,则直接放入;
    2. 如果存在,则调用 equals 方法进行比较,如果相同,就放弃添加,如果不同,则添加到最后;
  4. 如果一条链表的元素个数超过 TREEIFY_THRESHOLD(默认为 8),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认为 64),就会进行树化(红黑树);
    :::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// HashSet - 案例
public class HashSet01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Employee("老王",18));
hs.add(new Employee("张三",21));
hs.add(new Employee("李四",13));
hs.add(new Employee("老王",18));
System.out.println(hs);

}
}

class Employee{
private String name;
private int age;

public Employee(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

// 重写 equals 和 hashCode 方法
// 如果 equals 和 hashCode 相同,则不加入链表,如果不同,则加入链表最后边
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age);
}

@Override
public String toString() {
return "Employee{" + name + '\'' + age +'}';
}
}

// 运行结果
[Employee{张三'21}, Employee{老王'18}, Employee{李四'13}]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// HashSet - 案例(多个自定义类)
package com.jihe.set_;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;

/**
* @author Pupper
* @email pupper.cheng@gmail.com
*/
public class HashSet02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Employee1("张三",10000,new MyDate(1980,2,2)));
hs.add(new Employee1("李四",12000,new MyDate(1982,4,7)));
hs.add(new Employee1("王五",16000,new MyDate(1979,12,2)));
hs.add(new Employee1("张三",18000,new MyDate(1980,2,2)));

Iterator iterator = hs.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}

class Employee1{
private String name;
private double asl;
private MyDate birthday;

public Employee1(String name, double asl, MyDate birthday) {
this.name = name;
this.asl = asl;
this.birthday = birthday;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getAsl() {
return asl;
}

public void setAsl(double asl) {
this.asl = asl;
}

public MyDate getBirthday() {
return birthday;
}

public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee1 employee1 = (Employee1) o;
return Objects.equals(name, employee1.name) && Objects.equals(birthday, employee1.birthday);
}

@Override
public int hashCode() {
return Objects.hash(name, birthday);
}

@Override
public String toString() {
return "Employee1{" +
"name='" + name + '\'' +
", asl=" + asl +
", birthday=" + birthday +
'}';
}
}

class MyDate{
private int year;
private int month;
private int day;

public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

public int getMonth() {
return month;
}

public void setMonth(int month) {
this.month = month;
}

public int getDay() {
return day;
}

public void setDay(int day) {
this.day = day;
}

@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyDate myDate = (MyDate) o;
return year == myDate.year && month == myDate.month && day == myDate.day;
}

@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
}
LinkedHashSet

:::note

  • LinkedHashSet 是 HashSet 的子类;
  • LinkedHashSet 底层是一个 LinkedHashMap,维护了一个 数组 + 双向链表;
  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置;
    • 使用链表维护元素的次序,这使的元素看起来是以插入顺序保存的;
  • LinkedHashSet 不允许添加重复元素;
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// LinkedHashSet - 案例
public class LinkedHashSet02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Set lhs = new LinkedHashSet();
lhs.add(new Car("保时捷",900000));
lhs.add(new Car("纳智捷",100000));
lhs.add(new Car("保时捷",900000));
lhs.add(new Car("奥迪",666666));

Iterator it = lhs.iterator();
while (it.hasNext()) {
Object o = it.next();
System.out.println(o);
}
}

}

class Car{
private String name;
private double price;

public Car(String name, double price) {
this.name = name;
this.price = price;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}

@Override
public int hashCode() {
return Objects.hash(name, price);
}

@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
TreeSet

:::tip

  • 使用 TreeSet 的无参构造器创建的对象是无序的;
  • 使用 TreeSet 提供的有参构造器,传入一个比较器(匿名内部类)并必定规则;
    :::
1
2
3
4
5
6
7
8
9
// TreeSet 有参构造器 - 有序
// 匿名内部类
TreeSet treeSet = new TreeSet(new Comparator(){
@Override
public int compare(Object o1, Object o2){
// 根据 ASCii 码进行排序
return ((String)o2).compareTo((String) o1);
};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 从大到小排序 - 案例
public class TreeSet1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// 使用匿名内部类,重写排序方法
TreeSet t = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 从大到小排序
return ((String)o2).compareTo((String) o1);
}
});
t.add("a1");
t.add("b3");
t.add("d6");
t.add("c4");

System.out.println(t);
}
}

// 运行结果
[d6, c4, b3, a1]

2. Map 接口

:::tip
Map 接口的特点:

  • Map 和 Collection 并列存在,用于保存具有映射关系的数据: Key - Value;
  • Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中;
  • Map 中的 key 不允许重复,value 可以重复;
  • Map 的 key 可以为 null,value 也可以为 null;
    • 注意: key 为 null 的只能有一个,value 为 null 可以有多个;
  • 常用 String 类型为 Map 的 key;
  • key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value;
  • 一堆 key - value 是放在一个 HashMap$Node 中的,因为 Node 实现了 Entry 接口,所以也说 一对 k-v 就是一个 Entry;
    • 通过 keySet 和 valuesSet 方法,可以获取所有 key 或 value 的集合;


:::

Map 接口的常用方法

put添加如果key 存在,则更新值
remove删除如果 key 不存在,则返回 null;如果 key 存在,则返回 value
get根据键获取值返回 value
size获取元素个数返回元素个数
isEmpty判断个数是否为 0返回 布尔值
clear清空
containsKey查找键是否存在返回布尔值
keySet获取所有的键
values获取所有的值
entrySet获取所有关系 k-v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Map 常用方法
public class Map01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashMap hm = new HashMap();
// 判断集合是否为空,返回布尔值
System.out.println(hm.isEmpty());

// 添加元素
hm.put("张三",18);
hm.put("李四",20);
hm.put("王五",30);
hm.put("赵六",18);
System.out.println(hm);

// 如果 key 相同,则更新值
hm.put("张三",99);
System.out.println(hm);

// 删除元素
System.out.println(hm.remove("王五"));

// 获取元素
System.out.println(hm.get("张三"));

// 判断 key value 是否存在
System.out.println(hm.containsKey("123"));
System.out.println(hm.containsKey("李四"));

// 获取 key value 的集合
System.out.println(hm.keySet());
System.out.println(hm.values());

// 通过 EntrySet 来获取 k-v
Set entrySet = hm.entrySet();
for (Object entry : entrySet) {
// 将 entry 向下转型为 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}

// 清空集合
hm.clear();
System.out.println(hm);
}
}

// 运行结果
true
{李四=20, 张三=18, 王五=30, 赵六=18}
{李四=20, 张三=99, 王五=30, 赵六=18}
30
99
false
true
[李四, 张三, 赵六]
[20, 99, 18]
李四-20
张三-99
赵六-18
{}

1. HashMap

:::note
HashMap :

  • Map 接口的常用实现类: HashMap、Hashtable 和 Properties;
  • HashMap 是 Map 接口使用频率最高的实现类;
  • HashMap 是以 key-value 对的方式来存储数据;
  • key 不能重复,但是值可以重复,允许有一个key 为 null 的元素;
  • 如果添加相同的 key,则会覆盖原来的 key-value,等同于修改;
  • HashMap 没有实现同步,因此是线程不安全的;
    :::

:::warning
HashMap 底层机制:

  • HashMap 与 HashSet 的扩容机制相同
  • HashMap 底层维护了 Node 类型的数组 table,默认为 null;
  • 当创建对象时,将加载因子初始化为 0.75;
  • 当添加 key-value 时,通过 key 的哈希值得到在 table 的索引,然后判断该索引是否有元素;
    • 如果该索引处有元素,继续判断该元素的 key 是否和准备加入的可以相等;
      • 如果相等,则直接替换 value
      • 如果不相等,则需要判断是树结构还是链表结构,做出相应的处理;
      • 如果添加时发现容量不够,则需要扩容;
  • 第一次添加,则需要扩容 table 容量为 16,临界值为 12;
  • 以后扩容,则需要扩容 table 容量为原来的 2 倍,临界值为原来的 2 倍;
  • 如果一条链表元素个数超过 8,并且 table 大小 大于等于 64,则会进行树化;
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// HashMap - 案例
public class Map02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("001",new Person("001","张三",20000));
hashMap.put("002",new Person("002","李四",12000));
hashMap.put("003",new Person("003","王五",26000));
hashMap.put("004",new Person("004","赵六",18000));

Set keySet = hashMap.keySet();
for (Object value :keySet) {
// 向下转型
Person p = (Person) hashMap.get(value);
if (p.getSal() > 18000){
System.out.println(p);
}
}
}
}

class Person{
private String name,id;
private double sal;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public double getSal() {
return sal;
}

public void setSal(double sal) {
this.sal = sal;
}

public Person(String id,String name, double sal) {
this.name = name;
this.id = id;
this.sal = sal;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
", sal=" + sal +
'}';
}
}

2.HashTable

:::tip

  • HashTable 存放元素是键值对:即 key-value;
  • HashTable 的键和值不能为 null,否则会抛出 空指针异常(NullPointerException)
  • HashTable 的使用方法和 HashMap 基本一致;
  • HashTable 的线程是安全的,HashMap 的线程是不安全的;
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// HashTable - 案例
public class HashTable01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Hashtable hashtable = new Hashtable();

// 添加
hashtable.put("001","张三");
hashtable.put("002","李四");
hashtable.put("003","王五");
System.out.println(hashtable);

// 修改
hashtable.put("001","令狐冲");
System.out.println(hashtable);

// 删除
hashtable.remove("003");
System.out.println(hashtable);

// null 报错,NullPointerException
hashtable.put(null,"test");
// hashtable.put("test",null);
}
}

3. Properties

:::tip

  • Properties 类继承自 HashTable 类,并实现了 Map 接口;
  • Properties 是以键值对的形式存储,不能使用 null;
  • Properties 与 HashTable 类似;
  • Properties 可用于从 xxx.properties 文件中,加载数据到 Properties 类对象,并进行修改和读取;
    • xxx.properties 通常作为配置文件;
      :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Properties - 案例
public class Properties01 {
public static void main(String[] args) {
Properties p = new Properties();

// 增加
p.put("001","张三疯");
p.put("002","张三丰");
p.put("003","张君宝");
System.out.println(p);

// 修改
p.put("001","张无忌");
System.out.println(p);

// 获取值
System.out.println(p.get("002"));

// 删除
p.remove("003");
System.out.println(p);
}
}

4. TreeMap

TreeSet 和 TreeMap 的区别:

  • 底层数据不同
    • TreeSet 底层的 key 值是传入的值,value 是一个固定值;
    • TreeMap 底层的 key 和 value 都是可变的;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// TreeMap - 案例
public class TreeMap1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// 使用匿名内部类,重写排序方法
TreeMap t = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 从大到小排序,以 key 的 ASCII 码排序
return ((String)o2).compareTo((String) o1);
}
});
t.put("a1","张三");
t.put("b2","李四");
t.put("d4","王五");
t.put("c3","赵六");

System.out.println(t);
}
}

// 运行结果
{d4=王五, c3=赵六, b2=李四, a1=张三}

3. 如何选择集合实现类

:::danger

  1. 判断存储的类型(一组对象或一组键值对);
  2. 一组对象:Collection 接口
    1. 允许重复:List
      1. 增删多:LinkedLiist(底层维护了一个双向链表);
      2. 改查多:ArrayList(底层维护 Object 类型的可变数组);
    2. 不允许重复:Set
      1. 无序:HashSet(底层是 HashMap,维护了一个哈希表,即(数组+链表+));
      2. 有序:TreeSet
      3. 插入和取出的顺序一致:LinkedHashSet(底层维护了一个数组+双向链表);
  3. 一组键值对:Map 接口
    1. 键无序:HashMap(底层是:哈希表,数组+链表+红黑树);
    2. 键有序:TreeMap
    3. 键插入和取出顺序一致:LinkedHashMap
    4. 读取文件:Properties
      :::

4. Collections 工具类

:::tip
Collections 工具类介绍:

  1. Collections 是一个操作Set、List 和 Map 等集合的工具类;
  2. Collections 中提供了一系列静态的方法,对集合元素进行排序、查询、修改等操作;
  3. 排序操作均为 static 方法;
    1. reverse(List):反转 List 中元素的顺序;
    2. shuffle(List): 对 List 集合元素进行随机排序;
    3. sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
    4. sort(List,Comparator):根据指定的 Comparator 产生顺序排序;
    5. swap(List,int,int):将指定 list 集合中的 i 处元素和 j 处元素进行交换;
  4. 查找、替换
    1. Object max(Collection):根据自然排序, 返回给定集合中的最大元素;
    2. Object max(Collection, Comparator): 根据 Comparator 指定条件顺序,返回最大值;
    3. Object min(Collection):返回最小值;
    4. Object min(Collection,Comparator): 根据规则返回最小值;
    5. int frequency(Collection, Object): 返回指定集合中指定元素的出现次数;
    6. void copy(List dest,List src): 将 src 中的内容复制到 dest 中,新的集合元素个数需要和旧的集合元素个数一致,否则报错;
    7. boolean replaceAll(List list,Object oldVal, Object newVal): 使用新值替换 List 对象的所有旧值;
      :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Collection 工具类 - 排序 案例
public class Collections1 {
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add("张三");
al.add("张三疯");
al.add("张山峰");
al.add("张三芬");
System.out.println("原始数组= "+al);

// 反转排序
Collections.reverse(al);
System.out.println("反转排序= "+al);

// 随机排序
Collections.shuffle(al);
System.out.println("随机排序= " + al);

// 自然排序
Collections.sort(al);
System.out.println("根据元素自然排序= "+ al);

// 自定义排序
Collections.sort(al, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 从大到小排序,以 key 的 ASCII 码排序
return ((String)o2).compareTo((String) o1);
}
});
System.out.println("自定义排序(从大到小)= "+ al);

// 元素位置交换
Collections.swap(al,1,2);
System.out.println("下标 1 和 2 互换位置" + al);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Collections - 查找、替换 案例
public class Collections1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add("张三");
al.add("欧阳疯子");
al.add("鸡儿拖洛夫斯基");
al.add("张三芬");
al.add("张三");
System.out.println("原始数组= "+al);

// 获取最大值
System.out.println("最大值= " + Collections.max(al));

// 根据规则排序,获取最大值
Object maxs = Collections.max(al, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("最大值= "+ maxs);

// 查看元素出现的次数
System.out.println("元素出现的次数= "+ Collections.frequency(al,"张三"));

// 复查集合
// 新的集合元素个数需要和旧的元素集合个数一致,否则报错
ArrayList al1 = new ArrayList();
for(int i = 0; i< al.size(); i++){
al1.add(null);
}
Collections.copy(al1, al);
System.out.println(al1);

// 替换集合元素
Collections.replaceAll(al, "张三", "张无忌");
System.out.println(al);
}
}

案例 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 创建两个新闻,处理标题,倒序输出
public class HomeWork1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add(new News("新冠确诊病例超千万,数百万印度教信徒赶赴恒河\"圣浴\"引民众担忧"));
al.add(new News("男子突然想起 2 月前钓的鱼还在网兜里,捞起一看赶紧放生"));

//倒序遍历
Collections.reverse(al);
Iterator iterator = al.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
// 方式一
// 向下转型
News news = (News) o;
if (news.getTitle().length() > 15){
// 截取字符串,进行拼接
String title = news.getTitle().substring(0,15) + "...";
news.setTitle(title);
}
System.out.println(o);
}

}
}

class News {
private String title, body;

public News(String title) {
this.title = title;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

@Override
public String toString() {
// 方式二
//return title.length() > 15 ? title.substring(0, 15) + "..." : title;
return title;
}

public String getBody() {
return body;
}

public void setBody(String body) {
this.body = body;
}
}

// 运行结果
男子突然想起 2 月前钓的鱼还...
新冠确诊病例超千万,数百万印度...

案例 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 创建两个新闻,处理标题,倒序输出
public class HomeWork2 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashMap hm = new HashMap();
hm.put("jack",650);
hm.put("tom",1200);
hm.put("smith",2900);
System.out.println(hm);

// 修改 jack 的工资
hm.put("jack",2600);
System.out.println("修改 jack 的工资= " + hm);

// 为所有员工工资加薪 100
for (Object o : hm.keySet()) {
hm.put(o,(int)hm.get(o) + 100);
}
System.out.println("全体加薪 100=" + hm);

// 遍历所有 员工
Iterator i = hm.keySet().iterator();
while (i.hasNext()) {
Object o = (String) i.next();
System.out.println(o);
}

// 遍历所有 工资
for (Object o :hm.values()) {
System.out.println(o);
}
}
}

// 运行结果
{tom=1200, smith=2900, jack=650}
修改 jack 的工资= {tom=1200, smith=2900, jack=2600}
全体加薪 100={tom=1300, smith=3000, jack=2700}
tom
smith
jack
1300
3000
2700

二、泛型

1
2
interface 接口<T>
class 类<K,V>{}

:::tip
说明:

  • 其中,T、K、V 不代表值,而是表示类型;
  • 任何字母都可以,常用 T 或 E 表示;
    :::

:::note

  • 泛型又称参数化类型,解决数据类型安全性问题;
  • 在类声明或实例化时,只要指定号序号的具体类型即可;
    • 如:ArrayList<Dog> dog = new ArraryList<Dog>
  • 泛型可以保证编译时没有警告,运行时不会抛异常;
  • 泛型的作用:
    • 可以在类声明时通过一个标识(如:E)表示类中某个属性的类型,或是某个返回值的类型,或者是参数类型
      :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Generic01 {
public static void main(String[] args) {
// 指定对象 E 的数据类型
Person<String> per = new Person<>("1");
per.f();
}
}

class Person<E>{
// E 表示 s 的数据类型在创建对象时指定
E s;

// 表示参数类型使用 E
public Person(E s){
this.s = s;
}

// 表示返回类型使用 E
public E f(){
return s;
}
}

:::danger
注意事项:

  • 泛型 只能是 引用数据类型,不能是基本数据类型;
    • 如: List<Integer> - 引用数据是类型;
    • 如: List<int> 会报错 - 基本数据类型;
  • 在给泛型指定具体类型后,可以传入该类型或其子类型;
  • 如果不指定泛型的类型,默认给他的泛型为 Object;
    :::

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 对员工进行排序,(先按名称排序,再按生日排序)
public class Generic03 {
public static void main(String[] args) {
ArrayList<Employee> e = new ArrayList<>();
e.add(new Employee("Jack",20000,new MyDate(2,22,1999)));
e.add(new Employee("Jack",20000,new MyDate(1,22,1999)));
e.add(new Employee("Tom",18000,new MyDate(9,9,2003)));
e.add(new Employee("Rose",12000,new MyDate(12,2,1988)));
e.add(new Employee("Jack",20000,new MyDate(1,21,1999)));
e.add(new Employee("Jack",20000,new MyDate(1,22,1979)));

for (Object o :e) {
System.out.println(o);
}

e.sort(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
int res = (o1.getName()).compareTo(o2.getName());
if (res != 0){
return res;
}
return o1.getBirthday().compareTo(o2.getBirthday());
}
});
System.out.println("=========排序后=========");
for (Object o :e) {
System.out.println(o);
}

}
}

class Employee{
private String name;
private double sal;
private MyDate birthday;

public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getSal() {
return sal;
}

public void setSal(double sal) {
this.sal = sal;
}

public MyDate getBirthday() {
return birthday;
}

public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}

@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}

class MyDate implements Comparable<MyDate>{
private int month,day,year;

public MyDate(int month, int day, int year) {
this.month = month;
this.day = day;
this.year = year;
}

// 重写 比较方法
@Override
public int compareTo(MyDate o) {
int resYear = year - o.getYear();
if (resYear != 0){
return resYear;
}

int resMonth = month - o.getMonth();
if (resMonth != 0){
return resMonth;
}

return day - o.getDay();
}

public int getMonth() {
return month;
}

public void setMonth(int month) {
this.month = month;
}

public int getDay() {
return day;
}

public void setDay(int day) {
this.day = day;
}

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

@Override
public String toString() {
return "MyDate{" +
"month=" + month +
", day=" + day +
", year=" + year +
'}';
}
}

1. 自定义泛型 - 类

1
2
3
4
// 泛型标识可以有多个
class 类名 <T,R...>{
成员
}

::: warning
注意事项:

  • 普通成员可以使用泛型(属性,方法);
  • 使用泛型的数组,不能初始化;
  • 静态方法中不能使用类的泛型;
  • 泛型类的类型,是在创建对象时确定的 ( 创建对象时,需要指定确定的类型 );
  • 如果在创建对象时, 没有指定类型, 默认为 Object;
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 自定义泛型
public class Generic01 {
public static void main(String[] args) {
Test<Double, String, Integer> test = new Test<>();
test.setD(3.1);
test.setS("老王");
}
}

class Test<D,S,I>{
// 属性使用泛型
private D d;
private S s;
private I i;

// 方法使用 泛型
public D getD() {
return d;
}

public void setD(D d) {
this.d = d;
}

public S getS() {
return s;
}

public void setS(S s) {
this.s = s;
}

public I getI() {
return i;
}

public void setI(I i) {
this.i = i;
}
}

2. 自定义泛型 - 接口

1
2
3
4
// 泛型标识可以有多个
interface 接口名 <T,R...>{
成员
}

:::warning
注意事项:

  • 接口中, 静态成员也不能使用泛型;
  • 泛型接口的类型, 在继承接口 或者实现接口时确定;
  • 没有指定类型, 默认为 Object;
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 接口泛型
interface IUsb<U, R> {
// 静态属性成员不能使用泛型
// U name;

R get(U u);
// 抽象方法
void hi(R r);

void run(R r1, R r2, U u1,U u2);

default R method(U u) {
return null;
}
}

// 继承接口时需要指定泛型的类型
interface Ib extends IUsb<String, Double>{

}

// 实现接口时,指定泛型的类型
class Fa implements IUsb<Integer,Float>{
@Override
public Float get(Integer integer) {
return null;
}

@Override
public void hi(Float aFloat) {

}

@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {

}
}

3. 自定义泛型 - 方法

1
修饰符<T,R...> 返回类型 方法名(参数列表){}

:::warning
注意事项:

  • 泛型方法, 可以定义在普通方法中, 也可以定义在泛型类中;
  • 当泛型方法被调用时, 类型需要确定;
  • public void eat(E e){}; , 修饰符后没有表示不是泛型方法,而是使用了泛型;
  • 泛型方法, 既可以使用类 声明的泛型, 也可以使用 自己申明的泛型;
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 泛型方法
public class MethodGeneric {
public static void main(String[] args) {
Car car = new Car();
// 调用方法时,自动确定 泛型的类型
car.eat("老王", 18);
}
}

class Car{
// 普通方法
public void run(){};

//泛型方法
public <T,R> void eat(T t, R r){}
}

class Fish<E>{
// 泛型方法, 方法使用了泛型, 自己声明
public void eat(E e){}

// 泛型方法 定义了泛型, 类声明
public<X> void cat(X x){}
}

4. 泛型的继承和通配符

  1. 泛型不具备继承性;
    1. List<Object> list = new ArrayList<String>(); // 报错
  2. <?> : 表示支持任意泛型类型;
  3. <? extends A> : 表示支持 A 类 以及 A 类的子类, 规定了 泛型的上限;
  4. <? super A> : 表示支持 A 类 以及 A 类的父类, 规定了泛型的 下限;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 通配符的使用
public class GenericExtends01 {
public static void main(String[] args) {
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();

// <?> : 表示支持任意泛型类型;
collection1(list1);
collection1(list2);
collection1(list3);
collection1(list4);
collection1(list5);

// <? extends A> : 表示支持 A 类 以及 A 类的子类, 规定了 泛型的上限;
collection2(list1); // 报错 Object
collection2(list2); // 报错 String
collection3(list3);
collection2(list4);
collection2(list5);

// <? super A> : 表示支持 A 类 以及 A 类的父类, 规定了泛型的 下限;
collection3(list1);
collection3(list2); // 报错 String
collection3(list3);
collection3(list4); // 报错 BB
collection3(list5); // 报错 CC
}

public static void collection1(List<?> list){};
public static void collection2(List<? extends AA> list){};
public static void collection3(List<? super AA> list){};
}

class AA{}

class BB extends AA{}

class CC extends BB{}

5. JUnit (单元测试框架)

:::tip
介绍:

  • JUnit 是一个 java 语言的单元测试框架;
  • 多数 Java 开发环境都已经集成了 JUnit作为单元测试的工具;
  • 在需要测试的方法前加上 @Test , 引入相应的库即可
    :::
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// JUnit 使用
public class JUnit01 {
public static void main(String[] args) {

}

@Test
public void m1(){
System.out.println("m1被调用");
}

@Test
public void m2(){
System.out.println("m2被调用");
}
}

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class HomeWork01 {
public static void main(String[] args) {

}

@Test
public void tests(){
DAO<User> dao = new DAO<>();
dao.save("1001",new User(1001,20,"tom"));
dao.save("1002",new User(1002,20,"jack"));
dao.save("1003",new User(1003,20,"wear"));
dao.save("1004",new User(1004,20,"sam"));

System.out.println(dao.list());
}

}

class DAO<T>{
Map<String,T> map = new HashMap<>();

public void save(String id, T entity){
map.put(id, entity);
}

public T get(String id){
return map.get(id);
}

public void update(String id, T entity){
map.put(id, entity);
}

public List<T> list(){
List<T> list = new ArrayList<>();
for (String key : map.keySet()) {
list.add(get(key));
}
return list;
}
public void delete(String id){
map.remove(id);
}
}

class User{
private int id,age;
private String name;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}