「Java 进阶」--Lambda & 函数式编程

日期: 2025-02-27 00:02:52 |浏览: 83|编号: 76408

友情提醒:信息内容由网友发布,本站并不对内容真实性负责,请自鉴内容真实性。

「Java 进阶」--Lambda & 函数式编程

前些年 Scala 大肆流行,打出来 Java 颠覆者的旗号,究其底气来源,无非是函数式和面向对象的“完美结合”,各式各样的“语法糖”,但其过高的学习门槛,又给了新来者当头一棒。

随着 Java8 的发布, 特性的引入,之前的焦灼局面是否有所转变,让我们一起揭开 Java 函数式编程的面纱:

面向对象 VS 函数式 和 类库的升级改造(默认方法、静态方法、、) 下模式的进化 下并发程序1. 面向对象 VS 函数式编程

一句话总结两种的关系:面向对象编程是对数据进行抽象;而函数式编程是对行为进行抽象。

在现实世界中,数据和行为并存,程序也应如此,可喜可贺的是在 Java 世界中,两者也开启了融合之旅。

首先思考一个问题, 在 Java 编程中,我们如何进行行为传递,例如我们需要打印线程名称和当前时间,并将该任务提交到线程池中运行,会有哪些方法?

方法 1:新建 class Task 实现 接口

    public class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
        }
    }
    executorService.submit(new Task());

方法 2:匿名内部类实现 接口

    executorService.submit(new Runnable() {
            @Override
            public void run() {
              System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
            }
        });

方法 3:使用 表达式

    executorService.submit(()-> System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms"));

方法 4:使用方法引用

     private void print(){
        System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
    }
    {
        executorService.submit(this::print);
    }

通过上面不同的行为传递方式,能够比较直观的体会到随着函数式特性的引入,行为传递少了很多样板代码,增加了一丝灵活;可见表达式是一种紧凑的、传递行为的方式。

2. 和

Java 函数式编程,只有两个核心概念:

(函数接口)是只有一个抽象方法的接口,用作 表达式的类型。

表达式,及要传递的行为代码,更像是一个匿名函数(当然 java 中并没有这个概念),将行为像数据那样进行传递。

换个好理解但是不正规的说法, 为类型, 表达式为值;我们可以将一个 表达式赋予一个符合 要求的接口变量(局部变量、方法参数)。

2.1. 表达式

先看几个 表达式的例子:

        // 不包含参数,用()表示没有参数
        // 表达式主体只有一个语句,可以省略{}
        Runnable helloWord = () -> System.out.println("Hello World");
        // 表达式主体由多个语句组成,不能省略{}
        Runnable helloWords = () -> {
            System.out.println("Hello");
            System.out.println("Word");
            System.out.println("Word");
        };
        // 表达式中只有一个参数,可以省略()
        Consumer infoConsumer = msg -> System.out.println("Hello " + msg);
        // 表达式由多个参数组成,不可省略()
        BinaryOperator add1 = (Integer i ,Integer j) -> i + j;
        // 编译器会进行类型推断,在没有歧义情况下可以省略类型声明,但是不可省略()
        BinaryOperator add2 = (i, j) -> i + j;

综上可见,一个 表达式主要由三部分组成:

参数列表箭头分隔符(->)主体,单个表达式或语句块

我们在使用匿名内部类时有一些限制:引用方法中的变量时,需要将变量声明为 final,不能为其进行重新赋值,如下:

        final String msg = "World";
        Runnable print = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello"  + msg);
            }
        };

在 Java8 中放松了这个限制,可以引用非 final 变量,但是该变量在既成事实上必须是 final 的,虽然无需将变量声明为 final,在 表达式中,也无法用作非最终态变量,及只能给该变量赋值一次(与用 final 声明变量效果相同)。

2.2

,只有一个抽象方法的接口就是函数式接口,接口中单一方法命名并不重要,只要方法签名与 表达式的类型匹配即可。

Java 内置了常用函数接口如下:

1. Predicate
参数类型:T
返回值:boolean
示例:Predicate isAdmin = name -> "admin".equals(name);

2. Consumer
参数:T
返回值:void
示例:Consumer print = msg -> System.out.println(msg);

3. Function
参数:T
返回值:R
示例:Function toStr = value -> String.valueOf(value);

4. Supplier
参数:none
返回值:T
示例:Supplier now = () -> new Date();

5. UnaryOperator
参数:T
返回值:T
示例:UnaryOperator negation = value -> !value.booleanValue();

6. BinaryOperator
参数:(T, T)
返回值:T
示例:BinaryOperator intDouble = (i, j) -> i + j;

