澳门新葡萄京官网首页 1

Java工具类与函数编程毫不相干

最近,由于我把工具类看作反模式,所以被指责反对函数式编程。这是绝对错误的!我认为它们是很糟糕的反模式,因为他们与函数式编程无关。我认为其中有两个基本原因。首先,函数式编程是可声明的,然而工具类方法是命令式的。第二,函数式编程是基于lambda演算,即被传递参数的函数。从这个意义上来说,工具类方法不是函数。我会用一点时间来解释一下。

编程范式

详述三种现代JVM语言–Groovy,Scala和Clojure – 推酷
http://www.tuicool.com/articles/jYzuAv

在Java中,基本上有两种被Guava、Apache
Commons和其它开发库推荐使用的拙劣的工具类。第一种是使用传统的类,第二种就是Java
8的lambda。现在让我们看看为什么工具类和函数式编程关系不大,以及错误观念的来源。

Programming paradigm

Java的遗产将是平台,而不是程序设计语言。

澳门新葡萄京官网首页 1

范,模范、典范也。范式即模式、方法。常见的编程范式有:函数式编程、程序编程、面向对象编程、指令式编程等。

Clojure是最激进的,它的语法是从其它语言中分离出来,被认为是Lisp的方言。Clojure是强动态类型语言(就像Groovy),它反映了一种义无反顾的设计决策。虽然Clojure允许你与遗留的Java程序进行全面而深度的交互,但是它并不试图构建一座桥梁去连接面向对象范式。

这就是来源于Java
1.0中Math工具类的一个典型示例:

在面向对象编程的世界,程序是一系列相互作用的对象(Class
Instances),而在函数式编程的世界,程序会是一个无状态的函数组合序列。


public class Math {
  public static double abs(double a);
  // a few dozens of other methods of the same style
}

不同的编程语言也会提倡不同的“编程范型”。一些语言是专门为某个特定的范型设计的,如Smalltalk和Java支持面向对象编程。而Haskell和Scheme则支持函数式编程。现代编程语言的发展趋势是支持多种范型,如
C#、Java 8+、Kotlin、 Scala、ES6+ 等等。

