Scala学习笔记一

avatar
作者
筋斗云
阅读量:0

文章目录

Scala包

scala包和java包的声明一样在源文件的第一行;

scala包的导入和java包有些不同,java导入包使用import关键字
scala导入包也是import但是有几种不同的写法:

  1. 导入某个类:和java一样
  2. 导入某个包下的所有的类

    这个scala和java的写法有些不一样,java是import java.util.*;这种写法,但是scala不能这种写法而是 import java.util._;
  3. 导入某个包下的某几个类

    java需要一个类一个类去导入,scala可以这样些
import java.uitl.{List,ArrayList} import java.awt.{Color, Font} // 重命名成员 import java.util.{HashMap => JavaHashMap} // 隐藏成员 import java.util.{HashMap => _, _} // 引入了util包的所有成员,但是HashMap被隐藏了 

包的定义:

Scala 使用 package 关键字定义包,在Scala将代码定义到某个包中有两种方式:

  1. 第一种方法和 Java 一样,在文件的头定义包名,这种方法就后续所有代码都放在该包中。 比如:
package com.runoob  class HelloWorld{} 
  1. 第二种方法有些类似 C#,如:
package com.runoob {  	 class HelloWorld {} 	} 

第二种方法,可以在一个文件中定义多个包。

注意:默认情况下,Scala 总会引入 java.lang._ 、 scala._ 和 Predef._,这里也能解释,为什么以scala开头的包,在使用时都是省去scala.的。

Scala 关键字

abstractcasecatchclass
defdoelseextends
falsefinalfinallyfor
forSomeifimplicitimport
lazymatchnewnull
objectoverridepackageprivate
protectedreturnsealedsuper
thisthrowtraittry
truetypevalvar
whilewithyield
  • | : | = | =>
    <- | <: |<% | >:

| @ | |

变量定义

数据类型

Scala与Java具有相同的数据类型,具有相同的内存占用和精度。以下是提供Scala中可用的所有数据类型的详细信息的表格:

序号数据类型说明
1Byte8位有符号值,范围从-128至127
2Short16位有符号值,范围从-32768至32767
3Int32位有符号值,范围从-2147483648至2147483647
4Long64位有符号值,范围从-9223372036854775808至9223372036854775807
5Float32位IEEE 754单精度浮点值
6Double64位IEEE 754双精度浮点值
7Char16位无符号Unicode字符。范围从U+0000到U+FFFF
8String一个Char类型序列
9Boolean文字值true或文字值false
10Unit对应于无值
11Nullnull或空引用
12Nothing每种其他类型的亚型; 不包括无值
13Any任何类型的超类型; 任何对象的类型为Any
14AnyRef任何引用类型的超类型

上面列出的所有数据类型都是对象。

Scala中没有类似Java中那样的原始类型。这意味着您可以调用Int,Long等方法。

val 定义变量相当于java 的final不可变

声明变量类型

val x = 1 val s = "a string" val p = new Person("Regina") 

val x: Int = 1 val s: String = "a string" val p: Person = new Person("Regina") 

几种内置类型

Scala带有您期望的标准数字数据类型。在Scala中,所有这些数据类型都是成熟的对象(不是原始数据类型)。

这些示例说明如何声明基本数字类型的变量:

val b: Byte = 1 val x: Int = 1 val l: Long = 1 val s: Short = 1 val d: Double = 2.0 val f: Float = 3.0 

在第四个例子,如果你没有明确指定类型,数量1将默认为Int,所以如果你想在其他数据类型中的一种- Byte,Long或者Short-你需要显式声明的类型,如图所示。带小数的数字(如2.0)将默认为a Double,因此,如果需要a Float,则需要声明a Float,如最后一个示例所示。

BigInt和BigDecimal

For large numbers Scala also includes the types BigInt and BigDecimal:

var b = BigInt(1234567890) var b = BigDecimal(123456.789) 

String and Char

Scala also has String and Char data types, which you can generally declare with the implicit form:

val name = "Bill" val c = 'a' 

