JDK8 新特性

JDK8 新特性个人博客 个人博客 https www crystalblog xyz 备用地址 https wang qz gitee io crystal blog 1 简介 JDK8 是官方发布的一个大版本 提供了很多新特性功能给开发者使用 包含语言 编译器 库 工具和 JVM 等方面的十多个新特性 本文将介绍编码过程中常用的一些新特性 JDK8 帮助文档

大家好,我是讯享网,很高兴认识大家。

个人博客

个人博客: https://www.crystalblog.xyz/

备用地址: https://wang-qz.gitee.io/crystal-blog/

1. 简介

JDK8是官方发布的一个大版本, 提供了很多新特性功能给开发者使用, 包含语言、编译器、库、工具和JVM等方面的十多个新特性。 本文将介绍编码过程中常用的一些新特性. JDK8帮助文档

  • Lambda表达式
  • 函数式接口
  • 方法引用和构造器调用
  • Stream API
  • 接口中的默认方法和静态方法
  • 新的日期时间API
  • Optional

2. Lambda表达式

2.1 介绍

lambda表达式 (也称为闭包), 本质上是一段匿名内部类或一段可以传递的代码 . 可以理解为使用更加方便的“语法糖”,但原理不变的编写语法。 它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理 .

语法糖: 简洁而紧凑的语言结构 .

2.2 使用

Lambda表达式由逗号分隔的参数列表->符号语句块三部分组成 .

// jdk8之前需要循环集合进行遍历输出 Arrays.asList( "a", "b", "d" ).forEach( (String s) -> { 
    System.out.print( s ); } ); 

讯享网

如果语句块只有一句, 可以省略大括号; 参数列表的类型也可以省略, 如果只有一个参数, 参数的括号也可以省略.

讯享网Arrays.asList( "a", "b", "d" ).forEach( s -> System.out.print( s ) ); 

如果有语句块中有返回值

// jdk8之前,sort(Comparator c)需要写一个匿名内部类实现 Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> { 
    int result = e1.compareTo( e2 ); return result; } ); 

可以继续简化, 只有一个返回值语句, 大括号和return都可以省略.

讯享网Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) ); 

2.3 案例

需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.

传统方式编写, 每次筛选都要对集合进行循环遍历判断, 这些都是重复的操作.

/ * 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品. */ public class LambdaTest { 
    @Test public void testFilterProducts() { 
    List<Product> products = getProducts(); products = this.filterProductByColor(products); products = this.filterProductByPrice(products); products.forEach(System.out::println); } // 筛选颜色 public List<Product> filterProductByColor(List<Product> list) { 
    List<Product> prods = new ArrayList<>(); for (Product product : list) { 
    if ("红色".equals(product.getColor())) { 
    prods.add(product); } } return prods; } // 筛选价格 public List<Product> filterProductByPrice(List<Product> list) { 
    List<Product> prods = new ArrayList<>(); for (Product product : list) { 
    if (product.getPrice() < 8000) { 
    prods.add(product); } } return prods; } // 添加商品数据 private List<Product> getProducts() { 
    List<Product> products = new ArrayList<>(); for (int i = 1; i <= 10; i++) { 
    Product pro = Product.builder() .color(i % 2 == 0 ? "红色" : "绿色") .price(i * 1000) .build(); products.add(pro); } return products; } } 

使用lambda表达式+函数接口进行过滤

讯享网package cn.itcast.test.newf; import cn.itcast.pojo.Product; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Predicate; / * 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品. */ public class LambdaTest { 
    // lambda表达式过滤 @Test public void testFilterProducts2() { 
    List<Product> products = getProducts(); products = filterProductByPredicate(products, (product -> Objects.equals("红色", product.getColor()))); products = filterProductByPredicate(products, (product -> Double.compare(8000, product.getPrice()) > 0)); products.forEach(System.out::println); } // 使用lambda表达式, Predicate为断言的函数接口, 接口方法返回boolean类型 public List<Product> filterProductByPredicate(List<Product> list, Predicate<Product> p) { 
    List<Product> prods = new ArrayList<>(); for (Product product : list) { 
    if (p.test(product)) { 
    prods.add(product); } } return prods; } } 

不使用函数接口, 单纯使用lambda表达式实现

@Test public void testFilterProducts3() { 
    List<Product> products = getProducts(); products.stream() .filter((product -> Objects.equals("红色", product.getColor()))) .filter(product -> Double.compare(8000, product.getPrice()) > 0) .forEach(System.out::println); } 

测试结果

讯享网Product(color=红色, price=2000.0) Product(color=红色, price=4000.0) Product(color=红色, price=6000.0) 

2.4 总结

Lmabda表达式的语法总结: () -> ();

场景 语法
无参数无返回值 () -> System.out.println(“Hello WOrld”)
有一个参数无返回值 (x) -> System.out.println(x)
有且只有一个参数无返回值 x -> System.out.println(x)
有多个参数,有返回值,有多条lambda体语句 (x,y) -> {System.out.println(“xxx”);return xxxx;};
有多个参数,有返回值,只有一条lambda体语句 (x,y) -> xxxx

口诀:左右遇一省括号,左侧推断类型省

3. 函数式接口

函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。

3.1 简介

函数接口指的是只有一个抽象方法的接口,这样的接口可以隐式转换为Lambda表达式。 当然接口中可以包含其他的方法(默认,静态,私有) .

java.lang.Runnablejava.util.concurrent.Callable是函数式接口的**例子。函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface , 所以可以通过@FunctionalInterface注解检测接口是否为一个函数式接口 .

默认方法和静态方法不会破坏函数式接口的定义 , 如下所示:

@FunctionalInterface public interface FunctionalDefaultMethods { 
    void method(); // 默认方法 default void defaultMethod() { 
   } // 静态方法 static void staticMethod(){ 
   } } 

下面学习JDK8提供的常用函数式接口API, 主要有Consumer, Supplier , Predicate , Function.

3.2 Consumer

3.2.1 介绍

消费型接口,有参无返回值 , 参数类型由泛型决定 .

讯享网@FunctionalInterface public interface Consumer<T> { 
    //.... } 

3.2.2 accept方法

表示消费一个指定泛型类型的数据 .

void accept(T t); 

accept使用

讯享网/ * Consumer<T>:消费型接口,有参无返回值 */ public class ConsumerTest { 
    //定义一个方法 //方法的参数传递一个字符串的姓名 //方法的参数传递Consumer接口,泛型使用String //可以使用Consumer接口消费字符串的姓名 private void accept(String name, Consumer<String> consumer) { 
    consumer.accept(name); } @Test public void test() { 
    //对传递的字符串进行消费 //消费方式,把字符串进行反转输出 accept("admin", (name) -> { 
    String reName = new StringBuilder(name).reverse().toString(); System.out.println(reName); }); } } 

3.2.3 andThen方法

Consumer接口的默认方法andThen , 可以把两个Consumer接口组合到一起,再对数据进行消费 ( 谁写前边,谁先消费 ).

default Consumer<T> andThen(Consumer<? super T> after) { 
    Objects.requireNonNull(after); return (T t) -> { 
    accept(t); after.accept(t); }; } 

andThen方法使用

讯享网/ * Consumer<T>:消费型接口,有参无返回值 */ public class ConsumerTest { 
    //定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串 private void andThen(String s, Consumer<String> c1, Consumer<String> c2) { 
    //使用andThen方法,把两个Consumer接口连接到一起,先消费c1再消费c2 c1.andThen(c2).accept(s); } @Test public void test2() { 
    andThen("admin", (name) -> { 
    System.out.println(name); }, (name) -> { 
    //消费方式,把字符串转换为大写输出 System.out.println(name.toUpperCase(Locale.ROOT)); }); } } 

3.2.4 案例

需求: 格式化打印信息 , 字符串数组中存有多条信息,请按照格式,“姓名:xx,性别:xx“的格式信息打印出来 .

分析:

将打印姓名的动作作为第一个Consumer接口的Lambda实例
将打印性别的动作作为第二个Consumer接口的Lambda实例
将两个Consumer接口按照顺序”拼接“到一起

package cn.itcast.test.jdk8; import org.junit.Test; import java.util.Locale; import java.util.function.Consumer; / * Consumer<T>:消费型接口,有参无返回值 */ public class ConsumerTest { 
    //定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String // 用两个Consumer接口消费字符串 private void printInfo(String[] arr, Consumer<String> consumer, Consumer<String> consumer2) { 
    //遍历字符串数组 for (String s : arr) { 
    consumer.andThen(consumer2).accept(s); } } @Test public void test3() { 
    //定义一个字符串类型的数据 String[] arr = { 
   "刘辣子,女", "杨哈哈,男", "赵**,男"}; //调用printInfo方法,传递一个字符串数组,和两个Lambda表达式 printInfo(arr, (s) -> { 
    String name = s.split(",")[0]; System.out.print("姓名:" + name); }, (s) -> { 
    String gender = s.split(",")[1]; System.out.println("、性别:" + gender + "."); }); } } 

