lambda表达式 Java 8 引入的函数式编程思想
java8,作为一个从java5以来最具革命性的版本。
lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。
java8中一个非常重要的特性就是lambda表达式,我们可以把它看成是一种闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,一定程度上可以使代码看起来更加简洁。
虽然看着很先进,其实Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。本人建议不要乱用,因为这就和某些很高级的黑客写的代码一样,简洁,难懂,难以调试,维护人员想骂娘。
如果之前见到的话都会觉得,lambda就是一个匿名函数,我们可以这样来理解就好了,Lambda表达式就是为了使得我们的代码更加的简洁。
例如以前我们使用匿名内部类来实现代码:
//匿名内部类写法 new Thread(new Runnable() { @Override public void run() { System.out.println("内部类写法"); } }).start(); //使用lambda则更加简洁: new Thread(() -> System.out.println("lambda写法")).start();
lambda表达式语法
基本语法:
(parameters) -> expression
或
(parameters) ->{ statements; }
(1)左边括号:lambda的形参列表,就好比是我们定义一个接口,里面有一个抽象方法,这个抽象方法的形参列表。
(2)箭头:lambda的操作符,所以你看见这个箭头心中知道这是一个lambda表达式就可以了。
(3)右边lambda体:就好比是我们实现了接口中的抽象方法。
变量作用域
1、lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
public class Java8Tester { final static String salutation = "Hello! "; public static void main(String args[]){ GreetingService greetService1 = message -> System.out.println(salutation + message); greetService1.sayMessage("Runoob"); } interface GreetingService { void sayMessage(String message); } }
2、lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)。
public class Java8Tester { public static void main(String args[]) { final int num = 1; Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num)); s.convert(2); // 输出结果为 3 } public interface Converter<T1, T2> { void convert(int i); } }
3、在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = ""; Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错
函数式接口
想要对lambda表达式有一个深入的理解,我们需要去认识另外一个知识点,那就是函数式接口。
比如我们的Runnable就是一个函数式接口,我们可以到源码中看看。
他主要有如下的特点:
(1)含有@FunctionalInterface注解
(2)只有一个抽象方法
函数式接口能够接受匿名内部类的实例化对象,换句话说,我们可以使用匿名内部类来实例化函数式接口的对象,而Lambda表达式能够代替内部类实现代码的进一步简化。
因此lambda表达式 就是函数式接口的实例。
因此lambda表达式 就是函数式接口的实例。
因此lambda表达式 就是函数式接口的实例。
Java8里关于函数式接口的包是java.util.function,里面全部是函数式接口。
主要包含几大类:Function、Predicate、Supplier、Consumer和*Operator(没有Operator接口,只有类似BinaryOperator这样的接口)。
方法引用
当要传递给lambda体的操作已经有实现方法了,则可以使用方法引用。
方法引用就是lambda表达式。也就是函数式接口的实例。
要求:实现接口的抽象方法方法参数类型和返回值类型必须和方法引用的方法参数类型和返回值类型一致。
格式: ( ::)
有如下三种使用情况。
1 对象::实例方法,将lambda的参数当做方法的参数使用
Consumer<String> sc = System.out::println; //等效 Consumer<String> sc2 = (x) -> System.out.println(x); sc.accept("618, 狂欢happy");
2 类::静态方法,将lambda的参数当做方法的参数使用
//ClassName::staticMethod 类的静态方法:把表达式的参数值作为staticMethod方法的参数 Function<Integer, String> sf = String::valueOf; //等效 Function<Integer, String> sf2 = (x) -> String.valueOf(x); String apply1 = sf.apply(61888);
3 类::实例方法,将lambda的第一个参数当做方法的调用者,其他的参数作为方法的参数。开发中尽量少些此类写法,减少后续维护成本。
//ClassName::instanceMethod 类的实例方法:把表达式的第一个参数当成instanceMethod的调用者,其他参数作为该方法的参数 BiPredicate<String, String> sbp = String::equals; //等效 BiPredicate<String, String> sbp2 = (x, y) -> x.equals(y); boolean test = sbp.test("a", "A");
构造函数
无参的构造方法就是类::实例方法模型,如:
Supplier<User> us = User::new; //等效 Supplier<User> us2 = () -> new User(); //获取对象 User user = us.get();
当有参数时:
//一个参数,参数类型不同则会编译出错 Function<Integer, User> uf = id -> new User(id); //或加括号 Function<Integer, User> uf2 = (id) -> new User(id); //等效 Function<Integer, User> uf3 = (Integer id) -> new User(id); User apply = uf.apply(61888); //两个参数 BiFunction<Integer, String, User> ubf = (id, name) -> new User(id, name); User 狂欢happy = ubf.apply(618, "狂欢happy");
总结
java8引入lambda表达式是接收了函数式编程语言的思想,例如scala之类的。
lambda表达式可以使代码看起来简洁,但一定程度上增加了代码的可读性以及调试的复杂性。