- 我是一个线程(修订版)
- 我是一个 Java class
- Javascript:一个屌丝的逆袭
- Java : 一个帝国的诞生
- JSP 一个装配工的没落
- TCP/IP 之 大明王朝邮差
- TCP/IP 之大明内阁
- TCP/IP 之蓟辽督师
- CPU 阿甘
- CPU 阿甘之烦恼
- CPU 阿甘:函数调用的秘密
- 我是一个网卡
- 我是一个路由器
- 我是一个进程
- 我是一块硬盘(上)
- 我是一块硬盘(下)
- 我是一个键盘
- 张大胖的 socket
- 张大胖学递归
- 学习面向对象的令狐冲
- 张大胖学数据库
- 数据库村的旺财和小强
- 小李的数据库之旅(上)
- 小李的数据库之旅(下)
- 漫画:什么是机器学习?
- 那些烦人的同步和互斥问题
- IE 为什么把火狐和 Chrome 给打伤了?
- 对浏览器村的第二次采访
- 节约标兵 IE 的自述
- EMail 诞生记
- Email 诞生记(下)
- Http 历险记(上)
- Http 历险记(下)-- Struts 的秘密
- 动物王国的面向对象
- 冯·诺伊曼计算机的诞生
- Http Server : 一个差生的逆袭
- 张大胖的加法器
- 从 1 加到 100:一道简单的数学题挑战下你的大脑
- 编程语言
- Javascript:一个屌丝的逆袭
- 计算机语言之战
- 我和编程语言的爱恨情仇(上)
- 我和编程语言的爱恨情仇(下)
- Android 为什么选择了 Java
- iOS 为什么选择了 Object-C?
- Basic : 一个老兵的自述
- Node.js : 我只需要一个店小二
- 命令式编程 vs 声明式编程
- 编译还是解释?
- 程序人生
- “架构师"小赵
- 师兄说
- 师姐说
- 小王的架构师之路
- 小李的版本管理系统
- 小超穿越记
- 小李的 Build 之路(上)
- 小李的 Build 之路(下)
- 张大胖改 Bug
- 我的编程之路--大学趣事
- 码农小王的一天
- 小李在外企
- 张大胖的需求估算
- 从厨师到码农
- 聊一聊那些神一样的程序员们(上)
- 聊一聊那些神一样的程序员们(中)
- 聊一聊那些神一样的程序员们(下)
- 谁是互联网之父?
- 一个价值百万的创业教训
- 让自己与众不同 - 提升工作的价值
- 看看你的“易燃性”
- 从无聊的工作中寻找价值
- 什么样的学生适合报考计算机?
- 谈谈程序员的职业方向(上)
- 谈谈程序员的职业方向(中)
- 谈谈程序员的职业方向(下)
- 谈谈培训班的作用
- 码农需要知道的“潜规则”
- 学习编程的加速度
- 码农在工作中的必备能力
- 码农和英语
- 老司机经验
- 假如时光能够倒流, 我会这么学习 Java
- 假如我是计算机系老师
- 学会编程, 而不是学会 Java
- 从增删改查中突围
- 抽象:程序员必备的能力
- 懒就一个字
- 编程的自学方法
- 小王买房记
- 从一道面试题谈谈一线码农应该具备的基本素质
- 想写框架的看过来
- 苹果手机变砖头以后
- 如何快速的学习一门技术?
- 唯一不变的是变化: 谈谈微信应用号
- 什么是企业应用?
- 勿以浮沙筑高台
- 为什么敏捷开发难于成功?
- localhost vs 127.0.0.1
- GitHub/Stackoverflow 找工作时有什么用?
- 动词 or 名词 :这是一个问题
- 如何选择入行语言
- 有时候,沉默是金
- 零 Bug 的代码是怎么炼成的?
- 浮点数为什么不精确?
- 文章错误大全
- Open Source--不要为了开源而开源
- 一不留神,代码就腐化了
- 先做个“键盘侠”, 再来写程序
- 不加断点调试的程序员是好程序员
- 码农必备技能:烂代码的处理之道(上)
- 码农必备技能:烂代码的处理之道(下)
- 学习数据结构有用吗?
- 从现在开始,丰富你的简历
- 那些永不过时的书,你看过几本吗?
- 学好编程必备的一个品质你知道吗?
- 你最爱的 Java
- 搞懂了这几点,你就学会了 Web 编程
- Spring 的本质系列(1) -- 依赖注入
- Spring 本质系列(2)-AOP
- 三层架构和 MVC 那点事儿
- Java 帝国之拨云见日识回调
- 小张的 Duck Typing
- JDBC 的诞生
- JDBC 后传
- 一个不安分的 JDBC 驱动
- Java 帝国之 Java bean (上)
- Java 帝国之 Java bean(下)
- Java 帝国之函数式编程
- Java 帝国之函数式编程(下)
- 关于 Java 初学者需要知道的 10 件事
- JUnit 你不知道的那些事儿
- 圣诞礼物:Java EE 的历史
- Java EE 读书指南
- 给小白的 Java EE 指南
- 给小白的 Java EE 指南(2)
- 给小白的 Java EE 生存指南(3) : XML
- 给小白的 Java EE 生存指南(4) : 一只叫 Tom 的猫
- 给小白的 Java EE 指南(5) : AJAX
- 给小白的 Java EE 生存指南(6) :Java 反射
- 闲聊
- "饿了么"初体验
- 来自大脑的控诉
- 一个高中生是怎么玩自媒体的?
- 尝试 分答
- 到底应不应该上培训班?
- 自学编程中遇到问题怎么办?
- 据说 99%的初级程序员看完后都不迷茫了
- 一行代码引发的“血案”
- 对一个死锁问题的思考
- 通过外包进入名企
- 请开往十年前的今天
- 为什么自学中最好有个师傅指导一下?
- 这个网站值得你花时间投入
- 为什么你无法坚持自学编程?
Java 帝国之函数式编程
前言:最近看了点 Java 函数式编程, 写了个小故事和大家分享下心得,不妥之处,欢迎拍砖 1. 游行示威 Java 帝国成立 20 多年了, 20 多年来帝国给每个程序员都提供了趁手的工具箱, 里边装着面向对象, 自动内存管理,泛型 , 类型安全等强大的工具, 大家用这些工具开发了无数的框架和系统,都夸 Java 大法好。
可以最近出现了一点新情况, 一群函数式编程的狂热份子不停的到总部来游行, 要求帝国向 LISP 学习, 支持函数式编程, 他们的理由也很充分:
“很多语言都支持函数式编程了,比如 Ruby , Python...
“人家微软的 C#很早就支持了...”
“同样是运行在 Java 虚拟机上的语言,那些杂牌军 Scala , Clojure 都有函数式编程 , 我们的精锐,中央军 Java 怎么就不支持呢?”
这样的声音虽然不是主流,但越来越多也让我不胜其烦, 我把我最得力的助手小码哥叫来商量对策:
“小码, 外边那一群狂热分子叫嚣的函数式编程到底是什么东西?”
“唉, 老大, 其实我也不知道函数式编程的确切定义, 我只知道 LISP 是函数式编程的鼻祖,并且像 LISP 这样的语言学习起来很难,和我们的面向对象完全是两个世界, 但奇怪的是很多掌握了 LISP 的人都无可救药的爱上了她。”
“那它肯定有什么过人之处 !” 我说
“像 C,C++,Java, 用他们编程是一种命令式的风格, 告诉计算机如何如何做, 而用 LISP 编程是一种声明式的风格,就像 SQL 一样, 只是描述我要什么, 不去具体的描述怎么去做”
“听起来很不错啊 ” 我觉得一门语言能用声明式的方式来编程, 对程序员绝对是福音啊。
“此外, 面向对象编程更多的是对数据进行抽象, 而函数式编程是对行为进行抽象, 在 LISP 世界里,函数是一等公民, 函数可以当做参数传递给另外一个函数, 函数也可以作为另外一个函数的返回值。” “只用函数进行编程, 听起来不可思议啊!”
“是啊, 所以如果你学过面向对象的语言,像咱们 Java, 再去学 LISP, 会感到一片迷茫, 无所适从, 真气浑身乱窜, 好久才能平息下来。 ”
2. 强类型 “那我们能不能学一下 Ruby , Python 在咱们 Java 中添加一点函数式编程的特性, 至少让这些刁民, 奥不, 狂热分子安静下来? ”
小码想了一会说: “有点难度, 因为 Java 有一个特点,就是强类型的。”
“强类型? ,这是什么意思? “我问他
“是啊, 强类型, 简单的说无论任何变量都得有个类型, 并且不能变。” 小码哥没料到我还不知道这个概念, 开始兴致勃勃的给我普及。
”比如你可以声明一个变量 Person p = new Person(); 可是如果你想让 p 指向另外一个对象 : p = new House();
我们伟大的 java 编译器立刻就能告诉你错误: ‘类型不匹配, 不能把 House 转化成 Person'
强类型的好处是显而易见的, 我们在编译期间就可以消灭大部分的错误,并且还可以在你用 IDE 编程的时候做出很‘智能’的提示, 帮你自动补全代码。"
“与之相对的就是弱类型,例如在 Ruby 中一个变量根本不需要声明类型:
p = Person.new p = House.new
这是没有任何问题的, 变量 p 可以指向任何对象, 但是也丧失了提前发现错误的可能性, 很多错误只能在运行时暴露出来。
def do_something(p) p.walk(); // 假设 walk 只是 Person 独有的方法。 end
系统检查不出来你到底是传递进去一个什么类型的对象, 如果 p 指向一个 House, House 是不能 walk 的, 那在运行时就会报错: undefined method `walk' for #<House:0x00000002f40590>”
“但是弱类型也有好处, 例如 p = Animal.new 这个 Animal 也有一个 walk() 方法。 那在调用 do-something 方法的时候完全没有问题! ”
我有点懂了: “是不是说由于 Java 是强类型的, 如果想支持函数式编程, 我们也得给这些函数找个类型 ?”
小码哥说: “是啊,不过这有点难度”
”咱们能不能重新的设计下 Java 的类型系统 ?“ 我有点不甘心
“肯定不行, 我们 Java 帝国为了吸引程序员,并且为了维稳, 曾经对外郑重的承诺过: 帝国要保持向后兼容性,你就是用 Jdk 1.1 编译的代码, 在我们 jdk 8 中也能运行, 要是为了函数式编程,改动太大,估计很多程序员就要抛弃我们了。”
“不管如何, 你得想想办法,在 Java 中加上函数式编程, 帝国已经这么大了, 再加一点东西也没什么大不了的。”
我给了小码哥三天时间。
3. Lambda 表达式 和 类型推断 过了两天,小码哥就兴冲冲的来找我了: “老大,你看看,我设计出了 java 中的 Lambda 表达式”
我接过纸一看, 上面写着:
() -> System.out.println("Hello Lambda"); s-> s.toUpperCase(); (x,y) -> x +y ;
“这看起来像是个函数啊? 还起个这么时髦的名字 Lambda !”
“对,Lambda 可以说就是 匿名函数 , 我定义的语法是这样的, 箭头(->) 左边是 函数的参数列表 , 右边是 方法体 ”
我看了看, 确实是, 第一个表达式 没有参数, 函数体是打印"Hello Lambda"
第二个函数有一个参数 , 函数体似乎是把这个参数变成大写字符。
第三个函数有两个参数, 函数体是把两个参数相加
“小码,既然它们是函数, 按照函数式编程风格, 就能把这些函数当做参数传递给另外一些函数了? ”
“那是自然, 我给你举个例子“ 小码哥说
例如我们有这么个接口: public interface StringFuction { public String apply(String s) ; } 还有这么个函数: public String run ( StringFuction f ){ return f.apply ("Hello World"); }
现在就可以把“ 匿名函数 ”当做参数传递给另外一个函数了
run (s -> s.toUpperCase()) ;
返回值就是大写字符串: HELLO WORLD
如果我们传递进去一个别的 Lambda 表达式: run (s -> s.toLowerCase()) ;
返回值就是小写字符串: hello world
”我有个问题, 上次你说过咱们 Java 都是强类型的, 所有的东西都得有类型, 那这个 Lambda 表达式 s -> s.toUpperCase() 的类型是什么? ”
小码哥说: “唉, 为了这个问题, 我可是费劲了, 我搞了个‘ 类型推断 ’, 由编译器智能的推断出 s -> s.toUpperCase() 的类型, 其实在上面的例子中, 这个 Lambda 表达式的类型就是 StringFunction!"
“注意”小码哥接着说, “这个 StringFunction 的 apply 方法接收一个字符串参数, 然后返回另外一个字符串的值。
所以当我们用 s -> s.toUpperCase() 去调用的时候, 就会知道 s 就是 apply 的参数, s.toUpperCase() 其实就是这个方法的实现了, 这个方法的返回值也是 String ”
“奥,我明白了, 其实就是我们之前的匿名类嘛,我们完全可以这么写“ run(new StringFuction(){ public String apply(String s) { return s.toUpperCase(); } });
"对的,本质就是这样, 对于 Lambda 表达式,我们只是会做更多的类型推断,如果你这么调用 : " run(s -> s.length());
“那编译就不通过了, 因为这个匿名函数的返回值是 int , 它不符合接口(StringFunction)的方法(apply) 的语义, 接受一个字符串,然后返回一个字符串。 ”
“那个函数接口的方法名称(apply)重要吗?” 我接着问
“不重要, 关键是它的输入类型和返回值 , 至于名称,尽可能的好一点吧”
“唉,小码,我有点懂了, 为了维护 Java 的强类型, 还得定义一个函数接口, 然后编译器会把这些匿名函数(Lambda 表达式) 和这个接口做匹配, 但是你让程序员们定义函数接口, 太麻烦了吧? ”
小码哥不慌不忙 : “这个问题我考虑过了,我已经对咱的 JDK 做了增强, 特别引入了一个叫 java.util.function 的包, 里边现成的函数接口足够大部分人用了”
说着, 小码哥给我展示了一个文档:
(1) Function 函数接口: 传入一个类型为 T 的参数, 返回一个类型为 R 的参数
public interface Function<T,R>{ R apply(T t); ...... }
(2) Predicate<T> 函数接口 :传入一个类型为T 的参数, 返回 boolean
public interface Predicate<T> { boolean test(T t); ...... }
(3) Consumer<T>函数接口 : 传入一个类型为 T 的参数,没有返回值, 执行你自定义的操作 public interface Consumer<T> { void accept(T t); ...... }
例如 s -> s.length() 就可以匹配 (1) x -> x>5 就可以匹配 (2) s -> System.out.println(s) 就可以匹配 (3)
我不仅暗暗赞叹:还是抽象大法好。
"小码,我明白你的苦心, 这些确实都是高度的抽象啊"
“老大, 这是没办法的事啊, 想想我们 java 帝国那么多工具,框架, 咱必须得保持向后兼容性啊。 所以一定得坚持强类型, 即使是匿名函数, 也得给它找个类型,即函数接口 !“
“但是有了这么东西还是不够, 似乎没有体现出像 SQL 那样‘声明式’编程的好处啊? ”
“确实不够, 我们得把类库好好改改, 真正体现函数式编程的特性, 我打算引入一个叫做 Stream 的概念。”
(未完待续)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论