输出结果

讯享网姓名:刘辣子、性别:女. 姓名:杨哈哈、性别:男. 姓名:赵**、性别:男. 

3.3 Supplier

3.3.1 介绍

供给型接口,无参有返回值 .

@FunctionalInterface public interface Supplier<T> { 
    T get(); } 

3.3.2 get方法

Supplier接口仅包含一个无参的方法:T get() .

用来获取一个泛型参数指定类型的对象数据。意味着对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据。

讯享网/ * Supplier<T>:供给型接口,无参有返回值 */ public class SupplierTest { 
    public String getString(Supplier<String> supplier) { 
    return supplier.get(); } @Test public void test() { 
    //定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String String s = getString(() -> "人才"); System.out.println(s); } private Integer getMax(Supplier<Integer> supplier) { 
    //定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用包装类Integer return supplier.get(); } @Test public void test2() { 
    //定义一个int类型的数组,并赋值 int[] arr = { 
   100, 78, -887, 66, 90}; //调用getMax方法,方法参数Supplier是一个函数式接口,可以传递Lambda表达式 int maxValue = getMax(() -> { 
    int max = arr[0]; for (int a : arr) { 
    if (a > max) { 
    max = a; } } return max; }); System.out.println(maxValue); } } 

3.4 Predicate

3.4.1 介绍

断言型接口,有参有返回值, 对某种类型的数据进行判断 , 返回boolean类型 .