Scala字符串有很多不错的特性,但是我们想花点时间来强调两个特性,我们将在本书的其余部分中使用它们。第一个特性是Scala有一个很好的、类似Ruby的方法来合并多个字符串。考虑到这三个变量:

val firstName = "John" val mi = 'C' val lastName = "Doe" 

you can append them together like this, if you want to:

val name = firstName + " " + mi + " " + lastName 

However, Scala provides this more convenient form:

val name = s"$firstName $mi $lastName" 

如图所示,您只需在字符串前面加上字母s,然后在字符串中变量名前面加上$符号。此功能称为string interpolation

控制结构

与Java和许多其他语言不同,if / else构造返回一个值,因此,除其他外,您可以将其用作三元运算符:

val x = if (a < b) a else b 

match表达式

Scala有一个match表达式,其最基本的用法就像一个Java switch语句:

val result = i match {     case 1 => "one"     case 2 => "two"     case _ => "not 1 or 2" } 

这match是用作方法主体并针对许多不同类型进行匹配的示例:

def getClassAsString(x: Any):String = x match {     case s: String => s + " is a String"     case i: Int => "Int"     case f: Float => "Float"     case l: List[_] => "List"     case p: Person => "Person"     case _ => "Unknown" } 

try-catch-finally

Scala的try / catch控制结构使您可以捕获异常。它类似于Java,但是其语法与match表达式一致:

try {     writeToFile(text) } catch {     case fnfe: FileNotFoundException => println(fnfe)     case ioe: IOException => println(ioe) } 
try {     // your scala code here }  catch {     case foo: FooException => handleFooException(foo)     case bar: BarException => handleBarException(bar)     case _: Throwable => println("Got some other kind of Throwable exception") } finally {     // your scala code here, such as closing a database connection     // or file handle } 

for循环和表达式

Scala for循环-我们通常在本书中将其称为for循环 -如下所示:

for (arg <- args) println(arg)  // "x to y" syntax for (i <- 0 to 5) println(i)  // "x to y by" syntax for (i <- 0 to 10 by 2) println(i) 

您还可以将yield关键字添加到for循环中,以创建产生结果的for表达式。这是一个for表达式,它将序列1到5中的每个值加倍:

val x = for (i <- 1 to 5) yield i * 2 //返回一个Vector 

这是另一个针对字符串列表的for表达式:

val fruits = List("apple", "banana", "lime", "orange")  val fruitLengths = for {     f <- fruits     if f.length > 4 } yield f.length 

Using for and foreach with Maps

You can also use forand foreach when working with a Scala Map (which is similar to a Java HashMap). For example, given this Map of movie names and ratings:

val ratings = Map(     "Lady in the Water"  -> 3.0,      "Snakes on a Plane"  -> 4.0,      "You, Me and Dupree" -> 3.5 ) //for 循环 for ((name,rating) <- ratings) println(s"Movie: $name, Rating: $rating")  //或 for 表达式 ratings.foreach {     case(movie, rating) => println(s"key: $movie, value: $rating") } 

while do/while

// while loop while(condition) {     statement(a)     statement(b) }  // do-while do {    statement(a)    statement(b) }  while(condition) 

Scala方法

就像其他OOP语言一样,Scala类也具有方法,这就是Scala方法语法的样子:

def sum(a: Int, b: Int): Int = a + b def concatenate(s1: String, s2: String): String = s1 + s2 

您不必声明方法的返回类型,因此,如果愿意,编写这两个方法是完全合法的:

def sum(a: Int, b: Int) = a + b def concatenate(s1: String, s2: String) = s1 + s2 

trait

class Dog(name: String) extends Speaker with TailWagger with Runner {     def speak(): String = "Woof!" } 

集合类

Scala集合的主要类别

描述
ArrayBuffer索引的可变序列
List线性(链表),不可变序列
Vector索引不变的序列
Map基Map(键/值对)类
Set基Set类

Map和Set有可变版本和不可变版本。

ArrayBuffer

这是一个可变序列,因此您可以使用其方法来修改其内容,并且这些方法类似于Java序列上的方法。

要使用,ArrayBuffer您必须先将其导入:

import scala.collection.mutable.ArrayBuffer  val ints = ArrayBuffer[Int]() val names = ArrayBuffer[String]() 

ArrayBuffer就可以通过多种方式向其中添加元素

val ints = ArrayBuffer[Int]() ints += 1 ints += 2 

这只是创建ArrayBuffer和添加元素的一种方法。您还可以ArrayBuffer使用以下初始元素创建一个:

val nums = ArrayBuffer(1, 2, 3) 

可以通过以下几种方法向其中添加更多元素ArrayBuffer:

// add one element nums += 4  // add multiple elements nums += 5 += 6  // add multiple elements from another collection nums ++= List(7, 8, 9) 

可以ArrayBuffer使用-=和–=方法从中删除元素:

// remove one element nums -= 9  // remove multiple elements nums -= 7 -= 8  // remove multiple elements using another collection nums --= Array(5, 6) 

其他方法

val a = ArrayBuffer(1, 2, 3)         // ArrayBuffer(1, 2, 3) a.append(4)                          // ArrayBuffer(1, 2, 3, 4) a.append(5, 6)                       // ArrayBuffer(1, 2, 3, 4, 5, 6) a.appendAll(Seq(7,8))                // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8) a.clear                              // ArrayBuffer()  val a = ArrayBuffer(9, 10)           // ArrayBuffer(9, 10) a.insert(0, 8)                       // ArrayBuffer(8, 9, 10) a.insertAll(0, Vector(4, 5, 6, 7))   // ArrayBuffer(4, 5, 6, 7, 8, 9, 10) a.prepend(3)                         // ArrayBuffer(3, 4, 5, 6, 7, 8, 9, 10) a.prepend(1, 2)                      // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) a.prependAll(Array(0))               // ArrayBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  val a = ArrayBuffer.range('a', 'h')  // ArrayBuffer(a, b, c, d, e, f, g) a.remove(0)                          // ArrayBuffer(b, c, d, e, f, g) a.remove(2, 3)                       // ArrayBuffer(b, c, g)  val a = ArrayBuffer.range('a', 'h')  // ArrayBuffer(a, b, c, d, e, f, g) a.trimStart(2)                       // ArrayBuffer(c, d, e, f, g) a.trimEnd(2)                         // ArrayBuffer(c, d, e) 

List

List类是线性的,不可变的序列。这意味着它是一个您无法修改的链表。每当您想添加或删除List元素时,都可以List从一个现存的中创建一个新元素List。

列表赋值

val ints = List(1, 2, 3) val names = List("Joel", "Chris", "Ed") //也可以根据需要声明List的类型,尽管通常不必这样做: val ints: List[Int] = List(1, 2, 3) val names: List[String] = List("Joel", "Chris", "Ed")  val nums = List.range(0, 10) val nums = (1 to 10 by 2).toList val letters = ('a' to 'f').toList val letters = ('a' to 'f' by 2).toList 

Adding elements to a List

因为List是不可变的,所以不能向其中添加新元素。相反,您可以通过在现有元素之前或之后添加元素来创建新列表List。例如,鉴于此List:

val a = List(1,2,3)  //可以像这样将元素放在前面List: val b = 0 +: a  val e = List(-1, 0) ++: a 

访问元素

scala> var c = List(1,2,3,6,7) c: List[Int] = List(1, 2, 3, 6, 7)  scala> c(1) res0: Int = 2  scala> c(4) res1: Int = 7 

现在,IDE帮了我们很大的忙,但是记住这些方法名的一种方法是认为:字符表示序列所在的一侧,所以当您使用+:时,您知道列表需要在右侧,如下所示:

0+:a 

同样,当您使用+时,您知道列表需要在左侧:

a:+4 

因为List是一个链表类,所以不应该试图通过大列表的索引值来访问它们的元素。例如,如果列表中有一百万个元素,那么访问myList(999999)这样的元素将需要很长时间。如果要访问这样的元素,请改用VectorArrayBuffer

you can also create the exact same list this way:

  val list = 1 :: 2 :: 3 :: Nil 

This works because a List is a singly-linked list that ends with the Nil element.

Sequence methods

val nums = (1 to 10).toList val names = List("joel", "ed", "chris", "maurice") 

这是foreach方法:

scala> names.foreach(println) joel ed chris maurice 

这是filter方法,然后是foreach:

//这里的_代表每个元素 scala> nums.filter(_ < 4).foreach(println) 1 2 3 

以下是该map方法的一些示例

scala> val doubles = nums.map(_ * 2) doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)  scala> val capNames = names.map(_.capitalize) capNames: List[String] = List(Joel, Ed, Chris, Maurice)  scala> val lessThanFive = nums.map(_ < 5) lessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false) 

