Scala函数式对象

Stella981
• 阅读 498

有理数类的表示


实现规范:支持有理数的加减乘除,并支持有理数的规范表示

1.定义Rational


首先,考虑用户如何使用这个类,我们已经决定使用“Immutable”方式来使用Rational对象,我们需要用户在定义Rational对象时提供分子和分母。

class Rational(n:Int, d:Int)

可以看到,和Java不同的是,Scala的类定义可以有参数,称为类参数,如上面的n、d。Scala使用类参数,并把类定义和主构造函数合并在一起,在定义类的同时也定义了类的主构造函数。因此Scala的类定义相对要简洁些。

Scala编译器会编译Scala类定义包含的任何不属于类成员和类方法的其它代码,这些代码将作为类的主构造函数。比如,我们定义一条打印消息作为类定义的代码:

scala> class Rational (n:Int, d:Int) { 
                  | println("Created " + n + "/" +d) 
                  | }
defined class Rational

scala> new Rational(1,2)
Created 1/2
res0: Rational = Rational@22f34036

可以看到创建Ratiaonal对象时,自动执行类定义的代码(主构造函数)。

2.重新定义类的toString方法


上面的代码创建Rational(1,2),Scala 编译器打印出Rational@22f34036,这是因为使用了缺省的类的toString()定义(Object对象的),缺省实现是打印出对象的类名称+“@”+16进制数(对象的地址),显示结果不是很直观,因此我们可以重新定义类的toString()方法以显示更有意义的字符。

在Scala中,你也可以使用override来重载基类定义的方法,而且必须使用override关键字表示重新定义基类中的成员。比如:

scala> class Rational (n:Int, d:Int) { 
                  | override def toString = n + "/" +d 
                  | }
defined class Rational

scala> val x= new Rational(1,3)
x: Rational = 1/3

scala> val y=new Rational(5,7)
y: Rational = 5/7

3.前提条件检查


前面说过有理数可以表示为 n/d(其中d、n为正数,而d不能为0)。对于前面的Rational定义,我们如果使用0,也是可以的。

怎么解决分母不能为0的问题呢?面向对象编程的一个优点是实现了数据的封装,你可以确保在其生命周期过程中是有效的。对于有理数的一个前提条件是分母不可以为0,Scala中定义为传入构造函数和方法的参数的限制范围,也就是调用这些函数或方法的调用者需要满足的条件。Scala中解决这个问题的一个方法是使用require方法(require方法为Predef对象的定义的一个方法,Scala环境自动载入这个类的定义,因此无需使用import引入这个对象),因此修改Rational定义如下:

scala> class Rational (n:Int, d:Int) { | require(d!=0) | override def toString = n + "/" +d | }defined class Rational scala> new Rational(5,0) java.lang.IllegalArgumentException: requirement failed at scala.Predef$.require(Predef.scala:211) ... 33 elided 

可以看到,如果再使用0作为分母,系统将抛IllegalArgumentException异常。

4.添加成员变量


前面我们定义了Rational的主构造函数,并检查了输入不允许分母为0。下面我们就可以开始实行两个Rational对象相加的操作。我们需要实现的函数化对象,因此Rational的加法操作应该是返回一个新的Rational对象,而不是返回被相加的对象本身。我们很可能写出如下的实现:

class Rational (n:Int, d:Int) { require(d!=0) override def toString = n + "/" +d def add(that:Rational) : Rational = new Rational(n*that.d + that.n*d,d*that.d)}

实际上编译器会给出编译错误。

这是为什么呢?尽管类参数在新定义的函数的访问范围之内,但仅限于定义类的方法本身(比如之前定义的toString方法,可以直接访问类参数),但对于that来说,无法使用that.d来访问d。因为that不在定义的类可以访问的范围之内。此时需要定类的成员变量。

注:后面定义的case class类型编译器自动把类参数定义为类的属性,这是可以使用that.d等来访问类参数)。

修改Rational定义,使用成员变量定义如下:

class Rational (n:Int, d:Int) { 
    require(d!=0) 
    val number =n 
    val denom =d 
    override def toString = number + "/" +denom 
    def add(that:Rational) = new Rational( number * that.denom + that.number* denom, denom * that.denom )}

要注意的我们这里定义成员变量都使用了val,因为我们实现的是“immutable”类型的类定义。number和denom以及add都可以不定义类型,Scala编译能够根据上下文推算出它们的类型。

scala> val oneHalf=new Rational(1,2)
oneHalf: Rational = 1/2 scala> val twoThirds=new Rational(2,3) twoThirds: Rational = 2/3 scala> oneHalf add twoThirds res0: Rational = 7/6 scala> oneHalf.number res1: Int = 1 

5.自身引用


Scala 也使用this来引用当前对象本身,一般来说访问类成员时无需使用this,比如实现一个lessThan方法,下面两个实现是等效的。

第一种:

def lessThan(that:Rational) = this.number * that.denom < that.number * this.denom 

第二种:

def lessThan(that:Rational) = number * that.denom < that.number * denom 

但如果需要引用对象自身,this就无法省略,比如下面实现一个返回两个Rational中比较大的一个值的一个实现:

def max(that:Rational) = if(lessThan(that)) that else this 

其中的this就无法省略。

6.辅助构造函数


在定义类时,很多时候需要定义多个构造函数,在Scala中,除主构造函数之外的构造函数都称为辅助构造函数(或是从构造函数),比如对于Rational类来说,如果定义一个整数,就没有必要指明分母,此时只要整数本身就可以定义这个有理数。我们可以为Rational定义一个辅助构造函数,Scala定义辅助构造函数使用 this(…)的语法,所有辅助构造函数名称为this。

def this(n:Int) = this(n,1) 

**所有Scala的辅助构造函数的第一个语句都为调用其它构造函数**,也就是this(…)。被调用的构造函数可以是主构造函数或是其它构造函数(最终会调用主构造函数)。这样使得每个构造函数最终都会调用主构造函数,从而使得主构造函数称为创建类单一入口点。在Scala中也只有主构造函数才能调用基类的构造函数,这种限制有它的优点,使得Scala构造函数更加简洁和提高一致性。

7.私有成员变量和方法


Scala 类定义私有成员的方法也是使用private修饰符,为了实现Rational的规范化显示,我们需要使用一个求分子和分母的最大公倍数的私有方法gcd。同时我们使用一个私有变量g来保存最大公倍数,修改Rational的定义:

scala> class Rational (n:Int, d:Int) { | require(d!=0) | private val g =gcd (n.abs,d.abs) | val number =n/g | val denom =d/g | override def toString = number + "/" +denom | def add(that:Rational) = | new Rational( | number * that.denom + that.number* denom, | denom * that.denom | ) | def this(n:Int) = this(n,1) | private def gcd(a:Int,b:Int):Int = | if(b==0) a else gcd(b, a % b) | } defined class Rational scala> new Rational ( 66,42) res0: Rational = 11/7 

注意gcd的定义,因为它是个回溯函数,必须定义返回值类型。**Scala 会根据成员变量出现的顺序依次初始化它们**,因此g必须出现在number和denom之前。

8.定义运算符


本篇还将接着上篇Rational类,我们使用add定义两个Rational对象的加法。两个Rational加法可以写成x.add(y)或者x add y。
即使使用 x add y还是没有 x + y来得简洁。
我们前面说过,在Scala中,运算符(操作符)和普通的方法没有什么区别,任何方法都可以写成操作符的语法。比如上面的 x add y。
而在Scala中对方法的名称也没有什么特别的限制,你可以使用符号作为类方法的名称,比如使用+、-和*等符号。因此我们可以重新定义Rational如下:

class Rational (n:Int, d:Int) { require(d!=0) private val g =gcd (n.abs,d.abs) val numer =n/g val denom =d/g override def toString = numer + "/" +denom def +(that:Rational) = new Rational( numer * that.denom + that.numer* denom, denom * that.denom ) def * (that:Rational) = new Rational( numer * that.numer, denom * that.denom) def this(n:Int) = this(n,1) private def gcd(a:Int,b:Int):Int = if(b==0) a else gcd(b, a % b)} 

这样就可以使用 +、*号来实现Rational的加法和乘法。**+、*的优先级是Scala预设的**,和整数的+、-、*和/的优先级一样。下面为使用Rational的例子:

scala> val x= new Rational(1,2)
x: Rational = 1/2 scala> val y=new Rational(2,3) y: Rational = 2/3 scala> x+y res0: Rational = 7/6 scala> x+ x*y res1: Rational = 5/6 

从这个例子也可以看出Scala语言的扩展性,你使用Rational对象就像Scala内置的数据类型一样。

9.Scala中的标识符


从前面的例子我们可以看到Scala可以使用两种形式的标志符,字符数字和符号。字符数字使用字母或是下划线开头,后面可以接字母或是数字,符号“$”在Scala中也看作为字母。然而以“$”开头的标识符为保留的Scala编译器产生的标志符使用,应用程序应该避免使用“$”开始的标识符,以免造成冲突。

Scala的命名规则采用和Java类似的camel命名规则(驼峰命名法),首字符小写,比如toString。类名的首字符还是使用大写。此外也应该避免使用以下划线结尾的标志符以避免冲突。

符号标志符包含一个或多个符号,如+、:和?。对于+、++、:::、<、 ?>、 :->之类的符号,Scala内部实现时会使用转义的标志符。例如对:->使用$colon$minus$greater来表示这个符号。因此,如果你需要在Java代码中访问:->方法,你需要使用Scala的内部名称$colon$minus$greater。

混合标志符由字符数字标志符后面跟着一个或多个符号组成,如 unary_+为Scala对+方法的内部实现时的名称。

字面量标志符为使用‘’定义的字符串,比如 ‘x’ 、‘yield’ 。 你可以在‘’之间使用任何有效的Scala标志符,Scala将它们解释为一个Scala标志符,一个典型的使用是 Thread的yield方法, 在Scala中你不能使用Thread.yield()是因为yield为Scala中的关键字, 你必须使用 Thread.‘yield’()来使用这个方法。

10.方法重载


和Java一样,Scala也支持方法重载,重载的方法参数类型不同而使用同样的方法名称,比如对于Rational对象,+的对象可以为另外一个Rational对象,也可以为一个Int对象,此时你可以重载+方法以支持和Int相加

def + (i:Int) = new Rational (numer + i * denom, denom) 

11.隐式类型转换


上面我们定义Rational的加法,并重载+以支持整数,r + 2,当如果我们需要 2 + r如何呢?

可以看到 x + 3没有问题,3 + x就报错了,这是因为整数类型不支持和Rational相加。我们不可能去修改Int的定义(除非你重写Scala的Int定义)以支持Int和Rational相加。如果你写过.Net代码,这可以通过静态扩展方法来实现,Scala提供了类似的机制来解决这种问题。

如果Int类型能够根据需要自动转换为Rational类型,那么 3 + x就可以相加。Scala通过implicit def定义一个隐含类型转换,比如定义由整数到Rational类型的转换如下:

implicit def intToRational(x:Int) = new Rational(x) 

其实此时Rational的一个+重载方法是多余的, 当Scala计算 2 + r,发现 2(Int)类型没有可以和Rational对象相加的方法,Scala环境就检查Int的隐含类型转换方法是否有合适的类型转换方法,类型转换后的类型支持+ r,一检查发现定义了由Int到Rational的隐含转换方法,就自动调用该方法,把整数转换为Rational数据类型,然后调用Rational对象的 +方法。从而实现了Rational类或是Int类的扩展。关于implicit def的详细介绍将由后面的文章来说明,隐含类型转换在设计Scala库时非常有用。

转自:https://www.jianshu.com/p/61268f438485

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这