欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java8之lambda最佳实践_动力节点Java学院整理

程序员文章站 2024-02-15 12:57:53
在8 里面lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来了函数式编程的思想,我觉得优秀的程序员,有必要学习一下函数式编程的思想以开阔思路。所以这篇文章聊聊...

在8 里面lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来了函数式编程的思想,我觉得优秀的程序员,有必要学习一下函数式编程的思想以开阔思路。所以这篇文章聊聊lambda的应用场景,性能,也会提及下不好的一面。

java为何需要lambda

1996年1月,java 1.0发布了,此后计算机编程领域发生了翻天覆地的变化。商业发展需要更复杂的应用,大多数程序都跑在更强大的装备多核cpu的机器上。带有高效运行期编译器的java虚拟机(jvm)的出现,使得程序员将精力更多放在编写干净、易于维护的代码上,而不是思考如何将每一个cpu时钟、每一字节内存物尽其用。

多核cpu的出现成了“房间里的大象”,无法忽视却没人愿意正视。算法中引入锁不但容易出错,而且消耗时间。人们开发了java.util.concurrent包和很多第三方类库,试图将并发抽象化,用以帮助程序员写出在多核cpu上运行良好的程序。不幸的是,到目前为止,我们走得还不够远。

那些类库的开发者使用java时,发现抽象的级别还不够。处理大数据就是个很好的例子,面对大数据,java还欠缺高效的并行操作。java 8允许开发者编写复杂的集合处理算法,只需要简单修改一个方法,就能让代码在多核cpu上高效运行。为了编写并行处理这些大数据的类库,需要在语言层面上修改现有的java:增加lambda表达式。

当然,这样做是有代价的,程序员必须学习如何编写和阅读包含lambda表达式的代码,但是,这不是一桩赔本的买卖。与手写一大段复杂的、线程安全的代码相比,学习一点新语法和一些新习惯容易很多。开发企业级应用时,好的类库和框架极大地降低了开发时间和成本,也扫清了开发易用且高效的类库的障碍。

lambda的应用场景

下面我将重点放在函数式编程的实用性上,包括那些可以被大多数程序员理解和使用的技术,我们关心的如何写出好代码,而不是符合函数编程风格的代码。

1.1.1 1.使用() -> {} 替代匿名类

现在runnable线程,swing,javafx的事件监听器代码等,在java 8中你可以使用lambda表达式替代丑陋的匿名类。

//before java 8:
new thread(new runnable() {
 @override
 public void run() {
  system.out.println("before java8 ");
 }
}).start();
//java 8 way:
new thread(() -> system.out.println("in java8!"));
// before java 8:
jbutton show = new jbutton("show");
show.addactionlistener(new actionlistener() {
  @override
  public void actionperformed(actionevent e) {
   system.out.println("without lambda expression is boring");
  }
  });
// java 8 way:
show.addactionlistener((e) -> {
 system.out.println("action !! lambda expressions rocks");
});

使用内循环替代外循环

外循环:描述怎么干,代码里嵌套2个以上的for循环的都比较难读懂;只能顺序处理list中的元素;

内循环:描述要干什么,而不是怎么干;不一定需要顺序处理list中的元素

//prior java 8 :
list features = arrays.aslist("lambdas", "default method", 
"stream api", "date and time api");
for (string feature : features) {
 system.out.println(feature);
}
//in java 8:
list features = arrays.aslist("lambdas", "default method", "stream api",
 "date and time api");
features.foreach(n -> system.out.println(n));
// even better use method reference feature of java 8
// method reference is denoted by :: (double colon) operator
// looks similar to score resolution operator of c++
features.foreach(system.out::println);

output:
lambdas
default method
stream api
date and time api

支持函数编程

为了支持函数编程,java 8加入了一个新的包java.util.function,其中有一个接口java.util.function.predicate是支持lambda函数编程:

public static void main(args[]){
 list languages = arrays.aslist("java", "scala", "c++", "haskell", "lisp");
 system.out.println("languages which starts with j :");
 filter(languages, (str)->str.startswith("j"));
 system.out.println("languages which ends with a ");
 filter(languages, (str)->str.endswith("a"));
 system.out.println("print all languages :");
 filter(languages, (str)->true);
 system.out.println("print no language : ");
 filter(languages, (str)->false);
 system.out.println("print language whose length greater than 4:");
 filter(languages, (str)->str.length() > 4);
}
 public static void filter(list names, predicate condition) {
 names.stream().filter((name) -> (condition.test(name)))
  .foreach((name) -> {system.out.println(name + " ");
 });
 }
