简化你的Java代码,让工作更高效|语言

计算机专家在问题求解时非常重视表达式简洁性的价值。Unix的先驱者Ken Thompson曾经说过非常著名的一句话:“丢弃1000行代码的那一天是我最有成效的一天之一。”这对于任何一个需要持续支持和维护的软件项目来说,都是一个当之无愧的目标。早期的Lisp贡献者Paul Graham甚至将语言的简洁性等同为语言的能力。这种对能力的认识让可以编写紧凑、简介的代码成为许多现代软件项目选择语言的首要标准。

任何程序都可以通过重构,去除多余的代码或无用的占位符,如空格,变得更加简短,不过某些语言天生就善于表达,也就特别适合于简短程序的编写。认识到这一点之后,Perl程序员普及了代码高尔夫竞赛;其目标是用尽可能短的代码量解决某一特定的问题或者实现某个指定的算法。APL语言的设计理念是利用特殊的图形符号让程序员用很少量的代码就可以编写功能强大的程序。这类程序如果实现得当,可以很好地映射成标准的数学表达式。简洁的语言在快速创建小脚本时非常高效,特别是在目的不会被简洁所掩盖的简洁明确的问题域中。

相比于其他程序设计语言,Java语言的冗长已经名声在外。其主要原因是由于程序开发社区中所形成的惯例,在完成任务时,很多情况下,要更大程度地考虑描述性和控制。例如,长期来看,长变量名会让大型代码库的可读性和可维护性更强。描述性的类名通常会映射为文件名,在向已有系统中增加新功能时,会显得很清晰。如果能够一直坚持下去,描述性名称可以极大简化用于表明应用中某一特定的功能的文本搜索。这些实践让Java在大型复杂代码库的大规模实现中取得了极大的成功。

对于小型项目来说,简洁性则更受青睐,某些语言非常适于短脚本编写或者在命令提示符下的交互式探索编程。Java作为通用性语言,则更适用于编写跨平台的工具。在这种情况下,“冗长Java”的使用并不一定能够带来额外的价值。虽然在变量命名等方面,代码风格可以改变,不过从历史情况来看,在一些基本的层面上,与其他语言相比,完成同样的任务,Java语言仍需更多的字符。为了应对这些限制,Java语言一直在不断地更新,以包含一些通常称为“语法糖”的功能。用这些习语可以实现更少的字符表示相同功能的目标。与其对应的更加冗长的配对物相比,这些习语更受程序开发社区的欢迎,通常会被社区作为通用用法快速地采用。

本文将着重介绍编写简洁Java代码的最佳实践,特别是关于JDK8中新增的功能。简而言之,Java 8中Lambda表达式的引入让更加优雅的代码成为可能。这在用新的Java Streaming API处理集合时尤其明显。

冗长的Java

Java代码冗长之所以名声在外,一部分原因是由于其面向对象的实现风格。在许多语言中,只需要一行包含不超过20个字符的代码就可以实现经典的“Hello World”程序示例。而在Java中,除了需要类定义中所包含的main方法之外,在main方法中还需要包含一个方法调用,通过System.out.println()将字符串打印到终端。即使在使用最少的方法限定词、括号和分号,并且将所有空格全都删除的极限情况下,“Hello World”程序最少也需要86个字符。为了提高可读性,再加上空格和缩进,毋庸置疑,Java版的“Hello World”程序给人的第一印象就是冗长。

Java代码冗长一部分原因还应归咎于Java社区将描述性而非简洁性作为其标准。就这一点而言,选择与代码格式美学相关的不同标准是无关紧要的。此外,样板代码的方法和区段可以包含在整合到API中的方法中。无需牺牲准确性或清晰度,着眼于简洁性的程序代码重构可以大大简化冗余Java代码。

有些情况下,Java代码冗长之所以名声在外是由于大量的老旧代码示例所带来的错觉。许多关于Java的书籍写于多年之前。由于在整个万维网最初兴起时,Java便已经存在,许多Java的在线资源所提供的代码片段都源自于Java语言最早的版本。随着时间的推移,一些可见的问题和不足不断得到完善,Java语言也日趋成熟,这也就导致即使十分准确并实施的当的案例,可能也未能有效利用后来的语言习语和API。

