lambda表达式详解

lambda介绍

  • 匿名:没有一个确定的名称
  • 函数:lambda不属于一个特定的类,但是却有参数列表、函数主体、返回类型、异常列表
  • 传递:可以作为参数传递给方法、或者存储在变量中
  • 简洁:不需要写很多模板代码
基本语法
(parameters) -> expression
(parameters) -> (statements)
根据上述语法规则,以下哪个不是有效的Lambda表达式?
(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "Alan" + i;
(5) (String s) -> {"IronMan";}
答案:只有4和5是无效的Lambda。
(1) 这个Lambda没有参数,并返回void。 它类似于主体为空的方法: public void run() {}。
(2) 这个Lambda没有参数,并返回String作为表达式。
(3) 这个Lambda没有参数,并返回String(利用显式返回语句)。
(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:
(Integer i) -> {return "Alan" + i;}。
(5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号
和分号,如下所示: (String s) -> "Iron Man"。或者如果你喜欢,可以使用显式返回语
句,如下所示: (String s)->{return "IronMan";}

哪里使用lambda

  • 你可以在函数式接口上使用Lambda表达式
  • 函数式接口就是只定义一个抽象方法的接口。你已经知道了Java API中的一些
    其他函数式接口,如我们在第2章中谈到的Comparator和Runnable。
下面哪些接口是函数式接口?
public interface Adder{
int add(int a, int b);
}
public interface SmartAdder extends Adder{
int add(double a, double b);
}
public interface Nothing{
}
答案:只有Adder是函数式接口。
SmartAdder不是函数式接口,因为它定义了两个叫作add的抽象方法(其中一个是从Adder那里继承来的)。
Nothing也不是函数式接口,因为它没有声明抽象方法。
Runnable r1 = () -> System.out.println("Hello World 1");
Runnable r2 = new Runnable(){
	public void run(){
		System.out.println("Hello World 2");
	}
};
public static void process(Runnable r){
	r.run();
}
process(r1);
process(r2);
process(() -> System.out.println("Hello World 3"))
下哪些是使用Lambda表达式的有效方式?
(1) execute(() -> {});
public void execute(Runnable r){
	r.run();
}
(2) public Callable fetch() {
	return () -> "Tricky example ;-)";
}
(3) Predicate p = (Apple a) -> a.getWeight();
答案:只有1和2是有效的。
第一个例子有效,是因为Lambda() -> {}具有签名() -> void,这和Runnable中的
抽象方法run的签名相匹配。请注意,此代码运行后什么都不会做,因为Lambda是空的!
第二个例子也是有效的。事实上, fetch方法的返回类型是Callable。
Callable基本上就定义了一个方法,签名是() -> String,其中T被String代替
了。因为Lambda() -> "Trickyexample;-)"的签名是() -> String,所以在这个上下文
中可以使用Lambda。
第三个例子无效,因为Lambda表达式(Apple a) -> a.getWeight()的签名是(Apple) ->
Integer,这和Predicate:(Apple) -> boolean中定义的test方法的签名不同

环绕执行

初次定义自己的函数式接口:

package top.hengshare.interviewer.java8.lambda;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class HuanRao {

    /**
     * 记得行为参数化
     * @return
     * @throws IOException
     */
    public static String processFile() throws IOException {
        try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
            return br.readLine();
        }
    }

    /**
     * 执行一个行为
     * @param bufferedReaderProcessor
     * @return
     * @throws IOException
     */
    public static String processFile(BufferedReaderProcessor bufferedReaderProcessor) throws IOException{
        try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
            return bufferedReaderProcessor.process(br);
        }
    }

    public static void main(String[] args) throws IOException {
        String result = HuanRao.processFile();
    }
}

/**
 * 使用函数式接口传递行为
 * 接口只包含一个抽象方法,此实例的函数描述符为 (BufferedReader) -> String
 * 反应到函数定义就是:返回值为String,函数的参数为BuffereReader类型
 */
@FunctionalInterface
interface BufferedReaderProcessor{
    String process(BufferedReader b)throws IOException;

    public static void main(String[] args) throws IOException {
        //传递lambda表达式
        HuanRao.processFile(BufferedReader::readLine);
        HuanRao.processFile((BufferedReader br) -> br.readLine()+br.readLine());
    }
}

函数式接口

上面是我们自定义的函数式接口,现在,我们使用Java8中提供的几个函数式接口来完成一些功能:

package top.hengshare.interviewer.java8.lambda;

import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * 在 @{HuaRao}中我们发现函数式接口的定义很简单,所以应该是可以共用的。
 * Java8中,已经提供了几个常用的函数式接口供我们使用,比如:
 * Comparable, Runnable, Callable, Predicate, Consumer, Function等
 * 下面我们来使用这些函数式接口
 */
public class Java8Interface {

    /**
     * java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受泛型
     * T对象,并返回一个boolean。
     * @param list
     * @param p
     * @param 
     * @return
     */
    public static <T> List<T> filter(List<T> list, Predicate<T> p) {
        List<T> results = Lists.newArrayList();
        for (T s : list) {
            if (p.test(s)) {
                results.add(s);
            }
        }
        return results;
    }

    /**
     * java.util.function.Consumer定义了一个名叫accept的抽象方法,它接受泛型T
     * 的对象,没有返回( void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用
     * 这个接口。
     *
     * 你可以用它来创建一个forEach方法,接受一个Integers的列表,并对其中
     * 每个元素执行操作
     *
     * @param list
     * @param consumer
     * @param 
     */
    public static <T> void forEach(List<T> list, Consumer<T> consumer) {
        for (T i: list) {
            consumer.accept(i);
        }
    }

    /**
     * java.util.function.Function接口定义了一个叫作apply的方法,它接受一个
     * 泛型T的对象,并返回一个泛型R的对象。如果你需要定义一个Lambda,将输入对象的信息映射
     * 到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。
     *
     * 在下面的代码中,我们向你展示如何利用它来创建一个map方法,以将一个String列表映射到包含每个
     * String长度的Integer列表。
     * @param list
     * @param f
     * @param 
     * @param 
     * @return
     */
    public static <T, R> List<R> map(List<T> list, Function<T, R> f){
        ArrayList<R> result = Lists.newArrayList();
        for (T t : list) {
            result.add(f.apply(t));
        }
        return result;
    }

    public static void main(String[] args) {
        List<String> listOfString = Lists.newArrayList();
        Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
        List<String> filter = filter(listOfString, nonEmptyStringPredicate);
        System.out.println(filter);

        forEach(Arrays.asList(1,2,3,4,5), (System.out::println));

        List<Integer> map = map(Arrays.asList("lambdas", "in", "action"), (String::length));
        System.out.println(map);
    }

}

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,
								LongSupplier,
								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>
布尔表达式			 (List list) -> list.isEmpty() 			Predicate>
创建对象				 () -> new Apple(10) 							Supplier
消费一个对象			 (Apple a) ->System.out.println(a.getWeight())	Consumer
从一个对象中选择/提取	 (String s) -> s.length() 						Function或ToIntFunction
合并两个值			 (int a, int b) -> a * b						IntBinaryOperator
比较两个对象			 (Apple a1, Apple a2) ->a1.getWeight().compareTo(a2.getWeight())
																	Comparator或BiFunction或 ToIntBiFunction

类型检查、类型推断、限制

尽量不要使用局部变量,如果使用局部变量,那得把局部变量设置为final的,原因就是局部变量是存放在栈中的,存放在栈中的变量只能被读取,不能被改变,这类似于Java中的final限定符,为了在编译的时候可以检查出来这个错误,我们应该在lambda表达式使用到局部变量的地方将局部变量使用final进行限制。

对于实例变量的使用,我们可以正常使用,因为实例变量存放于内存的堆中,堆中的变量是可以被读取和改变的。这类似于Java中将对象引用作为参数传递到一个方法中的用法。

方法引用

方法引用我们可以把它看做是 仅仅调用特定方法的Lambda的一种快捷写法。
实例如下:

(Apple a) -> a.getWeight()			 			 Apple::getWeight
() -> Thread.currentThread().dumpStack()		 Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) 					 String::substring
(String s) -> System.out.println(s) 			 System.out::println

