朋友不上班靠玩 变量会不会被创建变量??

页面已拦截
无锡网警提示您:
该网址被大量用户举报,可能含有恶意信息。黄陵县汽车抵押贷款不押车贷款
标准价¥&1.00
产品名汽车抵押贷款,车辆抵押贷款,车子抵押贷款,不压车贷款
联系人武经理
黄陵县,汽车抵押贷款,当天放款
是否支持一件代发
&【主变量】汽车抵押贷款不押车业务贷款利息低至9厘.额度高至评估价格的12成.资料准.备齐1小时放款.贷款下来前保证不收任何费用& 山东学院建校60年来,了3万余名生,这些人才在自己岗位为山东乃至的事业贡献着自己的力量,喜讯一出,粉丝纷纷留言送上祝福。陈经认为,人工智能行业基础不稳固,相关靠谱应用也还不多。”网友看到后纷纷赞其好,并提醒她保暖。”母公司蚂蚁金服品牌与公众沟通部陈彩银表示,这次之后,个人中金额只能达到25万,
黄陵县范围内全国牌照不押车贷款,资料齐快至1小时放款,全款车,按揭车,非本人名下车均可办理,一站式服务,利息全黄陵县,连锁上市公司,靠谱。一个电话解决资金难题&& 车速贷提醒如果办理贷款一定要要找正规的大公司办理,以免造成没必要的麻烦.这是近发生的贷款纠纷案件给大家参考下:
&&&&& 27岁的农村女孩阿云,做梦都不会想到,因为“爱情”,她原本单纯无忧的平静生活被打破了:不仅背上20多万元的,还落得一个而被刑拘的恶名。而在她“爱情”的另一边,那个初次见面“特别健谈,外貌俊朗,很会讨女孩子欢心”的老乡阿斌,那个之前口口声声要与她厮守到白头的男孩,在拿走以她的名义贷款购买的汽车抵押款10万元后,仿佛人间蒸发一般,至今杳无音信。
  下催款通知才知被骗
  阿云一直记着阿斌对她的承诺,对两人的未来充满期待。然而,去年11月份,的几通催款电话将这份美好的等待击碎。
  当时,工作人员告诉她,她欠的贷款已有好几个月没有偿还了。
  “我很惊讶,因为在阿斌手里,她每个月都按时给阿斌还款,怎么会欠款呢?去查询后发现,从去年8月份开始,阿斌连续3个月都没有还一分钱,而我一直蒙在鼓里。”除了贷款外,阿云还发现,阿斌用车作抵押,向贷了5万元,又用另一家的了2万元,总达到30万元之多。“但除了建行外,其他都不是我亲自办理的。”阿娇告诉记者。
  阿云开始打多份工赚钱,白天在鞋店上班,晚上去酒吧售销酒水,劳累不堪。
  汽车抵押款也被拿走了
  今年2月,不堪折磨的阿云提出分手。为了向阿斌追回钱和车,今年3月底,姐妹俩回到老家,通过别人找到阿斌家,但阿斌的家里只有年迈的奶奶,阿斌和他的父母均不知去向。阿斌拒与阿云姐妹见面,他在电话中,对指导阿云办理贷款买车一事矢口否认。更糟糕的是,阿斌早在去年10月,用花言巧语,通过阿云,将在湛江刚买不久的锐志车以10万元价格抵押给别人。阿斌拿走这笔钱后,至今杳无音信。
  阿娇说,以名义贷款买的车,仅在抵押当天坐了一次,至于车牌等相关信息,更是一概不知。之后,阿云无论是在老家还是东莞,都以证据不足拒受理。4月22日,在廉江老家,阿云因罪被东莞万江带走调查。目前被关押在东莞第二,至今已超过一个月。
  昨日,记者拨打阿斌电话,一直没人接听。截至记者发稿前,仍没有阿斌的任何消息。
  辞工要替打官司
  阿娇告诉记者,“这一切,都是因法律意识淡薄引发。以为,贷款时,虽然是自己的,但只要不是她在操作,就没有问题,加上被爱情冲昏头脑,结果招来牢狱之灾。”
  如今,阿娇已辞掉工作,为找律师打官司,并四处还账。她说,“父母都是普通农民,收入有限,妈妈本身就有精神,原本好了一点,现在听到被捕的消息,又变严重了。”
  “我现在要做的就是替申冤,无论多难,也要进行下去,毕竟,她是被‘渣男’骗了,不能让这样的‘渣男’逍遥法外。”阿娇说。
  女子犯罪已刑拘
  对于“阿云被男朋友给骗了”,称没有证据证明其中有行为
  昨日下午,万江分局就此事发出通报,称日,某东莞分行称,李某云(女,26岁,广东廉江人)恶意。经,嫌疑人李某云向某申请贷款分期购车业务中,犯罪。2015年4月下旬,在廉江市将犯罪嫌疑人李某云抓获归案。目前,李某云已被刑事,案件正在进一步审理中。
  对于李某云的阿娇所称“是被男友给骗了”的说法,万江知情人士表示,阿娇所说的“阿云被男朋友骗了”,与阿云的案不是同一个案件,况且,阿娇所说的“阿云与他人的感情纠葛”,并没有证据证明当中存在行为。
  4月22日,阿云因,被万江从广东廉江老家带回东莞,随后,关押在东城牛山。
  阿云的阿娇表示,没读过什么书,不懂法,被男朋友给骗了,是蒙冤入狱。“阿云所欠的款项,全是因帮‘男友’阿斌贷款购买小轿车而起,但阿斌却对这一切不承认。”阿娇说。
  “渣男”求女友贷款买轿车
  据阿娇介绍,阿云今年27岁,广东廉江人,曾在南城步行街一家品牌鞋店做销售,月入4000元左右,生活还算过得去。
  2013年下半年,在从老家返回东莞的大巴上,阿云遇到小她一岁的老乡阿斌。后者自称在常平经营一家手机店。因为老乡的关系,加上阿斌“特别健谈,外貌俊朗,很会讨女孩子欢心”,阿云心中对他留下了不错的印象。
  之后,阿斌时不时会打个电话给阿云,嘘寒问暖,或倾诉工作上的烦恼。两个人越来越亲近。
  去年4月份前后,阿斌打电话给阿云,说自己手机店的生意遇上麻烦,希望阿云能陪她去散散心,阿云有点心疼,就答应了。
  “那天,大家都玩得很开心,之后见面越来越多了,两人就开始恋爱了。但恋爱期间,阿云从来没有去过阿斌所说的手机店,也没有见过他任何一位亲戚或朋友。他似乎刻意回避这一切。他本人差不多十天半个月过来一次。”阿云的一位同事表示。
  之后,阿斌又打来电话,说手机店生意很不好,需要买辆车开拓业务,但他自己有信用污点,不能贷款,央求阿云帮忙。
  “当时,我告诉我了,说她如果不同意,阿斌就要跳楼,当时,已经怀孕一个月,被他一吓,只好答应。”阿娇说。
  去年4月26日,在阿斌手把导下,初中没就辍学打工,且不知为何物的阿云,提交了一系列个人资料,在建设办理了一张用于汽车贷款的,额度为13万元。之后,阿云又在阿斌指导下,通过汽车金融公司贷款10万元,加上她自己的1万余元存款及阿斌的几千元现金,凑在一起,共花费近25万元,买了一辆丰田锐志轿车。
  律师支招
  阿云涉无争议 可通过诉讼挽回损失
  对于此事,广东雄爵律师事务所律师熊氢玲表示,从该案来看,阿云没有太大争议,因为卡是以阿云的名义办的,而方面所受理的全部资料也都是阿云的。阿云家属的当务之急是及时还款,看能否出具谅解书,请求从轻处理,再通过或检察院取保候审。
  而阿云与阿斌之间的纠纷又构成了另外一种法律关系。阿云买车后,车子一直为阿斌所用,可以定义为一种借用关系,而实际所有者为阿云本人。在后一层关系中,阿斌即便没有构成,也无权占用车子和车款。如果阿云能提供平日给他打款的相关凭证,就至少可以以不当得利名义他。若有足够证据证明阿斌虚构事实将车子据为己有,进而抵押卖掉,则可以以为由,向,再通过诉讼挽回损失。
