Java集合
集合的体系结构
可分为:Collection(单列集合)和 Map(双列集合)。

List 系列集合:添加的元素是有序、可重复、有索引
Set 系类集合:添加的元素是无序、不重复、无索引
Collection
Collection 是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
| 方法名称 | 说明 |
|---|---|
| public boolean add(E e) | 把给定的对象添加到当前集合中 |
| public void clear() | 清空集合中所有的元素 |
| public boolean remove(E e) | 把给定的对象在集合中删除 |
| public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
| public boolean isEmpty() | 判断当前集合是否为空 |
| public int size() | 返回集合中元素的个数/集合的长度 |
package com.faxont.collection;
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemo {
public static void main(String[] args) {
/*
public boolean add(E e) 把给定的对象添加到当前集合中
public void clear() 清空集合中所有的元素
public boolean remove(E e) 把给定的对象在集合中删除
public boolean contains(Object obj) 判断当前集合中是否包含给定的对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数/集合的长度
注意点:
Collection 是一个接口,我们不能直接创建它的对象
所以,现在我们学习他的方法时,只能创建他实现类的对象
实现类:ArrayList
*/
// 目的:为了学习 Collection 接口里面的方法
// 自己在做一些练习的时候,还是按照之前的方式去创建对象
Collection<String> coll = new ArrayList<>();
// 1. 添加元素
// 细节1:如果我们要往 List 系列集合中添加数据,那么方法永远返回 true,因为 List 系列的是允许元素重复的
// 细节2:如果我们要往 Set 系列集合中添加数据,如果当前要添加的元素不存在,方法返回 true,表示添加成功;
// 如果当前要添加的元素已经存在,方法返回 false,表示添加失败
// 因为 Set 系列的集合不允许重复
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
System.out.println(coll);
// 2. 清空
// coll.clear();
// System.out.println(coll);
// 3. 删除
// 细节1:因为 Collection 里面定义的是共性的方法,所以此时不能通过索引进行删除,只能通过元素的对象进行删除
// 细节2:方法会有一个布尔类型的返回值,删除成功返回 true,删除失败返回 false
// 如果要删除的元素不存在,就会失败
coll.remove("aaa");
System.out.println(coll);
// 4. 判断集合中是否包含某个元素
// 细节:底层是依赖 equals 方法进行判断是否存在的
// 所以,如果集合中存储的是自定义对象,也想通过 contains 方法判断元素是否存在,那么在 Javabean 类中一定要重写 equals 方法
System.out.println(coll.contains("aaa"));
System.out.println(coll.contains("bbb"));
// 5. 判断集合是否为空
System.out.println(coll.isEmpty());
// 6. 获取集合的长度
System.out.println(coll.size());
coll.add("ddd");
System.out.println(coll.size());
}
}
package com.faxont.collection;
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemo2 {
public static void main(String[] args) {
// 1. 创建集合的对象
Collection<Student> coll = new ArrayList<>();
// 2. 创建三个学生对象
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 24);
Student s3 = new Student("wangwu", 25);
// 3. 把学生对象添加到集合当中
coll.add(s1);
coll.add(s2);
coll.add(s3);
// 4. 判断集合中某个学生对象是否包含
Student s4 = new Student("zhangsan", 23);
// 如果姓名年龄相同,就认为是同一个学生
// 因为 Contains 方法在底层是调用 equals 方法判断对象是否一致的
// 所以,如果 equals 方法没有被重写,那么 Contains 方法判断对象是否一致,就是比较地址值
System.out.println(coll.contains(s4));// true
}
}
package com.faxont.collection;
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Collection 的遍历方式
迭代器遍历
迭代器不依赖索引
Java 中迭代器的核心是 java.util.Iterator 接口,迭代器是遍历集合(如 ArrayList、HashSet 等)元素的标准工具 / 方式。
Collection 集合获取迭代器
| 方法名称 | 说明 |
|---|---|
| Iterator |
返回迭代器对象,默认指向当前集合的0索引 |
Iterator 中的常用方法
| 方法名称 | 说明 |
|---|---|
| boolean hasNext() | 判断当前位置是否有元素,有元素返回 true,没有元素返回 false |
| E next() | 获取当前位置的元素,并将迭代器对象移向下一个位置 |
package com.faxont.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo3 {
public static void main(String[] args) {
/*
Collection 系列集合三种通用的遍历方式:
1. 迭代器遍历
2. 增强 for 遍历
3. lambda 表达式遍历
迭代器遍历相关的三个方法:
Iterator<E> iterator() : 获取一个迭代器对象
boolean hasNext() : 判断当前指向的位置是否有元素
E next() : 获取当前指向的元素并移动指针
*/
// 1. 创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
// 2, 获取迭代器对象
// 迭代器就好比是一个箭头,默认指向集合的 0 索引处
Iterator<String> it = coll.iterator();
// 3. 利用循环不断地去获取集合中的每一个元素
while (it.hasNext()) {
// 4. next 方法的两件事情:获取元素并移动指针
String s = it.next();
System.out.println(s);
}
}
}
注意:
- 当指针指向末尾无元素处时,仍然调用 next() 方法获取元素,会报错 NoSuchElementException
- 迭代器遍历完毕,指针不会复位
- 循环中只能用一次 next 方法
- 迭代器遍历时,不能用集合的方法进行增加或者删除
package com.faxont.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo4 {
public static void main(String[] args) {
/*
1. 当指针指向末尾无元素处时,仍然调用 next() 方法获取元素,会报错 NoSuchElementException
2. 迭代器遍历完毕,指针不会复位
3. 循环中只能用一次 next 方法
4. 迭代器遍历时,不能用集合的方法进行增加或者删除
*/
// 1. 创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
// 2. 获取迭代器对象
// 迭代器就好比是一个箭头,默认指向集合的 0 索引处
Iterator<String> it = coll.iterator();
// 3. 利用循环不断地去获取集合中的每一个元素
while(it.hasNext()){
// 4. next 方法的两件事情:获取元素并移动指针
String s = it.next();
System.out.println(s);
}
// 当上面的循环结束之后,迭代器的指针已经指向了最后没有元素的位置
// System.out.println(it.next());
// 迭代器遍历完毕,指针不会复位
// System.out.println(it.hasNext());// false
// System.out.println(it.hasNext());// false
// 如果我们要继续第二次遍历集合,只能再次获取一个新的迭代对象
System.out.println("------------------");
Iterator<String> it2 = coll.iterator();
while(it2.hasNext()){
String s = it2.next();
System.out.println(s);
}
}
}
package com.faxont.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo5 {
public static void main(String[] args) {
/*
1. 当指针指向末尾无元素处时,仍然调用 next() 方法获取元素,会报错 NoSuchElementException
2. 迭代器遍历完毕,指针不会复位
3. 循环中只能用一次 next 方法
4. 迭代器遍历时,不能用集合的方法进行增加或者删除
如果实在需要删除,可以使用迭代器提供的 remove() 方法进行删除
*/
// 1. 创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
coll.add("eee");
// 2. 获取迭代器对象
// 迭代器就好比是一个箭头,默认指向集合的 0 索引处
Iterator<String> it = coll.iterator();
// 3. 利用循环不断地去获取集合中的每一个元素
while(it.hasNext()){
// 4. next 方法的两件事情:获取元素并移动指针
// System.out.println(it.next());// aaa ddd eee
// System.out.println(it.next()); // bbb ccc (NoSuchElementException)
String s = it.next();
if("bbb".equals(s)){
// coll.remove("bbb");// ConcurrentModificationException
it.remove();
}
}
System.out.println(coll);
}
}
增强 for 循环遍历
- 增强 for 的底层就是迭代器,为了简化迭代器的代码书写的。
- 它是 JDK5 之后出现的,其内部原理就是一个 Iterator 迭代器。
- 所有的单列集合和数组才能用增强 for 进行遍历
// 格式
for(元素的数据类型 变量名 : 数组或者集合){
}
// 例子
for(String s : list){
System.out,println(s);
}
package com.faxont.collection;
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemo6 {
public static void main(String[] args) {
/* Collection 系列集合三种通用的遍历方式:
1. 迭代器遍历
2. 增强 for 遍历
3. lambda 表达式遍历
增强 for 遍历格式:
for(元素数据类型 变量名 : 集合/数组名){}
*/
// 1. 创建集合
Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");
// 2. 增强 for 遍历
// 注意点:
// s 其实就是一个第三方变量,在循环的过程中依次表示集合中的每一个数据
for(String s : coll){
s = "aaa";
}
System.out.println(coll);// [zhangsan, lisi, wangwu]
}
}
增强 for 的细节
- 修改增强 for 中的变量,不会改变集合中原本的数据。
for(String s : list){
s = "a";
}
System.out.println(list);// 输出的还是原本的集合
Lambda 表达式遍历
得益于 JDK8 开始的新技术 Lambda 表达式,提供了一种更简单、更直接的遍历集合的方式。
| 方法名称 | 说明 |
|---|---|
| defalut void forEach(Consumer<? super T> action): | 结合 lambda 遍历集合 |
package com.faxont.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
public class CollectionDemo7 {
public static void main(String[] args) {
/* Collection 系列集合三种通用的遍历方式:
1. 迭代器遍历
2. 增强 for 遍历
3. lambda 表达式遍历
lambda 表达式遍历:
default void forEach(Consumer<? super E> action):
*/
// 1. 创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");
// 2. 利用匿名内部类的方式遍历
// forEach 的底层原理:
// 其实也会自己遍历集合,依次得到每一个元素
// 把得到的每一个元素,传递给下面的 accept 方法
// s 依次表示集合中的每一个元素
// coll.forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
// 3. 利用 lambda 表达式进行遍历
coll.forEach(s -> System.out.println(s));
}
}
总结
- Collection 是单列集合的顶层接口,所有方法被 List 和 Set 系列集合共享
- 常见成员方法:
add、clear、remove、contains、isEmpty、size
- 三种通用的遍历方式:
- 迭代器:在遍历的过程中需要删除元素,请使用迭代器。
- 增强for、Lambda:仅仅想遍历,则使用增强 for 或 Lambda 表达式。
List 集合
List 集合的特点
- 有序:存和取的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List 集合的特有方法
- Collection 的方法 List 都继承了。
- List 集合因为有索引,所以多了很多索引操作的方法。
| 方法名称 | 说明 |
|---|---|
| void add(int index, E element) | 在此集合中的指定位置插入指定的元素 |
| E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
| E set(int index, E element) | 修改指定索引处的元素,返回被修改的元素 |
| E get(int index) | 返回指定索引处的元素 |
package com.faxont.list;
import java.util.ArrayList;
import java.util.List;
public class ListDemo1 {
public static void main(String[] args) {
/*
void add(int index, E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index, E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素
*/
// 1. 创建一个集合
List<String> list = new ArrayList<>();
// 2. 添加元素
list.add("aaa");
list.add("bbb");
list.add("ccc");
// void add(int index, E element) 在此集合中的指定位置插入指定的元素
// 细节:原来索引上的元素会依次往后挪
list.add(1, "QQQ");
// E remove(int index) 删除指定索引处的元素,返回被删除的元素
String remove = list.remove(0);
System.out.println(remove);// aaa 返回被删除的元素
// E set(int index, E element) 修改指定索引处的元素,返回被修改的元素
String set = list.set(0, "aaa");
System.out.println(set);// QQQ 返回被修改的元素
// E get(int index) 返回指定索引处的元素
String get = list.get(0);
System.out.println(get);
// 3. 打印集合
System.out.println(list);
}
}
package com.faxont.list;
import java.util.ArrayList;
import java.util.List;
public class ListDemo2 {
public static void main(String[] args) {
// 1. 创建集合并添加元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 2. 删除元素
// 请问:此时删除的是 1 这个元素,还是在 1 索引上的元素?
// 为什么?
// 因为在调用方法的时候,如果方法出现了重载现象
// 优先调用,实参与形参类型一致的那个方法
// list.remove(1);
// 手动装箱:手动把基本数据类型的 1,变成 Integer 类型
Integer i = Integer.valueOf(1);// Integer i = new Integer(1);
list.remove(i);
System.out.println(list);
}
}
List 的遍历方式
-
迭代器遍历
-
列表迭代器遍历
-
增强 for遍历
-
Lambda 表达式遍历
-
普通 for 循环遍历(因为 List 集合存在索引)
package com.faxont.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class ListDemo3 {
public static void main(String[] args) {
/*
List 集合五种遍历方式:
1. 迭代器遍历
2. 列表迭代器遍历
3. 增强 for遍历
4. Lambda 表达式遍历
5. 普通 for 循环遍历(因为 List 集合存在索引)
*/
// 创建集合并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 1. 迭代器遍历
// Iterator<String> it = list.iterator();
// while(it.hasNext()){
// String s = it.next();
// System.out.println(s);
// }
// 2. 增强 for 遍历
// for (String s : list) {
// System.out.println(s);
// }
// 3. Lambda 遍历
// forEach 方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
// 并把每一个元素传递给下面的 accept 方法
// list.forEach(s -> System.out.println(s));
// 4. 普通 for 循环遍历
// for (int i = 0; i < list.size(); i++) {
// System.out.println(list.get(i));
// }
// 5. 列表迭代器遍历
// 增加了一个新方法 add 用来在迭代器中添加元素
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String s = it.next();
if("bbb".equals(s)){
it.add("qqq");
}
System.out.println(s);
}
System.out.println(list);
}
}
五种遍历方式对比
- 迭代器遍历:在遍历的过程中需要删除元素,请使用迭代器
- 列表迭代器:在遍历的过程中需要添加元素,请使用列表迭代器
- 增强 for 循环 / Lambda 表达式:仅仅想遍历,那么使用增强 for 或 Lambda 表达式
- 普通 for:如果遍历的时候想操作索引,可以用普通 for
数据结构概述
数据结构是计算机底层存储、组织数据的方式。
是指数据相互之间是以什么方式排列在一起的。
数据结构是为了更加方便地管理和使用数据,需要结合具体的业务场景来进行选择。一般情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
- 每种数据结构长什么样子?
- 如何添加数据?
- 如何删除数据?
栈
栈的特点:后进先出,先进后出。
数据进入栈模型的过程称为:压 / 进栈。
数据离开栈模型的过程称为:弹 / 出栈。
队列
队列的特点:先进先出,后进后出。
数据从后端进入队列模型的过程称为:入队列。
数据从前端进入队列模型的过程称为:出队列。
数组
数组是一种查询快,增删慢的模型。
- 查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
- 删除效率低:要将原始数据删除,同时后面每个数据前移。
- 添加效率极低:添加位置后的每个数据后移,再添加元素。
链表
链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
链表查询慢,无论查询哪个数据都要从头开始找。
链表增删相对快。
ArrayList
ArrayList 集合底层原理
- 利用空参创建的集合,在底层创建一个默认长度为0的数组
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容1.5倍
- 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
LinkedList
- 底层数据结构是双链表,查询慢,增删快,但是如果朝左的是首尾元素,速度也是极快的。
- LinkedList 本身多了很多直接操作首尾元素的特有 API。
| 特有方法 | 说明 |
|---|---|
| public void addFirst(E e) | 在该列表开头插入指定的元素 |
| public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
| public E getFirst() | 返回此列表中的第一个元素 |
| public E getLast() | 返回此列表中的最后一个元素 |
| public E removeFirst() | 从此列表中删除并返回第一个元素 |
| public E removeLast() | 从此列表中删除并返回最后一个元素 |
泛型深入
泛型:是 JDK5 中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
泛型的格式:<数据类型>
注意:泛型只能支持引用数据类型。
package com.faxont.generics;
import java.util.ArrayList;
import java.util.Iterator;
public class GenericsDemo1 {
public static void main(String[] args) {
// 没有泛型的时候,集合如何存储数据
// 结论:
// 如果我们没有给集合指定类型,默认认为所有的数据类型都是 Object 类型
// 此时可以往集合添加任意的数据类型
// 带来一个坏处,我们在获取数据的时候,无法使用它特有的行为
// 此时推出了泛型,可以在添加数据的时候就把数据类型进行统一
// 而且我们在获取数据的时候,也省得强转了,非常的方便
// 1. 创建集合的对象
ArrayList list = new ArrayList();
// 2. 添加数据
// add(Object obj)
list.add("add");// String
list.add(123);// Integer
list.add(true);// Boolean
list.add(new Student("zhangsan", 20));// 自定义类
// 3. 遍历集合
Iterator it = list.iterator();
while(it.hasNext()){
Object next = it.next();
// 多态的弊端是无法访问子类的特有功能
System.out.println(next);
}
}
}
package com.faxont.generics;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
泛型的好处
- 统一数据类型。
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
扩展知识点:Java 中的泛型是伪泛型