ZDNet至顶网开发频道消息 在我与Martin
Fowler曾经合作呈现的一次主题演讲中,他作出了一个有洞察性的观点:
Java的遗产将是平台,而不是程序设计语言。
Java技术的原始工程师们作出了一个明智的决定,就是将编程语言与运行时环境分开,最终这使得超过200种语言能够运行在Java平台上。这种架构对于该平台的长期活力是至关重要的,因为计算机程序设计语言的寿命一般都是比较短。从2008年开始,由Oracle主办的一年一度的JVM语言峰会为JVM上其它的语言实现与Java平台工程师进行开放式合作提供了机遇。
欢迎来到Java.next专栏系列,在本系列的文章中,我将讲述三种现代JVM语言–Groovy,Scala和Clojure–它提供了范式,设计选择与舒适因子之间一种有趣的混合。在此我不会花时间去深入介绍每种语言;在它们各自的站点上都有这类深度介绍。但这些语言社区的站点–它们主要目的是为了传布这些语言–都缺乏客观的信息,或者是该语言不适用的例子。在本系列的文章中我将进行独立地比较,以填补上述空白。这些文章将概述Java.next程序设计语言,以及学习它们的好处。
超越Java
Java程序设计语言达到卓越的程度就是,按Bruce Tate在他的Beyond
Java一书中的说法,完美风暴:Web应用的兴起,已有Web技术由于种种原因不能适应需求,企业级多层应用开发的兴起,这些因素共同造就了Java的卓越。Tate也指出这场风暴是一系列独一无二的事件,曾经没有其它语言使用相同的途径达到相同的卓越程序。
Java语言已经证明其在功能方面的强大灵活性,但它的语法与固有范式则存在着长期已知的局限性。尽管一些承诺过的变化即将引入到该语言中,但Java语法却不能很容易地支持一些重要的未来语法特性,例如函数式编程中的某些特性。但如果你试图去找到一种语言去替代Java,那么你就找错了。
多语言编程
多语言编程–在2006年的一篇博客中我使这个术语重焕活力并重新流行起来–是基于这样的一种认识:没有一种编程语言能够解决每个问题。有些语言拥有某些内建的特性,使其能够更好地适应特定的问题。例如,由于Swing十分复杂,开发者们发现很难编写Java中的Swing
UI,因为它要求事先声明类型,为UI动作定义烦人的匿名内部类,还有其它的麻烦事儿。使用更适合构建UI的语言,如Groovy中的SwingBuilder工具,去构建Swing应用会美妙得多。
运行在JVM上的程序设计语言大量增多,这大大激发了多语言编程理念,因为你可以混用编号语言,并可使用最佳匹配的语言,但同时却维护着相同的底层字节码和类库。例如,SwingBuilder并不是要替代Swing;它只是搭建在已有的Swing
API之上。当然,在相当长的时间内,开发者们还是将在JVM之外混合使用编程语言–例如,为特定目的而使用SQL和JavaScript–但在JVM的世界内,混合编程将变得更为流行。ThoughtWorks中的许多项目就合用着多种编程语言,而所有由ThoughtWorks
Studios开发的工具则都要使用混合语言。
即便Java仍是你主要的开发语言,学习一下其它语言是如何工作的会让你将它们纳入你的未来战略中。Java仍将是JVM生态系统中的重要组成部分,但最终它更多是作为该平台的汇编语言–或是由于纯粹的性能原因,或是在应对特殊需求时才会用到它。
编程语言的进化
当上世纪八十年代我还在大学时,我们使用着一种称作Pecan
Pascal的开发环境。它独一无二的特性就是能使相同的Pascal代码既可运行在Apple
II上,又可以运行在IBM
PC上。Pecan的工程师们为了实现这一目的使用了一种称作”字节码”的神秘之物。开发者们将他们的Pascal代码编译成”字节码”,该”字节码”则运行在为各个平台编写的原生”虚拟机”上。那是一段可怕的经历!最终程序慢的出奇,即便只是一个简单的类赋值。当时的硬件无法应对这一挑战。
Pecan
Pascal之后的十年,Sun发布了使用相同架构的Java,它受限也受利于上世纪九十年代的硬件环境。Java还加入了其它的对开发者友好的特性,如自动的垃圾收集。由于曾经使用过像C++之样的语言,现在我再也不想使用没有垃圾收集功能的语言去编码了。我宁愿花时间在更高抽象层次上去思考复杂的业务问题,而不是像内存管理这样的复杂管道问题。
计算机语言通常没有很长寿命的原因之一就是语言和平台设计的创新速度。由于我们的平台变得更为强大,它们可以处理更多的额外工作。例如,Groovy的内存化(memoization)特性(2010年加入)会缓存函数调用的结果。不需要手工编写缓存代码,那样会引入潜在的缺陷,你仅仅只是需要调用memoize方法而以,如清单1所示:
清单1. 在Groovy中内存化函数
def static sum = { number ->
factorsOf(number).inject(0, {i, j -> i + j})
}
def static sumOfFactors = sum.memoize()
在清单1中,sumOfFactors方法返回的结果会被自动缓存。你还可以使用方法memoizeAtLeast()和memoizeAtMost()去定制缓存行为。Clojure也含有内存化特性,在Scala中也有略有实现。像内存化这样存在于下一代编程语言(以及某些Java框架)中的高级特性也将逐渐地进入到Java语言中。Java的下一个版本中将加入高阶函数(higher-order
function),这使得内存化更容易被实现。通过研究下一代Java语言,你就可以先睹Java的未来特性为快了。
Groovy,Scala和Clojure
Groovy是二十一世纪的Java语法–浓缩咖啡取代了传统咖啡。Groovy的设计目标是更新并消除Java语法中的障碍,同时还要支持Java语言中的主要编程范式。因此,Groovy要”知晓”诸如JavaBean,它会简化对属性的访问。Groovy会以很快的速度纳入新特性,包括函数式编程中的重要特性,这些特性我将在本系列的后续篇章中着重描述。Groovy仍然主要是面向对象的命令式语言。Groovy区别于Java的两个基本不同点:它是动态而非静态的;它是的元编程能力要好得多。
Scala从骨子里就是为了利用JVM而进行设计的,但是它的语法则是完全被重新设计过了。Scala是强静态类型语言–它的类型要求比Java还严格,但造成的麻烦却很少–它支持面向对象和函数式范式,但更偏好于后者。例如,Scala更喜欢val声明,这会生成不可变变量(类似于在Java中将变量声明为final)赋给var,而var将创建更为大家所熟悉的可变变量。通过对这两种范式的深度支持,Scala为你可能想要的(面向对象的命令式编程)与你所应该想要的(函数式编程)之间架起了一座桥梁。
Clojure是最激进的,它的语法是从其它语言中分离出来,被认为是Lisp的方言。Clojure是强动态类型语言(就像Groovy),它反映了一种义无反顾的设计决策。虽然Clojure允许你与遗留的Java程序进行全面而深度的交互,但是它并不试图构建一座桥梁去连接面向对象范式。例如,Clojure是函数式编程的铁杆,也支持面向对象以允许与该种范式进行互操作。尽管它支持面对对象程序员所习惯的全部特性,如多态–但,是以函数式风格,而非面向对象风格进行实现的。设计Clojure时遵循了一组核心的工程原则,如软件事务内存(Software
Transactional Memory),这是为了迎合新功能而打破了旧有的编程范式。
编程范式
除语法之外,这些语言之间的最有趣的不同之处就是类型及其内在的编程范式:函数式或命令式。
静态类型 vs. 动态类型
编程语言中的静态类型要求显式的类型声明,例如Java中的int
x;声明语句。动态类型语言并不要求在声明时提供类型信息。此处所考虑的语言都是强类型语言,意即程序在赋值之后能够反射出类型。
Java的类型系统广受诟病之处就是其静态类型有太多不便,且又没有提供足够的益处。例如,在当前的有限的类型推导出现之前,Java要求开发者在赋值语句两边要重复地声明类型。Scala的类型比Java的更为静态,但在日常使用中所遇到的不便要少得多,因为它大量使用了类型推导。
初看Groovy,它似乎有一种行为能够衔接静态与动态之间的隔阂。考虑如清单2所示的简单对象集合工厂:
清单2. Groovy集合工厂
class CollectionFactory {
def List getCollection(description) {
if (description == “Array-like”)
new ArrayList()
澳门新葡萄京官网首页 ,else if (description == “Stack-like”)
new Stack()
}
}
清单2中的类表现为一个工厂类,基于传入的description参数,该工厂返回List接口的两种实现–ArrayList或Stack–之一。对于Java开发者,上述代码确保了返回值能够符合约定。然后,清单3中的两个单元测试揭示了一种复杂性:
清单3. Groovy中的集合类型测试
@Test
void test_search() {
List l = f.getCollection(“Stack-like”)
assertTrue l instanceof java.util.Stack
l.push(“foo”)
assertThat l.size(), is(1)
def r = l.search(“foo”)
}
@Test(expected=groovy.lang.MissingMethodException.class)
void verify_that_typing_does_not_help() {
List l = f.getCollection(“Array-like”)
assertTrue l instanceof java.util.ArrayList
l.add(“foo”)
assertThat l.size(), is(1)
def r = l.search(“foo”)
}
在清单3中的第一个单元测试中,使用前述的工厂类获得一个Stack对象,并验证它是否确实是Stack对象,然后再执行栈操作,例如push(),size()和search()。然而,在第二个单元测试中,我必须声明一个期望的异常MissingMethodException才能确保该测试能够通过。当我获取一个Array-like的集合,并将它赋给List类型的变量时,我能够验证返回的类型确为一个List对象。但是,当我试图调用search()方法时将触发异常,因为ArrayList并不包含search()方法。因此,这种声明无法在编译时确保方法的调用是正确的。
虽然这看起来像是一个缺陷,但这种行为却是恰当的。Groovy中的类型只是确保赋值语句的有效性。例如,在清单3中,如果返回的实例未实现List接口,将会触发一个运行时异常GroovyCastException。鉴于此,可以肯定Groovy能够与Clojure同跻身于强动态类型语言家族。
然而,Groovy最新的一些变化使得它的静态与动态之间的隔阂变得扫地清。Groovy
2.0加入了注解@TypeChecked,该注解可使你特别地对类或方法决定进行严格的类型检查。清单4例证该注解的使用:
清单4. 使用注解的类型检查
@TypeChecked
@Test void type_checking() {
def f = new CollectionFactory()
List l = f.getCollection(“Stack-like”)
l.add(“foo”)
def r = l.pop()
assertEquals r, “foo”
}
在清单4中,我加入了注解@TypeChecked,它同时对赋值及随后的方法调用进行了验证。例如,清单5中的代码将不能通过编译:
清单5. 防止无效方法调用的类型检查
@TypeChecked
@Test void invalid_type() {
def f = new CollectionFactory()
Stack s = (Stack) f.getCollection(“Stack-like”)
s.add(“foo”)
def result = s.search(“foo”)
}
在清单5中,我必须对集合工厂返回的对象进行强制类型转换,这样才能允许我调用Stack类中的search()方法。但这种方式会产生一些局限性:当使类型静态化之后,Groovy的很多动态特性将无法工作。然而,上述救命证明了Groovy将继续进行改进,以弥合静态性与动态性之间的分歧。
所有这些语言都有十分强大的元编程功能,所以更为严苛的类型化可以在事后再添加进来。例如,已有多个分支项目将选择性类型(selective
type)引入到Clojure中。但一般认为选择性类型是可选的,它不是类型系统的一部分;它只是一个类型验证系统。
命令式 vs. 函数式
另一个主要的比较维度就是命令式与函数式。命令式编程注重于单步执行的结构,在许多情况下,它是模仿了早期底层硬件的有益结构。函数式编程则注重将函数作为第一等的结构体,以试图将状态传递与可变性降低到最小。
Groovy在很大程度上是受Java的启发,它在根本上仍然是命令式语言。但从一开始,Groovy就加入了许多函数式命令的特性,并且以后还会加入更多的此类特性。
Scala则弥合了这两种编程范式,它同时支持这两种范式。在更偏向(也更鼓励)函数式编程的同时,Scala依然支持面向对象和命令式编程。因此,为了恰当地使用Scala,就要求团队要受到良好的培训,以确保你不会混用和随意地选择编程范式,在多范式编程语言中,这一直都是一个危险。
Clojure是铁杆的函数式编程语言。它也支持面向对象特性,使得它能够很容易地与其它JVM语言进行交互,它并不试图去弥合这两种范式之间的隔阂。相反,Clojure这种义无反顾的决策使它的设计者所考虑的语句成为很好的工程学实践。这些决策具有深远的影响,它使Clojure能够以开创性的方法去解决Java世界中一些挥之不去的问题(如并发)。
在学习这些新语言时所要求的许多思想上的转变就是源自于命令式与函数式之间的巨大差别,而这也正是本系列文章所要探索的最有价值的领域之一。

