在上个月的文章中 ,您仅了解了Scala的语法,这是运行Scala程序并观察其一些更简单功能所必需的最低限度。 该文章中的Hello World和Timer示例使您可以看到Scala的Application类,其用于方法定义和匿名函数的语法,对Array[]的一瞥以及对类型推断的一些了解。 Scala提供了更多的功能,因此本文研究了Scala编码的复杂性。
Ted Neward深入探讨了Scala编程语言,并带您一起学习。 在这个新的developerWorks 系列文章中 ,了解最新的炒作内容,并了解Scala在实践中的语言能力。 无论有什么比较之处,Scala代码和Java代码都将并排显示,但是(正如您将发现的)Scala中的许多内容与您在Java中发现的任何内容都没有直接关联-这就是Scala的魅力所在! 毕竟,如果Java可以做到,为什么还要花时间学习Scala?
Scala的函数式编程功能引人注目,但这并不是Java开发人员对该语言感兴趣的唯一原因。 实际上,Scala融合了功能概念和面向对象。 为了让Java-cum-Scala程序员有种宾至如归的感觉,有必要查看Scala的对象功能,并了解它们如何在语言上映射到Java。 请记住,其中某些功能没有直接映射,或者在某些情况下,“映射”更多是模拟而不是直接并行。 但是在区别很重要的地方,我会指出。
与其开始对Scala支持的类功能进行冗长而抽象的讨论,不让我们看一看一个类的定义,该类可用于为Scala平台带来合理的数字支持(主要从“ Scala By Example”(Scala通过示例) 相关主题 ):
尽管清单1的总体结构在词汇上类似于您在过去十年中在Java代码中看到的结构,但是显然这里有一些新元素正在起作用。 在分开定义之前,请看一下执行新的Rational类的代码:
清单2中看到的结果并不令人兴奋:我创建了两个有理数,再创建两个Rational作为前两个的加减,并将所有内容回显到控制台。 (请注意, Console.println()来自Scala核心库,位于scala.* ,并且隐式导入到每个Scala程序中,就像Java编程中的java.lang 。)
现在再看一下Rational类定义的第一行:
尽管您可能认为您正在看清单3中的某种类似于泛型的语法,但它实际上是Rational类的默认和首选构造函数: n和d只是该构造函数的参数。
Scala对单个构造函数的偏爱在某种意义上是合理的-大多数类最终都只有一个构造函数或一个构造函数集合,以方便起见,所有这些“链结”都通过单个构造函数进行。 如果愿意,可以在Rational上定义更多构造函数,如下所示:
请注意,Scala的构造函数链通过调用首选的构造函数( Int,Int版本)来完成通常的Java构造函数链接事情。
在处理有理数时,它有助于执行一些数字上的表示:即找到一个公分母来简化某些运算。 如果您想将1-over-2(也称为“一半”)添加到2-over-4(也称为“四分之四”),那么Rational类应该足够聪明以实现2-over -4与1-over-2相同,请先进行相应转换,然后再将两者相加。
这是Rational类内部嵌套的私有gcd()函数和g值的目的。 当在Scala中调用构造函数时,将评估该类的整个主体,这意味着g将使用n和d的最大公分母进行初始化,然后依次用于适当地设置n和d 。
回顾清单1 ,还很容易看到我创建了一个重写的toString方法以返回Rational的值,当我从RunRational驱动程序代码开始RunRational它时,这将非常有用。
请注意toString周围的语法,但是:定义前面的override关键字是必需的,以便Scala可以检查以确保基类中存在相应的定义。 这可以帮助防止因意外的键盘滑动而产生的细微错误。 (正是这种动机导致在Java 5中创建@Override批注。)同样,请注意,未指定返回类型-从方法主体的定义中可以明显看出-并且返回的是值未使用Java要求的return关键字明确表示。 而是将函数中的最后一个值隐式视为返回值。 (但是,如果您喜欢Java语法,则可以始终使用return关键字。)
接下来是定义numer和denom ,分别。 语法介入,副手,会导致Java程序员认为numer和denom是公开的Int是初始化为分别正过G和d-过克 ,价值领域; 但是这个假设是不正确的。
形式上,斯卡拉称numer和denom 方法不带参数 ,这是用来创建定义存取一个快速和容易的语法。 在Rational类仍然有三个私有字段,N,d,和g,但他们来自世界由n和d的情况下,默认的专用通道,并通过G的情况下,明确私接隐患。
此时,您中的Java程序员可能会问:“ n和d的相应“设置程序”在哪里? 没有这样的二传手。 Scala的部分功能是鼓励开发人员默认创建不可变对象。 当然,可以使用语法来创建用于修改Rational内部的方法,但是这样做会破坏此类的隐式线程安全性。 其结果是,至少在这个例子中,我要离开Rational ,因为它是。
自然地,这就提出了一个问题,即如何操纵一个Rational 。 像java.lang.String一样,您不能采用现有的Rational并修改其值,因此唯一的选择是从现有值中创建新的Rational ,或者从头开始创建它。 这使下一组四个方法成为焦点:名为+ , - , *和/方法。
不,与其看起来相反,这不是操作员超载。
请记住,在Scala中, 一切都是对象 。 在上一篇文章中 ,您了解了该原理如何适用于函数本身就是对象的想法,这使Scala程序员可以将函数分配给变量,将函数作为对象参数传递,等等。 同样重要的原则是一切都是函数 ; 也就是说,在这种特殊情况下,名为add的函数和名为+的函数没有区别。 在Scala中,所有运算符都是类上的函数。 他们只是碰巧有个时髦的名字。
然后,在Rational类中,为有理数定义了四个运算。 这些是规范的数学运算加,减,乘和除。 其中每一个均以其数学符号命名: + , - , *和/ 。
但是请注意,每个运算符都通过每次构造一个新的Rational对象来工作。 同样,这与java.lang.String工作方式非常相似,它是默认实现,因为它会生成线程安全的代码。 (如果没有共享状态(并且线程之间共享的对象的内部状态是隐式共享状态)没有被线程修改,则不必担心并发访问该状态。)
一切都是函数规则,具有两个强大的作用:
正如您已经看到的,第一个是可以对函数进行操作并将其存储为对象本身。 这导致了强大的重用场景,例如本系列第一篇文章中探讨的场景。
第二个效果是,Scala语言设计人员可能认为提供的运算符与Scala程序员认为应该提供的运算符之间没有特殊区别。 例如,让我们暂时假设提供一个“反转”运算符是有意义的,该运算符将翻转分子和分母并返回一个新的Rational (以便Rational(2,5)将返回Rational(5,2) ) 。 如果确定~符号最能代表这个概念,则可以使用它作为名称来定义一个新方法,它的行为与Java代码中任何其他运算符一样,如清单5所示:
在Scala中定义这个一元的“运算符”有点棘手,但这纯粹是一种句法特征:
当然,棘手的部分是,您必须在~名称前加上“ unary_ ”前缀,以告知Scala编译器它打算成为一元运算符; 因此,该语法将与大多数对象语言中常见的传统“引用-然后-方法”语法“相背离”。
请注意,这与“一切都是对象”规则相结合,创建了一些功能强大但易于解释的代码机会:
当然,对于直接整数加法示例,Scala编译器“做正确的事”,但是从语法上讲,它们都是相同的。 这意味着您可以开发与Scala语言中附带的“内置”类型没有区别的类型。
Scala编译器甚至会尝试从具有某些预定含义的“运算符”中推断出某些含义,例如+=运算符。 请注意,尽管Rational类没有为+=明确定义的事实,但是以下代码是如何工作的:
打印时, r5的值为[13 / 12] r5 [13 / 12] ,正好是该值。
请记住,Scala会编译为Java字节码,这意味着它可以在JVM上运行。 如果需要证明, 0xCAFEBABE就像编译器正在生成以0xCAFEBABE开头的.class文件0xCAFEBABE ,就像javac一样。 还请注意,如果启动JDK( javap )附带的Java字节码反汇编程序并将其指向生成的Rational类,会发生什么,如清单9所示:
Scala类中定义的“运算符”在Java编程的最佳传统中会转化为方法调用,尽管它们似乎确实基于有趣的名称。 在类上定义了两个构造函数:一个构造函数采用int而一个构造函数则采用一对int 。 而且,如果您完全担心大写Int类型的使用是某种形式的java.lang.Integer的变相,请注意,Scala编译器足够聪明,可以将它们转换为常规Java原语int 。类定义。
众所周知,优秀的程序员编写代码,优秀的程序员编写测试是一个模因。 到目前为止,我一直在为我的Scala代码执行该规则时比较松懈,所以让我们看看将这个Rational类放入传统的JUnit测试套件中会发生什么,如清单10所示:
基于Scala的单元测试套件已经存在,名称为SUnit。 如果您使用SUnit进行清单10所示的测试,则不需要基于反射的解决方法。 基于Scala的单元测试代码将针对Scala类进行编译,因此编译器将能够使符号对齐。 实际上,一些开发人员发现在Scala中编写单元测试,行使POJO而不是采用其他方法更具吸引力。
SUnit是scala.testing软件包中标准Scala发行scala.testing 。
除了确认Rational类的行为合理之外,上述测试套件还证明可以从Java代码调用Scala代码(尽管在运算符方面存在一些阻抗不匹配的问题)。 当然,这很酷,它使您可以通过将Java类迁移到Scala类来缓慢地试用Scala,而不必更改支持它们的测试。
您可能会在测试代码中注意到的唯一怪异与操作员调用有关,在这种情况下,这是Rational类上的+方法。 回顾javap输出,Scala显然已经将+函数转换为JVM方法$plus ,但是Java语言规范不允许标识符中使用$字符(这就是为什么在嵌套和匿名嵌套类名称中使用$字符的原因)。
为了调用这些方法,您必须使用Groovy或JRuby(或其他对$字符没有限制的语言)编写测试,或者可以编写一些Reflection代码来调用它们。 我采用后一种方法,从Scala的角度来看,这并不是很有趣,但是如果您感到好奇的话,结果将包含在本文的代码包中。 (请参阅下载 。)
请注意,仅对于不是合法Java标识符的函数名称,才需要类似的解决方法。
在我刚开始学习C ++时,Bjarne Stroustrup建议学习C ++的一种方法是将其视为“更好的C”(请参阅参考资料 )。 在某些方面,当今的Java开发人员可能会将Scala视为“更好的Java”,因为它提供了一种更简洁简洁的方式来编写传统Java POJO。 考虑清单11中所示的传统Person POJO:
现在考虑用Scala编写的等效代码:
鉴于原始的“ Person有一些可变的二传手,这并不是一个完全的替代品。 但是考虑到原始的Person也没有围绕这些可变设置器的同步代码,因此Scala版本使用起来更安全。 同样,如果目标是真正减少Person的代码行数,则可以完全删除getFoo属性方法,因为Scala会围绕每个构造函数参数生成访问器方法getFoo firstName()返回String , lastName()返回String , age()返回int )。
即使对那些可变的setter方法的需求不可否认,Scala版本仍然更简单,如清单13所示:
顺便说一句,请注意在构造函数参数上引入了var关键字。 无需赘述, var告诉编译器该值是可变的。 结果,Scala生成访问器( String firstName(void) )和mutator( void firstName_$eq(String) )方法。 这样就很容易创建在setFoo使用生成的mutator方法的setFoo属性mutator方法。
Scala尝试将功能性概念和简洁性结合在一起,而又不会失去对象范式的丰富性。 正如您可能在本系列文章中开始看到的那样,Scala还纠正了Java语言中发现的一些令人讨厌的(事后看来)语法问题。
繁忙的Java开发人员Scala系列指南中的第二篇文章重点介绍了Scala的对象工具,使您可以开始使用Scala,而不必深入研究功能池。 根据到目前为止的经验,您可以开始使用Scala减少编程工作量。 除其他外,您可以使用Scala生成与其他编程环境(例如Spring或Hibernate)相同的POJO。
但是,请保留您的潜水帽和潜水用具,因为下个月的文章将标志着我们开始进入功能池深层的起点。
翻译自: https://www.ibm.com/developerworks/java/library/j-scala02198/index.html