@FunctionalInterface public interface Predicate<T> { 
    //... } 

3.4.2 test方法

boolean test(T t):用于对指定类型数据进行判断, 符合条件返回true,不符合条件返回false

讯享网boolean test(T t); 

test方法的使用

/ * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { 
    //定义一个方法 //参数传递一个String类型的字符串 //传递一个Predicate接口,泛型使用String //使用predicate中的方法test对字符串进行判断,并把判断的结果返回 public boolean checkString(String s, Predicate<String> predicate) { 
    // 对某种数据类型的数据进行判断,结果返回一个boolean值 return predicate.test(s); } @Test public void test() { 
    //定义一个字符串 String s = "abcdef"; //调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式 //对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回 boolean b = checkString(s, str -> str.length() > 5); System.out.println(b); } } 

3.4.3 and方法

逻辑表达式:可以连接多个判断的条件 .

&&:与运算符. 连接的多个判断条件都为true才返回true, 否则false.

讯享网default Predicate<T> and(Predicate<? super T> other) { 
    Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } 

需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件必须同时满足,使用&&运算符连接两个条件 .

/ * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { 
    //定义一个方法,方法的参数,传递一个字符串 //传递俩个Predicate接口 //1、判断字符串的长度是否大于5 2、判断字符串中是否包含a 两个条件必须同时满足,使用&&运算符连接两个条件 private boolean checkString(String s, Predicate<String> predicate, Predicate<String> predicate2) { 
    //等价于return pre1.test(s)&&pre2.test(s); return predicate.and(predicate2).test(s); } @Test public void test2() { 
    //定义一个字符串 String s = "asdfgi"; //调用checkString方法,参数传递字符串和两个Lambda表达式 boolean b = checkString(s, str -> !StringUtils.isEmpty(str) && str.length() > 5, str -> !StringUtils.isEmpty(str) && str.contains("a")); System.out.println(b); } } 

3.4.4 or方法

||:或运算符,连接的多个判断条件有一个为true就返回true, 都为false才返回false.

讯享网default Predicate<T> or(Predicate<? super T> other) { 
    Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } 

需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件满足一个即可,使用||运算符连接两个条件 .

/ * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { 
    @Test public void test2() { 
    //定义一个字符串 String s = "asdfgi"; //调用checkStringOr方法,参数传递字符串和两个Lambda表达式 boolean b = checkStringOr(s, str -> !StringUtils.isEmpty(str) && str.length() > 5, str -> !StringUtils.isEmpty(str) && str.contains("a") ); System.out.println(b); } //定义一个方法,方法的参数,传递一个字符串 //传递俩个Predicate接口 //1、判断字符串的长度是否大于5 2、判断字符串中是否包含a 满足一个条件即可,使用||运算符连接两个条件 private boolean checkStringOr(String s, Predicate<String> predicate, Predicate<String> predicate2) { 
    return predicate.or(predicate2).test(s); } } 

3.4.5 negate方法

!:非(取反):非真则假,非假则真 .

讯享网default Predicate<T> negate() { 
    return (t) -> !test(t); } 

需求:判断一个字符串长度是否大于5
如果字符串的长度大于5,返回false
如果字符串的长度小于5,返回true
使用取反符号!对判断的结果进行取反 .

/ * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { 
    //定义一个方法,方法的参数,传递一个字符串 public boolean checkStringNegate(String s, Predicate<String> predicate) { 
    //使用Predicate接口判断字符串的长度是否大于5 //return !pre.test(s); return predicate.negate().test(s); } @Test public void testNegate() { 
    //定义一个字符串 String s = "asdfg"; //调用checkString方法,参数传递字符串和Lambda表达式 boolean b = checkStringNegate(s, str -> str.length() > 5); System.out.println(b); } } 

3.4.6 案例

需求: 集合信息筛选

数组当中有多条“姓名+性别”的信息如下:

讯享网String[] array = { 
   "刘小甜甜,女","杨哈哈,男","赵**,男"} 

通过Predicate接口的拼装将符合要求的字符串筛选到ArrayList集合中
需求同时满足两个条件:必须为女生,姓名为4个字
1、有两个判断条件,需要使用两个Predicate接口,对条件进行判断
2、必须同时满足两个条件,可以使用and方法连接两个判断条件

/ * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型 */ public class PredicateTest { 
    // 定义一个方法, 两个Predicate接口参数 public ArrayList<String> filter(String[] arr, Predicate<String> predicate, Predicate<String> predicate2) { 
    ArrayList<String> list = new ArrayList<>(); for (String s : arr) { 
    if (predicate.and(predicate2).test(s)) { 
    list.add(s); } } return list; } @Test public void test4() { 
    //定义一个存储字符串的数组 String[] array = { 
   "刘小甜甜,女", "杨哈哈,男", "赵**,男"}; //调用filter方法,传递字符串数组和两个Lambda表达式 ArrayList<String> list = filter(array, str -> !StringUtils.isEmpty(str) && str.split(",")[0].length() == 4, str -> !StringUtils.isEmpty(str) && str.split(",")[1].equals("女") ); list.forEach(System.out::println); } } 

3.5 Function

3.5.1 介绍

讯享网@FunctionalInterface public interface Function<T, R> { 
    // ... } 

3.5.2 apply方法

Function接口中最主要的抽象方法为:R apply(T,t),根据类型T的参数获取类型的R结果 .

R apply(T t); 

例如将String转换为Integer类型 .

讯享网/ * Function<T, R> : 函数式接口,有参有返回值 * java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据 */ public class FunctionTest { 
    //定义一个方法 //方法的参数传递一个字符串类型的整数 //方法的参数传递一个Function接口,泛型使用<String,Integer> //使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数 private Integer change(String s, Function<String, Integer> function) { 
    Integer apply = function.apply(s); return apply; } @Test public void test() { 
    //定义一个字符串类型的整数 String s = "1234"; //调用change方法传递字符串类型的整数,和Lambda表达式 Integer change = change(s, str -> Integer.parseInt(str)); System.out.println(change instanceof Integer); } } 

3.5.3 andThen方法

Function接口中的默认方法andThen,用来进行组合操作 . 先转换第一个, 再转换第二个.

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { 
    Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } 

需求:把String类型的“123”,转换为Integer类型,把转换后的结果+10, 再把增加之后的Integer类型的数据转换为String类型 .

分析: String->Integer-> 加10->String.

讯享网/ * Function<T, R> : 函数式接口,有参有返回值 * java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据 */ public class FunctionTest { 
    //定义一个方法 //参数传一个字符串类型的整数 //参数在传两个Function接口 //一个泛型使用Function<String,Integer> //一个泛型使用Function<Integer,String> private String change(String s, Function<String, Integer> fun, Function<Integer, String> fun2) { 
    System.out.println("apply..."); return fun.andThen(fun2).apply(s); } @Test public void test2() { 
    //定义一个字符串类型的整数 String s = "123"; //调用change方法,传递字符串和两个Lambda表达式 String change = change(s, str -> Integer.parseInt(s) + 10, integer -> String.valueOf(integer) ); System.out.println(change + ", " + change instanceof String); } } 

3.5.4 案例

使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:String str = “刘辣子,18”;

分析:
(1)、将字符串截取数字年龄部分,得到数字字符串
Function<String,String> “刘辣子,18”->“18”
(2)、将上一步的数字字符串转换为int类型的数字
Function<String,Integer>“18”->18
(3)、将上一步的int数字累加100,得到结果int数字
Function<Integer,Inerger>18->118

/ * Function<T, R> : 函数式接口,有参有返回值 * java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据 */ public class FunctionTest { 
    //定义一个方法 //参数传递包含姓名和年龄的字符串 //参数传递3个Function接口用于类型转换 private Integer change(String s, Function<String, String> fun, Function<String, Integer> fun2, Function<Integer, Integer> fun3) { 
    return fun.andThen(fun2).andThen(fun3).apply(s); } / * 1、将字符串截取数字年龄部分,得到数字字符串 * Function<String,String> “刘辣子,18”->“18” * 2、将上一步的数字字符串转换为int类型的数字 * Function<String,Integer>“18”->18 * 3、将上一步的int数字累加100,得到结果int数字 * Function<Integer,Inerger>18->118 */ @Test public void test3() { 
    //定义一个字符串 String s = "刘辣子,18"; //调用change方法,参数传递字符串和3个Lambda表达式 Integer change = change(s, str -> str.split(",")[1], str -> Integer.parseInt(str), num -> num + 100 ); System.out.println(change); } } 

3.6 自定义函数式接口

定义函数式接口

讯享网@FunctionalInterface public interface MyFunctionalInterface { 
    //定义一个抽象方法, public abstract可以省略 public abstract void method(); } 

接口实现类方式实现接口方法

public class MyFunctionalInterfaceImpl implements MyFunctionalInterface { 
    @Override public void method() { 
    System.out.println(">>>MyFunctionalInterfaceImpl"); } } 

函数式接口的使用

讯享网/ * 函数式接口的使用测试 */ public class MyFunctionalInterfaceTest { 
    @Test public void test() { 
    // 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象 show(new MyFunctionalInterfaceImpl()); // 调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类 show(new MyFunctionalInterface() { 
    @Override public void method() { 
    System.out.println(">>>MyFunctionalInterface的匿名内部类实现"); } }); // 调用show方法,方法的参数是一个函数式接口,可以传递Lambda表达式 show(() -> System.out.println(">>>MyFunctionalInterface的lambda表达式实现")); } // 定义一个方法,参数使用函数式接口MyFunctionalInterInterface public void show(MyFunctionalInterface myFunctionalInterface) { 
    myFunctionalInterface.method(); } } 

测试结果

>>>MyFunctionalInterfaceImpl >>>MyFunctionalInterface的匿名内部类实现 >>>MyFunctionalInterface的lambda表达式实现 

3.7 Lambda的延迟加载

使用函数式接口, 应用Lambda表达式方式实现可以达到延迟加载的目的, 优化提升系统性能.

场景: 程序运行根据日志级别输出日志信息.

实现方式1

讯享网/ * 根据不同日志级别输出日志信息 */ public class LogPrintTest { 
    @Test public void test() { 
    //定义三个日志信息 String msg1 = "Hello "; String msg2 = "World "; String msg3 = "Java"; //调用showLog方法,传递日志级别和日志信息 printLog(2, msg1 + msg2 + msg3); } //定义一个根据日志的级别,显示日志信息的方法 public void printLog(int level, String message) { 
    //对日志的等级进行判断,如果式1级别,那么输出日志信息 if (level == 1) { 
    System.out.println(message); } } } 

分析:

以上代码存在性能浪费, 调用printLog方法,第二个参数是一个拼接后的字符串, 先把字符串拼接好,然后在调用printLog方法打印日志信息. printLog方法中如果传递的日志等级不是1, 也会进行字符串的拼接, 导致不必要的资源浪费.

实现方式2: Lamdba表达式优化

/ * 根据不同日志级别输出日志信息 */ public class LogPrintTest { 
    @Test public void test2() { 
    //定义三个日志信息 String msg1 = "Hello "; String msg2 = "World "; String msg3 = "Java"; //调用showLog方法,参数MessageBulider是一个函数式接口,所以可以传递Lambda表达式 showLog(1, () -> msg1 + msg2 + msg3); } //定义一个显示日志的方法,方法的参数传递日志的等级和Message接口 public void showLog(int level, MessageBulider mb) { 
    //对日志的等级进行判断,如果是1级,则调用MessageBulider接口中的BuilderMessage方法 if (level == 1) { 
    System.out.println(mb.builderMessage()); } } // 定义一个函数式接口 @FunctionalInterface public interface MessageBulider { 
    //定义一个拼接消息的抽象方法,返回拼接的消息 String builderMessage(); } } 

分析:

使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中 , 只有满足日志等级是1的条件, 才会传递第二个参数(接口MessageBuilder中的方法builderMessage拼接日志信息); 如果不满足日志等级是1的条件, 那么MessageBuilder接口中的方法builderMessage也不会执行, 拼接字符串的代码也不会执行, 所以不会存在性能的浪费 .

3.8 函数式接口作为方法的参数

Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。

例如, java.lang.Runnable 接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。

讯享网@FunctionalInterface public interface Runnable { 
    public abstract void run(); } 

lambda表达式作为方法的参数使用

/ * lambda表达式作为方法的参数 */ public class LambdaTest2 { 
    @Test public void test() { 
    startThread(() -> System.out.println(">>>lambda表达式实现线程任务执行!")); startThread(new Runnable() { 
    @Override public void run() { 
    System.out.println(">>>匿名内部类方式实现线程任务执行!"); } }); } //定义一个方法startThread,方法的参数使用函数式接口Runnable private void startThread(Runnable task) { 
    //开启多线程 new Thread(task).start(); } } 

3.9 函数式接口作为方法的返回值

类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。

例如, 当需要通过一个方法来获取一个java.util.Comparator 接口类型的对象作为排序器时, 就可以调该方法获取。

讯享网@FunctionalInterface public interface Comparator<T> { 
    int compare(T o1, T o2); // ..... } 

lambda表达式作为方法的返回值使用

/ * lambda表达式作为方法的返回值 */ public class LambdaTest3 { 
    @Test public void test() { 
    String[] array = { 
   "aaa", "bbb", "ccc"}; System.out.println(Arrays.toString(array)); // lambda表达式实现 Comparator#compare方法返回值 Arrays.sort(array, (o1, o2) -> o2.length() - o1.length()); System.out.println(Arrays.toString(array)); // 匿名内部类方式实现 Comparator#compare方法返回值 Arrays.sort(array, new Comparator<String>() { 
    @Override public int compare(String o1, String o2) { 
    return o2.length() - o1.length(); } }); } } 

4. 方法引用

4.1 介绍

若lambda体中的内容有方法已经实现了,那么可以使用方法引用 , 也可以理解为方法引用是lambda表达式的另外一种表现形式, 并且其语法比lambda表达式更加简单 . 下面先通过一个简单的案例来说明.

讯享网/ * 方法的基本引用 */ public class PrintFunctionalTest { 
    @Test public void test() { 
    // 方式1: lambda表达式实现了函数式接口, 进行字符串的输出打印 this.printstring("test print", str -> System.out.println(str)); // 方式2: 方法引用方式实现字符串的输出打印 // 引用方法的参数就是lambda表达式的参数 this.printstring("test print by qout", System.out::println); } //定义一个方法,方法的参数传递PrintFunctionInterface接口 private void printstring(String str, PrintFunctionInterface printFunctionInterface) { 
    printFunctionInterface.printStr(str); } // 定义一个函数式接口 @FunctionalInterface public interface PrintFunctionInterface { 
    //定义打印字符串的抽象方法 void printStr(String str); } } 

双冒号:: 为引用运算符 , 它所在的表达式被称为方法引用 . 如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号::来引用该方法作为Lambda表达式的替代者。

语义分析 :

上例中,System.out对象中有一个重载的println(String str)方法恰好就是我们所需要的。 那么对于printstring方法的函数式接口参数,对比下面两种写法,完全等效:

  • Lambda表达式写法 str -> system.out.println(str);
  • 方法引用写法 System.out : : println

两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。

注意 : Lambda表达式中传递的参数一定是方法引用中的那个方法可以接收的类型 , 否则会抛出异常

4.2 对象名引用成员方法

对象名已经存在,成员方法也已经存在的, 就可以使用对象名来引用成员方法 .

public class PrintFunctionalTest { 
    @Test public void test2() { 
    // 方式1: 通过对象直接调用类的成员方法 this.printString2("test print by qout", str -> { 
    MethodRerObject methodRerObject = new MethodRerObject(); methodRerObject.printUpperCaseString(str); }); // 方式2: 使用方法引用来优化lambda, 使用对象名引用成员方法 MethodRerObject methodRerObject = new MethodRerObject(); this.printString2("test print by qout", methodRerObject::printUpperCaseString); } //定义一个方法,方法的参数传递PrintFunctionInterface接口 private void printString2(String str, PrintFunctionInterface printFunctionInterface) { 
    printFunctionInterface.printStr(str); } // 定义一个函数式接口 @FunctionalInterface public interface PrintFunctionInterface { 
    //定义打印字符串的抽象方法 void printStr(String str); } // 定义一个类,提供一个字符串输出方法 class MethodRerObject { 
    //定义一个成员方法,传递字符串,把字符串按照大写输出 public void printUpperCaseString(String str) { 
    System.out.println(str.toUpperCase(Locale.ROOT)); } } } 

上面自定义的函数式接口PrintFunctionInterface是消费型接口 , 可以直接使用JDK8自带的Consumer接口, 调用里面accept抽象方法消费数据即可.

讯享网@Test public void test3() { 
    MethodRerObject methodRerObject = new MethodRerObject(); this.printString3("test print by qout", methodRerObject::printUpperCaseString); } //定义一个方法,方法的参数传递Consumer接口, 也就是消费数据 private void printString3(String str, Consumer<String> consumer) { 
    consumer.accept(str); } 

4.3 类名引用成员方法

当lambda表达式的实现内容有两个相同类型的参数 , 且一个参数对象调用成员方法的参数列表是另外一个参数, 那么可以使用类名引用成员方法来简化代码.

@Test public void test4() { 
    // 方式1: 参数对象进行调用 boolean b = this.equalsStr("hello", "world", (str1, str2) -> !StringUtils.isEmpty(str1) && str1.equals(str2)); // 方式2: 方法引用-类名::实例方法名 b = this.equalsStr("hello", "world", String::equals); System.out.println(b); } // 定义一个成员方法, 比较两个字符串是否相等, 第三个参数传递一个BiFunction函数 private boolean equalsStr(String str1, String str2, BiFunction<String, String, Boolean> fun) { 
    return fun.apply(str1, str2); } 

4.4 类名引用静态方法

类已经存在,静态成员方法也已经存在,就可以通过类名直接引用静态成员方法 .

讯享网/ * 类名引用静态方法 */ public class StaticMethodReferenceTest { 
    @Test public void test() { 
    //方式1: 调用method方法,传递计算绝对值的整数和Lambda表达式 int number = method(-10, num -> Math.abs(num)); System.out.println(number); //方式2: 使用方法引用优化Lambda表达式, 通过类名引用静态方法 number = method(-10, Math::abs); System.out.println(number); } //定义一个方法,方法的参数传递要计算绝对值的整数和函数式接口Calcable public int method(int num, Calcable c) { 
    return c.calsAbs(num); } @FunctionalInterface public interface Calcable { 
    //定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回 int calsAbs(int num); } } 

案例2

@Test public void test2() { 
    Integer number = method2(100, 200, (num1, num2) -> Integer.compare(num1, num2)); System.out.println(number); number = method2(200, 100, Integer::compare); System.out.println(number); } // 定义一个方法, 消费参数num1和num2, 并返回消费结果 private Integer method2(Integer num1, Integer num2, BiFunction<Integer, Integer, Integer> biFunction) { 
    return biFunction.apply(num1, num2); } 

4.5 super引用父类成员方法

因为有子父类关系,所以存在一个关键字super,代表父类,所以直接使用Super调用父类的成员方法.

定义父类

讯享网public class Parent { 
    void sayHello() { 
    System.out.println("hello, i'm parent"); } } 

定义子类

/ * super引用父类成员方法 */ public class SuperReferenceTest extends Parent { 
    @Test public void test() { 
    //调用method方法,方法的参数Greetable是一个函数式接口,所以可传递Lambda greet(() -> { 
    Parent parent = new Parent(); parent.sayHello(); }); // 直接使用super调用 greet(() -> { 
    super.sayHello(); }); // supper优化lambda表达式, 方法引用 greet(super::sayHello); } //定义一个方法参数传递Greetable接口 public void greet(Greetable greetable) { 
    greetable.greet(); } @Override void sayHello() { 
    System.out.println("hello, i'm children"); } // 定义打招呼的函数式接口 @FunctionalInterface interface Greetable { 
    void greet(); } } 