当你想要计算一个浮点型数字的绝对值,你可以使用如下方式:

1.命令式编程(Imperative programming)

double x = Math.abs(3.1415926d);

计算机的硬件负责运行使用命令式的风格来写的机器码。计算机硬件的工作方式基本上都是命令式的。大部分的编程语言都是基于命令式的。高级语言通常都支持四种基本的语句:

这里有什么问题呢?我们需要一个函数,并且我们从
Math类中得到了结果。这个类有许多有用的内置函数,可以用于许多典型的数学运算,比如计算最大值、最小值、正弦、余弦等。这是一个非常流行的概念,许
多商业化或者开源产品也是如此。自从Java出现(Math类在Java首个版本被引入),这些工具类就被广泛使用。当然,在技术上没有什么不妥。相反,
他们是命令式和过程式的。我们是否在意呢?这取决于你的选择。让我们来看看他们有什么区别。

运算语句

基本上有两种不同的选择,声明式和命令式。

一般来说都表现了在存储器内的数据进行运算的行为,然后将结果存入存储器中以便日后使用。高阶命令式编程语言更能处理复杂的表达式,产生四则运算和函数计算的结合。

就改变程序状态的声明来说,命令式编程的重点是描述一个程序是如何运作的。我们刚刚看到了上面一个命令式编程的例子。下面是另一个(这是一个和面向对象无关,纯粹的命令式并且程序化的代码):

