阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

JDK8新特性

199次阅读
没有评论

共计 24857 个字符,预计需要花费 63 分钟才能阅读完成。

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

JDK8 新特性

从上面的代码对比中,大家可以发现,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); }

JDK8 新特性

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

JDK8 新特性

2、JDK8 接口的方法增强

2.1、概述

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

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

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

JDK8 新特性

为了解决这一弊端,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();// 重写 } }

运行结果:

JDK8 新特性

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、静态方法的使用

JDK8 新特性

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

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

JDK8 新特性

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

JDK8 新特性

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

JDK8 新特性

案例:使用 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); }

JDK8 新特性

案例:使用 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); } }

打印结果:

JDK8 新特性

5.3、获取 Stream 流的两种方式

(1)根据 Collection 获取流

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

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

JDK8 新特性

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

JDK8 新特性

直接传入多个字符串

JDK8 新特性

传入一个字符串数组

JDK8 新特性

传入一个整数数组

JDK8 新特性

5.5、Stream 的注意事项

​ 1、Stream 只能操作一次

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

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

JDK8 新特性

5.4、Stream 常用 API

JDK8 新特性

Stream 的 API 分为两类:

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

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

注意:concat 是 Stream 的静态方法

5.6、Stream 常用 API 演示

forEach: 逐个遍历

JDK8 新特性

count: 逐个遍历

JDK8 新特性

limit: 显示前几个

JDK8 新特性

skip: 跳过前几个

JDK8 新特性

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

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

把字符串转换成整数

JDK8 新特性

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

JDK8 新特性

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

JDK8 新特性

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

(1)allMatch(): 匹配所有

JDK8 新特性

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

JDK8 新特性
(3)anyMatch(): 只要有一个匹配就行

JDK8 新特性

find: 元素查找

查找第一个:

方式一:findFirst()

JDK8 新特性

方式二:findAny()

JDK8 新特性

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

JDK8 新特性

reduce: 对数据进行加工处理

(1)对数据进行求和

JDK8 新特性

JDK8 新特性

(2)找最大值

JDK8 新特性

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

JDK8 新特性

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

JDK8 新特性
Stream 流的 mapToInt 方法

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

JDK8 新特性

InteStream 和 Stream 的继承体系

JDK8 新特性

mapToInt 的基本用法

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

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

JDK8 新特性

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

JDK8 新特性

JDK8 新特性

JDK8 新特性

收集结果到数组中去

JDK8 新特性

JDK8 新特性

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

(1)获取最大值

(2)获取最小值

(3)求总和

(4)求平均值

(5)分组

(6)多级分组

(7)分区

(8)拼接

获取最大值

求四大天王中年龄最大的

JDK8 新特性

求四大天王中年龄最小的

JDK8 新特性

求四大天王的年龄总和

JDK8 新特性

求四大天王的平均年龄

JDK8 新特性

对流中的数据进行分组

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

JDK8 新特性

map 的便捷遍历方式

JDK8 新特性

原理:

JDK8 新特性

JDK8 新特性

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

JDK8 新特性

JDK8 新特性

多级分组

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

JDK8 新特性

JDK8 新特性

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

JDK8 新特性

对流中的数据进行拼接

JDK8 新特性

JDK8 新特性

6、并行的 Stream 流

串行的 Stream 流

JDK8 新特性

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

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

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

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

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

JDK8 新特性
方式二:通过 Stream 对象的 parallel()方法将串行流转变成并行流

JDK8 新特性

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

需求:计算 8 亿的累加和

(1)for 循环

JDK8 新特性

(2)串行流

JDK8 新特性

(3)并行流

JDK8 新特性

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

线程安全问题现场

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

JDK8 新特性

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

(1)加锁

JDK8 新特性

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

JDK8 新特性

JDK8 新特性

(3)使用串行流

JDK8 新特性

把并行流转成串行流

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

JDK8 新特性

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

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

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

7.3、ForkJoin 案例

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

JDK8 新特性

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("查无此人"); } }

JDK8 新特性

8.1、Optional 类介绍

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

JDK8 新特性

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

JDK8 新特性

JDK8 新特性

JDK8 新特性

JDK8 新特性

JDK8 新特性

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

JDK8 新特性

JDK8 新特性

8.3、Optional 类的常用方法:

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

JDK8 新特性

JDK8 新特性

JDK8 新特性

JDK8 新特性

JDK8 新特性

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

JDK8 新特性

日期解析和格式化缺陷

线程不安全

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

JDK8 新特性

JDK8 新特性

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

JDK8 新特性

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

JDK8 新特性

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

JDK8 新特性

对日期的修改,使用 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); }

JDK8 新特性

增加和减少年份

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

JDK8 新特性

年份的判断

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

JDK8 新特性

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

JDK8 新特性

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

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

JDK8 新特性

时间日期的解析

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

JDK8 新特性

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

JDK8 新特性

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

JDK8 新特性

Instant 的加操作

JDK8 新特性

计算时间和日期差

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

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

JDK8 新特性

JDK8 新特性
JDK8 的时间矫正器

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

TemporalAdjuster: 时间校正器

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

TemporalAdjuster: 时间校正器

JDK8 新特性

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

JDK8 新特性

JDK8 新特性

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 新特性

JDK8 设置时间和日期的时区

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

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

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

JDK8 新特性

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

JDK8 新特性

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-06-06发表,共计24857字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中