泛型的细节
- 泛型中不能写基本数据类型
- 指定泛型的具体类型后,传递数据时,可以传入该类型或者其子类类型
- 如果不写泛型,类型默认是 ObJect
泛型可以在很多地方进行定义
- 类后面——泛型类
- 方法上面——泛型方法
- 接口后面——泛型接口
泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,可以定义带有泛型的类
// 格式
修饰符 class 类名<类型> {
}
// 举例
public class ArrayList<E> {// 创建该类对象时,E 就确定类型
}
此处 E 可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成:T、E、K、V等
package com.faxont.generics;
import java.util.Arrays;
/**
* 当我在编写一个类的时候,如果不能确定类型,那么这个类型就可以定义为泛型类
*/
public class MyArrayList<E> {
Object[] obj = new Object[10];
int size;
/**
* E:表示是不确定的类型,该类型在类名后面已经定义过了
* @param e:形参的名字,变量名
* @return
*/
public boolean add(E e){
obj[size++] = e;
return true;
}
public E get(int index){
return (E) obj[index];
}
@Override
public String toString() {
return Arrays.toString(obj);
}
}
package com.faxont.generics;
public class GenericsDemo2 {
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
System.out.println(list);
System.out.println(list.add("ddd"));
System.out.println(list.get(0));
System.out.println(list);
}
}
泛型方法
方法中形参类型不确定时,可以使用类名后面定义的泛型
// 格式
修饰符 <类型> 返回值类型 方法名(类型 变量名){
}
// 举例
public <T> void show(T t){// 调用该方法时,T 就确定类型
}
泛型方法的练习
定义一个工具类:ListUtil
类中定义一个静态方法 addAll,用来添加多个集合的元素。
package com.faxont.generics;
import java.util.ArrayList;
public class ListUtil {
private ListUtil(){}
public static <E> void addAll(ArrayList<E> list, E... array){
for (E e : array) {
list.add(e);
}
}
}
package com.faxont.generics;
import java.util.ArrayList;
public class GenericsDemo3 {
public static void main(String[] args) {
/*
定义一个工具类:ListUtil
类中定义一个静态方法 addAll,用来添加多个集合的元素。
*/
ArrayList<String> list = new ArrayList<>();
ListUtil.addAll(list, "aaa", "bbb", "ccc");
System.out.println(list);
}
}
泛型接口
// 格式
修饰符 interface 接口名<类型>{
}
// 举例
public interface List<E>{
}
重点:如何使用一个带泛型的接口
方式1:实现类给出具体类型 方式2:实现类延续泛型,创建对象时再确定
package com.faxont.generics;
public class GenericsDemo4 {
public static void main(String[] args) {
/*
泛型接口的两种使用方式:
1. 实现类给出具体的类型
2. 实现类延续泛型,创建实现类对象时再确定类型
*/
// 1. 实现类给出具体的类型
MyArrayList2 list = new MyArrayList2();
list.add("aaa");
// 2. 实现类延续泛型,创建实现类对象时再确定类型
MyArrayList3<String> list2 = new MyArrayList3<>();
list2.add("bbb");
}
}
package com.faxont.generics;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class MyArrayList2 implements List<String> {
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator<String> iterator() {
return null;
}
@Override
public Object[] toArray() {
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a) {
return null;
}
@Override
public boolean add(String s) {
return false;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
return false;
}
@Override
public boolean addAll(Collection<? extends String> c) {
return false;
}
@Override
public boolean addAll(int index, Collection<? extends String> c) {
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
return false;
}
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
@Override
public void clear() {
}
@Override
public String get(int index) {
return null;
}
@Override
public String set(int index, String element) {
return null;
}
@Override
public void add(int index, String element) {
}
@Override
public String remove(int index) {
return null;
}
@Override
public int indexOf(Object o) {
return 0;
}
@Override
public int lastIndexOf(Object o) {
return 0;
}
@Override
public ListIterator<String> listIterator() {
return null;
}
@Override
public ListIterator<String> listIterator(int index) {
return null;
}
@Override
public List<String> subList(int fromIndex, int toIndex) {
return null;
}
}
package com.faxont.generics;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class MyArrayList3<E> implements List<E> {
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator<E> iterator() {
return null;
}
@Override
public Object[] toArray() {
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a) {
return null;
}
@Override
public boolean add(E e) {
return false;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
return false;
}
@Override
public boolean addAll(Collection<? extends E> c) {
return false;
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
return false;
}
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
@Override
public void clear() {
}
@Override
public E get(int index) {
return null;
}
@Override
public E set(int index, E element) {
return null;
}
@Override
public void add(int index, E element) {
}
@Override
public E remove(int index) {
return null;
}
@Override
public int indexOf(Object o) {
return 0;
}
@Override
public int lastIndexOf(Object o) {
return 0;
}
@Override
public ListIterator<E> listIterator() {
return null;
}
@Override
public ListIterator<E> listIterator(int index) {
return null;
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return null;
}
}
泛型的继承和通配符
- 泛型不具备继承性,但是数据具备继承性
package com.faxont.generics;
import java.util.ArrayList;
public class GenericsDemo5 {
public static void main(String[] args) {
/*
泛型不具备继承性,但是数据具备继承性
*/
// 创建集合的对象
ArrayList<GrandParent> grandparents = new ArrayList<>();
ArrayList<Parent> parents = new ArrayList<>();
ArrayList<Child> children = new ArrayList<>();
ArrayList<Student2> students = new ArrayList<>();
// 调用 method() 方法
// method(grandparents);
// method(parents);// 错误
// method(children);// 错误
// 数据具备继承性
grandparents.add(new GrandParent());
grandparents.add(new Parent());
grandparents.add(new Child());
parents.add(new Parent());
parents.add(new Child());
children.add(new Child());
method2(grandparents);
method2(parents);
method2(children);
method2(students);
method3(grandparents);
method3(parents);
method3(children);
// method3(students);// 错误
method4(grandparents);
method4(parents);
method4(children);
// method4(students);// 错误
}
public static void method(ArrayList<GrandParent> list){}
public static <E> void method2(ArrayList<E> list){
}
public static void method3(ArrayList<? extends GrandParent> list){
}
public static void method4(ArrayList<? super Child> list){
}
}
class GrandParent{}
class Parent extends GrandParent{}
class Child extends Parent{}
class Student2{}
package com.faxont.generics;
import java.util.ArrayList;
public class GenericsDemo6 {
public static void main(String[] args) {
/**
* 需求:
* 定义一个方法,形参是一个集合,但是集合中的数据类型不确定
*/
}
/**
* 此时,我们可以使用泛型的通配符:
* ? 也表示不确定的类型
* 他可以进行类型的限定
* ? extends E 表示集合中的数据类型是E或者E的子类
* ? super E 表示集合中的数据类型是E或者E的父类
*
* 应用场景:
* 1. 如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口
* 2. 如果类型不确定,但是能知道以后只能传递某个继承体现中的数据,那么就可以使用泛型通配符
* 泛型的通配符:
* 关键点:可以限定类型的范围
*
* @param list
*/
public static void method(ArrayList<? extends GrandParent> list){
}
public static void method1(ArrayList<? super Child> list){
}
/**
* 此时,泛型里面写的是什么类型,那么只能传递什么类型的数据
* 弊端:
* 利用泛型方法有一个小弊端,此时可以该接收任意的数据类型
* GrandParent Parent Child Student
* @param list
* @param <E>
*/
public static <E> void method2(ArrayList<E> list){
}
}
小练习
package com.faxont.practice;
public abstract class Animal {
public Animal(){};
public Animal(String name, int age){
this.name = name;
this.age = age;
}
private String name;
private int age;
public abstract void eat();
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;
}
}
abstract class Cat extends Animal {
}
class PersianCat extends Cat {
@Override
public void eat(){
System.out.println("一只叫做" + getName() + "的" + getAge() + "岁的波斯猫,正在吃小饼干");
}
}
class TabbyCat extends Cat {
@Override
public void eat(){
System.out.println("一只叫做" + getName() + "的" + getAge() + "岁的狸花猫,正在吃鱼");
}
}
abstract class Dog extends Animal {
}
class Teddy extends Dog {
@Override
public void eat(){
System.out.println("一只叫做" + getName() + "的" + getAge() + "岁的泰迪,正在吃骨头,边吃边蹭");
}
}
class Husky extends Dog {
@Override
public void eat(){
System.out.println("一只叫做" + getName() + "的" + getAge() + "岁的哈士奇,正在吃骨头,边吃边拆家");
}
}
package com.faxont.practice;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
/*
需求:
定义一个继承结构
动物
| |
猫 狗
| | | |
波斯猫 狸花猫 泰迪 哈士奇
属性:名字、年龄
行为:吃东西
方法体打印:一只叫做...的,...岁的波斯猫,正在吃小饼干
方法体打印:一只叫做...的,...岁的狸花猫,正在吃鱼
方法体打印:一只叫做...的,...岁的泰迪,正在吃骨头,边吃边蹭
方法体打印:一只叫做...的,...岁的哈士奇,正在吃骨头,边吃边拆架
测试类中定义一个方法用于饲养动物
public static void keepPet(ArrayList<???> list){
// 遍历集合,调用动物的 eat 方法
}
要求1:该方法能养所有品种的猫,但是不能养狗
要求2:该方法能养所有品种的狗,但是不能养猫
要求3:该方法能养所有的动物,但是不能传递其它类型
*/
ArrayList<PersianCat> persianCats = new ArrayList<>();
ArrayList<TabbyCat> tabbyCats = new ArrayList<>();
ArrayList<Teddy> teddys = new ArrayList<>();
ArrayList<Husky> huskies = new ArrayList<>();
keepPet(persianCats);
keepPet(tabbyCats);
keepPet2(teddys);
keepPet2(huskies);
keepPet3(persianCats);
keepPet3(tabbyCats);
keepPet3(teddys);
keepPet3(huskies);
}
// 要求1:该方法能养所有品种的猫,但是不能养狗
public static void keepPet(ArrayList<? extends Cat> list){
// 遍历集合,调用动物的 eat 方法
for(Cat cat : list){
cat.eat();
}
}
// 要求2:该方法能养所有品种的狗,但是不能养猫
public static void keepPet2(ArrayList<? extends Dog> list){
// 遍历集合,调用动物的 eat 方法
for(Dog dog : list){
dog.eat();
}
}
// 要求3:该方法能养所有的动物,但是不能传递其它类型
public static void keepPet3(ArrayList<? extends Animal> list){
// 遍历集合,调用动物的 eat 方法
for(Animal animal : list){
animal.eat();
}
}
}
数据结构(树)
度:每一个节点的子节点数量 二叉树中,任意节点的度<=2
二叉查找树
二叉查找树,又称二叉排序树或者二叉搜索树
特点:
- 每一个节点上最多有两个子节点
- 任意节点左子树上的值都小于当前节点
- 任意节点右子树上的值都大于当前节点
添加规则:小的放左边,大的放右边,一样的不存
二叉树的遍历方式
前序遍历
从根节点开始,然后按照当前节点,左子节点,右子节点的顺序遍历

