文档章节

[译] 印度朋友手把手教你学Scala(3):方法

暗夜在火星
 暗夜在火星
发布于 2017/02/26 12:45
字数 3175
阅读 46
收藏 0
点赞 0
评论 0
/**
 * 谨献给我最爱的YoYo
 * 原文出处:https://madusudanan.com/blog/scala-tutorials-part-3-methods/
 * @author dogstar.huang <chanzonghuang@gmail.com> 2017-02-25
 */

本翻译已征得Madusudanan.B.N同意,并链接在原文教程前面。

Scala里的方法

自从2015年12月写了最后一篇关于Scala的文章后,到现在已经有很长一段时间了。希望我能写多一点。^_^

这是关于Scala系列教程的第三章。点击这里可查看整个系列。

这篇文章是关于怎么处理Scala里的方法。大部分都是关于风格,但当我们进入函数式编程的世界前这是很重要的。

目录

  • Primer
  • 方法的语法
  • Unit类型
  • 不可变的方法参数
  • return关键字的注意事项
  • 异构返回类型(Heterogeneous return types)
  • 传值调用和传名调用
  • 方法参数的默认值
  • 剔除方法

Primer

回到那些汇编编程的日子,那时有一种叫做子例程的东西。

相同的理念演变成了今天什么是方法。一种把程序识别成做不同事情的小代码块的简单方式。

子例程,程序,函数,方法可能意味相同的东西,也可能不是,并且真的很难给出一个广义的区别,因为根据深思熟虑的编程语言有不同的含义。

在Scala,我们只关心函数(Function)和方法(Method)。

方法的语法

声明和使用方法有好几种方式。以下只是一些方便熟悉语法糖的示例。

  • 带两个参数并返回一个值的方法
  • 不带大括号的方法
  • 不带任何参数的方法
  • 既没参数又没返回值的方法
  • 返回了未指明类型的值的方法

带两个参数并返回一个值的方法

def max(x:Int, y:Int) : Int = {  
    if (x>y) x else y
}

以上是一个简单的方法,接收两个参数x和y,然后返回这两个之中较大的那个。

所有的方法以关键字def开头,接着的是方法名称。随后跟着可选的参数和返回类型。

x和y是怎么返回的?暂且把这个问题放一边,稍候我们会说到。

不带大括号的方法

和Java相似,方法声明(译者注:应该是方法实现)放在大括号里,但这是可选的。

def max(x:Int, y:Int):Int= if (x>y) x else y  

这纯粹是出于简单考虑,当方法很小时,我们可以忽略括号。但当方法很大时,最好是把代码块放在大括号里。

不带任何参数的方法

如前面所述,方法参数是可选的。

def getValueOfPi: Double = 3.14159  

注意,为了可读性我已经省略了小括号。也可以像下面这样为空参数。

def getValueOfPi (): Double = 3.14159  

既没参数又没返回值的方法

打印Hello World信息到控制台。

def printHelloMsg = println("Hello there !!!!")  

关于用这种方式来声明重要的一点是不能使用像printHelloMsg()这样来调用,它只能使用例如没有小括号的printHelloMsg来调用。

当带小括号调用时,意味着我们传递了一个空参数,而不是没有参数。

这被称为0元数方法(0-arity method),这点在方法调用文档里有解释。

返回了未指明类型的值的方法

def greetPerson(msg :String) = "Hello " + msg + "!!!"  

这会返回“Hello”后面加上你传入的任何字符串。

在所有这些例子里,不管哪里需要类型信息,Scala都使用了类型接口

要注意的一件事是如果省略了=符号,编译器会把它作为一个Unit类型方法,即使我们从那个方法返回了一些东西。

例如,

def whichIsGreater (a : Int , b: Int)  {

    if(a>b) a else b

}


在函数式编程术语里,一个不返回任何类型的方法,例如返回Unit,被称为过程(procedure)。这些方法没有副作用,并且状态独立。

Unit类型

如果在Scala REPL里运行以下示例,

def printHelloMsg = println("Hello there !!!!")  

可以看到Unit类型的方法。

在Java里,和上面方法等价的示例,如下面所示。

void printHelloMsg() {  
    System.out.println("Hello there !!!!");
}

如果我们调用了这个方法,没有东西会打印/返回。

val t = someMethod()  
println(t)  

编译器会抛出一个类似以下的警告。 
warning: enclosing method someMethod has result type Unit: return value discarded

把返回类型赋值给一个变量会给到一个类似()这样的输出。可以从运行时boxed unit理解这点,而它会被Unit.scala unboxing所调用。这只是Unit类型的一个toString展示。

不可变的方法参数

