JDK8新特性


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 介绍

函数式接口,有参有返回值 . java.util.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据.
前者称为前置条件,后者称为后置条件 .

@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表达式,继而传递给 System.out.println方法去处理。
第二种语义是直接让System.out中的println方法来取代Lambda表达式。

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

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

Stream流属于一个管道流,只能被消费(使用)一次.
第一个Stream流调用完毕,数据就会流转到下一个Stream上, 而这时第一个Stream流已经使用完毕,就会关闭了, 所以第一个Stream就不能再调用方法了. Stream流调用filter方法后返回一个新的Stream流.

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.123456789”可以存储在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 = 23423422L;

    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, 10000000000L);
    // 没有返回值
    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.


文章作者: 王子
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王子 !
评论
  目录