文章目录
-
- java基础
-
- HashMap
- HashMap和HashTable的区别
- HashMap底层实现原理详解
- 为什么HashMap要用数组链表实现
- 为什么HashMap在jdk1.8以后引入红黑树
- HashMap 1.7 的头插法是如何实现的
- HashMap中的数组的大小有什么特点
- HashMap中是如何计算数组下标的
- HashMap1.7 的rehash底层是如何实现的
- HashMap中的modcount表示什么意思
- HashMap的put()和get()原理
- 多线程情况下HashMap1.7 扩容时为什么会出现线程不安全
- HashMap为什么会出现ConcurrentModificationException
- HashMap中的扰动机制
- HashMap中的扩容机制详解
- HashMap中的重写哈希机制
- JVM
-
- 高并发
-
- MySQL
- MongoDB
- Redis
- 微服务
文章目录
- java基础
- HashMap
- HashMap和HashTable的区别
- HashMap底层实现原理详解
- 为什么HashMap要用数组链表实现
- 为什么HashMap在jdk1.8以后引入红黑树
- HashMap 1.7 的头插法是如何实现的
- HashMap中的数组的大小有什么特点
- HashMap中是如何计算数组下标的
- HashMap1.7 的rehash底层是如何实现的
- HashMap中的modcount表示什么意思
- HashMap的put()和get()原理
- 多线程情况下HashMap1.7 扩容时为什么会出现线程不安全
- HashMap为什么会出现ConcurrentModificationException
- HashMap中的扰动机制
- HashMap中的扩容机制详解
- HashMap中的重写哈希机制
- JVM
- 高并发
- MySQL
- MongoDB
- Redis
- 微服务
注意:以下所有的代码环境是在JDK8下运行的
java基础
什么是面向对象,为什么要用面向对象编程,它和面向过程的区别,有哪些特性,以及你对这些特性的理解?
面向对象更注重事情的每一个步骤以及顺序,更注重有哪些参与者(对象),及各自需要做什么;面向过程是分析出解决问题所需要的步骤,然后按这些步骤一步步的实现下去,使用的时候一个一个调用就可以了;区别从概念就很明显了,前者注重的是参与的对象,后者注重的是步骤;面向对象的特点就是易维护,耦合度相对于面向过程低,系统更加灵活,并且具有一些特性;面向过程特点就是性能比面向对象高,类调用时需要实例化,所以比较消耗资源。面向对象的特性有继承,封装,多态,(抽象)。继承就是从已有类得到继承信息创建新类的过程,提供继承信息的类被称为父类(超类、基类),得到继承信息的类被称为子类(派生类),继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。封装通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口;面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。多态性是指允许不同子类型的对象对同一消息作出不同的响应;简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时, B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事: 1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法); 2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为。
JDK JRE JVM的区别和联系
JDK:Java Development Kit java开发工具
JRE:Java Runtime Environment java运行时环境
JVM:Java Virtual Machine java虚拟机
具体看下图
通过上图可以看出来 JDK包含JRE JRE包含JVM
final finally finalize的区别
final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
finally:异常处理语句结构的一部分,表示总是执行。
finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法
提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法
被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对
象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用
throw throws
throw:(1)throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
(2)throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常
throws:(1) throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
(2) throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
(3) throws 表示出现异常的一种可能性,并不一定会发生这种异常。
String StringBuilder StringBuffer 三者简述 区别 使用场景
String是被final修饰的,不可变,每次操作都会产生新的String对象
StringBuffer和StringBuilder都是在原对象上操作
StringBuffer是线程安全的,之所以线程安全是因为它的方法都是synchronized修饰的,StringBuilder是线程不安全的;性能:StringBuilder>StringBuffer>String
场景:经常需要改变字符串内容的话就用前两个(优先选择StringBuilder,多线程共享变量使用StringBuffer)
重载和重写的区别
重载:发生在同一类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中;方法名,参数列表必须相同,返回值范围小于等于父类,抛出的异常范围≤父类,访问修饰符范围≥父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
接口和抽象类的区别
相同:(1)不能实例化
(2)可以将抽象类和接口类型作为引用类型
(3).一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类
不同:
抽象类:
(1)抽象类中可以定义构造器
(2)可以有抽象方法和具体方法
(3)接口中的成员全都是 public 的
(4)抽象类中可以定义成员变量
(5)有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
(6)抽象类中可以包含静态方法
(7)一个类只能继承一个抽象类
接口:
(1)接口中不能定义构造器
(2)方法全部都是抽象方法
(3)抽象类中的成员可以是private,default,protected,public
(4)接口中定义的成员变量实际上都是常量
(5)接口中不能有静态方法
(6)一个类可以实现多个接口
接口的设计目的:是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为(共有的特征)。它只约束了行为的有无,但不对如何实现行为进行限制。
而抽象类的设计目的是代码复用,当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集记为B),可以让这些类都派生于一个抽象类。在这个 抽象类中实现了B,避免让所有的的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现,正是因为A-B在这里没有实现,所以抽象类不允许实例化出来,否则当调用A-B的时候,无法执行。
抽象类是对类本质的抽象,表达的是is a的关系,比如 Porsche is Car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。
而接口是对行为的抽象,表达的是like a的关系,比如 Birds like a Aircraft(像飞行器一样可以飞),但其本质上 is a bird ,接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁,是如何实现的,接口并不关心。
使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
抽象类的功能远远超过接口,但是定义抽象类的代价高,因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上弱化了很多,但是它只是针对一个动作的描述,而且你可以在一个类中同事实现多个接口,在设计阶段会降低难度
list和set Map
结构特点:
List 和 Set 是存储单列数据的集合, Map 是存储键和值这样的双列数据的集合; List 中存储的数据是有顺序,并且允许重复; Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的, Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的) ;
实现类:
List 接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢; ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除; Vector:基于数组实现,线程安全的,效率低)。Map 接口有三个实现类(HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null键; HashTable:线程安全,低效,不支持 null 值和 null 键; LinkedHashMap:是 HashMap 的一个子类,保存了
记录的插入顺序; SortMap 接口: TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法; LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMp)。
区别:
List 集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过list.get(i)方法来获取集合中的元素; Map 中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复; Set 集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet 类,可以按照默认顺序,也可以通过实现 Java.util.Comparator接口来自定义排序方式。
hashCode ,== ,equals
equals 和== 最大的区别是一个是方法一个是运算符。
==:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。
equals():用来比较方法两个对象的内容是否相等。
注意: equals 方法不能用于基本数据类型的变量,如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址。
hashCode是一种编码方式,hash一般翻译成散列,也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-maping),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。在java的定义中,对象相等则hashCode一定相等,hashCode相等的对象未必相等。
为什么要有hashCode?
以hashSet如何检查重复为例子来说明为什么要有hashCode
对象加入HashSet时,HashSet会先计算对象的hashCode值来判断对象加入的位置,看该位置是否有值,如果没有,HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功,如果不同的话,就会重新散列到其他位置,这样就打打减少了equals的次数,相应就大大提高了执行速度。
ArrayList和LinkedList的区别
ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链表在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素呗添加到天河任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList比ArrayList更占内存没因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素 。
(1)因为Array是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大的,因为这需要重排数组中的所有数据。
(2)相对于ArrayList,LinkedList插入是更快的,因为LinkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是ArrayList最坏的一种情况,时间复杂度是O(n),而LinkedList中插入或删除的时间复杂度仅为O(1)。ArrayList在插入数据时还需要更新索引(除了插入数组的尾部)
(3)类似于插入数据,删除数据时,LinkedList也优于ArrayList
(4)LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的事实际的数据和前后节点的位置(一个LinkedList实例存储了两个值:Node first和Node last 分别表示链表的其实节点和尾节点,每个Node实例存储了三个值:E item,Node next,Node pre)。
什么场景下更适合使用LinkedList,而不用ArrayList
(1)你的应用不会随机访问数据,因为如果你需要LinkedList中的第n个元素的时候,你需要从第一个元素顺序数到第n个数据,然后读取数据
(2)你的应用更多而插入和删除元素,更少的读取数据,因为插入和删除元素不涉及重排数据,所以它要比ArrayList更快
HashMap
HashMap和HashTable的区别
1.hashMap 去掉了 HashTable 的 contains 方法,但是加上了 containsValue()和 containsKey()方法。
2.hashTable 同步的,而 HashMap 是非同步的,效率上逼 hashTable 要高。
3.hashMap 允许空键值,而 hashTable 不允许。
一般用 hashmap 代替 hashtable
注意:
TreeMap:非线程安全基于红黑树实现。TreeMap 没有调优选项,因为该树总处于平衡状态。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
HashMap底层实现原理详解
为什么HashMap要用数组链表实现
为什么HashMap在jdk1.8以后引入红黑树
HashMap 1.7 的头插法是如何实现的
HashMap中的数组的大小有什么特点
HashMap中是如何计算数组下标的
HashMap1.7 的rehash底层是如何实现的
HashMap中的modcount表示什么意思
HashMap的put()和get()原理
- java7 及以前:
get()方法
首先判断输入的 key 是否为空,如果为空,从 hashmap 数组下标为 0 的位置获取值返回。如果不为空,根据 key
的值,从 hashmap 数组中获取对应的 entry 对象,判断这个对象是否为空,为空返回 null,不为空返回对应的 value
值, 获取 value 的方法中 key 为空和不为空时的方法里都先判断数组中的元素是否为 0 ,如果不为 0,才继续查找
put()方法
调用 put 方法的时候首先判断 hashmap 数组是否为空数组,
如果为空,进行初始化,判断 key 的值是否是 null,如果是 null,把对应的 value 值存进数组中下标为 0 的位置,计算
key 的 hash 值,并计算出下标,遍历下标对应的链表,匹配 hash 值和 key 的值,如果存在,则覆盖,返回旧值,如果
不存在,新添加一个,返回 null
最后判断数组大小,是否扩容 - java8
get()方法
对输入的 key 的值计算 hash 值,
首先判断 hashmap 中的数组是否为空和数组的长度是否为 0,如果为空和为 0,则直接放回 null
如果不为空和 0,计算 key 对应的数组下标,判断对应位置上的第一个 node 是否满足条件,如果满足条件,直接返
回 如
果不满足条件,判断当前 node 是否是最后一个,如果是,说明不存在 key,则返回 null
如果不是最后一个,判断是否是红黑树,如果是红黑树,则使用红黑树的方式获取对应的 key,
如果不是红黑树,遍历链表是否有满足条件的,如果有,直接放回,否则返回 null
put()方法
首先计算 key 的 hash 值,获取 hashmap 中的数组和数组长度,如果数组为空,初始化计算 key 的下标
数组对应下标的位置是否为空,如果为空,则先添加一个,放在这个下标位置,然后判断数组内元素是否大于阈值,
如果大于,则进行扩容
如果数组对应下标不为空,则先获取对应链表的第一个值,判断 hash 和 key 是否相同,如果相同,新 value 替换旧
value,返回旧 value
如果第一个值 key 不相同,判断当前链表是否是红黑树,如果是红黑树,调用红黑树链表 put 的方法
如果也不是红黑树,遍历链表,判断当前 node 是否是最后一个,如果是,说明链表中没有新添加的 key,则在最后面
新添加一个,然后判断是否超过阈值(8-1),如果超过,则转换成红黑树
如果不是最后一个,说明在中间已经存在 key 了, 把新值赋值给旧值,并返回旧值,判断是否需要扩容.
注:哈希碰撞:当两个不同的键对象的 hashcode 相同时,它们会储存在同一个 bucket 位置的链表中。键对
象的 equals()方法用来找到键值对()。
多线程情况下HashMap1.7 扩容时为什么会出现线程不安全
HashMap为什么会出现ConcurrentModificationException
HashMap中的扰动机制
HashMap中的扩容机制详解
HashMap中的重写哈希机制
JVM
请解释一下对象的创建过程(半初始化)
加问DCL要不要加Volatile问题?(指令重排)
对象在内存中的存储布局?(对象与数组的存储不同)
对象头具体包括什么(markword klasspointer)synchronized锁信息
对象怎么定位?(直接 间接)
对象怎么分配?(栈上-线程本地-Eden-Old)
Object o=new Object()在内存中占用多少字节?
为什么Hotspot不使用C++对象来代表java对象
CLass对象是在堆还是在方法区?
高并发
第一代线程按全集合类
Vector,Hashtable
是怎么保证线程安全的:使用synchronized修饰方法
缺点:效率低下
第二代线程费安全集合类
ArrayList,HashMap
线程不安全,但是性能好,用来替代vector,HashTable
使用ArrayList,HashMap,需要线程安全怎么办呢?
使用Collections.synchronizedList(list);Collections.synchronizedMap(map);
底层使用synchronized代码块锁,虽然也是锁住了所有的代码,但是所在方法里边,并所在方法外边性能可以理解为稍有提高吧,毕竟进方法本身就要分配资源
第三代线程安全集合类
在大量并发情况下如何提高集合的效率和安全呢
java.util.concurrent.;
ConcurrentHashMap:
CopyOnWriteArrayList
CopyOnWriteArraySet 注意不是CopyOnWriteHashSet
底层大都采用lock锁(1.8的ConcurrentHashMap不使用lock锁),保证安全的同时,性能也很高
JDK1.8的新特性
Optional类
以前对null的处理方式
package com.wh.Test9;
import org.junit.Test;
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/17 18:09
**/
public class MyOptionalOne {
/**
* 以前对null的处理:非空判断
*/
@Test
public void test01() {
String userName = "凤姐";
// String userName = null;
if (userName != null) {
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
}
}
Option类介绍
Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。
Optional的基本使用
Optional类的创建方式:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
Optional类的常用方法:
isPresent() : 判断是否包含值,包含值返回true,不包含值返回false
get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException
orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
@Test
public void test02() {
// Optional<String> userNameO = Optional.of("凤姐");
// Optional<String> userNameO = Optional.of(null);
// Optional<String> userNameO = Optional.ofNullable(null);
Optional<String> userNameO = Optional.empty();
// isPresent() : 判断是否包含值,包含值返回true,不包含值返回false。
if (userNameO.isPresent()) {
// get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException。
String userName = userNameO.get();
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
}
Optional高级使用
/**
* Optional高级使用
*/
@Test
public void ThirdTest() {
Optional<String> userName = Optional.of("张三");
// 存在做的什么 JDk8写法
userName.ifPresent(s -> System.out.println("用户名为" + s));
userName.orElse("用户名不存在");
// userNameO.ifPresent(s -> System.out.println("用户名为" + s));
// 存在做的什么,不存在做点什么 下面是JDK9新增的写法
//userNameO.ifPresentOrElse(s -> System.out.println("用户名为" + s) , () -> System.out.println("用户名不存在"));
}
@Test
public void FourthTest() {
Optional<String> userName=Optional.empty();
// 如果调用对象包含值,返回该值,否则返回参数t
System.out.println("用户名为" + userName.orElse("null"));
// 如果调用对象包含值,返回该值,否则返回参数Supplier得到的值
String s1 = userName.orElseGet(() -> {
return "未知用户名";
});
System.out.println("s1 = " + s1);
}
@Test
public void FifthTest(){
// User u = new User("凤姐", 18);
// User u = new User(null, 18);
// User u = null;
// System.out.println(getUpperCaseUserName1(u));
// 我们将可能会为null的变量构造成Optional类型
// User u = new User("凤姐", 18);
User u=new User(null,17);
Optional<User> UseOptional=Optional.of(u);
System.out.println(getUpperCaseUserName2(UseOptional));
}
public String getUpperCaseUserName2(Optional<User> UseOptional) {
return UseOptional.map(u -> u.getName())
.map(name -> name.toUpperCase())
.orElse("null");
}
/*public String getUpperCaseUserName1(User u) {
if (u != null) {
String userName = u.getName();
if (userName != null) {
return userName;
} else {
return null;
}
} else {
return null;
}
}*/
Lambda表达式
lambda的标准格式
(参数类型 参数名称) -> {
代码体;
}
格式说明;
参数类型 参数名称):参数列表
{代码体;}:方法体
-> :箭头,分隔参数列表和方法体
函数式接口
Stream流
举个例子比对一下普通写法和stream写法的区别
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
/**
* 一个ArrayList集合中存有一下元素 周杰伦 刘畊宏 林俊杰 张怀秋 方文山 张杰
* 现要求拿到长度为3且 名字含有杰的名字并打印数据
*/
public class StreamFilterDemo1<list> {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>(
Arrays.asList("周杰伦", "刘畊宏", "林俊杰", "张怀秋" ,"方文山", "张杰","杰克伦敦")
);
System.out.println("----------普通写法如下----------------");
test1(list);
System.out.println("----------stream写法如下--------------");
test2(list);
}
public static void test1(ArrayList<String> list1){
ArrayList<String> threeList=new ArrayList<>();
ArrayList<String> NameList=new ArrayList<>();
//Collections.addAll(list1,"周杰伦", "刘畊宏", "林俊杰", "张怀秋" ,"方文山", "张杰");
//先拿出所有长度为3的字符串
for (String lengthIsThree:list1
) {
if(lengthIsThree.length()==3){
threeList.add(lengthIsThree);
}
}
for (String s:threeList
) {
if(s.contains("杰")){
NameList.add(s);
}
}
System.out.println(NameList);
}
public static void test2(ArrayList<String> list2){
list2.stream()
.filter(s -> s.length()==3)
.filter(s -> s.contains("杰"))
.forEach(System.out::println);
}
}
Stream流式思想概述
首先stream流和io流没有任何关系
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工
处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品
Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。
获取stream流的两种方式
方式一:根据collection获取流
首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
public interface Collection {
default Stream<E> stream()
}
import java.util.*;
import java.util.stream.Stream;
public class GetStreamDemo7 {
public static void main(String[] args) {
// 集合获取流
// Collection接口中的方法: default Stream<E> stream() 获取流
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
// ...
Stream<String> stream2 = set.stream();
Vector<String> vector = new Vector<>();
// ...
Stream<String> stream3 = vector.stream();
/*java.util.Map 接口不是 Collection 的子接口,所以获取对应的流需要分key、value或entry等情况:*/
//Map获取流
Map<String,String> map=new HashMap<>();
// ...
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}
方式二:Stream中的静态方法of获取流
由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:
import java.util.stream.Stream;
public class GetStreamDemo8 {
public static void main(String[] args) {
// Stream中的静态方法: static Stream of(T... values)
Stream<String> stream6 = Stream.of("aa", "bb", "cc");
String[] arr = {"aa", "bb", "cc"};
Stream<String> stream7 = Stream.of(arr);
Integer[] arr2 = {11, 22, 33};
Stream<Integer> stream8 = Stream.of(arr2);
// 注意:基本数据类型的数组不行
int[] arr3 = {11, 22, 33};
Stream<int[]> stream9 = Stream.of(arr3);
}
}
stream常用方法
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | long | 终结 |
foreach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取用前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。本小节中,终结方法包括 count 和 | |||
forEach 方法。 | |||
非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结 | |||
方法。) |
Stream注意事项
1. Stream只能操作一次
2. Stream方法返回的是新的流
3. Stream不调用终结方法,中间的操作不会执行
Stream流的forEach方法
forEach 用来遍历流中的数据
void forEach(Consumer<? super T> action);
/**
* 该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理 基本使用
*/
@Test
public void ForEachTest(){
List<String> FirstList=new ArrayList<>();
Collections.addAll(FirstList,"华为","苹果","谷歌","鸿蒙","安卓");
System.out.println("---------第一种写法-------------");
FirstList.forEach((String s)->{
System.out.println(s);
});
System.out.println("---------第二种写法-------------");
FirstList.stream().forEach(s-> System.out.println(s));
System.out.println("---------第三种写法-------------");
FirstList.stream().forEach(System.out::println);
}
Stream流的count方法
Stream流提供 count 方法来统计其中的元素个数:
long count();
/**
* 该方法返回一个long值代表元素个数。基本使用:
*/
@Test
public void CountTest(){
List<String> SecondList=new ArrayList<>();
Collections.addAll(SecondList,"华为","苹果","谷歌","鸿蒙","安卓");
System.out.println(SecondList.stream().count());
}
Stream流的filter方法
filter用于过滤数据,返回符合过滤条件的数据
可以通过 filter 方法将一个流转换成另一个子集流。方法声明:
Stream<T> filter(Predicate<? super T> predicate);
/**
* 该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件 基本使用
*/
@Test
public void FilterTest() {
List<String> ThirdList = new ArrayList<>();
Collections.addAll(ThirdList, "华为", "苹果", "谷歌", "鸿蒙", "安卓","巴卡玛卡");
//这里通过lambda表达式筛选出长度为2的字符串
ThirdList.stream().filter(s->s.length()==2).forEach(System.out::println);
}
Stream流的limit方法
limit 方法可以对流进行截取,只取用前n个:
Stream<T> limit(long maxSize);
/**
* 参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。基本使用:
*/
@Test
public void LimitTest(){
List<String> FourthList = new ArrayList<>();
Collections.addAll(FourthList, "华为", "苹果", "谷歌", "鸿蒙", "安卓","巴卡玛卡");
FourthList.stream().limit(3).forEach(System.out::println);
}
Stream流的skip方法
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
Stream<T> skip(long n);
/**
* 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:
* 这个是从index为n的元素开始截取
*/
@Test
public void SkipTest(){
List<String> FifthList = new ArrayList<>();
Collections.addAll(FifthList, "华为", "苹果", "谷歌", "鸿蒙", "安卓","巴卡玛卡");
FifthList.stream().skip(2).forEach(System.out::println);
}
Stream流的map方法
如果需要将流中的元素映射到另一个流中,可以使用 map 方法:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
/**
* 该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
*/
@Test
public void MapTest(){
Stream<String> SixthStream= Stream.of("1","22","333","4444");
Stream<Integer> result=SixthStream.map(Integer::parseInt);
result.forEach(s-> System.out.println(s%10));
}
Stream流的sorted方法
如果需要将数据排序,可以使用 sorted 方法:
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
/**
* Stream流中的 sorted 方法基本使用的代码如:
*/
@Test
public void SortedTest(){
// sorted(): 根据元素的自然顺序排序
// sorted(Comparator<? super T> comparator): 根据比较器指定的规则排序
Stream.of(33, 22, 11, 55)
.sorted()
.sorted((o1, o2) -> o2 - o1)
.forEach(System.out::println);
}
Stream流的distinct方法
如果需要去除重复数据,可以使用 distinct 方法:
Stream<T> distinct();
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Fruits {
private String name;
private Integer weight ;
}
/**
* Stream流中的 distinct 方法基本使用的代码如:
* distinct 去掉重复元素
*/
@Test
public void DistinctTest() {
Stream.of(33, 22, 11, 33)
.distinct()
.forEach(System.out::println);
}
@Test
public void DistinctTest1() {
Stream.of(
new Fruits("粑粑柑", 2),
new Fruits("菠萝", 5),
new Fruits("西瓜", 12),
new Fruits("荔枝", 3),
new Fruits("荔枝", 3)
).distinct()
.forEach(System.out::println);
}
Stream流的match方法
如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法;
boolean allMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
/**
* Stream流中的 Match 相关方法基本使用的代码
*/
@Test
public void MatchTest(){
boolean b = Stream.of(5, 3, 6, 1)
// .allMatch(e -> e > 0); // allMatch: 元素是否全部满足条件
// .anyMatch(e -> e > 5); // anyMatch: 元素是否任意有一个满足条件
.noneMatch(e -> e < 0); // noneMatch: 元素是否全部不满足条件
System.out.println("b = " + b);
}
Stream流的find方法
如果需要找到某些数据,可以使用 find 相关方法:
Optional<T> findFirst();
Optional<T> findAny();
/**
* Stream流中的 find 相关方法基本使用的代码
*/
@Test
public void FindTest() {
Optional<Integer> first = Stream.of(5, 3, 6, 1).findFirst();
System.out.println("first = " + first.get());
Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny();
System.out.println("any = " + any.get());
}
Stream流的max和min方法
如果需要获取最大和最小值,可以使用 max 和 min 方法:
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
/**
* Stream流中的 max 和 min 相关方法基本使用的代码
*/
@Test
public void MaxAndMinTest() {
Optional<Integer> max = Stream.of(5, 3, 6, 1).max((o1, o2) -> o1 - o2);
System.out.println("first = " + max.get());
Optional<Integer> min = Stream.of(5, 3, 6, 1).min((o1, o2) -> o1 - o2);
System.out.println("any = " + min.get());
}
Stream流的reduce方法
如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法:
T reduce(T identity, BinaryOperator<T> accumulator);
/**
* Stream流中的 reduce 相关方法基本使用的代码
*/
@Test
public void testReduce() {
int reduce = Stream.of(4, 5, 3, 9)
.reduce(0, (a, b) -> {
System.out.println("a = " + a + ", b = " + b);
return a + b;
});
// reduce:
// 第一次将默认做赋值给x, 取出第一个元素赋值给y,进行操作
// 第二次,将第一次的结果赋值给x, 取出二个元素赋值给y,进行操作
// 第三次,将第二次的结果赋值给x, 取出三个元素赋值给y,进行操作
// 第四次,将第三次的结果赋值给x, 取出四个元素赋值给y,进行操作
System.out.println("reduce = " + reduce);
int reduce2 = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> {
return Integer.sum(x, y);
});
int reduce3 = Stream.of(4, 5, 3, 9).reduce(0, Integer::sum);
int max = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> {
return x > y ? x : y;
});
System.out.println("max = " + max);
}
Stream流的map和reduce组合使用
/**
* map和reduce组合使用
*/
@Test
public void testMapReduce() {
// 求出所有年龄的总和
int totalAge = Stream.of(
new Fruits("西瓜", 12),
new Fruits("荔枝", 3),
new Fruits("粑粑柑", 4),
new Fruits("草莓", 2))
.map((p) -> p.getWeight())
.reduce(0, (x, y) -> x + y);
System.out.println("totalAge = " + totalAge);
// 找出最大年龄
int maxAge = Stream.of(
new Fruits("西瓜", 12),
new Fruits("荔枝", 3),
new Fruits("粑粑柑", 4),
new Fruits("草莓", 2))
.map((p) -> p.getWeight())
.reduce(0, (x, y) -> x > y ? x : y);
System.out.println("maxAge = " + maxAge);
// 统计 数字2 出现的次数
int count = Stream.of(1, 2, 2, 1, 3, 2)
.map(i -> {
if (i == 2) {
return 1;
} else {
return 0;
}
})
.reduce(0, Integer::sum);
System.out.println("count = " + count);
}
Stream流的mapToInt
如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法;
IntStream mapToInt(ToIntFunction<? super T> mapper);
/**
* Stream流的mapToInt
*/
@Test
public void mapToIntTest() {
// Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
// 把大于3的和打印出来
// Integer result = stream
// .filter(i -> i.intValue() > 3)
// .reduce(0, Integer::sum);
// System.out.println(result);
// 先将流中的Integer数据转成int,后续都是操作int类型
IntStream intStream = stream.mapToInt(Integer::intValue);
int reduce = intStream
.filter(i -> i > 3)
.reduce(0, Integer::sum);
System.out.println(reduce);
// 将IntStream转化为Stream<Integer>
IntStream intStream1 = IntStream.rangeClosed(1, 10);
Stream<Integer> boxed = intStream1.boxed();
boxed.forEach(s -> System.out.println(s.getClass() + ", " + s));
}
Stream流的concat方法
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
@Test
public void ContactTest() {
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
Stream<String> result = Stream.concat(streamA, streamB);
result.forEach(System.out::println);
}
Stream综合案例
package com.wh.Test8;
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/16 22:42
**/
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
* 现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下
* 若干操作步骤:
* 1. 第一个队伍只要名字为3个字的成员姓名;
* 2. 第一个队伍筛选之后只要前3个人;
* 3. 第二个队伍只要姓张的成员姓名;
* 4. 第二个队伍筛选之后不要前2个人;
* 5. 将两个队伍合并为一个队伍;
* 6. 根据姓名创建 Person 对象;
* 7. 打印整个队伍的Person对象信息
*/
public class MyStreamCase {
/**
* 传统方法
*/
@Test
public void TraditionalTest() {
List<String> one=new ArrayList<>();
Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
List<String> two=new ArrayList<>();
Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
// 第一个队伍只要名字为3个字的成员姓名;
List<String> oneA = new ArrayList<>();
for (String name : one) {
if (name.length() == 3) {
oneA.add(name);
}
}
// 第一个队伍筛选之后只要前3个人;
List<String> oneB = new ArrayList<>();
for (int i = 0; i < 3; i++) {
oneB.add(oneA.get(i));
}
// 第二个队伍只要姓张的成员姓名;
List<String> twoA = new ArrayList<>();
for (String name : two) {
if (name.startsWith("张")) {
twoA.add(name);
}
}
// 第二个队伍筛选之后不要前2个人;
List<String> twoB = new ArrayList<>();
for (int i = 2; i < twoA.size(); i++) {
twoB.add(twoA.get(i));
}
// 将两个队伍合并为一个队伍;
List<String> totalNames = new ArrayList<>();
totalNames.addAll(oneB);
totalNames.addAll(twoB);
// 根据姓名创建Person对象;
List<Person> totalPersonList = new ArrayList<>();
for (String name : totalNames) {
totalPersonList.add(new Person(name));
}
// 打印整个队伍的Person对象信息。
for (Person person : totalPersonList) {
System.out.println(person);
}
}
/**
* Stream 方法
*/
@Test
public void StreamTest(){
List<String> one=new ArrayList<>();
Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
List<String> two=new ArrayList<>();
Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
// 第一个队伍只要名字为3个字的成员姓名;
// 第一个队伍筛选之后只要前3个人;
Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
// 第二个队伍只要姓张的成员姓名;
// 第二个队伍筛选之后不要前2个人;
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);
// 将两个队伍合并为一个队伍;
// 根据姓名创建Person对象;
// 打印整个队伍的Person对象信息。
Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);
}
}
收集Stream流中的结果
IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(Integer::intValue);
intStream.filter(n -> n > 3).forEach(System.out::println);
intStream.filter(n -> n > 3).count;
intStream.filter(n -> n > 3).reduce(0, Integer::sum);
下面提供几个别的操作
实体类
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Use {
public String name;
public Integer age;
public Integer Score;
}
Stream转集合
/**
* Stream流转list和set
*/
@Test
public void StreamToCollection(){
Stream<String> stream1= Stream.of("迪丽热巴","古力娜扎","马尔扎哈","你是傻瓜");
Stream<String> stream2= Stream.of("迪丽热巴","古力娜扎","马尔扎哈","你是傻瓜");
// List<String> list = stream.collect(Collectors.toList());
// Set<String> set = stream.collect(Collectors.toSet());
ArrayList<String> arrayList = stream1.collect(Collectors.toCollection(ArrayList::new));
HashSet<String> hashSet = stream2.collect(Collectors.toCollection(HashSet::new));
arrayList.forEach(s-> System.out.println(s));
System.out.println("-----------------------------------------------");
hashSet.forEach(s-> System.out.println(s));
}
不同的collection集合针对同一个Stream操作是会报错的,例如下面的写法
@Test
public void StreamToCollection(){
Stream<String> stream= Stream.of("迪丽热巴","古力娜扎","马尔扎哈","你是傻瓜");
// List<String> list = stream.collect(Collectors.toList());
// Set<String> set = stream.collect(Collectors.toSet());
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
arrayList.forEach(s-> System.out.println(s));
System.out.println("-----------------------------------------------");
hashSet.forEach(s-> System.out.println(s));
}
控制台报错
Stream流转数组
/**
* 转数组
*/
@Test
public void StreamToArray() {
Stream<String> stream = Stream.of("迪丽热巴", "古力娜扎", "马尔扎哈", "你是傻瓜");
// Object[] objects = stream.toArray();
// for (Object obj : objects) {
// System.out.println();
// }
String[] strings = stream.toArray(String[]::new);
for (String str : strings) {
System.out.println(str);
}
}
聚合计算
/**
* 对Stream中的数据进行聚合计算
*/
@Test
public void StreamToCalculate() {
Stream<Use> StreamUse=Stream.of(
new Use("迪丽热巴", 58, 95),
new Use("古力娜扎", 56, 88),
new Use("马尔扎哈", 56, 99),
new Use("法外狂徒", 52, 77)
);
// 获取最大值
//Optional<Use> collect = StreamUse.collect(Collectors.maxBy((o1, o2) -> o1.getScore() - o2.getScore()));
// 获取最小值
//Optional<Use> collect = StreamUse.collect(Collectors.minBy((o1, o2) -> o1.getScore() - o2.getScore()));
//System.out.println(collect.get());
// 求总和
//int sumAge = StreamUse.collect(Collectors.summingInt(s -> s.getAge()));
//System.out.println("sumAge = " + sumAge);
// 平均值
//double avgScore = StreamUse.collect(Collectors.averagingInt(s -> s.getScore()));
//System.out.println("avgScore = " + avgScore);
// 统计数量
//Long count = StreamUse.collect(Collectors.counting());
//System.out.println("count = " + count);
}
分组操作
/**
*分组
*/
@Test
public void StreamToGroup(){
Stream<Use> StreamUse=Stream.of(
new Use("迪丽热巴", 58, 95),
new Use("古力娜扎", 56, 45),
new Use("马尔扎哈", 56, 99),
new Use("法外狂徒", 52, 77),
new Use("阿里哈哈", 56, 45),
new Use("无力渣渣", 56, 59),
new Use("张三没啦", 52, 77)
);
//Map<Integer, List<Use>> map =StreamUse.collect(Collectors.groupingBy(Use::getAge));
// 将分数大于60的分为一组,小于60分成另一组
Map<String,List<Use>> map=StreamUse.collect(Collectors.groupingBy(s->{
if(s.getScore()>=60){
return "及格";
}else{
return "不及格";
}
}));
map.forEach((k,v)->{
System.out.println(k+":"+v);
});
}
多级分组
/**
* 多级分组操作
*/
@Test
public void StreamToMultistageGroup() {
Stream<Use> StreamUse = Stream.of(
new Use("迪丽热巴", 58, 95),
new Use("古力娜扎", 56, 45),
new Use("马尔扎哈", 56, 99),
new Use("法外狂徒", 52, 77),
new Use("阿里哈哈", 56, 45),
new Use("无力渣渣", 56, 59),
new Use("张三没啦", 52, 77)
);
Map<Integer, Map<String, List<Use>>> map =
StreamUse.collect(Collectors.groupingBy(s -> s.getAge(), Collectors.groupingBy(s -> {
if (s.getScore() >= 90) {
return "优秀";
} else if (s.getScore() >= 80 && s.getScore() < 90) {
return "良好";
} else if (s.getScore() >= 80 && s.getScore() < 80) {
return "及格";
} else {
return "不及格";
}
})));
map.forEach((k, v) -> {
System.out.println(k + " == " + v);
});
}
分区操作
@Test
public void StreamToPartition() {
Stream<Use> StreamUse = Stream.of(
new Use("迪丽热巴", 58, 95),
new Use("古力娜扎", 56, 45),
new Use("马尔扎哈", 56, 99),
new Use("法外狂徒", 52, 77),
new Use("阿里哈哈", 56, 45),
new Use("无力渣渣", 56, 59),
new Use("张三没啦", 52, 77)
);
// partitioningBy会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表
Map<Boolean, List<Use>> map = StreamUse.collect(Collectors.partitioningBy(s -> s.getScore() > 90));
map.forEach((k, v) -> {
System.out.println(k + " == " + v);
});
}
拼接
/**
* 拼接
*/
@Test
public void Jointing() {
Stream<Use> StreamUse = Stream.of(
new Use("迪丽热巴", 58, 95),
new Use("古力娜扎", 56, 45),
new Use("马尔扎哈", 56, 99),
new Use("法外狂徒", 52, 77),
new Use("阿里哈哈", 56, 45),
new Use("无力渣渣", 56, 59),
new Use("张三没啦", 52, 77)
);
String collect = StreamUse
.map(Use::getName)
.collect(Collectors.joining(">_<", "^_^", "^v^"));
System.out.println(collect);
}
小结
收集Stream流中的结果
到集合中: Collectors.toList()/Collectors.toSet()/Collectors.toCollection()
到数组中: toArray()/toArray(int[]::new)
聚合计算:
Collectors.maxBy/Collectors.minBy/Collectors.counting/Collectors.summingInt/Collectors.averagingInt
分组: Collectors.groupingBy
分区: Collectors.partitionBy
拼接: Collectors.joinging
并行的Stream流
1.串行的Stream流
目前我们使用的Stream流是串行的,就是在一个线程上执行。
2.并行的流
parallelStream其实就是一个并行执行的流。它通过默认的ForkJoinPool,可能提高多线程任务的速度。
/**
* 并行的流
* 获取并行流的两种方式
* 1.直接获取并行的流:Stream<Integer> stream = list.parallelStream();
* 2.将串行流转成并行流:Stream<Integer> stream = list.stream().parallel();
*/
@Test
public void GetParallelStream() {
long count = Stream.of(4, 5, 3, 9, 1, 2, 6)
.parallel() // 将流转成并发流,Stream处理的时候将才去
.filter(s -> {
System.out.println(Thread.currentThread() + ", s = " + s);
return true;
})
.count();
System.out.println("count = " + count);
}
package com.wh.Test9;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.stream.LongStream;
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/17 16:49
**/
/**
* 并行和串行Stream流的效率对比
*/
public class MyStreamFour {
private static long times = 50000000000L;
private long start;
/**
* @Before的作用就是在一个类中最先执行的方法
*/
@Before
public void init() {
start = System.currentTimeMillis();
}
/**
* @After的作用就是在一个类中最后执行的方法
*/
@After
public void destroy() {
long end = System.currentTimeMillis();
System.out.println("消耗时间: " + (end - start));
} // 测试效率,parallelStream 120
@Test
public void parallelStream() {
System.out.println("serialStream");
LongStream.rangeClosed(0, times)
.parallel()
.reduce(0, Long::sum);
} // 测试效率,普通Stream 342
@Test
public void serialStream() {
System.out.println("serialStream");
LongStream.rangeClosed(0, times)
.reduce(0, Long::sum);
} // 测试效率,正常for循环 421
@Test
public void forAdd() {
System.out.println("forAdd");
long result = 0L;
for (long i = 1L; i < times; i++) {
result += i;
}
}
}
控制台:
由图可知parallelStream的效率是最高的
parallelStream线程安全问题
示例代码:
package com.wh.Test9;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.LongStream;
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/17 16:49
**/
/**
* 并行和串行Stream流的效率对比
*/
public class MyStreamFour {
private static long times = 50000000000L;
private long start;
/**
* @Before的作用就是在一个类中最先执行的方法
*/
@Before
public void init() {
start = System.currentTimeMillis();
}
/**
* @After的作用就是在一个类中最后执行的方法
*/
@After
public void destroy() {
long end = System.currentTimeMillis();
System.out.println("消耗时间: " + (end - start));
} // 测试效率,parallelStream 120
@Test
public void parallelStream() {
System.out.println("serialStream");
LongStream.rangeClosed(0, times)
.parallel()
.reduce(0, Long::sum);
} // 测试效率,普通Stream 342
@Test
public void serialStream() {
System.out.println("serialStream");
LongStream.rangeClosed(0, times)
.reduce(0, Long::sum);
} // 测试效率,正常for循环 421
@Test
public void forAdd() {
System.out.println("forAdd");
long result = 0L;
for (long i = 1L; i < times; i++) {
result += i;
}
}
// 并行流注意事项
@Test
public void parallelStreamNotice() {
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
List<Integer> newList = new ArrayList<>();
// 使用并行的流往集合中添加数据
list.parallelStream()
.forEach(s -> {
newList.add(s);
});
System.out.println("newList = " + newList.size());
}
}
技术介绍
1.Fork/Join框架介绍
parallelStream使用的是Fork/Join框架。Fork/Join框架自JDK 7引入。Fork/Join框架可以将一个大任务拆分为很多小
任务来异步执行。 Fork/Join框架主要包含三个模块:
- 线程池:ForkJoinPool
- 任务对象:ForkJoinTask
- 执行任务的线程:ForkJoinWorkerThread
2.Fork/Join原理-分治法
ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法,
ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成
两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处
理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停
止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在
于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。
3.Fork/Join原理-工作窃取算法
Fork/Join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的cpu,那么如何利用好这个空闲的
cpu就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个Fork/Join框架的核心理念
Fork/Join工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。
那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖
的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来
执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的
任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就
去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任
务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永
远从双端队列的尾部拿任务执行。
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,
比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
上文中已经提到了在Java 8引入了自动并行化的概念。它能够让一部分Java代码自动地以并行的方式执行,也就是我
们使用了ForkJoinPool的ParallelStream。
对于ForkJoinPool通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置
系统属性:java.util.concurrent.ForkJoinPool.common.parallelism=N (N为线程数量),来调整ForkJoinPool的线
程数量,可以尝试调整成不同的参数来观察每次的输出结果。
package com.wh.Test9;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/17 17:49
**/
public class MyStreamSix {
public static void main(String[] args) {
long start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
SumRecursiveTask task = new SumRecursiveTask(1, 10000L);
Long result = pool.invoke(task);
System.out.println("result = " + result);
long end = System.currentTimeMillis();
System.out.println("消耗的时间为: " + (end - start));
}
}
class SumRecursiveTask extends RecursiveTask<Long> {
private static final long THRESHOLD = 3000L;
private final long start;
private final long end;
public SumRecursiveTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THRESHOLD) {
// 任务不用再拆分了.可以计算了
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
System.out.println("计算: " + start + " -> " + end + ",结果为: " + sum);
return sum;
} else {
// 数量大于预定的数量,任务还需要再拆分
long middle = (start + end) / 2;
System.out.println("拆分: 左边 " + start + " -> " + middle + ", 右边 " + (middle +
1) + " -> " + end);
SumRecursiveTask left = new SumRecursiveTask(start, middle);
left.fork();
SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
小结;
- parallelStream是线程不安全的
- parallelStream适用的场景是CPU密集型的,只是做到别浪费CPU,假如本身电脑CPU的负载很大,那还到处用
并行流,那并不能起到作用 - I/O密集型 磁盘I/O、网络I/O都属于I/O操作,这部分操作是较少消耗CPU资源,一般并行流中不适用于I/O密集
型的操作,就比如使用并流行进行大批量的消息推送,涉及到了大量I/O,使用并行流反而慢了很多 - 在使用并行流的时候是无法保证元素的顺序的,也就是即使你用了同步集合也只能保证元素都正确但无法保证
其中的顺序
新的日期和时间API
介绍:
JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包
中,下面是一些关键类。
LocalDate :表示日期,包含年月日,格式为 2019-10-16
LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
DateTimeFormatter :日期时间格式化类。
Instant:时间戳,表示一个特定的时间瞬间。
Duration:用于计算2个时间(LocalTime,时分秒)的距离
Period:用于计算2个日期(LocalDate,年月日)的距离
ZonedDateTime :包含时区的时间
JDK8的日期和时间类
示例代码:
package com.wh.Test9;
import org.junit.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/17 21:51
**/
public class MyDateAndTimeClass {
/**
* LocalDate:获取日期时间的信息。格式为 2019-10-16
*/
@Test
public void FirstTest() {
// 创建指定日期
LocalDate AppointedDate = LocalDate.of(1985, 9, 23);
System.out.println("AppointedDate= " + AppointedDate); // 1985-09-23
// 得到当前日期
LocalDate nowDate = LocalDate.now();
System.out.println("nowDate = " + nowDate); // 2019-10-16
// 获取日期信息
System.out.println("年: " + nowDate.getYear());
System.out.println("月: " + nowDate.getMonthValue());
System.out.println("日: " + nowDate.getDayOfMonth());
System.out.println("星期: " + nowDate.getDayOfWeek());
}
/**
* LocalTime类: 获取时间信息。格式为 16:38:54.158549300
*/
@Test
public void SecondTest() {
// 得到指定的时间
LocalTime time = LocalTime.of(12,15, 28, 129_900_000);
System.out.println("time = " + time);
// 得到当前时间
LocalTime nowTime = LocalTime.now();
System.out.println("nowTime = " + nowTime);
// 获取时间信息
System.out.println("小时: " + nowTime.getHour());
System.out.println("分钟: " + nowTime.getMinute());
System.out.println("秒: " + nowTime.getSecond());
System.out.println("纳秒: " + nowTime.getNano());
}
/**
* LocalDateTime类: 获取日期时间信息。格式为 2018-09-06T15:33:56.750
*/
@Test
public void ThirdTest(){
LocalDateTime fj = LocalDateTime.of(1985, 9, 23, 9, 10, 20);
System.out.println("fj = " + fj); // 1985-09-23T09:10:20
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // 2019-10-16T16:42:24.497896800
System.out.println(now.getYear());
System.out.println(now.getMonthValue());
System.out.println(now.getDayOfMonth());
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
}
/**
* 对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
* withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对
* 象,他们不会影响原来的对象。
* LocalDateTime类: 对日期时间的修改
*/
@Test
public void FourthTest(){
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
// 修改日期时间
LocalDateTime setYear = now.withYear(2078);
System.out.println("修改年份: " + setYear);
System.out.println("now == setYear: " + (now == setYear));
System.out.println("修改月份: " + now.withMonth(6));
System.out.println("修改小时: " + now.withHour(9));
System.out.println("修改分钟: " + now.withMinute(11));
// 再当前对象的基础上加上或减去指定的时间
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("5天后: " + localDateTime);
System.out.println("now == localDateTime: " + (now == localDateTime));
System.out.println("10年后: " + now.plusYears(10));
System.out.println("20月后: " + now.plusMonths(20));
System.out.println("20年前: " + now.minusYears(20));
System.out.println("5月前: " + now.minusMonths(5));
System.out.println("100天前: " + now.minusDays(100));
}
/**
* 日期时间的比较
*/
@Test
public void fifthTest(){
// 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2018, 8, 8);
System.out.println(now.isBefore(date)); // false
System.out.println(now.isAfter(date)); // true
}
}
JDK8的时间格式化与解析
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化
/**
* 日期格式化
*/
@Test
public void SixthTest(){
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 将日期时间格式化为字符串
String format = now.format(formatter);
System.out.println("format = " + format);
// 将字符串解析为日期时间
LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", formatter);
System.out.println("parse = " + parse);
}
JDK8的Instant类
/**
* 时间戳
*/
@Test
public void SeventhTest(){
Instant now = Instant.now();
System.out.println("当前时间戳 = " + now);
// 获取从1970年1月1日 00:00:00的秒
System.out.println(now.getNano());
System.out.println(now.getEpochSecond());
System.out.println(now.toEpochMilli());
System.out.println(System.currentTimeMillis());
Instant instant = Instant.ofEpochSecond(5);
System.out.println(instant);
}
JDK 8的计算日期时间差类
/**
* Duration/Period类: 计算日期时间差。
* 1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
* 2. Period:用于计算2个日期(LocalDate,年月日)的距离
*/
@Test
public void EighthTest(){
// Duration计算时间的距离
LocalTime now = LocalTime.now();
LocalTime time = LocalTime.of(14, 15, 20);
Duration duration = Duration.between(time, now);
System.out.println("相差的天数:" + duration.toDays());
System.out.println("相差的小时数:" + duration.toHours());
System.out.println("相差的分钟数:" + duration.toMinutes());
//System.out.println("相差的秒数:" + duration.toSeconds());//JDK9
// Period计算日期的距离
LocalDate nowDate = LocalDate.now();
LocalDate date = LocalDate.of(1998, 8, 8);
// 让后面的时间减去前面的时间
Period period = Period.between(date, nowDate);
System.out.println("相差的年:" + period.getYears());
System.out.println("相差的月:" + period.getMonths());
System.out.println("相差的天:" + period.getDays());
}
JDK8的时间校正器
/**
* 有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
* TemporalAdjuster : 时间校正器。
* TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现
*/
@Test
public void NinthTest(){
LocalDateTime now = LocalDateTime.now();
// 得到下一个月的第一天
TemporalAdjuster firsWeekDayOfNextMonth = temporal -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
System.out.println("nextMonth = " + nextMonth);
return nextMonth;
};
LocalDateTime nextMonth = now.with(firsWeekDayOfNextMonth);
System.out.println("nextMonth = " + nextMonth);
}
JDK8设置日期时间的时区
/**
* Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别
* 为:ZonedDate、ZonedTime、ZonedDateTime。
* 其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
* ZoneId:该类中包含了所有的时区信息。
*/
@Test
public void TenthTest(){
// 1.获取所有的时区ID
// ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 不带时间,获取计算机的当前时间
LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
System.out.println("now = " + now);
// 2.操作带时区的类
// now(Clock.systemUTC()): 创建世界标准时间
ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
System.out.println("bz = " + bz);
// now(): 使用计算机的默认的时区,创建日期时间
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println("now1 = " + now1); // 2019-10-19T16:19:44.007153500+08:00[Asia/Shanghai]
// 使用指定的时区创建日期时间
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
System.out.println("now2 = " + now2); // 2019-10-19T01:21:44.248794200-07:00[America/Vancouver]
}
JDK 8新的日期和时间 API的优势:
1. 新版的日期和时间API中,日期和时间对象是不可变的。操纵的日期不会影响老值,而是新生成一个实例。
2. 新的API提供了两种不同的时间表示方式,有效地区分了人和机器的不同需求。
3. TemporalAdjuster可以更精确的操纵日期,还可以自定义日期调整器。
4. 是线程安全的
JDK8重复注解与类型注解
1.定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests {
MyTest[] value();
}
2.定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
String value();
}
- 配置多个重复的注解
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
}
}
4.解析得到指定注解
// 3.配置多个重复的注解
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@Test
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
// 4.解析得到类上的指定注解
MyTest[] tests = Demo01.class.getAnnotationsByType(MyTest.class);
for (MyTest test : tests) {
System.out.println(test.value());
} /
/ 得到方法上的指定注解
Annotation[] tests1 =
Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (Annotation annotation : tests1) {
System.out.println("annotation = " + annotation);
}
}
}
类型注解的使用
JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: 、
TYPE_USE :表示注解可以再任何用到类型的地方使用。
TYPE_PARAMETER的使用
@Target(ElementType.TYPE_PARAMETER)
@interface TyptParam {
}
public class Demo02<@TyptParam T> {
public static void main( String[] args) {
}
public <@TyptParam E> void test( String a) {
}
}
TYPE_USE的使用
@Target(ElementType.TYPE_USE)
@interface NotNull {
}
p
ublic class Demo02<@TyptParam T extends String> {
private @NotNull int a = 10;
public static void main(@NotNull String[] args) {
@NotNull int x = 1;
@NotNull String s = new @NotNull String();
} p
ublic <@TyptParam E> void test( String a) {
}
}
接口的默认方法和静态方法
JDK8以前的接口:
interface 接口名{
静态常量;
抽象方法;
}
JDK8的接口
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法;
}
接口默认方法的定义格式
interface 接口名{
修饰符 default 返回值类型 方法名(){
代码;
}
}
接口默认方法的使用
方式一:实现类直接调用接口默认方法
方式二:实现类重写接口默认方法
示例代码如下;
public class UseDefaultFunctionDemo1 {
public static void main(String[] args) {
Function2 f2= new Function2();
//方式一:实现类直接调用接口默认方法
f2.test2();
Function3 f3= new Function3();
//调用实现类重写接口默认方法
f3.test2();
}
interface Function{
public abstract void test1();
public default void test2(){
System.out.println("Function:test2");
}
}
static class Function2 implements Function{
@Override
public void test1() {
System.out.println("Function2:test1");
}
}
static class Function3 implements Function{
@Override
public void test1() {
System.out.println("Function3:test1");
}
//方式二:实现类重写接口默认方法
@Override
public void test2() {
System.out.println("Function3实现类重写接口默认方法");
}
}
}
接口静态方法定义格式
interface 接口名{
修饰符 static 返回值类型 方法名(){
代码;
}
}
示例代码如下:
public class UseDefaultFunctionDemo2 {
public static void main(String[] args) {
//直接使用接口名调用即可:接口名.静态方法名();
Function.test1();
}
interface Function{
public static void test1(){
System.out.println("Function 接口的静态方法");
}
}
static class Function1 implements Function{
/* @Override 静态方法不能重写
public static void test01() {
System.out.println("AAA 接口的静态方法");
}*/
}
}
接口默认方法和静态方法的区别
1. 默认方法通过实例调用,静态方法通过接口名调用。
2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。
常用内置函数式接口
示例代码如下
public class UseFunctionalInterfaceDemo3 {
public static void main(String[] args) {
// 调用函数式接口中的方法
method((arr) -> {
int sum = 0;
for (int n : arr) {
sum += n;
}
return sum;
});
}
/**
* 使用自定义的函数式接口作为方法参数
*/
public static void method(Operator op) {
int[] arr = {1, 2, 3, 4};
int sum = op.getSum(arr);
System.out.println("sum = " + sum);
}
} @
FunctionalInterface
interface Operator {
public abstract int getSum(int[] arr);
}
常用内置函数式接口介绍
1.Supplier接口
@FunctionalInterface
public interface Supplier<T> {
public abstract T get();
}
供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回的接口。
示例代码如下:
public class SupplierDemo4 {
public static void main(String[] args) {
printMax(() -> {
int[] arr = {110, 120, 119, 12345, 40, 50};
// 先排序,最后就是最大的
Arrays.sort(arr);
return arr[arr.length - 1]; // 最后就是最大的
});
}
private static void printMax(Supplier<Integer> supplier){
int max = supplier.get();
System.out.println("max = " + max);
}
}
2.Consumer接口
@FunctionalInterface
public interface Consumer<T> {
public abstract void accept(T t);
}
示例代码如下;
import java.util.function.Consumer;
public class ConsumerDemo5 {
public static void main(String[] args) {
// Lambda表达式
test((String s)->{
System.out.println(s.toLowerCase());
}, (String s) -> {
System.out.println(s.toUpperCase());
});
// Lambda表达式简写
test(s -> System.out.println(s.toLowerCase()), s ->
System.out.println(s.toUpperCase()));
}
public static void test(Consumer<String> c1,Consumer<String> c2) {
String str="Welcome to wh";
//c1.accept(str);//转小写
//c2.accept(str);//转大写
c1.andThen(c2).accept(str);
System.out.println("--------------------");
c2.andThen(c1).accept(str);
}
}
3.Function接口
@FunctionalInterface
public interface Function<T, R> {
public abstract R apply(T t);
}
示例代码如下;
import java.util.function.Function;
public class FunctionDemo6 {
public static void main(String[] args) {
// Lambda表达式
test((String s) -> {
return Integer.parseInt(s);
}, (Integer i) -> {
return i * 10;
});
}
public static void test(Function<String, Integer> f1, Function<Integer, Integer> f2) {
// Integer in = f1.apply("66"); // 将字符串解析成为int数字
// Integer in2 = f2.apply(in);// 将上一步的int数字乘以10
Integer in3 = f1.andThen(f2).apply("66");
System.out.println("in3: " + in3); // 660
}
}
4.Predicate接口
@FunctionalInterface
public interface Predicate<T> {
public abstract boolean test(T t);
}
Predicate接口用于做判断,返回boolean类型的值
示例代码如下:
import java.util.function.Predicate;
/**
* 一个人的名字长度是否超过三个字就认为是很长的名字
*/
public class PredicateDemo6 {
public static void main(String[] args) {
test(s -> s.length()>3,"TheShy");
}
private static void test(Predicate<String> predicate, String str){
boolean veryLong = predicate.test(str);
System.out.println("名字很长?:" + veryLong);
}
}
介绍方法引用
Lambda的冗余场景
import org.junit.Test;
import java.util.function.Consumer;
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/17 23:50
**/
/**
* 使用lambda表达式求一个数组的和
*/
public class MyLambda {
public static void main(String[] args) {
printMax((int[] arr) -> {
int sum = 0;
for (int i : arr) {
sum += i;
}
System.out.println(sum);
});
}
private static void printMax(Consumer<int[]> consumer) {
int[] arr = {10, 20, 30, 40, 50};
consumer.accept(arr);
}
public static void getMax(int[] arr) {
int sum = 0;
for (int n : arr) {
sum += n;
}
System.out.println(sum);
}
}
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/18 11:45
**/
import java.util.function.Consumer;
/**
* 简化版
*/
public class MyLambdaSimple {
public static void getMax(int[] arr) {
int sum = 0;
for (int n : arr) {
sum += n;
}
System.out.println(sum);
}
/**
* 请注意其中的双冒号 :: 写法,这被称为“方法引用”,是一种新的语法。
* @param args
*/
public static void main(String[] args) {
printMax(MyLambdaTest::getMax);
}
private static void printMax(Consumer<int[]> consumer) {
int[] arr = {10, 20, 30, 40, 50};
consumer.accept(arr);
}
}
方法引用的格式
符号表示 ::
符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。
常见引用方式
方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
1. instanceName::methodName 对象::方法名
2. ClassName::staticMethodName 类名::静态方法
3. ClassName::methodName 类名::普通方法
4. ClassName::new 类名::new 调用的构造器
5. TypeName[]::new String[]::new 调用数组的构造器
示例代码
package com.wh.Test10;
import org.junit.Test;
import java.util.Date;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @Version 1.0
* @Author: swy
* @Date 2022/5/18 12:02
**/
public class MyClassName {
/**
* 对象名::引用成员方法
* 方法引用的注意事项
* 1. 被引用的方法,参数要和接口中抽象方法的参数一样
* 2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值
*/
@Test
public void FirstTest(){
Date now = new Date();
Supplier<Long> supp = () -> {
return now.getTime();
};
System.out.println(supp.get());
Supplier<Long> supp2 = now::getTime;
System.out.println(supp2.get());
}
/**
* 类名::引用静态方法
*/
@Test
public void SecondTest(){
Supplier<Long> supp = () -> {
return System.currentTimeMillis();
};
System.out.println(supp.get());
Supplier<Long> supp2 = System::currentTimeMillis;
System.out.println(supp2.get());
}
/**
* 类名::引用实例方法
*/
@Test
public void ThirdTest(){
Function<String, Integer> f1 = (s) -> {
return s.length();
};
System.out.println(f1.apply("abc"));
Function<String, Integer> f2 = String::length;
System.out.println(f2.apply("abc"));
BiFunction<String, Integer, String> bif = String::substring;
String hello = bif.apply("hello", 2);
System.out.println("hello = " + hello);
}
/**
* 类名::new引用构造器
*/
@Test
public void FourthTest(){
Supplier<Person> sup = () -> {
return new Person();
};
System.out.println(sup.get());
Supplier<Person> sup2=Person::new;
System.out.println(sup2.get());
//BiFunction<String,Integer,Person> fun2=Person::new;
//System.out.println(fun2.apply("张三", 18));
}
/**
* 数组::new引用数组构造器
*
*/
@Test
public void FifthTest(){
Function<Integer, String[]> fun = (len) -> {
return new String[len];
};
String[] arr1 = fun.apply(10);
System.out.println(arr1 + ", " + arr1.length);
Function<Integer, String[]> fun2 = String[]::new;
String[] arr2 = fun.apply(5);
System.out.println(arr2 + ", " + arr2.length);
}
}
MySQL
MongoDB
Redis
微服务
1.Springboot 简介
Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的开发过程。
Spring Boot 让我们的 Spring 应用变的更轻量化。 比如: 你可以仅仅依靠一个 Java 类来运行一个 Spring 应用。 你也可以打包你的应用为 jar 并通过使用 java -jar 来运行你的 Spring Web 应用。
Spring Boot 的主要优点:
(1)为所有 Spring 开发者更快的入门
(2)简化项目配置
(3)内嵌式容器简化 Web 项目
(4)springboot 整合的框架统一管理版本,不会存在版本冲突
(5)springcloud 基于 springboot 开发
Spring Boot 是微服务中最好的 Java 框架
2.Spring boot 自动配置原理
Spring Boot 关于自动配置的源码在 spring-boot-autoconfigure-x.x.x.x.jar 中:
当然, 自动配置原理的相关描述, 官方文档貌似是没有提及。 不过我们不难猜出, Spring Boot 的启动类上有一个@SpringBootApplication 注解, 这个注解是 Spring Boot 项目必不可少的注解。 那么自动配置原理一定和这个注解有着千丝万缕的联系!
@SpringBootApplication 是 一 个 复 合 注 解 或 派 生 注 解 , 在 @SpringBootApplication 中 有 一 个 注 解
@EnableAutoConfiguration,翻译成人话就是开启自动配置,其定义如下:
而这个注解也是一个派生注解,其中的关键功能由@Import 提供,其导入的 AutoConfigurationImportSelector 的
selectImports()方法通过 SpringFactoriesLoader.loadFactoryNames()扫描所有具有 META-INF/spring.factories 的 jar
包。spring-boot-autoconfigure-x.x.x.x.jar 里就有一个这样的 spring.factories 文件。
这个 spring.factories 文件也是一组一组的 key=value 的形式,其中一个 key 是 EnableAutoConfiguration 类的全类
名,而它的 value 是一个 xxxxAutoConfiguration 的类名的列表,这些类名以逗号分隔,如下图所示:
这个@EnableAutoConfiguration 注解通过@SpringBootApplication 被间接的标记在了 Spring Boot 的启动类上。在SpringApplication.run(…)的内部就会执行 selectImports()方法,找到所有 JavaConfig 自动配置类的全限定名对应的 class,然后将所有自动配置类加载到 Spring 容器中。
自动配置生效:
每一个 XxxxAutoConfiguration 自动配置类都是在某些条件之下才会生效的,这些条件的限制在 Spring Boot 中以注解的形式体现,常见的条件注解有如下几项:
@ConditionalOnBean:当容器里有指定的 bean 的条件下。
@ConditionalOnMissingBean:当容器里不存在指定 bean 的条件下。
@ConditionalOnClass:当类路径下有指定类的条件下。
@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
@ConditionalOnProperty : 指 定 的 属 性 是 否 有 指 定 的 值 , 比 如 @ConditionalOnProperties(prefix=”xxx.xxx”,
value=”enable”, matchIfMissing=true),代表当 xxx.xxx 为 enable 时条件的布尔值为 true,如果没有设置的情况下也为 true。
以 ServletWebServerFactoryAutoConfiguration 配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081 , 是 如 何 生 效 的 ( 当 然 不 配 置 也 会 有 默 认 值 , 这 个 默 认 值 来 自 于org.apache.catalina.startup.Tomcat)。
在 ServletWebServerFactoryAutoConfiguration 类上,有一个@EnableConfigurationProperties 注解:开启配置属性,而它后面的参数是一个 ServerProperties 类,这就是习惯优于配置的最终落地点。
在这个类上,我们看到了一个非常熟悉的注解:@ConfigurationProperties,它的作用就是从配置文件中绑定属性到对应的 bean 上,而@EnableConfigurationProperties 负责导入这个已经绑定了属性的 bean 到 spring 容器中(见上面截图)。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些 XxxxProperties 类,它与配置文件中定义的 prefix 关键字开头的一组属性是唯一对应的。至此,我们大致可以了解。在全局配置的属性如:server.port 等,通过@ConfigurationProperties 注解,绑定到对应的 XxxxProperties 配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties 注解导入到 Spring容器中。而诸多的 XxxxAutoConfiguration 自动配置类,就是 Spring 容器的 JavaConfig 形式,作用就是为 Spring 容器导入 bean,而所有导入的 bean 所需要的属性都通过 xxxxProperties 的 bean 来获得。
可能到目前为止还是有所疑惑,但面试的时候,其实远远不需要回答的这么具体,你只需要这样回答:
Spring Boot 启动的时候会通过@EnableAutoConfiguration 注解找到 META-INF/spring.factories 配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以 AutoConfiguration 结尾来命名的,它实际上就是一个JavaConfig 形式的 Spring 容器配置类,它能通过以 Properties 结尾命名的类中取得在全局配置文件中配置的属性如server.port,而 XxxxProperties 类是通过@ConfigurationProperties 注解与全局配置文件中对应的属性进行绑定的。
3.springboot 常见面试题
什么是自动配置?
Spring 和 SpringMVC 的问题在于需要配置大量的参数。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/views/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<mvc:resources mapping="/webjars/**" location="/webjars/"/>
我们能否带来更多的智能?当一个 MVC JAR 添加到应用程序中的时候,我们能否自动配置一些 beans?
Spring 查看(CLASSPATH 上可用的框架)已存在的应用程序的配置。在此基础上,Spring Boot 提供了配置应用程序和框架所需要的基本配置。这就是自动配置。
Spring 是如何快速创建产品就绪应用程序的?
Spring Boot 致力于快速产品就绪应用程序。为此,它提供了一些譬如高速缓存,日志记录,监控和嵌入式服
务器等开箱即用的非功能性特征。
spring-boot-starter-actuator - 使用一些如监控和跟踪应用的高级功能
spring-boot-starter-undertow, spring-boot-starter-jetty, spring-boot-starter-tomcat - 选择您的特定嵌入式
Servlet 容器
spring-boot-starter-logging - 使用 logback 进行日志记录
spring-boot-starter-cache - 启用 Spring Framework 的缓存支持
注:Spring Boot 2.0 需要 Java8 或者更新的版本。Java6 和 Java7 已经不再支持。
推荐阅读:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M1-Release-Notes
创建一个 Spring Boot Project 的最简单的方法是什么?
Spring Initializr 是启动 Spring Boot Projects 的一个很好的工具。
就像上图中所展示的一样,我们需要做一下几步:
登录 Spring Initializr,按照以下方式进行选择:
选择 com.in28minutes.springboot 为组
选择 studet-services 为组件
选择下面的依赖项
Web
Actuator
DevTools
点击生 GenerateProject
将项目导入 Eclipse。文件 - 导入 - 现有的 Maven 项目
Spring Initializr 是创建 Spring Boot Projects 的唯一方法吗?
不是的。
Spring Initiatlizr 让创建 Spring Boot 项目变的很容易,但是,你也可以通过设置一个 maven 项目并添加正确
的依赖项来开始一个项目。
在我们的 Spring 课程中,我们使用两种方法来创建项目。
第一种方法是 start.spring.io 。
另外一种方法是在项目的标题为“Basic Web Application”处进行手动设置。
手动设置一个 maven 项目
这里有几个重要的步骤:
在 Eclipse 中,使用文件 - 新建 Maven 项目来创建一个新项目
添加依赖项。
添加 maven 插件。
添加 Spring Boot 应用程序类。
到这里,准备工作已经做好!
为什么我们需要 spring-boot-maven-plugin?
spring-boot-maven-plugin 提供了一些像 jar 一样打包或者运行应用程序的命令。
spring-boot:run 运行你的 SpringBooty 应用程序。
spring-boot:repackage 重新打包你的 jar 包或者是 war 包使其可执行
spring-boot:start 和 spring-boot:stop 管理 Spring Boot 应用程序的生命周期(也可以说是为了集成测试)。
spring-boot:build-info 生成执行器可以使用的构造信息。
什么是嵌入式服务器?我们为什么要使用嵌入式服务器呢?
思考一下在你的虚拟机上部署应用程序需要些什么。
第一步: 安装 Java
第二部: 安装 Web 或者是应用程序的服务器(Tomat/Wbesphere/Weblogic 等等)
第三部: 部署应用程序 war 包
如果我们想简化这些步骤,应该如何做呢?
让我们来思考如何使服务器成为应用程序的一部分?
你只需要一个安装了 Java 的虚拟机,就可以直接在上面部署应用程序了,
是不是很爽?
这个想法是嵌入式服务器的起源。
当我们创建一个可以部署的应用程序的时候,我们将会把服务器(例如,tomcat)嵌入到可部署的服务器中。
例如,对于一个 Spring Boot 应用程序来说,你可以生成一个包含 Embedded Tomcat 的应用程序 jar。你就可以想运行正常 Java 应用程序一样来运行 web 应用程序了。
嵌入式服务器就是我们的可执行单元包含服务器的二进制文件(例如,tomcat.jar)。
我们能否在 spring-boot-starter-web 中用 jetty 代替 tomcat?
在 spring-boot-starter-web 移除现有的依赖项,并把下面这些添加进去。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
如何使用 Spring Boot 生成一个 WAR 文件?
1.第一步:提供一个 SpringBootServletInitializer 的子类并重写 configure 方法。Spring Framework 的 Servlet 3.0 支持并允许您在由 Servlet 容器启动时配置您的应用程序。通常,您需要更新您的应用程序的主类去继承
SpringBootServletInitializer:
2.第二步:您需要修改你的 pom.xml 配置文件,以便于您的项目生成的是一个 war 文件,而不是 jar 文件。如果您使用的是 Maven 并且使用了 spring-boot-starter-parent(它会配置 Maven 的 war 插件给您):<packaging>war</packaging>
注:如果您是用的是Gradle,你需要修改build.gradle将war插件应用于项目:apply plugin:'war'
最后一步:确保嵌入的Servlet容器不会干扰war文件将部署的servlet容器,为此,需要对嵌入的servlet容器进行标记,并提供其依赖项
<dependencies>
<dependercy>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependercy>
</dependencies>
我们如何连接一个像 MSSQL 或者 orcale 一样的外部数据库?
让我们以 MySQL 为例来思考这个问题:
第一步 - 把 mysql 连接器的依赖项添加至 pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
第二步 - 从 pom.xml 中移除 H2 的依赖项或者至少把它作为测试的范围。
<!--
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
-->
第三步 - 安装你的 MySQL 数据库
更多的来看看这里 -https://github.com/in28minutes/jpa-with-hibernate#installing-and-setting-up-mysql
第四步 - 配置你的 MySQL 数据库连接
配置 application.properties
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mysql://localhost:3306/todo_example
spring.datasource.username=todouser
spring.datasource.password=YOUR_PASSWORD
第五步 - 重新启动,你就准备好了!
就是这么简单!