循环语句

public class MyMath {
  public double f(double a, double b) {
    double max = Math.max(a, b);
    double x = Math.abs(max);
    return x;
  }
}

容许一些语句反复运行数次。循环可依据一个默认的数目来决定运行这些语句的次数;或反复运行它们,直至某些条件改变。

就采取的一系列举措来说,声明式编程侧重于在没有规定如何做的情况下程序应该完成哪些事情。就像是Lisp中的代码,一种函数式编程语言。

条件分支

(defun f (a b) (abs (max a b)))

容许仅当某些条件成立时才运行某个区块。否则,这个区块中的语句会略去,然后按区块后的语句继续运行。

我们明白了什么?只是句法的不同?不是这样的。

无条件分支

在命令式和声明式之间有很多描述差异,但是我尽量给出自己的理解。基本上有三种角色在使用f函数的场景下相互影响:买家、包装者和消费者,让我们谈一谈下面的调用:

容许运行顺序转移到程序的其他部分之中。包括跳跃(在很多语言中称为Goto)、副程序和Procedure等。

public void foo() {
  double x = this.calc(5, -7);
  System.out.println("max+abs equals to " + x);
}
private double calc(double a, double b) {
  double x = Math.f(a, b);
  return x;
}

循环、条件分支和无条件分支都是控制流程。