方法引用可以分为三类:

  1. 指向静态方法
    (args) -> ClassName.staticMethod(args)
    //可以写成
    ClassName::staticMethod
    
  2. 指向实例对象的任意方法(arg0是ClassName类型的)
    (arg0, rest) -> arg0.instanceMethod(rest)
    //可以写成
    ClassName::instanceMethod
    
  3. 指向实例对象的属性的方法引用
    (args) -> expr.instanceMethod(args)
    //可以写成
    expr::instanceMethod
    

下面是几个构造函数的例子,某些情况下,使用构造函数引用可以让代码简洁:

//不带参数的构造函数
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
//等价于
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
//带参数的构造函数
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);
//等价于
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);
//带有两个参数的构造函数
iFunction<String, Integer, Apple> c3 = Apple::new;
Apple c3 = c3.apply("green", 110);
//等价于
BiFunction<String, Integer, Apple> c3 =(color, weight) -> new Apple(color, weight);
Apple c3 = c3.apply("green", 110);
//不将构造函数实例化,但可以引用它,这有一些有趣的应用,如下:
static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
static {
	map.put("apple", Apple::new);
	map.put("orange", Orange::new);
	// etc...
}
//利用构造函数引用,新创建了一个方法,用两个参数可以得到具有制定重量的不同水果
public static Fruit giveMeFruit(String fruit, Integer weight){
	return map.get(fruit.toLowerCase())
			  .apply(weight);
}

