单例集合

List系列集合:添加的元素是有序、可重复、有索引
Set系列集合:添加的元素是无序、不重复、无索引
Collection
Collection是所有单例集合的祖宗接口,它的功能是所有单例集合都可以继承使用的
| 方法名称 | 说明 |
| public boolean add(E e) | 把给定的对象添加到当前集合中 |
| public void clear() | 清空集合中所有的元素 |
| public boolean remove(E e) | 把给定的对象在当前集合删除 |
| public boolean contains(Object obj) | 判断当前集合中是否包含obj |
| public boolean isEmpty() | 判断当前对象是否为空 |
| public int size() | 返回集合中元素的个数/集合长度 |
注意:
·其中的contains方法底层是通过调用equals方法来实现的,所以如果要使用自定义类的集合的contains方法,需要重写equals方法。
·重写equals方法一定要重写hashcode方法,即使是ArrayList中的contains方法并不依赖hashcode方法,虽然在ArrayList中不重写返回的结果也是正确的,但是因为 Java 是面向接口编程的,对象未来可能被传入任何集合中。当有一天需要把ArrayList改为HashSet,那就GG了。
遍历集合
迭代器遍历
//迭代器遍历
public class MyCollectionDemo1 {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("a");
collection.add("b");
collection.add("c");
collection.add("d");
//创建迭代器对象
Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
迭代器遍历不依赖集合的索引
1.通过collection.iterator()方法获取迭代器对象,迭代器指针初始指向索引0
2.通过hasnext()方法判断当前指针所指有没有元素
3.通过next()方法获取当前元素,并且指针向后移动一位
注意:
1.如果hasnext()方法已经返回false即指针所指已经没有元素,再次调用next()方法就会报错NoSuchElementException
2.迭代器遍历完毕后,指针不会复位
3.循环中只能调用一次next()方法
4.迭代器遍历时,不能用集合的方法进行增加或者删除,可以使用iterator.remove()方法删除元素
增强for遍历
//增强for遍历
public class MyCollectionDemo2 {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("a");
collection.add("b");
collection.add("c");
collection.add("d");
for (String s : collection) {
System.out.println(s);
}
}
}
注意:
遍历过程中无法对元素进行修改,无法中断(return、break)
lambda表达式遍历
//lambda表达式遍历
public class MyCollectionDemo3 {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("a");
collection.add("b");
collection.add("c");
collection.add("d");
//匿名内部类遍历
collection.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//使用lambda表达式遍历
collection.forEach(s -> System.out.println(s));
}
}
注意:
遍历过程中无法对元素进行修改,无法中断(return、break)
List集合
List集合的特有方法
·Collection的方法List都继承了
·List集合因为有索引,所以多了很多索引操作的方法
| 方法名称 | 说明 |
| void add(int index,E e) | 在此集合中的指定位置插入指定的元素 |
| E remove(index) | 删除指定索引处的元素,并返回删除的元素 |
| E set(int index,E e) | 修改指定索引处的元素,并返回被修改的元素 |
| E get(int index) | 返回指定索引处的元素 |
注意:
由于List中对方法remove()进行了重载,所以在使用的时候有一个小细节:当出现方法重载时,调用的时候优先调用形参与实参相同的方法,比如
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.remove(1);
这种情况下删除的是索引为1的对象,而不是直接删除对象1,如果要删除对象1的话,需要对1进行手动装箱为Integer(Integer i = Integer.valueOf(1);list.remove(i);)
List集合的五种遍历方式
- 迭代器遍历:在遍历的过程中需要删除元素
- 列表迭代器:在遍历的过程中需要添加元素
- 增强for遍历:仅仅想遍历
- Lambda表达式:同增强for
- 普通for:遍历的时候想操作索引
列表迭代器:
//列表迭代器遍历list集合
public class MyCollectionDemo4 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String str = it.next();
if ("b".equals(str)) {
it.add("new");
}
System.out.println(str);
}
System.out.println(list);
}
}
注意:
列表迭代器对比迭代器多了几个方法,最常用的是add方法
在调用add方法后,指针会向后移动一位
ArrayList集合
ArrayList集合底层原理
- 利用空参创建的集合,在底层创建一个默认长度为0的数组elementData
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容到max(oldSize*1.5,oldSize+addSize)大小
LinkedList集合
底层数据结构是双链表,查询慢,首尾操作的速度极快,所以多了很多首尾操作的特有API
这些首尾操作的方法都可以用Collection接口和List接口定义的方法实现,所以不做过多阐述
泛型
泛型的细节
- 泛型中不能写基本数据类型(伪泛型:在创建对象后,会自动将其他类型转换为Object类型,基本数据类型无法转换为Object)
- 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
- 如果不写泛型,类型默认时Object
泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
格式:修饰符 class 类名<类型>{}
举例
public class ArrayList<E>{}
此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成:T、E、K、V等。
泛型方法
使用场景:方法中形参类型不确定时
- 方案一:使用类名后面定义的泛型(所有方法都能用)
- 方案二:在方法申明上定义自己的泛型(只有本方法)
格式:修饰符<类型>返回值类型 方法名(类型 变量名){}
举例:
public<T> void show(T t){}
此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成:T、E、K、V等。
泛型接口
重点:如何使用一个带泛型的接口
- 方式1:实现类给出具体类型
- 方式2:实现类延续泛型,创建对象时再确定
格式:修饰符 interface 接口名<类型> {}
举例:
public interface List<E>{}
泛型的继承和通配符
- 泛型不具备继承性,但是数据具备继承性
public class GenericsDemo1 {
public static void main(String[] args) {
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
ArrayList<Stu> list4 = new ArrayList<>();
method(list1);
method(list2);//编译错误
method(list3);//编译错误
method(list4);//编译错误
}
private static void method(ArrayList<Ye> list){}
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}
class Stu{}
- 泛型的通配符:?
- ? extend E
- ? super E
private static void method(ArrayList<? extends Ye> list){}
改变后允许Ye和其子类
Vector
- 在1.2以后已经被市场淘汰,所以不做深入的学习
集合相关数据结构
二叉树
二叉树的定义:每个节点最多只有两个子节点
二叉查找树:在创建树的时候,从根节点开始,比当前节点值大的往节点右边走,小的往左边走
遍历二叉树:
- 前序遍历:当前节点、左子节点、右子节点
- 中序遍历:左子节点、当前节点、右子节点
- 后序遍历:左子节点、右子节点、当前节点
- 层序遍历:一层一层遍历
二叉查找树的弊端:有可能所有节点都只有一个节点的情况,例如2、3、4、5、6这组数据构建二叉查找树
平衡二叉树:任意节点左右子树的高度差不超过1
平衡二叉树旋转机制
- 左旋
- 右旋
- 触发时机:当添加一个节点之后,该树不再是平衡二叉树
左旋:
- 确定支点:从添加的节点开始,不断的往父节点找不平衡的节点
- 以不平衡的节点为支点
- 将根节点的右侧往左拉(把支点左旋降级,变成左子节点)
- 原先的右子节点变成新的父节点,并把多余的左子节点出让,让给已经降级的根节点当右子节点(晋升原来的右子节点)