7. Runnable
参数:none
返回值:void
示例:Runnable helloWord = () -> System.out.println("Hello World");

8. Callable
参数:nont
返回值:T
示例:Callable now1 = () -> new Date();

当然我们也可以根据需求自定义函数接口,为了保证接口的有效性,可以在上面添加 @ 注解,该注解会强制 javac 检测一个接口是否符合函数式接口的规范,例如:

    @FunctionalInterface
    interface CustomFunctionalInterface{
        void print(String msg);
    }
    CustomFunctionalInterface cfi= msg -> System.out.println(msg);

2.3 方法引用

表达式一种常用方法便是直接调用其他方法,针对这种情况,Java8 提供了一个简写语法,及方法引用,用于重用已有方法。

凡是可以使用 表达式的地方,都可以使用方法引用。

方法应用的标准语法为 ::,虽然这是一个方法,但不需要再后面加括号,因为这里并不直接调用该方法。

    Function f1 = user->user.getName();
    Function f2 = User::getName;
    Supplier s1 = ()->new User();
    Supplier s2 = User::new;
    Function sa1 = count -> new User[count];
    Function sa2 = User[]::new;

方法引用主要分为如下几种类型:

2.4 类型推断

类型推断,是 Java7 就引入的目标类型推断的扩展,在 Java8 中对其进行了改善,程序员可以省略 表达式中的所有参数类型,Javac 会根据 表达式式上下文信息自动推断出参数的正确类型。

