Collection、Map、Stream流
一、集合的概述和分类
主要分为两个系列:Collection和Map
- Collection代表单列集合,每个元素(数据)只包含一个值
- Map代表双列集合,每个元素包含两个值(键值对)
1.1 Collection
Collection的分类
Collection集合下包含两个系列的集合
-
List系列集合:添加的元素是有序、可重复、有索引。
- ArrayList:有序、可重复、有索引
- LinekdList :有序、可重复、有索引
-
Set系列集合:添加的元素是无序、不重复、无索引。
- HashSet: 无序、不重复、无索引;
- LinkedHashSet: 有序、不重复、无索引。
- TreeSet:按照大小默认升序排序、不重复、无索引。
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() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
集合的遍历方法
-
迭代器
Iterator<String> it = lists.iterator(); while(it.hasNext()){ String ele = it.next(); System.out.println(ele); }
-
增强for循环
Collection<String> c = new ArrayList<>(); for(String s : c) { System.*out*.println(s); }
-
forEach方法
这个方法是JDK8版本以后才有的。调用起来非常简单,最好是结合的lambda表达式一起使用。
匿名内部类的写法:
Collection<String> lists = new ArrayList<>(); lists.forEach(new Consumer<String>(){ @Override public void accept(String s){ System.out.println(s); } });
lamda表达式的写法:
lists.forEach(s -> { System.out.println(s); });
1.2 Map
Map下的实现类
- HashMap(由键决定特点): 无序、不重复、无索引;(用的最多)
- LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引
- TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引
Map的常用方法
Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的
方法名称 | 说明 |
---|---|
public V put(K key,V value) | 添加元素 |
public int size() | 获取集合的大小 |
public void clear() | 清空集合 |
public boolean isEmpty() | 判断集合是否为空,为空返回true , 反之 |
public V get(Object key) | 根据键获取对应值 |
public V remove(Object key) | 根据键删除整个元素 |
public boolean containsKey(Object key) | 判断是否包含某个键 |
public boolean containsValue(Object value) | 判断是否包含某个值 |
public Set |
获取全部键的集合 |
public Collection |
获取Map集合的全部值 |
Map的遍历方式
- 先获取Map集合的全部键,再通过键找值
此方法需要用到Map下的两个方法:
方法名称 | 说明 |
---|---|
public Set |
获取所有键的集合 |
public V get(Object key) | 根据键获取其对应的值 |
/**
* 目标:掌握Map集合的遍历方式1:键找值
*/
public class MapTest1 {
public static void main(String[] args) {
// 准备一个Map集合。
Map<String, Double> map = new HashMap<>();
map.put("蜘蛛精", 162.5);
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
System.out.println(map);
// map = {蜘蛛精=169.8, 牛魔王=183.6, 至尊宝=169.5, 紫霞=165.8}
// 1、获取Map集合的全部键
Set<String> keys = map.keySet();
// System.out.println(keys);
// [蜘蛛精, 牛魔王, 至尊宝, 紫霞]
// key
// 2、遍历全部的键,根据键获取其对应的值
for (String key : keys) {
// 根据键获取对应的值
double value = map.get(key);
System.out.println(key + "=====>" + value);
}
}
}
- 直接获取键值对对象,然后逐一获取键和值
Map 提供的方法 | 说明 |
---|---|
Set<Map.Entry<K, V>> entrySet() | 获取所有“键值对”的集合 |
Map.Entry 提供的方法 | 说明 |
---|---|
K getKey() | 获取键 |
V getValue() | 获取值 |
/**
* 目标:掌握Map集合的第二种遍历方式:键值对。
*/
public class MapTest2 {
public static void main(String[] args) {
Map<String, Double> map = new HashMap<>();
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
System.out.println(map);
// map = {蜘蛛精=169.8, 牛魔王=183.6, 至尊宝=169.5, 紫霞=165.8}
// entries = [(蜘蛛精=169.8), (牛魔王=183.6), (至尊宝=169.5), (紫霞=165.8)]
// entry = (蜘蛛精=169.8)
// entry = (牛魔王=183.6)
// ...
// 1、调用Map集合提供entrySet方法,把Map集合转换成键值对类型的Set集合
Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) {
String key = entry.getKey();
double value = entry.getValue();
System.out.println(key + "---->" + value);
}
}
}
-
forEach方法
此方法是JDK8版本以后才有的。调用起来非常简单,最好是结合的lambda表达式一起使用。
/**
* 目标:掌握Map集合的第二种遍历方式:键值对。
*/
public class MapTest3 {
public static void main(String[] args) {
Map<String, Double> map = new HashMap<>();
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
System.out.println(map);
// map = {蜘蛛精=169.8, 牛魔王=183.6, 至尊宝=169.5, 紫霞=165.8}
//遍历map集合,传递匿名内部类
map.forEach(new BiConsumer<String, Double>() {
@Override
public void accept(String k, Double v) {
System.out.println(k + "---->" + v);
}
});
//遍历map集合,传递Lambda表达式
map.forEach(( k, v) -> {
System.out.println(k + "---->" + v);
});
}
}
二、List系列集合的底层原理
2.1 ArrayList的底层原理
ArrayList底层是基于数组实现的。
但数组长度是固定的,ArrayList的长度是可变的,原理如下:
- 利用无参构造器创建一个集合时,会在底层创建一个默认长度为0的数组;
- 在添加第一个元素时,底层会创建一个新的长度为10的数组;
- 数组存满时,会扩容1.5倍;
- 如果一次性添加多个元素,使得数组扩容1.5倍都放不下,则新创建的数组长度以实际需要的长度为准。
数组扩容,并不是在原数组上扩容(原数组是不可以扩容的),底层是创建一个新数组,然后把原数组中的元素全部复制到新数组中去。
ArrayList的特点:
- 查询快(按索引查询),增删慢
2.2 LinkedList的底层原理
LinkedList集合是基于双向链表实现的。相对于普通链表而言,其对头尾节点的操作更方便,因此新增了一些针对头尾操作的方法:
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
2.3 LinkedList的应用场景
- 实现队列
//1.创建一个队列:先进先出、后进后出
LinkedList<String> queue = new LinkedList<>();
//入对列
queue.addLast("第1号人");
queue.addLast("第2号人");
queue.addLast("第3号人");
queue.addLast("第4号人");
System.out.println(queue);
//出队列
System.out.println(queue.removeFirst()); //第4号人
System.out.println(queue.removeFirst()); //第3号人
System.out.println(queue.removeFirst()); //第2号人
System.out.println(queue.removeFirst()); //第1号人
- 实现栈
//1.创建一个栈对象
LinkedList<String> stack = new ArrayList<>();
//压栈(push) 等价于 addFirst()
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack); //[第4颗子弹, 第3颗子弹, 第2颗子弹,第1颗子弹]
//弹栈(pop) 等价于 removeFirst()
System.out.println(statck.pop()); //第4颗子弹
System.out.println(statck.pop()); //第3颗子弹
System.out.println(statck.pop()); //第2颗子弹
System.out.println(statck.pop()); //第1颗子弹
//弹栈完了,集合中就没有元素了
System.out.println(list); //[]
三、Set系列的底层原理
3.1 HashSet的底层原理
HashSet集合底层是基于哈希表实现的,哈希表根据JDK版本的不同,也是有点区别的
- JDK8以前:哈希表 = 数组+链表
- JDK8以后:哈希表 = 数组+链表+红黑树
具体实现方法:
-
创建一个默认长度为16的数组,默认加载因子为0.75,数组名为table;
-
使用元素的哈希值对数组的长度求余计算出对应存入的位置;
-
判断当前位置是否为null,若是null,则直接存入;
-
如果不为null,表示该位置已有元素,则调用equals方法比较:
若二者相等,则不存;若不相等,则存入数组(JDK8以前和JDK8开始之后的存入方式也不同):
- JDK8之前:新元素存入数组,占据老元素的位置,老元素通过链表挂在后面
- JDK8之后:新元素直接挂在老元素后面
我们发现往HashSet集合中存储元素时,底层调用了元素的两个方法:一个是hashCode方法获取元素的hashCode值(哈希值);另一个是调用了元素的equals方法,用来比较新添加的元素和集合中已有的元素是否相同。
- 只有新添加元素的hashCode值和集合中以后元素的hashCode值相同、新添加的元素调用equals方法和集合中已有元素比较结果为true, 才认为元素重复。
- 如果hashCode值相同,equals比较不同,则以链表的形式连接在数组的同一个索引为位置(如上图所示)
在JDK8开始后,为了提高性能,当链表的长度超过8时,就会把链表转换为红黑树,如下图所示:
3.2 HashSet去重原理
HashSet存储元素的原理,依赖于两个方法:一个是hashCode方法用来确定在底层数组中存储的位置,另一个是用equals方法判断新添加的元素是否和集合中已有的元素相同。
要想保证在HashSet集合中没有重复元素,我们需要重写元素类的hashCode和equals方法。比如以下面的Student类为例,假设把Student类的对象作为HashSet集合的元素,想要让学生的姓名和年龄相同,就认为元素重复。
public class Student{
private String name; //姓名
private int age; //年龄
private double height; //身高
//无参数构造方法
public Student(){}
//全参数构造方法
public Student(String name, int age, double height){
this.name=name;
this.age=age;
this.height=height;
}
//...get、set、toString()方法自己补上..
//按快捷键生成hashCode和equals方法
//alt+insert 选择 hashCode and equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
if (Double.compare(student.height, height) != 0) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result;
long temp;
result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
temp = Double.doubleToLongBits(height);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
接着,写一个测试类,往HashSet集合中存储Student对象。
public class Test{
public static void main(String[] args){
Set<Student> students = new HashSet<>();
Student s1 = new Student("至尊宝",20, 169.6);
Student s2 = new Student("蜘蛛精",23, 169.6);
Student s3 = new Student("蜘蛛精",23, 169.6);
Student s4 = new Student("牛魔王",48, 169.6);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
for(Student s : students){
System.out.println(s);
}
}
}
打印结果如下,我们发现存了两个蜘蛛精,当时实际打印出来只有一个,而且是无序的。
Student{name='牛魔王', age=48, height=169.6}
Student{name='至尊宝', age=20, height=169.6}
Student{name='蜘蛛精', age=23, height=169.6}
3.3 LinkedHashSet的底层原理
LinkedHashSet是HashSet的子类。LinkedHashSet它底层采用的是也是哈希表结构,只不过额外新增了一个双向链表来维护元素的存取顺序。如下下图所示:
每次添加元素,就和上一个元素用双向链表连接一下。第一个添加的元素是双向链表的头节点,最后一个添加的元素是双向链表的尾节点。
3.4 TreeSet的底层原理
TreeSet底层是基于红黑树实现的。
TreeSet集合的特点是可以对元素进行排序,但是必须指定元素的排序规则。如果往集合中存储Integer类型的元素,则默认按升序排序;若存入String类型的元素,默认按照字符的ASCLL码升序排序。
Set<Integer> set1= new TreeSet<>();
set1.add(8);
set1.add(6);
set1.add(4);
set1.add(3);
set1.add(7);
set1.add(1);
set1.add(5);
set1.add(2);
System.out.println(set1); //[1,2,3,4,5,6,7,8]
Set<Integer> set2= new TreeSet<>();
set2.add("a");
set2.add("c");
set2.add("e");
set2.add("b");
set2.add("d");
set2.add("f");
set2.add("g");
System.out.println(set1); //[a,b,c,d,e,f,g]
如果往TreeSet集合中存储自定义类型的元素,比如说Student类型,则需要我们自己指定排序规则,否则会出现异常。
我们想要告诉TreeSet集合按照指定的规则排序,有两种办法:
方式1:让元素的类实现Comparable接口,重写compareTo方法
方式2:在创建TreeSet集合时,通过构造方法传递Compartor比较器对象
- 方式1:
//第一步:先让Student类,实现Comparable接口
//注意:Student类的对象是作为TreeSet集合的元素的
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
//无参数构造方法
public Student(){}
//全参数构造方法
public Student(String name, int age, double height){
this.name=name;
this.age=age;
this.height=height;
}
//...get、set、toString()方法自己补上..
//第二步:重写compareTo方法
//按照年龄进行比较,只需要在方法中让this.age和o.age相减就可以。
/*
原理:
在往TreeSet集合中添加元素时,add方法底层会调用compareTo方法,根据该方法的
结果是正数、负数、还是零,决定元素放在后面、前面还是不存。
*/
@Override
public int compareTo(Student o) {
//this:表示将要添加进去的Student对象
//o: 表示集合中已有的Student对象
return this.age-o.age;
}
}
此时,再运行测试类,结果如下
Student{name='至尊宝', age=20, height=169.6}
Student{name='紫霞', age=20, height=169.8}
Student{name='蜘蛛精', age=23, height=169.6}
Student{name='牛魔王', age=48, height=169.6}
- 方式2:
//创建TreeSet集合时,传递比较器对象排序
/*
原理:当调用add方法时,底层会先用比较器,根据Comparator的compare方是正数、负数、还是零,决定谁在后,谁在前,谁不存。
*/
//下面代码中是按照学生的年龄升序排序
Set<Student> students = new TreeSet<>(new Comparator<Student>{
@Override
public int compare(Student o1, Student o2){
//需求:按照学生的身高排序
return Double.compare(o1,o2);
}
});
//创建4个Student对象
Student s1 = new Student("至尊宝",20, 169.6);
Student s2 = new Student("紫霞",23, 169.8);
Student s3 = new Student("蜘蛛精",23, 169.6);
Student s4 = new Student("牛魔王",48, 169.6);
//添加Studnet对象到集合
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students);
四、Collection的其他操作
4.1 并发修改异常
那就是在使用迭代器遍历集合时,可能存在并发修改异常。
先把这个异常用如下代码演示出来,再解释一下为什么会有这个异常产生
List<String> list = new ArrayList<>();
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("张全蛋");
list.add("晓李");
list.add("李玉刚");
System.out.println(list); // [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
//需求:找出集合中带"李"字的姓名,并从集合中删除
Iterator<String> it = list.iterator();
while(it.hasNext()){
String name = it.next();
if(name.contains("李")){
list.remove(name);
}
}
System.out.println(list);
运行上面的代码,会出现下面的异常。这就是并发修改异常
为什么会出现这个异常呢?那是因为迭代器遍历机制,规定迭代器遍历集合的同时,不允许集合自己去增删元素,否则就会出现这个异常。
怎么解决这个问题呢?不使用集合的删除方法,而是使用迭代器的删除方法,代码如下:
List<String> list = new ArrayList<>();
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("张全蛋");
list.add("晓李");
list.add("李玉刚");
System.out.println(list); // [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
//需求:找出集合中带"李"字的姓名,并从集合中删除
Iterator<String> it = list.iterator();
while(it.hasNext()){
String name = it.next();
if(name.contains("李")){
//list.remove(name);
it.remove(); //当前迭代器指向谁,就删除谁
}
}
System.out.println(list);
4.2 Collections工具类
方法名称 | 说明 |
---|---|
public static |
给集合批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合中的元素顺序 |
public static |
对List集合中的元素进行升序排序 |
public static |
对List集合中元素,按照比较器对象指定的规则进行排序 |
注意:
- 第三个方法 public static
void sort(List list) 可以直接对自定义类型的List集合排序,但自定义类型必须实现了Comparable接口,指定了比较规则才可以使用; - 第四个方法 public static
void sort(List list,Comparator<? super T> c) 则是在调用该方法时把比较器传递给该方法。
五、Map系列的底层原理
5.1 HashMap的底层原理
HashMap底层原理是基于哈希表(数组 + 链表 + (红黑树))实现的。实际上HashMap底层原理和HashSet是一样的,HashSet只是利用了HashMap,只取HashMap的键,不管值。
我们可以看源码验证这一点,如下图所示,我们可以看到,创建HashSet集合时,底层帮你创建了HashMap集合;往HashSet集合中添加添加元素时,底层却是调用了Map集合的put方法把元素作为了键来存储。所以实际上根本没有什么HashSet集合,把HashMap的集合的值忽略不看就是HashSet集合。
具体实现如下:
HashMap底层数据结构: 哈希表结构
JDK8之前的哈希表 = 数组+链表
JDK8之后的哈希表 = 数组+链表+红黑树
哈希表是一种增删改查数据,性能相对都较好的数据结构往HashMap集合中键值对数据时,底层步骤如下
- 当你第一次往HashMap集合中存储键值对时,底层会创建一个长度为16、加载因子为0.75的数组
- 把键然后将键和值封装成一个对象,叫做Entry对象
- 再根据Entry对象的键计算hashCode值(和值无关)
- 利用hashCode值和数组的长度做一个类似求余数的算法,会得到一个索引位置
- 判断这个索引的位置是否为null:
- 如果为null,就直接将这个Entry对象存储到这个索引位置
- 如果不为null,则还需要进行第6步的判断
- 继续调用equals方法判断两个对象键是否相同:
- 如果equals返回false,则以链表的形式往下挂
- 如果equals方法true,则认为键重复,此时新的键值对会替换旧的键值对
HashMap底层需要注意这几点:
- 底层数组默认长度为16,如果数组中有超过12个位置已经存储了元素,则会对数组进行扩容2倍
数组扩容的加载因子是0.75,意思是:16*0.75=12- 数组的同一个索引位置有多个元素、并且在8个元素以内(包括8),则以链表的形式存储
JDK7版本:链表采用头插法(新元素往链表的头部添加)
JDK8版本:链表采用尾插法(新元素我那个链表的尾部添加)- 数组的同一个索引位置有多个元素、并且超过了8个,则以红黑树形式存储
从HashMap底层存储键值对的过程中我们发现:决定键是否重复依赖与两个方法,一个是hashCode方法、一个是equals方法。有两个键计算得到的hashCode值相同,并且两个键使用equals比较为true,就认为键重复。
所以,往Map集合中存储自定义对象作为键,为了保证键的唯一性,我们应该重写hashCode方法和equals方法。
比如有如下案例:往HashMap集合中存储Student对象作为键,学生的家庭住址当做值。要求,当学生对象的姓名和年龄相同时就认为键重复。
public class Student implements Comparable<Student> {
private String name;
private int age;
private double height;
// this o
@Override
public int compareTo(Student o) {
return this.age - o.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 && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
//...get,set方法自己补全....
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
写一个测试类,在测试类中,创建HashMap集合,键是Student类型,值是Stirng类型
/**
* 目标:掌握Map集合下的实现类:HashMap集合的底层原理。
*/
public class Test1HashMap {
public static void main(String[] args) {
Map<Student, String> map = new HashMap<>();
map.put(new Student("蜘蛛精", 25, 168.5), "盘丝洞");
map.put(new Student("蜘蛛精", 25, 168.5), "水帘洞");
map.put(new Student("至尊宝", 23, 163.5), "水帘洞");
map.put(new Student("牛魔王", 28, 183.5), "牛头山");
System.out.println(map);
}
}
上面存储的键,有两个蜘蛛精,但是打印出只会有最后一个。
5.2 LinkedHashMap的底层原理
LinkedHashMap的底层原理,和LinkedHashSet底层原理是一样的。底层是基于哈希表实现的,此外多一个双向链表来维护键的存储顺序。
5.3 TreeMap的底层原理
- TreeMap集合的特点也是由键决定的,默认按照键的升序排列,键不重复,也是无索引的。
- TreeMap集合的底层原理和TreeSet也是一样的,底层都是基于红黑树实现的。所以可以对键进行排序。
注意:往TreeMap中存储自定义类对象需要自己实现排序方法:
方法1:写一个自定义Student类,在Student类中实现Comparable接口
//第一步:先让Student类,实现Comparable接口
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
//无参数构造方法
public Student(){}
//全参数构造方法
public Student(String name, int age, double height){
this.name=name;
this.age=age;
this.height=height;
}
//...get、set、toString()方法自己补上..
//按照年龄进行比较,只需要在方法中让this.age和o.age相减就可以。
/*
原理:
在往TreeSet集合中添加元素时,add方法底层会调用compareTo方法,根据该方法的
结果是正数、负数、还是零,决定元素放在后面、前面还是不存。
*/
@Override
public int compareTo(Student o) {
//this:表示将要添加进去的Student对象
//o: 表示集合中已有的Student对象
return this.age-o.age;
}
}
方式2:在创建TreeMap集合时,直接传递Comparator比较器对象。
/**
* 目标:掌握TreeMap集合的使用。
*/
public class Test3TreeMap {
public static void main(String[] args) {
Map<Student, String> map = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight());
}
});
// Map<Student, String> map = new TreeMap<>(( o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()));
map.put(new Student("蜘蛛精", 25, 168.5), "盘丝洞");
map.put(new Student("蜘蛛精", 25, 168.5), "水帘洞");
map.put(new Student("至尊宝", 23, 163.5), "水帘洞");
map.put(new Student("牛魔王", 28, 183.5), "牛头山");
System.out.println(map);
}
}
这种方式都可以对TreeMap集合中的键排序。注意:只有TreeMap的键才能排序,HashMap键不能排序。
六、Stream流
Stream流是JDK8开始才有的新特性,是用于对集合或者数组进行便捷操作的
6.1 Stream流简介
案例需求:有一个List集合,元素有"张三丰","张无忌","周芷若","赵敏","张强"
,找出姓张,且是3个字的名字,存入到一个新集合中去。
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
System.out.println(names);
- 用传统方式来做,代码是这样的
// 找出姓张,且是3个字的名字,存入到一个新集合中去。
List<String> list = new ArrayList<>();
for (String name : names) {
if(name.startsWith("张") && name.length() == 3){
list.add(name);
}
}
System.out.println(list);
- 用Stream流来做,代码是这样的
List<String> list2 = names.stream().filter(s ->
s.startsWith("张")).filter(a -> a.length()==3).collect(Collectors.toList());
System.out.println(list2);
总之,Stream流就像流水线一样,开始输入的是待处理的集合和数组,在中间进行一系列操作后,最后得到想要的结果,如下图,总共分为三个步骤:
- 获取Stream流
- Stream流的中间处理方法
- stream流的终结方法(用于获取最终的结果)
6.2 Stream流的创建
主要要知道如何获取List、Set、Map以及数组的Stream流,具体操作如下代码:
/**
* 目标:掌握Stream流的创建。
*/
public class StreamTest2 {
public static void main(String[] args) {
// 1、如何获取List集合的Stream流?
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
Stream<String> stream = names.stream();
// 2、如何获取Set集合的Stream流?
Set<String> set = new HashSet<>();
Collections.addAll(set, "刘德华","张曼玉","蜘蛛精","马德","德玛西亚");
Stream<String> stream1 = set.stream();
stream1.filter(s -> s.contains("德")).forEach(s -> System.out.println(s));
// 3、如何获取Map集合的Stream流?
Map<String, Double> map = new HashMap<>();
map.put("古力娜扎", 172.3);
map.put("迪丽热巴", 168.3);
map.put("马尔扎哈", 166.3);
map.put("卡尔扎巴", 168.3);
Set<String> keys = map.keySet();
Stream<String> ks = keys.stream();
Collection<Double> values = map.values();
Stream<Double> vs = values.stream();
Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
kvs.filter(e -> e.getKey().contains("巴"))
.forEach(e -> System.out.println(e.getKey()+ "-->" + e.getValue()));
// 4、如何获取数组的Stream流?
String[] names2 = {"张翠山", "东方不败", "唐大山", "独孤求败"};
Stream<String> s1 = Arrays.stream(names2);
Stream<String> s2 = Stream.of(names2);
}
}
6.3 Stream流中间方法
中间方法指的是:调用完方法之后其结果是一个新的Stream流,于是可以继续调用方法,这样一来就可以支持链式编程(或者叫流式编程)。常用方法如下:
如下代码具体演示上述方法如何使用:
/**
* 目标:掌握Stream流提供的常见中间方法。
*/
public class StreamTest3 {
public static void main(String[] args) {
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 88.5, 100.0, 60.0, 99.0, 9.5, 99.6, 25.0);
// 需求1:找出成绩大于等于60分的数据,并升序后,再输出。
scores.stream().filter(s -> s >= 60).sorted().forEach(s -> System.out.println(s));
List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 25, 169.0);
Student s5 = new Student("牛魔王", 35, 183.3);
Student s6 = new Student("牛夫人", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求2:找出年龄大于等于23,且年龄小于等于30岁的学生,并按照年龄降序输出.
students.stream().filter(s -> s.getAge() >= 23 && s.getAge() <= 30)
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.forEach(s -> System.out.println(s));
// 需求3:取出身高最高的前3名学生,并输出。
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.limit(3).forEach(System.out::println);
System.out.println("-----------------------------------------------");
// 需求4:取出身高倒数的2名学生,并输出。 s1 s2 s3 s4 s5 s6
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.skip(students.size() - 2).forEach(System.out::println);
// 需求5:找出身高超过168的学生叫什么名字,要求去除重复的名字,再输出。
students.stream().filter(s -> s.getHeight() > 168).map(Student::getName)
.distinct().forEach(System.out::println);
// distinct去重复,自定义类型的对象(希望内容一样就认为重复,重写hashCode,equals)
students.stream().filter(s -> s.getHeight() > 168)
.distinct().forEach(System.out::println);
Stream<String> st1 = Stream.of("张三", "李四");
Stream<String> st2 = Stream.of("张三2", "李四2", "王五");
Stream<String> allSt = Stream.concat(st1, st2);
allSt.forEach(System.out::println);
}
}
6.4 Stream流终结方法
这些方法的特点是,调用完方法之后,其结果就不再是Stream流了,所以不支持链式编程。
下面的代码具体演示上述方法如何使用:
/**
* 目标:Stream流的终结方法
*/
public class StreamTest4 {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 25, 169.0);
Student s5 = new Student("牛魔王", 35, 183.3);
Student s6 = new Student("牛夫人", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求1:请计算出身高超过168的学生有几人。
long size = students.stream().filter(s -> s.getHeight() > 168).count();
System.out.println(size);
// 需求2:请找出身高最高的学生对象,并输出。
Student s = students.stream().max((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(s);
// 需求3:请找出身高最矮的学生对象,并输出。
Student ss = students.stream().min((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(ss);
// 需求4:请找出身高超过170的学生对象,并放到一个新集合中去返回。
// 流只能收集一次。
List<Student> students1 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toList());
System.out.println(students1);
Set<Student> students2 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toSet());
System.out.println(students2);
// 需求5:请找出身高超过170的学生对象,并把学生对象的名字和身高,存入到一个Map集合返回。
Map<String, Double> map =
students.stream().filter(a -> a.getHeight() > 170)
.distinct().collect(Collectors.toMap(a -> a.getName(), a -> a.getHeight()));
System.out.println(map);
// Object[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray();
Student[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray(len -> new Student[len]);
System.out.println(Arrays.toString(arr));
}
}
标签:Map,Stream,map,Collection,println,Student,集合,new,public
From: https://www.cnblogs.com/sunjuil/p/17777588.html