车速贷黄陵县汽车抵押贷款公司在此提醒广大市民,办理汽车抵押贷款一定要选择正规的贷款公司,在贷款之前一定要到工商局网站核实公司信息,有无营业执照;同时贷款公司也应当有固定地址,随便约定见面地址,很可能就是,希望大家在办理的中注意警惕,防止上当受骗。
黄陵县汽车抵押贷款的特点:
1.放款速度快,手续简便,60分钟放款;
2.贷款期限灵活,适用于生意生活周转,期限一般为1-36个月;
3.利率全城
4. 可靠& 大公司
黄陵县汽车抵押贷款申请条件:
1、个人客户:18周岁以上,具有完全民事行为能力的年满18周岁公民
2、公司客户:具有法人资格,在工商行政局注册的境内企业
黄陵县汽车抵押贷款申请资料:
一:个人名下车
1.机动车登记证原件 2.行驶本正副本原件
3.车辆单原件 4.或本地居住证
5.车辆备用钥匙 6.驾驶证
二、公司车辆
1、机动车登记证原件、行驶本正副本原件;
2、机动车购车原始、车辆单、
3、企业组织机构代码证书原件、营业执照;
4、法人、法人委托书;
5、客户需带单位公章;
黄陵县汽车抵押贷款微信申请:676-980-289
汽车抵押贷款热门信息
咨询主题价格发货与交货商品参数其它
小提示:本信息描述文字和图片由用户自行上传发布,其真实性、合法性由发布人负责。
主营:汽车抵押贷款,房产抵押贷款
地址:北京
—————
—————
没有个人认证
企业认证已通过
手机认证已通过
微信认证已通过邮箱认证已通过阅读笔记–Java 8函数式编程
为什么需要再次修改Java
1996年1月,Java1.0发布,商业发展需要更复杂的应用,跑在功能强大的多核CPU机器上。带有高效运行时编译器的Java虚拟机的出现,使程序员将更多的精力放在编写干净,易于维护的代码上,而不是思考将每一个CPU时钟周期,每字节内存物尽其用。
多核CPU,涉及锁的编程算法不但容易出错,而且耗费时间。
java.util.concurrent包和很多第三方类库,试图将并发抽象化,但还不够。
处理大数据集合,Java还欠缺高效的并行操作。
为了编写这类处理批量数据的并行类库,需要再语言层面上修改现有的Java:增加Lambda表达式。
函数式编程
核心:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
面向对象编程是对数据进行抽象
函数式编程是对行为进行抽象
现实世界中,数据和行为并存
Lambda表达式
目标类型:由编译器推断出来,Java初始化数组和将null赋值给一个变量,才能知道它的类型。
引用值: Lambda表达式引用的是值,而不是变量。这种行为也解释了为什么Lambda表达式也被成为闭包。未赋值的变量与周边环境隔离起来,进而被绑定到一个特定的值。
Lambda表达式都是静态类型的,因此,Lambda表达式本身的类型:接口函数。
定义:函数接口是只有一个抽象方法的接口,用作Lambda表达式的类型。
Lambda表达式中的类型推断,实际上是Java7就音域的目标类型推断的扩展。
菱形操作符
Lambda表达式是一个匿名方法,将行为像数据一样进行传递。
Lambda表达式常见结构:BinaryOperator&Integer& add = (x, y) -& x + y。
函数接口指仅具有单个抽象方法的接口,用来表示Lambda表达式的类型。
for循环是一个封装了迭代的语法糖。首先调用Iterator方法,产生一个新的Iterator对象,进而控制整个迭代过程。
外部迭代 本质上来讲是一种串行化操作。总体来看,使用for循环会将行为和方法混为一谈。
stream()操作返回内部迭代中的相应接口:Stream。
stream()操作后返回的不是新的集合,而是创建新集合的配方。比如filter操作只是刻画出了Stream,但是没有产生新的集合。最终不产生新集合的方法叫做惰性求值方法;而像count这样的最终会从Stream产生值的方法叫做及早求值方法。
类似于建造者模式
camparing()方法
实现是接受一个函数并返回另一个函数。
max和min方法都属于更通用的一种编程模式。
Object accumulator = initialV
for(Object element : collection) {
accumulator = combine(accululator, element);
这种模式中两个可变项是initialValue初始值和combine函数。
reduce操作
reduce操作可以实现从一组值中生成一个值。count、min和max都是reduce操作。
int count = Stream.of(1, 2, 3)
.reduce(0, (acc, element) -& acc + element);
reducer的类型是BinaryOperator
BinaryOperator&Integer& accumulator = (acc, element) -& acc + element
int count = accumulator.apply(
accumulator.apply(
accumulator.apply(0, 1),
使用命令编程方式求和
int acc = 0;
for(Integer element : asList(1, 2, 3)) {
acc = acc +
每一次循环将集合中的元素和累加器相加,用相加后的结果更新累加器的值。对于集合来说,循环在外部,且需要手动更新变量。
使用Java8编程的时候,可以优先暴露Stream接口,而不是某个具体的实现。他很好的封装了内部实现的数据结构。仅暴露一个Stream接口,用户在实际操作中无论如何使用,都不会影响内部的List或Set。
高阶函数是指接受另外一个函数作为参数,或返回一个函数的函数。
高阶函数的参数列表里包含函数接口,或该函数返回一个函数接口,那么该函数就是高阶函数。
Comparator实际上应该是个函数,但是那是的Java只有对象,因此才再出一个类,一个匿名类。成为对象实属巧合,函数接口向正确的方向迈出了一步。
回调函数是一个合法的Lambda表达式,但并不能真正帮助用户写出更简单,更抽象的代码,因为他仍然在指挥计算机执行一个操作。
在Java中有一些相伴的类型,比如int和Integer。基本类型内建在语言和运行环境中,是基本的程序构建模块,而装箱类型属于普通的Java类,只不过是对基本类型的一种封装。
Java的泛型是基于对泛型参数类型的擦出,换句话说,假设它是Object对象的实例—-因此只有装箱类型才能作为泛型参数。这就解释了为什么在Java中想要一个包含整型值得列表List,实际上得到的确实一个包含整型对象的列表List。
麻烦的是,整型在内存中占用4个字节,整型对象占16字节,数组上表现更严重,整型数据中的每个元素只占用基本类型的内存,而整型对象数组中,每个元素都是内存中的一个指针,指向Java堆中的某个对象。在最坏的情况下,同样大小的数组,Integer[]要比int[]多占用6倍内存。
装箱和拆箱操作都需要额外的计算开销。
ToLongFunction和LongFunction
Java8仅对整型,长整型和双浮点型做了特殊处理,因为他们在数值计算中用的最多
返回基本类型,在基本类型前加To,如果参数是基本类型,则不加前缀只需类型名即可。如果高阶函数使用基本类型,则在操作后加后缀To再加基本类型。
LongUnaryOperator
特殊mapToLong实现
通过一些高阶函数装箱方法,mapToObj,也可以从一个基本类型的Stream得到一个装箱后的Stream,如Stream.
尽可能多用对基本类型做过处理的方法,改善性能。
summaryStatistic方法
mapToInt返回一个IntStream对象,它包含一个summaryStatistics方法,这个方法能计算出各种各样的统计值。
在Java中可以重载方法,造成多个方法有相同的方法名,但签名确不一样。这时,javac会挑出最具体的类型。
BinaryOperator是一种特殊的BiFunction类型,参数的类型和返回值的类型相同。比如,两个整数相加就是一个BinaryOperator。
Lambda表达式的类型就是对于的函数接口类型,因此,将Lambda表达式作为参数传递时,情况也依然如此。操作时可以重载一个方法,分别接受BinaryOperator和该接口的一个子类作为参数调用这些方法时,Java推导出的Lambda表达式的类型正是最具体的函数接口的类型。
- 如果只有一个可能的目标类型。由相应函数接口里的参数类型推导得出;
- 如果有多个可能的目标类型,由最具体的函数推导得出;
- 如果有多个可能的目标类型且最具体的类型不明确,则需人为指定类型。
@FunctionalInterface
事实上,每个用作函数接口的接口都应该添加这个注释。
原因:Java中有一些接口,虽然只含一个方法,但并不是为了使用Lambda表达式来实现的。比如,有些对象内部可能保存着某种状态,使用带有一个方法的接口可能纯属巧合。java.lang.Comparable和java.io.Closeable.
一个科关闭的对象必须持有某种打开的资源,比如一个需要关闭的文件句柄。同样,该接口也不能是一个纯函数,因为关闭资源是一个更改状态的另一种形式。
和Closeable和Compareable接口不同,为了提高Stream对象可操作性而引入的各种新接口,都需要有Lambda表达式可以实现它。它们存在的意义是在于将代码块作为数据打包起来。因此,它们都天剑了@FunctionalInterface注释。
该注释会强制javac检查一个接口是否符合函数接口的标准。
二进制接口的兼容性
二进制的兼容性一直被视为Java的关键优势所在。但在Java8中Collection接口增加stream方法,java类库实现方法就没问题,第三方类库就会存在问题。
在任何接口中,无论函数接口还是非函数接口,都可以使用该方法。
default void forEach(Consumer&? super T& action) {
for(T t : this) {
action.accept(t);
default关键字告诉javac用户真正需要的是为接口添加一个新方法。
默认方法在集成规则是和普通方法也略有区别。
和类不同,接口没有成员变量,因此默认方法只能通过调用子类的方法来修改子类本身,避免了对子类的实现做出各种假设。
默认方法成了虚方法–和静态方法刚好相反,任何时候,一旦与类中定义的方法产生冲突,都要优先选择类中定义的方法。
public class OverridingChild extends OverridingParent implements Child {
与接口定义的默认方法相比,类中重写的方法更具体。
类中中邪的方法胜出,这样的设计主要是增加默认方法的目的决定的,增加默认方法主要是为了在接口上向后兼容。让类中重写的方法的优先级高于默认方法能简化很多集成问题。
接口允许多重继承,因此可能碰到两个接口包含签名相同的默认方法的情况。
public interface Jukebox {
public default String rock() {
return "... all over the world!"
public interface Carriage {
public default String rock() {
return "...from side to side";
public class MusicalCarriage implements Carriage, Jukebox {
这种情况下编译器会报错:class Musical Carriage iniherits unrelated defaults for rock() from types Carriage and Jukebox.
在类中实现rock方法就能解决这个问题。
public class MusicalCarriage implements Carriage, Jukebox {
public String rock() {
return Carriage.super.rock();
使用增强的super语法,用来指明使用接口Carriage中定义的默认方法。
类胜于接口
子类胜于父类
没有规则三
其中第一条规则是为了让代码向后兼容。
Java优于C++的原因之一是舍弃了多重继承。
语言特性的利弊也在不断的演化。很多人认为多重继承的问题在于对象状态的继承,而不是代码块的继承,默认方法避免了状态的继承,因此避免了C++中多继承的最大缺点。
突破语言上的局限性吸引着无数优秀的程序员不断尝试,现在已有一些博客文章,阐述在Java8中实现完全的多重继承做出的尝试,包括状态的继承和默认方法。尝试突破Java8这些有意为之的语言限制,却往往又掉进C++的旧陷阱之中。
接口和抽象类之间还是存在明显的区别。接口允许多重继承,却没有成员变量;抽象类可以继承成员变量,却不能多重继承。在对问题域建模时,需要根据具体情况进行权衡,而在以前的Java中可能并不需要这样。
接口的静态方法
Stream.of,Stream是个接口,Stream.of是接口的静态方法。旨在帮助编写类库的开发人员,但对于日常应用程序的开发人员也同样适用。
人们在编程过程当中总结出了一条经验,那就是一个包含很多静态方法的类。有时,类是一个放置工具方法的好地方,比如Java7中引入的Objects类,就包含很多工具方法,这些方法不是具体属于某个类。
如果一个方法有充分的语义原因和某个概念相关,那么就应该将该方法和相关的类或接口放在一起,而不是放到另一个工具类中。这有助于更好的组织代码,阅读代码的人也更容易找到相关方法。
reduce方法有两种形式,一种需要有一个初始值,另一种变式则不需要有初始值。在没有初始值的情况下,reduce的第一步使用Stream中的前两个元素。有时,reduce操作不存在有意义的初始值,这样做就是有意义的。
Optional是为核心类库新设计的一个数据类型,用来代替null值。
意义:检查变量,避免缺陷;将可能为空文档化。
of(), empty(), ofNullable()
isPresent(),表示一个Optional对象里是否有值;
orElse(), orElseGet();
assertEqual("b", emptyOptional.orElse("b");
assertEqual("c", emptyOptional.orElseGet(() -& "c")
高级集合类和收集器
artist -& artist.getName()
Lambda经常调用参数,所以提供简写语法,叫做方法引用
需要注意的是,这里并不调用该方法。我们只是提供了和Lambda表达式等价的一种结构,在需要的时候才会调用。
构造函数也有同样的缩写形式
(name, nationality) -& new Artist(name, nationality)
Artist:new
这段代码不仅比原来的代码段,而且更易阅读。需要注意的是方法引用自动支持多个参数,前提是选对了正确的函数接口。
还可以用这种方式创建数组
String[]::new
实质是语法糖的语法糖
直观上看,流是有序的,因为流中的元素都是按顺序处理的。这种顺序成为出现顺序。出现顺序的定义依赖数据源和对流的操作。
在一个有序集合中创建一个流时,流中的元素就按出现顺序排列。
List&Integer& numbers = asList(1, 2, 3, 4);
List&Integer& sameOrder = numbers.stream().collect(toList());
assertEquals(numbers, sameOrder);
一些操作在有序的流上开销更大,调用unordered方法消除这种顺序就能解决该问题。大多数操作在有序流觞效率更高,比如filter,map和reduce等。
使用并行流操作时,forEach方法并不能保证元素是按顺序处理的,保证按顺序使用forEachOrdered。
使用收集器
一种通用的,从流生成复杂值得结构。只要传给collect方法,所有的流就都可以使用它了。
toCollection 接受一个函数作为参数,创建集合。
stream.collect(toCollection(TreeSet::new));
maxBy和minBy允许用户按某种特定的顺序生成一个值。
public Optional&Artist& biggestGroup(Stream&Artist& artists) {
Function&Artist, Long& getCount = artist -& artist.getMembers().count();
return artists.collect(maxBy(comparing(getCount))));
类似还有averagingInt。
partitioningBy()
public Map&Boolean, List&Artist&& bandsAndSoloRef(Stream&Artist& artists) {
return artists.collect(partitioningBy(Artist));
数据分组是一种更自然的分割数据操作,可以对数据任意分组。
public Map&Artist, List&Album&& albumsByArtist(Stream&Album& albums) {
return albums.collect(groupingBy(album -& album.getMainMusician()));
很多时候,收集流中的数据都是为了在最后生成一个字符串。
// 使用流和收集器格式化艺术家名字
String result =
artists.stream()
.map(Artist::getName)
.collect(Collectors.joining(", ", "[", "]"))
组合收集器
pubic &Artist, Long& numberOfAlbums(Stream&Album& albums) {
return albums.collect(groupingBy(album -& album.getMainMusician(), counting()));
groupingBy先将元素分成块,每块都与分类函数getMainMusician提供的键值相关联,然后使用下游的另外一个收集器收集每块中的元素,最好将结果映射为一个Map。
我们需要有一种方法,可以告诉groupingBy将它的值做映射,生成最终结果。
所以,mapping收集器。
mapping允许在收集器的容器上执行类似map的操作。但是需要指明使用什么样的集合类存储结果,比如toList。
public Map&Artist, List&String&& nameOfAlbums(Stream&Album& albums) {
return albums.collect(groupingBy(Album, mapping(Album, toList())));
第二个收集器用以收集最终结果的一个子集。这些收集器叫做下游收集器.
重构和定制收集器
实现一个StringCollector
- 待收集元素的类型,这里是S
- 累加器的类型StringC
- 最终结果的类型,这里依然是S
public class StringCollector implements Collector&String, StringCombiner, String& {}
一个收集器由四部分组成。首先是一个Supplier,这是一个工厂方法,用来创建容器,在这个例子中,就是StringCombiner. 和reduce操作中的第一个参数类似,它是后续操作的初始值。
public Supplier&StringCombiner& supplier() {
return () -& new StringCombiner(delim, prefix, suffix);
public BiConsumer&StringCombiner, String& accumulator() {
return StringCombiner::
public BinaryOperator&StringCombiner& combiner() {
return StringCombiner::
public Function&StringCombiner, String& finisher() {
return StringCombiner::toS
由于收集器可以并行收集,如图(暂无)
Supplier创建出新的容器
收集器accumulator,结合之前操作的结果和当前值,生成并返回新的值。
combine,如果有两个容器,需要将其合并。
在收集阶段,容器被combiner方法成对合并进一个容器,直到最后只剩一个容器为止。
Finisher,进行转换,在我们想创建字符串等不可变的值时特别有用,这里容器是可变的。
特征:特征是一组描述收集器的对象,框架可以对其适当优化。characteristic方法定义了特征。
自定义收集器适用于,在自己特定的领域内的类,希望从集合中构建一个操作,而标准的集合类并没有提供这种操作时;
identity函数:它返回传入参数的值。如果这样,收集器就会展现出IDENTITY_FINISH的特征,需要使用characteristics方法声明。
对收集器的归一化处理
如果你想要为自己领域内的类定制一个收集器,不妨考虑一下其他替代方案。最容易想到的方案是构建若干个集合对象,作为参数传给另一内类的构造函数。如果领域内类包含多种集合,这种方式又简单又适用。
如果领域内没有这些集合,需要在已有数据上计算,仍然可以使用reducing收集器代替,因为他为流上的归一操作提供了统一实现。
String result =
artists.stream()
.map(Artist)
.collect(Collectors.reducing(
new StringCombiner(", ", "[", "]"),
name -& new StringCombiner(", ", "[", "]").add(name),
StringCombiner))
.toString();
这种方式很低效,Collectors.reducing的第二个参数,我们为流中每个元素创建了唯一的StringC
Lambda表达式的引入也推动了一些新方法被加入集合类。
public Artist getArtist(String name) {
Artist artist = artistCache.get(name);
if(artist == null) {
artist = readArtistFromDB(name);
artistCache.put(name, artist);
Java8引入了一个新方法computeIfAbsent,该方法接受一个Lambda表达式,值不存在时使用该Lambda表达式计算新值。
public Artist getArtist(String name) {
return artistCache.computeIfAbsent(name, this::readArtistFromDB);
Map&Artist, Integer& countOfAlbums = new HashMap&&();
albumsByArtist.forEach((artist, albums) -& {
countOfAlbums.put(artist, albums.size());
两个任务共享时间段
两个任务在同一时间发生
数据并行化
并行化是指为缩短任务执行时间,将一个任务分解为几部分,然后并行执行。实际上,和顺序执行相比,并行化执行任务时,CPU承载的工作量更大。
数据并行化是指将数据分成块,为每块数据分配单独的处理单元。当需要在大量数据上执行同样操作时,数据并行化很管用。
在任务并行化中,线程不同,工作各异。我们最常遇到的Java EE应用容器便是任务并行化的例子之一,每个线程不光可以为不同用户服务,还可以为同一个用户执行不同的任务,比如登录或往购物车添加商品。
阿姆达尔定律,一个简单定律,预测搭载多核处理器的机器提升速度的理论最大值。以一段完全串行化的程序为例,如果将其一半改为并行化处理,则不管增加多少处理器,其理论上最大速度只是原来的2倍。有了大量的处理器后,现在这已经是现实了,问题的求解时间完全取决于它可被分解成几个部分。
并行化流操作
public int serialArraySum() {
return albums.stream()
.flatMap(Album::getTracks)
.mapToInt(Track::getLength)
public int serialArraySum() {
return albums.parallelArraySum()
.flatMap(Album::getTracks)
.mapToInt(Track::getLength)
并行化操作的用武之地是使用简单的操作处理大量数据,比如模拟系统。
蒙特卡洛模拟法,重复相同的模拟很多次,每次模拟都使用随机生成的种子。每次模拟的结果都被记录下来,汇总得到一个对系统全面的模拟。蒙特卡洛模拟法被大量用在工程,金融和计算科学领域。
public Map&Integer, Double& parallelDiceRolls() {
double fraction = 1.0 / 100000;
return IntStream.range(0, 100000)
.parallel()
.mapToObj(twoDiceThrows())
.collect(groupingBy(side -& side),
summingDouble(n -& fraction));
private static IntFunction&Integer& twoDiceThrows() {
return i -& {
ThreadLocalRandom random = ThreadLocalRandom.current();
int firstThrow = random.nextInt(1, 7);
int secondThrow = random.nextInt(1, 7);
return firstThrow + secondT
The sequence of values generated from a Random should be uncorrelated.
Similarly the sequence of values generated from a ThreadLocalRandom should be uncorrelated with values from itself, and values from any other ThreadLocalRandom with a different seed.
So switching to ThreadLocalRandom should give values with the same statistics as using Random.
The advantage is that you avoid any need for synchronization.
public class ManualDiceRolls {
private static final int N = ;
private final double
private final Map&Integer, Double&
private final int numberOfT
private final ExecutorS
private final int workPerT
public static void main(String[] args) {
ManualDiceRolls roles = new ManualDiceRolls();
roles.simulateDiceRoles();
public ManualDiceRolls() {
fraction = 1.0 / N;
results = new ConcurrentHashMap&&();
numberOfThreads = Runtime.getRuntime().availableProcessors();
executor = Executors.newFixedThreadPool(numberOfThreads);
workPerThread = N / numberOfT
public void simulateDiceRoles() {
List&Future&?&& features = submitJobs();
awaitCompletion(features);
printResults();
private void printResults() {
results.entrySet().forEach(System.out::println);
private void awaitCompletion(List&Future&?&& features) {
features.forEach((future -& {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
executor.shutdown();
private List&Future&?&& submitJobs() {
List&Future&?&& futures = new ArrayList&&();
for (int i = 0; i & numberOfT i++) {
futures.add(executor.submit(makeJob()));
private Runnable makeJob() {
return () -& {
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i & workPerT i++) {
int entry = twoDiceThrows(random);
accumulateResult(entry);
private void accumulateResult(int entry) {
results.compute(entry, (key, previous) -&
previous == null ? fraction : previous + fraction
private int twoDiceThrows(ThreadLocalRandom random) {
int firstThrow = random.nextInt(1, 7);
int secondThrow = random.nextInt(1, 7);
return firstThrow + secondT
之前调用reduce方法,初始值可以为任意值,为了让其在并行化时能工作正常,初始值必须为组合函数的恒等值。拿恒等值和其他值做reduce操作时,其它值保持不变。比如使用reduce操作求和,组合函数为(acc, element) -& acc + element,其初始值必须为0,因为任何数字加0,值不变。
reduce操作的另一个限制是组合操作必须符合结合律。这意味着只要序列的值不变,组合操作的顺序不重要。
要避免持有锁
parallel和sequential,最后调用的那个方法起效。
影响并行流性能的主要因素有五个
1. 数据大小
2. 源数据结构 每个管道的操作都是基于一些初始数据源,通常是集合 。
3. 装箱 处理基本数据类型要比处理装箱类型快。
4. 核的数量 越多越好
5. 单元处理开销,这是一场并行执行花费时间和分解合并操作开销之间的战争。花在流中每个元素身上的时间越长,并行操作带来的性能提升越明显。
private int addIntegers(List&Integer& values) {
return values.parallelStream()
.mapToInt(i -& i)
在底层,并行流沿用了fork/join框架。fork递归式地分解问题,然后每段并行执行,最终由join合并结果,返回最后的值。
性能好:ArrayLit,数组或IntStream.range,这些数据结构支持随机读取,也就是说它们可以被轻而易举地被任意分解。
性能一般:HashSet、TreeSet,这些数据结构不易,但大多数时候是可能的。
性能差:可能要花O(N)的时间复杂度来分解问题。其中包括LinkedList,Stream.iterate,BufferedReader.lines,后两种长度位置,很难预测应该哪里分解。
流单独操作每一块时,分成两种不同的操作:无状态和有状态,有状态则有维护状态所需的开销和限制。
无状态:map,filter,flatMap
有状态:sorted,distinct,limit。
并行化数组操作
Arrays.java
parallelPrefix 任意给定一个函数,计算数据的和
parallelSetAll 使用Lambda表达式更新数组元素
parallelSort 并行化数组元素排序。
public static double[] parallelInitialize(int size) {
double[] values = new double[size];
Arrays.parallelSetAll(values, i -& i);
parallelPrefix擅长对时间序列数据做累加,他会更新一个数组,将每一个元素替换为当前元素和其前驱元素的和,这里的和是一个宽泛的概念,可以是任意一个BinaryOperator.
public static double[] simpleMovingAverage(double[] values, int n) {
double[] sums = Arrays.copyOf(values, values.length);
Arrays.parallelPrefix(sums, Double::sum);
int start = n - 1;
return IntStream.range(start, sums.length)
.mapToDouble(i -& {
double prefix = i == start ? 0 : sums[i - n];
return (sums[i] - prefix) /
.toArray();
测试驱动开发
Kent Beck 写的
Test-Driven Development,以及由 Steve Freeman 和 Nat Pryce 写的 Growing Object-Oriented
Software, Guided by Tests(两本书均由 Addison-Wesley 出版社出版)
在选择内部设计模型时,想想以何种形式向外展示API是大有裨益的。
进进出出,摇摇晃晃
如果你发现自己代码不断地查询和操作某对象,目的只是为了在最后给该对象设个值,那么这段代码本该属于你所操作的对象。
一种方式:传入代码即是数据
与其查询并设置一个对象的值,不如传入一个Lambda,该表达式按照计算的出的值执行相应的行为。
Logger logger = new Logger();
logger.debug(() -& "Look at this: " + expensiveOperation());
孤独的覆盖
这个代码异味是使用集成,其目的只是为了覆盖一个方法。ThreadLocal就是一个很好的例子。ThreadLocal能创建一个工厂,为每个线程最多产生一个值。这是确保非线程安全的类在并发环境下安全使用的一种简单方式。
ThreadLocal&Album& thisAlbum = new ThreadLocal&Album&() {
protected Album initialValue() {
return database.lookupCurrentAlbum();
在Java8中,可以为工厂方法withInitial传入一个Supplier实例来创建对象。
ThreadLocal&Album& thisAlbum =
ThreadLocal.withInitial(() -& database.lookupCurrentAlbum());
有点是任何已有的Supplier实例不需要重新封装,鼓励了重用和组合。
信躁降低,JVM少加载一个类。
同样的东西写两遍
Don`t Repeat Yourself, 重复的样板代码,产生了更多需要测试的代码。这样的代码难于重构,一改就坏。
如果有一个整体上大概相似的模式,只是行为上有所不同,就可以试着加入一个Lambda表达式。
ToLongFunction,传入不同的数据结构,返回相同的数据结构;
Lambda表达式的单元测试
单元测试是测试一段代码的行为是否符合预期的方式。
第一种,将Lambda表达式放入一个方法测试,这种方法时测试方法本身,而不是Lambda表达式本身。
第二种,不使用Lambda。
第三种,改成方法引用,任何Lambda都可以改写为普通方法,然后使用方法引用直接引用。
在测试替身时使用Lambda表达式
编写但愿测试的常用方式之一是使用测试替身描述系统中其他模块的期望行为。因为单元测试可以脱离其他模块来测试你的类或方法,测试替身让你能用但愿测试来实现这种隔离。
测试替身也常被成为模拟,事实上测试存根和模拟都属于测试替身。区别是模拟可以验证代码的行为。
测试代码时,使用Lambda表达式的最简单方式就是实现轻量级的测试存根。如交互的类本身就是一个函数接口,实现这样的存根就非常简单和自然。
List&String& list = mock(List.class);
when(list.size()).thenAnswer(inv -& otherList.size());
assertEqual(3, list.size());
Mockito使用Answer接口允许用户提供其他行为(代码即数据)。
惰性求值和调试
在传统的命令式编程看来,代码就是达到某种目的的一系列行动,在行动前后查看程序状态是有意义的。在Java8中,你仍然可以使用IDE提供的各种调试工具,但有时需要调整实现方式,以期达到更好的结果。
日志和打印消息
解决方案:peek
流有一个方法能让你查看每个值,同时能继续操作流。这就是peek方法。
Set&String& nationalities
= album.getMusician()
.filter(artist -& artist.getName.startsWith("The"))
.map(artist -& artist.getNationality())
.peek(nation -& System.out.println("Found nationality: " + nation))
.collect(Collectors.&String&toSet())
peek方法还能以同样的方式,将输出定向到现有的日志系统中,比如log4j。
在流中设置断点
可以在peek方法中加入断点,这样就逐个调试流中的元素。
此时,peek方法可知包含一个空的方法体,只要能设置断点就行。有一些调试器不允许在空的方法体重设置断点,此时,我将值简单的映射为其本身。
设计和架构的原则
软件开发最重要的设计工具不是什么技术,而是一个在设计原则方面训练有素的头脑。
Lambda表达式改变了设计模式
设计模式是人们熟悉的另一种设计思想,他是软件架构中解决通用问题的模板。
曾经风靡一时的单例模式,在过去十年里,人们批评它让程序变得脆弱,且难于测试。敏捷开发的流行,让测试显得更加重要,单例模式的这个问题把它变成了一个反模式:一种应该避免使用的模式。
命令者模式
命令者是一个对象,他封装了调用另一个方法的所有细节,命令者模式使用该对象,可以编写出根据运行期条件,顺序调用方法的一般化代码。命令者模式中有四个类参与其中。
命令接受者:执行实际任务
命令者:封装了所有调用命令执行者的信息
发起者:控制一个或多个命令的顺序和执行
客户端:创建具体的命令者实例
Macro macro = new Marco();
marco.record(editor::open);
marco.record(editor::save);
marco.record(editor::close);
marco.run();
宏只是使用命令者模式的一个例子,它被大量用在实现组件化的图形界面系统,撤销功能,线程池,事务和向导中。
策略模式能在运行时改变软件的算法行为。其主要思想是定义一个通用的问题,使用不同的算法来实现,然后将这些算法都封装在一个统一接口的背后。
文件压缩就是一个很好的例子。
public interface CompressionStrategy {
public OutputStream compress(OutputStream data) throws IOE
public class GzipCompressionStrategy implements CompressionStategy {
public OutputStream compress(OutputStream data) throws IOException {
return new GZIPOutputStream(data);
public class ZipCompressionStrategy implements CompressionStategy {
public OutputStream compress(OutputStream data) throws IOException {
return new ZIPOutputStream(data);
public class Compressor {
private final CompressionS
public Compressor(CompressionStrategy strategy) {
this.stragegy =
public compress(Path inFile, File outFile) throws IOException {
try (OutputStream outStream = new FileOutputStream(outFile)) {
Files.copy(inFile, stragegy.compress(outStream));
Compressor gzipCompressor = new Compressor(new GzipCompressStragegy());
gzipCompressor.compress(inFile, outFile);
Compressor gzipCompressor = new Compressor(GZIPOutputStream::new);
gzipCompressor.compress(inFile, outFile);
从某种角度来说,将大量代码塞进一个方法会让可读性变差是决定如何使用Lambda表达式的黄金法则。编写一般方法时的黄金法则!
模板方法真正要做的是将一组方法调用按一定顺序组织起来。。如果用函数接口表示函数,用Lambda表达或者方法引用实现这些接口,相比使用继承构建算法,就好得到极大的灵活性。
public class LoanApplication {
private final C
private final Criteria creditH
private final Cruteria incomeH
public LoanApplication(Criteria identity,
Criteria crediHistory,
Criteria incomeHistory) {
this.identity =
this.creditHistory = creditH
this.incomeHistory = incomeH
public void checkLoanApplication() throws ApplicationDenied {
identity.check();
creditHistory.check();
incomeHistory.check();
reportFindings();
public interface Criteria {
public void check() throws ApplicationD
采用这种方式的好处是不需要再LoanApplication极其子类中实现算法,分配功能是有了更大的灵活性。比如,我们想让Company类负责所有的检查,那么Company类就会多出一系列方法。
使用Lambda表达式的领域专用语言
领域专用语言(DSL)是针对软件系统中某 特定部分的编程语言。DSL高度专用:不求面面俱到,但求有所专长。
人们通常将DSL分为两类:内部DSL和外部DSL。外部DSL脱离程序源码编写,然后单独解析和实现。比如级联样式表(CSS)和正则表达式,就是常用的外部DSL。内部DSL嵌入编写他们的编程语言中。如果读者使用过JMock和Mockito等模拟类库,或用过SQL构建API,如JOOQ或Querydsl,那么就知道什么是内部DSL。
BDD:行为驱动开发,DBB DSL:LambdaBehave。
BDD是测试驱动开发(TDD)的一个变种,它的重点是描述程序的行为,而非一组需要通过的单元测试。
灵感来源Jasmine(JavaScript BDD)。
- 每一个规则描述了程序的一种行为;
- 期望是描述行为的一种方式,在规则中定义;
- 对个规则合在一起,形成一个套件。
这些概念在传统的测试框架,比如Junit,规则对应一个测试方法,期望对应断言,套件对应一个测试类。
使用Java编写DSL
public class StackSpec {{
describe("a stack", it -& {
it.should("be empty when created", expect -& {
expect.that(new Stack()).isEmpty();
it.should("push new elements onto the top of the stack", expect -& {
Stack&Integer& stack = new Stack&&();
stack.push(1);
expect.that(stack.get(0)).isEqualTo(1);
it.should("pop the last element pushed onto the stack", expect -& {
Stack&Integer& stack = new Stack&&();
stack.push(2);
stack.push(1);
expect.that(stack.pop()).isEqualTo(2);
描述行为首先看到的是describe这个动词,简单导入一个静态方法就够了。Description类就是我们定义的DSL中的it。
public static void describe(String name, Suite behavior) {
Description description = new Description(name);
behavior.specifySuite(description);
每个套件的规则描述由用户使用一个Lambda表达式实现,因此我们需要一个Suite函数接口表示规则组成的套件。
public interface Suite {
public void specifySuite(Description description);
public interface Specification {
public void specifyBehaviour(Expect expect);
我们希望用户可以使用it.should命名他们的规则,这就是说Description类需要有一个should方法。
public void should(String description, Specification specification) {
Except except = new Except();
specification.specifyBehaviour(except);
Runner.current.recordSuccess(suite, description);
} catch (AssertionError cause) {
Runner.current.recordFailure(suite, description, cause);
} catch (Throwable cause) {
Runner.current.recordFailure(suite, description, cause);
规则通过expect.that描述期望的行为,也就是说Expect类需要一个that方法供用户调用。
public final class Expect {
public BoundException that(Object value) {
return new BoundException(value);
// 匿名构造函数,可以执行任意的Java代码块
public class StackSpec {
使用Lambda表达式的SOLID原则
Single responsibility
Open/closed
Liskov substitution
Interface segregation
Dependency inversion
单一功能原则
程序中的类或方法只能有一个改变的理由。这是强内聚性设计的一部分,Lambda表达式在方法级别更容易实现单一功能原则。
线程模型也是代码的职责之一
public long countPrimes(int upTo) {
return IntStream.range(1, upTo)
.parallel()
.filter(this::isPrime)
public boolean isPrime(int number) {
return IntStream.range(2, number)
.allMatch(x -& (number % x) != 0);
软件应该对扩展开放,对修改闭合。
首要目标与单一功能原则类似,让软件易于修改。
不改变实现如何扩展一个类的功能?答案是借助于抽象,可插入新的功能。
ThreadLocal有一个特殊的变量,每个线程都有一个该变量的副本并与之交互。该类的静态方法withInitial是一个高阶函数,传入一个负责生成初始值的Lambda表达式。
这符合开闭原则,因为不用修改ThreadLocal类,就能得到新的行为。给withInitial方法传入不同的工厂方法,就能得到永远不同行为的ThreadLocal实例。
// ThreadLocal日期格式化器,线程安全
ThreadLocal&DataForamt& localFormatter = ThreadLocal.withInitial(() -& new SimpleDataFormat());
int idForThisThread = localId.get();
开闭原则有另外一种和传统思维不同,那就是使用不可变对象实现开闭原则。不可变是指一经创建就不能改变的对象。
- 观测不可变性:在其他对象看来,该类是不可变的
- 实现不可变性:是指对象本身不可变。实现不可变性意味着观测不可变性,反之则不一定成立。
java.lang.String, 只是观测不可变,因为他在第一次调用hashCode方法是缓存了生成的散列值。在其他类看来,这是完全安全的,他们看不出散列值是在每次构造函数中计算出来的,还是从缓存中返回的。
我们说不可变对象实现了开闭原则,是因为他们的内部状态无法改变,可以安全地为其增加新的方法。新增的方法无法改变对象的内部状态,因此对修改是闭合的;但他们又增加了新的行为,因此对扩展是开放的。当然,你还需要留意不要改变程序其他部分的状态。
因其天生线程安全的特性,不可变对象引起了人们的格外注意,他们没有内部状态可变,因此可以安全的在不同线程之间共享。
本质可以看做多态来实现开闭原则。
依赖反转原则
抽象不应依赖细节,细节应该依赖抽象。
让程序变得死板、脆弱,难于改变的方法之一是将上层业务逻辑和底层粘合模块的代码混在一起,因为这两样东西都会随着时间发生变化。
依赖反转原则的目的是让程序员脱离底层粘合代码,编写上层业务逻辑代码。这样就让上层代码依赖于底层细节的抽象。
public List&String& findHeadings(Reader input) {
return withLinesOf(input,
lines -& lines.filter(line -& line.endWith(":"))
.map(line -& line.substring(0, line.length() - 1))
.collect(toList)),
HeadingLookupException::new);
private &T& T withLineOf(Reader input, Function&Stream&String&, T& handler, Fucntion&IOException, RuntimeException& error) {
try (BufferedReader reader = new BufferedReader(input)) {
return handler.apply(reader.lines());
} catch (IOException e) {
throw error.apply(e);
总结下来,高阶函数提供了反转控制,这就是依赖反转的一种形式,可以很容易地和Lambda表达式一起使用。依赖反转原则另外值得注意的一点是待依赖的抽象不必是接口。这里我们使用Stream对原始的Reader和文件处理做抽象,这种方式也适用于函数式编程语言中的资源管理—通常使用高阶函数管理资源,接受一个回调函数使用打开的资源,然后再关闭资源。
事实上,如果Java7就要Lambda表达式,那么Java7中的try-with-resources功能可能只需要一个库函数就能实现。
使用Lambda表达式编写并发程序
在处理大量数据时,并行化处理并不是唯一可用的线层模型。
非阻塞式I/O,也叫异步I/O,可以处理大量并发网络连接,而且一个县城可以为多个连接服务。
在设计里不共享任何状态
为了确保不再verticle对象之间共享状态,我们对事件总线上传递的消息做了某些限制。String是天生不可变的,但是Buffer是可变的,如果使用不当,消息发送者和接受者都可以通过读写消息共享状态。
Vert.x通过在发送消息时复制消息的方式来避免这种问题。这保证了接受者得到了正确的结果,又不会共享状态。
不可变可以解决,复制也可以解决状态问题。
基于消息传递的系统让隔离错误变得简单,也便于编写可靠的代码。如果一个消息处理程序发生错误,可以选择重启本地verticle对象,而不用去重启整个JVM。
使用Lambda表达式表示行为,构建API来管理并发。
末日金字塔
构建 复杂并行操作的另外一种方案是使用Future。Future像一张欠条,方法不是返回一个值,而是返回一个Future对象,该对象第一次创建时没有值,但以后能拿它“换回”一个值;
调用Future对象的get方法取值会阻塞当前线程直到返回一个值。
我们真正需要的是不必调用get方法阻塞当前线程,就能操作Future对象返回的结果。
CompletableFuture
结合了Future对象打欠条的主意和使用回调处理事件驱动的任务。其要点是可以组合不同的实例,而不用担心末日金字塔问题。
在其他语言中这被叫做延迟对象或约定。在Google Guava类库和Spring框架中,这被叫做ListenableFutures.
public Album lookupByName(String albumName) {
CompletableFuture&List&Artist&& artistLookup
= loginTo("artist")
.thenCompose(trackLogin -& lookupArtists(albumName, artistLogin));
return loginTo("track")
.thenCompose(trackLogin -& lookupTracks(albumName, trackLogin))
.thenCombine(artistLookup, (tracks, artist) -& new Album(albumName, tracks, artists))
创建CompletableFuture对象分两部分:创建对象和传给它欠客户代码的值。
// 异步创建CompletableFuture&Track& lookupTrack(String id) {
return CompletableFuture.supplyAsync( () -& {
// 这里会做一些繁重的工作
return track;
}, services)
supplyAsync方法接受一个Supplier对象作为参数,然后执行它,提供一个叫做service的Executor,告诉CompletableFuture对象在哪里执行任务。如果没有提供Executor,就会使用相同的fork/join线程池并行执行。
碰上异常,CompletableFuture提供了completeExceptionally,用于处理异常情况,该方法可以视为complete方法的备选项,但不能同时调用complete和completeExceptionally方法。
future.completeExceptionally(new AlbumLookupException("Unable to find " + name));
CompletableFuture接口有很多有用的方法,可以各种组合使用。
用例(Use Cases)
在链的末端执行一些代码而不返回任何值,比如Consumer和Runnable,那就看看thenAccept和thenRun方法。
可使用thenApply方法转换CompletableFuture对象的值,有点像使用Stream的map方法。
在CompletableFuture对象出现异常时,可使用exceptionally方法修复,可以将一个函数注册到该方法,返回一个替代值。
如果你想要一个map,包含异常情况和正常情况,请使用handle方法。
要找出CompletableFuture对象到底出了什么问题,可使用isDone和isCompletedExceptionally方法辅助调查。
响应式编程
CompletableFuture 背后的概念可以从单一的返回值推广到数据流,这就是响应式编程。
RxJava类库引入一个叫做Observable的类,代表一组待相应的时间,可理解为一沓欠条。
和Stream接口很像,都需要使用Lambda表达式将行为和一般的操作关联、都需要将高阶函数链接起来定义完成任务的规则。相同的操作map、filter、reduce。
最大的不同在于用例。Stream是为构建内存中集合的计算流程而设计的,而RxJava则是为了组合异步和基于事件的系统流程而设计的。它没有取数据,而是把数据放进去。换个角度理解RxJava,它是处理一组值,而CompletableFuture来处理一个值。
Stream为了计算出最终的结果,RxJava在线程模型上像CompletableFuture。
使用CompletableFuture时,我们通过给complete方法一个值来偿还欠条。而Observable代表了一个事件流,我们需要有能力传入多个值。
Apache Camel已经加入了一个叫做Camel RX的模块,可以使用RxJava。
Vert.x项目也启动了一个Rxify他的API项目。
何时何地使用新技术
CompletableFuture和RxJava相对较新,使用它们仍然有一定的复杂度。
对很多问题来说,传统的阻塞式Web应用开发技术就足够了。
如果还能用,就别修理。
事件驱动和响应式应用正在变得越来越流行,而且经常会是为你的问题建模建模的最好方式之一。
两种情况适合:
业务逻辑本身就是有事件来描述,Twitter,订阅文字流信息,用户彼此之间推送消息。图形化展示股票价格,每次变价都可认为是一个事件。
另一种用例是同时处理大量I/O操作。阻塞式需要使用大量线程,导致大量锁之间的竞争和太多上下文切换。处理成千上万的连接,非阻塞更好。
亲不尊,熟生蔑
没有更多推荐了,

我要回帖

更多关于 创建新变量 的文章

 

随机推荐