Lambda in Java VS in C#
核心+变化
“凡是钱能解决的问题,就不是大问题。有很多问题是钱无法解决的,比如生老病死,比如不再相爱。”,看过《蜗居》的朋友一眼就能认出来。虽然这部电视剧讲的是chugui,但是毫无违和感,我当时都看出来真感情了。
海藻和宋思明虽然是因借钱开始的,但是后面的发展却远远超出了它。这里面钱是问题的核心,后面发生的事情都是围绕着核心的变化。
社会是一张庞大而复杂的网,有节点和连线组成。节点就是人,连线就是人际关系。这里面人是核心,人际关系是围绕着核心的变化。
那到底是核心影响变化呢,还是变化影响核心呢,还是二者皆而有之呢?不管怎样,请记住都是:核心+变化。
如果把核心看作是数据,那变化就是行为。如果把核心看作是字段,那变化就是方法。
哎呀,终于回到了编程上,差点没绕回来。
那些年,我的专业课老师
“好了,我们这本书已经讲完了”。老师,明明后面还有一半呢,怎么就讲完了呢?“后面那是指针,给你们讲了你们也不懂”。
都看看,这可是我计算机生涯的第一门语言,C语言呀,碰上这样的老师,“不仅侮辱了我们的人格,还侮辱了我们的智商”。所以我今天取得的“成就”都是我自己努力得来的。
哈哈,开个玩笑,不管怎样,还是要感谢老师带我“上道”的。其它老师的“名言”,后续再分享。
有句话怎么讲,“明知山有虎、偏向虎山行”,就是不信这个邪了,我倒要看看指针有多难。
事实证明,很多事情因人而异。好多人都说指针很难,但是我从一开始学习指针,直到现在,从来没觉得它难。
指针只不过是在变量的基础上又往前走了一步。变量对应的是数据本身,指针对应的是数据的地址。所以从指针获取数据需要执行一步解引用,即星号(*)操作符。
不过变量类型很多,所以指针的类型也很多,还有指向指针的指针,因此指针在写法上比较繁琐,不容易记住,但并不难理解。
C语言中的函数指针
人们对带“美”字的东西都比较感兴趣。如美景、美食、美酒、美元、美女等。当然也有讨厌的,如美国。
来个美食吧。鱼类绝对算一个,特别是深海鱼。无污染,低脂肪,高蛋白,维生素,不饱和脂肪酸,而且肉质嫩滑,味道鲜美。关键还符合我国的传统文化,年年有鱼啊。
岛国喜欢做成生鱼片或寿司。我国的花样就多了,红烧鱼,清蒸鱼,水煮鱼,麻辣鱼,剁椒鱼,酸菜鱼,蕃茄鱼,烤鱼等等。
那么问题来了,如何用C语言实现这么多的做鱼呢?
首先从生活入手,厨房 + 厨师 + 生鱼 = 熟鱼。假设每个厨师只会做一种鱼。用伪代码表示:
CookedFish kitchen(UncookedFish, CookWay) {
if (CookWay == "生鱼片") {
//厨师A做生鱼片
} else if (CookWay == "红烧鱼") {
//厨师B做红烧鱼
} else if (CookWay == "酸菜鱼") {
//厨师C做酸菜鱼
}...
}
这个方法看起来很臃肿,因为它把所有的做鱼方法都放进来了。
就像所有的厨师都在厨房里候着,然后进来一条生鱼,并告知要做成什么样的,对应的厨师起身去做鱼,剩余的厨师仍继续候着。
现实中是厨师都在自己的岗位上,而非厨房里,需要做鱼的时候,厨师和生鱼进厨房即可。
就像这样,厨房(厨师,生鱼)= 熟鱼,用伪代码表示:
CookedFish kitchen(Cooker*, UncookedFish) {
//厨师做鱼
Cooker(UncookedFish);
}
这里已经不需要CookWay了,因为一个厨师只会做一种鱼,从厨师就可以知道鱼的做法了。
这里面的核心是鱼,围绕核心的变化是厨师。如果鱼是字段,那厨师就是方法了。
第一种方法之所以繁琐,是因为我们只把字段传进来了,方法全部在“厨房”里。其实即使厨房里有再多的方法,一次也只能用一个。
为了简洁和更加符合实际,我们除了把字段传进来之外,也把方法传进来了。即,既把鱼传进来,也把厨师传经来。这样厨房里只有一个厨师在工作,就清爽多了。
我们可以看到,除了数据(鱼)可以当作参数传递外,行为(厨师)也可以当作参数传递。
C语言不是OO的,没有对象的概念,也没有方法的概念,只有函数,一段可执行的代码就是函数。
为了传递函数,需要用到函数指针,Cooker*就是函数指针,它指向的就是一个代码片段。
函数指针代表的是函数签名,即某一类函数。
如void (*fp)();这里的fp就是函数指针,它表示所有没有入参也没有返回值的这类函数。
如下:
//函数
void foo(){...};
void bar(){...};
//把函数赋给函数指针
fp = &foo;
fp = &bar;
//通过指针调用函数
(*fp)();
再看一个例子:
//函数指针
int (*fp2)(int, int);
//加
int add(int a, int b) {
return a + b;
}
//减
int sub(int a, int b) {
return a + b;
}
//乘
int mul(int a, int b) {
return a + b;
}
//除
int div(int a, int b) {
return a + b;
}
//函数
int op2(int (*fp2)(int, int), int a, int b) {
return fp2(a, b);
}
//调用,把函数当作参数传入
op2(add, 1, 2) == 3;
op2(sub, 2, 1) == 1;
op2(mul, 1, 2) == 2;
op2(div, 4, 2) == 2;
看不懂函数指针没关系,后面还有C#和Java代码。
C#中的Lambda表达式
初次接触lambda表达式就是在C#中,已是很多年前的事了。C#确实从C和C++中继承了很多特性。
在C#中应该也支持指针,但是不推荐使用。为了完成C语言中函数指针这种功能,C#提供了类型安全的“函数指针”,就是委托。
委托的关键字是delegate,它的用法如下:
public delegate void FB(int a);
委托表示的是方法签名,即一类方法。此处定义的委托类型是FB,它表示所有入参为一个整型且没有返回值的方法。
所以可以把方法赋值给委托,自然可以调用委托,如下:
//一个整型入参,无返回值
public void Foo(int a)
{
Console.WriteLine("Foo: " + a);
}
//一个整型入参,无返回值
public void Bar(int a)
{
Console.WriteLine("Bar: " + a);
}
//委托的赋值与调用
public void TestDelegate()
{
//把方法赋给委托
FB fb = this.Foo;
//调用委托
fb(1);
//把方法赋给委托
fb = this.Bar;
//调用委托
fb(2);
}
委托还可以用作方法的参数,此时就可以把一个方法当作参数传给其它方法。
//第一个参数FB就是委托
public void TestDelegate(FB fb, int a)
{
fb(a);
}
//下面是对这个方法的调用
Program p = new Program();
//p.Foo是一个方法,可以作为参数传入
p.TestDelegate(p.Foo, 1);
//p.Bar是一个方法,可以作为参数传入
p.TestDelegate(p.Bar, 2);
lambda表达式本质上就是一段可执行的代码,但是不同语言对它的实现是不同的,在C#中就实现为委托。
public void TestLambda()
{
//这就是lambda表达式的写法,它被赋值给了委托
FB fb = (int a) => Console.WriteLine("lambda 1: " + a);
fb(1);
//lambda表达式
fb = (a) =>
{
Console.WriteLine("lambda 2: " + a);
};
fb(2);
}
由于编译器会进行类型推断,所以可以省略参数类型。如果只有一个入参的话,可以省略那个小括号。如果只有一个语句的话,可以不用要大括号。
lambda表达式作为参数传递:
public void TestLambda(FB fb, int a)
{
fb(a);
}
//lambda表达式直接作为参数
p.TestLambda((int a) => Console.WriteLine("lambda 1: " + a), 1);
//lambda表达式直接作为参数
p.TestLambda((a) => { Console.WriteLine("lambda 2: " + a); }, 2);
C#中的匿名方法,如下:
public void TestAnonymousMethod()
{
//匿名方法可以赋值给委托,用关键字delegate替代方法名
FB fb = delegate(int a)
{
Console.WriteLine("anonymous method: " + a);
};
fb(1);
fb(2);
}
匿名方法作为参数传递:
public void TestAnonymousMethod(FB fb, int a)
{
fb(a);
}
//匿名方法直接作为方法参数
p.TestAnonymousMethod(delegate(int a) { Console.WriteLine("anonymous method: " + a); }, 1);
//匿名方法直接作为方法参数
p.TestAnonymousMethod(delegate(int a) { Console.WriteLine("anonymous method: " + a); }, 2);
总之,C#中的普通方法,lambda表达式,匿名方法,最后都可以赋值给委托进行传递和调用。
看不懂C#没关系,后面还有Java代码。
Java中的Lambda表达式
自从Java换了爸爸后,简直像坐上了火箭。各种其它语言的特性都陆续加进来了。从Java 8开始也可以使用lambda表达式了。
不过说来惭愧,我用的不多,因为它在我的编程生涯中已经不再“稀奇”了,因为之前已经在C#中体验过了。
Java也是从C和C++发展过来的,继承了一些特性,但抛弃的更多。类似函数指针的功能,就被优化没了。
因此在Java语言中,对于行为(可执行代码片段)的传递,无法做到方法级别,只能再往上走一步,做到接口级别或类级别。
也就是说,你想传递一个方法时,必须要传递一个类或对象作为方法的载体才行。这种使用方式其实一直都存在着的。
Java 8中引入一个新的概念叫做函数式接口。它规定这样的接口只能包含一个抽象方法,但可以包含其它带默认实现的任意方法。
函数式接口也可以像普通接口那样使用。只不过会有一些特定功能,毕竟是为了配合Java 8支持函数式编程而特意起的这个名字。
有一个函数式接口叫做Consumer<T>,它只有一个抽象方法是void accept(T t);这个方法接收一个入参但没有返回值。
所以这个函数式接口就代表了这样一类方法,即有一个入参但没有返回值的所有方法。所以函数式接口大致上也可以看作是一类方法的“方法签名”。
定义一个函数式接口变量,表示只有一个入参但没有返回值的这类方法。
//函数式接口变量
public Consumer<Integer> fb;
可以把方法赋值给函数式接口,然后进行调用,如下:
//只有一个入参且没有返回值的方法
public void foo(int a) {
System.out.println("foo: " + a);
}
//只有一个入参且没有返回值的方法
public void bar(int a) {
System.out.println("bar: " + a);
}
//函数式接口的赋值与调用
public void testFunctionalInterface() {
//把方法赋值给函数式接口变量
fb = this::foo;
//调用函数式接口,就是调用赋给它的方法
fb.accept(1);
//把方法赋给函数式接口变量
fb = this::bar;
//调用
fb.accept(2);
}
注意引用方法时使用了::操作符,这个应该是C++中的写法吧。
函数式接口作为方法参数:
public void testFunctionalInterface(Consumer<Integer> fb, int a) {
fb.accept(a);
}
Program p = new Program();
//把方法作为参数传给其它方法
p.testFunctionalInterface(p::foo, 1);
//把方法作为参数传给其它方法
p.testFunctionalInterface(p::bar, 2);
毫无悬念,lambda表达式可以赋给函数式接口变量。
public void testLambda() {
//lambda表达式的写法
fb = (Integer a) -> System.out.println("lambda 1: " + a);
fb.accept(1);
//lambda表达式
fb = (a) -> {
System.out.println("lambda 2: " + a);
};
fb.accept(2);
}
很显然,没有太多的惊喜。
lambda表达式作为参数传递:
public void testLambda(Consumer<Integer> fb, int a) {
fb.accept(a);
}
//lambda表达式直接作为参数传递
p.testLambda((Integer a) -> System.out.println("lambda 1: " + a), 1);
//lambda表达式直接作为参数传递
p.testLambda((a) -> { System.out.println("lambda 2: " + a); }, 2);
Java中的匿名类:
public void testAnonymousClass() {
//匿名类,函数式接口作为普通接口使用
fb = new Consumer<Integer>() {
@Override
public void accept(Integer a) {
System.out.println("anonymous class: " + a);
}
};
fb.accept(1);
fb.accept(2);
}
匿名类作为参数传递:
public void testAnonymousClass(Consumer<Integer> fb, int a) {
fb.accept(a);
}
//匿名类直接做参数
p.testAnonymousClass(new Consumer<Integer>() {
@Override
public void accept(Integer a) {
System.out.println("anonymous class: " + a);
}
}, 1);
//匿名类直接做参数
p.testAnonymousClass(new Consumer<Integer>() {
@Override
public void accept(Integer a) {
System.out.println("anonymous class: " + a);
}
}, 2);
总之,在Java中无论是普通方法,还是lambda表达式,或是匿名类,其实它们最后都变成了一个类,且都实现了这个函数式接口。
只不过匿名类是我们自己定义的。lambda表达式最后对应的类是编译器造出来的,所以它是“人造”的,但不是匿名的。
看不懂Java没关系,只要明白了原理就算是知道了精髓。
in C# VS in Java
为了“模拟”函数指针的功能,在C#中使用委托,在Java中使用函数式接口。
函数式接口其实就是一个接口,它看起来具有表示“方法签名”的功能,但是很丑陋。
委托看起来更像“方法签名”,也很优雅,但不要被迷惑,其实它也是一个类,没有什么高级东西。
对于引用方法的写法不同,C#中使用this.Foo,this.Bar,Java中使用this::foo,this::bar。
lambda表达式的写法略微不同,C#中是() => {},Java中是() -> {}。
关于匿名,C#中虽然叫匿名方法,其实最后还是一个类,而且是委托类型的。直接用delegate关键字定义匿名方法。
Java中就叫匿名类,名副其实,最后就是一个类。只不过需要先定义一个接口,然后直接使用接口实现匿名类。
我们发现C#和Java对lambda表达式的支持其实差不多。不同的是C#更优雅一些,Java更丑陋一些。
思想提升
一定要明白不仅普通数据可以当作参数传递,代码片段(就是逻辑)也可以当作参数传递。
这个思想就是核心,至于写法和用法就是围绕着核心的变化而已。
PS:最近几年很少看到语言之争啊,莫非大家都觉得PHP是世界上最好的语言啦
Lambda in Java VS in C#的更多相关文章
- 初探Lambda表达式/Java多核编程【1】从集合到流
从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...
- 初探Lambda表达式/Java多核编程【2】并行与组合行为
今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...
- 初探Lambda表达式/Java多核编程【3】Lambda语法与作用域
接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍L ...
- 初探Lambda表达式/Java多核编程【4】Lambda变量捕获
这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来.加上假期两个月左右都过去了书才看了1/7都 ...
- lambda -- Filter Java Stream to 1 and only 1 element
up vote10down votefavorite I am trying to use Java 8 Streams to find elements in a LinkedList. I wan ...
- 初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代
开篇 放假前从学校图书馆中借来一本书,Oracle官方的<精通Lambda表达式:Java多核编程>. 假期已过大半才想起来还没翻上几页,在此先推荐给大家. 此书内容及其简洁干练,如果你对 ...
- Java8函数之旅 (六) -- 使用lambda实现Java的尾递归
前言 本篇介绍的不是什么新知识,而是对前面讲解的一些知识的综合运用.众所周知,递归是解决复杂问题的一个很有效的方式,也是函数式语言的核心,在一些函数式语言中,是没有迭代与while这种概念的,因为此类 ...
- 快速了解Lambda表达式-Java
目录 lambda表达式 前言 简介 简单入门 用法 好处 总结 lambda表达式 前言 最近因为疫情,也不能正常返校什么的,希望大家都能好好的,希望武汉加油,中国加油,在家也看了很多视频,学了一点 ...
- Java 8特性探究(1):通往lambda之路与 lambda表达式10个示例
本文由 ImportNew 函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是只包含一个方法的接口.比如Java标准库中的ja ...
随机推荐
- JAVA_AesCBC例子
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.Secre ...
- echarts-for-react 从新渲染数据
<ReactEcharts option={option} notMerge={true} style={{height: '600px', width: '100%'}} className ...
- C# yield return 和 yield break
yield关键字用于遍历循环中,yield return用于返回IEnumerable<T>,yield break用于终止循环遍历. 以下对比了使用yield return与不使用yie ...
- ElasticSearch(6.2.2)的java API官方文档的总结 (三)
一 : SearchRequest用于任何与搜索文档,聚合和建议有关的操作,并且还提供了对生成的文档进行高亮显示的方法. 在最基本的形式中,我们可以向请求添加一个查询: 1:添加一个Search ...
- 实验三:分别用for,while;do-while循坏语句以及递归的方法计算n!,并输出算式。
源代码: package jiecheng;import java.util.Scanner;public class JieCheng {public static void main(String ...
- PIL成就你的自信之路
1.强大的PIL库 在Python中,有一个优秀的图像处理框架,就是PIL库,本博文会分模块,介绍PIL库中的各种方法,并列举相关例子. 学习总结:PIL库可以让我们得到更多的需求,以此来满足我们的需 ...
- Kotlin基础
1.函数也是对象,可以作为参数和返回值 2.使用驼峰命名,尽量避免下划线 3.public函数应当有说明文档 4.lambda中花括号内前后都应该有空格 5.空值安全检查 var s: String ...
- vue中实现动态切换不同的值
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- [Swift]LeetCode287. 寻找重复数 | Find the Duplicate Number
Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), pro ...
- [Swift]LeetCode343. 整数拆分 | Integer Break
Given a positive integer n, break it into the sum of at least two positive integers and maximize the ...