4.6 this引用本类成员方法

使用this引用本类的成员方法

讯享网/ * super引用父类成员方法 */ public class SuperReferenceTest extends Parent { 
    @Test public void test2() { 
    // 方式1 greet(() -> { 
    SuperReferenceTest children = new SuperReferenceTest(); children.sayHello(); }); // 方式2 greet(() -> this.sayHello()); // 方式三 this引用 greet(this::sayHello); } //定义一个方法参数传递Greetable接口 public void greet(Greetable greetable) { 
    greetable.greet(); } @Override void sayHello() { 
    System.out.println("hello, i'm children"); } // 定义打招呼的函数式接口 @FunctionalInterface interface Greetable { 
    void greet(); } } 

4.7 类的构造器引用

格式:ClassName::new

定义一个创建Person对象的函数式接口 , 创建Person类.


讯享网

有参构造器方法的引用

/ * 构造器的方法引用 */ public class ConstructRefrenceTest { 
    / * 有参构造方法的引用 */ @Test public void test() { 
    // 方式1: lambda表达式, 自己new对象实例化, 传入name参数 this.printName("crysw", name -> new Person(name)); // 方式2: 传入的name参数就是构造方法的参数, 可以使用类的构造器引用来替换lambda表达式实现 this.printName("crysw agin", Person::new); } private void printName(String name, PersonBuilder personBuilder) { 
    personBuilder.builderPerson(name); System.out.println(name); } //定义一个创建Person对象的函数式接口 @FunctionalInterface interface PersonBuilder { 
    //定义一个方法,根据传递的姓名,创建Person对象返回 Person builderPerson(String name); } // 定义一个Person类 public class Person { 
    private String name; public Person() { 
    } public Person(String name) { 
    this.name = name; } public String getName() { 
    return name; } public void setName(String name) { 
    this.name = name; } } } 

无参构造方法的引用

讯享网/ * 无参构造方法的引用 */ @Test public void test2() { 
    // 方式1: Person person = this.getPerson(() -> new Person()); System.out.println(">>" + person); person = this.getPerson(Person::new); System.out.println(">>>" + person); } private Person getPerson(Supplier<Person> supplier) { 
    return supplier.get(); } 

4.8 数组的构造器引用

格式:Type[]::new

demo1