这个例子中,方法calc()是一个买家,方法Math.f()是结果的包装者,方法foo()是消费者。无论使用哪种编程风格,总是有这三个参与其中,买家、包装者,和消费者。

早期的命令式编程语言,例如汇编,都是机器指令。虽然硬件的运行更容易,却阻碍了复杂程序的设计。

想象一下,你是一个买家并希望购买礼物给你的女朋友或男朋友。首先会想到进一家店铺,消费50美元,让别人喷上香水打包给你,然后寄给你的朋友(回报是一枚香吻),这是命令式的风格。

1954年开始开发的FORTRAN,是第一个编译型的编程语言,支持命名变量、复杂表达式、副程序和其他一些功能。后来的二十年中,大量的其他高级命令式编程语言被发明出来。

第二个选项是进一家店铺,消费50美元,并得到一张礼品券,你将此券展示给你的朋友(回报是一枚香吻)。当他或者她想要得到这股芳香,他或她就会进这家店来得到它。这就是声明式风格。

在1980年后,面向对象编程有迅速的发展;面向对象编程语言均有着命令式的风格,但引入了类和对象的核心概念,从此编程进入了
OOP 时代。

看到什么区别了么?

2.面向对象编程(Object-oriented programming,OOP)

在第一个场景中,这是命令式的风格,你要求包装者(一家店铺)使用库存中的香水来打包,并作为准备好的礼品呈现给你。在第二个
场景中,这是声明式的,你最终得到了店铺的承诺,当必要的时候店铺职员会找到香水来打包礼物,并提供给需要的人。如果你的朋友从来没有进过有礼品券的这家
店,这股芳香将一直留在这家店中。

怎样为一个模糊不清的问题找到一个最恰当的描述?
抽象(Abstraction)通常是我们用来简化复杂的现实问题的方法。

此外,你的朋友可以用这个礼品券当做这个礼品本身,就不用去这家店。他或她可能会将这张券作为礼物给其他人,或者用来交换其它礼券或者礼品。这个礼品券本身成为了一个礼品。

在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。

因此,区别就是消费者得到了什么,是用来当做礼品(命令式)还是之后可以转换成真实礼品的礼券(声明式)。

对象包含数据与方法。

工具类,就像从JDK中的Math类或 者Apache
Commons中的StringUtils类中立刻得到了准备好的礼品。然而,从Lisp中的函数和其它函数式编程中,却得到了“礼券”。比如,如果你想
调用Lisp中的求最大值的方法,但只有当你真正开始使用的时候才能计算出来。

面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对计算机下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。此外,支持者声称面向对象程序设计要比以往的做法更加便于学习,因为它能够让人们更简单地设计并维护程序,使得程序更加便于分析、设计、理解。反对者在某些领域对此予以否认。

(let (x (max 1 5))
  (print "X equals to " x))

当我们提到面向对象的时候,它不仅指一种程序设计方法。它更多意义上是一种程序开发方式。在这一方面,我们必须了解更多关于面向对象系统分析和面向对象设计(Object
Oriented
Design,简称OOD)方面的知识。许多流行的编程语言是面向对象的,它们的风格就是会透由对象来创出实例。