大多数情况下 javac 能够准确的完成类型推断,但由于 表达式与函数名无关,只与方法签名相关,因此会出现类型对推断失效的情况,这时可以使用手工类型转换帮助 javac 进行正确的判断。

    // Supplier, Callable 具有相同的方法签名
    private void print(Supplier stringSupplier){
        System.out.println("Hello " + stringSupplier.get());
    }
    private void print(Callable stringCallable){
        try {
            System.out.println("Hello " + stringCallable.call());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    {
        // Error, 因为两个print同时满足需求
        print(()->"World");
		// 使用类型转换,为编译器提供更多信息
        print((Supplier) ()->"World");
        print((Callable) ()-> "world");
    }

3. 类库的升级改造

Java8 另一个变化是引入了 默认方法 和接口的 静态方法 ,自此以后 Java 接口中方法也可以包含代码体了。

3.1 默认方法

**默认方法允许接口方法定义默认实现,而所有子类都将拥有该方法及实现。**使其能够在不改变子类实现的情况下(很多时候我们无法拿到子类的源码),为所有子类添加新的功能,从而最大限度的保证二进制接口的兼容性。

默认方法的另一个优势是该方法是可选的,子类可以根据不同的需求 默认实现,为其提供扩展性保证。

其中 中的 , 功能都是通过该技术统一添加到接口中的。

    // Collection 中的forEache实现
    default void forEach(Consumer action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    // Collection中的stream实现
    default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
    }

从上可见,默认方法的写法也是比较简单的,只需在方法声明中添加 关键字,然后提供方法的默认实现即可。

和类不同,接口中没有成员变量,因此默认方法只能通过调用子类的方法来修改子类本身,避免了对子类的实现做出各种假设。

3.1.1 默认方法与子类

添加默认方法特性后,方法的重写规则也发生了变化,具体的场景如下:

a. 没有重写

没有重写是最简单的情况,子类调用该方法的时候,自然继承了默认方法。

    interface Parent{
        default void welcome(){
            System.out.println("Parent");
        }
    }
    // 调用Parent中的welcome, 输入"Parent"
    class ParentNotImpl implements Parent{
    }

b. 子接口重写

子接口对父接口中的默认方法进行了重新,其子类方法被调用时,执行子接口中的默认方法

     interface Parent{
        default void welcome(){
            System.out.println("Parent");
        }
    }
    interface ChildInterface extends Parent{
        @Override
        default void welcome(){
            System.out.println("ChildInterface");
        }
    }
    // 执行ChildInterface中的welcome, 输入 "ChildInterface"
    class ChildImpl implements ChildInterface{
    }

c. 类重写

一旦类中重写了默认方法,优先选择类中定义的方法,如果存在多级类继承,遵循类继承逻辑。

     interface Parent{
        default void welcome(){
            System.out.println("Parent");
        }
    }
    interface ChildInterface extends Parent{
        @Override
        default void welcome(){
            System.out.println("ChildInterface");
        }
    }
    //执行子类中的welcome方法,输出"ChildImpl"
    class ChildImpl1 implements ChildInterface{
        @Override
        public void welcome(){
            System.out.println("ChildImpl");
        }
    }

3.1.2 多重继承

接口允许多重继承,因此有可能会碰到两个接口包含签名相同的默认方法的情况,此时 javac 并不明确应该继承哪个接口中的方法,因此会导致编译出错,这时需要在类中实现该方法,如果想调用特定父接口中的默认方法,可以使用

.super.() 的方式来指明具体的接口。

    interface Parent1 {
        default void print(){
            System.out.println("parent1");
        }
    }
    interface Parent2{
        default void print(){
            System.out.println("parent2");
        }
    }
    class Child implements Parent1, Parent2{
        @Override
        public void print() {
            System.out.println("self");
            Parent1.super.print();
            Parent2.super.print();
        }
    }

现在的接口提供了某种形式上的多继承功能,然而多重继承存在很多诟病。很多人认为多重继承的问题在于对象状态的继承,而不是代码块的继承,默认方法避免了状态的继承,也因此避免了 C++ 中多重继承最大的缺点。

接口和抽象类之间还是有明显的区别。接口允许多重继承,却没有成员变量;抽象类可以继承成员变量,却不能多重继承。

从某种角度出发,Java 通过接口默认方法实现了代码多重继承,通过类实现了状态单一继承。

3.1.3 三定律

如果对默认方法的工作原理,特别是在多重继承下的行为没有把握,可以通过下面三条简单定律帮助大家。

类胜于方法。 如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法。子类胜于父类。 如果一个接口继承另一个接口,且两个接口都定义了一个默认方法,那么子接口中定义的方法胜出。没有规则三。 如果上面两条规则不适用,子类要么实现该方法,要么将该方法声明为抽象方法。3.2 接口静态方法

人们在编程过程中积累了这样一条经验,创建一个包含很多静态方法的一个类。很多时候类是一个放置工具方法的好地方,比如 Java7 引入的 类,就包含很多工具方法,这些方法不是属于具体的某个类。

如果一个方法有充分的语义原因和某个概念相关,那么就应该讲该方法和相关的类或接口放在一起,而不是放到另一个工具类中,这非常有助于更好的组织代码。

在接口中定义静态方法,只需使用 关键字进行描述即可,例如 接口中的 of 方法。

    /**
     * Returns a sequential {@code Stream} containing a single element.
     *
     * @param t the single element
     * @param  the type of stream elements
     * @return a singleton sequential stream
     */
    public static Stream of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }

3.3

是 Java8 中最耀眼的亮点,它使得程序员得以站在更高的抽象层次对集合进行操作。

是用函数式编程方式在集合类上进行复杂操作的工具。

3.3.1. 从外部迭代到内部迭代

Java 程序员使用集合时,一个通用模式就是在集合上进行迭代,然后处理返回的每一个元素,尽管这种操作可行但存在几个问题:

常见集合遍历如下:

     // 常见写法1,不推荐使用
    public void printAll1(List msg){
        for (int i=0; i< msg.size(); i++){
            String m = msg.get(i);
            System.out.println(m);
        }
    }
    // Java5之前,正确写法,过于繁琐
    public void printAll2(List msg){
        Iterator iterator = msg.iterator();
        while (iterator.hasNext()){
            String m = iterator.next();
            System.out.println(m);
        }
    }
    // Java5之后,加强for循环,采用语法糖,简化for循环,内部转化为Iterator方式
    public void printAll3(List msg){
        for (String m : msg){
            System.out.println(m);
        }
    }

整个迭代过程,通过显示的调用 对象的 和 next 方法完成整个迭代,这成为外部迭代。

另一种方式成为内部迭代,及将操作行为作为参数传递给 ,在 内部完成迭代操作。

     // Java8中,使用Stream进行内部迭代操作
    public void printAll4(List msg){
        msg.stream().forEach(System.out::println);
    }

内部迭代:

3.3.2. 惰性求值 VS 及早求值

中存在两类方法,不产生值的方法称为惰性方法;从 中产生值的方法叫做及早求值方法。

判断一个方法的类别很简单:如果返回值是 ,那么就是惰性方法;如果返回值是另一个值或为空,那么就是及早求值方法。

惰性方法返回的 对象不是一个新的集合,而是创建新集合的配方, 本身不会做任何迭代操作,只有调用及早求值方法时,才会开始真正的迭代。

整个过程与 模式有共通之处,惰性方法负责对 进行装配(设置 的属性),调用及早求值方法时(调用 的 build 方法),按照之前的装配信息进行迭代操作。

常见 操作:

3.3.2.1 (())

及早求值方法:

(()) 方法由 里面的值生成一个列表,是一个及早求值操作。

的功能不仅限于此,它是一个非常强大的结构。

     @Data
    class User{
        private String name;
    }
    public List getNames(List users){
        List names = new ArrayList<>();
        for (User user : users){
            names.add(user.getName());
        }
        return names;
    }
    public List getNamesUseStream(List users){
      // 方法引用
      //return users.stream().map(User::getName).collect(toList());
        // lambda表达式
        return users.stream().map(user -> user.getName()).collect(toList());
    }

3.3.2.2. count、max、min

及早求值方法:

上最常用的操作之一就是求总数、最大值和最小值,count、max 和 min 足以解决问题。

    public Long getCount(List users){
        return users.stream().filter(user -> user != null).count();
    }
    // 求最小年龄
    public Integer getMinAge(List users){
        return users.stream().map(user -> user.getAge()).min(Integer::compareTo).get();
    }
    // 求最大年龄
    public Integer getMaxAge(List users){
        return users.stream().map(user -> user.getAge()).max(Integer::compareTo).get();
    }

min 和 max 入参是一个 对象,用于元素之间的比较,返回值是一个 ,它代表一个可能不存在的值,如果 为空,那么该值不存在,如果不为空,该值存在。通过 get 方法可以获取 中的值。

3.3.2.3 、

及早求值方法:

两个函数都以为返回值,用于表示是否找到。

    public Optional getAnyActiveUser(List users){
        return users.stream()
                .filter(user -> user.isActive())
                .findAny();
    }
    public Optional getFirstActiveUser(List users){
        return users.stream()
                .filter(user -> user.isActive())
                .findFirst();
    }

3.3.2.4 、、

及早求值方法:

均以 作为输入参数,对集合中的元素进行判断,并返回最终的结果。

    // 所有用户是否都已激活
    boolean allMatch = users.stream().allMatch(user -> user.isActive());
    // 是否有激活用户
    boolean anyMatch = users.stream().anyMatch(user -> user.isActive());
    // 是否所有用户都没有激活
    boolean noneMatch = users.stream().noneMatch(user -> user.isActive());

3.3.2.6.

及早求值:

以 为参数,对 中复合条件的对象进行操作。

    public void printActiveName(List users){
        users.stream()
                .filter(user -> user.isActive())
                .map(user -> user.getName())
                .forEach(name -> System.out.println(name));
    }

3.3.2.7

及早求值方法:

操作可以实现从一组值中生成一个值,之前提到的 count、min、max 方法因为比较通用,单独提取成方法,事实上,这些方法都是通过 完成的。

下图展示的是对 进行求和的过程,以 0 为起点,每一步都将 中的元素累加到 中,遍历至最后一个元素, 就是所有元素值的和。

3.3.2.8.

惰性求值方法:

以 作为参数(相当于 if 语句),对 中的元素进行过滤,只有复合条件的元素才能进入下面的处理流程。

处理流程如下:

    public List getActiveUser(List users){
        return users.stream()
                .filter(user -> user.isActive())
                .collect(toList());
    }

3.3.2.9 map

及早求值方法: 以 作为参数,将 中的元素从一种类型转换成另外一种类型。

处理过程如下:

    public List getNames(List users){
        return users.stream()
                .map(user -> user.getName())
                .collect(toList());
    }

3.3.2.10 peek

提供的是内迭代,有时候为了功能调试,需要查看每个值,同时能够继续操作流,这时就会用到 peek 方法。

    public void printActiveName(List users){
        users.stream()
                .filter(user -> user.isActive())
                .peek(user -> System.out.println(user.isActive()))
                .map(user -> user.getName())
                .forEach(name -> System.out.println(name));
    }

3.3.2.11 其他

针对集合 还提供了许多功能强大的操作,暂不一一列举,简单汇总一下。

3.4

Java 程序中出现最多的异常就是 ,没有之一。 的出现力求改变这一状态。

对象相当于值的容器,而该值可以通过 get 方法获取,同时 提供了很多函数用于对值进行操作,从而最大限度的避免 的出现。

与 的用法基本类型,所提供的方法同样分为惰性和及早求值两类,惰性方法主要用于流程组装,及早求值用于最终计算。

3.4.1 of

使用工厂方法 of,可以从一个值中创建一个 对象,如果值为 null,会报 。

    Optional dataOptional = Optional.of("a");
    String data = dataOptional.get(); // data is "a"
    Optional dataOptional = Optional.of(null);
    String data = dataOptional.get(); // throw NullPointerException

3.4.2 empty

工厂方法 empty,可以创建一个不包含任何值的 对象。

    Optional dataOptional = Optional.empty();
    String data = dataOptional.get(); //throw NoSuchElementException

3.4.3

工厂方法 ,可将一个空值转化成 。

     public static  Optional ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

3.4.4 get、、、

直接求值方法,用于获取 中值,避免空指针异常的出现。

    Optional dataOptional = Optional.of("a");
    dataOptional.get(); // 获取Optional中的值, 不存在会抛出NoSuchElementException
    dataOptional.orElse("b"); //获取Optional中的值,不存在,直接返回"B"
    dataOptional.orElseGet(()-> String.valueOf(System.currentTimeMillis())); //获取Optional中的值,不存在,对Supplier进行计算,并返回计算结果
    dataOptional.orElseThrow(()-> new XXXException()); //获取Optional中的值,不存在,抛出自定义异常

3.4.5 、

直接求值方法, 用于判断 中是否有值, 接收 对象,当 有值的情况下执行。

    Optional dataOptional = Optional.of("a");
    String value = null;
    if (dataOptional.isPresent()){
        value = dataOptional.get();
    }else {
        value = "";
    }
    //等价于
    String value2 = dataOptional.orElse("");
    // 当Optional中有值的时候执行
    dataOptional.ifPresent(v->System.out.println(v));

3.4.6 map

惰性求值方法。map 与 中的用法基本相同,用于对 中的值进行映射处理,从而避免了大量 if 语句嵌套,多个 map 组合成链,只需对最终的结果进行操作,中间过程中如果存在 null 值,之后的 map 不会执行。

    @Data
    static class Order{
        private Name owner;
    }
    @Data
    static class User{
        private Name name;
    }
    @Data
    static class Name{
        String firstName;
        String midName;
        String lastName;
    }
    private String getFirstName(Order order){
        if (order == null){
            return "";
        }
        if (order.getOwner() == null){
            return "";
        }
        if (order.getOwner().getFirstName() == null){
            return "";
        }
        return order.getOwner().getFirstName();
    }
    private String getFirstName(Optional orderOptional){
        return orderOptional.map(order -> order.getOwner())
                .map(user->user.getFirstName())
                .orElse("");
    }

3.4.7

惰性求值,对 中的值进行过滤,如果 为 empty,直接返回 empty;如果 中存在值,则对值进行验证,验证通过返回原 ,验证不通过返回 empty。

     public Optional filter(Predicate predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

4. 下模式的进化

设计模式是人们熟悉的一种设计思路,他是软件架构中解决通用问题的模板,将解决特定问题的最佳实践固定下来,但设计模式本身会比较复杂,包含多个接口、若干个实现类,应用过程相对繁琐,这也是影响其应用的原因之一。

表达式大大简化了 Java 中行为传递的问题,对于很多行为式设计模式而言,减少了不少构建成本。

4.1 命令模式

命令者是一个对象,其封装了调用另一个方法的实现细节,命令者模式使用该对象可以编写根据运行时条件,顺序调用方法的一般性代码。

大多数命令模式中的命令对象,其实是一种行为的封装,甚至是对其他对象内部行为的一种适配,这种情况下, 表达式并有了用武之地。

    interface Command{
        void act();
    }
    interface Editor{
        void open();
        void write(String data);
        void save();
    }
    class CommandRunner{
        private List commands = new ArrayList<>();
        public void run(Command command){
            command.act();
            this.commands.add(command);
        }
        public void redo(){
            this.commands.forEach(Command::act);
        }
    }
    class OpenCommand implements Command{
        private final Editor editor;
        OpenCommand(Editor editor) {
            this.editor = editor;
        }
        @Override
        public void act() {
            this.editor.open();
        }
    }
    class WriteCommand implements Command{
        private final Editor editor;
        private final String data;
        WriteCommand(Editor editor, String data) {
            this.editor = editor;
            this.data = data;
        }
        @Override
        public void act() {
            editor.write(this.data);
        }
    }
    class SaveCommand implements Command{
        private final Editor editor;
        SaveCommand(Editor editor) {
            this.editor = editor;
        }
        @Override
        public void act() {
            this.editor.save();
        }
    }
    public void useCommand(){
        CommandRunner commandRunner = new CommandRunner();
        Editor editor = new EditorImpl();
        String data1 = "data1";
        String data2 = "data2";
        commandRunner.run(new OpenCommand(editor));
        commandRunner.run(new WriteCommand(editor, data1));
        commandRunner.run(new WriteCommand(editor, data2));
        commandRunner.run(new SaveCommand(editor));
    }
    public void useLambda(){
        CommandRunner commandRunner = new CommandRunner();
        Editor editor = new EditorImpl();
        String data1 = "data1";
        String data2 = "data2";
        commandRunner.run(()->editor.open());
        commandRunner.run(()->editor.write(data1));
        commandRunner.run(()->editor.write(data2));
        commandRunner.run(()->editor.save());
    }
    class EditorImpl implements Editor{
        @Override
        public void open() {
        }
        @Override
        public void write(String data) {
        }
        @Override
        public void save() {
        }
    }

从代码中可见, 表达式的应用,减少了创建子类的负担,增加了代码的灵活性。

4.2 策略模式

策略模式能够在运行时改变软件的算法行为,其核心的实现思路是,使用不同的算法来解决同一个问题,然后将这些算法封装在一个统一的接口背后。

可见策略模式也是一种行为行为传递的模式。

    interface CompressionStrategy{
        OutputStream compress(OutputStream outputStream) throws IOException;
    }
    class GzipBasedCompressionStrategy implements CompressionStrategy{
        @Override
        public OutputStream compress(OutputStream outputStream) throws IOException {
            return new GZIPOutputStream(outputStream);
        }
    }
    class ZipBasedCompressionStrategy implements CompressionStrategy{
        @Override
        public OutputStream compress(OutputStream outputStream) throws IOException {
            return new ZipOutputStream(outputStream);
        }
    }
    class Compressor{
        private final CompressionStrategy compressionStrategy;
        Compressor(CompressionStrategy compressionStrategy) {
            this.compressionStrategy = compressionStrategy;
        }
        public void compress(Path inFile, File outFile) throws IOException {
            try (OutputStream outputStream = new FileOutputStream(outFile)){
                Files.copy(inFile, this.compressionStrategy.compress(outputStream));
            }
        }
    }
    {
        Compressor gzipCompressor = new Compressor(new GzipBasedCompressionStrategy());
        gzipCompressor.compress(in,out);
        Compressor ziCompressor = new Compressor(new ZipBasedCompressionStrategy());
        ziCompressor.compress(in,out);
    }
    {
        Compressor gzipCompressor = new Compressor(GZIPOutputStream::new);
        gzipCompressor.compress(in,out);
        Compressor ziCompressor = new Compressor(ZipOutputStream::new);
        ziCompressor.compress(in,out);
    }

4.3 观察者模式

观察者模式中,被观察者持有观察者的一个列表,当被观察者的状态发送变化时,会通知观察者。

对于一个观察者来说,往往是对一个行为的封装。

    interface NameObserver{
        void onNameChange(String oName, String nName);
    }
    @Data
    class User {
        private final List nameObservers = new ArrayList<>();
        @Setter(AccessLevel.PRIVATE)
        private String name;
        public void updateName(String nName){
            String oName = getName();
            setName(nName);
            nameObservers.forEach(nameObserver -> nameObserver.onNameChange(oName, nName));
        }
        public void addObserver(NameObserver nameObserver){
            this.nameObservers.add(nameObserver);
        }
    }
    class LoggerNameObserver implements NameObserver{
        @Override
        public void onNameChange(String oName, String nName) {
            System.out.println(String.format("old Name is %s, new Name is %s", oName, nName));
        }
    }
    class NameChangeNoticeObserver implements NameObserver{
        @Override
        public void onNameChange(String oName, String nName) {
            notic.send(String.format("old Name is %s, new Name is %s", oName, nName));
        }
    }
    {
        User user = new User();
        user.addObserver(new LoggerNameObserver());
        user.addObserver(new NameChangeNoticeObserver());
        user.updateName("张三");
    }
    {
        User user = new User();
        user.addObserver((oName, nName) ->
                System.out.println(String.format("old Name is %s, new Name is %s", oName, nName)));
        user.addObserver((oName, nName) ->
                notic.send(String.format("old Name is %s, new Name is %s", oName, nName)));
        user.updateName("张三");
    }

4.4 模板方法模式

模板方法将整体算法设计成一个抽象类,他有一系列的抽象方法,代表方法中可被定制的步骤,同时这个类中包含一些通用代码,算法的每一个变种都由具体的类实现,他们重新抽象方法,提供相应的实现。

模板方法,实际是行为的一种整合,内部大量用到行为的传递。 先看一个标准的模板方法:

    interface UserChecker{
        void check(User user);
    }
    abstract class AbstractUserChecker implements UserChecker{
        @Override
        public final void check(User user){
            checkName(user);
            checkAge(user);
        }
        abstract void checkName(User user);
        abstract void checkAge(User user);
    }
    class SimpleUserChecker extends AbstractUserChecker {
        @Override
        void checkName(User user) {
            Preconditions.checkArgument(StringUtils.isNotEmpty(user.getName()));
        }
        @Override
        void checkAge(User user) {
            Preconditions.checkArgument(user.getAge() != null);
            Preconditions.checkArgument(user.getAge().intValue() > 0);
            Preconditions.checkArgument(user.getAge().intValue() < 150);
        }
    }
    {
        UserChecker userChecker = new SimpleUserChecker();
        userChecker.check(new User());
    }
    class LambdaBaseUserChecker implements UserChecker{
        private final List> userCheckers = Lists.newArrayList();
        public LambdaBaseUserChecker(List>userCheckers){
            this.userCheckers.addAll(userCheckers);
        }
        @Override
        public void check(User user){
            this.userCheckers.forEach(userConsumer -> userConsumer.accept(user));
        }
    }
    {
        UserChecker userChecker = new LambdaBaseUserChecker(Arrays.asList(
                user -> Preconditions.checkArgument(StringUtils.isNotEmpty(user.getName())),
                user -> Preconditions.checkArgument(user.getAge() != null),
                user -> Preconditions.checkArgument(user.getAge().intValue() > 0),
                user -> Preconditions.checkArgument(user.getAge().intValue() < 150)
        ));
        userChecker.check(new User());
    }
    @Data
    class User{
        private String name;
        private Integer age;
    }

在看一个 ,如果使用 进行简化:

    public JdbcTemplate jdbcTemplate;
    public User getUserById(Integer id){
        return jdbcTemplate.query("select id, name, age from tb_user where id = ?", new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement preparedStatement) throws SQLException {
                preparedStatement.setInt(1, id);
            }
        }, new ResultSetExtractor() {
            @Override
            public User extractData(ResultSet resultSet) throws SQLException, DataAccessException {
                User user = new User();
                user.setId(resultSet.getInt("id"));
                user.setName(resultSet.getString("name"));
                user.setAge(resultSet.getInt("age"));
                return user;
            }
        });
    }
    public User getUserByIdLambda(Integer id){
        return jdbcTemplate.query("select id, name, age from tb_user where id = ?",
                preparedStatement -> preparedStatement.setInt(1, id),
                resultSet -> {
                    User user = new User();
                    user.setId(resultSet.getInt("id"));
                    user.setName(resultSet.getString("name"));
                    user.setAge(resultSet.getInt("age"));
                    return user;
                });
    }
    @Data
    class User {
        private Integer id;
        private String name;
        private Integer age;
    }