/ * 数组的构造器引用 */ public class ArrayRefrenceTest { 
    @Test public void test() { 
    // 方式1: lambda表达式,传递length参数构造数组对象 int[] array = this.createArray(2, (length) -> new int[length]); System.out.println(">>" + array.length); // 方式2: 传递的length参数就是构造数组对象的参数, 可以使用数组的构造器引用 array = this.createArray(2, int[]::new); System.out.println(">>>" + array.length); } //定义一个方法,方法的参数传递创建数组的长度和ArrayBuilder接口 //方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回 private int[] createArray(int length, ArrayBuilder arrayBuilder) { 
    return arrayBuilder.builderArray(length); } @FunctionalInterface interface ArrayBuilder { 
    //定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组 int[] builderArray(int length); } } 

demo2

讯享网@Test public void test2() { 
    // 方式1: lambda表达式, 通过传递的参数构建String数组 String[] strArr = this.getString(10, integer -> new String[integer]); System.out.println(">>" + strArr.length); // 方式2: String数组构造方法的引用实现 strArr = this.getString(20, String[]::new); System.out.println(">>>" + strArr.length); } // 定义一个方式, 参数传入函数接口, 将Integer类型参数转换为一个String[interger]类型返回 private String[] getString(Integer integer, Function<Integer, String[]> function) { 
    return function.apply(integer); } 

5. Stream流

5.1 介绍

Java8 中添加了一个新的流接口操作的概念, 在 java.util.stream包下面,相当于高级版的 Iterator,它可以通过 Lambda 表达式对集合进行大批量数据操作,或者各种便利、高效的聚合数据操作, 用于解决集合类库既有的弊端。

为什么选择Stream流

在 Java8 之前,主要通过 for 循环或者 Iterator 迭代来重新排序合并数据,又或者通过重新定义 Collections.sorts 的 Comparator 方法来实现,这两种方式对于大数据量系统来说,效率并不是很理想。

Stream 的聚合操作与数据库 SQL 的聚合操作 sorted、filter、map 等类似。我们在应用层就可以高效地实现类似数据库 SQL 的 聚合操作了,而在数据操作方面,Stream 不仅可以通过串行的方式实现数据操作,还可以通过并行的方式处理大批量数据,提高数据 的处理效率。

超类接口 BaseStream 派生出了 IntStreamLongStreamStream 以及 DoubleStream 4个流接口.

public interface IntStream extends BaseStream<Intege、r, IntStream> { 
   } public interface LongStream extends BaseStream<Long, LongStream> { 
   } public interface DoubleStream extends BaseStream<Double, DoubleStream> { 
   } public interface Stream<T> extends BaseStream<T, Stream<T>> { 
   } 

Stream操作的三个步骤 : 创建Stream, 中间操作(过滤, map), 终止操作.
在这里插入图片描述

5.2 创建Stream

获取Stream流的方式

  • 所有的Collection集合都可以通过默认的stream方法获取Stream流 .

    default Stream stream()

  • 所有的Collection集合也可以通过默认的parallelStream方法获取Stream流, 将数据拆分并行处理.

    default Stream<E> parallelStream()

  • Stream接口的of静态方法可以获取数组对应的流.

    static Stream of (T… values)

讯享网@Test public void test2() { 
    //把集合转换为Stream流 List<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); // parallelStream 将数据拆分成小块并行处理 Stream<String> parallelStream = list.parallelStream(); Set<String> set = new HashSet<>(); Stream<String> stream1 = set.stream(); Map<String, String> map = new HashMap<>(); //获取键,存储到一个Set集合中 Set<String> keySet = map.keySet(); Stream<String> stream2 = keySet.stream(); //获取值,存储到一个Collection集合中 Collection<String> values = map.values(); Stream<String> stream3 = values.stream(); //获取键值对(键与值的映射关系 entrySet) Set<Map.Entry<String, String>> entries = map.entrySet(); Stream<Map.Entry<String, String>> stream4 = entries.stream(); //把数组转换为Stream流 Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5); //可变参数可以传递数组 Integer[] arr = { 
   1, 2, 3, 4, 5}; Stream<Integer> stream5 = Stream.of(arr); String[] arr2 = { 
   "a", "bb", "ccc"}; Stream<String> stream6 = Stream.of(arr2); // 4.创建无限流 // 迭代 Stream<Integer> stream7 = Stream.iterate(0, x -> x + 2); //生成 Stream<Double> stream8 = Stream.generate(() -> Math.random()); } 

5.3 中间操作

中间操作会返回一个新的流,一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤. 然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),仅仅调用到这类方法,并没有真正开始流的遍历, 而是在终结操作开始的时候才真正开始执行。

中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作.

无状态是指元素的处理不受之前元素的影响;

有状态是指该操作只有拿到所有元素之后才能继续下去。

因为 Stream 操作类型非常多,列出常用的:

  • map():将流中的元素进行再次加工形成一个新流,流中的每一个元素映射为另外的元素。
  • filter(): 返回结果生成新的流中只包含满足筛选条件的数据
  • limit():返回指定数量的元素的流。返回的是 Stream 里前面的 n 个元素。
  • skip():和 limit()相反,将前几个元素跳过(取出)再返回一个流,如果流中的元素小于或者等于 n,就会返回一个空的流。
  • sorted():将流中的元素按照自然排序方式进行排序。
  • distinct():将流中的元素去重之后输出。
  • peek():对流中每个元素执行操作,并返回一个新的流,返回的流还是包含原来流中的元素。

5.3.1 遍历 forEach

/ * Stream流中的常用方法 遍历forEach * void forEach(Consumer<? super T> action); * 该方法接收一个Consumer接口函数,会将一个流元素交给函数进行处理 * forEach方法,用来遍历流中的数据 * 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法 */ @Test public void test3() { 
    //获取一个Stream流 Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七"); //使用Stream流中的方法forEach对Stream流中的数据进行遍历 stream.forEach(System.out::println); } 

5.3.2 过滤 filter

讯享网/ * Stream流中的常用方法filter,用于对Stream流中的数据进行过滤 * Stream filter(Predicate<? super T>predicate); * filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤 * Predicate中的抽象方法:boolean test(T t) */ @Test public void test4() { 
    //创建一个Stream流 Stream<String> stream = Stream.of("张三丰", "张翠山", "赵煜", "杨哲", "刘哈哈", "武**"); //对Stream流中的元素进行filter过滤,只要姓张的人 stream.filter(name -> name.startsWith("张")).forEach(System.out::println); // 第一个Stream流已经使用完毕,就会关闭了,所以第一个Stream就不能再调用方法了 // java.lang.IllegalStateException: stream has already been operated upon or closed // System.out.println(stream.count());  } 

5.3.3 映射 map

例如,我们之前使用的元素为圆形,调用map方法就可以映射为菱形 .
在这里插入图片描述

/ * 如果需要将流中的元素映射到另一个流中,可以使用map方法 * Stream map(Function<? super T,? extends R> mapper); * IntStream mapToInt(ToIntFunction mapper):产生包含这些元素的一个新的流 * 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流 * Function中的抽象方法 apply(T t) */ @Test public void test5() { 
    //获取一个String类型的Stream流 Stream<String> stream = Stream.of("1", "2", "3", "4", "5"); //使用map方法,把字符串类型的整数转换(映射)为Integer类型的整数 Stream<Integer> integerStream = stream.map((str) -> Integer.parseInt(str)); //遍历Stream1流 integerStream.forEach(System.out::println); } 

5.3.4 截取流中的元素 limit

讯享网/ * Stream流中常用方法:limit:用于截取流中的元素 * limit方法可以对流进行截取,只取用前n个, * Stream limit(long maxSize); * 参数是一个long型,如果集合当前长度大于参数则进行截取,否则不进行操作 * limit是一个延迟方法,只对流的元素进行截取,返回一个新的流,所以可以继续调用Stream流中的其他方法 */ @Test public void test7() { 
    //获取一个Stream流 String[] arr = { 
   "慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"}; Stream<String> stream = Stream.of(arr); Stream<String> limitStream = stream.limit(4); limitStream.forEachOrdered(System.out::println); } 

5.3.5 跳过元素 skip

/ * Stream流中的常用方法:skip:用于跳过元素 * 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流 * Stream ship(long n); * 如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流 */ @Test public void test8() { 
    //获取一个Stream流 String[] arr = { 
   "慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"}; Stream<String> stream = Stream.of(arr); Stream<String> stringStream = stream.skip(4); stringStream.forEach(System.out::println); } 

5.3.6 去重 distinct

讯享网/ * Stream流中的常用方法 distinct, 返回流中的不同元素, 达到去重的效果 * Stream<T> distinct(); * 需要流中的元素重写hashCode和equals方法 */ @Test public void test90() { 
    String[] arr = { 
   "喜羊羊", "喜羊羊", "懒羊羊", "美羊羊", "喜羊羊", "暖羊羊", "暖羊羊", "暖羊羊"}; Stream<String> stream = Stream.of(arr); stream.distinct().forEach(System.out::println); } 

5.3.7 排序 sorted

/ * Stream流中的常用方法 sorted, 排序 * Stream<T> sorted(); 默认排序 * Stream<T> sorted(Comparator<? super T> comparator); 自定义排序 * {@link Comparator#compare} */ @Test public void test91() { 
    //获取一个String类型的Stream流 Stream<String> stream = Stream.of("3", "1", "2", "5", "4"); // 升序 stream.sorted().forEach(System.out::println); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>"); Stream<String> stream2 = Stream.of("3", "1", "2", "5", "4"); // 降序, 自定义排序 stream2.sorted((a, b) -> b.compareTo(a)).forEach(System.out::println); } 

5.3.8 组合流 concat

讯享网/ * Stream流中的常用方法concat 用于把流组合到一起 * 如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat, 返回一个新的流 * static Stream concat(Stream<? extends T>a,Stream(Stream<? extends T>b) */ @Test public void test9() { 
    String[] arr = { 
   "慢羊羊", "喜羊羊", "懒羊羊", "美羊羊", "沸羊羊", "暖羊羊", "快羊羊", "瘦羊羊"}; Stream<String> stream = Stream.of("张三丰", "张翠山", "赵煜", "杨哲", "刘哈哈", "武**"); Stream<String> stream1 = Stream.of(arr); //把以上两个流组合为一个流 Stream<String> concatStream = Stream.concat(stream, stream1); concatStream.forEach(System.out::println); } 

5.3.9 迭代 iterate

@Test public void test6() { 
    Stream.iterate(0, n -> n + 3). // 从0开始, 迭代加3 limit(10). // 返回10个元素 forEach(x -> System.out.print(x + " ")); // 执行结果 0 3 6 9 12 15 18 21 24 27  } 

5.4 终止操作

终止操作是指返回最终的结果。一个流只能有一个终止操作,当这个操作执行后,这个流就被使用完毕了,无法再**作。终止操作的执行才会真正开始流的遍历,并且会生成一个结果。

终止操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作.

短路是指遇到某些符合条件的元素就可以得到最终结果.

非短路是指必须处理完所有元素才能得到最终结果.

常用终止操作的方法:

allMatch-检查是否匹配所有元素
anyMatch-检查是否至少匹配一个元素
noneMatch-检查是否没有匹配所有元素
findFirst-返回第一个元素
findAny-返回当前流中的任意元素
count-返回流中元素的总个数
max-返回流中最大值
min-返回流中最小值

5.4.1 统计个数 count

讯享网/ * Stream流中的常用方法,count,用于统计Stream流中元素的个数 * long count(); * count方法是一个终结方法,返回值是一个long类型的整数 * 所以不能再继续调用Stream流中的其他方法 */ @Test public void test6() { 
    //获取一个Stream流 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); Stream<Integer> stream = list.stream(); long count = stream.count(); System.out.println(count); } 

5.4.2 缩减数据 reduce

reduce 的主要作用是把 Stream 元素组合起来。它提供一个起始值, 然后依照运算规则(BinaryOperator) , 和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就可以用reduce实现,如下:

@Test public void test7() { 
    // reduce(T identity, BinaryOperator<T> accumulator); List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Integer sum = list.stream() .reduce(0, (x, y) -> x + y); System.out.println(sum); // Optional<T> reduce(BinaryOperator<T> accumulator); Optional<Integer> sum2 = list.stream() .reduce(Integer::sum); System.out.println(sum2.get()); } 

字符串拼接, 求和, 计算最大值, 最小值操作.

讯享网@Test public void test4() { 
    // 字符串连接,有起始值(空字符串), concat = "ABCD" String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); System.out.println(concat); // 求最小值,minValue = -3.0 double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MIN_VALUE, Double::min); System.out.println(minValue); minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double::min).get(); System.out.println(minValue); // 求和,sumValue = 10, 有起始值 int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); System.out.println(sumValue); // 求和,sumValue = 10, 无起始值 sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get(); System.out.println(sumValue); // 过滤,字符串连接,有起始值(空字符串), concat = "ace" concat = Stream.of("a", "B", "c", "D", "e", "F") .filter(x -> x.compareTo("Z") > 0) .reduce("", String::concat); System.out.println(concat); } 

5.4.3 收集数据 collect

对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中 .

collect(Collectors.toList()) 返回List集合

@Test public void test8(){ 
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); List<Integer> squaresList = numbers.stream() .map(i -> i * i) // 计算元素平方 .distinct() // 去重 .collect(Collectors.toList()); // 聚合元素 System.out.println("Squares List: " + squaresList); } 

collect(Collectors.joining(“,”) )聚合数据后加入分隔符, 并转换为字符串类型返回

讯享网@Test public void testJdk8P() { 
    //数字平方排序(倒叙)输出 List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); String squaresList = numbers.stream() .map(i -> i * i) // 获取对应的平方数 .sorted((x, y) -> y - x) // 降序排序 .map(x -> String.valueOf(x)) // 集合元素由Integer转换为String .collect(Collectors.joining(","));// 聚合数据且加入分隔符, 返回String类型结果集 System.out.println(squaresList); // 49,25,9,9,9,4,4 } 

collect(Collectors.toMap(Function keyMapper, Function valueMapper)) 将List转换成Map, keyMapper的lambda实现对应Map的key, valueMapper的lambda实现对应Map的value

@Test public void testJdk8P2() { 
    //字符串转 map 输出 List<String> strList = Arrays.asList("a", "ba", "bb", "abc", "cbb", "bba", "cab"); Map<Integer, String> strMap = strList.stream().collect(Collectors.toMap( // key-value key=索引 value=str str -> strList.indexOf(str), // key=index str -> str) // value=str ); strMap.forEach((key, value) -> { 
    System.out.println(key + ":" + value); / 执行结果 0:a 1:ba 2:bb 3:abc 4:cbb */ }); } 

5.5 JDK7与JDK8实现对比

JDK7实现

讯享网@Test public void testJdk7() { 
    System.out.println("使用 Java 7: "); // 计算空字符串 List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); System.out.println("列表: " + strings); long count = getCountEmptyStringUsingJava7(strings); System.out.println("空字符数量为: " + count); // 计算字符串长度为3的个数 count = getCountLength3UsingJava7(strings); System.out.println("字符串长度为 3 的数量为: " + count); // 删除空字符串 List<String> filtered = deleteEmptyStringsUsingJava7(strings); System.out.println("筛选后的列表: " + filtered); // 删除空字符串,并使用逗号把它们合并起来 String mergedString = getMergedStringUsingJava7(strings, ", "); System.out.println("合并字符串: " + mergedString); // 获取列表元素平方数 List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); List<Integer> squaresList = getSquares(numbers); System.out.println("平方数列表: " + squaresList); List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19); System.out.println("列表: " + integers); System.out.println("列表中最大的数 : " + getMax(integers)); System.out.println("列表中最大的数 : " + getMax2(integers)); System.out.println("列表中最小的数 : " + getMin(integers)); System.out.println("所有数之和 : " + getSum(integers)); System.out.println("平均数 : " + getAverage(integers)); System.out.println("随机数: " + new Random().nextInt()); } // 计算空字符串的个数 private static int getCountEmptyStringUsingJava7(List<String> strings) { 
    int count = 0; for (String string : strings) { 
    if (string.isEmpty()) { 
    count++; } } return count; } // 计算字符串长度为3的个数 private static int getCountLength3UsingJava7(List<String> strings) { 
    int count = 0; for (String string : strings) { 
    if (!StringUtils.isEmpty(string) && Objects.equals(3, string.length())) { 
    count++; } } return count; } // 删除空字符串 private List<String> deleteEmptyStringsUsingJava7(List<String> strings) { 
    List<String> resultList = new ArrayList<>(); for (String string : strings) { 
    if (!StringUtils.isEmpty(string)) { 
    resultList.add(string); } } return resultList; } // 删除空字符串,并使用分隔符(逗号)把它们合并起来 private String getMergedStringUsingJava7(List<String> strings, String delimiter) { 
    List<String> resultList = deleteEmptyStringsUsingJava7(strings); StringBuilder sb = new StringBuilder(); for (int i = 0; i < resultList.size(); i++) { 
    if (i == resultList.size() - 1) { 
    sb.append(resultList.get(i)); } else { 
    sb.append(resultList.get(i)).append(delimiter); } } return sb.toString(); } // 获取列表元素平方数 private List<Integer> getSquares(List<Integer> numbers) { 
    List<Integer> resultList = new ArrayList<>(); for (int i = 0; i < numbers.size(); i++) { 
    Integer num = numbers.get(i); resultList.add(num * num); } return resultList; } private Integer getMax(List<Integer> integers) { 
    Integer max = integers.get(0); for (int i = 0; i < integers.size(); i++) { 
    Integer num = integers.get(i); if (num > max) { 
    max = num; } } return max; } private Integer getMax2(List<Integer> integers) { 
    // 先集合元素升序排序, 然后取最大值 Collections.sort(integers, new Comparator<Integer>() { 
    @Override public int compare(Integer o1, Integer o2) { 
    return o1.compareTo(o2); } }); return integers.get(integers.size() - 1); } private Integer getMin(List<Integer> integers) { 
    Integer min = integers.get(0); for (int i = 0; i < integers.size(); i++) { 
    Integer num = integers.get(i); if (num < min) { 
    min = num; } } return min; } // 集合元素求和 private Integer getSum(List<Integer> integers) { 
    Integer sum = 0; for (int i = 0; i < integers.size(); i++) { 
    Integer num = integers.get(i); sum += num; } return sum; } // 集合元素计算平均数 private double getAverage(List<Integer> integers) { 
    double average = getSum(integers) * 1.00 / integers.size(); return average; } 

