别翻了,Lambda 表达式入门,看这篇就够了
Posted 沉默王二
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了别翻了,Lambda 表达式入门,看这篇就够了相关的知识,希望对你有一定的参考价值。
今天分享的主题是《Lambda 表达式入门》,这也是之前一些读者留言强烈要求我写一写的,不好意思,让你们久等了,现在来满足你们,为时不晚吧?
01、初识 Lambda
Lambda 表达式描述了一个代码块(或者叫匿名方法),可以将其作为参数传递给构造方法或者普通方法以便后续执行。考虑下面这段代码:
() -> System.out.println("沉默王二")
来从左到右解释一下,()
为 Lambda 表达式的参数列表(本例中没有参数),->
标识这串代码为 Lambda 表达式(也就是说,看到 ->
就知道这是 Lambda),System.out.println("沉默王二")
为要执行的代码,即将“沉默王二”打印到标准输出流。
有点 Java 基础的同学应该不会对 Runnable 接口感到陌生,这是多线程的一个基础接口,它的定义如下:
@FunctionalInterface
public interface Runnable
public abstract void run();
Runnable 接口非常简单,仅有一个抽象方法 run()
;细心的同学会发现一个陌生的注解 @FunctionalInterface
,这个注解是什么意思呢?
我看了它的源码,里面有这样一段注释:
Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.
大致的意思就是说,通过 @FunctionalInterface
标记的接口可以通过 Lambda 表达式创建实例。具体怎么表现呢?
原来我们创建一个线程并启动它是这样的:
public class LamadaTest
public static void main(String[] args)
new Thread(new Runnable()
@Override
public void run()
System.out.println("沉默王二");
).start();
通过 Lambda 表达式呢?只需要下面这样:
public class LamadaTest
public static void main(String[] args)
new Thread(() -> System.out.println("沉默王二")).start();
是不是很妙!比起匿名内部类,Lambda 表达式不仅易于理解,更大大简化了必须编写的代码数量。
02、Lambda 语法
每个 Lambda 表达式都遵循以下法则:
( parameter-list ) -> expression-or-statements
()
中的 parameter-list
是以逗号分隔的参数。你可以指定参数的类型,也可以不指定(编译器会根据上下文进行推断)。Java 11 后,还可以使用 var
关键字作为参数类型,有点 javascript 的味道。
->
相当于 Lambda 的标识符,就好像见到圣旨就见到了皇上。
中的
expression-or-statements
为 Lambda 的主体,可以是一行语句,也可以多行。
可以通过 Lambda 表达式干很多事情,比如说
1)为变量赋值,示例如下:
Runnable r = () -> System.out.println("沉默王二"); ;
r.run();
2)作为 return 结果,示例如下:
static FileFilter getFilter(String ext)
return (pathname) -> pathname.toString().endsWith(ext);
3)作为数组元素,示例如下:
final PathMatcher matchers[] =
(path) -> path.toString().endsWith("txt"),
(path) -> path.toString().endsWith("java")
;
4)作为普通方法或者构造方法的参数,示例如下:
new Thread(() -> System.out.println("沉默王二")).start();
需要注意 Lambda 表达式的作用域范围。
public static void main(String[] args)
int limit = 10;
Runnable r = () ->
int limit = 5;
for (int i = 0; i < limit; i++)
System.out.println(i);
;
上面这段代码在编译的时候会提示错误:变量 limit 已经定义过了。
和匿名内部类一样,不要在 Lambda 表达式主体内对方法内的局部变量进行修改,否则编译也不会通过:Lambda 表达式中使用的变量必须是 final 的。
这个问题发生的原因是因为 Java 规范中是这样规定的:
Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression
must either be declared final or be effectively final (§4.12.4),
or a compile-time error occurs where the use is attempted.
大致的意思就是说,Lambda 表达式中要用到的,但又未在 Lambda 表达式中声明的变量,必须声明为 final 或者是 effectively final,否则就会出现编译错误。
关于 final 和 effectively final 的区别,可能有些小伙伴不太清楚,这里多说两句。
final int a;
a = 1;
// a = 2;
// 由于 a 是 final 的,所以不能被重新赋值
int b;
b = 1;
// b 此后再未更改
// b 就是 effectively final
int c;
c = 1;
// c 先被赋值为 1,随后又被重新赋值为 2
c = 2;
// c 就不是 effectively final
明白了 final 和 effectively final 的区别后,我们了解到,如果把 limit 定义为 final,那就无法在 Lambda 表达式中修改变量的值。那有什么好的解决办法呢?既能让编译器不发出警告,又能修改变量的值。
思前想后,试来试去,我终于找到了 3 个可行的解决方案:
1)把 limit 变量声明为 static。
2)把 limit 变量声明为 AtomicInteger。
3)使用数组。
下面我们来详细地一一介绍下。
01)把 limit 变量声明为 static
要想把 limit 变量声明为 static,就必须将 limit 变量放在 main()
方法外部,因为 main()
方法本身是 static 的。完整的代码示例如下所示。
public class ModifyVariable2StaticInsideLambda
static int limit = 10;
public static void main(String[] args)
Runnable r = () ->
limit = 5;
for (int i = 0; i < limit; i++)
System.out.println(i);
;
new Thread(r).start();
来看一下程序输出的结果:
0
1
2
3
4
OK,该方案是可行的。
02)把 limit 变量声明为 AtomicInteger
AtomicInteger 可以确保 int 值的修改是原子性的,可以使用 set()
方法设置一个新的 int 值,get()
方法获取当前的 int 值。
public class ModifyVariable2AtomicInsideLambda
public static void main(String[] args)
final AtomicInteger limit = new AtomicInteger(10);
Runnable r = () ->
limit.set(5);
for (int i = 0; i < limit.get(); i++)
System.out.println(i);
;
new Thread(r).start();
来看一下程序输出的结果:
0
1
2
3
4
OK,该方案也是可行的。
03)使用数组
使用数组的方式略带一些欺骗的性质,在声明数组的时候设置为 final,但更改 int 的值时却修改的是数组的一个元素。
public class ModifyVariable2ArrayInsideLambda
public static void main(String[] args)
final int [] limits = 10;
Runnable r = () ->
limits[0] = 5;
for (int i = 0; i < limits[0]; i++)
System.out.println(i);
;
new Thread(r).start();
来看一下程序输出的结果:
0
1
2
3
4
OK,该方案也是可行的。
03、Lambda 和 this 关键字
Lambda 表达式并不会引入新的作用域,这一点和匿名内部类是不同的。也就是说,Lambda 表达式主体内使用的 this 关键字和其所在的类实例相同。
来看下面这个示例。
public class LamadaTest
public static void main(String[] args)
new LamadaTest().work();
public void work()
System.out.printf("this = %s%n", this);
Runnable r = new Runnable()
@Override
public void run()
System.out.printf("this = %s%n", this);
;
new Thread(r).start();
new Thread(() -> System.out.printf("this = %s%n", this)).start();
Tips:%s
代表当前位置输出字符串,%n
代表换行符,也可以使用 \\n
代替,但 %n
是跨平台的。
work()
方法中的代码可以分为 3 个部分:
1)单独的 this 关键字
System.out.printf("this = %s%n", this);
其中 this 为 main()
方法中通过 new 关键字创建的 LamadaTest 对象——new LamadaTest()
。
2)匿名内部类中的 this 关键字
Runnable r = new Runnable()
@Override
public void run()
System.out.printf("this = %s%n", this);
;
其中 this 为 work()
方法中通过 new 关键字创建的 Runnable 对象——new Runnable()...
。
3)Lambda 表达式中的 this 关键字
其中 this 关键字和 1)中的相同。
我们来看一下程序的输出结果:
this = com.cmower.java_demo.journal.LamadaTest@3feba861
this = com.cmower.java_demo.journal.LamadaTest$1@64f033cb
this = com.cmower.java_demo.journal.LamadaTest@3feba861
符合我们分析的预期。
04、最后
尽管 Lambda 表达式在简化 Java 编程方面做了很多令人惊讶的努力,但在某些情况下,不当的使用仍然会导致不必要的混乱,大家伙慎用。
好了,我亲爱的读者朋友们,以上就是本文的全部内容了。能在疫情期间坚持看技术文,二哥必须要伸出大拇指为你点个赞👍。原创不易,如果觉得有点用的话,请不要吝啬你手中点赞的权力——因为这将是我写作的最强动力。
以上是关于别翻了,Lambda 表达式入门,看这篇就够了的主要内容,如果未能解决你的问题,请参考以下文章