Java 基础(二)| 使用 lambad 表达式的正确姿势
前言
为跳槽面试做准备,今天开始进入 Java 基础的复习。希望基础不好的同学看完这篇文章,能掌握 lambda 表达式,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆。
一、什么是 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 接口。那这里采用的就是后者,后者是一个函数式接口。
1.1 函数式接口
@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 表达式,那为什么要使用它呢?注意到使用 lambda 创建线程的时候,我们并不关心接口名,方法名,参数名。我们只关注他的参数类型,参数个数,返回值。所以原因就是简化代码,提高可读性。
三、如何使用 lambda表达式
3.1 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脱单"));
}
}
3.3 方法引用
看一个简单的方法引用例子:
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
3.4 构造器引用
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());
}
}
3.5 变量作用域
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());
四、十大 lambda 表达式示例
示例:https://blog.csdn.net/qq_37176126/article/details/81273195
Github 源码地址(觉得不错,给个 star 呗):https://github.com/turoDog/review_java/tree/master/src/main/java/com/nasus/lambda
五、源码地址
- github 源码地址:https://github.com/turoDog/review_java
如果看到这里,喜欢这篇文章的话,帮忙 " 转发 "或者点个" 在看 ",行吗?祝你们 2020 暴富。微信搜索「一个优秀的废人」,欢迎关注。
回复「1024」送你一套完整的 java、python、c++、go、前端、linux、算法、大数据、人工智能、小程序以及英语教程。
回复「电子书」送你 50+ 本 java 电子书。
Java 基础(二)| 使用 lambad 表达式的正确姿势的更多相关文章
- Java面试题总结之Java基础(二)
Java面试题总结之Java基础(二) 1.写clone()方法时,通常都有一行代码,是什么? 答:super.clone(),他负责产生正确大小的空间,并逐位复制. 2.GC 是什么? 为什么要有G ...
- Java基础教程:Lambda表达式
Java基础教程:Lambda表达式 本文部分内容引用自OneAPM:http://blog.oneapm.com/apm-tech/226.html 引入Lambda Java 是一流的面向对象语言 ...
- Java入土--Java基础(二)
Java基础(二) 接上一讲,我们接着来聊聊Java的一些基础知识,下一讲就会进行流程的控制. 类型转换 首先呢,是类型的转换,接上一个内容的数据类型,类型转换就是数据类型更进一步的应用. 由于Jav ...
- java基础(二)-----java的三大特性之继承
在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...
- Java基础教程(23)--lambda表达式
一.初识lambda表达式 1.定义 lambda表达式是一个可传递的代码块,或者更确切地说,可以把lambda表达式理解为简洁地表示可传递的匿名方法的一种方式.它没有名称,但它有参数列表.函数主 ...
- Java基础(二) 基本类型数据类型、包装类及自动拆装箱
我们知道基本数据类型包括byte, short, int, long, float, double, char, boolean,对应的包装类分别是Byte, Short, Integer, Long ...
- [ 转载 ] Java基础二
前言 关于赢在面试的Java题系列基本收集整理完成了,所有题目都是经过精心挑选的,很基础又考验求职者的基本功,应该说被面试到的几率很大.这里整理挑选出来供大家面试前拿来看一看,所有题目整理自网络,有一 ...
- Java基础二(变量、运算符)
1.变量2.运算符 ###01变量概述 * A: 什么是变量? * a: 变量是一个内存中的小盒子(小容器),容器是什么?生活中也有很多容器,例如水杯是容器,用来装载水:你家里的大衣柜是容器,用来装载 ...
- JAVA基础学习day26--正则表达式
一.正则表达式 1.1.概述 符合一规则的表达式:用于专门操作字符串. 正则表达式则必须依靠Pattern类与Matcher类,这两个类都在java.util.regex包中定义.Pattern类的主 ...
随机推荐
- redis cluster和hash slot
redis cluster介绍 从redis3.0.0开始,官方支持了redis cluster的集群模式,结束了redis没有集群的时代. redis cluster 支撑 N 个 redis ma ...
- Educational Codeforces Round 54 (Rated for Div. 2) D Edge Deletion (SPFA + bfs)
题目大意:给定你一个包含n个点m条边的无向图,现在最多在图中保留k条边,问怎么删除多的边,使得图中良好的节点数最多,求出保留在图中的边的数量和编号. 良好的节点定义为:删除某条边后该点到点1的最短距离 ...
- linux 安装一个中断处理
如果你想实际地"看到"产生的中断, 向硬件设备写不足够; 一个软件处理必须在系统中配 置. 如果 Linux 内核还没有被告知来期待你的中断, 它简单地确认并忽略它. 中断线是一个 ...
- 修改github上的项目语言类型
当在github上上传一个项目时,可能会出现一个问题就是项目代码类型是自动生成的,可能与我们实际项目代码种类不匹配,此时就需要修改项目语言类型了. 由于无法直接更改,所以用到此方法: 在你的项目根目录 ...
- 2018.11.23 浪在ACM 集训队第六次测试赛
2018.11.23 浪在ACM 集训队第六次测试赛 整理人:刘文胜 div 2: A: Jam的计数法 参考博客:[1] 万众 B:数列 参考博客: [1] C:摆花 参考博客: [1] D:文化之 ...
- CodeChef Ada Pawns
最小割 留下最多的点 形如左上或者右上没有点的点一定会留下 对于斜着的关系的两个点不能共存 黑白行染色! 白行的点称为 白点,黑点类似 反着连关系 对于一定会留下的,S到白点,黑点到T,都连inf 不 ...
- linux安装python3.*,更换Python2.*
下载并解压:Python-3.5.7.tgz [root@AH-aQYWTYSJZX01 python3]# ll total 20268 -rw-r----- 1 temp01 temp01 207 ...
- video实现有声音自动播放
video实现自动播放有声音 需求:老板见人家可以的,我们的也要可以!!! 前端:自动播放,简单... 要实现:鼠标移入视频播放同时有声音,移出让你暂停,,,,, 问题集合 1- 自动播放实现没有声音 ...
- windows下使用cmake+mingw配置makefile
前面一节说了cmake简易使用,但是实际开发中项目文件非常多,使用哪种简易方式会导致代码十分混乱,因此本文介绍一种cmake管理大型项目的demo流程. 具体步骤如下: 1.创建相关的项目目录 cmd ...
- Math类入门学习
Math类 Math类包含用于执行基本的数字运算等基本指数.对数.平方根法.三角函数. import java.lang.*; public class TestMath { public stati ...