JDK8实现

@Test public void testJdk8() { 
    System.out.println("使用 Java 8: "); List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); System.out.println("列表: " + strings); long count = strings.stream().filter(str -> StringUtils.isEmpty(str)).count(); System.out.println("空字符串数量:" + count); count = strings.stream().filter(str -> !StringUtils.isEmpty(str) && Objects.equals(3, str.length())).count(); System.out.println("字符串长度为 3 的数量为: " + count); List<String> filtered = strings.stream().filter(str -> !StringUtils.isEmpty(str)).collect(Collectors.toList()); System.out.println("筛选后的列表: " + filtered); String mergedString = strings.stream().filter(str -> !StringUtils.isEmpty(str)).collect(Collectors.joining(", ")); System.out.println("合并字符串: " + mergedString); List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); List<Integer> squaresList = numbers.stream() .map(i -> i * i) .distinct() .collect(Collectors.toList()); System.out.println("Squares List: " + squaresList); List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19); System.out.println("列表: " + integers); IntSummaryStatistics stats = integers.stream().mapToInt(x -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("所有数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage()); System.out.println("随机数: "); Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println); // 并行处理 count = strings.parallelStream().filter(str -> StringUtils.isEmpty(str)).count(); System.out.println("空字符串的数量为: " + count); } 

5.6 综合练习

文件流读取文件的文本内容, 然后转换成Stream流进行一些列操作.

讯享网@Test public void test5() throws IOException { 
    // 找出最长一行的长度 BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log")); int longest = br.lines() .mapToInt(String::length) // 读取每行的字符长度 .max() // 最大长度 .getAsInt(); // 转换为int返回 br.close(); System.out.println(longest); // 找出全文的单词,转小写,并排序 List<String> words = br.lines(). flatMap(line -> Stream.of(line.split(" "))) // 每行分割单词 .filter(word -> word.length() > 0) // 去除空格 .map(String::toLowerCase) // 单词转大写 .distinct() // 去重 .sorted() // 默认升序排序 .collect(Collectors.toList()); // 收集数据 br.close(); System.out.println(words); } 

6. 接口中的默认方法和静态方法

Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。 默认方法让开发者可以在不破坏二进制兼容性的前提下,往现存接口中添加新的方法,不用强制那些已经实现了该接口的类也同时实现这个新加的方法。

6.1 默认方法

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:

private interface Defaulable { 
    // 默认方法 default String notRequired() { 
    return "Default implementation"; } } // 继承接口的默认方法 private static class DefaultableImpl implements Defaulable { 
    } // 重写接口的默认方法 private static class OverridableImpl implements Defaulable { 
    @Override public String notRequired() { 
    return "Overridden implementation"; } } 

