Java8实战-总结1

基础知识

流处理

流是一系列数据项,一次只生成一项。程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据项写入输出流。一个程序的输出流很可能是另一个程序的输入流。

一个实际的例子是在UnixLinux中,很多程序都从标准输入(UnixC中的stdin,Java中的System.in)读取数据,然后把结果写入标准输出(UnixC中的stdout, Java中的System.out)。Unixcat命令会把两个文件连接起来创建一个流,tr会转换流中的字符,sort会对流中的行进行排序,而tail -3则给出流的最后三行。Unix命令行允许这些程序通过管道(I)连接在一起,比如

cat filel file2 | tr "[A-Z]"   "[a-z]"   | sort | tail -3

会(假设file1file2中每行都只有一个词)先把字母转换成小写字母,然后打印出按照词典排序出现在最后的三个单词。sort把一个行流作为输入,产生了另一个行流(进行排序)作为输出,如下图所示。请注意在Unix中,命令(cattrsorttail)是同时执行的,这样sort就可以在cattr完成前先处理头几行。就像汽车组装流水线一样,汽车排队进入加工站,每个加工站会接收、修改汽车,然后将之传递给下一站做进一步的处理。尽管流水线实际上是一个序列,但不同加工站的运行一般是并行的。
在这里插入图片描述

操作流的Unix命令

基于这一思想,Java 8java.util.stream中添加了一个Stream API; Stream<T>就是一系列T类型的项目。现在可以把它看成一种比较花哨的迭代器。Stream API的很多方法可以链接起来形成一个复杂的流水线,就像先前例子里面链接起来的Unix命令一样。

推动这种做法的关键在于,现在可以在一个更高的抽象层次上写Java 8程序了:思路变成了把这样的流变成那样的流(就像写数据库查询语句时的那种思路),而不是一次只处理一个项目。另一个好处是,Java 8可以透明地把输入的不相关部分拿到几个CPU内核上去分别执行Stream操作流水线——这是几乎免费的并行,用不着去费劲搞Thread了。

用行为参数化把代码传递给方法

Java 8中增加的另一个编程概念是通过API来传递代码的能力。在Unix的例子里,可能想告诉sort命令使用自定义排序。虽然sort命令支持通过命令行参数来执行各种预定义类型的排序,比如倒序,但这毕竟是有限的。

比方说,有一堆发票代码,格式类似于2013UK0001、2014US0002……前四位数代表年份,接下来两个字母代表国家,最后四位是客户的代码。可能想按照年份、客户代码,甚至国家来对发票进行排序。真正想要的是,能够给sort命令一个参数让用户定义顺序:给sort命令传递一段独立代码。

那么,直接套在Java上,是要让sort方法利用自定义的顺序进行比较。可以写一个compareUsingCustomerId来比较两张发票的代码,但是在Java8之前,没法把这个方法传给另一个方法。可以创建一个Comparator对象,将之传递给sort方法,但这不但啰嗦,而且让“重复使用现有行为”的思想变得不那么清楚了。Java 8增加了把方法作为参数传递给另一个方法的能力。下图描绘了这种思路。这一概念称为行为参数化。它的重要之处在哪儿呢?Stream API就是构建在通过传递代码使操作行为实现参数化的思想上的,当把compareUsingCustomerId传进去,就把sort的行为参数化了。

在这里插入图片描述
将compareUsingCustomerId方法作为参数传给sort

并行与共享的可变数据

第三个编程概念更隐晦一点,它来自前面讨论流处理能力时说的“几乎免费的并行”。需要放弃什么吗?可能需要对传给流方法的行为的写法稍作改变。这些改变可能一开始会让人感觉有点儿不舒服,但一旦习惯了就会爱上它们。实际的行为必须能够同时对不同的输入安全地执行。一般情况下这就意味着,写代码时不能访问共享的可变数据。这些函数有时被称为“纯函数”或“无副作用函数”或"无状态函数"。前面说的并行只有在假定代码的多个副本可以独立工作时才能进行。但如果要写入的是一个共享变量或对象,这就行不通了:如果两个进程需要同时修改这个共享变量怎么办?(上面的图给出了更详细的解释。)

Java8的流实现并行比Java现有的线程API更容易,因此,尽管可以使用synchronized来打破“不能有共享的可变数据”这一规则,但这相当于是在和整个体系作对,因为它使所有围绕这一规则做出的优化都失去意义了。在多个处理器内核之间使用synchronized,其代价往往比预期的要大得多,因为同步迫使代码按照顺序执行,而这与并行处理的宗旨相悖。

这两个要点(没有共享的可变数据,将方法和函数即代码传递给其他方法的能力)是函数式编程范式的基石。与此相反,在命令式编程范式中,程序则是一系列改变状态的指令。“不能有共享的可变数据”的要求意味着,一个方法是可以通过它将参数值转换为结果的方式完全描述的;换句话说,它的行为就像一个数学函数,没有可见的副作用。

Java需要演变

之前已经见过了Java的演变。例如,引入泛型,使用List<String>而不只是List,可能一开始都挺烦人的。但现在你已经熟悉了这种风格和它所带来的好处,即在编译时能发现更多错误,且代码更易读,因为现在知道列表里面是什么了。

其他改变让普通的东西更容易表达,比如,使用for-each循环而不用暴露Iterator里面的套路写法。Java 8中的主要变化反映了它开始远离常侧重改变现有值的经典面向对象思想,而向函数式编程领域转变,在大面上考虑做什么(例如,创建一个值代表所有从A到B低于给定价格的交通线路)被认为是头等大事,并和如何实现(例如,扫描一个数据结构并修改某些元素)区分开来。

语言需要不断改进以跟进硬件的更新或满足程序员的期待。要坚持下去,Java必须通过增加新功能来改进,而且只有新功能被人使用,变化才有意义。

Java 中的函数

编程语言中的函数一词通常是指方法,尤其是静态方法;这是在数学函数,也就是没有副作用的函数之外的新含义。在Java 8谈到函数时,这两种用法几乎是一致的Java 8中新增了函数——值的一种新形式。它有助于使用流,有了它,Java 8可以进行多核处理器上的并行编程。

Java程序可能操作的值,首先有原始值,比如42(int类型)和3.14(double类型)。其次,值可以是对象(更严格地说是对象的引用)。获得对象的唯一途径是利用new,也许是通过工厂方法或库函数实现的;对象引用指向类的一个实例。例子包括"abc"(String类型),new Integer(1111)(Integer类型),以及new HashMap<Integer,String>(100)的结果——它显然调用了HashMap的构造函数。甚至数组也是对象。

要注意到,编程语言的整个目的就在于操作值,要是按照历史上编程语言的传统,这些值因此被称为一等值。编程语言中的其他结构也许有助于表示值的结构,但在程序执行期间不能传递,因而是二等公民。前面所说的值是Java中的一等公民,但其他很多Java概念(如方法和类等)则是二等公民。用方法来定义类很不错,类还可以实例化来产生值,但方法和类本身都不是值。人们发现,在运行时传递方法能将方法变成一等公民。这在编程中非常有用,因此Java8的设计者把这个功能加入到了Java中。顺便说一下,让类等其他二等公民也变成一等公民可能也是个好主意。有很多语言,如SmalltalkJavaScript,都探索过这条路。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值