美文网首页
java8新特性

java8新特性

作者: jianshujoker | 来源:发表于2020-01-20 11:07 被阅读0次

行为参数化(Lambda 以及方法引用)

简洁灵活代码的演进

让我们先来看下对apple根据重量排序的不同写法

public class AppleSortTest {
    private static List<Apple>  inventory = new ArrayList<>();
    static {
        inventory.add(new Apple(9,"qing"));
        inventory.add(new Apple(4,"red"));
        inventory.add(new Apple(7,"red"));
    }

    /**
     * 传递代码 想排序需要继承Comparator重写一个类,尽管我只写了很少
     * 的代码;而当我需要排序的属性变了后,我又需要重新写一个类
     */
    @Test
    public void transCodeSort() {
        inventory.sort(new AppleComparator());
    }

    /**
     * 传递匿名类 我没有为一个单独的排序创建类了,可是依然有和我
     * 排序无关的代码类名方法名
     */
    @Test
    public void transAnonymousClassSort(){
        inventory.sort(new Comparator<Apple>() {
            public int compare(Apple a1, Apple a2){
                return a1.getWeight().compareTo(a2.getWeight());
            }
        });
    }

    /**
     * 使用lambda表达式进行排序 我们没有了和排序无关的代码
     */
    @Test
    public void  useLambda(){
        inventory.sort((Apple a1, Apple a2)
                -> a1.getWeight().compareTo(a2.getWeight())
        );
    }

    /**
     * 使用方法引用+lambda表达式 方法引用就是替代那些转发参数的Lambda表达式的语法糖;
     * 方法引用让你的代码更简洁
     * 重复使用现有的方法实现并直接传递它们
     */
    @Test
    public void useMethodQuote(){
        inventory.sort(Comparator.comparing(Apple::getWeight));
    }
}

通过上面这个例子很明显可以看出使用lambda表达式和方法引用,可以使我们代码变得更简洁、灵活;他们的进阶情况如下图:

演进.png

函数式编程简介

  • 一种使用函数进行编程的方式
  • 函数式:如果一个方法既不修改它内嵌类的状态,也不修改其他对象的状态,使用return返回所有的计算结果,那么我们称其为纯粹的或者无副作用的


    没有副作用函数.png
  • 函数式编程:程序有一定的副作用,不过该副作用不会为其他的调用者感知,是否我们能假设这种副作用不存在呢?调用者不需要知道,或者完全不在意这些副作用,因为这对它完全没有影响


    带有副作用的函数.png
  • 将方法等概念作为一等值可以扩充程序员的工具库,从而让编程变得更容易

lambda表达式

概述

  • Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表
  • 什么时候以及在哪里可以使用Lambda表达式
    • 只有在接受函数式接口的地方才可以使用Lambda表达式;它们可以从赋值的上下文、方法调用的上下文(参数和返回值),以及类型转换的上下文中获得目标类型
  • Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例

函数式接口

  • 只定义一个抽象方法的接口
    • 无论是否加@FunctionalInterface
    • 可以多加default方法

常用的函数式接口

  • Predicate
    • 谓词:在计算机语言的环境下,谓词是指条件表达式的求值返回真或假的过程
  • Consumer
    • 消费者 访问类型T的对象,并对其执行某些操作,没有返回
  • Supplier
    • 供应商 无参数的提供一个类型T的对象
  • Function
    • 方便使用的范本 它接受一个泛型T的对象,并返回一个泛型R的对象

类型检查

lambda表达式类型检查.png

类型推断

  • Java编译器会从上下文(目标类型)推断出用什么函数式接口
    • 可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到接口
    • 编译器了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型
      类型推断.png
    • 没有哪一种更好的说法,选用哪一种取决于程序员自己,比如有的人觉得第二种更好,而有的人觉得第二种会让人困惑,因为不能一眼看出参数类型

编译

  • lambda表达式编译会使用invokedynamic避免创建额外的类
  • invokedynamic会在首次执行时链接代码