5. 下并发程序

并发与并行:

5.1 并行化流操作

并行化流操作是 提供的一个特性,只需改变一个方法调用,就可以让其拥有并行操作的能力。

如果已经存在一个 对象,调用他的 方法就能让其并行执行。

如果已经存在一个集合,调用 方法就能获取一个拥有并行执行能力的 。

并行流主要解决如何高效使用多核 CPU 的事情。

    @Data
    class Account{
        private String name;
        private boolean active;
        private Integer amount;
    }
    public int getActiveAmount(List accounts){
        return accounts.parallelStream()
                .filter(account -> account.isActive())
                .mapToInt(account -> account.getAmount())
                .sum();
    }
    public int getActiveAmount2(List accounts){
        return accounts.stream()
                .parallel()
                .filter(account -> account.isActive())
                .mapToInt(Account::getAmount)
                .sum();
    }

并行流底层使用 fork/join 框架,fork 递归式的分解问题,然后每个段并行执行,最终有 join 合并结果,返回最后的值。

5.2 阻塞 IO VS 非阻塞 IO

BIO VS NIO

BIO 阻塞式 IO,是一种通用且容易理解的方式,与程序交互时通常都符合这种顺序执行的方式,但其主要的缺陷在于每个 会绑定一个 进行操作,当长链过多时会消耗大量的 资源,从而导致其扩展性性下降。