Java的设计目标包括面向对象、易于上手(在当时,这意味着使用C++格式的语法),健壮、安全、可移植、多线程以及高性能。简洁并非其中之一。相比于用面向对象语法实现的任务,函数式语言所提供的替代方案要简洁的多。Java 8中新增的Lambda表达式改变了Java的表现形式,减少了执行许多通用任务所需的代码数量,为Java开启了函数式编程习语的大门。

函数式编程

函数式编程将函数作为程序开发人员的核心结构。开发人员可以以一种非常灵活的方式使用函数,例如将其作为参数传递。利用Lambda表达式的这种能力,Java可以将函数作为方法的参数,或者将代码作为数据。Lambda表达式可以看作是一个与任何特定的类都无关的匿名方法。这些理念有着非常丰富多彩并且引人入胜的数学基础。

函数式编程和Lambda表达式仍然是比较抽象、深奥的概念。对于开发人员来说,主要关注如何解决实际生产中的任务,对于跟踪最新的计算趋势可能并不感兴趣。随着Lambda表达式在Java中的引入,对于开发人员来说对这些新特性的了解至少需要能够达到可以读懂其他开发人员所编写代码的程度。这些新特性还能带来实际的好处——可以影响并发系统的设计,使其拥有更优的性能。而本文所关心的是如何利用这些机制编写简洁而又清晰的代码。

之所以能够用Lambda表达式生成简洁的代码,有如下几个原因。局部变量的使用量减少,因此声明和赋值的代码也随之减少。循环被方法调用所替代,从而将三行以上的代码缩减为一行。本来在嵌套循环和条件语句中的代码现在可以放置于一个单独的方法中。实现连贯接口,可以将方法以类似于Unix管道的方式链接在一起。以函数式的风格编写代码的净效应并不只限于可读性。此类代码可以避免状态维护并且不会产生副作用。这种代码还能够产生易于并行化,提高处理效率的额外收益。

Lambda表达式

与Lambda表达式相关的语法比较简单直白,不过又有别于Java之前版本的习语。一个Lambda表达式由三部分组成,参数列表、箭头和主体。参数列表可以包含也可以不包含括号。此外还新增了由双冒号组成的相关操作符,可以进一步缩减某些特定的Lambda表达式所需的代码量。这又称为方法引用。

线程创建

在这个示例中,将会创建并运行一个线程。Lambda表达式出现在赋值操作符的右侧,指定了一个空的参数列表,以及当线程运行时写到标准输出的简单的消息输出。

处理集合

Lambda表达式的出现会被开发人员注意到的首要位置之一就是与集合API相关。假设我们需要将一个字符串列表根据长度排序。

java.util.List<String> l;
l= java.util.Arrays.asList
(new String[]
{"aaa", "b", "cccc", "DD"});

可以创建一个Lambda表达式实现此功能。

