Lambda表达式

在兼顾面向对象特性的基础上,Java语言通过Lambda表达式与方法引用等,为开发者打开了函数式编程的大门。下面我们做一个初探。

Lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。

 

一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:

public class Demo01Logger {
    private static void log(int level, String msg) {
        if (level == 1) {
          	System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";

        log(1, msgA + msgB + msgC);
    }
}

  这段代码存在问题:无论级别是否满足要求,作为log方法的第二个参数,三个字符串一定会首先被拼接并传入log方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费

 

体验Lambda的更优写法

使用Lambda必然需要一个函数式接口:

@FunctionalInterface
public interface MessageBuilder {  
   	String buildMessage(String... msgs);//可变参数
}

  然后对log方法进行改造:

public class Demo02LoggerLambda {
    private static void log(int level, MessageBuilder builder) {
        if (level == 1) {
          	System.out.println(builder.buildMessage());
        }
    }

    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";

        log(1, () -> msgA + msgB + msgC );
    }
}

  

说明:

由于上述使用了Lambda表达式,Lambda表达式是在执行代码:builder.buildMessage() 时才会执行msgA + msgB + msgC。

而builder.buildMessage() 能够执行的前提是level等于1.如果level不是1,那么就不会执行builder.buildMessage(),这样一来msgA + msgB + msgC也就不会执行了。

所以,只有当级别level满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。这样效率就比之前要提高了。

 

证明Lambda的延迟

下面的代码可以通过结果进行验证:

public class Demo03LoggerDelay {
    private static void log(int level, MessageBuilder builder) {
        if (level == 1) {
          	System.out.println(builder.buildMessage());
        }
    }

    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";

        log(2, () -> {
            System.out.println("Lambda执行!");
            return msgA + msgB + msgC;
        });
    }
}

  

从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。

扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。只不过使用new会在堆中开辟一个对象空间,这样也会浪费空间。

 

使用Lambda作为参数

如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的语法糖如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数

例如java.lang.Runnable接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。

public class Demo04Runnable {
    public static void main(String[] args) {
        startThread(()-> System.out.println("线程任务执行"));   
    }

    //自定义方法
    private static void startThread(Runnable task) {     //函数式接口作为参数,task是一个任务类
        new Thread(task).start();    //使用匿名函数创建线程,将任务类作为参数传入,并启动线程。
    }
}

  使用Lambda来取代内部类有诸多好处,不仅语义更加简洁明确,而且可以减少独立的.class字节码文件的相关操作从而节省性能。

 

自定义Lambda参数

自定义一个函数式接口MySupplier,含有无参数的抽象方法get得到Object类型的返回值。并使用该函数式接口作为方法的参数。

 

函数式接口MySupplier如:

@FunctionalInterface
//自定义一个函数式接口,抽象方法名为get,返回值类型为Object类型。
public interface MySupplier {
    Object get();
}

  使用该接口作为方法的参数,并且在传递参数时将实际参数写成Lambda:

public class Demo05Supplier {
    public static void main(String[] args) {
        printParam(()->"Hello");  //在传递参数时将实际参数写成Lambda
    }

    private static void printParam(MySupplier supplier) { //使用函数式接口作为参数
        System.out.println(supplier.get());
    }
}

  

使用Lambda作为返回值

类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时:

 

定义一个字符串数组按照字符串的长度降序排序

//需求:定义一个字符串数组按照字符串的长度降序排序。
public class Demo {
    public static void main(String[] args) {
        //定义一个字符串数组
        String[] arr={"abc","ab","abcds","adef"};
        //自然排序
//        Arrays.sort(arr);
//        //输出
//        System.out.println(Arrays.toString(arr));//[ab, abc, abcds, adef]
          //使用自定义比较器Comparator按照字符串长度进行排序
          /*Arrays.sort(arr, new Comparator<String>() {
              @Override
              public int compare(String o1, String o2) {
                  return o2.length() - o1.length();//因为是降序,所以是o2在前面
              }
          });*/
          //使用Lambda完成上述功能
        /*Arrays.sort(arr,(o1,o2)->{
            return o2.length() - o1.length();//[abcds, adef, abc, ab]
        });*/
        //简化版
//        Arrays.sort(arr,(o1,o2)-> o2.length() - o1.length());//[abcds, adef, abc, ab]
        Arrays.sort(arr,getComparator());//[abcds, adef, abc, ab]
        System.out.println(Arrays.toString(arr));//[abcds, adef, abc, ab]
    }
    //定义一个方法来提供自定义比较器Comparator的对象
    public static Comparator<String> getComparator()
    {
        //返回Comparator的对象
        /*return new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.length() - o1.length();//因为是降序,所以是o2在前面
            }
        };*/
        //使用Lambda表达式返回Comparator的对象
        return (o1,o2)-> o2.length() - o1.length();
    }
}
------------------------------------------------------
  运行结果:
[abcds, adef, abc, ab]

  其中直接return一个Lambda表达式即可

自定义Lambda返回值

请结合上文的MySupplier接口作为方法的返回值类型,并在方法的实现中使用Lambda表达式作为返回值内容。

public class Demo07MySupplier {
    private static MySupplier getData() {
      	return () -> "Hello";
    }

    private static void printData(MySupplier supplier) {
      	System.out.println(supplier.get());
    }

    public static void main(String[] args) {
      	printData(getData()); //其中main方法不再自己指定Lambda表达式,而是通过调用一个getData方法来获取Lambda的内容。
    }
}

  

其中main方法不再自己指定Lambda表达式,而是通过调用一个getData方法来获取Lambda的内容。

对于Lambda的总结:

其实可以理解为1.8之后Lambda的诞生的思想是write less do more(写的少,做的多)。间接可以将Lambda理解为是对匿名内部类的简化,Lambda可以更节省空间和代码量并完成相同的功能。

 

上一篇:JavaScript类继承


下一篇:leetcode [179]Largest Number