传名参数
传名参数(Call-by-Name Parameters)是一种特殊的参数传递方式,它允许我们将表达式作为参数传递给函数,并在需要时进行求值。传名参数使用 =>
符号来定义,以表示传递的是一个表达式而不是具体的值。下面是关于传名参数的解释和示例代码:
传名参数的特点是,在每次使用参数时都会重新求值表达式,而不是在调用函数时进行求值。这样可以延迟表达式的求值,只在需要时才进行计算。传名参数通常用于需要延迟计算、惰性求值或者需要按需执行的场景。
下面是一个示例代码:
def callByName(param: => Int): Unit = { println("Inside callByName") println("Param 1: " + param) println("Param 2: " + param) } def randomNumber(): Int = { println("Generating random number") scala.util.Random.nextInt(100) } callByName(randomNumber())
在上述示例中,定义了一个名为 callByName
的函数,它接受一个传名参数 param
。在函数体内,我们打印出两次参数的值。
另外,定义了一个名为 randomNumber
的函数,它用于生成随机数。在该函数内部,我们打印出生成随机数的消息,并使用 scala.util.Random.nextInt
方法生成一个介于 0 到 100 之间的随机数。
在主程序中,我们调用 callByName
函数,并将 randomNumber()
作为传名参数传递进去。
当程序执行时,会先打印出 "Inside callByName" 的消息,然后两次调用 param
,即 randomNumber()
。在每次调用时,都会重新生成一个新的随机数,并打印出相应的值。
这说明传名参数在每次使用时都会重新求值表达式,而不是在调用函数时进行求值。这样可以实现按需执行和延迟计算的效果。
implicit
implicit
关键字用于定义隐式转换和隐式参数。它可以用来简化代码,让编译器自动执行一些操作。
下面是一些使用 implicit
关键字的示例:
- 隐式转换:可以使用
implicit
关键字定义隐式转换函数,让编译器自动将一种类型的值转换为另一种类型的值。
implicit def intToString(x: Int): String = x.toString val x: String = 1 println(x) // 输出 "1"
在这个例子中,定义了一个隐式转换函数 intToString
,它接受一个 Int
类型的参数,并返回它的字符串表示。由于这个函数被定义为 implicit
,因此编译器会在需要时自动调用它。
在主程序中,我们将一个 Int
类型的值赋值给一个 String
类型的变量。由于类型不匹配,编译器会尝试寻找一个隐式转换函数来将 Int
类型的值转换为 String
类型的值。在这个例子中,编译器找到了我们定义的 intToString
函数,并自动调用它将 1
转换为 "1"
。
- 隐式参数:可以使用
implicit
关键字定义隐式参数,让编译器自动为方法提供参数值。
implicit val x: Int = 1 def foo(implicit x: Int): Unit = println(x) foo // 输出 1
在这个例子中,定义了一个隐式值 x
并赋值为 1
。然后我们定义了一个方法 foo
,它接受一个隐式参数 x
。
在主程序中,我们调用了方法 foo
,但没有显式地传入参数。由于方法 foo
接受一个隐式参数,因此编译器会尝试寻找一个隐式值来作为参数传入。在这个例子中,编译器找到了我们定义的隐式值 x
并将其作为参数传入方法 foo
。
Object和Class
在Scala中,class
和 object
都可以用来定义类型,但它们之间有一些重要的区别。class
定义了一个类,它可以被实例化。每次使用 new
关键字创建一个类的实例时,都会创建一个新的对象。
class MyClass(x: Int) { def printX(): Unit = println(x) } val a = new MyClass(1) val b = new MyClass(2) a.printX() // 输出 1 b.printX() // 输出 2
构造器可以通过提供一个默认值来拥有可选参数:
class Point(var x: Int = 0, var y: Int = 0) val origin = new Point // x and y are both set to 0 val point1 = new Point(1) println(point1.x) // prints 1
在这个版本的Point
类中,x
和y
拥有默认值0
所以没有必传参数。然而,因为构造器是从左往右读取参数,所以如果仅仅要传个y
的值,你需要带名传参。
class Point(var x: Int = 0, var y: Int = 0) val point2 = new Point(y=2) println(point2.y) // prints 2
而 object
定义了一个单例对象。它不能被实例化,也不需要使用 new
关键字创建。在程序中,一个 object
只有一个实例。此外,object
中定义的成员都是静态的,这意味着它们可以在不创建实例的情况下直接访问。而 class
中定义的成员只能在创建实例后访问。
object MyObject { val x = 1 def printX(): Unit = println(x) } MyObject.printX() // 输出 1
另外,在Scala中,如果一个 object
的名称与一个 class
的名称相同,那么这个 object
被称为这个 class
的伴生对象。伴生对象和类可以相互访问彼此的私有成员:
class MyClass(x: Int) { private val secret = 42 def printCompanionSecret(): Unit = println(MyClass.companionSecret) } object MyClass { private val companionSecret = 24 def printSecret(c: MyClass): Unit = println(c.secret) } val a = new MyClass(1) a.printCompanionSecret() // 输出 24 MyClass.printSecret(a) // 输出 42
在这个例子中,定义了一个类 MyClass
和它的伴生对象 MyClass
。类 MyClass
中定义了一个私有成员变量 secret
和一个方法 printCompanionSecret
,用于打印伴生对象中的私有成员变量 companionSecret
。而伴生对象 MyClass
中定义了一个私有成员变量 companionSecret
和一个方法 printSecret
,用于打印类 MyClass
的实例中的私有成员变量 secret
。
在主程序中,创建了一个类 MyClass
的实例 a
,并调用了它的 printCompanionSecret
方法。然后我们调用了伴生对象 MyClass
的 printSecret
方法,并将实例 a
作为参数传入。
这就是Scala中类和伴生对象之间互相访问私有成员的基本用法。
样例类
样例类(case class)是一种特殊的类,常用于描述不可变的值对象(Value Object)。它们非常适合用于不可变的数据。定义一个样例类非常简单,只需在类定义前加上case
关键字即可。例如,下面是一个简单的样例类定义:
case class Person(var name: String, var age: Int)
创建样例类的实例时,不需要使用new
关键字,直接使用类名即可。例如,下面是一个创建样例类实例并修改其成员变量的示例:
object Test01 { case class Person(var name: String, var age: Int) def main(args: Array[String]): Unit = { val z = Person("张三", 20) z.age = 23 println(s"z = $z") } }
_(下划线)
在Scala中,下划线 _
是一个特殊的符号,它可以用在许多不同的地方,具有不同的含义。
- 作为通配符:下划线可以用作通配符,表示匹配任意值。例如,在模式匹配中,可以使用下划线来表示匹配任意值。
x match { case 1 => "one" case 2 => "two" case _ => "other" }
- 作为忽略符:下划线也可以用来忽略不需要的值。例如,在解构赋值时,可以使用下划线来忽略不需要的值。
val (x, _, z) = (1, 2, 3)
- 作为函数参数占位符:下划线还可以用作函数参数的占位符,表示一个匿名函数的参数。例如,在调用高阶函数时,可以使用下划线来简化匿名函数的定义。
val list = List(1, 2, 3) list.map(_ * 2)
- 将方法转换为函数:在方法名称后加一个下划线,会将其转化为偏应用函数(partially applied function),就能直接赋值了。
def add(x: Int, y: Int) = x + y val f = add _
这只是下划线在Scala中的一些常见用法。由于下划线在不同的上下文中具有不同的含义,因此在使用时需要根据具体情况进行判断。
println
println
函数用于向标准输出打印一行文本。它可以接受多种不同类型的参数,并将它们转换为字符串进行输出。
下面是一些常见的使用 println
函数进行输出的方式:
- 输出字符串:直接将字符串作为参数传入
println
函数,它会将字符串原样输出。
println("Hello, world!")
- 输出变量:将变量作为参数传入
println
函数,它会将变量的值转换为字符串并输出。
val x = 1 println(x)
- 输出表达式:将表达式作为参数传入
println
函数,它会计算表达式的值并将其转换为字符串输出。
val x = 1 val y = 2 println(x + y)
- 使用字符串插值:可以使用字符串插值来格式化输出。在字符串前加上
s
前缀,然后在字符串中使用${expression}
的形式来插入表达式的值。
val name = "Alice" val age = 18 println(s"My name is $name and I am $age years old.")
这些是 println
函数的一些常见用法。你可以根据需要使用不同的方式来格式化输出。
集合
在Scala中,集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable,所以Scala中的集合都可以使用 foreach方法。在Scala中集合有可变(mutable)和不可变(immutable)两种类型。
List
如我们可以使用如下方式定义一个List,其他集合类型的定义方式也差不多。
object Main { def main(args: Array[String]): Unit = { // 定义一个空的字符串列表 var emptyList: List[String] = List() // 定义一个具有数据的列表 var intList = List(1, 2, 3, 4, 5, 6) // 定义空列表 var emptyList2 = Nil // 使用::运算符连接元素 var numList = 1 :: (2 :: (3 :: Nil)) println(emptyList) println(intList) println(emptyList2) println(numList) } } 输出: List() List(1, 2, 3, 4, 5, 6) List() List(1, 2, 3)
下面是一些List的常用方法:
val list = List(1, 2, 3, 4) // 获取列表的长度 val length = list.length // 获取列表的第一个元素 val first = list.head // 获取列表的最后一个元素 val last = list.last // 获取列表除第一个元素外剩余的元素 val tail = list.tail // 获取列表除最后一个元素外剩余的元素 val init = list.init // 反转列表 val reversed = list.reverse // 在列表头部添加元素 val newList1 = 0 +: list // 在列表尾部添加元素 val newList2 = list :+ 5 // 连接两个列表 val list1 = List(1, 2) val list2 = List(3, 4) val concatenatedList = list1 ++ list2 // 检查列表是否为空 val isEmpty = list.isEmpty // 检查列表是否包含某个元素 val containsElement = list.contains(1) // 过滤列表中的元素 val filteredList = list.filter(_ > 2) // 映射列表中的元素 val mappedList = list.map(_ * 2) // 折叠列表中的元素(从左到右) val sum1 = list.foldLeft(0)(_ + _) // 折叠列表中的元素(从右到左) val sum2 = list.foldRight(0)(_ + _) // 拉链操作 val names = List("Alice", "Bob", "Charlie") val ages = List(25, 32, 29) val zipped = names.zip(ages) // List(("Alice", 25), ("Bob", 32), ("Charlie", 29)) // 拉链操作后解压缩 val (unzippedNames, unzippedAges) = zipped.unzip // (List("Alice", "Bob", "Charlie"), List(25, 32, 29))
更多方法不再赘述,网上很容易查阅到相关文章。
Map
object Main { def main(args: Array[String]): Unit = { // 定义一个空的映射 val emptyMap = Map() // 定义一个具有数据的映射 val intMap = Map("key1" -> 1, "key2" -> 2) // 使用元组定义一个映射 val tupleMap = Map(("key1", 1), ("key2", 2)) println(emptyMap) println(intMap) println(tupleMap) } } 输出: Map() Map(key1 -> 1, key2 -> 2) Map(key1 -> 1, key2 -> 2)
下面是map常用的一些方法:
val map = Map("key1" -> 1, "key2" -> 2) // 获取映射的大小 val size = map.size // 获取映射中的所有键 val keys = map.keys // 获取映射中的所有值 val values = map.values // 检查映射是否为空 val isEmpty = map.isEmpty // 检查映射是否包含某个键 val containsKey = map.contains("key1") // 获取映射中某个键对应的值 val value = map("key1") // 获取映射中某个键对应的值,如果不存在则返回默认值 val valueOrDefault = map.getOrElse("key3", 0) // 过滤映射中的元素 val filteredMap = map.filter { case (k, v) => v > 1 } // 映射映射中的元素 val mappedMap = map.map { case (k, v) => (k, v * 2) } // 遍历映射中的元素 map.foreach { case (k, v) => println(s"key: $k, value: $v") }
这里的case
关键字起到匹配的作用。
Range
Range
属于序列(Seq
)这一类集合的子集。它表示一个整数序列,可以用来遍历一个整数区间内的所有整数。例如,1 to 5
表示一个从1到5的整数序列,包括1和5。
Range常见于for循环中,如下可定义一个Range:
// 定义一个从1到5的整数序列,包括1和5 val range1 = 1 to 5 // 定义一个从1到5的整数序列,包括1但不包括5 val range2 = 1 until 5 // 定义一个从1到10的整数序列,步长为2 val range3 = 1 to 10 by 2 // 定义一个从10到1的整数序列,步长为-1 val range4 = 10 to 1 by -1
如果我们想把Range转为List,我们可以这样做:
val range = 1 to 5 val list = range.toList
Range
继承自Seq
,因此它拥有Seq
的所有常用方法,例如length
、head
、last
、tail
、init
、reverse
、isEmpty
、contains
、filter
、map
、foldLeft
和foldRight
等。它还拥有一些特殊的方法,例如:
val range = 1 to 10 by 2 // 获取序列的起始值 val start = range.start // 获取序列的结束值 val end = range.end // 获取序列的步长 val step = range.step // 获取一个包括结束值的新序列 val inclusiveRange = range.inclusive