为跳槽面试做准备,今天开始进入 Java 基础的复习。希望基础不好的同学看完这篇文章,能掌握 lambda 表达式,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆。
Java8 是我们使用最广泛的稳定 Java 版本,lambda 就是其中最引人瞩目的新特性。lambda 是一种闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,可以使代码看起来更加简洁。是不是听得一脸懵逼?我举个栗子你就明白了。
烂掉牙的例子,在没有 lambda 时候,我们是这样写的:
// 内部类写法 public class InnerClassMain { public static void main(String[] args) { //匿名内部类写法 new Thread(new Runnable() { @Override public void run() { System.out.println("内部类写法"); } }).start(); } } 复制代码
有 lambda 之后,我们就用 lambda 写:
// lambda 写法 public class LambdaMain { public static void main(String[] args) { //lambda 写法 new Thread(() -> System.out.println("lambda写法")).start(); } } 复制代码
我们应该知道,实现线程有两种方法, 一是继承 Thread 类,二是实现 Runnable 接口。 那这里采用的就是后者,后者是一个 函数式接口。
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); } 复制代码
从 Runnable 源码可以看到,它是一个**函数式接口。**这类接口的特点是: 用 @FunctionalInterface 注解修饰(主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错),有且只有一个抽象方法。在原生 JDk 中的这类接口就可以使用 lambda 表达式。
上面的概念提到,把函数当做参数来使用。上面的 lambda 例子中, Thread 类的参数就是一个 Runnable 接口,lambda 就是实现这个接口并把它当做参数使用。所以上面的 () -> System.out.println("lambda写法") 就是一个整个 lambda 表达式的参数 (注意与后面的方法参数区分开,后面会讲)。细品加粗这句话,可以总结出,**lambda 表达式就是创建某个类的函数式接口的实例对象。**如:
Runnable runnable = () -> System.out.println("lambda写法"); 复制代码
明白了什么是 lambda 表达式,那为什么要使用它呢?注意到使用 lambda 创建线程的时候,我们并不关心 接口名,方法名,参数名。 我们只关注他的 参数类型,参数个数,返回值。 所以原因就是 简化代码,提高可读性 。
// 格式遵循: (接口参数)->表达式(具体实现的方法) (paramters) -> expression 或 (parameters) ->{ expressions; } 复制代码
具体解释,如上图。此外,lambda 语法注意点:
使用示例:
public class Example { // 定义函数式接口,只能有一个抽象接口,否则会报错 // 希望在编译期检出报错,请加 @FunctionalInterface 注解 public interface Hello { String hi(); } public interface Hello2 { String hei(String hello); } public interface Hello3 { String greet(String hello, String name); } public static void main(String[] args) { // 入参为空 Hello no_param = () -> "hi, no param"; Hello no_param2 = () -> { return "hi, no param"; }; System.out.println(no_param.hi()); System.out.println(no_param2.hi()); // 单个参数,一条返回语句,可以省略大括号和 return Hello2 param = name -> name; Hello2 param2 = name -> { return name; }; // 打印 System.out.println(param.hei("hei, 一个优秀的废人")); System.out.println(param2.hei("hei, 一个优秀的废人")); // 多个参数 Hello3 multiple = (String hello, String name) -> hello + " " + name; // 一条返回语句,可以省略大括号和 return Hello3 multiple2 = (hello, name) -> hello + name; // 多条处理语句,需要大括号和 return Hello3 multiple3 = (hello, name) -> { System.out.println(" 进入内部 "); return hello + name; }; // 打印 System.out.println(multiple.greet("hello,", "祝2020脱单")); System.out.println(multiple2.greet("hello,", "祝2020脱单")); System.out.println(multiple3.greet("hello,", "祝2020脱单")); } } 复制代码
看一个简单的方法引用例子:
Consumer<String> sc = System.out::println; sc.accept("一个优秀的废人"); // 等效于 Consumer<String> sc2 = (x) -> System.out.println(x); sc2.accept("一个优秀的废人"); 复制代码
Consumer 函数式接口源码:
@FunctionalInterface public interface Consumer<T> { void accept(T t); } 复制代码
你可能有点懵,为什么可以这样写?别急我们分析一波:Consumer 是一个函数式接口,抽象方法是 void accept(T t),参数都是 T。那我们现在有这样一个需求,我想利用这个接口的抽象方法,做一下控制台打印。正常情况下,我们需要实现这个接口,实现它的抽象方法,来实现这个需求:
public class ConsumerImpl implements Consumer<String> { @Override public void accept(String s) { System.out.println(s); } } 复制代码
实现之后,这个抽象方法变具体了。作用就是控制台打印,那就意味着抽象方法刚好可以用实际方法: System.out.println(s) 来实现,所以我们可以使用方法引用。
方法引用的三种形式:
// 将抽象方法参数当做实际方法的参数使用 对象::实例方法 objectName::instanceMethod // 将抽象方法参数当做实际方法的参数使用 类::静态方法 ClassName::staticMethod // 将方法参数的第一个参数当做方法的调用者,其他的参数作为方法的参数 类::实例方法 ClassName::instanceMethod 复制代码
自定义一个方法类:
public class Method { // 静态方法 public static void StaticMethod(String name) { System.out.println(name); } // 实例方法 public void InstanceMethod(String name) { System.out.println(name); } // 无参构造方法 public Method() { } // 有参数构造 public Method(String methodName) { System.out.println(methodName); } } 复制代码
测试用例:
public class MethodExample { public static void main(String[] args) { // 静态方法引用--通过类名调用 Consumer<String> consumerStatic = Method::StaticMethod; consumerStatic.accept("静态方法"); // 等价于 Consumer<String> consumerStatic2 = (x) -> Method.StaticMethod(x); consumerStatic2.accept("静态方法"); System.out.println("--------------------------"); //非静态方法引用--通过实例调用 Method method = new Method(); Consumer<String> consumerInstance = method::InstanceMethod; consumerInstance.accept("对象的实例方法"); // 等价于 Consumer<String> consumerInstance2 = (x) -> method.InstanceMethod(x); consumerInstance2.accept("对象的实例方法"); System.out.println("--------------------------"); //ClassName::instanceMethod 类的实例方法:把表达式的第一个参数当成 instanceMethod 的调用者,其他参数作为该方法的参数 BiPredicate<String, String> sbp = String::equals; System.out.println("类的实例方法 " + sbp.test("a", "A")); // 等效 BiPredicate<String, String> sbp2 = (x, y) -> x.equals(y); System.out.println("类的实例方法 " + sbp2.test("a", "A")); } } 复制代码
输出结果:
静态方法 静态方法 -------------------------- 对象的实例方法 对象的实例方法 -------------------------- 类的实例方法false 类的实例方法false 复制代码
public class ConstructMethodExample { public static void main(String [] args) { // 构造方法方法引用--无参数(可以使用方法引用) Supplier<Method> supplier = Method::new; System.out.println(supplier.get()); // 等价于 Supplier<Method> supplier2 = () -> new Method(); System.out.println(supplier2.get()); // 构造方法方法引用--有参数 Function<String, Method> uf = name -> new Method(name); Method method = uf.apply("一个优秀的废人"); System.out.println(method.toString()); } } 复制代码
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
public class VariableScopeTest { // 定义一个接口 public interface Converter<T1, T2> { void convert(int i); } public static void main(String [] args) { // 定义为 final 强制不能修改 final int num = 1; Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num)); // 输出结果为 3 s.convert(2); } } 复制代码
变量不声明为 final ,导致可以修改外部变量报错:
int num = 1; Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num)); s.convert(2); 复制代码
此外,在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量
String first = ""; // 同为 first 变量名,编译会出错 Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); 复制代码
Github源码地址: github.com/turoDog/rev…
如果看到这里,喜欢这篇文章的话,帮忙 " 转发 "或者点个" 在看 ",行吗?祝你们 2020 暴富。微信搜索「 一个优秀的废人 」,欢迎关注。
回复「 1024 」送你一套完整的 java、python、c++、go、前端、linux、算法、大数据、人工智能、小程序以及英语教程。
回复「 电子书 」送你 50+ 本 java 电子书。