回炉重造图片加说说(《回炉重造》——Lambda表达式)
前言
Lambda 表达式(Lambda Expression) ,相信大家对 Lambda 肯定是很熟悉的 ,毕竟我们数学上经常用到它 ,即 λ 。不过 ,感觉数学中的 Lambda 和编程语言中的 Lambda 表达式没啥关系 ,要说有关系就是都有 Lambda 这个词 ,噢!当然还有一个关系就是 Lambda 演算 。
λ 演算(英语:lambda calculus ,λ-calculus)是一套从数学逻辑中发展 ,以变量绑定和替换的规则 ,来研究函数如何抽象化定义 、函数如何被应用以及递归的形式系统 。它由数学家阿隆佐·邱奇在20世纪30年代首次发表 。lambda演算作为一种广泛用途的计算模型 ,可以清晰地定义什么是一个可计算函数 ,而任何可计算函数都能以这种形式表达和求值 ,它能模拟单一磁带图灵机的计算过程 。
回到编程语言这方面,其实不只是 Java 引入了这个 Lambda 表达式 ,其他编程语言也有 ,比如 C++ 、Javascript 、Python 等等 。当然,本篇文章回顾的是 Java 中的 Lambda 表达式 。
作为一个初学者 ,下面对于 Lambda 的理解肯定不够严谨 ,甚至可能包含错误 ,望观众老爷们能在评论区指出!
为什么要学这个 Lambda 表达式?
Java 8 的新特性 ,简化代码的编写 。 工作中会用到 ,防止看不懂别人写的代码 。 大家都学我也学 。什么是 Lambda 表达式?
Lambda 表达式是一个匿名函数 ,换句话说 ,你有匿名函数 ,那么它这个函数就是所谓的 Lambda 表达式 。
所谓匿名函数 ,顾名思义 ,就是没有函数名的函数 。
那么肯定有小伙伴会说 ,没有函数名 ,那我怎么调用这个函数啊?
是的,这个问题问得很好 ,先保持这个疑问!在回答这个问题之前 ,我先来说说另一个概念——「函数式编程」。
什么是函数式编程?
函数式编程是一种编程范式,除此之外 ,还有声明式编程 、命令式编程 ,也都是编程范式 。
好吧 ,这里又扯出一个新的专业名词——「编程范式(Programming Paradigm)」 。范式?相信正在阅读的你 ,在学习数据库知识的时候 ,肯定学过第一范式 、第二范式 、第三范式 ,那现在又有一个编程范式 ,这是什么鬼?
百度百科是这样说的:
编程范型 、编程范式或程序设计法(英语:Programming paradigm) ,(范即模范 、典范之意 ,范式即模式 、方法) ,是一类典型的编程风格 ,是指从事软件工程的一类典型的风格(可以对照方法学)。如:函数式编程 、程序编程 、面向对象编程 、指令式编程等等为不同的编程范型 。
是不是太官方了 ,没关系,简单理解 ,我认为知道函数式编程是一种写代码时的风格就OK了 。
我们需要注意的是 ,函数式编程中的「函数」二字,是数学上的函数 ,并不是我们现在习惯理解的函数 ,也就是说 ,这是纯纯数学概念上的函数 ,即自变量的映射 ,比如 $y = f(x)$ ,自变量 $x$ ,通过函数 $f$ 映射成 $y$ 。
函数式编程和 Lambda 表达式有什么关系?
可以说 ,函数式编程允许使用一种表达式来表示一个函数 ,这种表达式就是 Lambda 表达式 。
在 Java 中 ,函数式编程是通过一个接口规范来实现的 ,接口具有这种特点:
该接口有且只有一个抽象方法 该接口使用 @FunctionalInterface 注解进行标识具有这个特点的接口称为「函数式接口」 。
现在 ,回到最开始说的,「没有函数名 ,那我怎么调用这个函数啊?」 ,这就是函数式接口的用途了,接口中只有一个抽象方法 ,不用指定方法名称 ,就能够用 Lambda 表达式来调用这个函数(方法)了 ,不需要知道函数名就能够实现调用 。好比想在某个房间(接口)找个人(方法)来做事 ,我这个房间只有一个人 ,那么除了这个人 ,没有其他人可以来做事了 ,就不需要指定那谁谁谁过来帮忙 ,而是直接喊:就决定是你了!(这个比喻可能也不是很恰当 ,当大概意思是这样哈哈哈)
函数式接口
Comparator
我们可以看看 Comparator 接口 ,它有 @FunctionalInterface 注解 ,那么可以肯定它是一个函数式接口 。
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); default Comparator<T> reversed() { return Collections.reverseOrder(this); } default Comparator<T> thenComparing(Comparator<? super T> other) { Objects.requireNonNull(other); return (Comparator<T> & Serializable) (c1, c2) -> { int res = compare(c1, c2); return (res != 0) ? res : other.compare(c1, c2); }; } ... }有小伙伴应该要说了 ,这个接口这么多方法,为什么还能是函数式接口?
注意了啊 ,我们可以看到一个好像是抽象的 equals 方法 ,但是,因为 equals 是 Object 中的方法 ,这种对Object 类的方法的重新声明是会让方法变成一个具体的方法 。所以 ,不要误会了 ,这里的抽象方法就只有 compare 方法 。
那可能有小伙伴要说了 ,接口中还能有具体的方法?
是的 ,没错 ,在 Java 8 中 ,接口中可以写具体的方法了 。比如上面的 reversed 和 thenComparing 方法 ,都是具体的方法 。
常见的函数式接口
java.lang.Runnable @FunctionalInterface public interface Runnable { public abstract void run(); } java.util.concurrent.Callable @FunctionalInterface public interface Callable<V> { V call() throws Exception; } java.lang.reflect.InvocationHandler @FunctionalInterface public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }如何使用 Lambda 表达式?
在 Java 8 之前 ,我们使用 Collections 的需要比较器的 sort 方法 ,是这样的 。
等等 ,忘了有比较器参数的 sort 方法了?没关系 ,代码给你贴上:
public static <T> void sort(List<T> list, Comparator<? super T> c) { list.sort(c); }最开始的写法是这样的,由于 Comparator 是一个接口 ,不能直接实例化 ,所以需要一个类来实现这个接口作为真正的比较器类,然后将这个 Comparator 实例对象作为 sort 方法第二个参数传入 ,实现排序 ,如下:
public class KeyComparator implements Comparator<Integer> { @Override public int compare(Integer v1, Integer v2) { return v1 - v2; } } List<Integer> keys = Arrays.asList(9, 3, 5, 10, 2); Collections.sort(keys, new KeyComparator()); System.out.println(keys); // [2, 3, 5, 9, 10]后来 ,这种写法比较麻烦 ,于是用匿名内部类改写这种写法 ,我们不需要自己去编写一个类来实现这个接口了 ,直接用匿名内部类。就是这种写法:
List<Integer> keys = Arrays.asList(9, 3, 5, 10, 2); Collections.sort(keys, new Comparator<Integer>() { @Override public int compare(Integer v1, Integer v2) { return v1 - v2; } }); System.out.println(keys); // [2, 3, 5, 9, 10]现在 ,匿名内部类比起 Lambda 表达式 ,也是麻烦 ,我们用 Lambda 进行改写:
List<Integer> keys = Arrays.asList(9, 3, 5, 10, 2); Collections.sort(keys, (Integer v1, Integer v2) -> {return v1 - v2;}); System.out.println(keys); // [2, 3, 5, 9, 10]是吧 ,(Integer v1, Integer v2) -> {return v1 - v2;} 的写法 ,没有函数名 ,也能进行调用 。
实际上,这样还不是最简的 ,最简的是这样:(v1, v2) -> v1 - v2
是不是很好奇啥时候能这样写?现在就告诉你!
基本语法
(参数类型 参数名) -> { 方法体 }
基本上 ,这样写,是不会有问题的 。下面说说何时能写得更加简单。
为了便于阅读 ,下面的「方法」指的是函数式接口中的抽象方法
方法没有参数 ,那么可以直接写小括号 ,然后箭头 ,再写中括号 ,最后写方法体 ,即 () -> { 方法体 } 方法有多个参数 ,那么多个参数就用逗号分开 ,同时参数类型是可以省略的 ,即 (v1, v2, ...) -> {方法体} 方法只有一个参数 ,那么小括号可以去掉 ,直接写参数名 ,然后箭头,再中括号和方法体 ,即 v -> {方法体} 方法体只有一条语句 ,无论是否有返回值,都可以省略大括号、return 关键字及语句分号 。情况一:方法无参数 、无返回值
常见的就是 Runnable 接口了 。
@FunctionalInterface public interface Runnable { public abstract void run(); } 未使用 Lambda(使用匿名内部类): new Thread(new Runnable() { @Override void run() { System.out.println("线程开始跑了"); } }).start(); 使用 Lambda: // 写法一 new Thread(() -> { System.out.println("线程开始跑了") }).start(); // 写法二 ,一条语句 ,那么省略大括号 、return 关键字及语句分号 new Thread(() -> System.out.println("线程开始跑了")).start();情况二:方法无参数 ,有返回值
例子:Callable 接口
@FunctionalInterface public interface Callable<V> { V call() throws Exception; } 未使用 Lambda(使用匿名内部类): FutureTask<String> stringFutureTask = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { return "这里是返回值"; } }); stringFutureTask.run(); System.out.println(stringFutureTask.get()); 使用 Lambda: // 一条语句 ,省略大括号、return 关键字及语句分号 FutureTask<String> stringFutureTask = new FutureTask<>(() -> "这里是返回值"); stringFutureTask.run(); System.out.println(stringFutureTask.get());情况三:方法一个参数 、有返回值
我随便找了 JDK 中的一个接口 ,如下:
@FunctionalInterface interface Recognizer { boolean recognize(int c); } 未使用 Lambda(使用匿名内部类): private final Recognizer A = new Recognizer() { @Override public boolean recognize(int c) { return c == a || c == A; } } 使用 Lambda: private final Recognizer A = (c) -> c == a || c == A; // 一个参数 ,可省略小括号 private final Recognizer A = c -> c == a || c == A;情况四:方法多个参数 、有返回值
直接举 Comparator 这个例子 。
未使用 Lambda(使用匿名内部类): List<Integer> keys = Arrays.asList(9, 3, 5, 10, 2); keys.sort(new Comparator()<Integer> { @Override public int compare(Integer v1, Integer v2) { return v1 - v2; } }); 使用 Lambda: List<Integer> keys = Arrays.asList(9, 3, 5, 10, 2); // 多个参数以逗号分开 ,可省略类型 ,一条语句 ,省略大括号 、return 关键字及语句分号 keys.sort((v1, v2) -> v1 - v2); System.out.println(keys);总结
到这里 ,相信大家对于 Lambda 表达式有了一个基本的认识 。总的来说:
必须是函数式接口才能使用 Lambda 表达式语法:(参数类型 参数名称) ‐> { 方法体 }
若方法的参数列表: 没有参数 ,则可直接用 () ; 有一个参数 ,可以省略 (),直接写参数; 有多个参数 ,则()不可省略 () 内的参数类型可以省略 若方法体: 只有一条语句 ,无论是否有返回值,都可以省略大括号 、return 关键字及语句分号 。 处理逻辑过于臃肿复杂 ,还是使用具体子类改写较好 ,保证可读性 。最后的最后
由本人水平所限 ,难免有错误以及不足之处 , 屏幕前的靓仔靓女们 如有发现 ,恳请指出!
最后 ,谢谢你看到这里 ,谢谢你认真对待我的努力 ,希望这篇博客对你有所帮助!
你轻轻地点了个赞 ,那将在我的心里世界增添一颗明亮而耀眼的星!
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!