默认地,传给方法的方法参数是不可变的,并且不能改变。以下图片展示了一个例子。 

记住,Scala比Java更加信奉不可变性。等效的Java方法可能像是这样:

public void calculateInterest(final int interestAmount){  
        interestAmount = interestAmount % 10;
}

重写它而不改变方法参数是非常直截了当的,但之所以这样设计的原因,再一次是因为函数式编程、摆脱可变状态以及钟爱[不可变对象](http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html。

return关键字的注意事项

Scala编译器以这种的方式来构建,在缺少return语句里会取最后一条语句并返回它。

但是,在最后的语句不打算作为方法的输出的地主,那么结果可能会和我们期待的有所出入。

def whichIsGreater (a : Int , b: Int)  = {

    if (a>b) a else b

    // 这是故意的
    "Some String"

}

我们可以使用任何整型参数来调用这个方法,它都会返回Some String。记住,Scala类型接口有限,在这种特殊场景里这是它能想到的最好方式。

如果你使用IDE,你可以看到鼠标经过时会有个警告在这些变量上。 

展开是:“高亮表达式,如果它没有副作用并且不会影响任何变量值或者函数的返回。可以删除或者转换成一个返回语句”。

这告诉我们这条语句没有副作用,因为在下面我们声明了一个字符串变量。

如果使用同样的代码,现在使用return关键字,编译器顿时就抱怨了。 

这个错误的原因是,我们立即切断了执行流程,并且接着从那里返回。如果我们推断类型,它将涉及遍历整个调用堆栈,所以编译器采用一种更安全的方式使程序员明确注明返回类型。

这里是另外一个例子。

 def whichIsGreater (a : Int , b: Int)  = {

    if (a>b) a

  }

如果a大于b则返回a,否则在编译时会推断返回Unit类型。注意方法返回类型是AnyVal,因为else部分是Unit,而if部分是一个Int,这会提升到导次结构中可用的下一个可用的类型。

根据我们的分析,可以推断出以下几点。

  • 子类型用于提升类型顺序。
  • 如果使用return关键字,必须强制指明方法类型。
  • 最后一条语句会用来作为返回类型,如果给了一个错误的类型,编译器会识别不了。
  • 全部类型错误都在编译时,所以确保了完全类型安全。
  • 应该指明方法参数的类型。因为Scala使用本地类型接口,所以不能在编译时推断他们。

当用的是纯函数式编程,你根本都不需要用到return关键字。

避免return关键可能一开始看起来奇怪,但一旦我们学习了更多的函数式编程就习以为常了。

异构返回类型(Heterogeneous return types)

子类型使得我们可以做很多东西。其中一点就是可以通过异构的方式混合类型。

考虑以下示例。

def whichIsGreater (a : Int , b: Int)  = {

    if (a>b) a else "a is lesser"

}

此方法返回类型是Any,位于层次结构的顶级。如果你是一位Java程序员,你会很快就用instanceof来猜测返回类型。

使用instanceof是一个反模式(anti-pattern)并且被视为糟糕的编程实践。在Scala里你应该使用Either类型。但那是另外一篇博客的主题。

传值调用和传名调用

当正在调用方法时,理解编译器如何对待它是迫切的。在Scala,方法对待它的参数有两种方式。

  • 传值调用
  • 传名调用

在参数被求值前就减少的地方,传值调用是一种通常的策略,而传名调用直到它在代码里的某个地方被用到时才会对表达式进行求值。

def multiply(x : Int, y: Int) : Int ={  
    x * x
}

如果我们通过multiply(6 + 4,3)来调用这个方法,会发生以下事情。

注意:方法名称中的值描述了计算 ,而不是对实际方法的调用。

传值调用 -> multiply(10,3) -> multiply(10 * 10) -> 100 
传名调用 -> multiply(6+4,3) -> multiply(10,3) -> multiply(10 * 10) -> 100

在传名调用,表达式6 + 4只有当在它进入了调用栈后才会被求值,而在传值调用里它在进入前求值。

调用也可以变成这样:multiply(10,4+3)

传值调用 -> multiply(10,7) -> multiply(10 * 10) -> 100 
传名调用 -> multiply(10*10) -> 100

在这里,传名调用更短,因为第二个变量压根都不需要所以不用求值。

可以看到这两种策略都可以产出同样的结果。

Scala默认使用传值调用,因为在通常的使用场景里它比它的竞争对手要好, 但也支持传名调用如果强制的话。

以下是一个现实世界的例子。

object FunctionCallType extends App {


  println(callByValue(2 + 2))
  println(callByName(2 + 2))


  def callByValue(value: Int): Int = {
    value + 1

  }


  def callByName(value: => Int): Int = {
    value + 1
  }

}

=>用于描述这个变量应该传名调用。

如果使用IDE来调试,可以看到callByValue已经把变量value计算成4. 

而callByName则不是。 

如果执行的话,这两个例子都会得到同样的结果,区别是参数被调用的方式。

通过这个例子,可以更清晰明白关于变量是如何被求值的。

好了,但在什么场景我们需要传名调用,以及哪里我们需要传值调用?

这个问题需要更进一步对函数式编程的理解,函数式编程会在后面的文章中讲到。通常你会用传值调用,但有些场景用传名调用是一个更好的选择。

方法参数的默认值

在声明方法时,我们也可以指定参数的默认值,以防调用者什么也不提供。

  def isAllowedURl(url: String = "default"): String = {
      if(url.equals("default"))
        "No URL provided"
      else
        "Access allowed"
  }

在不提供参数值的情况下,默认值就会赋给String类型的url

剔除方法

一个普遍广泛使用的编程实践是把方法声明为Stubs,例如没有逻辑实现。人们选择这样做可能有几个原因,这取决于开发人员在合适的地方使用它。

在Java里,你可能通常使用java.lang.UnsupportedOperationException来添加被剔除的方法(stubbed methods),无名的一种类型,给定一个缺失的实现,表示不支持的操作。

Scala用???来暗示方法尚未被实现,如下面的声明。

// 被剔除的方法
def getID(): String = ???  

当调用这些方法,可以看到一个更清晰的异常调用栈。

Exception in thread "main" scala.NotImplementedError: an implementation is missing  
    at scala.Predef$.$qmark$qmark$qmark(Predef.scala:230)
    at Child.getAge(Child.scala:7)
    at Runnable$.delayedEndpoint$Runnable$1(Runnable.scala:8)
    at Runnable$delayedInit$body.apply(Runnable.scala:4)
    at scala.Function0$class.apply$mcV$sp(Function0.scala:34)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.collection.immutable.List.foreach(List.scala:381)
    at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
    at scala.App$class.main(App.scala:76)
    at Runnable$.main(Runnable.scala:4)
    at Runnable.main(Runnable.scala)

这里我所解释的大部分都是关于理解Scala面向对象的方面。以我的经验,最好先理解这方面,再进入函数式编程。

敬请关注我下一篇关于对象和类的文章。


------------------------ 

© 著作权归作者所有

共有 人打赏支持
暗夜在火星

暗夜在火星

粉丝 150
博文 150
码字总数 310930
作品 1
广州
程序员
ZigBEE

手把手教你学Zigbee第一讲:http://www.tudou.com/programs/view/fQattIYSwBo/ 手把手教你学Zigbee第二讲:http://www.tudou.com/programs/view/7-dIvUgk2kg/ 手把手教你学Zigbee第三讲:htt...

GIFCOOL ⋅ 2016/09/02 ⋅ 0

免费的计算机编程类中文书籍

免费的编程中文书籍索引,欢迎投稿。 国外程序员在 stackoverflow 推荐的程序员必读书籍,中文版。 stackoverflow 上的程序员应该阅读的非编程类书籍有哪些? 中文版 github 上的一个流行的编...

justjavac ⋅ 2014/08/13 ⋅ 10

更上一层楼:国外30个优秀的UI/UX在线学习网站

最近都在浏览Quora网站,这是一个能够很好的和其他设计师交流的网站,但几乎都是用英文交流,对于大部分中国人来说还是有困难的,可能大家也更愿意去知乎上找答案,都说知乎是中国版的Quora...

jongde ⋅ 2016/12/08 ⋅ 0

编程类开放书籍荟萃(转载)

关于开源图书有人在网络上做了大量整理,本文为大家刊载《免费的编程中文书籍索引》 国外程序员在 stackoverflow 推荐的程序员必读书籍,中文版。 stackoverflow 上的程序员应该阅读的非编程...

行者PHPer ⋅ 2016/10/09 ⋅ 0

Scala 技术周刊 | 第 24 期

这里有最新的 Scala 社区动态、技术博文。 微信搜索 「scalacool」关注我们,及时获取最新资讯。 深度阅读 Resolve me, Implicitly 依赖注入 Refined types, what are they good for? 让类型...

ScalaCool ⋅ 2017/10/23 ⋅ 0

迷渡:免费的编程中文书籍索引

本文之前发布过,近半年后,本文作者(迷渡,JustJavaC)大幅度进行了更新,因此再次分享给大家。感谢 @justjavac 和众多的分享者!感谢写作这些文档、手册的人们! 语言无关类 操作系统 开源...

山哥 ⋅ 2015/01/22 ⋅ 4

手把手教你如何提高神经网络的性能

神经网络是一种机器学习算法,它可以提高许多用例(项目)的精确度。但是,很多时候我们构建的神经网络的准确性可能不令人满意,或者可能无法让我们排在数据科学竞赛排行榜的头号位置。因此,...

【方向】 ⋅ 05/20 ⋅ 0

如何不编程,采集网站评论信息?(视频教程)

如果你不想学编程,数据采集爬虫也是可以用的。 简介 最近的一次组会,我们请来了一位分享嘉宾——15级研究生庞琳同学,给我们科研团队分享网站评论数据的采集。 还记得去年这个时候,庞琳过...

王树义 ⋅ 04/23 ⋅ 0

免费的编程中文书籍索引【收藏速度】

语言无关类 优质博客 PyTab在线手册中心 ImportNew 廖雪峰的官方网站 程序员博客墙 操作系统 开源世界旅行手册 鸟哥的Linux私房菜 Linux 系统高级编程 The Linux Command Line (中英文版) L...

yonghu86 ⋅ 2015/04/15 ⋅ 0

程序员的四个阶段

这是我在一本书中看到过一种说法,程序员对编程之道的追求大致要经历下面的四个阶段: 1.学会 - 知其所然 掌握一些具体编程知识。 2.会学 - 知所以然 能快速和深刻的理解某项技术并举一反三。...

x3d ⋅ 2016/12/01 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

服务网关过滤器

过滤器作用 我们的微服务应用提供的接口就可以通过统一的API网关入口被客户端访问到了。但是,每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都需要有一定的限制,系统并不会...

明理萝 ⋅ 14分钟前 ⋅ 1

【2018.06.21学习笔记】【linux高级知识 14.1-14.3】

14.1 NFS介绍 NFS服务全称是NetWork File System:网络文件系统,最早有sun公司开发的,4.0版本由Netapp公司开发,是基于RPC远程过程调用(Remote Procedure Call)协议的服务。 14.2 NFS服务...

lgsxp ⋅ 23分钟前 ⋅ 0

Day18 vim编辑模式、命令模式与练习

编辑模式 命令模式 :nohl 不高亮显示 :x与:wq类似,如果在更改文件之后操作,两者效果一样;如果打开文件,没有任何操作; :wq会更改mtime,但是:x不会。 练习题 扩展 vim的特殊用法 ht...

杉下 ⋅ 26分钟前 ⋅ 0

Enum、EnumMap、EnumSet

1、Enum 不带参数 public enum Car { AUDI { @Override public int getPrice() { return 25000; } }, MERCEDES { ......

职业搬砖20年 ⋅ 27分钟前 ⋅ 0

Java中的锁使用与实现

1.Lock接口 锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源。 在Lock出现之前,java程序是靠synchronized关键字实现锁功能的,而Java SE5之后,...

ZH-JSON ⋅ 28分钟前 ⋅ 0

线程组和 ThreadLocal

前言 在上面文章中,我们从源码的角度上解析了一下线程池,并且从其 execute 方法开始把线程池中的相关执行流程过了一遍。那么接下来,我们来看一个新的关于线程的知识点:线程组。 线程组 ...

猴亮屏 ⋅ 29分钟前 ⋅ 0

相对路径和绝对路径

基本概念   文件路径就是文件在电脑中的位置,表示文件路径的方式有两种,相对路径和绝对路径。在网页设计中通过路径可以表示链接,插入图像、Flash、CSS文件的位置。   物理路径:物理路...

临江仙卜算子 ⋅ 33分钟前 ⋅ 0

消息队列属性及常见消息队列介绍

什么是消息队列? 消息队列是在消息的传输过程中保存消息的容器,用于接收消息并以文件的方式存储,一个队列的消息可以同时被多个消息消费者消费。分布式消息服务DMS则是分布式的队列系统,消...

中间件小哥 ⋅ 35分钟前 ⋅ 0

java程序员使用web3j进行以太坊开发详解

如何使用web3j为Java应用或Android App增加以太坊区块链支持,教程内容即涉及以太坊中的核心概念,例如账户管理包括账户的创建、钱包创建、交易转账,交易与状态、智能合约开发与交互、过滤器...

笔阁 ⋅ 36分钟前 ⋅ 0

vim编辑模式、vim命令模式

vim编辑模式 使用vim filename 进入的界面是一般模式,在这个模式下虽然我们能够查看,复制,剪切,粘贴,但是不能编辑新的内容,如何能直接写入东西呢?这就需要进入编辑模式了,从一般模式...

李超小牛子 ⋅ 38分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部