中序遍历
从最左边的子节点开始,然后按照左子节点,当前节点,右子节点的顺序遍历

后序遍历
从最左边的子节点开始,然后按照左子节点,右子节点,当前节点的顺序遍历

层序遍历
一层一层地去遍历
平衡二叉树
规则:任意节点左右子树高度差不超过1
平衡二叉树的旋转机制
问:平衡二叉树是怎么保持平衡的呢?
答:平衡二叉树的旋转机制
规则1:左旋 规则2:右旋 触发时机:当添加一个节点之后,该树不再是一颗平衡二叉树
左旋
确定支点:从添加的节点开始,不断地往父节点找不平衡的节点
情况一

步骤:
- 以不平衡的点作为支点
- 把支点左旋降级,变成左子节点
- 晋升原来的右子节点
情况二

步骤:
- 以不平衡的点作为支点
- 将根节点的右侧往左拉
- 原先的右子节点变成新的父节点,并把多余的左子节点出让给已经降级的根节点当右子节点

右旋
跟左旋刚好相反
需要旋转的四种情况
左左
当根节点左子树的左子树有节点插入,导致二叉树不平衡,一次右旋即可
左右
当根节点左子树的右子树有节点插入,导致二叉树不平衡
先局部左旋,变为左左的情况,再整体右旋
右右
当根节点右子树的右子树有节点插入,导致二叉树不平衡,一次左旋即可
右左
当根节点右子树的左子树有节点插入,导致二叉树不平衡
先局部右旋,变为右右的情况,再整体左旋
红黑树
- 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
- 1972年出现,当时被称之为平衡二叉B树。后来,1978年被修改为如今的“红黑树”。
- 它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色。
- 每一个节点可以是红或者黑;红黑树不是高度平衡的,它的平衡是通过“红黑规则”进行实现的
- 红黑树的增删改查性能都很好
红黑规则
- 每一个节点或是红色的,或是黑色的
- 根节点必须是黑色的
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为 Nil,这些 Nil 视为叶节点,每个叶节点(Nil)是黑色的
- 如果某一个节点是红色的,那么它的子节点必须是黑色的(不能出现两个红色节点相连的情况)
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

默认颜色:添加节点默认是红色的(效率高)

