Scala学习之——变量、函数、方法及两者联系

最近刚开始学习Scala,常被其“古怪”的语法、丰富的操作符、各式的函数操作方法、灵活的使用方式多雷到。这不刚开始,还没有入门就被其变量搞得有点迷糊了。在这里特意,写下这篇笔记,以作提醒铭记。

1 变量

Scala中,有两种类型的变量——val类型和var类型。

1.1 类型定义

在Java中,变量类型写在名称之前;而在Scala中,变量类型写在其名称之后,重甲使用英文冒号:分隔。

scala> var str:String  //变量必须被初始化之后才能使用
<console>:7: error: only classes can have declared but undefined members
(Note that variables need to be initialized to be defined)
       var str:String
           ^

scala> var str:String = "hello"
str: String = hello

scala> str = "How are you!"
str: String = How are you!

从上面的代码中,可以发现,在Scala中变量(包括var和val类型的变量)必须初始化之后才能被使用,否则将报错。

1.2 val类型变量

val类似于Java里面的final变量,可以将val类型的变量简单记忆为value(数值)类型的变量,一旦初始化,val就不能再被赋予新值了,就相当于一旦赋予一个val类型的变量(比如,定义一张纸币一定的价值)具体的值,就无法再改变其值了。

//val类型变量的定义与使用
scala> val a = 112  //定义不可变类型变量                          
a: Int = 112
scala> a = 13       //不可变类型变量一旦初始化,就不能再赋值
<console>:8: error: reassignment to val
       a = 13
         ^
scala> val a = 13   /* 注意:这是又定义一个名字同为a的不可变类型的局部变量,不是重新赋值,重新赋值时变量前不带val关键字的 */
a: Int = 13

1.3 var类型变量

相反,var如同Java里面的非final变量。可以将其简记为variable(可变;变量),其在初始化后可以在它的生命周期中被多次重新赋值。注意,仔细看val和var变量使用过程的区别。

//var类型变量的定义及使用
scala> var b = 112 //定义可变类型的变量
b: Int = 112
scala> b =13       //可以给可变类型变量重新赋新值
b: Int = 13
scala> var b = 36  //又重新定义局部变量
b: Int = 36

1.4 类型推断

在Scala中,为什么没有给变量具体的是Int、String还是其他类型的定义,而结果显示有具体的类型呢?这是因为Scala有类型推断的能力,它能让Scala编译器可以自动理解省略的具体的类型。当然,在定义变量的时候也可以带上具体的类型。

scala> val ms: String = "Hello, World!"
ms: String = Hello, World!

scala> val num:Double = 36.6
num: Double = 36.6

2 函数的定义与使用

2.1 粗略认识函数

在Scala中,函数的定义以def关键字开始,然后是函数名,跟着是括号里使用冒号分隔的参数列表即函数的返回类型。注意:在函数中,每个参数都必须使用带有前缀冒号的类型标注,因为Scala编译器及解释器无法推断出函数的参数类型。如下maxInt函数的定义:

//Scala中函数定义
def maxInt(x: Int, y: Int): Int = {
    if(x > y) x
    else y
}

//Scala中函数调用
val m = maxInt(6, 9)
println(m)

上面的示例中,maxInt函数带有两个参数:x和y,都是Int类型。在函数参数列表之后,会使用“: 返回值类型 = ”的形式声明函数的返回值类型,之后是使用扩括号括起来的函数体。注意,如果没有返回值,可以使用Unit表示返回类型。在Scala中,返回值的时候可以不带return,如上面示例中的if-else表达式中返回的值之前都没有带return。来跟Java代码直观地比较一下吧:

//Java中maxInt函数的定义
public int maxInt(int x, int y){
    if(x > y) 
        return x;
    else 
        return y;
}

//Java中maxInt函数的调用
int m = maxInt(6, 9);
System.out.println(m);

函数花括号前的等号提示我们,按照函数式编程的风格来看,函数是对能产生值的表达式的定义。
有时候Scala编译器需要函数结果类型的定义,比如,如果函数是递归的(方法调用自身),那么函数结果类型就必须被明确地说明。在上面maxInt的示例中,即使不写结果类型,编译器也可以推断出。同样,如果函数仅包含一个语句,那么连花括号也可以不用带。这样maxInt函数就能够这些写:

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

一旦函数定义完成,就可以通过函数名调用,如maxInt(3, 7)。
以下是既不带参数也不返回有用结果的函数的定义:

scala> def greet() = println("Hello, World!")
greet: ()Unit

scala> def sayHello(): Unit = { println("Hello!") }
sayHello: ()Unit

从上面的两个函数中,可以发现函数不返回结果时,可以使用Unit当做返回值的类型,也可以不写,同样都表明函数没有有效的返回值。有没有发现Scala中的Unit类型与Java中的void类型相似,同样都是面向对象类型的编程语言难免有相互借鉴的地方。

关于Scala中,对于函数更多更详细的介绍,可以参考 Scala菜鸟教程http://www.runoob.com/scala/scala-functions.html 。为了方便学习和记忆,现在将其也一块整理如下:

2.2 函数的详细介绍

2.2.1 Scala 函数定义

函数是一组一起执行一个任务的语句。 您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的,但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。
Scala 有函数和方法,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。
我们可以在任何地方定义函数,甚至可以在函数内定义函数(内嵌函数)。更重要的一点是 Scala 函数名可以有以下特殊字符:+, ++, ~, &,-, – , , /, : 等。

2.2.2 函数声明

Scala 函数声明格式如下:
def functionName ([参数列表]) : [return type]
如果你不写等于号和方法主体,那么方法会被隐式声明为”抽象(abstract)”,这样包含它的类型也同样是一个抽象类型。

2.2.3 函数定义

方法定义由一个def 关键字开始,紧接着是可选的参数列表,一个冒号”:” 和方法的返回类型,一个等于号”=”,最后是方法的主体。
Scala 函数定义格式如下:

def functionName ([参数列表]) : [return type] = {
   function body
   return [expr]
}

以上代码中 return type 可以是任意合法的 Scala 数据类型。参数列表中的参数可以使用逗号分隔。
以下函数的功能是将两个传入的参数相加并求和:

object add{
   def addInt( a:Int, b:Int ) : Int = {
      var sum:Int = 0
      sum = a + b
      return sum
   }
}

如果函数没有返回值,可以返回为 Unit,这个类似于 Java 的 void, 实例如下:

object Hello{
   def printMe( ) : Unit = {
      println("Hello, Scala!")
   }
}

2.2.4 函数调用

Scala 提供了多种不同的函数调用方式,以下是调用方法的标准格式:
functionName( 参数列表 )
如果函数使用了实例的对象来调用,我们可以使用类似java的格式 (使用 . 号)去调用:
[instance.]functionName( 参数列表 )
以上实例演示了定义与调用函数的实例:

object Test {
   def main(args: Array[String]) {
        println( "Returned Value : " + addInt(5,7) );
   }
   def addInt( a:Int, b:Int ) : Int = {
      var sum:Int = 0
      sum = a + b

      return sum
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala 
$ scala Test
Returned Value : 12

Scala也是一种函数式语言,所以函数是 Scala 语言的核心。以下一些函数概念有助于我们更好的理解 Scala 编程。

2.3 函数中的一些概念

2.3.1 函数传名调用(call-by-name)

Scala的解释器在解析函数参数(function arguments)时有两种方式:

  • 传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;
  • 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部。

在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。

object Test {
   def main(args: Array[String]) {
        delayed(time());
   }

   def time() = {
   //对于函数返回的基本数据类型,如Int、Long、Double等,编译器能够自动推断出类型,可以不用写返回类型;
   //对于嵌套的类型,自定义类型需要在函数参数列表后加上“:返回类型 = ”的方式带上返回类型。
      println("获取时间,单位为纳秒")
      System.nanoTime
   }
   def delayed( t: => Long ) = {
      println("在 delayed 方法内")
      println("参数: " + t)
      t
   }
}

以上实例中我们声明了 delayed 方法, 该方法在变量名和变量类型后使用 => 符号来设置传名调用。执行以上代码,输出结果如下:

$ scalac Test.scala 
$ scala Test
在 delayed 方法内
获取时间,单位为纳秒
参数: 241550840475831
获取时间,单位为纳秒

实例中 delay 方法打印了一条信息表示进入了该方法,接着 delay 方法打印接收到的值,最后再返回 t。

2.3.2 指定函数参数名

一般情况下函数调用参数,就按照函数定义时的参数顺序一个个传递。但是我们也可以通过指定函数参数名,并且不需要按照顺序向函数传递参数。同时,在定义函数参数时指定参数的参数和默认值,在调用时如果不写指定默认值的参数,那么编译器会使用指定默认值的参数。实例如下:

object Test {
   def main(args: Array[String]) {
        printInt(b=5, a=7);
   }
   def printInt( a:Int, b:Int ) = {
      println("Value of a : " + a );
      println("Value of b : " + b );
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
Value of a :  7
Value of b :  5

2.3.3 Scala 函数 - 可变长度参数

Scala 允许指明函数的最后一个参数可以是重复的,即我们不需要指定函数参数的个数,可以向函数传入可变长度的参数列表。
注意:Scala 通过在参数的类型之后放一个星号来设置可变参数(可重复的参数),而在Java中是使用三个英文的点号“…”表示可变参数的。例如:

object Test {
   def main(args: Array[String]) {
        printStrings("Runoob", "Scala", "Python");
   }
   def printStrings( args:String* ) = {
      var i : Int = 0;
      for( arg <- args ){
         println("Arg value[" + i + "] = " + arg );
         i = i + 1;
      }
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
Arg value[0] = Runoob
Arg value[1] = Scala
Arg value[2] = Python

2.3.4 Scala 递归函数

递归函数在函数式编程的语言中起着重要的作用,Scala 同样支持递归函数。
递归函数意味着函数可以调用它本身。
下面的实例使用递归函数来计算阶乘:

object Test {
   def main(args: Array[String]) {
      for (i <- 1 to 10)
         println(i + " 的阶乘为: = " + factorial(i) )
   }

   def factorial(n: BigInt): BigInt = {  
      if (n <= 1)
         1  
      else    
      n * factorial(n - 1)
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
1 的阶乘为: = 1
2 的阶乘为: = 2
3 的阶乘为: = 6
4 的阶乘为: = 24
5 的阶乘为: = 120
6 的阶乘为: = 720
7 的阶乘为: = 5040
8 的阶乘为: = 40320
9 的阶乘为: = 362880
10 的阶乘为: = 3628800

2.3.5 Scala 函数 - 默认参数值

Scala 可以为函数参数指定默认参数值,使用了默认参数,在调用函数的过程中可以不写要传递的参数,这时函数就会调用它的默认参数值,如果传递了参数,则传递值会取代默认值。实例如下:

object Test {
   def main(args: Array[String]) {
        println( "返回值 : " + addInt() );    //调用时没有指定要传递的参数,这是会使用定义时给定的默认参数
   }
   def addInt( a:Int=5, b:Int=7 ) : Int = {
      var sum:Int = 0
      sum = a + b

      return sum
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
返回值 : 12

2.3.6 Scala 高阶函数

高阶函数(Higher-Order Function)就是操作其他函数的函数。Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。
以下实例中,apply() 函数使用了另外一个函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v:

object Test {
   def main(args: Array[String]) {

      println( apply( layout, 10) )

   }
   // **函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v,这里的函数f使用的就是传名调用**
   def apply(f: Int => String, v: Int) = f(v)

   def layout[A](x: A) = "[" + x.toString() + "]"

}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
[10]

2.3.7 函数嵌套

在 Scala中,可以在 函数内定义函数,定义在函数内的函数称之为局部函数。
以下实例我们实现阶乘运算,并使用内嵌函数:

object Test {
   def main(args: Array[String]) {
      //输出各个数的阶乘
      println( factorial(0) )
      println( factorial(1) )
      println( factorial(2) )
      println( factorial(3) )
   }

   def factorial(i: Int): Int = {
      def fact(i: Int, accumulator: Int): Int = {
         if (i <= 1)
            accumulator
         else
            fact(i - 1, i * accumulator)
      }
      fact(i, 1)
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
1
1
2
6

2.3.8 Scala 匿名函数

Scala 中定义匿名函数的语法很简单,箭头(=>)左边是参数列表,右边是函数体。使用匿名函数后,我们的代码变得更简洁了。
下面的表达式就定义了一个接受一个Int类型输入参数的匿名函数:
var inc = (x:Int) => x+1 //相当于数学中,定义的函数表达式inc = f(x) = x + 1
上述定义的匿名函数,其实是下面这种写法的简写:

def add2 = new Function1[Int,Int]{  
    def apply(x:Int):Int = x+1;  
} 

以上实例的 inc 现在可作为一个函数,使用方式如下:
var x = inc(7)-1
同样我们可以在匿名函数中定义多个参数
var mul = (x: Int, y: Int) => x*y
mul 现在可作为一个函数,使用方式如下:
println(mul(3, 4))
也可以不给匿名函数设置参数,需要不带参数的使用圆括号,记住这个圆括号不能省略,如下所示:
var userDir = () => { System.getProperty(“user.dir”) }
userDir 现在可作为一个函数,使用方式如下:
println( userDir() )
示例:

object Demo {
   def main(args: Array[String]) {
      println( "multiplier(1) value = " +  multiplier(1) )
      println( "multiplier(2) value = " +  multiplier(2) )
   }
   var factor = 3
   val multiplier = (i:Int) => i * factor
}

将以上代码保持到 Demo.scala 文件中,执行以下命令:

$ scalac Demo.scala
$ scala Demo
//输出结果为:
multiplier(1) value = 3
multiplier(2) value = 6

2.3.9 Scala 偏应用函数

Scala 偏应用函数是一种表达式,不必提供函数需要的所有参数,只需要提供部分,或不提供所需参数。
如下实例,我们打印日志信息:

import java.util.Date

object Test {
   def main(args: Array[String]) {
      val date = new Date
      log(date, "message1" )
      Thread.sleep(1000)
      log(date, "message2" )
      Thread.sleep(1000)
      log(date, "message3" )
   }

   def log(date: Date, message: String)  = {
     println(date + "----" + message)
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
Mon Dec 02 12:52:41 CST 2013----message1
Mon Dec 02 12:52:41 CST 2013----message2
Mon Dec 02 12:52:41 CST 2013----message3

实例中,log() 方法接收两个参数:date 和 message。我们在程序执行时调用了三次,参数 date 值都相同,message 不同。
我们可以使用偏应用函数优化以上方法,绑定第一个 date 参数,第二个参数使用下划线(_)替换缺失的参数列表,并把这个新的函数值的索引赋给变量。以上实例修改如下:

import java.util.Date

object Test {
   def main(args: Array[String]) {
      val date = new Date
      val logWithDateBound = log(date, _ : String)

      logWithDateBound("message1" )
      Thread.sleep(1000)
      logWithDateBound("message2" )
      Thread.sleep(1000)
      logWithDateBound("message3" )
   }

   def log(date: Date, message: String)  = {
     println(date + "----" + message)
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
Mon Dec 02 12:53:56 CST 2013----message1
Mon Dec 02 12:53:56 CST 2013----message2
Mon Dec 02 12:53:56 CST 2013----message3

2.3.10 函数柯里化(Currying)

柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
首先,定义一个函数:
def add(x:Int,y:Int)= x + y
那么我们应用的时候,应该是这样用:add(1,2)
现在我们把这个函数变一下形:
def add(x:Int)(y:Int) = x + y
那么我们应用的时候,应该是这样用:add(1)(2),最后结果都一样是3,这种方式(过程)就叫柯里化。
实现过程:
add(1)(2) 实际上是依次调用两个普通函数(非柯里化函数),第一次调用一个参数 x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值。
实质上最先演变成这样一个方法:
def add(x:Int)=(y:Int)=>x+y
那么这个函数是什么意思呢? 接收一个x为参数,返回一个匿名函数,该匿名函数的定义是:接收一个Int型参数y,函数体为x+y。现在我们来对这个方法进行调用。
val result = add(1)
返回一个result,那result的值应该是一个匿名函数:(y:Int)=>1+y
所以为了得到结果,我们继续调用result。
val sum = result(2)
最后,打印出来的结果就是3。

下面是一个完整实例:

object Test {
   def main(args: Array[String]) {
      val str1:String = "Hello, "
      val str2:String = "Scala!"
      println( "str1 + str2 = " +  strcat(str1)(str2) )
   }

   def strcat(s1: String)(s2: String) = {
      s1 + s2
   }
}

执行以上代码,输出结果为:

$ scalac Test.scala
$ scala Test
str1 + str2 = Hello, Scala!

3 方法与函数的区别

由于Scala是函数式编程语言,而在函数式编程语言中,函数是“头等公民”,它可以像任何其他数据类型一样被传递和操作 ,因此在平常的使用时也常将方法也泛称为函数,如第2节所说的函数许多是不合适的。
在Scala中函数和方法有什么区别,平时使用中也做太多的关注,这里也趁着这篇文章了解、总结一下。

方法和函数是作为两种东西在scala中存在的,在大多数时候他们差不太多,但是时不时的可能会有一些问题体现出他们各自不一样的情况。

函数类型:形式为(T1,…, Tn) => U,其实是FunctionN trait的简写形式,匿名函数和方法值具有这种类型(其实可以作为方法类型的一部分)。
方法类型:一个非值类型(non-value type),里边是没有值的,一个方法值具有函数类型,用def定义。

1.方法可以作为一个表达式的一部分出现(调用函数并传参),但(带参方法)不能作为最终的表达式,函数可以作为最终的表达式出现:

scala> def m(x:Int) = x*x        //定义一个方法
m: (x: Int)Int

scala> m(3)                      //方法调用
res5: Int = 9

scala> val f = (x: Int) => x*x   //定义一个函数
f: Int => Int = <function1>

scala> f(3)                      //调用函数
res6: Int = 9

scala> f                         //函数可以作为最终表达式出现
res7: Int => Int = <function1>

scala> m                         //方法不能作为最终表达式出现
<console>:9: error: missing arguments for method m in object $iw;
follow this method with `_' if you want to treat it as a partially applied function
              m
              ^

2.方法可以没有参数列表,函数必须有,也就是说参数列表方法可省略,函数不可省略。**

scala> def m1 = 136
m1: Int
scala> def m2() = 136
m2: ()Int
//上面的m1和m2都是方法,第一种省略了参数列表,第二种没有省略,但这种方法接收参数的个数都为0

scala> val f1 = () => 136     //定义参数列表为空的函数
f1: () => Int = <function0>

scala> f1                     //调用函数名,返回函数的返回类型
res10: () => Int = <function0>

scala> f1()                   //调用参数列表为空的函数,返回相应的值
res11: Int = 136

scala> val f2 = => 136        //没有这种写法,函数必须有参数列表,即使为空,代表参数列表的圆括号也不能省略
<console>:1: error: illegal start of simple expression
       val f2 = => 136
                ^

3.函数名后必须加括号才代表函数调用,否则为该函数本身,而方法名后不加括号为方法调用。
方法名意味着方法调用,函数名只是代表函数自身
因为方法不能作为最终的表达式存在,所以如果你写了一个方法的名字并且该方法不带参数(没有参数列表或者无参),该表达式的意思是:调用该方法得到最终的表达式。
而函数可以作为最终表达式出现,如果直接使用函数的名字,函数
调用并不会发生,该方法自身将作为最终的表达式进行返回,如果要强制调用一个函数,你必须在函数名后面写()。

scala> def m2 = 100              //定义没有参数列表的方法
m2: Int

scala> def m3() = 100            //定义有一个为空的参数列表的方法
m3: ()Int

scala> var f1 = => 100           //函数必须有参数列表,否则报错
<console>:1: error: illegal start of simple expression
       var f1 = => 100
                ^

scala> var f2 = () => 100        //函数可以有一个空的参数列表
f2: () => Int = <function0>

scala> m2                        //该方法没有参数列表
res12: Int = 100

scala> m3                        //该方法有一个空的参数列表
res13: Int = 100

scala> f2                         //得到函数自身,不会发生函数调用
res16: () => Int = <function0>

scala> f2()                       //调用函数
res17: Int = 100

4.方法可以进行eta展开,即自动转换为函数

5.方法不是值,函数是值,所以方法不能绑定给一个val变量,函数可以

6.方法有重载情况时,如果自动将方法转换为函数,需要指定参数和返回值类型

7.方法可以使用参数序列,转换称函数使用Seq对象

8.方法支持默认参数值,函数不能省略参数,不支持

下面通过几张图片再来比较一下方法,函数,及其转换:
①定义方法
这里写图片描述

②定义函数
这里写图片描述

③方法和函数的比较
这里写图片描述

④将方法转换为函数
将方法转换为函数
可以在方法名之后,加上一个空格,然后再加上一个下划线的形式,变成函数,赋值给一个变量,最后通过该变量名,带上参数列表调用该函数,运行过程如下:

scala> def ml(x: Int, y: Int) : Int = x * y
ml: (x: Int, y: Int)Int

scala> val a = ml(3,6)
a: Int = 18

scala> val f1 = ml _
f1: (Int, Int) => Int = <function2>

scala> f1(3, 6)
res18: Int = 18

参考:

Scala 函数 | 菜鸟教程
http://www.runoob.com/scala/scala-functions.html

scala中方法和函数的区别 - 方文才 - 博客园
https://www.cnblogs.com/fangwencai/p/4859662.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

love666666shen

谢谢您的鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值