1、Lambda表达式
1.1、概述
首先,要想明白Lambda表达式就要先明白函数式接口,所以,咱们先来了解一下什么是函数式接口吧!
所谓函数式接口就是有且仅有一个抽象方法的接口
函数式接口就是适用于函数式编程场景的接口,java中的函数式编程的体现就是lambda!所以函数式接口 就是可以适用于Lambda使用的接口。
只有当接口中有且只有一个抽象方法的时候,Java中的lambda表达式才能顺利推导!
也就是说在Java中使用Lambda表达式必须符合函数式接口的规范
所以,使用Lambda接口的前提是:
(1)Lambda关联的接收对象必须是函数式接口。(也就是说方法的形参必须是接口)
(2)这个接口只能有一个抽象方法(函数式接口的规范)
1.2、函数式接口
代码语言:javascript复制/*
* 函数式接口:有且只有一个抽象方法!
* @FunctionalInterface:用来检测该接口中是否是只有一个抽象方法,如果不止一个就报错!
* */
@FunctionalInterface
public interface FunctionInter {
public void method();
}
1.3、Lambda表达式和匿名内部类
Lambda表达式“本质上”是一个匿名内部类,只是二者体现形式不一样,但可以把Lambda表达式当做匿名内部类来理解!
1.3.1、匿名内部类
匿名内部类就是某个实现了接口的子类对象
不用匿名内部类
代码语言:javascript复制//未用匿名内部类
public class MyComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
}
代码语言:javascript复制public static void main(String[] args) {
Comparator c = new MyComparator();//实现比较器接口,创建对象
TreeSet<Integer> set = new TreeSet<Integer>(c);
}
使用匿名内部类
代码语言:javascript复制public static void main(String[] args) {
Comparator c = new MyComparator();//实现比较器接口,创建对象
TreeSet<Integer> set = new TreeSet<Integer>(c);
//使用匿名内部类
Comparator c2 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
};//匿名内部类实现比较器接口
TreeSet<Integer> set2 = new TreeSet<Integer>(c2);
}
1.3.2、Lambda表达式
代码语言:javascript复制//超简洁
TreeSet<Integer> set3 = new TreeSet<Integer>((o1,o2) ->o1-o2);
从上面的代码对比中,大家可以发现,lambda表达式真的超简洁!
1.4、Lambda表达式详解
1.4.1、Lambda表达式的标准写法
代码语言:javascript复制//可以没有形参,如果有多个形参,那么用“,”隔开
//方法体和形参之间用“->”连接
(参数类型 形参1,参数类型 形参2)->{
方法体;
}
代码演示一(无参无返回值)
代码语言:javascript复制public interface FlyAble {
public void fly();
}
代码语言:javascript复制public class Test {
public static void main(String[] args) {
//匿名内部类的写法
rocket(new FlyAble() {
@Override
public void fly() {
System.out.println("i can fly!--匿名内部类");
}
});
//lambda表达式的写法
rocket(()->{
System.out.println("i can fly!--Lambda表达式");
});
}
public static void rocket(FlyAble f){
f.fly();
}
}
lambda表达式的本质就是重写接口中的方法
代码演示一(有参有返回值)
代码语言:javascript复制public interface FlyAble {
public int fly(String demo2);
}
代码语言:javascript复制 public static void main(String[] args) {
//匿名内部类的写法
rocket(new FlyAble() {
@Override
public int fly(String name) {
System.out.println("i can fly!--匿名内部类");
return 30;//分行高度
}
},"小鸟");
//lambda表达式的写法
rocket((String name)->{
System.out.println("i can fly!--Lambda表达式");
return 10000;
},"飞机");
}
public static void rocket(FlyAble f,String name){
int height = f.fly(name);
System.out.println(name "的分行高度是:" height);
}
1.4.2、Lambda表达式的省略写法
(1)小括号内参数的类型可以省略
(2)如果小括号内有且仅有一个参数,则小括号可以省略
(3)如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
例如:
代码语言:javascript复制public interface FlyAble {
public void fly(String name);
}
代码语言:javascript复制public class Test {
public static void main(String[] args) {
//lambda表达式的写法
rocket(name->System.out.println(name "can fly!--Lambda表达式"));
}
public static void rocket(FlyAble f){
f.fly("bird");
}
}
2、JDK8接口的方法增强
2.1、概述
JDK1.8之前,接口中允许出现的成员有静态常量、抽象方法
代码语言:javascript复制//JDK1.8以前
public interface InterA {
//静态常量
//抽象方法
}
JDK1.8之前只允许接口中出现抽象方法,但是在实际的使用过程中,发现这样会影响接口的扩展性。例如:当往一个接口中添加新的抽象方法时,原来实现该接口的类都会报错!这样就显得“牵一发而动全身”!
为了解决这一弊端,JDK在1.8版本中,对接口的功能进行了扩展!
JDK1.8之后,接口中允许出现的成员有静态常量、抽象方法、默认方法、静态方法
代码语言:javascript复制//JDK1.8以后
public interface InterB {
//静态常量
//抽象方法
//默认方法
//静态方法
}
2.2、JDK1.8接口新增方法种类
- 默认方法
- 静态方法
2.3、默认方法的定义和使用
2.3.1、默认方法的定义
默认方法定义在接口中
他是个有方法体的方法
他定义的关键字是default
default出现的位置在方法返回值类型前面
定义格式如下:
代码语言:javascript复制interface InterA {
public void methodA();
//默认方法
public default void methodDef(){
//功能代码
}
}
2.3.2、默认方法的使用
(1)直接用
(2)重写
代码语言:javascript复制interface InterA {
public void methodA();
public default void methodDef(){
System.out.println("default ... method");
}
}
//直接用
class A implements InterA{
@Override
public void methodA() {
}
}
//重写
class B implements InterA{
@Override
public void methodA() {
}
@Override
public void methodDef() {
System.out.println("B ...default ... method");
}
}
class Test{
public static void main(String[] args) {
InterA a = new A();
a.methodDef();//直接用
InterA b = new B();
b.methodDef();//重写
}
}
运行结果:
2.4、静态方法的定义和使用
2.4.1、静态方法的定义
静态方法定义在接口中
他是个有方法体的静态方法
定义格式如下:
代码语言:javascript复制interface InterD {
//定义静态方法
public static void methodSta(){
//方法体
}
public void methodA();
public default void methodDef(){
System.out.println("default ... method");
}
}
从上面可以看出,接口中静态方法的定义和普通类中的静态方法的定义没啥区别!
2.4.2、静态方法的使用
注意:接口中的静态方法只能通过接口名调用
2.5、接口中静态方法和默认方法的区别
1、默认方法通过实例调用,静态方法通过接口名调用
2、默认方法可以被继承,可以被重写
3、静态方法不能被继承,不能被重写,只能使用接口名调用静态方法
3、JDK提供的常用内置函数式接口
3.1、为什么JDK要提供这些常用内置函数接口?
因为Lambda表达式不关心接口名称,只关心接口的形参列表及返回值,所以为了方便我们使用Lambda表达式进行编程(函数式编程),JDK就提供了大量形参列表不同,返回值不同的函数式接口!
3.2、常用的函数式接口如下
(1)Supplier接口:供给型接口
(2)Consumer接口:消费型接口
(3)Function接口:转换型接口
(4)Predicate接口:判断型接口
3.2.1、Supplier接口:供给型接口
代码语言:javascript复制@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
Supplier接口,他可以完成供给的功能,对应的lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
供给型接口:通过get方法得到一个返回值,该返回值类型,通过泛型规定,是一个无参有返回值的接口!
案例:使用Lambda表达式返回数组元素的最小值!
代码语言:javascript复制 public static void main(String[] args) {
printMin(()->{
int[] arr = {1,2,3,4,5};
Arrays.sort(arr);
return arr[0];
});
}
public static void printMin(Supplier<Integer> min){
Integer minVlaue = min.get();
System.out.println(minVlaue);
}
3.2.2、Consumer接口:消费型接口
代码语言:javascript复制@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
案例:把一个字符串中的字母全部转换成小写
代码语言:javascript复制 public static void main(String[] args) {
toLower((String str)->{
String lower = str.toLowerCase();
System.out.println(lower);
});
}
public static void toLower(Consumer<String> consumer){
consumer.accept("HelloWorld");
}
案例:把一个字符串既转换成大小,又转换成小写
代码语言:javascript复制 public static void main(String[] args) {
toLowerAndToUpper((String str)->{
System.out.println(str.toLowerCase());
},
(String str)->{
System.out.println(str.toUpperCase());
});
}
public static void toLowerAndToUpper(Consumer<String> consumer1,Consumer<String> consumer2){
consumer1.accept("HelloWorld");
consumer2.accept("HelloWorld");
}
另一种写法
代码语言:javascript复制public static void main(String[] args) {
toLowerAndToUpper((String str)->{
System.out.println(str.toLowerCase());
},
(String str)->{
System.out.println(str.toUpperCase());
});
}
public static void toLowerAndToUpper(Consumer<String> consumer1,Consumer<String> consumer2){
consumer1.andThan(consumer2).accept("HelloWorld");
}
3.2.3、Function接口:转换型接口
代码语言:javascript复制@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
案例:把字符串转换成一个整数并返回
代码语言:javascript复制 public static void main(String[] args) {
parseToInt((String str)->{
return Integer.parseInt(str);
});
}
public static void parseToInt(Function<String,Integer> fun){
Integer apply = fun.apply("10");
System.out.println(apply);
}
案例:把两个字符串转换成整数,并对这两个整数求和
代码语言:javascript复制 public static void main(String[] args) {
sum((String str)->{
return Integer.parseInt(str);
},(String str)->{
return Integer.parseInt(str);
});
}
public static void sum(Function<String,Integer> fun1,Function<String,Integer> fun2){
Integer num1 = fun1.apply("10");
Integer num2 = fun1.apply("10");
System.out.println(num1 num2);
}
案例:传递两个参数,第一个参数是个字符串,要求把这个字符串转成数字;第二个是要求把转换之后的数字乘以5
代码语言:javascript复制 public static void main(String[] args) {
mul((String str)->{
return Integer.parseInt(str);
},(Integer i)->{
return i*5;
});
}
public static void mul(Function<String,Integer> fun1,Function<Integer,Integer> fun2){
Integer num1 = fun1.apply("10");
Integer num2 = fun2.apply(num1);
System.out.println(num2);
}
另外的实现方式
代码语言:javascript复制public static void main(String[] args) {
mul((String str)->{
return Integer.parseInt(str);
},(Integer i)->{
return i*5;
});
}
public static void mul(Function<String,Integer> fun1,Function<Integer,Integer> fun2){
fun1.andThen(fun2).apply("10");
}
3.2.4、Predicate接口:判断型接口
代码语言:javascript复制@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
案例:判断字符串的长度是否大于3
代码语言:javascript复制 public static void main(String[] args) {
is3Length((String str)->{
return str.length()>3;
});
}
public static void is3Length(Predicate<String> pre){
boolean fbb = pre.test("fbbb");
System.out.println("长度是否是3个长度:" fbb);
}
案例:判断字符串中是否既包含W,也包含H
代码语言:javascript复制 public static void main(String[] args) {
wANDh((String str)->{
return str.contains("w") && str.contains("h");
});
}
public static void wANDh(Predicate<String> pre){
boolean rst = pre.test("wwaaah");
System.out.println("是否既包含W也包含H:" rst);
}
另外一种写法
代码语言:javascript复制public static void main(String[] args) {
wANDh((String str)->{
return str.contains("w");
},(String str)->{
return str.contains("h");
});
}
public static void wANDh(Predicate<String> pre1,Predicate<String> pre2){
boolean rst1 = pre1.test("hahwahah");
boolean rst2 = pre2.test("hahwahah");
boolean rst=rst1&&rst2;
System.out.println("是否既包含W也包含H:" rst);
}
另外一种写法
代码语言:javascript复制 public static void main(String[] args) {
wANDh((String str)->{
return str.contains("w");
},(String str)->{
return str.contains("h");
});
}
public static void wANDh(Predicate<String> pre1,Predicate<String> pre2){
boolean rst = pre1.and(pre2).test("aaahhaaawww");
System.out.println("是否既包含W也包含H:" rst);
}
案例:使用Lambda表达式判断一个字符串中包含W或者包含H
代码语言:javascript复制 public static void main(String[] args) {
wORh((String str)->{
return str.contains("w");
},(String str)->{
return str.contains("h");
});
}
public static void wORh(Predicate<String> pre1,Predicate<String> pre2){
boolean rst = pre1.or(pre2).test("aaahhaaawww");
System.out.println("是否包含W或包含H:" rst);
}
案例:使用Lambda表达式判断一个字符串中是否不包含W
代码语言:javascript复制 public static void main(String[] args) {
isNotContain((String str)->{
return str.contains("a");
});
}
public static void isNotContain(Predicate<String> pre1){
boolean rst = pre1.negate().test("hahwahah");
System.out.println("是否不包含:" rst);
}
negate()的作用就是对后面的test方法的结果取反。
4、Lambda表达式的方法引用
定义:把方法中的代码像变量值一样传递 (int a=10;int b=a)
把方法传递给抽象方法
4.1、Lambda表达式的方法引用的作用是什么?
先来看一下Lambda表达式中代码冗余的场景
代码语言:javascript复制public class DemoReferenceMethod {
public static void main(String[] args) {
int[] arr = {1,2,3};
printSum(arr);//普通方法调用
//lambda方法调用
printSumLambda((Integer[] arr2)->{
int sum=0;
for (int i : arr2) {
sum =i;
}
System.out.println(sum);
});
}
public static void printSum(int[] arr){
int sum=0;
for (int i : arr) {
sum =i;
}
System.out.println(sum);
}
public static void printSumLambda(Consumer<int[]> con){
int[] arr = {1,2,3};
con.accept(arr);
}
}
从上面代码中中可以看出,printSum的方法内容和printSumLambda方法的lambda表达式的形参内容是一样的,所以这里存在了代码冗余。
printSum的方法内容=printSumLambda方法的lambda表达式
将上面的内容用方法引用改进
代码语言:javascript复制 //lambda方法调用
printSumLambda((Integer[] arr2)->{
int sum=0;
for (int i : arr2) {
sum =i;
}
System.out.println(sum);
});
//用方法引用改进上面的lambda表达式
printSumLambda(DemoReferenceMethod::printSum)
4.2、方法引用的格式
符号表示: ::
符号解释:双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景:如果Lambda所有实现的方案,已经有其他方法存在相同方案,那么则可以使用方法引用。
4.3、常见引用方式
对象::方法名
类名::静态方法名
类名::方法名
类名::new (调用的构造器)
数组类型::new (调用数组的构造器)
4.3.1、对象::方法名
代码语言:javascript复制 public static void main(String[] args) {
Date d = new Date();
//不使用方法引用
printTime(()->{
return d.getTime();
});
//使用方法引用
printTime(d::getTime);
}
public static void printTime(Supplier<Long> supp){
Long aLong = supp.get();
System.out.println(aLong);
}
代码语言:javascript复制 public void test(){
Date d = new Date();
Supplier<Long> aLong= d::getTime;
Long l=aLong.get();
System.out.println(l);
}
方法引用的注意事项:
A:被引用的方法,参数要和接口中抽象方法的参数一样
B:当接口抽象方法有返回值时,被引用的方法也必须有返回值
4.3.2、类名::静态方法名
代码语言:javascript复制 public void test(){
Supplier<Long> aLong= ()->{
return System.currentTimeMillis();
};
System.out.println(aLong.get());
Supplier<Long> aLong2= System::currentTimeMillis;
System.out.println(aLong2.get());
}
4.3.3、类名::方法名
代码语言:javascript复制 public void test(){
Function<String,Integer> fun1 = (String str)->{
return str.length();
}
System.out.println(fun1.apply("hello"));
//方法引用
Function<String,Integer> fun2 = String::length;
System.out.println(fun2.apply("helloabc"));
//方法引用
BiFunction(String,Integer,String) fun3 = String:substring;
String sub=fun3.apply("hello",2);
System.out.println(sub);
}
4.3.4、类名::new (引用构造器)
代码语言:javascript复制class Person{
public Person(){}
public Person(String name,int age){
this.name=name;
this.age=age;
}
private String name;
private int age;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
}
代码语言:javascript复制 public void test(){
Supplier<Person> sup = ()->{
return new Person();
}
Person p = sup.get();
//方法引用
Supplier<Person> sup2 = Person::new;
Person p2 = sup2.get();
//方法引用
BiFunction(String,Integer,Person) fun3 = Person:new;
Person p3=fun3.apply("zs",2);
}
4.3.5、数组类型::new
代码语言:javascript复制public void test{
Function<String[],Integer> fun = (len)->{
return new String[len];
}
String[] strs1 = fun.apply(10);
Function<String[],Integer> fun2 = String[]::new
String[] strs2 = fun2.apply(10);
}
4.4、方法引用总结
方法引用是对Lambda表达式符合特定情况下的一种缩写,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式,不过要注意的是方法引用只能引用已经存在的方法!
5、Stream流
5.1、Stream流思想概述
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序把一个原材料加工成一个商品。
通过使用Stream的API,能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归纳。
5.2、Stream流思想的体现案例
代码语言:javascript复制/*
假设集合中有如下人物:张学友,周润发,赵薇,张绍忠,张三丰。
要求:1、拿到所有姓张的人物
2、拿到名字长度为3个字的
3、打印这些数据
实现:
传统做法:遍历三次集合,实现规定的要求
流式做法:遍历一个,实现规定要求
*/
//1、传统做法
public class StreamDemo{
public static void main(String[] args){
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list,"张无忌","周芷若","赵薇","张强","张三丰");
//1、拿到所有姓张的人物
ArrayList<String> zhang = new ArrayList<String>();
for(String name:list){
if(name.startsWith("张")){
zhang.add(name);
}
}
//2、拿到名字长度为3个字的
ArrayList<String> threeLength = new ArrayList<String>();
for(String name:zhang){
if(name.length()==3){
threeLength.add(name);
}
}
//3、打印这些数据
for(String name:threeLength){
System.out.println(name);
}
}
}
代码语言:javascript复制//2、流式做法
public class StreamDemo2{
public static void main(String[] args){
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list,"张无忌","周芷若","赵薇","张强","张三丰");
list.stream().filter(s->s.startsWith("张")).
filter(s->s.length()==3).
forEach(System.out::println);
}
}
打印结果:
5.3、获取Stream流的两种方式
(1)根据Collection获取流
(2)Stream中的静态方法of获取流
5.3.1、根据Collection的stream()方法获取流
5.3.2、根据Stream类的静态of()方法
直接传入多个字符串
传入一个字符串数组
传入一个整数数组
5.5、Stream的注意事项
1、Stream只能操作一次
2、Stream方法返回的是新的流
3、Stream不调用终结方法,终结的操作不会执行
5.4、Stream常用API
Stream的API分为两类:
1、终结方法—>返回值不是Stream类型—>不支持链式调用
2、非终结方法—>返回值是Stream类型—>支持链式调用
注意:concat是Stream的静态方法
5.6、Stream常用API演示
forEach:逐个遍历
count:逐个遍历
limit:显示前几个
skip:跳过前几个
map:就是把一种类型的流映射成另外一种流
这里的map和集合中的map不是一个意思,这里的map只是把一种类型的值映射成另外一种类型的值,没有键值对!
把字符串转换成整数
sorted:对流中的数据进行排序
distinced:对流中的数据进行去重
math:元素匹配,有三种匹配情况
(1)allMatch():匹配所有
(2)noneMatch():判断是否是无匹配
(3)anyMatch():只要有一个匹配就行
find:元素查找
查找第一个:
方式一:findFirst()
方式二:findAny()
max和min方法,查找最大值和最小值
reduce:对数据进行加工处理
(1)对数据进行求和
(2)找最大值
map和reduce结合的练习
(1)求集合中Person对象的年龄总和
代码语言:javascript复制 public static void main(String[] args) {
//求流中的人的年龄和
ArrayList<Person> list = new ArrayList<Person>();
Collections.addAll(list,new Person("张学友",18),
new Person("周杰伦",19),
new Person("周润发",20),
new Person("张学友",40));
Integer totalAge = list.stream().map((p) -> {
return p.getAge();
}).reduce(0, (x, y) -> {
return x y;
});
System.out.println(totalAge);
}
(2)求Person中年龄最大是多少
代码语言:javascript复制 public static void main(String[] args) {
//求流中的人的最大年龄
ArrayList<Person> list = new ArrayList<Person>();
Collections.addAll(list,new Person("张学友",18),
new Person("周杰伦",19),
new Person("周润发",20),
new Person("张学友",40));
Integer max = list.stream().map((p) -> p.getAge()).reduce(0, Integer::max);
System.out.println(max);
}
(3)求字符串数组中”a“出现了多少次?
代码语言:javascript复制 public static void main(String[] args) {
//求字符串数组中“a”出现了多少次
String[] str = {"a","b","c","a","b"};
Integer count = Stream.of(str).map((s) -> {
if (s == "a") {
return 1;
} else {
return 0;
}
}).reduce(0, (x, y) -> {
return x y;
});
System.out.println(count);
}
Stream流的mapToInt方法
如果需要将Stream中的Integer类型数据转换成int类型,可以使用mapToInt方法。
InteStream和Stream的继承体系
mapToInt的基本用法
代码语言:javascript复制public static void main(String[] args) {
IntStream intStream = Stream.of(1, 2, 3).mapToInt((i)->i.intValue());
intStream.forEach(System.out::println);
}
使用基本数据类型可以节省内存空间
收集Stream流的结果
(1)收集流的结果到集合中去
(2)收集流的结果到数组中去
收集流的结果到集合中去
public static Collector<T,?,List> toList():转换为List集合
public static Collector<T,?,Set> toSet():转换为List集合
public static <T, C extends Collection>Collector<T, ?, C> toCollection(Supplier collectionFactory):转换为指定集合
收集结果到数组中去
对流中的数据进行聚合计算
(1)获取最大值
(2)获取最小值
(3)求总和
(4)求平均值
(5)分组
(6)多级分组
(7)分区
(8)拼接
获取最大值
求四大天王中年龄最大的
求四大天王中年龄最小的
求四大天王的年龄总和
求四大天王的平均年龄
对流中的数据进行分组
四大天王按照年龄进行分组
map的便捷遍历方式
原理:
将年龄19岁以上(包括19)分为一组,19岁以下分为一组。
多级分组
先按性别进行分组,再按年龄进行分组
对流中的数据进行分区,true为一个列表区,false为一个列表区
对流中的数据进行拼接
6、并行的Stream流
串行的Stream流
这样单线程处理,如果数量大,cpu核心多,势必会造成效率低下、资源浪费的情况!
6.1、并行的Stream流的获取方式
(1)通过集合直接获取并行流
(2)通过Stream对象的parallel()方法将串行流转变成并行流
方式一:通过集合直接获取并行流
方式二:通过Stream对象的parallel()方法将串行流转变成并行流
并行的Stream流和串行的Stream流的计算效率对比
需求:计算8亿的累加和
(1)for循环
(2)串行流
(3)并行流
6.2、并行流的线程安全问题
线程安全问题现场
需求:把1000个数字用并行流存到集合中去。
线程安全问题的解决方案:
(1)加锁
(2)使用线程安全的集合
(3)使用串行流
把并行流转成串行流
(4)使用Collectors的toList方法/Stream的toArray方法
7、Fork/Join框架
Fork/Join框架是并行流parallelStream底层使用的技术,Fork/Join框架是JDK1.7底层使用的技术。Fork/Join框架可以将一个大任务拆分成很多个小任务来异步执行。
Fork/Join框架主要包含三个模块:
(1):线程池,ForkJoinPool
(2):任务对象,ForkJoinTask
(3):执行任务的线程,ForkJoinWorkerThread
7.1、Fork/Join框架原理-分治法
ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法,ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万数据的合并任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000 个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行(其实就是采用了递归算法)。
7.2、Fork/Join原理-工作窃取法
当执行新的任务时Fork/Join框架会将任务拆分分成更小的任务执行,并将小任务加到线程队列中,当多个线程同时执行时,就总会有线程先执行完毕,有线程后执行完毕。先执行完毕的线程会从其它线程队列的末尾窃取任务来执行。为什么会从其它线程的末尾窃取了,因为如果从头部位置开始窃取,可能会遇到线程安全的问题。
7.3、ForkJoin案例
需求:使用Fork/Join计算1-10000的累加和,当一个任务的计算量大于3000时拆分任务,数量小于3000时计算。
代码语言:javascript复制package Test;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Demo10 {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
SumRecursiveTask task = new SumRecursiveTask(1L,10000L);
Long rst = pool.invoke(task);
long endTime = System.currentTimeMillis();
System.out.println("结果是:" rst);
System.out.println("计算时间是:" (endTime-startTime));
}
}
class SumRecursiveTask extends RecursiveTask<Long>{
private Long start;
private Long end;
private static final int THRESHOLD=3000;
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:" 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();
}
}
}
8、Optional的使用
我们之前写代码,要经常进行空值的判断,避免出现空指针异常。JDK8针对这一情况推出了Optional来改进这一情况!
首先来看一下之前对null值的处理情况吧!
代码语言:javascript复制 public static void main(String[] args) {
Person p= new Person("张三",18);
p=null;
if(p!=null){
System.out.println(p);
}else{
System.out.println("查无此人");
}
}
8.1、Optional类介绍
Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。
8.2、Optional类的创建方式:
代码语言:javascript复制Optional.of(T t):创建一个Optional实例
Optional.empty():创建一个空的Optional实例
Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
代码语言:javascript复制 //Optional.of(T t):创建一个Optional实例
public static void main(String[] args) {
Person p= new Person("张三",18);
Optional<Person> p1 = Optional.of(p);//不能放空值
Person person = p1.get();//获取对象
System.out.println(person);
}
代码语言:javascript复制 public static void main(String[] args) {
Person p= new Person("张三",18);
//Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
Optional<Person> p1 = Optional.ofNullable(p);
Person person = p1.get();
System.out.println(person);
}
8.3、Optional类的常用方法:
代码语言:javascript复制isPresent():判断是否包含值,包含值返回true,不包含值返回false
get():如果Optional有值则将其返回,否则抛出NoSuchElementException
orElse(T t):如果调用对象包含值,返回该值,否则返回参数t
orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值
map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
9、JDK8的时间和日期
9.1、旧版日期时间API存在的问题
1、设计很差:在Java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此处用于格式化和解析的类在java.text包中定义。
2、非线程安全:java.util.Date是非线程安全的,所有的日期类都是可变的,这是java日期类最大的问题之一。
3、时区处理麻烦:日期类并不提供国际化,没有时区支持。因此Java引入了Java.util.Calendar和Java.util.TimeZone类,但他们同样存在上述所有的问题。
Date类的缺陷
代码语言:javascript复制 public static void main(String[] args) throws Exception {
Date d = new Date(1990,1,1);
System.out.println(d);
}
日期解析和格式化缺陷
线程不安全
代码语言:javascript复制 public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i=0;i<100;i ){
new Thread(()->{
Date d = null;
try {
d = sdf.parse("2020-1-1");
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(d.toLocaleString());
}).start();
}
}
9.2、新版本日期时间API介绍
JDK8中增加了一套全新的日期和时间API,这套API设计合理,是线程安全的。新的日期及时间API位于java.time包中,下面是一些关键类。
LocalDate:表示日期,包含年月日,格式为2019-10-16
LocalTime:表示时间,包含时分秒,格式为 16:38:54 158549300
LocalDateTime:表示日期时间,包含年月日,时分秒,格式为:2018-09-06T15:33:56.750
DateTimeFormatter:日期时间格式化类。
Instant:时间戳,表示一个特定的时间瞬间
Duration:用于计算两个时间(LocalTime,时分秒)的距离
Period:用于计算两个日期(LocalDate,年月日)的距离
ZonedDateTime:包含时区的时间
9.3、Java中的历法
java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年有366天。此外Java 8还提供了4套其他历法,分别是:
ThaiBuddhistDate:泰国佛教历
MinguoDate:中华民国历
JapaneseDate:日本历
HijrahDate:伊斯兰历
9.4、JDK8的日期和时间类
LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。他们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
代码语言:javascript复制 public static void main(String[] args) throws Exception {
//LocalDate:表示日期,年、月、日
//创建指定日期
LocalDate fj = LocalDate.of(2020,1,1);
System.out.println(fj);//2020-01-01
//创建当前日期
LocalDate now = LocalDate.now();
System.out.println(now);
//获取日期信息
System.out.println(now.getYear());//获取年
System.out.println(now.getMonthValue());//获取月
System.out.println(now.getDayOfMonth());//日
System.out.println(now.getDayOfWeek());//周几
}
代码语言:javascript复制 public static void main(String[] args) throws Exception {
//LocalTime:表示十分秒
//得到当前时间对象
LocalTime lt = LocalTime.now();
System.out.println(lt);
int hour = lt.getHour();//得到小时
int minute = lt.getMinute();//得到分钟
int second = lt.getSecond();//得到秒
int nano = lt.getNano();//得到纳秒
System.out.println(hour);
System.out.println(minute);
System.out.println(second);
System.out.println(nano);
}
代码语言:javascript复制 public static void main(String[] args) throws Exception {
//获取当前日期和时间
LocalDateTime ldt = LocalDateTime.now();
//以下分别得到:年、月、日、时、分、秒、纳秒
int year = ldt.getYear();
int month = ldt.getMonthValue();
int day = ldt.getDayOfMonth();
int hour = ldt.getHour();
int minute = ldt.getMinute();
int second = ldt.getSecond();
int nano = ldt.getNano();
System.out.println(year);
System.out.println(month);
System.out.println(day);
System.out.println(hour);
System.out.println(minute);
System.out.println(second);
System.out.println(nano);
}
对日期的修改,使用withAttribute方法,该方法会返回修改之后的日期时间对象,原来的日期时间对象不会发生改变。
修改日期
代码语言:javascript复制 public static void main(String[] args) throws Exception {
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
//修改年份
LocalDateTime ldt2 = ldt.withYear(2022);
System.out.println(ldt2);
}
增加和减少年份
代码语言:javascript复制 public static void main(String[] args) throws Exception {
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
//增加和减少年份
LocalDateTime localDateTime = ldt.plusYears(10);//增加年份
System.out.println(localDateTime);
//减少年份
LocalDateTime localDateTime1 = localDateTime.minusYears(10);
System.out.println(localDateTime1);
}
年份的判断
代码语言:javascript复制 public static void main(String[] args) throws Exception {
LocalDateTime ldt = LocalDateTime.now();
LocalDateTime ldt2 = LocalDateTime.of(2019, 10, 10, 10, 10, 10);
boolean after = ldt.isAfter(ldt2); //是否在ldt2之后
System.out.println(after);
boolean before = ldt.isBefore(ldt2);//是否在ldt2之前
System.out.println(before);
boolean equal = ldt.isEqual(ldt2);//是否和ldt2相等
System.out.println(equal);
}
JDK 8的时间格式化与解析
通过java.time.format.DateTimeFormatter类可以进行日期时间解析与格式化
JDK自带的时间、日期格式化模板
代码语言:javascript复制 public static void main(String[] args) throws Exception {
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_DATE_TIME;//jdk自带的时间日期格式化模板
String format = isoDateTime.format(ldt);
System.out.println(format);
}
自定义时间、日期格式化模板
代码语言:javascript复制 public static void main(String[] args) throws Exception {
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter zdy = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");//自定义解析模板
String format = zdy.format(ldt);
System.out.println(format);
}
时间日期的解析
代码语言:javascript复制 public static void main(String[] args) throws Exception {
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter zdy = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");//自定义解析模板
LocalDateTime parse = LocalDateTime.parse("2020年01月04日 10时30分05秒", zdy);
System.out.println(parse);
}
代码语言:javascript复制 public static void main(String[] args) throws Exception {
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter zdy = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");//自定义解析模板
for (int i=0;i<100;i ){
new Thread(
new Runnable() {
@Override
public void run() {
LocalDateTime parse = LocalDateTime.parse("2020年01月04日 10时30分05秒", zdy);
System.out.println(parse);
}
}
).start();
}
}
Instant
Instant时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒
代码语言:javascript复制 public static void main(String[] args) throws Exception {
Instant now = Instant.now();
System.out.println("当前时间戳=" now);
System.out.println(now.getNano());//获取时间戳的纳秒值
System.out.println(now.getEpochSecond());
System.out.println(now.toEpochMilli());
System.out.println(System.currentTimeMillis());
}
Instant的加操作
计算时间和日期差
1、Duration:用于计算2个时间(LocalTime,时分秒)的距离
2、Period:用于计算2个日期(LocalDate,年月日)的距离
JDK8的时间矫正器
有时我们可能需要获得特定的日期。例如:将日期调整到“下个月的第一天”等操作。可以通过时间矫正器来进行。
TemporalAdjuster:时间校正器
TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster实现。
TemporalAdjuster:时间校正器
代码语言:javascript复制//TemporaAdjuster:时间矫正器
//TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster的实现
public class Demo36 {
public static void main(String[] args) throws Exception {
LocalDateTime dateTime = LocalDateTime.now();
//得到下个月的第一天
TemporalAdjuster firsWeekDayOfNextMonth = (Temporal temporal) -> {
LocalDateTime ldt = (LocalDateTime) temporal;//把temporal转换成LocalDateTime类型
LocalDateTime nextMonth = ldt.plusMonths(1).withDayOfMonth(1);//调整时间,加一个月,加一个月后的第一天
System.out.println(nextMonth);
return nextMonth;//返回调整之后的时间
};
}
}
代码语言:javascript复制public static void main(String[] args) throws Exception {
LocalDateTime dateTime = LocalDateTime.now();
//得到下个月的第一天
TemporalAdjuster firsWeekDayOfNextMonth = (Temporal temporal) -> {
LocalDateTime ldt = (LocalDateTime) temporal;//把temporal转换成LocalDateTime类型
LocalDateTime nextMonth = ldt.plusMonths(1).withDayOfMonth(1);//调整时间,加一个月,加一个月后的第一天
System.out.println(nextMonth);
return nextMonth;//返回调整之后的时间
};
LocalDateTime now = dateTime.with(firsWeekDayOfNextMonth);
// LocalDateTime now2 = (LocalDateTime)firsWeekDayOfNextMonth;
System.out.println(now);
}
结果:
JDK8设置时间和日期的时区
Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。
其中每个时区都对应着ID,ID的格式为“区域/城市”。例如:Asia/Shanghai 等。
Zoneld:该类中包含了所有的时区信息
代码语言:javascript复制public static void main(String[] args) throws Exception {
//获得所有的时区ID
// ZoneId.getAvailableZoneIds().forEach(System.out::println);
//创建世界标准时间
final ZonedDateTime zonedDateTimeFromClock = ZonedDateTime.now(Clock.systemUTC());
System.out.println(zonedDateTimeFromClock);//2020-01-09T01:44:36.951697400Z
//创建一个当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);//2020-01-09T09:48:21.130519700
//创建一个带时区的ZonedDateTime
final ZonedDateTime zonedDateTimeFromZone = ZonedDateTime.now(ZoneId.of("Africa/Nairobi"));
System.out.println(zonedDateTimeFromZone);//2020-01-09T04:48:21.131519700 03:00[Africa/Nairobi]
}