- 以这个二叉树为例,添加12节点后,破坏了平衡性,故从12节点开始找支点,可以看到7为第一个满足不平衡的节点,右子树高度为1,左子树高度为3,高度差为2,所以选取7为支点
- 将根节点的右侧往左拉,10作为新的根节点
- 将多余的9节点给已经降级的根节点7当右子节点

右旋
同左旋,只是方向不同,不做过多阐述
平衡二叉树需要旋转的四种情况:
- 右右:一次左旋
- 右左:先局部右旋,再整体左旋
- 左左:一次右旋
- 左右:先局部左旋,再整体右旋
红黑树
红黑规则
- 每一个节点是红色或者黑色
- 根节点必须是黑色
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红节点相连的情况)
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;

红黑树添加节点的规则
- 添加的节点默认是红色的(效率高)

Set集合
特性:
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
Set集合的实现类
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:可排序、不重复、无索引
Set接口中的方法基本上与Collection的API一致
HashSet
底层原理:
- HashSet底层采用哈希表存储数据
- 哈希表是一种对于增删改查数据性能都很好的结构
哈希表组成:
- JDK8之前:数组+链表
- JDK8之后:数组+链表+红黑树
哈希值:
- 根据hashCode方法算出来的int类型的整数
- 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
- 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
对象的哈希值特点:
- 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
- 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
- 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希冲突)
HashSet底层原理
- 创建一个默认长度16,默认加载因子为0.75的数组,数组名为table
- 根据元素的哈希值跟数组的长度计算出应存入的位置
- 判断当前位置是否为null,如果是null直接存入
- 如果不为null,表示有元素,调用equals方法比较属性值
- 一样:不存 不一样:存入数组,形成链表
注意:
- JDK8以前:新元素存入数组,老元素挂在新元素下面
- JDK8以后:新元素直接挂在老元素下面
- JDK8以后,当链表长度超过8,且数组长度大于等于64时,自动转换为红黑树
- 如果集合中存储的是自定义对象,必须要重写hashCode和equals方法
- 转换红黑树时,是只将链表长度超过8的链表转换为红黑树,然后将根节点存入数组,还是将数组和所有链表一起转换为红黑树?–只将链表长度超过 8 的那一个链表转换为红黑树,而不是将数组和所有链表一起转换
LinkedHashSet
底层原理:
- 有序、不重复、无索引
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序