NIO 非阻塞 IO,一般指的是 IO 多路复用,可以使用一个线程同时对多个 的读写进行监控,从而使用少量线程服务于大量 。

由于客户端开发的简便性,大多数的驱动都是基于 BIO 实现,包括 MySQL、Redis、Mongo 等;在服务器端,由于其高性能的要求,基本上是 NIO 的天下,以最大限度的提升系统的可扩展性。

由于客户端存在大量的 BIO 操作,我们的客户端线程会不停的被 BIO 阻塞,以等待操作返回值,因此线程的效率会大打折扣。

如上图,线程在 IO 与 CPU 之间不停切换,走走停停,同时线程也没有办法释放,一直等到任务完成。

5.3

构建并发操作的另一种方案便是 , 是一种凭证,调用方法不是直接返回值,而是返回一个 对象,刚创建的 为一个空对象,由后台线程执行耗时操作,并在结束时将结果写回到 中。

当调用 对象的 get 方法获取值时,会有两个可能,如果后台线程已经运行完成,则直接返回;如果后台线程没有运行完成,则阻塞调用线程,知道后台线程运行完成或超时。

使用 方式,可以以并行的方式运行多个子任务。

当主线程需要调用比较耗时的操作时,可以将其放在辅助线程中执行,并在需要数据的时候从 中获取,如果辅助线程已经运行完成,则立即拿到返回的结果,如果辅助线程还没有运行完成,则主线程等待,并在完成时获取结果。

