组团学

JDK8新特性

阅读 (325425)

1、Lambda表达式

1.1、概述

首先,要想明白Lambda表达式就要先明白函数式接口,所以,咱们先来了解一下什么是函数式接口吧!

所谓函数式接口就是有且仅有一个抽象方法的接口

函数式接口就是适用于函数式编程场景的接口,java中的函数式编程的体现就是lambda!所以函数式接口 就是可以适用于Lambda使用的接口。

只有当接口中有且只有一个抽象方法的时候,Java中的lambda表达式才能顺利推导!

也就是说在Java中使用Lambda表达式必须符合函数式接口的规范

所以,使用Lambda接口的前提是:

(1)Lambda关联的接收对象必须是函数式接口。(也就是说方法的形参必须是接口)

(2)这个接口只能有一个抽象方法(函数式接口的规范)

1.2、函数式接口

/* * 函数式接口:有且只有一个抽象方法! * @FunctionalInterface:用来检测该接口中是否是只有一个抽象方法,如果不止一个就报错! * */ @FunctionalInterface public interface FunctionInter { public void method(); }

1.3、Lambda表达式和匿名内部类

Lambda表达式“本质上”是一个匿名内部类,只是二者体现形式不一样,但可以把Lambda表达式当做匿名内部类来理解!

1.3.1、匿名内部类

匿名内部类就是某个实现了接口的子类对象

不用匿名内部类

//未用匿名内部类 public class MyComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { return o1-o2; } }
public static void main(String[] args) { Comparator c = new MyComparator();//实现比较器接口,创建对象 TreeSet<Integer> set = new TreeSet<Integer>(c); }

使用匿名内部类

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表达式

//超简洁 TreeSet<Integer> set3 = new TreeSet<Integer>((o1,o2) ->o1-o2);

image20191223154231791.png

从上面的代码对比中,大家可以发现,lambda表达式真的超简洁

1.4、Lambda表达式详解

1.4.1、Lambda表达式的标准写法

//可以没有形参,如果有多个形参,那么用“,”隔开
//方法体和形参之间用“->”连接
(参数类型 形参1,参数类型 形参2)->{ 
	方法体;
}

代码演示一(无参无返回值)

public interface FlyAble { public void fly(); }
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表达式的本质就是重写接口中的方法

代码演示一(有参有返回值)

public interface FlyAble { public int fly(String demo2); }
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); }

image20191225101215001.png

1.4.2、Lambda表达式的省略写法

(1)小括号内参数的类型可以省略

(2)如果小括号内有且仅有一个参数,则小括号可以省略

(3)如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号

例如:

public interface FlyAble { public void fly(String name); }
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"); } }

image20191223171055120.png

2、JDK8接口的方法增强

2.1、概述

JDK1.8之前,接口中允许出现的成员有静态常量、抽象方法

//JDK1.8以前 public interface InterA { //静态常量 //抽象方法 }

JDK1.8之前只允许接口中出现抽象方法,但是在实际的使用过程中,发现这样会影响接口的扩展性。例如:当往一个接口中添加新的抽象方法时,原来实现该接口的类都会报错!这样就显得“牵一发而动全身”!

image20191224143119319.png

为了解决这一弊端,JDK在1.8版本中,对接口的功能进行了扩展!

JDK1.8之后,接口中允许出现的成员有静态常量、抽象方法、默认方法静态方法

//JDK1.8以后 public interface InterB { //静态常量 //抽象方法 //默认方法 //静态方法 }

2.2、JDK1.8接口新增方法种类

  • 默认方法
  • 静态方法

2.3、默认方法的定义和使用

2.3.1、默认方法的定义

默认方法定义在接口中

他是个有方法体的方法

他定义的关键字是default

default出现的位置在方法返回值类型前面

定义格式如下:

interface InterA { public void methodA(); //默认方法 public default void methodDef(){ //功能代码 } }

2.3.2、默认方法的使用

(1)直接用

(2)重写

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();//重写 } }

运行结果:

image20191224144944826.png

2.4、静态方法的定义和使用

2.4.1、静态方法的定义

静态方法定义在接口中

他是个有方法体的静态方法

定义格式如下:

interface InterD { //定义静态方法 public static void methodSta(){ //方法体 } public void methodA(); public default void methodDef(){ System.out.println("default ... method"); } }

从上面可以看出,接口中静态方法的定义和普通类中的静态方法的定义没啥区别!

2.4.2、静态方法的使用

image20191224150525522.png

注意:接口中的静态方法只能通过接口名调用

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接口:供给型接口

@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }

Supplier接口,他可以完成供给的功能,对应的lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

供给型接口:通过get方法得到一个返回值,该返回值类型,通过泛型规定,是一个无参有返回值的接口!

案例:使用Lambda表达式返回数组元素的最小值!

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接口:消费型接口

@FunctionalInterface public interface Consumer<T> { void accept(T t); }

案例:把一个字符串中的字母全部转换成小写

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"); }

案例:把一个字符串既转换成大小,又转换成小写

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"); }

另一种写法

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"); }

image20191224164102167.png

3.2.3、Function接口:转换型接口

@FunctionalInterface public interface Function<T, R> { R apply(T t); }

案例:把字符串转换成一个整数并返回

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); }