foldLeft:

scala> nums.foldLeft(0)(_ + _) res0: Int = 55  scala> nums.foldLeft(1)(_ * _) res1: Int = 3628800 

Vector

Vector类是一个索引的,不变的序列。

创建

可以通过以下几种方法创建Vector:

val nums = Vector(1, 2, 3, 4, 5)  val strings = Vector("one", "two")  val peeps = Vector(     Person("Bert"),     Person("Ernie"),     Person("Grover") ) 

因为Vector是不可变的,所以不能向其中添加新元素。相反,您可以通过将元素添加或添加到现有之前创建新序列Vector。例如,鉴于此Vector

val a = Vector(1,2,3) //您可以添加以下元素:  val b = a :+ 4 //还有这个:  val b = a ++ Vector(4, 5) //也可以在前面加上这样的内容:  val b = 0 +: a //还有这个:  val b = Vector(-1, 0) ++: a 

Map

Scala具有可变和不变的Map类。这里,我们将展示如何使用可变的类。

创建

import scala.collection.mutable.Map  val states = Map(     "AK" -> "Alaska",     "IL" -> "Illinois",     "KY" -> "Kentucky" ) 

添加元素

states += ("AL" -> "Alabama")  states += ("AR" -> "Arkansas", "AZ" -> "Arizona")  states ++= Map("CA" -> "California", "CO" -> "Colorado") 

删除元素

tates -= "AR"  states -= ("AL", "AZ")  states --= List("AL", "AZ") 

更新地图元素

states("AK") = "Alaska, A Really Big State" 

遍历地图

val ratings = Map(     "Lady in the Water"-> 3.0,      "Snakes on a Plane"-> 4.0,     "You, Me and Dupree"-> 3.5 ) //for循环语法 for ((k,v) <- ratings) println(s"key: $k, value: $v")  //将match表达式与foreach方法一起使用 ratings.foreach {     case(movie, rating) => println(s"key: $movie, value: $rating") } 

Set

Scala具有可变和不变的Set类。在本课程中,我们将展示如何使用可变的类。

添加元素

添加到一个可变Set用+=,++=和add方法。

set += 1 set += 2 += 3 set ++= Vector(4, 5) 

删除元素

以使用-=和–=方法从集合中删除元素

set -= 1 set -= (2, 3) set --= Array(4,5) 

还有更多使用集合的方法,包括clear和remove:

Tuples

元组可让您将元素的异构集合放入一个小容器中。元组可以包含两个到22个值,并且它们都可以是不同的类型。

val t = (11, "Eleven", new Person("Eleven"))   t._1 t._2 t._3 

或将元组字段分配给变量:

val (num, string, person) = (11, "Eleven", new Person("Eleven")) 

表达式

for 表达式

首字母大写

val names = List("adam", "david", "frank") //yield 转换 val ucNames = for (name <- names) yield name.capitalize 

Success! Each name in the new variable ucNames is capitalized.

yield关键字

val doubledNums = for (n <- nums) yield n * 2  val ucNames = for (name <- names) yield name.capitalize 
val names = List("_adam", "_david", "_frank")  val capNames = for (name <- names) yield {     val nameWithoutUnderscore = name.drop(1)     val capName = nameWithoutUnderscore.capitalize     capName } 

match 表达式

Scala has a concept of a match expression. In the most simple case you can use a match expression like a Java switch statement:

// i is an integer i match {     case 1  => println("January")     case 2  => println("February")     case 3  => println("March")     case 4  => println("April")     case 5  => println("May")     case 6  => println("June")     case 7  => println("July")     case 8  => println("August")     case 9  => println("September")     case 10 => println("October")     case 11 => println("November")     case 12 => println("December")     // catch the default with a variable so you can print it     case _  => println("Invalid month") } 