java.util.Collections.sort
(l, (s1, s2) ->new Integer(s1.length()).compareTo(s2.length())

这个示例中包含两个传递给Lambda表达式体的参数,以比较这两个参数的长度。

除此之外还有许多替代方案,在无需使用标准的“for”或“while”循环的前提下,就可以操作列表中的各个元素。通过向集合的“forEach”方法传入Lambda表达式也可以完成用于比较的语义。这种情况下,只有一个参数传入,也就无需使用括号。

这个特殊的示例还可以通过使用方法引用将包含类和静态方法分开的方式进一步减少代码量。每个元素都会按顺序传入println方法。

l.forEach(System.out::println)

java.util.stream是在Java 8中新引入的包,以函数式程序开发人员所熟悉的语法处理集合。在包的摘要中对包中的内容解释如下:“为流元素的函数式操作提供支持的类,如对集合的map-reduce转换。”

下方的类图提供了对该包的一个概览,着重介绍了接下来的示例中将要用到的功能。包结构中列示了大量的Builder类。这些类与连贯接口一样,可以将方法链接成为管道式的操作集。

字符串解析和集合处理虽然简单,在真实世界中仍有许多实际应用场景。在进行自然语言处理(NLP)时,需要将句子分割为单独的词。生物信息学将DNA和RNA表示为有字母组成的碱基,如C,G,A,T或U。在每个问题领域中,字符串对象会被分解,然后针对其各个组成部分进行操作、过滤、计数以及排序等操作。因此,尽管示例中所包含的用例十分简单,其理念仍适用于各类有实际意义的任务。

下方的示例代码解析了一个包含一个句子的字符串对象,并统计单词的数量和感兴趣的字母。包括空白行在内,整个代码清单的行数不超过70行。

  1. import java.util.*;
    2.3. import static
    java.util.Arrays.asList;
  2. import static java.util.function.
    Function.identity;
  3. import static java.util.
    stream.Collectors.*;
    1. public class Main
      {8. 9.public static void p(String s)
      {10. System.out.println
      (s.replaceAll("[\]\[]", ""));11.}
      12.13.private static
      List uniq(List letters)
      {14.return new ArrayList
      (new HashSet(letters));15.}
      16.17.private static
      List sort(List letters)
      {18.return letters.stream
      ().sorted().collect(toList());19.}
      20.21.private staticMap uniqueCount
      (List letters)
      {22.return letters.stream()
      .23. collect(groupingBy(identity
      (), counting()));24.}
      25.26.private static String
      getWordsLongerThan
      (int length, List words)
      {27.return String.join(" | ",
      words28. .stream().filter(w ->
      w.length() > length)
  4. .collect(toList())30. );
  5. }32.33.private static String
    getWordLengthsLongerThan
    (int length, List words)
  6. {35. return String.join(" | ",
    words36..stream().filter
    (w -> w.length() > length)
    37..mapToInt(String::length)
    38..mapToObj(n -> String.format
    ("%" + n + "s", n))
    39..collect(toList()));
    40.}41.42. public static void main
    (String[] args) {43.44. String
    s = "The quick brown fox jumped
    over the lazy dog.";
    45 String sentence = s.toLowerCase().
    replaceAll("[^a-z ]", "");
    46.47 List words = asList(sentence.split(" "));
  7. List letters = asList(sentence.split(""));
    49.50. p("Sentence : " + sentence);
    51 p("Words : " + words.size());
  8. p("Letters : " + letters.size());
    53.54. p("\nLetters : " + letters);
  9. p("Sorted : " + sort(letters));
  10. p("Unique : " + uniq(letters));
    57.58. Map m = uniqueCount(letters);
    59 p("\nCounts");
    60.61. p("letters");
  11. p(m.keySet().toString().replace(",", ""));
  12. p(m.values().toString().replace(",", ""));
    64.65. p("\nwords");
  13. p(getWordsLongerThan(3, words));
  14. p(getWordLengthsLongerThan(3, words));
  15. }69. }

示例程序执行输出:

Sentence : the quick brown fox
jumped over the lazy dog
Words : 9Letters : 44Letters :
t, h, e, , q, u, i, c, k, , b, r, o,
w, n, , f, o, x, , j, u, m, p, e, d,
, o, v, e, r, , t, h, e, , l, a, z,
y, , d, o, g
Sorted : , , , , , , , ,
a, b, c, d, d, e, e, e, e, f, g, h, h,
i, j, k, l, m, n, o, o, o, o, p, q, r,
r, t, t, u, u, v, w, x, y, z
Unique : , a, b, c, d, e, f, g, h, i,
j, k, l, m, n, o, p, q, r,
t, u, v, w, x, y, z
Counts
letters
a b c d e f g h i j k l m n
o p q r t u v w x y z8
1 1 1 2 4 1 1 2 1 1 1 1 1 1 4
1 1 2 2 2 1 1 1 1
1wordsquick | brown | jumped |
over | lazy
5 | 5 | 6 | 4 | 4

上述代码已经经过了多重精简。其中一些方式并非在各个版本的Java中都可行,而且有些方式可能并不符合公认的编码风格指南。思考一下在较早版本的Java中如何才能够获得相同的输出?首先,需要创建许多局部变量用于临时存储数据或作为索引。其次,需要通过许多条件语句和循环告知Java如何处理数据。新的函数式编程方式更加专注于需要什么数据,而并不关心与其相关的临时变量、嵌套循环、索引管理或条件语句的处理。

在某些情况下,采用早期版本中的标准Java语法以减少代码量是以牺牲清晰度为代价的。例如,示例代码第一行的标准import语句中的Java包引用了java.util下的所有类,而不是根据类名分别引用。对System.out.println的调用被替换为对一个名为p的方法的调用,这样在每次方法调用时都可以使用短名称(行9-11)。由于可能违反某些Java的编码规范,这些改变富有争议,不过有着其他背景的程序开发人员查看这些代码时可能并不会有何问题。

另外一些情况下,则利用了从JDK8预览版才新增的功能特性。静态引用(行3-5)可以减少内联所需引用的类的数量。而正则表达式(行10,45)则可以用与函数式编程本身无关的方式,有效隐藏循环和条件语句。这些习语,特别是正则表达式的使用,经常会因为难以阅读和说明而受到质疑。如果运用得当,这些习语可以减少噪音的数量,并且能够限制开发人员需要阅读和说明的代码数量。

最后,示例代码利用了JDK 8中新增的Streaming API。使用了Streaming API中大量的方法对列表进行过滤、分组和处理(行17-40)。尽管在IDE中它们与内附类的关联关系很清晰,不过除非你已经很熟悉这些API,否则这种关系并不是那么显而易见。下表展示了示例代码中所出现的每一次方法调用的来源。

uniq()(行13)和sort()(行17)方法体现了同名的Unix实用工具的功能。sort引入了对流的第一次调用,首先对流进行排序,然后再将排序后的结果收集到列表中。UniqueCount()(行21)与uniq -c类似,返回一个map对象,其中每个键是一个字符,每个值则是这个字符出现次数的统计。两个“getWords”方法(行26和行33)用于过滤出比给定长度短的单词。getWordLengthsLongerThan()方法调用了一些额外的方法,将结果格式化并转换成不可修改的String对象。

整段代码并未引入任何与Lambda表达式相关的新概念。之前所介绍的语法只适用于Java Stream API特定的使用场景。

总结

用更少的代码实现同样任务的理念与爱因斯坦的理念一致:“必须尽可能地简洁明了,但又不能简单地被简单。”Lambda表达式和新的Stream API因其能够实现扩展性良好的简洁代码而备受关注。它们让程序开发人员可以恰当地将代码简化成最好的表现形式。

函数式编程习语的设计理念就是简短,而且仔细思考一下就会发现许多可以让Java代码更加精简的场景。新的语法虽然有点陌生但并非十分复杂。这些新的功能特性清晰地表明,作为一种语言,Java已经远远超越其最初的目标。它正在用开放的态度接受其他程序设计语言中最出色的一些功能,并将它们整合到Java之中。

喜欢小编轻轻点个关注吧!

原文地址:http://blog.51cto.com/13952975/2298437

时间: 2024-10-29 19:53:22

简化你的Java代码,让工作更高效|语言的相关文章

让JAVA代码跑得更快

本文简单介绍一下在写代码过程中用到的一些让JAVA代码更高效的技巧. 1.   将一些系统资源放在池中(如数据库连接, 线程等) 在standalone的应用中, 数据库连接池可以使用一些开源的连接池实现, 如C3P0, proxool和 DBCP等,运行在容器中的应用可以使用服务器提供的DataSource.线程池可以使用JDK本身就提供的java.util.concurrent.ExecutorService. 1 import java.util.concurrent.Executors;

如何写出更好的Java代码

Java是最流行的编程语言之一,但似乎并没有人喜欢使用它.好吧,实际上Java是一门还不错的编程语言,由于最近Java 8发布了,我决定来编辑一个如何能更好地使用Java的列表,这里面包括一些库,实践技巧以及工具. 这篇文章在GitHub上也有.你可以随时在上面贡献或者添加你自己的Java使用技巧或者最佳实践. 编码风格 结构体 builder模式 依赖注入 避免null值 不可变 避免过多的工具类 格式 文档 Stream 部署 框架 Maven 依赖收敛 持续集成 Maven仓储 配置管理

lombok 简化java代码注解 理解

lombok 注解: lombok 提供的注解不多,可以参考官方视频的讲解和官方文档.     Lombok 注解在线帮助文档:http://projectlombok.org/features/index.    下面介绍几个我常用的 lombok 注解:         @Data   :注解在类上:提供类所有属性的 getting 和 setting 方法,此外还提供了equals.canEqual.hashCode.toString 方法         @Setter:注解在属性上:为

Lombok简化Java代码

导包:import lombok.Data; Lombok简化Java代码: 在Lombok中,生成构造方法的annotation一共有三个:@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsContructor.使用这三个annotation来完成项目中对于不同构造方法的需求.@NoArgsConstructor : 生成一个无参数的构造方法,这个annotation在与其他的annotation配合起来使用的时候更加能凸显出他的重要性

如何更规范化编写Java 代码

如何更规范化编写Java 代码 Many of the happiest people are those who own the least. But are we really so happy with our IPhones, our big houses, our fancy cars? 忘川如斯,拥有一切的人才更怕失去. 背景:如何更规范化编写Java 代码的重要性想必毋需多言,其中最重要的几点当属提高代码性能.使代码远离Bug.令代码更优雅. 一.MyBatis 不要为了多个查询条

使用 Lombok 简化项目中无谓的Java代码

在写使用Java时,难免会有一些模板代码要写,不然get/set,toString, hashCode, close 资源,定义构造函数等等.代码会显得很冗余,很长.Lombok项目可以是我们摆脱这些东西,通过一系列的注解,Lombok可以帮我们自动生成这些函数. Lombok 官网地址:https://projectlombok.org/ 参考文档:https://projectlombok.org/features/index.html 1. 安装 到官网下载 lombok.jar,直接双击

java gc的工作原理、如何优化GC的性能、如何和GC进行有效的交互

java gc的工作原理.如何优化GC的性能.如何和GC进行有效的交互 一个优秀的Java 程序员必须了解GC 的工作原理.如何优化GC的性能.如何和GC进行有效的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等.只有全面提升内存的管理效 率,才能提高整个应用程序的性能. 本篇文章首先简单介绍GC的工作原理,然后再对GC的几个关键问题进行深入探讨,最后提出一些Java程序设计建议,从GC角度提高Java程序的性能. GC的基本原理     Java 的内存管理实际上就是对象的管

只用120行Java代码写一个自己的区块链

区块链是目前最热门的话题,广大读者都听说过比特币,或许还有智能合约,相信大家都非常想了解这一切是如何工作的.这篇文章就是帮助你使用 Java 语言来实现一个简单的区块链,用不到 120 行代码来揭示区块链的原理! “用不到120行 Java 代码就能实现一个自己的区块链!” 听起来不可思议吧?有什么能比开发一个自己的区块链更好的学习实践方法呢?那我们就一起来实践下! 因为我们是一家从事互联网金融的科技公司,所以我们采用虚拟资产金额作为这篇文章中的示例数据.大家可以先为自己想一个数字,后面我们会用

夯实Java基础系列7:一文读懂Java 代码块和执行顺序

目录 Java中的构造方法 构造方法简介 构造方法实例 例 1 例 2 Java中的几种构造方法详解 普通构造方法 默认构造方法 重载构造方法 java子类构造方法调用父类构造方法 Java中的代码块简介 Java代码块使用 局部代码块 构造代码块 静态代码块 Java代码块.构造方法(包含继承关系)的执行顺序 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github