行为参数化和Lambda表达式

时间:2021-02-23 19:09:52

  行为参数化是指拿出一个代码块把他准备好却不执行它。这个代码块以后可以被程序的其他部分调用,意味着你可以推迟这块代码的执行。方法接受多种行为作为参数,并在内部使用来完成不同的行为。行为参数话的好处在于可以把迭代要筛选的集合的逻辑与对集合中的每个元素应用的行为区分开来。

  Java的匿名类可以同时声明和实力化一个类。但它往往很笨重,占用了很多空间同时还不易理解。

  可以把Lambda表达式看作匿名功能,它基本上是没有声明名称的方法,但和匿名类一样,它也可以作为参数传递给一个方法。Lambda表达式可以简洁地表示可传递的匿名(Lambda不像普通方法那样有一个明确的名称)函数(Lambda函数不属于某个特定的类,但Lambda有参数列表,函数主题,返回类型和异常列表)的一种方式:它没有名称,但它有参数列表,返回类型还可能有一个可以抛出的异常列表。Lambda表达式可以作为参数传递给方法或存储在变量中,并且无需像匿名类那样写很多模板代码。

  Lambda表达式有三个部分:参数列表,箭头和Lambda主体。(parameters) -> expression 或 (parameters) -> {statements;}

  通常在函数式接口上使用Lambda表达式。函数式接口就是只定义一个抽象方法的接口。

  Lambda表达式以內联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例,具体来讲时函数式接口的一个具体实现的实例。函数式接口的抽象方法的签名基本上就是Lambda表达式的签名,这种抽象方法称为函数描述符。()->void代表了参数列表为空且返回void的函数。函数式接口通常带有@FunctionalInterface的注解。它用于表示该接口会被设计为一个函数式接口。若定义了一个不是函数式接口但带有@FunctionalInterface的注解,编译器将报错:Multiple non-overriding abstract methods found in interface XX。

  

  java.util.function包中引入了几个新的函数式接口:

  Predicate<T>接口定义了一个名为test的抽象方法,它接受一个T对象,并返回一个boolean。通常用于一个涉及类型T的布尔表达式。

  @FunctionalInterface

  public interface Predicate<T>{

    boolean test(T t);

  }

  public static <T> List<T> filter(List<T> list, Predicate<T> p){

    List<T> results = new ArrayList<>();

    for(T t : list){

      if(p.test(t)){

        results.add(t);

      }

    }

  }

 

  Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();

  List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);

 

  Consumer<T>定义了一个accpet抽象方法,它接受一个T对象,没有返回。通常用于访问类型T的对象并对其执行某些操作。

  @FunctionalInterface

  public interface Consumer<T>{

    void accept(T t);

  }

  public static <T> void forEach(List<T> list, Consumer<T> c){

    for(T t : list){

      c.accpet(t);

    } 

  }

  forEach(Arrays.asList(1, 2, 3, 4), (Integer i) -> System.out.println());

 

  Function<T, R>接口定义一个apply方法,接受一个T对象,并返回一个R对象。通常用于将输入对象的信息映射到输出。

  @FunctionalInterface

  public interfaces Function<T, R>{

    R apply(T t);

  }

  public static <T, R> List<R> map(List<T> list, Function<T, R> f){

    List<R> results = new ArrayList<>();

    for(T t : list){

      results.add(f.apply(t));

    }

    return results;

  }

  List<Integer> list = map(Arrays.asList("lambdas", "in", "action"), (String s) -> s.length());

 

  将一个原始类型转换为对应的引用类型的机制称为装箱。将引用类型转换为对应的原始类型称为拆箱。Java有自动装箱机制,但是会影响性能。装箱后的值本质上是把原始类型包裹起来,并保存在堆里。因此装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。

  通常,针对专门的输入参数类型的函数式接口的名称要加上对应的原始类型前缀,比如DoublePredicate,IntConsumer等。Function接口还有针对输出参数类型的变种:ToIntFunction<T>, IntToDoubleFunction等。

  

                      java8常用函数式接口

  函数式接口        函数描述符      原始类型特化

  Predicate<T>         T -> boolean      IntPredicate,LongPredicate,DoublePredicate

  Consumer<T>      T -> void       IntConsumer, LongConsumer, DoubleConsumer

  Function<T, R>       T -> R          IntFunction<R>, IntToDoubleFunction, IntToLongFunction,LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T>

  Supplier<T>         () -> T         BooleanSupplier, IntSupplier, LongSuppier, DoubleSupplier

  UnaryOperator<T>     T -> T                     IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator

  BinaryOperator<T>    (T, T) -> T        IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator

  BiPredicate<L, R>    (L, R) -> boolean  

  BiConsumer<T, U>   (T, U) -> void     ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T>

  BiFunction<T, U, R>   (T, U) -> R     ToIntBiFunction<T, U>, ToLongBiFunction<T, U>, ToDoubleBiFunction<T, U>

  

  任何函数式接口都不允许抛出受检查异常。若需要Lambda表达式来抛出异常,有两种方l法,一种是定义一个自己的函数式接口,并声明受检查异常,另一种是把Lambda放到一个try-catch块中。

  Lambda的类型是从使用Lambda的上下文推断出来的。上下文中Lambda表达式需要的类型称为目标类型。若Lambda的主体是语句表达式,它就和一个返回void的函数描述符兼容。

  Lambda可以使用自有变量,它们被称为捕获Lambda。Lambda可以没有限制地捕获实例变量和静态变量。但局部变量必须显式的声明为final或事实上就是final。因为Lambda表达式只能捕获指派给它们的局部变量一次,实例变量可以看作捕获最终局部变量this。

  闭包是一个函数的实例,且它可以无限制地访问哪个函数的非本地变量。

 

  方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是若一个Lambda代表的只是直接调用这个方法,最好还是用名称来调用它,而不是去描述如何调用它。方法的引用实际上就是根据已有的方法实现来创建Lambda表达式。使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。方法的名称不需要括号,因为并么有实际调用这个方法。

  方法引用主要有三类

    1.指向静态方法的方法引用,如Integer::parseInt

    2.指向任意类型实例方法的方法引用,如String::length

    3.指向现有对象的实例方法的引用,如obja::methodName

  对于构造函数,利用它的名字和关键字new来创建它的一个引用。ClassName::new。若是无参的构造函数,() -> ClassName更适合。  

  Comparator具有一个叫comparing的静态辅助方法,它接受一个Function来提取Comparable健值,并声称一个Comparator对象。

  inventory.sort(comparing( (a) -> a.getWeight()));

 

  把多个简单的Lambda复合成复杂的表达式。如两个谓词之间做个or操作或让另一个函数的结果变为另一个函数的输出。

  可以通过reversed间给定的比较器逆序。inventory.sort(comparing((a) -> a.getWeight()).reversed());

  thenComparing接受一个函数作为参数,若两个对象用第一个Comparator比较后是一样的,则提供第二Comparator。

  inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));

  

  谓语接口包括三个方法:negate,and和or。

  negate方法用来返回一个Predicate的非。

  Predicate<Apple> notRedApple = redApple.negate();

  and和or可以将两个Lambda表达式组合起来。and和or方法是按照在表达式链中的位置,从左到右确定优先级的

  Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150).or(a -> "green".equals(a.getColor()));

  

  Function中有andThen和compose两个默认方法,他们都会返回一个Function实例。

  andThen方法会返回一个函数,它先对输入应用过一个给定函数,在对输出应用另一个函数。

  Function<Integer, Integer> f = x -> x + 1;

  Function<Integer, Integer> g = x -> x * 2;

  Function<Integer, Integer> h = f.andThen(g);

  int result = h.apply(1); // g(f(x))

  h = f.compose(g);

  result = h.apply(1); // f(g(x));