match 表达式

val monthName = i match {     case 1  => "January"     case 2  => "February"     case 3  => "March"     case 4  => "April"     case 5  => "May"     case 6  => "June"     case 7  => "July"     case 8  => "August"     case 9  => "September"     case 10 => "October"     case 11 => "November"     case 12 => "December"     case _  => "Invalid month" } 

Using a match expression as the body of a method

正常的函数

def convertBooleanToStringMessage(bool: Boolean): String = {     if (bool) "true" else "false"         } 

match表达式

def convertBooleanToStringMessage(bool: Boolean): String = bool match {     case true => "you said true"     case false => "you said false" } 

Handling alternate cases

match表达式使您可以在单个case语句中处理多种情况。

为了说明这一点,假设您想像Perl编程语言那样对“布尔相等”进行求值:a 0或空白字符串求值为false,其他任何求值为true。这是您使用match表达式编写方法的方式,该表达式的计算结果为true和false:

example_1

def isTrue(a: Any) = a match {     case 0 | "" => false     case _ => true } 

example_2

val evenOrOdd = i match {     case 1 | 3 | 5 | 7 | 9 => println("odd")     case 2 | 4 | 6 | 8 | 10 => println("even")     case _ => println("some other number") } 

example_3

cmd match {     case "start" | "go" => println("starting")     case "stop" | "quit" | "exit" => println("stopping")     case _ => println("doing nothing") } 

Using if expressions in case statements

example_1

count match {     case 1 => println("one, a lonely number")     case x if x == 2 || x == 3 => println("two's company, three's a crowd")     case x if x > 3 => println("4+, that's a party")     case _ => println("i'm guessing your number is zero or less") } 

提高可读性可在if表达式添加括号

count match {     case 1 => println("one, a lonely number")     case x if (x == 2 || x == 3) => println("two's company, three's a crowd")     case x if (x > 3) => println("4+, that's a party")     case _ => println("i'm guessing your number is zero or less") } 

example_2

i match {   case a if 0 to 9 contains a => println("0-9 range: " + a)   case b if 10 to 19 contains b => println("10-19 range: " + b)   case c if 20 to 29 contains c => println("20-29 range: " + c)   case _ => println("Hmmm...") } 

example_3

stock match {   case x if (x.symbol == "XYZ" && x.price < 20) => buy(x)   case x if (x.symbol == "XYZ" && x.price > 50) => sell(x)   case x => doNothing(x) } 

CLASS

Basic class constructor

example_1

//这是一个Scala类,其构造函数定义了两个参数,firstName和lastName: class Person(var firstName: String, var lastName: String) //有了这个定义,您就可以Person像这样创建新的实例: val p = new Person("Bill", "Panner") //在类构造函数中定义参数会自动在该类中创建字段, //在本示例中,您可以像这样访问firstName和lastName字段: println(p.firstName + " " + p.lastName) 

在此示例中,由于两个字段都被定义为var字段,因此它们也是可变的,这意味着可以更改它们。这是您如何更改它们:

scala> p.firstName = "William" p.firstName: String = William  scala> p.lastName = "Bernheim" p.lastName: String = Bernheim 

val makes fields read-only

在第一个示例中,两个字段都被定义为var字段:

class Person(var firstName: String, var lastName: String) 

这使得这些字段可变。您还可以将它们定义为val字段,这使它们不可变:

class Person(val firstName: String, val lastName: String) 

Class constructors

在Scala中,类的主构造函数是以下各项的组合:

  • 构造函数参数
  • 在类主体中调用的方法
  • 在类主体中执行的语句和表达式

Other Scala class examples

class Pizza (var crustSize: Int, var crustType: String)  // a stock, like AAPL or GOOG class Stock(var symbol: String, var price: BigDecimal)  // a network socket class Socket(val timeout: Int, val linger: Int) {     override def toString = s"timeout: $timeout, linger: $linger" }  class Address (     var street1: String,     var street2: String,     var city: String,      var state: String ) 