TreeSet
TreeSet的特点
- 不重复、无索引、可排序
- 可排序:按照元素的默认规则(从小到大)排序
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好
TreeSet集合默认的规则
- 对于数值类型:Integer、Double。默认按照从小到大的顺序进行排序。
- 对于字符、字符串类型:按照字符在ASCII表中的数字升序进行排序
TreeSet的两种比较方式
- 方式一:
- 默认排序/自然排序:Javabean类实现Comparable接口指定比较规则
public class Student implements Comparable<Student>{
@Override
public int compareTo(Student o) {
return this.age - o.getAge();
}
底层红黑树根据compareTo返回的值进行节点插入,比如值为0,则不插入、值为复数则往根节点的左边走
- 方式二:
- 比较器排序:创建集合时,自定义Comparator比较器对象,指定比较规则
public class TreeSetDemo2 {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("cb");
treeSet.add("abd");
treeSet.add("dasg");
treeSet.add("z");
System.out.println(treeSet);
System.out.println("--------------------------------");
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int n = o1.length() - o2.length();
return n == 0 ? o1.compareTo(o2) : n;
}
});
ts.add("cb");
ts.add("abd");
ts.add("dasg");
ts.add("z");
System.out.println(ts);
System.out.println("--------------------------------");
}
}
应用场景
- 如果想要集合中的元素可重复
- 用ArrayList集合,基于数组的。(用的最多)
- 如果想要集合中的元素可重复,并且当前的增删操作明显多于查询
- 用LinkedList集合,基于链表的。
- 如果想对集合中的元素去重
- 用HashSet集合,基于哈希表的。(用的最多)
- 如果想对集合中的元素去重,并且保证存取顺序
- 用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
- 如果想对集合中的元素去重且排序
- 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
Collections
Collections 是 Java 标准库中的一个工具类,它位于 java.util 包下。
它是一个完全由静态方法组成的工具类,专门用于操作或返回集合(如 List、Set 等),提供了排序、搜索、同步等多种实用功能。
常用API:
| 方法名称 | 说明 |
| 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) | 交换集合中指定位置的元素 |
双列集合