案例:把两个字符串转换成整数,并对这两个整数求和

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

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); }

另外的实现方式

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"); }

image20191224194531786.png

3.2.4、Predicate接口:判断型接口

@FunctionalInterface public interface Predicate<T> { boolean test(T t); }

案例:判断字符串的长度是否大于3

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

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); }

另外一种写法

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); }

另外一种写法

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); }

image20191224222921968.png

案例:使用Lambda表达式判断一个字符串中包含W或者包含H

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); }

image20191224223510283.png

案例:使用Lambda表达式判断一个字符串中是否不包含W

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表达式中代码冗余的场景

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表达式

将上面的内容用方法引用改进

//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、对象::方法名

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); }
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、类名::静态方法名

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、类名::方法名

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 (引用构造器)

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; } }
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

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流思想的体现案例

/* 假设集合中有如下人物:张学友,周润发,赵薇,张绍忠,张三丰。 要求: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); } } }
//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); } }

打印结果:

image20191226223540709.png

5.3、获取Stream流的两种方式

(1)根据Collection获取流

(2)Stream中的静态方法of获取流

5.3.1、根据Collection的stream()方法获取流

image20191226224852262.png

5.3.2、根据Stream类的静态of()方法

image20191226231338356.png

直接传入多个字符串

image20191226230915393.png

传入一个字符串数组

image20191226231234495.png

传入一个整数数组

image20191226231749246.png

5.5、Stream的注意事项

​ 1、Stream只能操作一次

​ 2、Stream方法返回的是新的流

​ 3、Stream不调用终结方法,终结的操作不会执行

image20191226233055160.png

5.4、Stream常用API

image20191226231955153.png

Stream的API分为两类:

​ 1、终结方法—>返回值不是Stream类型—>不支持链式调用

​ 2、非终结方法—>返回值是Stream类型—>支持链式调用

注意:concat是Stream的静态方法

5.6、Stream常用API演示

forEach:逐个遍历

image20191228112330032.png

count:逐个遍历

image20191228112514406.png

limit:显示前几个

image20191226233524336.png

skip:跳过前几个

image20191226233710848.png

map:就是把一种类型的流映射成另外一种流

这里的map和集合中的map不是一个意思,这里的map只是把一种类型的值映射成另外一种类型的值,没有键值对!

把字符串转换成整数

image20191227091942113.png

sorted:对流中的数据进行排序

image20191227093053933.png

distinced:对流中的数据进行去重

image20191227094003626.png

math:元素匹配,有三种匹配情况

(1)allMatch():匹配所有

image20191227101047230.png

(2)noneMatch():判断是否是无匹配

image20191227101407973.png
(3)anyMatch():只要有一个匹配就行

image20191227101648684.png

find:元素查找

查找第一个:

方式一:findFirst()

image20191227102116763.png

方式二:findAny()

image20191227102736429.png

max和min方法,查找最大值和最小值

image20191227103408901.png

reduce:对数据进行加工处理

(1)对数据进行求和

image20191227105940811.png

image20191227110149167.png

(2)找最大值

image20191227110413811.png

map和reduce结合的练习

(1)求集合中Person对象的年龄总和

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); }

image20191228165443504.png

(2)求Person中年龄最大是多少

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“出现了多少次?

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); }

image20191228171603967.png
Stream流的mapToInt方法

如果需要将Stream中的Integer类型数据转换成int类型,可以使用mapToInt方法。

image20191229204107906.png

InteStream和Stream的继承体系

image20191229210122426.png

mapToInt的基本用法

public static void main(String[] args) { IntStream intStream = Stream.of(1, 2, 3).mapToInt((i)->i.intValue()); intStream.forEach(System.out::println); }

使用基本数据类型可以节省内存空间

image20191229211033176.png

收集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):转换为指定集合

image20191230173604413.png

image20191230173803747.png

image20191230173410436.png

收集结果到数组中去

image20191230174101361.png

image20191230174308330.png

对流中的数据进行聚合计算

(1)获取最大值

(2)获取最小值

(3)求总和

(4)求平均值

(5)分组

(6)多级分组

(7)分区

(8)拼接

获取最大值

求四大天王中年龄最大的

image20191230175836562.png

求四大天王中年龄最小的

image20191230180206353.png

求四大天王的年龄总和

image20191230180939667.png

求四大天王的平均年龄

image20191230181225322.png

对流中的数据进行分组

四大天王按照年龄进行分组

image20191230202724654.png

map的便捷遍历方式

image20191230203939935.png

原理:

image20191230204019979.png

image20191230204106461.png

将年龄19岁以上(包括19)分为一组,19岁以下分为一组。

image20191230204953753.png

image20191230205019565.png

多级分组

先按性别进行分组,再按年龄进行分组

image20191230213347869.png

image20191230213406807.png

对流中的数据进行分区,true为一个列表区,false为一个列表区

image20191230214934916.png

对流中的数据进行拼接

image20191230222348255.png

image20191230222410033.png

6、并行的Stream流

串行的Stream流

image20191230225844732.png

这样单线程处理,如果数量大,cpu核心多,势必会造成效率低下、资源浪费的情况!

6.1、并行的Stream流的获取方式

(1)通过集合直接获取并行流

(2)通过Stream对象的parallel()方法将串行流转变成并行流

方式一:通过集合直接获取并行流

image20191230231325206.png
方式二:通过Stream对象的parallel()方法将串行流转变成并行流

image20191230232040141.png

并行的Stream流和串行的Stream流的计算效率对比

需求:计算8亿的累加和

(1)for循环

image20191230233854538.png

(2)串行流

image20191231101905444.png

(3)并行流

image20191231101716330.png

6.2、并行流的线程安全问题

线程安全问题现场

需求:把1000个数字用并行流存到集合中去。

image20191231113610997.png

线程安全问题的解决方案:

(1)加锁

image20191231114953682.png

(2)使用线程安全的集合

image20191231115422463.png

image20191231120044278.png

(3)使用串行流

image20191231120857567.png

把并行流转成串行流

(4)使用Collectors的toList方法/Stream的toArray方法

image20191231135448301.png

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+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行(其实就是采用了递归算法)。
image20191231142324818.png

7.2、Fork/Join原理-工作窃取法

当执行新的任务时Fork/Join框架会将任务拆分分成更小的任务执行,并将小任务加到线程队列中,当多个线程同时执行时,就总会有线程先执行完毕,有线程后执行完毕。先执行完毕的线程会从其它线程队列的末尾窃取任务来执行。为什么会从其它线程的末尾窃取了,因为如果从头部位置开始窃取,可能会遇到线程安全的问题。

7.3、ForkJoin案例

需求:使用Fork/Join计算1-10000的累加和,当一个任务的计算量大于3000时拆分任务,数量小于3000时计算。

image20191231143800285.png

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值的处理情况吧!

public static void main(String[] args) { Person p= new Person("张三",18); p=null; if(p!=null){ System.out.println(p); }else{ System.out.println("查无此人"); } }

image20191231164652214.png

8.1、Optional类介绍

Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。

image20191231165217233.png

8.2、Optional类的创建方式:

Optional.of(T t):创建一个Optional实例 Optional.empty():创建一个空的Optional实例 Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
//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); }

image20191231230306292.png

image20191231231319502.png

image20191231235130795.png

image20191231235746538.png

image20191231235514487.png

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); }

image20200101000645913.png

image20200101121339917.png

8.3、Optional类的常用方法:

isPresent():判断是否包含值,包含值返回true,不包含值返回false get():如果Optional有值则将其返回,否则抛出NoSuchElementException orElse(T t):如果调用对象包含值,返回该值,否则返回参数t orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值 map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()

image20200101121801975.png

image20200101122148711.png

image20200101123402325.png

image20200101131845270.png

image20200101160143095.png

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类的缺陷

public static void main(String[] args) throws Exception { Date d = new Date(1990,1,1); System.out.println(d); }

image20200101231927774.png

日期解析和格式化缺陷

线程不安全

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(); } }

image20200101232858717.png

image20200101233052852.png

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日历系统的日期、时间、日期和时间。他们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

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());//周几 }

image20200102000432192.png

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); }

image20200102093413552.png

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); }

image20200102094751708.png

对日期的修改,使用withAttribute方法,该方法会返回修改之后的日期时间对象,原来的日期时间对象不会发生改变。

修改日期

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); }

image20200102095558100.png

增加和减少年份

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); }

image20200102100248798.png

年份的判断

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); }

image20200104101419093.png

JDK 8的时间格式化与解析

通过java.time.format.DateTimeFormatter类可以进行日期时间解析与格式化

JDK自带的时间、日期格式化模板

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); }

image20200104102548529.png

自定义时间、日期格式化模板

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); }

image20200104103401582.png

时间日期的解析

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); }

image20200104104108548.png

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(); } }

image20200104104541374.png

Instant

Instant时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒

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()); }

image20200104105942818.png

Instant的加操作

image20200104110542794.png

计算时间和日期差

1、Duration:用于计算2个时间(LocalTime,时分秒)的距离

2、Period:用于计算2个日期(LocalDate,年月日)的距离

image20200107222700274.png

image20200107225408777.png
JDK8的时间矫正器

有时我们可能需要获得特定的日期。例如:将日期调整到“下个月的第一天”等操作。可以通过时间矫正器来进行。

TemporalAdjuster:时间校正器

TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster实现。

TemporalAdjuster:时间校正器

image20200109090642797.png

//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;//返回调整之后的时间 }; } }

image20200109091317949.png

image20200109092531958.png

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); }

结果:

image20200109092658531.png

JDK8设置时间和日期的时区

Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。

其中每个时区都对应着ID,ID的格式为“区域/城市”。例如:Asia/Shanghai 等。

Zoneld:该类中包含了所有的时区信息

image20200109094137479.png

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] }

image20200109095203946.png

需要 登录 才可以提问哦