辅助类构造函数

您可以通过定义名为this的方法来定义辅助类构造函数:

  • 每个辅助构造函数必须具有不同的签名(不同的参数列表)
  • 每个构造函数都必须调用先前定义的构造函数之一

Here’s an example of a Pizza class that defines multiple constructors:

val DefaultCrustSize = 12 val DefaultCrustType = "THIN"  // the primary constructor class Pizza (var crustSize: Int, var crustType: String) {      // one-arg auxiliary constructor     def this(crustSize: Int) {         this(crustSize, DefaultCrustType)     }      // one-arg auxiliary constructor     def this(crustType: String) {         this(DefaultCrustSize, crustType)     }      // zero-arg auxiliary constructor     def this() {         this(DefaultCrustSize, DefaultCrustType)     }      override def toString = s"A $crustSize inch pizza with a $crustType crust"  } 
val p1 = new Pizza(DefaultCrustSize, DefaultCrustType) val p2 = new Pizza(DefaultCrustSize) val p3 = new Pizza(DefaultCrustType) val p4 = new Pizza 

关于此示例,有两个重要说明:

  • 在DefaultCrustSize和DefaultCrustType变量不处理这种情况的优选方式,但因为我们并没有给出如何处理枚举的是,我们用这种方式来让事情变得简单。
  • 辅助类构造函数是一个很棒的功能,但是由于您可以将默认值用于构造函数参数,因此您不需要经常使用此功能。下一课演示如何使用这样的默认参数值构造函数

提供构造函数参数的默认值

Scala允许您提供构造函数参数的默认值。

example_1

class Socket(var timeout: Int, var linger: Int) {     override def toString = s"timeout: $timeout, linger: $linger" } 
class Socket(var timeout: Int = 2000, var linger: Int = 3000) {     override def toString = s"timeout: $timeout, linger: $linger" } 

通过提供参数的默认值,您现在可以Socket通过多种不同方式创建新的参数:

new Socket() new Socket(1000) new Socket(4000, 6000) 

Named parameters(命名参数)

class Socket(var timeout: Int, var linger: Int) {     override def toString = s"timeout: $timeout, linger: $linger" } 

可以这样创建一个新的Socket:

val s = new Socket(timeout=2000, linger=3000) 

该功能有时会派上用场,例如当所有类构造函数参数都具有相同的类型时(例如Int本示例中的参数)。例如,有些人发现此代码:

val s = new Socket(timeout=2000, linger=3000) 

比以下代码更具可读性:

val s = new Socket(2000, 3000) 

枚举

枚举是创建常量组的有用工具,这些常量组可以是星期几,一年中的月份,一副纸牌中的花色等,您可以使用一组相关的常量值。

sealed trait DayOfWeek case object Sunday extends DayOfWeek case object Monday extends DayOfWeek case object Tuesday extends DayOfWeek case object Wednesday extends DayOfWeek case object Thursday extends DayOfWeek case object Friday extends DayOfWeek 

披萨相关的枚举

sealed trait Topping case object Cheese extends Topping case object Pepperoni extends Topping case object Sausage extends Topping case object Mushrooms extends Topping case object Onions extends Topping  sealed trait CrustSize case object SmallCrustSize extends CrustSize case object MediumCrustSize extends CrustSize case object LargeCrustSize extends CrustSize  sealed trait CrustType case object RegularCrustType extends CrustType case object ThinCrustType extends CrustType case object ThickCrustType extends CrustType 

这些枚举为处理披萨馅料,大小和类型提供了一种不错的方法

给定这些枚举,我们可以定义一个Pizza这样的类:

class Pizza (var crustSize: CrustSize = MediumCrustSize, var crustType: CrustType = RegularCrustType) {      // ArrayBuffer is a mutable sequence (list)     val toppings = scala.collection.mutable.ArrayBuffer[Topping]()      def addTopping(t: Topping): Unit = toppings += t     def removeTopping(t: Topping): Unit = toppings -= t     def removeAllToppings(): Unit = toppings.clear()  } 

trait 和 abstract class

trait

using scala trait as interface

一种使用Scala的trait方法就像原始的Java一样interface,在其中您可以为某些功能定义所需的接口,但是您没有实现任何行为。

example_1

trait TailWagger {     def startTail(): Unit     def stopTail(): Unit } 

等效与Java

public interface TailWagger {     public void startTail();     public void stopTail(); } 

您可以编写一个扩展特征并实现如下方法的类:

class Dog extends TailWagger {     // the implemented methods     def startTail(): Unit = println("tail is wagging")     def stopTail(): Unit = println("tail is stopped") } 

请注意,无论哪种情况,都可以使用extends关键字创建一个扩展单个特征的类

extends和with用于从多个trait创建类:

using scala trait like abstract class

example_1

trait Pet {     def speak = println("Yo")     // concrete implementation of a speak method     def comeToMaster(): Unit      // abstract } 
class Dog(name: String) extends Pet {     def comeToMaster(): Unit = println("Woo-hoo, I'm coming!") } 

  1. Overriding an implemented method
class Cat extends Pet {     // override 'speak'     override def speak(): Unit = println("meow")     def comeToMaster(): Unit = println("That's not gonna happen.") } 

  1. 混合有行为的多个trait
trait Speaker {     def speak(): String   //abstract }  trait TailWagger {     def startTail(): Unit = println("tail is wagging")     def stopTail(): Unit = println("tail is stopped") }  trait Runner {     def startRunning(): Unit = println("I'm running")     def stopRunning(): Unit = println("Stopped running") } 

创建一个Dog扩展所有这些特征的类,同时为speak方法提供行为:

class Dog(name: String) extends Speaker with TailWagger with Runner {     def speak(): String = "Woof!" } 
class Cat extends Speaker with TailWagger with Runner {     def speak(): String = "Meow"     override def startRunning(): Unit = println("Yeah ... I don't run")     override def stopRunning(): Unit = println("No need to stop") } 

  1. 即时混合trait

使用具有具体方法的特征可以做的一件非常有趣的事情是将它们动态混合到类中。例如

trait TailWagger {     def startTail(): Unit = println("tail is wagging")     def stopTail(): Unit = println("tail is stopped") }  trait Runner {     def startRunning(): Unit = println("I'm running")     def stopRunning(): Unit = println("Stopped running") } 

以在创建Dog实例时创建一个混合了这些特征的Dog实例

val d = new Dog("Fido") with TailWagger with Runner 

此示例之所以有效,是因为定义了TailWagger和Runner特性中的所有方法(它们不是抽象的)。

abstract class

Scala还具有类似于Java的抽象类的抽象类的概念。但是,由于特征是如此强大,因此您几乎不需要使用抽象类。实际上,仅在以下情况下才需要使用抽象类:

  • 要创建一个需要构造函数参数的基类
  • Scala代码将从Java代码中调用

创建一个需要构造函数参数的基类

Scala trait不允许使用构造函数参数

// this won’t compile trait Animal(name: String) 

因此,只要基本行为必须具有构造函数参数,就需要使用抽象类:

abstract class Animal(name: String) 

注意,一个类只能扩展一个抽象类。

Scala代码将从Java代码中调用

因为Java对Scala traits一无所知,如果要从Java代码调用Scala代码,则需要使用抽象类而不是特质。

Abstract class syntax

abstract class Pet (name: String) {     def speak(): Unit = println("Yo")   // concrete implementation     def comeToMaster(): Unit            // abstract method } 
class Dog(name: String) extends Pet(name) {     override def speak() = println("Woof")     def comeToMaster() = println("Here I come!") } 

注意如何name传递

所有这些代码都类似于Java,因此我们将不对其进行详细说明。需要注意的一件事是如何将name构造函数参数从Dog类构造函数传递给Pet构造函数:

class Dog(name: String) extends Pet(name) { 

记住,Pet声明name为构造函数参数:

abstract class Pet (name: String) { ... 

因此,此示例说明如何将构造函数参数从Dog类传递给Pet抽象类。您可以验证此代码是否适用:

abstract class Pet (name: String) {     def speak: Unit = println(s"My name is $name") }  class Dog(name: String) extends Pet(name)  val d = new Dog("Fido") d.speak 

函数

匿名函数

val ints = List(1,2,3) //要创建更大的列表时,还可以使用List的 range方法创建它们 val ints = List.range(1, 10)  

example_1

//给出如下列表 val ints = List(1,2,3) //每个元素加倍来创建新列表ints val doubledInts = ints.map(_ * 2) //其中 _ * 2 //此代码是匿名函数 //也可以这样写: val doubledInts = ints.map((i: Int) => i * 2) val doubledInts = ints.map(i => i * 2)  //等效于此Java代码  List<Integer> ints = new ArrayList<>(Arrays.asList(1, 2, 3));  // the `map` process List<Integer> doubledInts = ints.stream()                                 .map(i -> i * 2)                                 .collect(Collectors.toList());  //等效 val doubledInts = for (i <- ints) yield i * 2 

_Scala中的字符是通配符。您会看到它在几个不同的地方使用过。在这种情况下,这是一种简短的说法:“列表中的元素,” ints。

PURE FUNCTIONS(纯函数)

在函数式编程Simplified中,Alvin Alexander定义了一个纯函数,如下所示:

  • 函数的输出仅取决于其输入变量
  • 它不会改变任何隐藏状态
  • 它没有任何“后门”:它不会从外界读取数据(包括控制台,Web服务,数据库,文件等),也不会向外界写入数据

纯函数示例

正如您可能想象的那样,在给定纯函数定义的情况下,scala.math._包中的此类方法就是纯函数:

  • abs
  • ceil
  • max
  • min

这些Scala String方法也是纯函数:

  • isEmpty
  • length
  • substring

在Scala集合类的很多方法也作为纯粹的功能,包括drop,filter,和map。


不纯函数的例子
相反,以下功能不纯,因为它们违反了定义。

foreach集合类上的方法是不纯的,因为它仅用于其副作用,例如打印到STDOUT。

通常,不纯函数会执行以下一项或多项操作:

  • 读取隐藏的输入,即,它们访问未显式传递为输入参数的变量和数据
  • 写隐藏的输出
  • 突变给定的参数
  • 与外界进行某种I / O

编写纯函数

只需使用Scala的方法语法编写纯函数。这是一个纯函数,它将给定的输入值加倍:

def double(i: Int): Int = i * 2 

传递函数

Scala 可以将函数创建为变量,就像创建String和Int变量一样。

此功能有很多好处,其中最常见的是,它使您可以将函数作为参数传递给其他函数。在演示了map和filter方法时,您已经在本书的前面看到了:

val nums = (1 to 10).toList  val doubles = nums.map(_ * 2) val lessThanFive = nums.filter(_ < 5) 

匿名函数传递到map和中filter。

没有空值

函数式编程就像编写一系列代数方程式一样,并且由于在代数中不使用空值,因此在FP中不使用空值。这就提出了一个有趣的问题:在Java / OOP代码中通常使用空值的情况下,您该怎么办?

Scala的解决方案是使用诸如Option / Some / None类之类的构造。在本课中,我们将介绍这些技术。

def toInt(s: String): Int = {     try {         Integer.parseInt(s.trim)     } catch {         case e: Exception => 0     } } 

此函数的思想是,如果字符串转换为整数,则返回转换后的Int,但如果转换失败,则返回0。这可能在某些方面是可以的,但它不是真的准确。例如,该方法可能接收到“0”,但它也可能接收到“foo”或“bar”或无限数量的其他字符串。这就产生了一个真正的问题:如何知道方法何时真正收到“0”,或者何时收到其他东西?答案是,用这种方法,没有办法知道。

Scala解决这个问题的方法是使用三个类,即Option、Some和None。Some类和None类是ABC的子类,因此解决方案如下:

  • 声明toInt返回Option类型
  • 如果toInt接收到一个可以转换为Int的字符串,则将该Int包装在Some中
  • 如果toInt接收到一个无法转换的字符串,它将返回一个None
def toInt(s: String): Option[Int] = {     try {         Some(Integer.parseInt(s.trim))     } catch {         case e: Exception => None     } } 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!