lambda和方法引用实战

在上面,我们分别对java8中的函数式,匿名类,lambda表达式,方法引用进行了详细的介绍,下面,我们分步骤,一步一步的综合起来看一下在实际的应用中我们可以怎么使用,以及为什么这么使用:

package top.hengshare.interviewer.java8.lambda;

import com.google.common.collect.Lists;

import java.util.Comparator;
import java.util.List;

/**
 * @author StivenYang
 * @program interview
 * @description Java8实战第一章实战代码
 * @date 2019-07-22 23:55
 **/
public class Practice01 {
    public static void main(String[] args) {
        List<Apple> appleList = Lists.newArrayList();
        Apple green = new Apple("green");
        green.setWeight(2D);
        appleList.add(green);
        Apple red = new Apple("red");
        red.setWeight(3D);
        appleList.add(red);

        //第一步:传递代码
        appleList.sort(new AppleComparator());
        System.out.println(appleList);
        //第二步:使用匿名类
        appleList.sort(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight().compareTo(o2.getWeight());
            }
        });
        System.out.println(appleList);
        //第三部:使用lambda表达式
        appleList.sort((Apple o1, Apple o2) -> o1.getWeight().compareTo(o2.getWeight()));
        System.out.println(appleList);
        //因为jvm可以根据lambda上下文来推断lambda表达式的参数类型,所以还可以简化为下面这种写法
        appleList.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));
        System.out.println(appleList);
        //又因为java8的comparator接口中有comparing静态方法,它可以像下面这样用:
        appleList.sort(Comparator.comparing((apple -> apple.getWeight())));
        System.out.println(appleList);
        //第四步:使用方法引用
        appleList.sort(Comparator.comparing(Apple::getWeight));
        System.out.println(appleList);
    }
}

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

复合lambda表达式

比较器复合,从表面上意思是多个lambda表达式连在一起使用,实际也是如此,这样我们可以根据对象的不同属性对列表中的对象进行排序或者做其他一些操作:

package top.hengshare.interviewer.java8.lambda;

import com.google.common.collect.Lists;

import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

import static top.hengshare.interviewer.java8.lambda.Java8Interface.filter;