捕获变量

  • Lambda表达式可以没有限制地捕获实例变量和静态 ;但局部变量必须显式声明为final,或事实上是final;看个例子

    public class CaptureVarTest {
        public  List<String> jio=new ArrayList<>();
    
        /**
         * lambda 对final对象的引用
         */
        @Test
        public void lambdaCaptureVar() {
            int i = 0;
            Apple apple = new Apple(2, "pork");
            List<Apple> apples = new ArrayList<>();
              List<String> jio1=new ArrayList<>();
            System.out.println(checkFunctionInterface(() -> {
    //            jio = null; //不报错
    //            String ji=String.valueOf(i); //不报错
    //            i+=1; //编译报错
    //            apple.setWeight(2); //不报错
    //            apples.add(apple); //不报错
    //            apple = null; //编译报错
    //            apples = new ArrayList<>(); //编译报错
                return "123";
            }));
    //        apple = null; //lambda里面原先对重量赋值不报错的,这行放开后,对重量赋值会报错
        }
    
        /**
         * 传递的是一个函数式接口
         */
        private String checkFunctionInterface(OneMethodInterface one) {
            return one.onlyOne();
        }
    }
    
    • lambda表达式捕获的值是通过复制对象
      • 当为值对象时,改变值,lambda表达式里面和外面无法互相同步
      • 当为引用对象,拥有的是地址,当地址改变时,lambda表达式里面和外面无法互相同步
      • 而实例变量和静态变量在堆上,拥有实例的地址,实例变量改变时总是可以同步的;静态变量总是同步的
    • 函数式编程应尽量降低状态的副作用

概述

流的流水线.png
  • 从支持数据处理操作(如filter、map、reduce、find、match、sort等)的源生成的元素序列(目的在于表达计算)
  • 以声明性方式(首先做这个,紧接着更新那个,然后......)处理数据集合
  • 只能遍历一次

常用操作

  • 中间操作:返回Stream,可以接着执行

  • 终端操作:从流水线产生结果,返回不是stream的值

    • 中间操作和终端操作也体现了流的延迟性,只有遇到终端操作,流才会真正执行;下面代码会先输出分隔符
          @Test
          public void streamDelayExecute() {
              Stream stream = Dish.menu.stream()
                      .filter(d -> {
                          System.out.println("filter execute");
                          return d.getCalories() < 400;
                      })
                      .limit(1);
              System.out.println("分割符");
              stream.collect(Collectors.toList());
          }
    
流操作.png

Collector收集器

groupingBy简化代码例子

    /**
     * 将交易信息根据货币分组;java8以前的写法
     */
    @Test
    public void groupImperatively() {
        Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
        for (Transaction transaction : transactions) {
            Currency currency = transaction.getCurrency();
            List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
            if (transactionsForCurrency == null) {
                transactionsForCurrency = new ArrayList<>();
                transactionsByCurrencies.put(currency, transactionsForCurrency);
            }
            transactionsForCurrency.add(transaction);
        }

        System.out.println(transactionsByCurrencies);
    }

    /**
     * 将交易信息根据货币分组;使用收集器Collector
     */
    @Test
    public void groupFunctionally() {
        Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream()
                .collect(groupingBy(Transaction::getCurrency));
        System.out.println(transactionsByCurrencies);
    }

接口方法

public interface Collector<T, A, R> { 
 Supplier<A> supplier(); 
 BiConsumer<A, T> accumulator(); 
 Function<A, R> finisher(); 
 BinaryOperator<A> combiner(); 
 Set<Characteristics> characteristics(); 
}

  • 规约过程


    顺序规约过程的逻辑步骤.png
  • combiner 并行的时候使用
  • characteristics
    • UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响
    • CONCURRENT:收集器可以并行归约流;如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约
    • IDENTITY_FINISH:完成器方法返回的函数是一个恒等函数;累加器A不加检查地转换为结果R是安全的

并发流

  • 并行处理一个流,而无需在代码中显式使用和协调不同的线程

  • 使用的是fork/join;避免了java7中继承RecursiveTask那一套

  • 使用parallel、sequential切换顺序流和并行流,但只有最后一个生效

    stream.parallel() 
     .filter(...) 
     .sequential() 
     .map(...) 
     .parallel() 
     .reduce();//这几行代码执行的就是并行流
    
  • Spliterator做流的切分

    • trySplit() 把一些元素划出去分给第二个Spliterator
fork.png

Optional

  • 对存在或缺失的变量值进行建模;更好的应对null对象
  • 使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值
  • Optional类支持多种方法,比如map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似
    /**
     * 会正常输出ko2
     */
    @Test
    public void showOrElse() {
        Optional<String> optional = Optional.ofNullable(null);
        System.out.println(optional.filter(str -> str.contains("kol"))
                .map(str -> str + "")
                .orElse("ko2"));
    }

默认方法

  • 兼容的方式改进API
  • 相同方法签名带来的继承问题
    • 1.类中的方法优先级最高
    • 2.子接口的优先级更高
    • 3.显式覆盖和调用期望的方法

相关文章

网友评论

      本文标题:java8新特性

      本文链接:https://www.haomeiwen.com/subject/xfkjzctx.html