直到输出结果打印到屏幕上,求最大值的函数才会调用。当你尝试去“购买”1到5之间最大值的时候,这个x就是一个返回给你的“礼券”。

重要的面向对象编程语言包含Common
Lisp、Python、C++、Objective-C、Smalltalk、Delphi、Java、Swift、C#、Perl、Ruby
与 PHP等。

但是请注意,嵌套的Java静态函不会让他们可声明化,代码仍然是命令式的,因为此时方法进行了传值。

面向对象编程中,通常利用继承父类,以实现代码重用和可扩展性。

public class MyMath {
  public double f(double a, double b) {
    return Math.abs(Math.max(a, b));
  }
}

3.声明式编程(Declarative programming)

你可能会说,“好吧,我明白了。但是为什么声明式的风格比命令式的更好呢?有什么大不了的呢?”我会慢慢解释的。首先让我来展示在面向对象中函数式编程中的函数和静态方法的区别。正如上面所提到的,这是工具类和函数式编程之间第二大的区别。

一种编程范式,与命令式编程相对立。它描述目标的性质,让计算机明白目标,而非具体过程。声明式编程不用告诉计算机问题领域,从而避免随之而来的副作用。而命令式编程则需要用算法来明确的指出每一步该怎么做。

在函数式变成语言中,你可以这么做:

声明式编程通常被看做是形式逻辑的理论,把计算看做推导。声明式编程因大幅简化了并行计算的编写难度,自2009起备受关注。

(defun foo (x) (x 5))

常见的声明式编程语言有:

然后,你可以调用这个x:

数据库查询语言(SQL,XQuery)正则表达式逻辑编程函数式编程组态管理系统等。

(defun bar (x) (+ x 1)) // defining function bar
(print (foo bar)) // passing bar as an argument to foo

声明式编程透过函数、推论规则或项重写(term-rewriting)规则,来描述变量之间的关系。它的语言运行器采用了一个固定的算法,以从这些关系产生结果。

就函数式编程而
言,Java中的静态方法不是函数。你不能用一个静态方法做这样的事。你不能将一个静态方法当做参数传递给其他方法。基本上静态方法是生产者,或者简单地说,Java由唯一的名字所声明。唯一的方法就是调用一个程序并且传递所有必要的参数给它。这个程序将会计算出结果并立即返回给调用者。

很多文本标记语言例如HTML、MXML、XAML和XSLT往往是声明式的。函数式编程,特别是纯函数式编程,尝试最小化状态带来的副作用,因此被认为是声明式的。不过,大多数函数式编程语言,例如Scheme、Clojure、Haskell、OCaml、Standard
ML和Unlambda,允许副作用的存在。

现在,我们来到了最终的问题上,我能听到你在问:“好吧,工具类不是函数式编程,但是他们看起来很像函数式编程,他们运行的很快,并且使用很方便。为什么不用他们?为什么当20年的Java历史证明了工具类是每一个Java开发者的主要手段的时候,又要力求完美?”

除了面向对象的,这点我经常受指责,这里有一些实际的原因(顺便说一句,我推崇面向对象)。

可测试性。在工具类中调用静态方法是硬编码式的依赖,它不能因为测试的需要而被打断。如果你的类正在调用FileUtils.readFile(),除非我的磁盘上有一个实际的文件,否则我无法测试。

效率。工具类,由于其命令式的性质,比可替代的声明式更加低效。即使当他们不是必要使用
的时候,他们也盲目地进行所有的计算,处理资源。而不是返回一个期望值来分隔字符串chunks、StringUtils.split()可以立即打断
它。同时,这也打破了所有可能的chunks,即使“买家”仅仅需要第一个。

可读性。工具类往往 是庞大的(尝试从Apache
Commons阅读StringUtils或者FileUtils的源码)。关注点分离可以使得面向对象如此优雅,但这些想法在工具类中是没有的。他们尽
量把所有可能的程序放进一个.java文件,这导致当它的大小超过了许多静态方法的时候是极难维护的。

最后,我要重申一下:工具类与函数编程无关。他们仅仅是静态方法的包装,是命令式的程序。无论你要声明他们多少次,他们有多渺小,都要尽量远离他们而去使用可靠、健壮的对象。

发表评论

电子邮件地址不会被公开。 必填项已用*标注