public class Complex {
    public static void main(String[] args) {
        List<Apple> appleList = Lists.newArrayList();
        Apple green = new Apple("green");
        green.setWeight(2D);
        appleList.add(green);
        Apple red = new Apple("red");
        red.setWeight(3D);
        appleList.add(red);

        //比较器复合
        //好处:简单排序很方便
        //逆序排序一个数组
        appleList.sort(Comparator.comparing(Apple::getWeight).reversed());
        //比较器链
        appleList.sort(Comparator.comparing(Apple::getColor)
                .reversed()
                .thenComparing(Apple::getWeight));

        //谓词复合
        //谓词接口包含三个方法:and\or\negate,可以使用已经有的Predicate来创建更加复杂的谓词
        //好处是:谓词复杂了,lambda表达式就可以写的比较简单,易读了
        Predicate<Apple> redApple = apple -> "red".equals(apple.getColor());
        List<Apple> redAppleList = filter(appleList, redApple);
        System.out.println(redAppleList);
        //不是红苹果
        Predicate<Apple> notRedApple = redApple.negate();
        List<Apple> notRedAppleList = filter(appleList, notRedApple);
        System.out.println(notRedAppleList);
        //红苹果,而且重量超过150g
        Predicate<Apple> andPredicate = redApple.and(apple -> apple.getWeight() > 150);
        List<Apple> andPredicateList = filter(appleList, andPredicate);
        System.out.println(andPredicateList);
        //要么是重150g以上的红苹果,要么是绿苹果
        Predicate<Apple> or = redApple.and(apple -> apple.getWeight() > 150).or(apple -> "green".equals(apple.getColor()));
        List<Apple> result = filter(appleList, or);
        System.out.println(result);

        //函数复合
        //好处是,可以重复利用我们定义好的函数,来拼装成为一个更加复杂的函数,类似于数学中的积分
        //可以把Function接口所代表的Lambda表达式复合起来。 Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例
        Function<Integer, Integer> f = x -> x + 1;
        Function<Integer, Integer> g = x -> x * 2;
        //andThen: 下面类似于g(f(x))
        Function<Integer, Integer> h = f.andThen(g);
        int function1 = h.apply(1);
        System.out.println(function1);
        //compose: 下面类似于f(g(x))
        Function<Integer, Integer> k = f.compose(g);
        Integer function2 = k.apply(1);
        System.out.println(function2);
    }
}

有关函数复合的应用,我们可以用一个对静态方法的使用的过程来说明:我们有一个处理信封的工具类,这个工具类可以添加标题、添加结尾、以及检查语法。我们可以使用这三个方法来组装成不同的处理流水线来处理数据:

package top.hengshare.interviewer.java8.lambda;


import java.util.function.Function;

public class FunctionComplex {
    public static void main(String[] args) {
        String text = "我是一个普通的程序员,我喜欢敲代码,它可以让我实现自己的价值,帮助这个社会变得更美好。\n我最近在学习labda表达式,他真是太方便,太强大了!";

        Function<String, String> addHeader = Letter::addHeader;
        Function<String, String> resultFunction = addHeader.andThen(Letter::addFooter)
                .andThen(Letter::checkSpelling);
        String result = resultFunction.apply(text);
        System.out.println(result);
    }
}

class Letter{
    public static String addHeader(String text){
        return "亲爱的xxx: \n    " + text;
    }
    public static String addFooter(String text){
        return text + " \n    此致!敬礼!";
    }
    public static String checkSpelling(String text){
        return text.replaceAll("labda", "lambda");
    }
}

数学中的类似思想

下面用lambda表达式完成一个简单的积分计算:

  1. 设计函数的调用形式,根据调用形式来确定函数的描述符
  2. 根据需求,完成函数的定义
package top.hengshare.interviewer.java8.lambda;

import java.util.function.DoubleFunction;

public class Integral {
    public static void main(String[] args) {
        //1. 对线性方程y=x+10求积分,函数调用的时候是这样 g(f, a, b),其中,a和b是积分的上下区间
        //f的描述符应该是double->double,符合的有DoubleFunction接口
        double result = Integral.integral((double x) -> x + 10, 3, 7);
        System.out.println(result);
    }

    //2. 完成函数的定义
    public static double integral(DoubleFunction<Double> f, double a, double b){
        //上底加下底乘高除以2是梯形的面积
        return (f.apply(a)+f.apply(b))*(b-a)/2.0;
    }
}

小结

  1. Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表
  2. Lambda表达式让你可以简洁地传递代码
  3. 函数式接口就是仅仅声明了一个抽象方法的接口
  4. 只有在接受函数式接口的地方才可以使用Lambda表达式
  5. Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例
  6. Java 8自带一些常用的函数式接口,放在java.util.function包里,包括Predicate、 Function、 Supplier、 Consumer和BinaryOperator
  7. 为了避免装箱操作,对Predicate和Function等通用函数式接口的原始类型
    特化: IntPredicate、 IntToLongFunction等
  8. 环绕执行模式(即在方法所必需的代码中间,你需要执行点儿什么操作,比如资源分配和清理)可以配合Lambda提高灵活性和可重用性
  9. Lambda表达式所需要代表的类型称为目标类型
  10. 方法引用让你重复使用现有的方法实现并直接传递它们
  11. Comparator、 Predicate和Function等函数式接口都有几个可以用来结合Lambda表达式的默认方法

你可能感兴趣的:(java)