Defaulable接口使用关键字default定义了一个默认方法notRequired()DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。

6.2 静态方法

Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:

讯享网private interface DefaulableFactory { 
    // 静态方法 static Defaulable create( Supplier< Defaulable > supplier ) { 
    return supplier.get(); } } 

6.3 **实践

下面的代码片段整合了默认方法和静态方法的使用场景:

/ * 接口中的默认方法和静态方法 */ public class DefaultNStaticInterfaceTest { 
    @Test public void test() { 
    Defaulable defaulable = DefaulableFactory.create(DefaultableImpl::new); System.out.println(defaulable.notRequired()); defaulable = DefaulableFactory.create(OverridableImpl::new); System.out.println(defaulable.notRequired()); } private interface DefaulableFactory { 
    // 静态方法 static Defaulable create(Supplier<Defaulable> supplier) { 
    return supplier.get(); } } private interface Defaulable { 
    // 默认方法 default String notRequired() { 
    return "Default implementation"; } } // 继承接口的默认方法 private static class DefaultableImpl implements Defaulable { 
    } // 重写接口的默认方法 private static class OverridableImpl implements Defaulable { 
    @Override public String notRequired() { 
    return "Overridden implementation"; } } } 

尽管默认方法有这么多好处,但在实际开发中应该谨慎使用. 在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档。

7. 新的日期时间API

7.1 LocalDateTime

LocalDateTime是一个不可变的日期时间对象,代表日期时间,通常被视为年 - 月 - 日 - 时 - 分 - 秒。 也可以访问其他日期和时间字段,例如日期,星期几和星期。 时间表示为纳秒精度。 例如,值“2007年10月2日在13:45.30.”可以存储在LocalDateTime 。

获取当前的日期时间

讯享网LocalDateTime date = LocalDateTime.now(); System.out.println("当前时间: " + date); System.out.println(date.getYear()); System.out.println(date.getMonthValue()); System.out.println(date.getDayOfMonth()); System.out.println(date.getHour()); System.out.println(date.getMinute()); System.out.println(date.getSecond()); System.out.println(date.getNano()); 

手动创建一个LocalDateTime实例

LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31); //LocalDateTime date3 = date2.withMonth(10).withDayOfMonth(10).withYear(2012); System.out.println(date2); // 进行加操作,得到新的日期实例 LocalDateTime date3 = date2.plusDays(12); System.out.println(date3); // 进行减操作,得到新的日期实例 LocalDateTime date4 = date3.minusYears(2); System.out.println(date4); 

7.2 LocalDate

LocalDate是一个不可变的日期对象,表示日期,通常被视为年月日。 也可以访问其他日期字段,例如日期,星期几和星期。 例如,值“2007年10月2日”可存储在LocalDate 。

创建LocalDate实例

讯享网// 12 december 2014 LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12); System.out.println("date3: " + date3); // 解析日期字符串 LocalDate parseDate = LocalDate.parse("2014-12-12"); System.out.println("parseDate: " + parseDate); 

7.3 LocalTime

LocalTime是一个不可变的时间对象,代表一个时间,通常被看作是小时 - 秒。 时间表示为纳秒精度。

创建LocalTime实例

// 22 小时 15 分钟 LocalTime date4 = LocalTime.of(22, 15); System.out.println("date4: " + date4); // 解析时间字符串 LocalTime date5 = LocalTime.parse("20:15:30"); System.out.println("date5: " + date5); 

7.4 ZonedDateTime

ZonedDateTime是具有时区的日期时间的不可变表示。 此类存储所有日期和时间字段,精度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间。

创建ZonedDateTime对象

讯享网// 获取当前时间日期 ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]"); System.out.println("date1: " + date1); // date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai] // 获取时区 System.out.println(date1.getZone().getId()); // Asia/Shanghai ZoneId id = ZoneId.of("Europe/Paris"); System.out.println("ZoneId: " + id); // Europe/Paris 

7.5 Instant

Instant表示在时间线上建立单个瞬时点。 这可能用于在应用程序中记录事件时间戳。

// 时间戳 1970年1月1日00:00:00 到某一个时间点的毫秒值 // 默认获取UTC时区 Instant ins = Instant.now(); System.out.println(ins); // 下面两个等价 long x = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli(); System.out.println(x); long x1 = System.currentTimeMillis(); System.out.println(x1); System.out.println(Instant.now().toEpochMilli()); System.out.println( Instant.now().atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli() ); 

7.6 Duration

Duration用于计算两个时间之间的间隔 .

计算Instance