一种常见的场景是在中从多个中获取结果,并将其封装成一个View对象返回给前端用于显示,假设需要从三个接口中获取结果,每个接口的平均响应时间是20ms,那按照串行模式,总耗时为sum(i1, i2, i3) = 60ms;如果按照并发模式将加载任务交由辅助线程处理,总耗时为max(i1, i2, i3 ) = 20ms, 大大减少了系统的响应时间。

     private ExecutorService executorService = Executors.newFixedThreadPool(20);
    private User loadUserByUid(Long uid){
       sleep(20);
       return new User();
    }
    private Address loadAddressByUid(Long uid){
        sleep(20);
        return new Address();
    }
    private Account loadAccountByUid(Long uid){
        sleep(20);
        return new Account();
    }
    /**
     * 总耗时 sum(LoadUser, LoadAddress, LoadAccount) = 60ms
     * @param uid
     * @return
     */
    public View getViewByUid1(Long uid){
        User user = loadUserByUid(uid);
        Address address = loadAddressByUid(uid);
        Account account = loadAccountByUid(uid);
        View view = new View();
        view.setUser(user);
        view.setAddress(address);
        view.setAccount(account);
        return view;
    }
    /**
     * 总耗时 max(LoadUser, LoadAddress, LoadAccount) = 20ms
     * @param uid
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public View getViewByUid(Long uid) throws ExecutionException, InterruptedException {
        Future userFuture = executorService.submit(()->loadUserByUid(uid));
        Future
addressFuture = executorService.submit(()->loadAddressByUid(uid)); Future accountFuture = executorService.submit(()->loadAccountByUid(uid)); View view = new View(); view.setUser(userFuture.get()); view.setAddress(addressFuture.get()); view.setAccount(accountFuture.get()); return view; } private void sleep(long time){ try { TimeUnit.MILLISECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } @Data class View{ private User user; private Address address; private Account account; } class User{ } class Address{ } class Account{ }

方式存在一个问题,及在调用 get 方法时会阻塞主线程,这是资源的极大浪费,我们真正需要的是一种不必调用 get 方法阻塞当前线程,就可以操作 对象返回的结果。

上例中只是子任务能够拆分并能并行执行的一种典型案例,在实际开发过程中,我们会遇到更多、更复杂的场景,比如:

对此,我们引入了 对象。

5.4

提供了一组函数用于定义流程,其中包括:

5.4.1 创建函数

提供了一组静态方法用于创建 实例:

public static  CompletableFuture completedFuture(U value)// :使用已经创建好的值,创建 CompletableFuture 对象。
public static CompletableFuture 	runAsync(Runnable runnable)// 基于 Runnable 创建 CompletableFuture 对象,返回值为 Void,及没有返回值
public static CompletableFuture 	runAsync(Runnable runnable, Executor executor)// 基于 Runnable 和自定义线程池创建 CompletableFuture 对象,返回值为 Void,及没有返回值
public static  CompletableFuture 	supplyAsync(Supplier supplier)// 基于 Supplier 创建 CompletableFuture 对象,返回值为 U
public static  CompletableFuture 	supplyAsync(Supplier supplier, Executor executor)   // 基于 Supplier 和自定义线程池创建 CompletableFuture 对象,返回值为 U

以 Async 结尾并且没有指定 的方法会使用 () 作为它的线程池执行异步代码。

方法的参数类型都是函数式接口,所以可以使用 表达式实现异步任务。

5.4.2 计算结果完成后

当 计算完成或者计算过程中抛出异常时进行回调。

public CompletableFuture 	whenComplete(BiConsumer action)
public CompletableFuture 	whenCompleteAsync(BiConsumer action)
public CompletableFuture 	whenCompleteAsync(BiConsumer action, Executor executor)
public CompletableFuture     exceptionally(Function fn)

的类型是

提醒:请联系我时一定说明是从旅游网上看到的!