双列集合的特点
- 双列集合一次需要存一对数据,分别为键和值
- 键不能重复,值可以重复
- 键和值是一一对应的,每一个键只能找到自己对应的值
- 键+值这个整体我们称之为“键值对”或者“键值对对象”,在Java中叫做“Entry”对象
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() | 集合的长度,也就是集合中键值对的个数 |
Map的遍历方式
- 键找值
- 键值对
- Lambda表达式
键找值
//根据key来get value进行遍历
//增强for
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key + " = " + map.get(key));
}
通过调用map.keySet()方法,将键
键值对
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + " = " + value);
}
Lambda表达式
//lambda表达式遍历
map.forEach((key, value) -> System.out.println(key + " = " + value));
forEach其实就是利用第二种方式进行遍历,以此得到每一个键和值
HashMap
- HashMap是Map里面的一个实现类
- 没有额外需要学习的特有方法,直接使用Map里面的方法就可以
- 特点都是由键决定的:无序、不重复、无索引
- HashMap跟HashSet底层原理是一模一样的,都是哈希表结构
底层原理
- HashMap底层是哈希表结构的
- 依赖hashCode和equals方法保证键的唯一
- 如果键存储的是自定义对象,则需要重写hashCode和equals方法,如果值存储的是自定义对象则不需要
练习
练习一:
public class HashMapdemo1 {
/*
* 需求:创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)
* 存储三个键值对元素,并遍历
* 要求:同姓名、年龄认为是同一个学生*/
public static void main(String[] args) {
HashMap<Student,String> hashMap = new HashMap<>();
Student s1 = new Student("xiaozhi",18);
Student s2 = new Student("xiaoxia",16);
Student s3 = new Student("xiaogang",21);
Student s4 = new Student("xiaozhi",18);
hashMap.put(s1, "北京");
hashMap.put(s2, "上海");
hashMap.put(s3, "深圳");
hashMap.put(s4, "重庆");
//根据键遍历
Set<Student> keys = hashMap.keySet();
for (Student key : keys) {
System.out.println(key + "=" + hashMap.get(key));
}
System.out.println("---------------------------------------------------");
//根据键值对遍历
Set<Map.Entry<Student, String>> entries = hashMap.entrySet();
for (Map.Entry<Student, String> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println("---------------------------------------------------");
//Lambda表达式遍历
hashMap.forEach((Student student, String s) -> System.out.println(student + "=" + s));
}
}
一定要重写Student类的hashCode和equals方法
@Override
public boolean equals(Object o) {
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);
}
练习二:
public class HashMapDemo2 {
/*
* Map集合案例-统计投票人数
* 需求
* 某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次时(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。*/
public static void main(String[] args) {
//获取数据(学生的投票)
String[] scenicSpots = {"A", "B", "C", "D"};
Random random = new Random();
HashMap<String, Integer> hashMap = new HashMap<>();
for (int i = 0; i < 80; i++) {
int index = random.nextInt(scenicSpots.length);
String key = scenicSpots[index];
if (hashMap.containsKey(key)){
int count = hashMap.get(key);
count++;
hashMap.put(key, count);
} else {
hashMap.put(key, 1);
}
}
hashMap.forEach((String s, Integer i) -> System.out.println(s + "=" + i));
//获取投票数最多的景点并且输出
int max = 0;
//根据键遍历
Set<String> set = hashMap.keySet();
for (String s : set) {
max = hashMap.get(s) > max ? hashMap.get(s) : max;
}
System.out.println(max);
//根据max来获取景点
for (String s : set) {
if(hashMap.get(s) == max){
System.out.println(s);
}
}
}
}
重点是该如何创建Map集合,也就是数据建模?
LinkedHashMap
- 由键决定:有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
TreeMap
原理
- TreeMap跟TreeSet底层原理一样,都是红黑树结构。
- 由键决定特性:不重复、无索引、可排序
- 可排序:对键进行排序
- 注意:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
代码书写两种排序规则
- 实现Comparable接口,指定比较规则
- 创建集合时传递Comparator比较器对象,指定比较规则
练习
练习一:
public class TreeMapDemo1 {
/*
* 需求:
* 键:整数表示id
* 值:字符串表示商品名称
* 要求:按照id的升序排序、按照id的降序排序*/
public static void main(String[] args) {
//升序排序
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(1, "蒙牛");
treeMap.put(3, "伊利");
treeMap.put(2, "特仑苏");
treeMap.put(5, "旺仔");
treeMap.put(4, "优酸乳");
System.out.println(treeMap);
//降序排序
TreeMap<Integer, String> tm = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
tm.put(1, "蒙牛");
tm.put(3, "伊利");
tm.put(2, "特仑苏");
tm.put(5, "旺仔");
tm.put(4, "优酸乳");
System.out.println(tm);
}
}
练习二:
public class TreeMapDemo2 {
/*
* 需求:
* 键:学生对象
* 值:籍贯
* 要求:按照学生年龄的升序排序,年龄一样按照姓名的字母排序,同姓名年龄视为同一个人*/
public static void main(String[] args) {
TreeMap<Student, String> treeMap = new TreeMap<>();
Student s1 = new Student("b", 18);
Student s2 = new Student("a", 22);
Student s3 = new Student("c", 17);
Student s4 = new Student("e", 19);
Student s5 = new Student("d", 19);
Student s6 = new Student("a", 22);
treeMap.put(s1, "北京");
treeMap.put(s2, "北京");
treeMap.put(s3, "北京");
treeMap.put(s4, "北京");
treeMap.put(s5, "北京");
treeMap.put(s6, "上海");
System.out.println(treeMap);
}
}
实现接口Comparable
@Override
public int compareTo(Student o) {
return (this.age - o.age) == 0 ? this.name.compareTo(o.name) : (this.age - o.age);
}
练习三:
public class TreeMapDemo3 {
/*
* TreeMap集合:基本应用
* 需求:字符串"aababcabcdabcde"
* 请统计字符串中每一个字符出现的次数,并按照一下格式输出
* 输出结果:
* a(5)b(4)c(3)d(2)e(1)*/
public static void main(String[] args) {
String str = "aababcabcdabcde";
//定义集合
TreeMap<Character, Integer> treeMap = new TreeMap<>();
for (int i = 0; i < str.length(); i++) {
char a = str.charAt(i);
if (treeMap.containsKey(a)) {
int count = treeMap.get(a);
count++;
treeMap.put(a, count);
} else {
treeMap.put(a, 1);
}
}
System.out.println(treeMap);
//字符串拼接(StringBuilder)
StringBuilder sb = new StringBuilder();
//遍历集合
treeMap.forEach((Character s, Integer i) -> sb.append(s).append("(").append(i).append(")"));
System.out.println(sb);
}
}
可变参数
- 可变参数本质上就是一个数组
- 作用:在形参中接收多个数据
- 格式:数据类型…参数名称
举例:int…a - 注意事项:
- 形参列表中可变参数只能有一个
- 可变参数必须放在形参列表的后面
不可变集合
不可变集合的特点:
- 定义完成后不可以修改,或者添加、删除
如何创建不可变集合:
- List、Set、Map接口中,都存在of方法可以创建不可变集合
| 方法名称 | 说明 |
| static<E> List<E> of(E…elements) | 创建一个具有指定元素的List集合对象 |
| static<E> Set<E> of(E…elements) | 创建一个具有指定元素的Set集合对象 |
| static <K,V> Map<K,V> of(E…elements) | 创建一个具有指定元素的Map集合对象 |
三种方式的细节:
- List:直接用
- Set:元素不能重复
- Map:元素不能重复、键值对数量最多是10个
- 当需要创建超过10个键值对的不可变 Map 时,应该使用
Map.ofEntries()方法。
public class ImmutableCollectionDemo1 {
public static void main(String[] args) {
//List
List<String> IList = List.of("xiaohuolong", "jienigui", "chaomeng","xiaohuolong");
Set<String> ISet = Set.of("xiaohuolong", "jienigui", "chaomeng");
Map<String, String> IMap = Map.of("xiaohuolong", "huo", "jienigui", "cao");
IList.add("miaowazhongzi");
System.out.println(IList);
System.out.println(ISet);
System.out.println(IMap);
//在Map存放超过10个的键值对
Map map = Map.ofEntries(Map.entry("a","A"),Map.entry("b", "B"));
}
}
注意:
- Map.entry()返回的是Map.Entry的一个匿名实现对象,Entry是Map的一个嵌套接口
- Map.ofEntries()返回的是一个Map集合
Stream流
获取Stream流的方法:
| 获取方式 | 方法名 | 说明 |
| 单列集合 | default Stream<E> stream() | Collection中的默认方法 |
| 双列集合 | 无 | 无法直接使用stream流 |
| 数组 | public static <T> Stream<T> stream(T[] array) | Arrays工具类中的静态方法 |
| 一堆零散数据 | public static <T> Stream<T> of(T…values) | Stream接口中的静态方法 |
注意:双列集合如果要使用Stream流,则先调用keySet或entrySet方法将集合中的键提取出来放到Set集合中,然后再用stream方法获取流
public class StreamDemo1 {
public static void main(String[] args) {
//获取Stream流对象
//单列集合,使用Collection默认方法stream
Collection<String> coll = new ArrayList<>();
Collections.addAll(coll, "a", "b", "c", "d");
Stream<String> stream = coll.stream();
//双列集合,
HashMap<String, String> map = new HashMap<>();
Set<String> set = map.keySet();
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<String> stream1 = set.stream();
Stream<Map.Entry<String, String>> stream2 = entries.stream();
//数组
String[] arr = {"aaa", "bbb", "ccc"};
Stream<String> stream3 = Arrays.stream(arr);
//零散数据
Stream<Integer> stream4 = Stream.of(1, 1, 1);
}
}
Stream流的中间方法:
| 名称 | 说明 |
| Stream<T> filter(Predicate<? super T> predicate) | 过滤 |
| Stream<T> limit(long maxSize) | 获取前几个元素 |
| Stream<T> skip(long n) | 跳过前几个元素 |
| Stream<T> distinct() | 元素去重,依赖(hashCode和equals方法) |
| static <T> Stream<T> concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
| Stream<R> map(Function<T,R> mapper) | 转换流中的数据类型 |
注意:
- Stream中的filter方法的参数Predicate是一个函数式接口
- filter的底层原理:当流里的所有元素经过filter后,test方法会返回true或false,当返回true时,保留元素,false则不保留
- distinct方法底层通过Set集合进行去重,所以依赖hashCode和equals方法
//Stream流的中间方法
public class StreamDemo2 {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
Collections.addAll(coll, "a", "b", "c", "d");
Stream<String> stream = coll.stream();
//数组
String[] arr = {"aaa", "bbb", "ccc"};
Stream<String> stream1 = Arrays.stream(arr);
//filter
stream.filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.equals("a");
}
}).forEach(s -> System.out.println(s));//a
//limit,skip
coll.stream().skip(1).limit(2).forEach(s -> System.out.println(s));//b c
//distinct
Stream.of("a","a","a","b","c").distinct().forEach(s -> System.out.println(s));//a b c
//concat
Stream<String> a = Stream.of("a", "a", "a", "b", "c");
Stream<String> b = Stream.of("a", "a", "a", "b", "c");
Stream.concat(a, b).forEach(s -> System.out.println(s));//a a a b c a a a b c
//map
Stream.of(1, 2, 3).map(new Function<Integer, String>() {
@Override
public String apply(Integer integer) {
return integer + "s";
}
}).forEach(s -> System.out.println(s));
}
}
Stream流的终结方法:
| 名称 | 说明 |
| void forEach(Consumer action) | 遍历 |
| long count() | 统计 |
| toArray() | 收集流中的数据据,放到数组中 |
| collect(Collector collector) | 收集流中的数据,放到集合中 |
//Stream流的终结方法
public class StreamDemo3 {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
Collections.addAll(coll, "a", "b", "c", "d");
Stream<String> stream = coll.stream();
String[] arr = {"aaa", "bbb", "ccc"};
Stream<String> stream1 = Arrays.stream(arr);
//forEach
stream.forEach(s -> System.out.println(s));//a b c d
//count
System.out.println(Stream.of("a", "b", "c", "d").count());//4
//toArray
String[] array = Stream.of("a", "b", "c", "d").toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
System.out.println(Arrays.toString(array));//[a, b ,c, d]
System.out.println("------------------------------------");
//collect
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
"张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");
//List
List<String> list1 = list.stream().filter(s -> "男".equals(s.split("-")[1])).collect(Collectors.toList());
System.out.println(list1);
System.out.println("------------------------------------");
//Set
Set<String> set = list.stream().filter(s -> "男".equals(s.split("-")[1])).collect(Collectors.toSet());
System.out.println(set);
System.out.println("------------------------------------");
//Map
//apply方法返回已经生成的键或者值
//toMap第一个参数是键的生成规则,第二个是值
//注意:键不能重复,不然会报错
Map<String, Integer> map = list.stream().filter(s -> "男".equals(s.split("-")[1])).collect(Collectors.toMap(new Function<String, String>() {
@Override
public String apply(String s) {
return s.split("-")[0];
}
}, new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s.split("-")[2]);
}
}));
System.out.println(map);
}
}
练习
练习一:
/*数据过滤
定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
过滤奇数,只留下偶数。
并将结果保存起来*/
public class StreamDemo5 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8,9,10);
Object[] array = list.stream().filter(num -> num % 2 == 0).toArray();
Arrays.stream(array).forEach(o -> System.out.println(o));
}
}
练习二:
/*数据操作
练习:
创建一个ArrayList集合,并添加以下字符串,字符串中前面是姓名,后面是年龄
"zhangsan, 23”
"lisi, 24”
“wangwu, 25”
保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值*/
public class StreamDemo6 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "zhangsan, 23", "lisi, 24", "wangwu, 25");
Map<String, String> map = list.stream().filter(s -> Integer.parseInt(s.split(", ")[1]) >= 24).collect(Collectors.toMap(k -> k.toString().split(", ")[0], v -> v.toString().split(", ")[1]));
System.out.println(map);
}
}
练习三:
/* 数据操作
现在有两个ArrayList集合,
第一个集合中:存储6名男演员的名字和年龄。第二个集合中:存储6名女演员的名字和年龄。
姓名和年龄中间用逗号隔开。比如:张三,23
要求完成如下的操作:
1,男演员只要名字为3个字的前两人
2,女演员只要姓杨的,并且不要第一个
3,把过滤后的男演员姓名和女演员姓名合并到一起
4,将上一步的演员信息封装成Actor对象
5,将所有的演员对象都保存到List集合中。
备注:演员类Actor,属性有:name, age
*/
public class StreamDemo7 {
public static void main(String[] args) {
ArrayList<String> actors = new ArrayList<>();//男演员集合
ArrayList<String> actresses = new ArrayList<>();
Collections.addAll(actors, "张强强,20", "王刚刚,25", "刘洋样,30", "李伟,22", "赵磊,28", "陈明,35");
Collections.addAll(actresses, "周芷若,14", "杨敏,13", "王语嫣,18", "小龙女,20", "杨蓉,19", "任盈盈,22");
Stream<String> streamActors = actors.stream().filter(s -> s.split(",")[0].length() == 3).limit(2);
Stream<String> streamActresses = actresses.stream().filter(s -> s.startsWith("杨")).skip(1);
Stream<Actor> stream = Stream.concat(streamActors, streamActresses).map(new Function<String, Actor>() {
@Override
public Actor apply(String s) {
String name = s.split(",")[0];
int age = Integer.parseInt(s.split(",")[1]);
return new Actor(name, age);
}
});
List<Actor> list = stream.collect(Collectors.toList());
System.out.println(list);
}
}
方法引用
什么是方法引用:
- 把已经存在的方法拿过来用,当做函数式接口中抽象方法的方法体
::是什么符号:
- 方法引用符
方法引用时需要注意什么:
- 需要有函数式接口
- 被引用方法必须已经存在
- 被引用方法的形参和返回值需要跟抽象方法保持一致
- 被引用方法的功能要满足当前的需求
引用静态方法
- 格式:类名::静态方法
- 范例:Integer::parseInt
引用成员方法
- 格式:对象::成员方法
- 其他类:其他类对象::方法名
- 本类:this::方法名
- 父类:super::方法名
注意:static方法中没有this、super
引用构造方法
- 格式:类名::new
- 范例:Student::new
使用类名引用成员方法
- 格式:类名::成员方法
- 范例:String::substring
注意:
- 不能引用所有类中的成员方法
- 如果抽象方法的第一个参数是A类型的只能引用A类中的方法(理解:只能引用Stream流中的数据类型的类中的方法)
引用数组的构造方法
- 格式:数据类型::new
- 范例:int[]::new
Commons-io


Hutool