讯享网@Test public void test6() throws InterruptedException { 
    // Duration:计算两个时间之间的间隔 Instant ins1 = Instant.now(); TimeUnit.MILLISECONDS.sleep(1000); Instant ins2 = Instant.now(); Duration dura = Duration.between(ins1, ins2); System.out.println(dura); System.out.println(dura.toMillis()); } 

计算LocalTime

@Test public void test7() throws InterruptedException { 
    // Period:计算两个日期之间的间隔 LocalTime localTime = LocalTime.now(); TimeUnit.MILLISECONDS.sleep(1000); LocalTime localTime2 = LocalTime.now(); Duration du2 = Duration.between(localTime, localTime2); System.out.println(du2); System.out.println(du2.toMillis()); } 

计算LocalDateTime

讯享网@Test public void test8() throws InterruptedException { 
    LocalDateTime localDateTime = LocalDateTime.now(); TimeUnit.MILLISECONDS.sleep(1000); LocalDateTime localDateTime2 = LocalDateTime.now(); Duration du2 = Duration.between(localDateTime, localDateTime2); System.out.println(du2); System.out.println(du2.toMillis()); } 

7.7 Period

Period:计算两个日期之间的间隔.

@Test public void test9() throws InterruptedException { 
    LocalDate localDate = LocalDate.now(); TimeUnit.MILLISECONDS.sleep(1000); LocalDate localDate1 = LocalDate.now(); Period p = Period.between(localDate, localDate1); System.out.println(p); System.out.println(p.getYears() + "-" + p.getMonths() + "-" + p.getDays()); } 

7.8 常用API转换

LocalDate

讯享网@Test public void localDateTest() { 
    //获取当前日期,只含年月日 固定格式 yyyy-MM-dd 2018-05-04 LocalDate today = LocalDate.now(); // 根据年月日取日期,5月就是5, LocalDate oldDate = LocalDate.of(2018, 5, 1); // 根据字符串取:默认格式yyyy-MM-dd,02不能写成2 LocalDate yesteday = LocalDate.parse("2022-05-03"); // 如果不是闰年 传入29号也会报错 LocalDate.parse("2022-02-28"); } 

LocalDate常用转化

/ * 日期转换常用,第一天或者最后一天... */ @Test public void localDateTransferTest() { 
    //2018-05-04 LocalDate today = LocalDate.now(); // 取本月第1天: 2018-05-01 LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 取本月第2天:2018-05-02 LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 取本月最后一天,再也不用计算是28,29,30还是31: 2018-05-31 LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 取下一天:2018-06-01 LocalDate nextDay = lastDayOfThisMonth.plusDays(1); // 取2018年10月第一个周三 so easy?: 2018-10-03 LocalDate thirdMondayOf2018 = LocalDate.parse("2018-10-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY)); } 

LocalTime

讯享网@Test public void localTimeTest() { 
    //16:25:46.448(纳秒值) LocalTime todayTimeWithMillisTime = LocalTime.now(); //16:28:48 不带纳秒值 LocalTime todayTimeWithNoMillisTime = LocalTime.now().withNano(0); LocalTime time1 = LocalTime.parse("23:59:59"); } 

LocalDateTime

@Test public void localDateTimeTest() { 
    //LocalDateTime转化为时间戳 毫秒值 long time1 = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli(); long time2 = System.currentTimeMillis(); //时间戳转化为localdatetime DateTimeFormatter df = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS"); String dateTime = df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time1), ZoneId.of("Asia/Shanghai"))); System.out.println(dateTime); } 

Clock

讯享网@Test public void clockTest() { 
    // Get the system clock as UTC offset final Clock clock = Clock.systemUTC(); clock.withZone(ZoneId.of("Asia/Shanghai")); System.out.println(clock.instant()); System.out.println(clock.millis()); System.out.println(clock.getZone().getId()); } 

7.9 总结

  • 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum.
  • java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
  • java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍
  • 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。

8. Optional容器

8.1 介绍

Java应用中最常见的bug就是空指针异常NPE。在Java 8之前,Google Guava引入了Optionals类来解决这个空指针异常NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。官方资料

Optional仅仅是一个容器:存放T类型的值或者null。它提供了方法来避免显式的null检查.

常用的方法介绍:

Optional.of(T t) 创建一个非空Optional实例, 如果传递的参数是null,抛出NullPointerException.

Optional.empty() 创建一个空的Optional实例

Optional.ofNullable(T t) 若参数T t不为null,创建一个非空Optional实例,否则创建一个空实例.

isPresent() 如果Optional实例持有一个非空值 , 返回true ; 否则 返回false.

ifPresent(Consumer<? super T> consumer) 如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。

orElse(T t) 如果Optional实例包含值,返回该值,否则返回默认值T t.

orElseGet(Supplier s) 如果Optional实例包含值,返回该值,否则返回函数接口的lambda表达式实现生成的默认值;

map(Function f) 如果Optional实例有值, 将现有值转换成新的值,否则返回Optional.empty();

flatMap(Function f, , Optional> mapper) 与 map(Function f)类似, 返回值是Optional.

提示:Optional.of(null) 会直接报NullPointerException.

8.2 Optional测试1

@Test public void test() { 
    Integer value1 = null; Integer value2 = new Integer(10); // Optional.ofNullable - 允许传递为 null 参数 Optional a = Optional.ofNullable(value1); // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException (NPE) Optional b = Optional.of(value2); System.out.println("求和:" + sum(a, b)); System.out.println(a.toString()); System.out.println(b.toString()); } private Integer sum(Optional<Integer> a, Optional<Integer> b) { 
    // Optional.isPresent - 判断值是否存在 System.out.println("第一个参数值存在: " + a.isPresent()); System.out.println("第二个参数值存在: " + b.isPresent()); // Optional.orElse - 如果值存在,返回它,否则返回默认值 Integer value = a.orElse(new Integer(20)); //Optional.get - 获取值,值需要存在 Integer value2 = b.get(); return value + value2; } 

8.3 Optional测试2

讯享网@Test public void test2() { 
    Optional<String> fullName = Optional.ofNullable("admin"); System.out.println("Full Name is set? " + fullName.isPresent()); System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]")); System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!")); } // 执行结果 Full Name is set? true Full Name: admin Hey admin! 

8.4 Optional测试3

private void print(String s) { 
    // 如果参数s构建的Optional实例不为空, 则执行指定的lambda进行消费 Optional.ofNullable(s).ifPresent(System.out::println); } private Integer getLength(String s) { 
    // 如果参数s构建的Optional实例不为空, 则继续转换获取s的长度, 如果为空返回-1 return Optional.ofNullable(s).map(String::length).orElse(-1); } @Test public void test3() { 
    String strA = "abcd "; String strB = null; print(strA); // abcd print(strB); // 没有操作被执行 System.out.println(getLength(strA)); // 4 System.out.println(getLength(strB)); // -1 // java.util.NoSuchElementException: No value present System.out.println(Optional.empty().get()); // java.lang.NullPointerException System.out.println(Optional.of(null)); // java.util.NoSuchElementException: No value present System.out.println(Optional.ofNullable(null).get()); } 

9. 并行流和串行流

在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api中声明可以通过parallel()sequential()方法在并行流和串行流之间进行切换; 集合中的 parallelStream ()方法开启一个并行流处理。jdk1.8并行流使用的是fork/join框架进行并行操作.

常用流转换方法:

  • boolean isParallel():判断一个流是否是并行流,如果是则返回 true,否则返回 false。
  • S sequential():基于调用流返回一个顺序流,如果调用流本身就是顺序流,则返回其本身。
  • S parallel():基于调用流,返回一个并行流,如果调用流本身就是并行流,则返回其本身。
  • S unordered():基于调用流,返回一个无序流。
  • S onClose(Runnable closeHandler):返回一个新流,closeHandler 指定了该流的关闭处理程序,当关闭该流时,将调用这个处理程序。
  • void close():从 AutoCloseable 继承来的,调用注册关闭处理程序,关闭调用流(很少会被使用到)。

Stream#parallel()

讯享网/ * java8 并行流 parallel() */ @Test public void test2() { 
    //开始时间 Instant start = Instant.now(); // 并行流计算 累加求和 long reduce = LongStream.rangeClosed(0, 10L).parallel() .reduce(0, Long::sum); System.out.println("result=" + reduce); //结束时间 Instant end = Instant.now(); System.out.println(Duration.between(start, end).getSeconds()); } 

List#parallelStream()

@Test public void test3() { 
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); // 顺序流执行 list.stream().forEach(System.out::print); // 12345 System.out.println("\n"); // 并行流, 多核多线程处理, 打印结果不是顺序打印 list.parallelStream() .forEach(System.out::print); // 34521 System.out.println("\n"); } 

10. Fork/Join

Fork/Join 框架:Java7就出现的框架, 在必要的情况下,将一个大任务拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行汇总( join )。

Fork/Join框架学习博文

在一般的线程池中, 如果一个线程正在执行的任务由于某些原因无法继续运行, 那么该线程会处于等待(阻塞)状态.

在fork/join框架中,如果某个子任务由于等待另外一个子任务的完成而无法继续运行. 那么处理该子任务的线程会主动寻找其他尚未运行的子任务来执行. 这种方式减少了线程的等待时间, 提高了性能.。

讯享网/ * 要想使用Fark—Join,类必须继承 * RecursiveAction(无返回值) * Or * RecursiveTask(有返回值) * */ public class ForkJoin extends RecursiveTask<Long> { 
    / * 要想使用Fark—Join * 类必须继承RecursiveAction(无返回值) 或者 RecursiveTask(有返回值) */ private static final long serialVersionUID = L; private long start; private long end; public ForkJoin() { 
    } public ForkJoin(long start, long end) { 
    this.start = start; this.end = end; } // 定义阙值 private static final long THRESHOLD = 10000L; @Override protected Long compute() { 
    if (end - start <= THRESHOLD) { 
    long sum = 0; for (long i = start; i < end; i++) { 
    sum += i; } return sum; } else { 
    long middle = (end - start) / 2; ForkJoin left = new ForkJoin(start, middle); //拆分子任务,压入线程队列 left.fork(); ForkJoin right = new ForkJoin(middle + 1, end); right.fork(); //合并并返回 return left.join() + right.join(); } } } 

测试

/ * 实现数的累加 */ @Test public void test1() { 
    //开始时间 Instant start = Instant.now(); //这里需要一个线程池的支持 ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoin(0L, L); // 没有返回值 pool.execute(task); // 有返回值 // long sum = pool.invoke(task); // System.out.println("sum=" + sum); //结束时间 Instant end = Instant.now(); System.out.println(Duration.between(start, end).get(ChronoUnit.SECONDS)); } 

over.

小讯
上一篇 2025-01-28 20:39
下一篇 2025-03-11 20:37

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/29628.html