Set 系列集合
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通 for 循环遍历,也不能通过索引来获取元素
Set 集合的实现类
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:可排序、不重复、无索引
Set 接口中的方法上基本上与 Collection 的 API 一致
练习:存储字符串并遍历
利用 Set 系列的集合,添加字符串,并使用多种方式遍历
- 迭代器
- 增强 for
- Lambda 表达式
package com.faxont.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetDemo1 {
public static void main(String[] args) {
/*
利用 Set 系统的集合,添加字符串,并使用多种方式遍历
1. 迭代器
2. 增强 for
3. Lambda 表达式
*/
// 1. 创建一个 Set 集合的对象
Set<String> set = new HashSet<>();
// 2. 添加元素
// 如果当前元素是第一次添加,那么可以添加成功,返回 true
// 如果当前元素是第二次添加,那么添加失败,返回 false
System.out.println(set.add("aaa"));
System.out.println(set.add("bbb"));
System.out.println(set.add("bbb"));
System.out.println(set.add("ccc"));
// 3. 打印集合
// 无序
System.out.println(set);// [aaa, ccc, bbb]
// 迭代器
// Iterator<String> it = set.iterator();
// while(it.hasNext()){
// String s = it.next();
// System.out.println(s);
// }
// 增强 for
// for(String s : set){
// System.out.println(s);
// }
// Lambda 遍历
set.forEach(s -> System.out.println(s));
}
}
HashSet
- HashSet 集合底层采取哈希表存储数据
- 哈希表是一种对于增删改查数据性能都较好的结构
哈希表的组成
- JDK8 之前:数组 + 链表
- JDK8 开始:数组 + 链表 + 红黑树
哈希值
哈希值:对象的整数表现形式
int index = (数组长度 - 1) & 哈希值;
- 根据 hashCode 方法算出来的 int 类型的整数
- 该方法定义在 Object 类中,所有对象都可以调用,默认使用地址值进行计算
- 一般情况下,会重写 hashCode 方法,利用对象内部的属性值计算哈希值
对象哈希值特点
- 如果没有重写 hashCode 方法,不同对象计算出的哈希值是不同的
- 如果已经重写 hashCode 方法,不同对象只要属性值相同,计算出的哈希值就是一样的
- 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞 / 哈希冲突)
package com.faxont.set;
public class HashSetDemo1 {
public static void main(String[] args) {
/*
哈希值:
对象哈希值特点
1. 如果没有重写 hashCode 方法,不同对象计算出的哈希值是不同的
2. 如果已经重写 hashCode 方法,不同对象只要属性值相同,计算出的哈希值就是一样的
3. 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞 / 哈希冲突)
*/
// 1. 创建对象
Student s1 = new Student("zhangsan" ,23);
Student s2 = new Student("zhangsan" ,23);
// 2. 如果没有重写 hashCode 方法,不同对象计算出的哈希值是不同的
System.out.println(s1.hashCode());// 990368553
System.out.println(s2.hashCode());// 1096979270
// 3. 如果已经重写 hashCode 方法,不同对象只要属性值相同,计算出的哈希值就是一样的
System.out.println(s1.equals(s2));// true
System.out.println(s1.hashCode() == s2.hashCode());// true
System.out.println(s1.hashCode());// -1461067292
System.out.println(s2.hashCode());// -1461067292
// 4. 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞 / 哈希冲突)
System.out.println("abc".hashCode());// 96354
System.out.println("acD".hashCode());// 96354
}
}
package com.faxont.set;
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
HashSet 底层原理
- 创建一个默认长度16,默认加载因子为0.75的数组,数组名 table
- 根据元素的哈希值跟数组的长度计算出应存入的位置
- 判断当前位置是否为 null,如果是 null 直接存入
- 如果位置不为 null,表示有元素,则调用 equals 方法比较属性值
- 一样:不存 不一样:存入数组,形成链表
JDK8 以前:新元素存入数组,老元素挂在新元素下面
JDK8 以后:新元素直接挂在老元素下面
- 当 HashSet 内的元素达到
长度 * 加载因子时,它的大小会扩容到原来的两倍 - JDK8 以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
- 如果集合中存储的是自定义对象,必须要重写hashCode和equals方法
HashSet 的三个问题
问题1:HashSet 为什么存和取的顺序不一样?
问题2:HashSet 为什么没有索引?
问题3:HashSet 是利用什么机制保证数据去重的?
利用 HashSet 集合去除重复元素
需求:创建一个存储学生对象的集合,存储多个学生对象。
使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认定是同一个对象
package com.faxont.set;
import java.util.HashSet;
public class HashSetDemo2 {
public static void main(String[] args) {
/*
需求:创建一个存储学生对象的集合,存储多个学生对象。
使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认定是同一个对象
*/
// 1. 创建学生对象
Student s1 = new Student("zhangsan" ,23);
Student s2 = new Student("lisi" ,24);
Student s3 = new Student("wangwu" ,25);
Student s4 = new Student("zhangsan" ,23);
// 2. 创建集合用来添加学生
HashSet<Student> hs = new HashSet<>();
// 3. 添加元素
System.out.println(hs.add(s1));
System.out.println(hs.add(s2));
System.out.println(hs.add(s3));
System.out.println(hs.add(s4));
// 4. 打印集合
System.out.println(hs);
}
}
LinkedHashSet
LinkedHashSet 底层原理
- 有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
package com.faxont.set;
import java.util.LinkedHashSet;
public class LinkedHashSetDemo {
public static void main(String[] args) {
// 1. 创建4个学生对象
Student s1 = new Student("zhangsan" ,23);
Student s2 = new Student("lisi" ,24);
Student s3 = new Student("wangwu" ,25);
Student s4 = new Student("zhangsan" ,23);
// 2. 创建集合的对象
LinkedHashSet<Student> lhs = new LinkedHashSet<>();
// 3. 添加元素
System.out.println(lhs.add(s1));
System.out.println(lhs.add(s2));
System.out.println(lhs.add(s3));
System.out.println(lhs.add(s4));
System.out.println(lhs);
}
}
TreeSet
- 不重复、无索引、可排序
- 可排序:按照元素的默认规则(从小到大)顺序。
- TreeSet 集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
TreeSet 对象排序练习题(1)
需求:存储整数并进行排序
package com.faxont.set;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetDemo1 {
public static void main(String[] args) {
/**
* 需求:利用 TreeSet 存储整数并进行排序
*/
// 1. 创建 TreeSet 集合对象
TreeSet<Integer> ts = new TreeSet<>();
// 2. 添加元素
ts.add(4);
ts.add(5);
ts.add(1);
ts.add(3);
ts.add(2);
// 3. 打印集合
System.out.println(ts);
// 4. 遍历集合
// 三种遍历方法都可以
// 迭代器
Iterator<Integer> it = ts.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
System.out.println("------------------");
// 增强 for 循环
for (Integer i : ts) {
System.out.println(i);
}
System.out.println("------------------");
// lambda 表达式
ts.forEach(i -> System.out.println(i));// lambda
System.out.println("------------------");
/**
* System.out::println 这种 类名::方法名 / 对象::方法名 写法,叫 方法引用(Method Reference)。
*
* 它是 Lambda 表达式的语法糖:
* 当 Lambda 体只调用一个现成方法
* 且参数顺序完全匹配
* 就可以简写成 方法引用
*/
ts.forEach(System.out::println);// 方法引用,简化的 lambda 表达式
}
}
TreeSet 集合默认的规则
- 对于数值类型:Integer。Double,默认按照从小到大的顺序进行排序。
- 对于字符、字符串类型:按照字符在 ASCII 码表中的数字升序进行排序。
TreeSet 对象排序练习题(2)
需求:创建 TreeSet 集合,并添加3个学生对象
学生对象属性:姓名,年龄
要求按照学生的年龄进行排序
同年龄按照姓名字母排序(暂不考虑中文)
同姓名,同年龄认为是同一个人
package com.faxont.set;
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
/*
需求:创建 TreeSet 集合,并添加3个学生对象
学生对象属性:姓名,年龄
要求按照学生的年龄进行排序
同年龄按照姓名字母排序(暂不考虑中文)
同姓名,同年龄认为是同一个人
*/
// 1. 创建学生对象
Student2 s1 = new Student2("zhangsan", 23);
Student2 s2 = new Student2("lisi", 24);
Student2 s3 = new Student2("wangwu", 25);
Student2 s4 = new Student2("zhangsan", 23);
// 2. 创建集合对象
TreeSet<Student2> ts = new TreeSet<>();
// 3. 添加元素
System.out.println(ts.add(s3));
System.out.println(ts.add(s1));
System.out.println(ts.add(s2));
System.out.println(ts.add(s4));
// 4. 打印集合
System.out.println(ts);
// hashCode 和 equals 方法:哈希表有关的
// TreeSet 底层是红黑树
}
}
package com.faxont.set;
public class Student2 implements Comparable<Student2>{
private String name;
private int age;
public Student2() {
}
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student2{name = " + name + ", age = " + age + "}";
}
/**
* this:表示当前要添加的元素
* o:表示已经在红黑树中存在的元素
*
* 返回值:
* 负数:this < o,认为要添加的元素是小的,存左边
* 正数:this > o,认为要添加的元素是较大的,存右边
* 0:this == o,认为要添加的元素跟已经存在的元素一样,不添加
* @param o
* @return
*/
@Override
public int compareTo(Student2 o) {
System.out.println("-----------");
System.out.println("this: " + this);
System.out.println("o: " + o);
// 指定排序的规则
// 只看年龄,按照年龄的升序进行排序
return this.getAge() - o.getAge();
}
}
TreeSet 的两种比较方式
方式一:默认排序 / 自然排序:Javabean 类实现 Comparable 接口指定比较规则
详情可看练习(2)
方式二:比较器排序:创建 TreeSet 对象时,传递比较器 Comparator 指定规则
详情可看练习(3)
使用原则:默认使用第一种,如果第一种不能满足当前需求,就使用第二种
当方式一二同时存在时,以方式二为准
TreeSet 对象排序练习题(3)
需求:请自行选择比较强排序和自然排序两种方式;
要求:存入四个字符串,"c","ab","df","qwer"
按照长度排序,如果一样长则按照首字母排序
package com.faxont.set;
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetDemo3 {
public static void main(String[] args) {
/*
需求:请自行选择比较强排序和自然排序两种方式;
要求:存入四个字符串,"c","ab","df","qwer"
按照长度排序,如果一样长则按照首字母排序
*/
// 1. 创建集合
/**
* o1:表示要添加的元素
* o2:表示已经在红黑树的元素
* 返回值规则跟之前一样
*/
TreeSet<String> ts = new TreeSet<>((o1, o2) -> {
// 按照长度排序
int i = o1.length() - o2.length();
// 如果长度一样,按照首字母排序
i = i == 0 ? o1.compareTo(o2) : i;
return i;
});
// 2. 添加元素
ts.add("c");
ts.add("ab");
ts.add("df");
ts.add("qwer");
// 3. 打印集合
System.out.println(ts);
}
}
TreeSet 对象排序练习题(4)
需求:创建5个学生对象
属性:(姓名,年龄,语文成绩,数学成绩,英语成绩)。
按照总分从高到低输出到控制台
如果总分一样,按照语文成绩排
如果语文一样,按照数学成绩排
如果数学一样,按照英语成绩排
如果英语一样,按照年龄排
如果年龄一样,按照姓名的字母顺序排
如果都一样,认为是同一个学生,不存
package com.faxont.set;
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetDemo4 {
public static void main(String[] args) {
/*
需求:创建5个学生对象
属性:(姓名,年龄,语文成绩,数学成绩,英语成绩)。
按照总分从高到低输出到控制台
如果总分一样,按照语文成绩排
如果语文一样,按照数学成绩排
如果数学一样,按照英语成绩排
如果英语一样,按照年龄排
如果年龄一样,按照姓名的字母顺序排
如果都一样,认为是同一个学生,不存
*/
// 1. 创建5个学生对象
Student3 s1 = new Student3("zhangsan", 23, 90, 99, 50);
Student3 s2 = new Student3("lisi", 24, 90, 98, 50);
Student3 s3 = new Student3("wangwu", 25, 95, 100, 30);
Student3 s4 = new Student3("zhaoliu", 26, 60, 99, 70);
Student3 s5 = new Student3("qianqi", 26, 70, 80, 70);
// 2. 创建集合对象
/**
* 默认使用 ArrayList(最常见)
* 想要数据唯一,在 Set 里面选,默认选择 HashSet
* 既要唯一,又要排序,使用 TreeSet
*/
TreeSet<Student3> ts = new TreeSet<>();
// 3. 添加元素
System.out.println(ts.add(s1));
System.out.println(ts.add(s2));
System.out.println(ts.add(s3));
System.out.println(ts.add(s4));
System.out.println(ts.add(s5));
// 4. 遍历集合
for (Student3 t : ts) {
int sum = t.getChinese() + t.getMath() + t.getEnglish();
System.out.println("总分为: " + sum);
System.out.println(t);
}
}
}
package com.faxont.set;
public class Student3 implements Comparable<Student3>{
private String name;
private int age;
private int chinese;
private int math;
private int english;
public Student3() {
}
public Student3(String name, int age, int chinese, int math, int english) {
this.name = name;
this.age = age;
this.chinese = chinese;
this.math = math;
this.english = english;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return chinese
*/
public int getChinese() {
return chinese;
}
/**
* 设置
* @param chinese
*/
public void setChinese(int chinese) {
this.chinese = chinese;
}
/**
* 获取
* @return math
*/
public int getMath() {
return math;
}
/**
* 设置
* @param math
*/
public void setMath(int math) {
this.math = math;
}
/**
* 获取
* @return english
*/
public int getEnglish() {
return english;
}
/**
* 设置
* @param english
*/
public void setEnglish(int english) {
this.english = english;
}
public String toString() {
return "Student3{name = " + name + ", age = " + age + ", chinese = " + chinese + ", math = " + math + ", english = " + english + "}";
}
/*
按照总分从高到低输出到控制台
如果总分一样,按照语文成绩排
如果语文一样,按照数学成绩排
如果数学一样,按照英语成绩排
如果英语一样,按照年龄排
如果年龄一样,按照姓名的字母顺序排
如果都一样,认为是同一个学生,不存
*/
@Override
public int compareTo(Student3 o) {
int thisScore = this.getChinese() + this.getMath() + this.getEnglish();
int oScore = o.getChinese() + o.getMath() + o.getEnglish();
int i = thisScore > oScore ? -1 : 1;
i = i == 0 ? this.getChinese() - o.getChinese() : i;
i = i == 0 ? this.getMath() - o.getMath() : i;
i = i == 0 ? this.getEnglish() - o.getEnglish() : i;// 可以去掉,因为总分、语文、数学都一样,那么英语一定一样
i = i == 0 ? this.getAge() - o.getAge() : i;
i = i == 0 ? this.getName().compareTo(o.getName()) : i;
return i;
}
}
单列集合的应用场景
- 如果想要集合中的元素可重复
- 用 ArrayList 集合,基于数组的。(用的最多)
- 如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
- 用 LinkedList 集合,基于链表的。
- 如果想对集合中的元素去重
- 用 HashSet 集合,基于哈希表的。(用的最多)
- 如果想对集合中的元素去重,而且保证存取顺序
- 用 LinkedHashSet 集合,基于哈希表和双链表,效率低于 HashSet。
- 如果想对集合中的元素进行排序
- 用 TreeSet 集合,基于红黑树。后续也可以用 List 集合实现排序。
双列集合的特点
- 双列集合一次需要存一对数据,分别为键和值
- 键不能重复,值可以重复
- 键和值是一一对应的,每一个键只能找到自己对应的值
- 键 + 值 这个整体我们称之为 “键值对” 或者 “键值对对象”,在 Java 中叫做 “Entry对象”
Map
Map 的常见API
Map 是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
| 方法名称 | 说明 |
|---|---|
| V put(K key, V value) | 添加元素 |
| V remove(Object key) | 根据键删除键值对元素 |
| void clear() | 移除所有的键值对元素 |
| boolean containsKey(Object key) | 判断集合是否包含指定的键 |
| boolean containsValue(Object value) | 判断集合是否包含指定的值 |
| boolean isEmpty() | 判断集合是否为空 |
| int size() | 集合的长度,也就是集合中键值对的个数 |
package com.faxont.map;
import java.util.HashMap;
import java.util.Map;
public class MapDemo1 {
public static void main(String[] args) {
/*
V put(K key, V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
*/
// 1. 创建 Map 集合对象
Map<String, String> m = new HashMap<>();
// 2. 添加元素
// 有两个功能 添加 / 覆盖
// 在添加数据的时候,如果键不存在,那么直接把键值对对象添加到 map 集合当中,会返回 null
// 在添加数据的时候,如果键是存在的,那么会把原有的键值对对象覆盖,会把被覆盖的对象返回
String value1 = m.put("Kindred", "千珏");
// System.out.println(value1);// null
m.put("Faxont", "弋湖");
m.put("Scroll", "卷頔");
// String value2 = m.put("Kindred", "镜爪");// 直接覆盖了上面的键值对
// System.out.println(value2);// 千珏 返回了被覆盖的键值对的值
// 删除
// String result = m.remove("Kindred");
// System.out.println(result);// 千珏
// 清空
// m.clear();
// 判断是否包含
// boolean keyResult = m.containsKey("Kindred");
// System.out.println(keyResult);
// boolean valueResult = m.containsValue("千珏");
// System.out.println(valueResult);
// 判断是否为空
// System.out.println(m.isEmpty());
int size = m.size();
System.out.println(size);
// 3. 打印集合
System.out.println(m);
}
}
Map 的遍历方式
- 键找值
package com.faxont.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapDemo2 {
public static void main(String[] args) {
// 1. 创建 Map 对象
Map<String, String> m = new HashMap<>();
// 2.添加元素
m.put("Kindred", "千珏");
m.put("Faxont", "弋湖");
m.put("Scroll", "卷頔");
// 3. 通过键找值
// 3.1 获取所有的键,把这些键放到一个单列集合当中
Set<String> s = m.keySet();
// 3.2 遍历单列集合,得到每一个键
// 增强 for 遍历
// for (String key : s) {
// // 3.3 通过键获取对应的值
// String value = m.get(key);
// System.out.println(key + "=" + value);
// }
// 迭代器遍历
// Iterator it = s.iterator();
// while (it.hasNext()){
// String key = (String) it.next();
// String value = m.get(key);
// System.out.println(key + "=" + value);
// }
// Lambda 表达式遍历
s.forEach(key -> System.out.println(key + "=" + m.get(key)));
}
}
- 键值对
package com.faxont.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapDemo3 {
public static void main(String[] args) {
// 1. 创建 Map 集合对象
Map<String, String> m = new HashMap<>();
// 2. 添加元素
m.put("Kindred", "千珏");
m.put("Faxont", "弋湖");
m.put("Scroll", "卷頔");
// 3. 通过键值对对象进行遍历
// 3.1 通过一个方法获取所有的键值对对象,返回一个 Set 集合
Set<Map.Entry<String, String>> entries = m.entrySet();
// 3.2 遍历 entries 这个对象,得到每个键值对对象
// 增强 for 遍历
// for (Map.Entry<String, String> entry : entries) {
// // 3.3 利用 entry 调用 get 方法获取键值对对象的键和值
// String key = entry.getKey();
// String value = entry.getValue();
// System.out.println(key + "=" + value);
// }
// 迭代器遍历
// Iterator<Map.Entry<String, String>> it = entries.iterator();
// while (it.hasNext()){
// Map.Entry<String, String> entry = it.next();
// String key = entry.getKey();
// String value = entry.getValue();
// System.out.println(key + "=" + value);
// }
// Lambda 遍历
entries.forEach(entry -> System.out.println(entry.getKey() + "=" + entry.getValue()));
}
}
- Lambda 表达式
| 方法名称 | 说明 |
|---|---|
| default void forEach(BiConsumer<? super K, ? super V> action) | 结合 lambda 遍历 Map 集合 |
package com.faxont.map;
import java.util.HashMap;
import java.util.Map;
public class MapDemo4 {
public static void main(String[] args) {
// 1. 创建 Map 集合对象
Map<String, String> m = new HashMap<>();
// 2. 添加元素
m.put("Kindred", "千珏");
m.put("Faxont", "弋湖");
m.put("Scroll", "卷頔");
// 3. 利用 lambda 表达式进行遍历
// 底层:
// forEach 其实就是利用第二种方式进行遍历,依次得到每一个键和值
// 再调用 accept 方法
m.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
HashMap
HashMap 的特点
- HashMap 是 Map 里面的一个实现类。
- 没有额外需要学习的特有方法,直接使用 Map 里面的方法就可以了。
- 特点都是由键决定的:无序、不重复、无索引
- HashMap 跟 HashSet 底层原理是一模一样的,都是哈希表结构
总结
- HashMap 底层是哈希表结构的
- 依赖 hashCode 方法和 equals 方法保证键的唯一
- 如果键存储的是自定义对象,需要重写 hashCode 和 equals 方法
如果值存储自定义对象,不需要重写 hashCode 和 equals 方法
练习1:存储学生对象并遍历
需求
创建一个 HashMap 集合,键是学生对象(Student),值是籍贯(String)。
存储三个键值对元素,并遍历
要求:同姓名,同年龄认为是同一个学生
package com.faxont.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapDemo1 {
public static void main(String[] args) {
/*
需求
创建一个 HashMap 集合,键是学生对象(Student),值是籍贯(String)。
存储三个键值对元素,并遍历
要求:同姓名,同年龄认为是同一个学生
*/
// 1. 创建 HashMap 集合对象
HashMap<Student, String> hm = new HashMap<>();
// 2. 创建学生对象
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 24);
Student s3 = new Student("wangwu", 25);
Student s4 = new Student("zhangsan", 23);
// 3. 添加元素
hm.put(s1, "北京");
hm.put(s2, "上海");
hm.put(s3, "广州");
hm.put(s4, "深圳");
// 4. 遍历集合
Set<Student> students = hm.keySet();
for (Student key : students) {
String value = hm.get(key);
System.out.println(key + "=" + value);
}
System.out.println("-------------------");
Set<Map.Entry<Student, String>> entries = hm.entrySet();
for (Map.Entry<Student, String> entry : entries) {
Student key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("-------------------");
hm.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
package com.faxont.map;
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
练习2:统计投票人数
需求
某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A, B, C, D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人最多。
package com.faxont.map;
import java.util.*;
public class HashMapDemo2 {
public static void main(String[] args) {
/*
需求
某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A, B, C, D),
每个学生只能选择一个景点,请统计出最终哪个景点想去的人最多。
*/
// 1. 先获取学生的投票情况
// 定义一个数组,存储4个景点
String[] arr = {"A", "B", "C", "D"};
// 利用随机数模拟80个同学的投票,并把投票结果存储起来
ArrayList<String> list = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 80; i++) {
int index = r.nextInt(arr.length);
list.add(arr[index]);
}
// 2. 如果要统计的对象比较多,不方便使用计数器思想
// 我们可以定义 map 集合,利用集合进行统计
HashMap<String, Integer> hm = new HashMap<>();
for (String name : list) {
// 判断当前景点在 map 中是否存在
if(hm.containsKey(name)){
// 存在,则获取当前景点的票数
Integer count = hm.get(name);
// 票数 + 1
count++;
// 存储到 map 中
hm.put(name, count);
}else{
// 不存在,则添加到 map 中
hm.put(name, 1);
}
}
System.out.println(hm);
// 3. 求最大值
Set<Map.Entry<String, Integer>> entries = hm.entrySet();
int maxCount = 0;
for (Map.Entry<String, Integer> entry : entries) {
int count = entry.getValue();
if(count > maxCount){
maxCount = count;
}
}
// 4. 判断哪个景点的票数跟最大值一样,一样则打印出来
for (Map.Entry<String, Integer> entry : entries) {
if(entry.getValue() == maxCount){
System.out.println(entry.getKey() + "景点想去的人最多");
}
}
}
}
LinkedHashMap
- 由键决定:有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致。
- 原理:底层数据结构依然是哈希表,只是每个键值对元素又额外多了一个双链表机制记录存储的顺序。
package com.faxont.map;
import java.util.LinkedHashMap;
public class LinkedHashMapDemo1 {
public static void main(String[] args) {
/*
由键决定:有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致。
原理:底层数据结构依然是哈希表,只是每个键值对元素又额外多了一个双链表机制记录存储的顺序。
*/
// 1. 创建 LinkedHashMap 集合对象
LinkedHashMap<String, Integer> lhm = new LinkedHashMap<>();
// 2. 添加元素
lhm.put("a", 123);
lhm.put("b", 456);
lhm.put("a", 123);
lhm.put("a", 111);
lhm.put("c", 789);
// 3. 打印集合
System.out.println(lhm);
}
}
TreeMap
- TreeMap 跟 TreeSet 底层原理一样,都是红黑树结构
- 由键决定特性:不重复、无索引、可排序
- 可排序:对键进行排序
- 注意:默认按照键从小到大进行排序,也可以直接规定键的排序规则
代码书写两种排序规则
- 实现 Comparable 接口,指定比较规则
- 创建集合时传递 Comparator 比较器对象,指定比较规则
TreeMap 基本应用
需求1:
键:整数表示 id
值:字符串表示商品名称
要求:按照 id 的升序排列、按照 id 的降序排列
package com.faxont.map;
import java.util.Comparator;
import java.util.TreeMap;
public class TreeMapDemo1 {
public static void main(String[] args) {
/*
需求1:
键:整数表示 id
值:字符串表示商品名称
要求:按照 id 的升序排列、按照 id 的降序排列
*/
// 1. 创建 TreeMap
// Integer Double 默认情况下是按照升序排列的
// String 按照字符在 ASCII 码值进行升序排列
TreeMap<Integer, String> tm = new TreeMap<>(new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
// o1:当前要添加的元素
// o2:已经存在的元素
// o1 - o2:升序排列
// o2 - o1:降序排列
return o2 - o1;
}
});
// 2. 添加元素
tm.put(1, "巫妖");
tm.put(3, "军团");
tm.put(2, "护士");
tm.put(4, "幽灵");
tm.put(5, "怨灵");
// 3. 打印集合
System.out.println(tm);
}
}
需求2:
键:学生对象
值:籍贯
要求:按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名年龄视为同一个人
package com.faxont.map;
import java.util.TreeMap;
public class TreeMapDemo2 {
public static void main(String[] args) {
/*
需求2:
键:学生对象
值:籍贯
要求:按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名年龄视为同一个人
*/
// 1. 创建 TreeMap 集合对象
TreeMap<Student2, String> tm = new TreeMap<>();
// 2. 创建学生对象
Student2 s1 = new Student2("lisi", 23);
Student2 s2 = new Student2("zhangsan", 24);
Student2 s3 = new Student2("wangwu", 25);
Student2 s4 = new Student2("lisi", 23);
Student2 s5 = new Student2("qianqi",23);
// 3. 添加元素
tm.put(s1, "上海");
tm.put(s2, "北京");
tm.put(s3, "广州");
tm.put(s4, "深圳");
tm.put(s5, "杭州");
// 4. 遍历集合
tm.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
package com.faxont.map;
public class Student2 implements Comparable<Student2>{
private String name;
private int age;
public Student2() {
}
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student2{name = " + name + ", age = " + age + "}";
}
@Override
public int compareTo(Student2 o) {
int i = this.getAge() - o.getAge();
i = i == 0 ? this.getName().compareTo(o.getName()) : i;
return i;
}
}
练习:统计个数
需求:字符串 “aababcabcdabcde”
请统计字符串中每一个字符出现的次数,并按照以下格式输出
输出结果:
a (5) b (4) c (3) d (2) e (1)
package com.faxont.map;
import java.util.*;
public class TreeMapDemo3 {
public static void main(String[] args) {
/*
需求:字符串 “aababcabcdabcde”
请统计字符串中每一个字符出现的次数,并按照以下格式输出
输出结果:
a (5) b (4) c (3) d (2) e (1)
新的统计思想:利用 map 集合进行统计
如果题目中没有要求对结果进行排序,默认使用 HashMap
如果题目中要求对结果进行排序,则使用 TreeMap
键:表示要统计的内容
值:表示次数
*/
// 1. 创建字符串
String str = "aababcabcdabcde";
// 2. 创建集合
TreeMap<Character, Integer> tm = new TreeMap<>();
// 3. 遍历字符串获取字符
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
// 拿着字符去集合中判断字符是否存在
// 如果存在,则获取字符的次数,次数 + 1
// 如果不存在,则次数为 1
if(tm.containsKey(c)){
Integer count = tm.get(c);
count++;
tm.put(c, count);
}else{
tm.put(c, 1);
}
}
// 4. 遍历集合,按照要求输出结果
// for (Map.Entry<Character, Integer> entry : tm.entrySet()) {
// System.out.print(entry.getKey() + " (" + entry.getValue() + ") ");
// }
// 拼接字符串
// StringBuilder sb = new StringBuilder();
// tm.forEach((key, value) -> sb.append(key).append(" (").append(value).append(") "));
// System.out.println(sb);
StringJoiner sj = new StringJoiner("", "", "");
tm.forEach((key, value) -> sj.add(key + "").add(" (").add(value + "").add(") "));
System.out.println(sj);
}
}
可变参数的练习
假如需要定义一个方法求和,该方法可以灵活地完成如下需求:
计算2个数据的和
计算3个数据的和
计算4个数据的和
计算n个数据的和
package com.faxont.args;
public class ArgsDemo1 {
public static void main(String[] args) {
/*
假如需要定义一个方法求和,该方法可以灵活地完成如下需求:
计算2个数据的和
计算3个数据的和
计算4个数据的和
计算n个数据的和
*/
System.out.println(getSum(10, 20));
System.out.println(getSum(10, 20, 30));
System.out.println(getSum(10, 20, 30, 40));
}
// 可变参数的小细节:
// 1. 在方法的形参中最多只能写一个可变参数
// 2. 可变参数必须放在形参列表的最后
public static int getSum(int n, int ... args){
int sum = n;
for(int i = 0; i < args.length; i++){
sum += args[i];// 可变参数的本质是数组
}
return sum;
}
}
Collections
- java.util.Collections:是集合工具类
- 作用:Collections 不是集合,而是集合的工具类
Collections 常用的 API
| 方法名称 | 说明 |
|---|---|
| public static |
批量添加元素 |
| public static void shuffle(List<?> list) | 打乱 List 集合元素的顺序 |
| public static |
排序 |
| public static |
根据指定的规则进行排序 |
| public static |
以二分查找法查找元素 |
| public static |
拷贝集合中的元素 |
| public static |
使用指定的元素填充集合 |
| public static |
根据默认的自然排序获取最大 / 小值 |
| public static |
交换集合中指定位置的元素 |
package com.faxont.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class CollectionsDemo1 {
public static void main(String[] args) {
/*
public static <T> boolean addAll(Collection<T> c, T ... elements) 批量添加元素
public static void shuffle(List<?> list) 打乱 List 集合元素的顺序
public static <T> void sort(List<T> list) 排序
public static <T> void sort(List<T> list, Comparator<T> c 根据指定的规则进行排序
public static <T> int binarySearch(List<T> list, T key) 以二分查找法查找元素
public static <T> void copy(List<T> dest, List<T> src) 拷贝集合中的元素
public static <T> int fill(List<T> list, T obj) 使用指定的元素填充集合
public static <T> void max / min(Collection<T> coll) 根据默认的自然排序获取最大 / 小值
public static <T> void swap(List<?> list, int i, int j) 交换集合中指定位置的元素
*/
// addAll 批量添加元素
// 1. 创建集合对象
ArrayList<String> list = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
// 2. 批量添加元素
Collections.addAll(list, "Kindred", "Scroll", "Faxont", "Dead By Daylight", "Tomb Raider");
// 3. 打印集合
System.out.println(list);
System.out.println("-------------");
// 打乱 List 集合元素的顺序
Collections.shuffle(list);
System.out.println(list);
System.out.println("-------------");
// 排序
Collections.sort(list);
System.out.println(list);
System.out.println("-------------");
// 根据指定的规则进行排序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
System.out.println(list);
System.out.println("-------------");
// 以二分查找法查找元素
int index = Collections.binarySearch(list, "Tomb Raider");
System.out.println(index);
System.out.println(list.get(index));
System.out.println("-------------");
// 拷贝集合中的元素
// 先给 list2 填满占位,让长度足够
// 因为 copy 不会自动扩容
for (int i = 0; i < list.size(); i++) {
list2.add(null);
}
Collections.copy(list2, list);
System.out.println(list2);
System.out.println("-------------");
// 使用指定的元素填充集合
Collections.fill(list2, "Faxont");
System.out.println(list2);
System.out.println("-------------");
// 根据默认的自然排序获取最大 / 小值
System.out.println(Collections.max(list));
System.out.println(Collections.min(list));
System.out.println("-------------");
// 交换集合中指定位置的元素
Collections.swap(list, 0, 2);
System.out.println(list);
}
}
综合练习
练习1:自动点名1
班级里有 N 个学生,实现随机点名器。
package com.faxont.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class Test1 {
public static void main(String[] args) {
/*
班级里有 N 个学生,学生属性:姓名、年龄、性别,实现随机点名器
*/
// 1. 定义集合
ArrayList<String> list = new ArrayList<>();
// 2. 添加元素
Collections.addAll(list, "范闲", "范建", "杜子腾", "杜琦燕", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛");
// 3. 随机点名
// 创建随机数对象
// Random r = new Random();
// int index = r.nextInt(list.size());
// System.out.println(list.get(index));
// 打乱集合,获取 0 索引处的元素
Collections.shuffle(list);
System.out.println(list.get(0));
}
}
练习2:自动点名器2
班级里有 N 个学生
要求:
70%的概率随机到男生
30%的概率随机到女生
package com.faxont.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class Test2 {
public static void main(String[] args) {
/*
班级里有 N 个学生
要求:
70%的概率随机到男生
30%的概率随机到女生
"范闲", "范建", "杜子腾", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰",
"杜琦燕", "袁明媛", "李猜", "田蜜蜜"
*/
// 1. 定义集合
ArrayList<Integer> list = new ArrayList<>();
// 2. 添加元素
Collections.addAll(list, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0);
// 3. 随机点名,点到1选择男生,点到0选择女生
Random r = new Random();
int number = r.nextInt(list.size());
// 4. 创建两个集合分别存储男女生的数据
ArrayList<String> boyList = new ArrayList<>();
ArrayList<String> girlList = new ArrayList<>();
// 5. 添加数据
Collections.addAll(boyList, "范闲", "范建", "杜子腾", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰");
Collections.addAll(girlList, "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛", "李猜", "田蜜蜜");
if (number == 1) {
System.out.println("男生");
System.out.println(boyList.get(r.nextInt(boyList.size())));
} else {
System.out.println("女生");
System.out.println(girlList.get(r.nextInt(girlList.size())));
}
}
}
练习3:自动点名器3
班级里有N个学生
要求:
被点到的学生不会再被点到。
但是如果班级中所有的学生都点完了,需要重新开启第二轮点名。
package com.faxont.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class Test3 {
public static void main(String[] args) {
/*
班级里有N个学生
要求:
被点到的学生不会再被点到。
但是如果班级中所有的学生都点完了,需要重新开启第二轮点名。
*/
// 1. 定义集合
ArrayList<String> list = new ArrayList<>();
// 2. 添加元素
Collections.addAll(list, "范闲", "范建", "杜子腾", "杜琦燕", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛");
// 3. 随机点名
// 创建随机数对象
Random r = new Random();
rollCall(list, r, 2);
}
private static void rollCall(ArrayList<String> list, Random r, int count) {
for (int i = 0; i < count; i++) {
ArrayList<String> l = new ArrayList<>();
l.addAll(list);
while(!list.isEmpty()){
System.out.println(list.remove(r.nextInt(list.size())));
}
System.out.println(
i == count - 1
? "第" + (i + 1) + "轮点名已结束,所有点名已完成!"
: "第" + (i + 1) + "轮点名已结束,接下来进行第" + (i + 2) + "轮点名..."
);
list.addAll(l);
l.clear();
}
}
}
练习4:自动点名器4
TxT 文件中事先准备好80个学生姓名,每个学生的名字独占一行。
要求1:每次被点到的学生,再次被点到的概率在原先的基础上降低一半。
举例:80个学生,点名5次,每次都点到小A,概率变化情况如下:
第一次每人概率:1.25%
第二次小A概率:0.625% 其它学生概率:1.2579%
第三次小A概率:0.3125% 其它学生概率:1.261867%
第四次小A概率:0.15625% 其它学生概率:1.2638449%
第五次小A概率:0.078125% 其它学生概率:1.26483386%
要求2:作弊要求,第三次点名一定是张三
提示:本题需要用到集合,IO,权重随机算法
练习5:自动点名器5
练习6:Map 集合案例-省和市
需求
定义一个 Map 集合,键用于表示省份名称 province,值表示市 city,但是市会有多个。
添加完毕后,遍历结果格式如下:
江苏省 = 南京市, 扬州市, 苏州市, 无锡市, 常州市
湖北省 = 武汉市, 孝感市, 十堰市, 宜昌市, 鄂州市
河北省 = 石家庄市, 唐山市, 邢台市, 保定市, 张家口市
package com.faxont.test;
import java.util.*;
public class Test4 {
public static void main(String[] args) {
/*
定义一个 Map 集合,键用于表示省份名称 province,值表示市 city,但是市会有多个。
添加完毕后,遍历结果格式如下:
江苏省 = 南京市, 扬州市, 苏州市, 无锡市, 常州市
湖北省 = 武汉市, 孝感市, 十堰市, 宜昌市, 鄂州市
河北省 = 石家庄市, 唐山市, 邢台市, 保定市, 张家口市
*/
// 1. 创建 Map 集合
HashMap<String, ArrayList<String>> hm = new HashMap<>();
// 2. 创建单列集合存储城市
ArrayList<String> jiangsu = new ArrayList<>();
ArrayList<String> hubei = new ArrayList<>();
ArrayList<String> hebei = new ArrayList<>();
Collections.addAll(jiangsu, "南京市", "扬州市", "苏州市", "无锡市", "常州市");
Collections.addAll(hubei, "武汉市", "孝感市", "十堰市", "宜昌市", "鄂州市");
Collections.addAll(hebei, "石家庄市", "唐山市", "邢台市", "保定市", "张家口市");
// 3. 添加元素
hm.put("江苏省", jiangsu);
hm.put("湖北省", hubei);
hm.put("河北省", hebei);
// 4. 遍历得到结果
Set<Map.Entry<String, ArrayList<String>>> entrySet = hm.entrySet();
for (Map.Entry<String, ArrayList<String>> entry : entrySet) {
String province = entry.getKey();
ArrayList<String> city = entry.getValue();
StringJoiner sj = new StringJoiner(", ");
city.forEach(sj::add);
System.out.println(province + " = " + sj);
}
}
}
斗地主小游戏
package com.faxont.pokerGame;
public class App {
public static void main(String[] args) {
/*
完成控制台的三步:
准备牌
洗牌
发牌
"♦", "♣", "❤", "♠"
"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
*/
new PokerGame();
}
}
package com.faxont.pokerGame;
import java.util.ArrayList;
import java.util.Collections;
public class PokerGame {
// 牌盒
static ArrayList<String> list = new ArrayList<>();
// 准备牌
// 静态代码块
// 特点:随着类的加载而加载,而且只执行一次
static {
// 准备牌
// "♦", "♣", "❤", "♠"
// "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
String[] colors = {"♦", "♣", "❤", "♠"};
String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
for (String color : colors) {
for (String number : numbers) {
list.add(color + number);
}
}
list.add("B_Joker");
list.add("R_Joker");
}
public PokerGame() {
// 洗牌
Collections.shuffle(list);
// 发牌
ArrayList<String> lord = new ArrayList<>();
ArrayList<String> player1 = new ArrayList<>();
ArrayList<String> player2 = new ArrayList<>();
ArrayList<String> player3 = new ArrayList<>();
// 遍历牌盒得到每一张牌
for (int i = 0; i < list.size(); i++) {
String poker = list.get(i);
if(i <= 2){
lord.add(poker);
continue;
}
// 给三个玩家轮流发牌
// i % 3 == 0 player1
// i % 3 == 1 player2
// i % 3 == 2 player3
if(i % 3 == 0){
player1.add(poker);
}else if(i % 3 == 1){
player2.add(poker);
}else{
player3.add(poker);
}
}
// 看牌
lookPoker("底牌", lord);
lookPoker("玩家1", player1);
lookPoker("玩家2", player2);
lookPoker("玩家3", player3);
}
/**
*
* @param name 玩家名称
* @param list 玩家牌组
*/
public void lookPoker(String name, ArrayList<String> list){
System.out.print(name + ": ");
for (String poker : list) {
System.out.print(poker + " ");
}
System.out.println();
}
}
方式一:利用序号进行排序
- 如果原始数据的规律非常复杂,我们可以先手动排序让每一个数据跟唯一的序号产生对应关系。
- 序号就是数字,规律非常简单,后续的所有操作,我们以序号为准。
- 当真正需要操作原始数据的时候,再通过序号找到原始数据即可。
package com.faxont.pokerGame2;
public class App {
public static void main(String[] args) {
/*
完成控制台的三步:
准备牌
洗牌
发牌
"♦", "♣", "❤", "♠"
"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
*/
new PokerGame();
}
}
package com.faxont.pokerGame2;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;
public class PokerGame {
// 牌盒 Map
// 此时我们只要把牌跟序号产生对应关系就可以了,不需要按照序号进行排序,所以只要 HashMap 就可以了
static HashMap<Integer, String> hm = new HashMap<>();
static ArrayList<Integer> list = new ArrayList<>();
static{
String[] colors = {"♦", "♣", "♥", "♠"};
String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
// 序号
int serialNumber = 1;
// 细节
for(String number : numbers){
// 依次表示每一个数字
for(String color : colors){
// 依次表示每一个花色
hm.put(serialNumber, color + number);
list.add(serialNumber);
serialNumber++;
}
}
hm.put(serialNumber, "B_Joker");
list.add(serialNumber);
hm.put(serialNumber + 1, "R_Joker");
list.add(serialNumber + 1);
}
public PokerGame(){
// 洗牌
Collections.shuffle(list);
// 发牌
TreeSet<Integer> lord = new TreeSet<>();
TreeSet<Integer> player1 = new TreeSet<>();
TreeSet<Integer> player2 = new TreeSet<>();
TreeSet<Integer> player3 = new TreeSet<>();
for (int i = 0; i < list.size(); i++) {
// i:依次表示集合中的每一个索引
int serialNumber = list.get(i);
if(i <= 2){
lord.add(serialNumber);
continue;
}
if(i % 3 == 0){
player1.add(serialNumber);
}else if(i % 3 == 1){
player2.add(serialNumber);
}else{
player3.add(serialNumber);
}
}
// 看牌
lookPoker("底牌", lord);
lookPoker("玩家1", player1);
lookPoker("玩家2", player2);
lookPoker("玩家3", player3);
}
/**
*
* @param name 玩家名称
* @param list 玩家牌组
*/
public void lookPoker(String name, TreeSet<Integer> list){
System.out.print(name + ": ");
for (int serialNumber : list) {
System.out.print(hm.get(serialNumber) + " ");
}
System.out.println();
}
}
方式二:给每一张牌计算价值
package com.faxont.pokerGame3;
public class App {
public static void main(String[] args) {
/*
完成控制台的三步:
准备牌
洗牌
发牌
"♦", "♣", "❤", "♠"
"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
*/
new PokerGame();
}
}
package com.faxont.pokerGame3;
import java.util.*;
public class PokerGame {
// 牌盒
static HashMap<String, Integer> hm = new HashMap<>();
static ArrayList<String> list = new ArrayList<>();
// 准备牌
// 静态代码块
// 特点:随着类的加载而加载,而且只执行一次
static {
// 准备牌
// "♦", "♣", "❤", "♠"
// "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
String[] colors = {"♦", "♣", "❤", "♠"};
String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
for (String color : colors) {
for (String number : numbers) {
list.add(color + number);
}
}
list.add("B_Joker");
list.add("R_Joker");
// 指定牌的价值
// 牌上的数字到 Map 集合中判断是否存在
// 存在,获取价值
// 不存在,本身的数字就是价值
hm.put("J", 11);
hm.put("Q", 12);
hm.put("K", 13);
hm.put("A", 14);
hm.put("2", 15);
hm.put("B_Joker", 50);
hm.put("R_Joker", 100);
}
public PokerGame() {
// 洗牌
Collections.shuffle(list);
// 发牌
ArrayList<String> lord = new ArrayList<>();
ArrayList<String> player1 = new ArrayList<>();
ArrayList<String> player2 = new ArrayList<>();
ArrayList<String> player3 = new ArrayList<>();
// 遍历牌盒得到每一张牌
for (int i = 0; i < list.size(); i++) {
String poker = list.get(i);
if(i <= 2){
lord.add(poker);
continue;
}
// 给三个玩家轮流发牌
// i % 3 == 0 player1
// i % 3 == 1 player2
// i % 3 == 2 player3
if(i % 3 == 0){
player1.add(poker);
}else if(i % 3 == 1){
player2.add(poker);
}else{
player3.add(poker);
}
}
// 排序
order(lord);
order(player1);
order(player3);
order(player3);
// 看牌
lookPoker("底牌", lord);
lookPoker("玩家1", player1);
lookPoker("玩家2", player2);
lookPoker("玩家3", player3);
}
// 利用牌的价值进行排序
// 参数:集合
public void order(ArrayList<String> list){
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// 1. 计算 o1 的花色和价值
String color1 = o1.substring(0, 1);
int value1 = getValue(o1);
// 2. 获取 o2 的花色和价值
String color2 = o2.substring(0, 1);
int value2 = getValue(o2);
// 3. 比较 o1 和 o2 的价值
int i = value1 - value2;
return i == 0 ? color1.compareTo(color2) : i;
}
});
}
// 计算牌的价值
// 参数:牌
// 返回值:价值
public int getValue(String poker){
// 先判断是不是大小王
if(poker.equals("B_Joker") || poker.equals("R_Joker")){
return hm.get(poker);
}
// 再截取
// 获取牌上的数字
String number = poker.substring(1);
// 拿着数字到 map 集合中判断是否存在
// 存在,获取价值
// 不存在,类型转换
if(hm.containsKey(number)){
return hm.get(number);
}else{
return Integer.parseInt(number);
}
}
/**
*
* @param name 玩家名称
* @param list 玩家牌组
*/
public void lookPoker(String name, ArrayList<String> list){
System.out.print(name + ": ");
for (String poker : list) {
System.out.print(poker + " ");
}
System.out.println();
}
}
不可变集合
不可变集合:不可以被修改的集合
创建不可变集合的应用场景
- 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
- 当集合对象被不可信的库调用时,不可变形式是安全的。
创建不可变集合的书写格式
在 List、Set、Map 接口中,都存在静态的 of 方法,可以获取一个不可变的集合。
| 方法名称 | 说明 |
|---|---|
| static |
创建一个具有指定元素的 List 集合对象 |
| static |
创建一个具有指定元素的 Set 集合对象 |
| static <K, V> Map<K, V> of(E...elements) | 创建一个具有指定元素的 Map 集合对象 |
注意:这个集合不能添加,不能删除,不能修改。
package com.faxont.immutable;
import java.util.List;
public class ImmutableDemo1 {
public static void main(String[] args) {
/*
创建不可变的 List 集合
"张三", "李四", "王五", "赵六"
*/
// 一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
List<String> list = List.of("张三", "李四", "王五", "赵六");
list.forEach(System.out::println);
}
}
package com.faxont.immutable;
import java.util.List;
import java.util.Set;
public class ImmutableDemo2 {
public static void main(String[] args) {
/*
创建不可变的 Set 集合
"张三", "李四", "王五", "赵六"
细节:
当我们要获取一个不可变的 Set 集合时,里面的参数一定要保证唯一性
*/
// 一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Set<String> set = Set.of("张三", "李四", "王五", "赵六");
// Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");
set.forEach(System.out::println);
}
}
package com.faxont.immutable;
import java.util.Map;
import java.util.Set;
public class ImmutableDemo3 {
public static void main(String[] args) {
/*
创建不可变的 Map 集合
"张三", "南京", "李四", "北京", "王五", "上海", "赵六",
"广州", "孙七", "深圳", "周八", "杭州", "吴九", "宁波",
"郑十", "苏州", "刘一", "无锡", "陈二", "嘉兴"
细节1:
键是不能重复的
细节2:
Map 里面的 of 方法,参数是有上限的,最多只能传递20个参数,10个键值对
细节3:
如果我们要传递多个键值对对象,数量大于10个,在 Map 接口中还有一个方法:ofEntries
*/
// 一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Map<String, String> map = Map.of("张三", "南京", "李四", "北京", "王五", "上海", "赵六",
"广州", "孙七", "深圳", "周八", "杭州", "吴九", "宁波",
"郑十", "苏州", "刘一", "无锡", "陈二", "嘉兴");
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key + "=" + map.get(key));
}
System.out.println("-----------------------------");
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
package com.faxont.immutable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ImmutableDemo4 {
public static void main(String[] args) {
/*
如果我们要传递多个键值对对象,数量大于10个,在 Map 接口中还有一个方法:ofEntries
*/
// 1. 创建一个普通的 Map 集合
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "南京");
hm.put("李四", "北京");
hm.put("王五", "上海");
hm.put("赵六", "广州");
hm.put("孙七", "深圳");
hm.put("周八", "杭州");
hm.put("吴九", "宁波");
hm.put("郑十", "苏州");
hm.put("刘一", "无锡");
hm.put("陈二", "嘉兴");
hm.put("aaa","111");
// 2. 利用上面的数据来获取一个不可变的集合
// // 获取到所有的键值对对象(Entry 对象)
// Set<Map.Entry<String, String>> entries = hm.entrySet();
// // 把 entries 变成一个数组
// // toArray 方法在底层会比较集合的长度跟数组的长度两者大小
// // 如果集合的长度 > 数组的长度,数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
// // 如果集合的长度 <= 数组的长度,数据在数组中放得下,此时不会创建新的数组,而是直接用,多余部分用 null 补全
// Map.Entry<String, String>[] entryArray = entries.toArray(new Map.Entry[0]);
//
// // 不可变的 map 集合
// Map<String, String> map = Map.ofEntries(entryArray);
// System.out.println(map);
// Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
Map<String, String> map = Map.copyOf(hm);// JDK 10 开始
}
}

浙公网安备 33010602011771号