zoukankan      html  css  js  c++  java
  • 《Java 8 实战》(二)—— Lambda

    Lambda表达式可以理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表/函数主体/返回类型,可能还有一个可以抛出的异常列表。

    Lambda表达式由参数/箭头和主体组成

    (Apple a1, Apple a2)  -> a1.getWeight().compareTo(a2.getWeight());

    之前的代码形式:

    Comparator<Apple> byWeight = new Comparator<Apple>() {
      public int compare(Apple a1, Apple a2) {

        return a1.getWeight().compareTo(a2.getWeight());

      }

    }

    Java 8 中有效的Lambda表达式:

    1,  (String s) -> s.length()

      具有一个String类型的参数,并返回一个int。Lambda表达式没有return语句,因为已经隐含了return。

    2,  (Apple a) -> a.getWeight() > 150

      参数为Apple类型,返回一个boolean类型。

    3,  (int x, int y) -> {

        System.out.println("Result");

        System.out.println(x+y);

      }

      该Lambda表达式具有两个int类型的参数而没有返回值。Lambda表达式可以包含多行语句。

      () -> {return "Mario";}

      该Lambda表达式也是有效的。

    4, () - > 42

      该Lambda表达式没有参数,返回一个int

    5, (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())

      该表达式具有两个Apple类型的参数,返回一个int

    无效Lambda表达式

    (Integer i) -> return "Alan" + i;

    (String s) -> {"IronMan";}

    第一个表达式中,return是一个控制流语句。要使得这个语句有效,需要加上花括号。

    第二个表达式中,"IronMan" 是一个表达式,不是语句。要使得此Lambda有效,可以去除花括号和分号。

    Lambda基本语法

    (parameters) -> expression

    or

    (parameters) -> { statements;}

    可以使用Lambda表达式的地方 —— 函数式接口

      只有在接受函数式接口的地方才可以使用Lambda表达式。

      函数式接口就是只定义一个抽象方法的接口。如Comparator和Runnable接口。

      Lambda表达式可以直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

      函数式接口的抽象方法的签名就是Lambda表达式的签名。这种抽象方法叫做函数描述符。

      新的Java API中,函数式接口带有@FunctionalInterface的标注。如果用@FunctionalInterface定义了一个接口,但它却不是函数式接口的话,便一起将返回一个提示原因的错误,例如“Multiple non-overriding abstract methods found in interface Foo”,表明存在多个抽象方法。

    使用Lambda表达式的步骤

    1,行为参数化:

      提取Lambda表达式,设计好参数,函数主体和返回值。

    2,使用函数式接口来传递行为:

      创建一个能匹配Lambda表达式的函数式接口I,并把这个接口作为参数传递给需要使用Lambda表达式的函数M。

    3,执行函数式接口中的行为

      在函数M中调用接口I中的抽象函数

    4,传递Lambda表达式

    常用函数式接口:

    Java 8 以前已有的函数式接口:

      Comparable

      Runnable

      Callable

    Java 8 在java.util.function包中引入的新的函数式接口:

      Predicate  

    @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 s: list){
            if(p.test(s)){
                results.add(s);
            }
        }
        return results;
    }
    Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
    List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);

      Consumer  

      

    @FunctionalInterface
    public interface Consumer<T>{
        void accept(T t);
    }
    public static <T> void forEach(List<T> list, Consumer<T> c){    
        for(T i: list){
            c.accept(i);
        }
    }
    forEach(Arrays.asList(1,2,3,4,5),(Integer i) -> System.out.println(i));

      Function

    @FunctionalInterface
    public interface Function<T, R>{
        R apply(T t);
    }
    public static <T, R> List<R> map(List<T> list,Function<T, R> f) {
        List<R> result = new ArrayList<>();
        for(T s: list){
            result.add(f.apply(s));
        }
        return result;
    }
    
    List<Integer> l = map(Arrays.asList("lambdas","in","action"),(String s) -> s.length());

    Lambda及函数式接口例子

    使用案例  Lambda的例子  对应的函数式接口
    布尔表达式  (List<String> list) -> list.isEmpty()   Predicate<List<String>>
    创建对象  () -> new Apple(10) Supplier<Apple>
    消费一个对象 (Apple a) -> System.out.println(a.getWeight())

     

    Consumer<Apple>

     从一个对象中选择/提取  (String s) -> s.length()  

    Function<String, Integer>或
    ToIntFunction<String>

     合并两个值  (int a, int b) -> a * b  IntBinaryOperator
    比较两个对象   

    (Apple a1, Apple a2) ->
    a1.getWeight().compareTo(a2.getWeight())

     

    Comparator<Apple>或
    BiFunction<Apple, Apple, Integer>
    或ToIntBiFunction<Apple, Apple>

    函数式接口异常:

    任何函数式接口都不允许抛出受检异常(checked Exception。 如果需要Lambda表达式抛出异常,有两种方式:

    1,定义一个自己的函数式接口,并声明受检异常: 

    @FunctionalInterface
    public interface BufferedReaderProcessor {
        String process(BufferedReader b) throws IOException;
    }
    BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();

    2,把Lambda包在一个try/cache块中。

    Function<BufferedReader, String> f = (BufferedReader b) -> {
        try {
            return b.readLine();
        }
        catch(IOException e) {
            throw new RuntimeException(e);
        }
    };

    Lambda表达式类型检查:

    Lambda的类型是从使用Lambda的上下文推断出来的。上下文中Lambda表达式需要的类型成为目标类型。例如: 

    List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);

    类型检查过程:

    1,找出filter方法的声明

    2,要求它是predicate<Apple>对象的第二个正式参数。

    3,Predicate<Apple>是一个函数式接口,定义了一个叫做test的抽象方法。

    4,test方法描述了一个函数描述符,它可以接受一个Apple,并返回一个boolean.

    5, filter的任何实际参数都匹配这个要求。

    Lambda表达式类型推断

    编译器可以了解Lambda表达式的参数类型,这样可以在Lambda语法中省去标注参数类型,即:

    List<Apple> greenApples = filter(inventory, a -> "green".equals(a.getColor()));

    但有时显式写出类型更易读,有时候去掉更易读,需要自己权衡。

    当Lambda仅有一个类型需要推断的参数时,参数名称两边的括号也可以省略。

    方法引用

    参考:http://www.cnblogs.com/chenpi/p/5885706.html

    什么是方法引用

      简单地说,就是一个Lambda表达式。在Java 8中,我们会使用Lambda表达式创建匿名方法,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8的方法引用允许我们这样做。方法引用是一个更加紧凑,易读的Lambda表达式,注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。

    方法引用例子

     先看一个例子

    首先定义一个Person类,如下:

    复制代码
    package methodreferences;
    
    import java.time.LocalDate;
    
    public class Person
    {
    
        public Person(String name, LocalDate birthday)
        {
            this.name = name;
            this.birthday = birthday;
        }
    
        String name;
        LocalDate birthday;
    
        public LocalDate getBirthday()
        {
            return birthday;
        }
    
        public static int compareByAge(Person a, Person b)
        {
            return a.birthday.compareTo(b.birthday);
        }
    
        @Override
        public String toString()
        {
            return this.name;
        }
    }
    复制代码

    假设我们有一个Person数组,并且想对它进行排序,这时候,我们可能会这样写:

    原始写法

    复制代码
    package methodreferences;
    
    import java.time.LocalDate;
    import java.util.Arrays;
    import java.util.Comparator;
    
    public class Main
    {
    
        static class PersonAgeComparator implements Comparator<Person> {
            public int compare(Person a, Person b) {
                return a.getBirthday().compareTo(b.getBirthday());
            }
        }
        
        public static void main(String[] args)
        {
            Person[] pArr = new Person[]{
                new Person("003", LocalDate.of(2016,9,1)),
                new Person("001", LocalDate.of(2016,2,1)),
                new Person("002", LocalDate.of(2016,3,1)),
                new Person("004", LocalDate.of(2016,12,1))};
    
            Arrays.sort(pArr, new PersonAgeComparator());
            
            System.out.println(Arrays.asList(pArr));
        }
    }
    复制代码

    其中,Arrays类的sort方法定义如下:

    public static <T> void sort(T[] a, Comparator<? super T> c)

    这里,我们首先要注意Comparator接口是一个函数式接口,因此我们可以使用Lambda表达式,而不需要定义一个实现Comparator接口的类,并创建它的实例对象,传给sort方法。

    使用Lambda表达式,我们可以这样写:

    改进一,使用Lambda表达式,未调用已存在的方法

    复制代码
    package methodreferences;
    
    import java.time.LocalDate;
    import java.util.Arrays;
    
    public class Main
    {
    
        public static void main(String[] args)
        {
            Person[] pArr = new Person[]{
                new Person("003", LocalDate.of(2016,9,1)),
                new Person("001", LocalDate.of(2016,2,1)),
                new Person("002", LocalDate.of(2016,3,1)),
                new Person("004", LocalDate.of(2016,12,1))};
    
            Arrays.sort(pArr, (Person a, Person b) -> {
                return a.getBirthday().compareTo(b.getBirthday());
            });
            
            System.out.println(Arrays.asList(pArr));
        }
    }
    复制代码

    然而,在以上代码中,关于两个人生日的比较方法在Person类中已经定义了,因此,我们可以直接使用已存在的Person.compareByAge方法。

    改进二,使用Lambda表达式,调用已存在的方法

    复制代码
    package methodreferences;
    
    import java.time.LocalDate;
    import java.util.Arrays;
    
    public class Main
    {
    
        public static void main(String[] args)
        {
            Person[] pArr = new Person[]{
                new Person("003", LocalDate.of(2016,9,1)),
                new Person("001", LocalDate.of(2016,2,1)),
                new Person("002", LocalDate.of(2016,3,1)),
                new Person("004", LocalDate.of(2016,12,1))};
    
            Arrays.sort(pArr, (a, b) -> Person.compareByAge(a, b));
            
            System.out.println(Arrays.asList(pArr));
        }
    }
    复制代码

    因为这个Lambda表达式调用了一个已存在的方法,因此,我们可以直接使用方法引用来替代这个Lambda表达式,

    改进三,使用方法引用

    复制代码
    package methodreferences;
    
    import java.time.LocalDate;
    import java.util.Arrays;
    
    public class Main
    {
    
        public static void main(String[] args)
        {
            Person[] pArr = new Person[]{
                new Person("003", LocalDate.of(2016,9,1)),
                new Person("001", LocalDate.of(2016,2,1)),
                new Person("002", LocalDate.of(2016,3,1)),
                new Person("004", LocalDate.of(2016,12,1))};
    
            Arrays.sort(pArr, Person::compareByAge);
            
            System.out.println(Arrays.asList(pArr));
        }
    }
    复制代码

    在以上代码中,方法引用Person::compareByAge在语义上与Lambda表达式 (a, b) -> Person.compareByAge(a, b) 是等同的,都有如下特性:

    • 真实的参数是拷贝自Comparator<Person>.compare方法,即(Person, Person);
    • 表达式体调用Person.compareByAge方法;

    四种方法引用类型

    静态方法引用

    我们前面举的例子Person::compareByAge就是一个静态方法引用。

    特定实例对象的方法引用

    如下示例,引用的方法是myComparisonProvider 对象的compareByName方法;

    复制代码
            class ComparisonProvider
            {
                public int compareByName(Person a, Person b)
                {
                    return a.getName().compareTo(b.getName());
                }
    
                public int compareByAge(Person a, Person b)
                {
                    return a.getBirthday().compareTo(b.getBirthday());
                }
            }
            ComparisonProvider myComparisonProvider = new ComparisonProvider();
            Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
    复制代码

    任意对象(属于同一个类)的实例方法引用

    如下示例,这里引用的是字符串数组中任意一个对象的compareToIgnoreCase方法。

            String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
            Arrays.sort(stringArray, String::compareToIgnoreCase);

    构造方法引用

    如下示例,这里使用了关键字new,创建了一个包含Person元素的集合。

    Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
    transferElements方法的定义如下,功能为集合拷贝,
    复制代码
    public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
        DEST transferElements(
            SOURCE sourceCollection,
            Supplier<DEST> collectionFactory) {
            
            DEST result = collectionFactory.get();
            for (T t : sourceCollection) {
                result.add(t);
            }
            return result;
    }
    复制代码

    什么场景适合使用方法引用

    当一个Lambda表达式调用了一个已存在的方法

    什么场景不适合使用方法引用

    当我们需要往引用的方法传其它参数的时候,不适合,如下示例:

    IsReferable demo = () -> ReferenceDemo.commonMethod("Argument in method.");

    Lambda 和 方法引用实战

    第一步,传递代码

    public class AppleComparator implements Comparator<Apple> {
        public int compare(Apple a1, Apple a2) {
            return a1.getWeight().compareTo(a2.getWeight());
        }
    }
    
    inventory.sort(new AppleComparator());

    第二步,使用匿名类

    inventory.sort(new Comparator<Apple>() {
        public int compare(Apple a1, Apple a2) {
            return a1.getWeight().compareTo(a2.getWeight());
        }
    })

    第三步,使用Lambda表达式

    inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

    Java编译器可以根据Lambda出现的上下文来推断Lambda表达式参数的类型,所以可以改写为:

    inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));

    Comparator具有一个叫做comparing的静态方法,可以接受一个Function来提取Comparable键值,并生成一个Comparator对象,如下:

    Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());

    所以再次改写:

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

    第四步,使用方法引用

    inventory.sort(comparing(Apple::getWeight));

    复合Lambda表达式

    1,比较器复合

      a,逆序

    inventory.sort(comparing(Apple::getWeight).reversed())

      b, 比较器链

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

    2, 谓词复合

      a,negate

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

      b, and

    Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);

      c, or

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

    3, 函数复合

      a,andThen 相当于g(f(x))

    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); // result = 4

      b, compose 相当于f(g(x))

    Function<Integer, Integer> f = x -> x + 1;
    Function<Integer, Integer> g = x -> x * 2;
    Function<Integer, Integer> h = f.compose(g);
    int result = h.apply(1); // result = 3
  • 相关阅读:
    jquery 应用
    SQL Server表分区
    .NET Framework 各版本区别
    后台添加控件时,必须每次重画控件,才能从前台获取控件数据。
    SVN文件库移植(转)
    C# WebService 的缓存机制
    OpenGL C#绘图环境配置
    java 调用webservice的各种方法总结
    SQLServer锁的概述
    C# Word 类库
  • 原文地址:https://www.cnblogs.com/IvySue/p/6737133.html
Copyright © 2011-2022 走看看