output:
languages which starts with j :
java
languages which ends with a
java
scala
print all languages :
java
scala
c++
haskell
lisp
print no language :
print language whose length greater than 4:
scala
haskell

处理数据?用管道的方式更加简洁

java 8里面新增的stream api ,让集合中的数据处理起来更加方便,性能更高,可读性更好

假设一个业务场景:对于20元以上的商品,进行9折处理,最后得到这些商品的折后价格。

final bigdecimal totalofdiscountedprices = prices.stream()
.filter(price -> price.compareto(bigdecimal.valueof(20)) > 0)
.map(price -> price.multiply(bigdecimal.valueof(0.9)))
.reduce(bigdecimal.zero,bigdecimal::add);
system.out.println("total of discounted prices: " + totalofdiscountedprices);

想象一下:如果用面向对象处理这些数据,需要多少行?多少次循环?需要声明多少个中间变量?

lambda的阴暗面

前面都是讲lambda如何改变java程序员的思维习惯,但lambda确实也带来了困惑

jvm可以执行任何语言编写的代码,只要它们能编译成字节码,字节码自身是充分oo的,被设计成接近于java语言,这意味着java被编译成的字节码非常容易被重新组装。

但是如果不是java语言,差距将越来越大,scala源码和被编译成的字节码之间巨大差距是一个证明,编译器加入了大量合成类 方法和变量,以便让jvm按照语言自身特定语法和流程控制执行。

我们首先看看java 6/7中的一个传统方法案例:

// simple check against empty strings
public static int check(string s) {
 if (s.equals("")) {
  throw new illegalargumentexception();
 }
 return s.length();
}
//map names to lengths
list lengths = new arraylist();
for (string name : arrays.aslist(args)) {
 lengths.add(check(name));
}

如果一个空的字符串传入,这段代码将抛出错误,堆栈跟踪如下:

at lmbdamain.check(lmbdamain.java:19)
at lmbdamain.main(lmbdamain.java:34)

再看看lambda的例子

stream lengths = names.stream().map(name -> check(name));
 at lmbdamain.check(lmbdamain.java:19)
at lmbdamain.lambda$0(lmbdamain.java:37)
at lmbdamain$$lambda$1/821270929.apply(unknown source)
at java.util.stream.referencepipeline$3$1.accept(referencepipeline.java:193)
at java.util.spliterators$arrayspliterator.foreachremaining(spliterators.java:948)
at java.util.stream.abstractpipeline.copyinto(abstractpipeline.java:512)
at java.util.stream.abstractpipeline.wrapandcopyinto(abstractpipeline.java:502)
at java.util.stream.reduceops$reduceop.evaluatesequential(reduceops.java:708)
at java.util.stream.abstractpipeline.evaluate(abstractpipeline.java:234)
at java.util.stream.longpipeline.reduce(longpipeline.java:438)
at java.util.stream.longpipeline.sum(longpipeline.java:396)
at java.util.stream.referencepipeline.count(referencepipeline.java:526)
at lmbdamain.main(lmbdamain.java:39)

 这非常类似scala,出错栈信息太长,我们为代码的精简付出力代价,更精确的代码意味着更复杂的调试。
但这并不影响我们喜欢lambda!

总结

在java世界里面,面向对象还是主流思想,对于习惯了面向对象编程的开发者来说,抽象的概念并不陌生。面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。

这种新的抽象方式还有其他好处。很多人不总是在编写性能优先的代码,对于这些人来说,函数式编程带来的好处尤为明显。程序员能编写出更容易阅读的代码——这种代码更多地表达了业务逻辑,而不是从机制上如何实现。易读的代码也易于维护、更可靠、更不容易出错。

在写回调函数和事件处理器时,程序员不必再纠缠于匿名内部类的冗繁和可读性,函数式编程让事件处理系统变得更加简单。能将函数方便地传递也让编写惰性代码变得容易,只有在真正需要的时候,才初始化变量的值。
总而言之,java更趋于完美了。