Chapter12 Scala操作符
最后更新于:2022-04-01 20:28:19
### 1. 标识符
-
变量名、函数名、类名等统称为标识符,scala可以使用任何字符来作标识符,比如 `()!#%&×+-/:<>=?@\^|~` 等。
-
反引号中可以使用任何字符序列。
~~~
val √ = scala.math.sqrt _
√(2)
val `val` = 42
println(`val`)
~~~
### 2. 中置操作符
-
操作符要接两个参数,分别位于操作符两边。
-
`a 标识符 b` 等价于 `a.标识符(b)` 。
~~~
1 to 10
等价于 1.to(10)
1 -> 10
等价于 1 .->(10)
~~~
### 3. 一元操作符
-
只有一个参数的操作符成为一元操作符,操作符的位置可能位于参数的前或后,所有有下面两种情况。
-
`a 标识符` 等价于 `a.标识符()` ,`标识符 a` 等价于 `a.unary_标识符` 。
~~~
1 toString
等价于 1.toString()
val a = 42
-a
等价于 a.unary_-
~~~
### 4. 赋值操作符
- 赋值操作符的形式为 `操作符=`,表达式是 `a 操作符= b` ,等价于 `a = a 操作符 b` 。
~~~
a += 1
等价于 a = a + 1
~~~
- `<=、>=、!=、==、===、=/=` 等不是赋值操作符,
### 5. 优先级
-
后置操作符优先级低于中置操作符,`a 中置操作符 b 后置操作符` 等价于 `(a 中置操作符 b) 后置操作符` 。
-
符号优先级如下表所示:
| 操作符 | 优先级 |
|-----|-----|
| 除下面字符以外的操作符 | 1(最高) |
| * / % | 2 |
| + - | 3 |
| : | 4 |
| < > | 5 |
| != | 6 |
| & | 7 |
| ^ | 8 |
| | | 9 |
| 赋值操作符 | 10(最低) |
### 6. 结合性
- Scala除冒号操作符和赋值操作符是右结合,所有其他操作符都是左结合。
~~~
// 构造列表List
1 :: 2 :: Nil // :: 右结合
1 :: (2 :: Nil)
(1 :: 2) :: Nil // 错误
~~~
- 右结合的的第二个参数就是方法,比如 `2::Nil` 等价于 `Nil.::(2)`。
### 7. apply和update方法
- 函数或方法位于赋值语句的等号左侧,调用的是update方法,否则调用的apply方法。
~~~
val scores = new scala.collection.mutable.HashMap[String, Int]
scores("Bob") = 100
等价于调用:scores.update("Bob", 100)
val bobsScore = scores("Bob")
等价于调用:scores.apply("Bob")
~~~
### 8. 提取器unapply
-
提取器:是一个带有unapply方法的对象,可以当做是伴生对象apply的方法的反操作。
-
unapply接受一个对象,从中提取值。
~~~
class Fraction(n: Int, d: Int) {
private val num: Int = if (d == 0) 1 else n;
private val den: Int = if (d == 0) 0 else d;
def *(other: Fraction) = new Fraction(num * other.num, den * other.den)
}
// unapply返回的就是构造该对象的两个值
object Fraction {
def apply(n: Int, d: Int) = new Fraction(n, d)
def unapply(input: Fraction) =
if (input.den == 0) None else Some((input.num, input.den))
}
object Main extends App {
var Fraction(a, b) = Fraction(3, 4) * Fraction(2, 5)
println(a)
println(b)
}
~~~
-
每个样例类都会自动具备apply方法和unapply方法,样例类后面章节会讲解,这里只要了解就可以。
-
提取器可以只测试输入而不真正将其值提取出来,只返回一个Boolean。
### 9. unapplySeq方法
- 使用unapplySeq方法可以提取任意长度的值序列,它返回一个Option[Seq[A]],A是被提取的类型。
~~~
// Name 提取器可以产生所有组成部分的序列
// 关于 Option,Some,None后面章节会详细讲解,这里只需了解upapplySeq的作用就可以。
object Name {
def unapplySeq(input: String): Option[Seq[String]] =
if (input.trim == "") None else Some(input.trim.split("\\s+"))
}
~~~
【待续】
';
Chapter11 类型参数
最后更新于:2022-04-01 20:28:17
### 1. 泛型类
> **个人理解:**泛型实质就是类或方法在定义时没有给定具体参数类型,需要在编译或运行时自动推断出类型的一种方法。
- 用方括号来定义类型参数,放在类名后面。下面就定义了一个带有两个类型参数T和S的类。
~~~
class Pair[T, S](val first: T, val second: S) {
override def toString = "(" + first + "," + second + ")"
}
~~~
-
在类的定义中,可以用类型参数类定义变量、方法参数,以及返回值类型。
-
带有一个或多个类型参数的类是泛型的,Scala会从构造参数推断出实际类型,也可以自己指定类型。
~~~
val p = new Pair(42, "String")
val p2 = new Pair[Any, Any](42, "String")
~~~
### 2. 泛型函数
- 函数和方法也可以带类型参数,类型参数放在函数名或方法名后面。
~~~
def getMiddle[T](a: Array[T]) = a(a.length / 2)
~~~
### 3. 类型变量界定
> **个人理解:**使用类型参数定义了某个参数或变量之后,要使用某种类型的的方法,比如使用String类型的compareTo方法,但我们可能传进去的是一个Int类型,Int类型没有该方法,这时程序就会出错,下面就介绍如何解决这类问题。
- 有时需要对类型变量进行限制,比如有个Pair类,该类中使用了compareTo方法,但我们并不知道first有这个方法。
~~~
class Pair[T](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second // Error
}
~~~
- 解决方法,添加一个上界`T <: Comparable[T]`。
~~~
// 这里要求T必须是Comparable[T]的子类型。
class Pair[T <: Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
}
~~~
- 如果我们现在想要定义一个方法,用一个值替换上面定义Pair中的第一个值,如果Pair是不可变的,我们就需要返回新的Pair。这时候我们要引入**下界**来解决这个问题,下界用`[R >: T]`来表示。
~~~
// 替换进来的类型必须是原类型的超类型
// 比如现在有Pair[Student],要用Person来替换第一个值,结果是Pair[Person]。
class Pair[T](val first: T, val second: T) {
def replaceFirst[R >: T](newFirst: R) = new Pair(newFirst, second)
}
~~~
### 4. 视图界定
> **个人理解:**即使上面使用上界约束了传入的类型,我们在调用`Pair(1,4)`时,编译器还是会报Int不是Comparable[Int]子类的错误,其实我们有一个RichInt中实现了该方法,Int到RichInt可以通过隐式转换来完成。下面就是讲解如何用视图界定的方法解决该问题。
- 视图界定中使用`<%`关系表示T可以被隐式转换成Comparable[T]。
~~~
class Pair[T <% Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second
}
// Ordered提供关系操作符
class Pair[T <% Ordered[T]](val first: T, val second: T) {
def smaller = if (first < second) first else second
}
~~~
- 隐式转换将在后面一个Chapter中讲解,这里只要知道有这个概念就行。
### 5. 上下文界定
> **个人理解:**其实上面视图界定还有一个问题,当你使用视图界定前必须知道有从T到V的隐式转换存在,要是没有呢,我们该怎么办?下面就介绍该小结内容”上下文界定“,它是如何解决次问题的。
- 上下文界定的形式是:`T:M`,M是另一个泛型类,它要求必须存在一个类型为M[T]的隐式值,这里只要知道隐式值是用`implicit`定义就可以了,下节会详细讲解。
~~~
class Pair[T : Ordering](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]) =
if (ord.compare(first, second) < 0) first else second
}
~~~
### 6. Manifest上下文界定
> **个人理解:**构造一个泛型数组,在Scala中需要我们将上下文界定的M指定为Manifest,这节其实是上下文界定的一个实际应用场景。
- 要实例化一个泛型的Array[T]或者说是构造一个泛型数组,我们需要一个`Manifest[T]`对象。Manifest就是一个隐式参数,这里可以用上下文界定。
~~~
def makePair[T : Manifest](first: T, second: T) = {
val r = new Array[T](2); r(0) = first; r(1) = second; r
}
~~~
### 7. 多重界定
-
类型变量可以同时有上界和下界:`T >: Lower <: Upper`
-
不能同时有多个上界或多个下界,一个类型可以实现多个特质。
~~~
T <: Comparable[T] with Serializable with Cloneable
~~~
-
可以有多个视图界定:`T <% Comparable[T] <% String`
-
可以有多个上下文界定:`T : Ordering : Manifest`
### 8. 类型约束
> **个人理解:**该小节提出类型约束,主要是要掌握它的两种用途。
- 类型约束有如下三种方式:
~~~
T =:= U // 测试T是否等于U
T <:< U // 测试T是否是U的子类
T <%< U // 测试T是否被隐式转换为U
~~~
- 使用约束,需要添加隐式类型证明参数。
~~~
class Pair[T](val first: T, val second: T)(implicit ev: T <:< Comparable[T]) = // 使用约束
~~~
-
上面的类型约束的使用并没有比类型界定带来更多优点,类型约束主要用在以下两种场景。
-
类型约束让你可以在泛型类中定义只能在特定条件下使用的方法。
~~~
// 即使File不是带先后顺序的,这里还是可以构造出Pair[File]
// 只是当你调用smaller方法时,才会报错
class Pair[T](val first: T, val second: T) {
def smaller(implicit ev: T <:< Ordered[T]) = // 使用约束
if (first < second) first else second
}
~~~
- 类型约束的另一个用途是改进类型推断。
~~~
// 下面调用第二条语句会得到推断的类型参数是[Nothing, List[Int]]不符合[A, C <: Iterable[A]]
// 因为单凭List(1, 2, 3)是无法推断出A是什么,因为它们是在同一个步骤中匹配A和C的。
def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)
firstLast(List(1, 2, 3)) // 出错
~~~
- 解决办法是,先匹配C,在匹配A。
~~~
def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) =
(it.head, it.last)
firstLast(List(1, 2, 3)) // 成功
~~~
### 9. 型变
> **个人理解:**如果只是看书本,这小结还是挺不容易看明白的。其实,仔细分析下,协变和逆变还是很好理解的。如果将父类作为函数参数,想子类作为参数也可以调用,这就是协变;如果将子类作为函数参数,想父类作为参数也可以调用,这就是逆变。这段话如果没看明白,先看下面具体例子,看完后再回来看这段话。
- 假设有函数定义`def makeFriends(p: Pair[Person])`,并且Student是Person的子类,如果使用Pair[Student]作为参数去调用makeFriends函数,程序会报错。要想实现这种方式的调用,需要把Pair定义修改如下:
~~~
class Pair[+T](val first: T, val second: T)
~~~
-
`+T`(协变)表示某个泛型类的子类型关系和参数T方向一致。
-
下面讲解第二种型变方式逆变。
~~~
trait Friend[-T] { // -T表示逆变
def befriend(someone: T)
}
class Person(name: String) extends Friend[Person] {
def befriend(someone: Person) {
this + " and " + someone + " are now friends."
}
}
// Student是Person子类
class Student(name: String) extends Person(name)
// 需要注意该函数定义的传入参数是子类类型
def makeFriendWith(s: Student, f: Friend[Student]) { f.befriend(s) }
val susan = new Student("Susan")
val fred = new Person("Fred")
// 可以调用
makeFriendWith(susan, fred)
~~~
- `-T`(逆变)表示某个泛型类的父类型关系和参数T方向一致,刚好与协变相反。
### 10. 协变和逆变点
-
函数在参数上是逆变的,返回值上是协变的。逆变适应于表示输入类型参数,协变适应于表示输出类型参数。
-
Scala中数组类型是不支持型变的,因为它作为输入和输出类型要保持不变。
-
参数的位置是逆变点,返回值类型的位置是协变点,在函数的参数中,型变是反过来的,它的参数是协变的。
~~~
foldLeft[B](z: B)(op: (B, A) => B): B
- + + - +
~~~
### 11. 对象不能泛型
- 不能将参数化类型添加到对象。
~~~
abstract class List[+T] {
def isEmpty: Boolean
def head: T
def tail: List[T]
}
// 对象不能写成 object Empty[T] extends List[T]
// 类可以使用 class Empty[T] extends List[T]
// 这里可以使用Nothing,前面说过Nothing是所有类型的子类型。
object Empty extends List[Nothing] {
def isEmpty = true
def head = throw new UnsupportedOperationException
def tail = throw new UnsupportedOperationException
}
~~~
### 12. 类型通配符
- Scala中类型可以使用通配符。
~~~
def process(people: java.util.List[_ <: Person]
~~~
【待续】
';
Chapter10 注 解
最后更新于:2022-04-01 20:28:15
> Chapter10这节还是挺重要的,在看Spark源码的过程中,发现其源码使用到很多注解的知识,所以这节一定要好好学习。
### 1. 什么是注解
-
**注解:**是那些你插入到代码中以方便工具可以对它们进行处理的**标签**。
-
**注解作用:**是描述那些被注解的表达式、变量、字段、方法或类型。
-
Java中注解不会影响编译器生成字节码,而Scala中会影响。
-
下面是注解的一个简单实例:
~~~
import scala.reflect.BeanProperty
import javax.persistence.Entity
import javax.persistence.Id
import org.junit._
import org.junit.Assert._
@Entity class Credentials {
@Id @BeanProperty var username : String = _
@BeanProperty var password : String = _
}
class MyTest {
@Test(timeout = 100) def testSomeFeature() {
assertTrue(6 * 7 == 42)
}
}
~~~
`@Entity`注解标识该类是一个实体,`@id`是设置主键,`@BeanProperty`在Chapter05中讲过,它会生成getter和setter两种方法,`@Test`表示测试方法,在这里可以测试期望异常和超时时间。具体含义可以上google查询Junit和JPA注解。
### 2. 什么可以被注解
-
类、方法、字段、局部变量和参数都可添加注解。
-
可以添加多个注解,没有先后顺序。
~~~
@BeanProperty @Id var lastName = ""
~~~
-
给主构造函数添加注解时,注解放置在构造器之前,并加上一对圆括号。
~~~
class Credentials @Inject() (var username: String, var password: String) {
}
~~~
-
给表达式添加注解,需要在表达式后添加冒号,然后是注解。
~~~
(n: @unchecked) match { ... }
~~~
### 3. 注解参数
-
注解可以带参数。
~~~
@Test(timeout = 100, expected = classOf[IOException])
def testNonexistentFile() {
new FileReader("/fred.txt")
}
~~~
-
注解不带参数,圆括号可以省略。注解有默认值,比如Test的Timeout的默认值为0,表示没有超时,expected默认是不预期任何异常。
### 4. 针对Java特性的注解
-
`@volatile`将字段标记为易失的,易失的字段可以被多个线程同时更新。
-
`@transient`将字段标记为瞬态的,瞬态的字段不会被序列化,只会作为临时的缓存数据。(序列化:将对象状态信息转换为可以存储或传输的形式的过程)
-
`@strictfp`使用IEEE的double进行浮点计算,而不是使用80位扩展精度,计算慢但移植性高。
-
`@native`标记在C或C++代码中的实现方法。
### 5. 标记接口
-
`@cloneable`和`@remote`标记接口可被克隆或远程的对象。
-
`@SerialVersionUID`指定序列化版本,标记可序列化的类。
~~~
@cloneable @remote @SerialVersionUID(6157032470129070425L)
class Employee(var name: String, var salary: Double) extends Serializable {
var hireDay = new Date
}
~~~
### 6. 受检异常和变长参数
-
`@throws`标记可能抛出异常的对象。
-
`@varargs`可以让你从Java调用Scala的带有变长参数的方法。
~~~
class Processor {
@varargs def process(args: String*) {
println(args.mkString("<", "|", ">"))
}
}
~~~
### 7. 跳转表生成与内联
- C++和Java中switch通常被编译为跳转表,更高效,`@switch`检查Scala是不是将match编译成跳转表。
~~~
(n: @switch) match {
case 0 => "Zero"
case 1 => "One"
case _ => "?"
}
~~~
- `@inline`将方法标记为内联方法,`@noinline`告诉编译器不要内联。
### 8. 可省略方法
-
`@elidable`给生产代码中需要移除的方法打上标记。它后面会接一个参数,
~~~
@elidable(800) def dump(props: Map[String, String]) {}
~~~
-
编译时需要使用下面命令:
~~~
scalac -Xelide-below 800 prog.scala
~~~
-
具体参数表如下,默认值低于1000的方法都会被省略。
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-07_57061f7706882.jpg "")
### 9. 基本类型的特殊化
-
给类型参数添加`@specialized`注解,**编译器会自动生成对应类型的所有方法**。不需要自己写重载函数。
-
可用的类型有:**Unit、Boolean、Byte、Short、Char、Int、Long、Float、Double**。
~~~
def allDifferent[@specialized(Double, Long) T](x: T, y: T, z: T) = x != y && x != z && y != z
~~~
### 10. 用于错误和警告的注解
-
`@deprecated`注解被添加后,编译器遇到被注解的方法使用时就会生成一个警告,它有两个参数可选,messsage和since。
~~~
@deprecated(message = "Use factorial(n: BigInt) instead")
def factorial(n: Int): Int = if (n <= 0) 1 else n * factorial(n - 1)
~~~
-
`@implicitNotFound`注解用于某个隐式参数不存在时会生成有意义的错误提示。
-
`@unchecked`注解用于在匹配不完整时取消警告信息。
【待续】
';
Chapter09 特质
最后更新于:2022-04-01 20:28:13
### 1. 为什么没有多重继承
-
准备这个Chapter讲文件和正则表达式,但该内容按照其他语言安排一般都是在最后几章节,所以暂时先忽略该章节,后面会补上。
-
转入正题,如果有两个类Student和Employee,它们都有name这个属性,如果要同时扩展这两个基类该如何做,主要问题是name属性如何处理?如果保留两个,那么访问时到底访问的是哪个呢?如果只保留一个,那么保留哪一个?该问题就是**菱形问题**。画一个形象的图形如下:
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-07_57061f76d1442.jpg "")
-
Scala通过提供**特质**解决这个问题,特质可以同时拥有抽象方法和具体方法,而类可以实现多个特质。
### 2. 当做接口使用的特质
-
特质**完全**可以像Java接口那样工作。
-
特质中未被实现的方法默认是抽象方法,不需要用`abstract`申明。
-
重写特质抽象方法时不需要给出`override`关键字。
-
特质不止一个,可以使用`with`关键字来添加额外的特质。
-
Scala类只能有一个超类,可以有任意数量的特质。
### 3. 带有具体实现的特质
- 特质中可以有具体的方法。
~~~
trait ConsoleLogger {
def log(msg: String) { println(msg) } // 具体方法
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with ConsoleLogger {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
}
~~~
### 4. 带有特质的对象
- 构造单个对象时,可以为它添加特质,用`with`关键字。
~~~
trait Logged {
def log(msg: String) { }
}
trait ConsoleLogger extends Logged {
override def log(msg: String) { println(msg) }
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with Logged {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
}
object Main extends App {
val acct1 = new SavingsAccount
acct1.withdraw(100) // 没有任何信息会打印出来
println("Overdrawing acct2");
val acct2 = new SavingsAccount with ConsoleLogger // 添加特质进来
acct2.withdraw(100) // 会打印信息
}
~~~
### 5. 添加多个特质
> **个人理解:**添加多个特质要研究的问题实际是,添加的这几个特质哪个先执行?即执行的顺序,执行完的结果是要传给另一个特质进行下一步处理,这里有点像linux中pipe的概念。
- 类或对象可以添加多个特质,特质从最后一个开始执行,非常适合分阶段处理的场景。
~~~
trait Logged {
def log(msg: String) { }
}
trait ConsoleLogger extends Logged {
override def log(msg: String) { println(msg) }
}
trait TimestampLogger extends Logged { // 给消息添加时间戳
override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger extends Logged { // 截断过长的信息
val maxLength = 15
override def log(msg: String) {
super.log(
if (msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "...")
}
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with Logged {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds") // 调用特质中的log方法
else balance -= amount
}
}
object Main extends App {
val acct1 = new SavingsAccount with ConsoleLogger with
TimestampLogger with ShortLogger
val acct2 = new SavingsAccount with ConsoleLogger with
ShortLogger with TimestampLogger
acct1.withdraw(100)
acct2.withdraw(100)
}
~~~
- 由于特质添加的顺序,上面的执行结果为:
![运行结果](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-07_57061f76e514e.jpg "")
### 6. 在特质中重写抽象方法
> **个人理解:**这节就是想区分用类来扩展特质是不需要加override,而特质扩展特质就要加override。
- 特质中重写抽象方法需要加`abstract`和`override`关键字。
~~~
trait Logger {
def log(msg: String) // 抽象的方法
}
trait TimestampLogger extends Logger {
abstract override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ConsoleLogger extends Logger {
override def log(msg: String) { println(msg) } // 这里说明abstract是可以省略的
}
~~~
### 7. 当做富接口使用的特质
> ***个人理解:**这节无非是上面的基本应用,train中可以根据不同的应用场景来定义多个同一功能的方法。
- 比如下面这段代码,每个日志消息都会区分消息的类型。
~~~
trait Logger {
def log(msg: String)
def info(msg: String) { log("INFO: " + msg) }
def warn(msg: String) { log("WARN: " + msg) }
def severe(msg: String) { log("SEVERE: " + msg) }
}
~~~
### 8. 特质中的具体字段
> **个人理解:**这节主要目的是知道特质中可以有具体的字段,具体的意思就是经过初始化的字段。并且**类**在扩展特质后,具体字段不是被继承的,而是**直接被加入到子类中**。说白了就是在类中又拷贝了该字段的一个副本,类中可以直接使用。
### 9. 特质中的抽象字段
> **个人理解:**具体字段可以直接使用,那么如果是抽象字段该怎么办?这节给出特质中如果有抽象字段,**类**中使用就必须要重新定义该字段,并且不需要使用override关键字,这在第二小节已经明确。
~~~
trait Logger {
val maxLength: Int // 抽象的字段
}
class TimestampLogger extends Logger {
val maxLength: Int = 20 // 不需要写override
}
~~~
### 10. 特质中的构造顺序
> **个人理解:**和类一样,特质也有构造器,同样特质中也存在构造执行顺序的问题。记住特质的构造顺序对写程序是很有帮助的。
-
特质的构造器执行顺序:
1. 首先调用超类构造器;
1. 特质构造器在超类构造器之后、类构造器之前执行;
1. 特质由左到右构造;
1. 每个特质中,父特质先被构造;
1. 如果过个特质共有一个父特质,而父特质已被构造,则不会再次被构造;
1. 所有特质构造完毕,子类被构造。
### 11. 初始化特质中的字段
> **个人理解:**首先知道**特质是不能有构造参数**,对某种定制的特质来说这就是一个问题,那么该如何解决?这小结给给出了两种解决方案:**提前定义**和**懒值**。
-
特质不能有构造参数,但都有一个无参的构造器,缺少构造器参数这是特质和类之间**唯一**的技术差别,特质可以具备类的所有特性。
-
提前定义的方法如下:
~~~
trait Logger {
def log(msg: String)
}
// 使用提前定义定义变量filename
trait FileLogger extends Logger {
val filename: String
val out = new PrintWriter(filename)
def log(msg: String) { out.println(msg); out.flush() }
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
}
object Main extends App {
val acct = new { // new之后就是提前定义块,在构造器执行前就定义好
val filename = "myapp.log"
} with SavingsAccount with FileLogger
acct.withdraw(100)
}
~~~
- 懒值方法,懒值字段会在第一次使用时才会被初始化。
~~~
trait Logger {
def log(msg: String)
}
// 使用懒值
trait FileLogger2 extends Logger {
val filename: String
lazy val out = new PrintWriter(filename)
def log(msg: String) { out.println(msg); out.flush() }
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
}
object Main extends App {
val acct2 = new SavingsAccount with FileLogger2 {
val filename = "myapp2.log"
}
acct2.withdraw(100)
}
~~~
### 12. 扩展类的特质
> **个人理解:**标题有点绕,其实意思就是**某个特质扩展了类**,这种情况scala会有哪些特殊的地方需要注意。
- 特质是可以扩展特质的,其实特质也是可以扩展类,扩展后这个类自动成为该特质的超类。
~~~
trait Logged {
def log(msg: String) { }
}
trait LoggedException extends Exception with Logged {
def log() { log(getMessage()) }
}
~~~
这里LoggedException扩展了Exception类,并调用了从Exception超类继承下来的getMessage()方法。
### 13. 自身类型
> **个人理解:**除上面可以扩展某个类的方法之外,scala还提供另外一种方式,不用扩展某个类,直接把某个类作为自身的一个类型,从而达到同样的目的。
- 定义语法:`this: 类型 =>`,特质定义自身类型后,只能被混入指定类型的子类。比如:
~~~
trait Logged {
def log(msg: String) { }
}
trait LoggedException extends Logged {
this: Exception => // 特质不是扩展Exception类,而是有一个自身类型Exception
def log() { log(getMessage()) }
}
~~~
- 自身类型还可以用来处理结构类型,比如只指定一个方法,而不是类名。
~~~
trait Logged {
def log(msg: String) { }
}
trait LoggedException extends Logged {
this: { def getMessage() : String } =>
def log() { log(getMessage()) }
}
~~~
【待续】
';
Chapter08 继承
最后更新于:2022-04-01 20:28:10
### 1. 扩展类
- 扩展的方法是使用`extends`关键字
~~~
class Person {
var name = ""
}
class Employee extends Person {
var salary = 0.0
def description = "An employee with name " + name + " and salary " + salary
}
object Main extends App {
val fred = new Employee
fred.name = "Fred"
fred.salary = 50000
println(fred.description)
}
~~~
- 在类、方法或字段前加`final`关键字,这样类或方法就不会被扩展或重写。
### 2. 重写方法
- Scala重写一个非抽象方法,必须用`override`修饰符。
~~~
class Person {
var name = ""
override def toString = getClass.getName + "[name=" + name + "]"
}
class Employee extends Person {
var salary = 0.0
override def toString = super.toString + "[salary=" + salary + "]"
}
object Main extends App {
val fred = new Employee
fred.name = "Fred"
fred.salary = 50000
println(fred)
}
~~~
- 调用超类(父类),使用`super`关键字。
### 3. 类型检查和转换
-
`isInstanceOf`方法,测试某个对象是否属于某个给定类。
-
`asinstancdOf`方法,将引用转换为子类的引用。
-
`classOf`测试p是Employee对象但又不是子类。
~~~
class Person {
var name = ""
override def toString = getClass.getName + "[name=" + name + "]"
}
class Employee extends Person {
var salary = 0.0
override def toString = super.toString + "[salary=" + salary + "]"
}
class Manager extends Employee
object Main extends App {
val r = scala.math.random
val p = if (r < 0.33) new Person
else if (r < 0.67) new Employee
else new Manager
if (p.isInstanceOf[Employee]) {
val s = p.asInstanceOf[Employee] // s has type Employee
println("It's an employee.")
s.salary = 50000
if (p.getClass == classOf[Manager]) {
println("Actually, it's a manager")
s.salary *= 2
}
}
println(p)
}
~~~
### 4. 受保护字段和方法
-
将字段或方法声明为`protected`,这样的成员可以被任意子类访问,但不能从其他位置看到。
-
protected的成员对类所属的包是不可见的。
-
`protected[this]`将访问权限限定在当前对象。
### 5. 超类的构造
- 只有主构造器可以调用超类的构造器。
~~~
class Person(val name: String, val age: Int) {
override def toString = getClass.getName + "[name=" + name +
",age=" + age + "]"
}
class Employee(name: String, age: Int, val salary : Double) extends
Person(name, age) {
override def toString = super.toString + "[salary=" + salary + "]"
}
new Employee("Fred", 42, 50000)
~~~
### 6. 重写字段
-
重写有如下限制:
1. def只重写另一个def。
1. val只能重写另一个val或不带参数的def。
1. var只能重写另一个抽象的var。
![重写val/def/var](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-07_57061f7694b4c.jpg "")
~~~
class Person(val name: String) {
override def toString = getClass.getName + "[name=" + name + "]"
}
class SecretAgent(codename: String) extends Person(codename) {
override val name = "secret" // 不暴露真名
override val toString = "secret" // ...或类名
}
val fred = new Person("Fred")
fred.name
val james = new SecretAgent("007")
james.name
// 用val重写抽象类的def
abstract class Person {
def id: Int
}
class Student(override val id: Int) extends Person
class SecretAgent extends Person {
override val id = scala.util.Random.nextInt
}
val fred = new Student(1729)
fred.id
val james = new SecretAgent
james.id
~~~
### 7. 匿名子类
-
通过包含`带有定义或重写的`代码块的方法创建一个匿名的子类。
-
匿名类作为参数的定义类型为:`Person{def greeting:String}`。
~~~
class Person(val name: String) {
override def toString = getClass.getName + "[name=" + name + "]"
}
val alien = new Person("Fred") {
def greeting = "Greetings, Earthling! My name is Fred."
}
def meet(p: Person{def greeting: String}) {
println(p.name + " says: " + p.greeting)
}
meet(alien)
~~~
### 8. 抽象类
-
`abstract`关键字标记不能被实例化的类,因为它的某个或几个方法没有被定义,这种没有方法体的方法是抽象方法。
-
某个类存在抽象方法,则必须申明为`abstract`。有抽象方法以及下面将提到的抽象字段的存在才导致了抽象类的出现。
-
子类中重写超类的抽象方法时,不需要使用`override`关键字。
~~~
abstract class Person(val name: String) {
def id: Int // 没有方法体的方法是 抽象方法
}
class Employee(name: String) extends Person(name) {
def id = name.hashCode // override 不需要
}
val fred = new Employee("Fred")
fred.id
~~~
### 9. 抽象字段
- 抽象字段:没有初始值的字段。
~~~
abstract class Person {
val id: Int // 没有初始化,带有抽象getter方法的抽象字段
var name: String // 带有抽象getter和setter方法
override def toString = getClass.getName + "[id=" + id + ",name=" + name + "]"
}
class Employee(val id: Int) extends Person { // 子类具体的id
var name = ""
}
val james = new Employee(7)
val fred = new Person {
val id = 1729
var name = "Fred"
}
~~~
### 10. 构造顺序和提前定义
- 问题来源:子类将父类方法重写后,父类构造时对应的方法失效,由子类来构造。如下例,实际构造完成后rannge的值为2。
~~~
class Creature {
val range: Int = 10
val env: Array[Int] = new Array[Int](range)
}
class Ant extends Creature {
override val range = 2
}
~~~
-
有三种方法解决上述问题
1.
将val申明为final,安全但不灵活。
1.
将val申明为lazy,安全但不高效。
1.
子类中使用提前定义。
-
提前定义:在超类构造之前初始化子类val字段,将val字段放在extends关键字之后的一个块中,块中不能包含类中其他字段。并且超类前用`with`关键字。
~~~
class Creature {
val range: Int = 10
val env: Array[Int] = new Array[Int](range)
}
class Ant extends Creature {
override val range = 2
}
class Bug extends {
override val range = 3
} with Creature
~~~
### 11. scala继承层级
-
所有的Scala类都实现`ScalaObject`这个接口。
-
`Any`类是整个继承层级的根节点。
-
`Null`唯一实例是`null`值,可赋值给任何引用,但不能赋值给值类型变量。
-
`Unit`类型它的值是`()`,可赋值给任何值变量。
-
`Nothing`类型没有实例。
![ scala继承层级](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-07_57061f76b3b5a.jpg "")
【待续】
';
Chapter07 包和引入
最后更新于:2022-04-01 20:28:08
### 1. 包 package
- 源文件目录和包之间并没有强制的关联关系。比如下面Manager.scala不一定要在`./com/horstmann/impatient`目录中。
~~~
// Manager.scala
package com {
package horstmann {
package impatient {
class Manager(name: String) {
def description = "A manager with name " + name
}
}
}
}
~~~
- 同一个文件中可以给多个包贡献内容,比如Employee.scala文件可以包含:
~~~
package com {
package horstmann {
package impatient {
class Employee(id: Int) {
def description = "An employee with id " + id
}
}
}
}
package org {
package bigjava {
class Counter {
private var value = 0
def increment() { value += 1 }
def description = "A counter with value " + value
}
}
}
~~~
### 2. 作用域规则
-
Scala的包作用域支持嵌套,包路径都是相对的。
-
`java.lang`、`scala`和`Predef`总是被引入,这种引入被称为隐式引入。
### 3. 串联式包语句
- 包语句可以包含一个“串”或者说是“路径区段”
~~~
package com.horstmann.impatient {
package people {
class Person(val name: String) {
val friends = new collection.mutable.ArrayBuffer[Person]
// com和com.hosrstmann的成员这里不可见
def description = name + " with friends " +
friends.map(_.name).mkString(", ")
}
}
}
~~~
### 4. 文件顶部标记法
- 可以在文件顶部使用`package`语句,不带花括号。
~~~
package com.horstmann.impatient
class Car
~~~
### 5. 包对象
- 每个包都可以有一个包对象,要在父包中定义它,且名称与子包一样。
~~~
package com.horstmann.impatient
package object people {
val defaultName = "John Q. Public"
}
package people {
class Person {
var name = defaultName // A constant from the package
def description = "A person with name " + name
}
}
// Run as scala com.horstmann.impatient.Main
object Main extends App {
val john = new com.horstmann.impatient.people.Person
println(john.description)
}
~~~
### 6. 包可见性
- 没有被public、private或protected声明的类成员,在包含该类的包中可见,可以使用`private[包名]`达到同样效果。
~~~
package com.horstmann.impatient
package object people {
val defaultName = "John Q. Public"
}
package people {
class Person {
var name = defaultName // A constant from the package
private[impatient] def description = "A person with name " + name
}
}
~~~
### 7. 引入import
-
引入让你可以使用更短的名称
~~~
import java.awt.Color
~~~
-
引入包的全部成员,也可以引入类或对象的全部成员。
~~~
import java.awt._
~~~
-
任何地方都可以出现import引入,作用到该块的结尾。
### 8. 重命名和隐藏方法
-
只项引入几个成员,使用选择器。
~~~
import java.awt.{Color, Font}
~~~
-
重命名选到的成员。
~~~
import java.util.{HashMap => JavaHashMap}
~~~
-
`HashMap => _` 是用来隐藏某个成员。
【待续】
';
Chapter06 对象
最后更新于:2022-04-01 20:28:06
### 1. 单例对象
- scala中没有静态方法或静态变量,可以使用object达到同样的目的。
~~~
object Accounts {
private var lastNumber = 0
def newUniqueNumber() = { lastNumber += 1; lastNumber }
}
~~~
-
对象的构造器在该对象第一次被使用时调用。
-
scala对象可以用来实现:
> 1. 存放工具函数或常量
> 1. 共享单个不可变实例
> 1. 需要用单个实例协调某个服务
### 2. 伴生对象
- JAVA中会既有实例方法又有静态方法的类,Scala中用类和与类同名的“伴生”对象实现。
~~~
class Account {
val id = Account.newUniqueNumber()
private var balance = 0.0
def deposit(amount: Double) { balance += amount }
def description = "Account " + id + " with balance " + balance
}
object Account { // 伴生对象
private var lastNumber = 0
private def newUniqueNumber() = { lastNumber += 1; lastNumber }
}
val acct1 = new Account
val acct2 = new Account
acct1.deposit(1000)
val d1 = acct1.description
val d2 = acct2.description
~~~
- 类和它的伴生对象可以相互访问私有特征。
**总结**:个人理解,scala中引入object就是为了解决没有静态变量或静态方法的问题。
### 3. 扩展类或特质的对象
- 一个object可以扩展类以及一个或多个特质。
~~~
abstract class UndoableAction(val description: String) {
def undo(): Unit
def redo(): Unit
}
object DoNothingAction extends UndoableAction("Do nothing") {
override def undo() {}
override def redo() {}
}
val actions = Map("open" -> DoNothingAction, "save" -> DoNothingAction)
actions("open") == actions("save")
~~~
### 4. apply方法
- 不使用new,而直接使用object(参数1,…,参数N),这时候apply方法会被调用。
~~~
class Account private (val id: Int, initialBalance: Double) {
private var balance = initialBalance
def deposit(amount: Double) { balance += amount }
def description = "Account " + id + " with balance " + balance
}
object Account { // The companion object
def apply(initialBalance: Double) =
new Account(newUniqueNumber(), initialBalance)
private var lastNumber = 0
private def newUniqueNumber() = { lastNumber += 1; lastNumber }
}
val acct = Account(1000.0)
val d = acct.description
~~~
### 5. 应用程序对象
- scala程序都是从对象main方法开始
~~~
object Hello {
def main(args: Array[String]) {
println("Hello, World!")
}
}
~~~
- 保存为Hello.scala文件,执行:scalac Hello.scala编译文件,执行:scala Hello运行程序。
### 6. 枚举
- scala 中没有枚举类型,但有枚举类,Enumeration
~~~
object TrafficLightColor extends Enumeration {
val Red, Yellow, Green = Value
}
TrafficLightColor.Red
TrafficLightColor.Red.id
object TrafficLightColor extends Enumeration {
val Red = Value(0, "Stop")
val Yellow = Value(10) // Name "Yellow"
val Green = Value("Go") // ID 11
}
~~~
-
Value中可以不传值,可以传入ID、名称。
-
枚举的ID可以通过id方法返回,名称通过toString方法返回。
【待续】
';
Chapter05 类
最后更新于:2022-04-01 20:28:04
### 1. 简单的类和无参方法
- 类的定义及调用方法。所有类都是公有的。
~~~
class Counter {
private var age = 0 // 必须初始化字段
def incre() { // 方法默认是公用的
age += 1
}
def current() = age // 可以不带()
}
//调用部分
val myCounter = new Counter // 或new Counter()
myCounter.incre()
println(myCounter.current) // 调用无参数方法()可以加上
~~~
- 风格定义:对于改值方法建议使用(),对于取值方法建议省略(),比如上面的current方法。
### 2. 带getter和setter的属性
-
getter和setter被称为值得属性,可以在方法里定义取值和改值具体限制。
-
在下面案例中`getter`和`setter`分别叫做`age`和`age_=`,如果下面定义的是私有变量,那么这两个方法也是私有的。公有字段的这两个方法是公有的。
~~~
class Person {
var age = 0
}
~~~
- 可以重新定义age和age_=,它们在调用时都是一样的,都是直接用age就可以了。
~~~
class Counter {
private var Age = 0 // 必须初始化字段
def age_= (newAge: Int) {
if (newAge > Age) // 保证值不能变小
Age = newAge
else
println("不能使值变小!")
}
val myCounter = new Counter
myCounter.age = 10
myCounter.age = 3
println(myCounter.age)
~~~
-
Scala对每个字段都生成getter和setter方法,这些字段要被定义为**var**,若字段是**val**,只有getter方法生产。
-
Scala中不能只写setter,不写getter。(会报错)
### 3. 对象私有字段
~~~
class Counter {
private var value = 0
def increament() {
value += 1
}
// 可以访问另外一个对象的私有变量
def isLess(other: Counter) = value < other.value
}
~~~
- 要想限制上面的这种访问对象私有变量,就必须将value定义成`private [this] var value = 0`,这种定义字段被成为对象私有字段,scala不会生成getter或setter方法。
### 4. Bean 属性
- JavaBeans规范中把属性定义为一对`getFoo/setFoo`方法。只要在scala字段标记@BeanProperty时,这样的方法会自动生成。
~~~
import scala.reflect.BeanProperty
class Person {
@BeanProperty var name: String = _
}
~~~
- 字段生成方法:
![字段生成方法](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-07_57061f764c447.jpg "")
### 5. 辅助构造器
-
scala中辅助构造器和Java中的构造函数对应。
-
辅助构造器名称是this
-
辅助构造器必须以一个先前以一个已经定义的主构造器或其他辅助构造器的调用开始。
~~~
class Person {
private var name = ""
private var age = 0
def this(name: String) {
this() // 主构造器,没有显示定义就自动拥有一个无参主构造函数
this.name = name
}
def this(name: String, age: Int) {
this(name)
this.age = age
}
}
~~~
- 调用方法如下
~~~
var p1 = new Person
var p2 = new Person("Aaron")
var p3 = new Person("Aaron", 23)
~~~
### 6. 主构造器
-
每个类都有主构造器。
-
主构造器的参数直接放置在类名之后,可以使用默认参数,如age。
~~~
class Person(val name: String, val age: Int =0) {
...
}
~~~
-
主构造器会执行类中的所有语句。
-
如果类名后没有参数,则该类是一个无名主构造器。
-
构造参数不带val或var,这样参数如何处理取决于它们在类中如何使用。
-
主构造器参数生成的字段和方法:
![主构造器参数生成的字段和方法](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-07_57061f766f1c0.jpg "")
### 7. 嵌套类
~~~
import scala.collection.mutable.ArrayBuffer
class Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Member]
}
private val members = new ArrayBuffer[Member]
}
~~~
【待续】
';
Chapter04 映射和元组
最后更新于:2022-04-01 20:28:01
### 1. 构造映射
-
映射是键值对偶的集合,映射只做查询。
-
构造一个不可变的Map[String, Int]。
~~~
val scores = Map("Alice" -> 10, "Aaron" -> 20, "Bob" -> 13) // 这两种方法等价
val scores = Map(("Alice", 10), ("Aaron", 20), ("Bob", 13))
~~~
- 构造一个可变的映射。
~~~
val scores = scala.collection.mutable.Map("Alice" -> 10, "Aaron" -> 20, "Bob" -> 13)
~~~
- 构造一个空的映射。
~~~
// chapter01 中提到如果没有初始化是要用new函数的
val scores = new scala.collection.mutable.HashMap[String, Int]
~~~
### 2. 获取映射中的值
- 若映射不包含请求中使用的键,要检查有某个键要用contains方法,组合语句是getOrElse。
~~~
val Bobscore = scores("Bob") // 获取“Bob"对应的值
val bobsocre = if (scores.contains("BoB")) scores("Bob") else 0
val bobsocre = scores.getOrElse("Bob", 0) // 与上面的第二条语句等价
~~~
### 3. 更新键中的值
- 映射可变
~~~
socres("Bob") = 10 // 更新键“Bob”值
scores("Fred") = 7 // 添加新键值对
scores += ("Bob" -> 10, "Fred" -> 7) // +=可以添加多个
socres -= "Alice" // 移除某个键值对
~~~
- 若映射是不可变的,可以用同样的方法获取一个包含所需要的更新的新映射。
~~~
val newScores = scores + ("Bob" -> , "Fred" -> 7)
~~~
- 还可以将scores定义为var直接更新var变量。
~~~
var scores = Map("Alice" -> 10, "Aaron" -> 20, "Bob" -> 13)
scores = scores + ("Bob" -> 13, "Fred" -> 7)
scores = scores - "Alice" // 移除某个键值对
~~~
### 4. 迭代映射
- 用`for ((k, v) <- 映射)`就可以遍历所有键值对。若只需访问键或值,用`values`和`keySet`方法。
~~~
for ((k, v) <- scores) println(k + v)
for (k <- scores.keySet) println(k)
for (v <- scores.values) println(v)
~~~
### 5. 已排序映射
- 要的的一个不可变的树形映射而不是默认的哈希映射的话,可以用:
~~~
val scores = scala.collection.immutable.SortedMap("Alice" -> 10, "Aaron" -> 20, "Bob" -> 13)
~~~
- scala中可变的树形映射还没有
### 6. 元组
-
元组(tuple `['tjup(ə)l]`)是不同类型的值的聚集,映射是键值对偶的集合,对偶是元组的最简单形态。
-
访问元组的组元,用`_1、_2、_3`方法,元组下表是从**1**开始。
~~~
val t = (1, 2.2323, "Bob")
val second = t._2
val second = t _2//点也可以用空格来表示
~~~
- 用模式匹配来获取元组的组元,不需要的组元用`_`表示。
~~~
val (first, second, third) = t
val (first, second, _) = t
~~~
- 元组可用于函数返回多个值得情况。
### 7. 拉链操作
- 元组就是把多个值绑定在一起,可以用`zip`操作,`toMap`方法将对偶转成映射。
~~~
val name = Array("Bob", "Fred")
val scores = Array(2, 4)
val pairs = name.zip(counts) // 得到元组
val score = pairs.toMap // 转成映射(哈希表)
~~~
【待续】
';
Chapter03 数组相关操作
最后更新于:2022-04-01 20:27:59
### 1. 定长数组
- Scala定义用到Array,定义如下。
~~~
val nums = new Array[Int](10) // 10个整数数组,初始化为0,String初始化为null
val s = Array("hello", "world") // 已经提供初始值就不需要new
s(0) // 调用时用的是()而不是[]
~~~
### 2. 变长数组:数组缓冲
- 变长数组用到ArrayBuffer。
~~~
import scala.collection.mutable.ArrayBuffer// 头文件
val b = ArrayBuffer[Int]() // 一个空数组缓冲
b += 1 // 在尾端添加元素
b += (1, 2, 3, 4) // 在尾端添加多个元素
b ++= Array(7, 9, 8) // ++=可以追加任何一个集合
b.trimEnd(3) // 移除最后3个元素
b.insert(2, 5) // 下标2之前插入5
b.insert(2, 4, 5, 8) // 下标2之前插入多个数
b.remove(2) // 将2号下标移除
b.remove(2, 3) // 从2号下标开始移除3个元素
b.toArray // 把b从ArrayBuffer转成Array
b.toBuffer // 把b从Array转成ArrayBuffer
~~~
### 3. 遍历数组和数组缓冲
- 用for循环,until返回所有小于(不包括)上限的数字。
~~~
for (i <- 0 until b.length)
{
println(i + ": " + b(i)) // i是从0到b.length-1
}
~~~
- 也可以不用下标,直接访问数组。
~~~
for (i <- b)
{
println(i) // i就是数组中存的每个量
}
~~~
- 每两个数字一跳,i的遍历如下。
~~~
0 until (b.lenght, 2)
~~~
- 从数组尾部开始,i的遍历如下。
~~~
(0 until b.lenght).reverse
~~~
### 4. 数组转换
- 转换不改变元数组,产生一个新数组。用for(…)yield来进行数组转换。
~~~
val result = for (elem <- b if elem % 2 == 0) yield 2 * elem // 去掉奇数元素,对偶数元素翻倍
// 另外一种做法如下:
b.filter(_ % 2 == 0).map(2 * _)
// 或者
b.filter { _ % 2 == 0} map { 2 * _ }
~~~
### 5. 常用算法
- 常用算法如下:
~~~
Array(1, 4, 8).sum // 直接求和,对ArrayBuffer也一样,还有max,min
val a = b.sorted // b没有改变,将排序好的结果赋值给a
val c = b.sortWith(_>_) // 通过sortWith函数将b降序排列
~~~
- 可直接对Array排序,但不能对ArrayBuffer排序。
~~~
val a = Array(9, 3, 1)
scala.util.Sorting.quickSort(a) // a现在是Array(1, 3, 9)
~~~
- 显示Array或ArrayBuffer内容,可以用mkString,可以指定分隔符,及前后缀。
~~~
a.mkString(" and ")
//"1 and 3 and 9"
a.mkString("<", ",", ">")
//"<1,3,9>
~~~
### 6. 多维数组
- 用ofDim方法构造二维数组。
~~~
val m = Array.ofDim[Double](3, 4) // 三行,四列
m(row)(colum) = 2 // 元素访问
~~~
【待续】
';
Chapter02 控制结构和函数
最后更新于:2022-04-01 20:27:57
### 1. 条件表达式
-
Scala表达式中if/else有值。
~~~
if (x > 0) 1 else -1
等同于C++中:x>0 ? 1 : -1
~~~
-
else 部分缺失,引入`Unit类`,写成(),不带else的语句等同于:
~~~
if (x > 0) 1 else ()
~~~
这里Unit相当于C++中void。
-
Scala没有switch语句。
-
在REPL(Read-Eval-Print Loop)中输入代码块,可以键入`:paste`,然后按`Ctrl+D`结束。
### 2. 语句终止
-
Scala语言中行尾不需要分号,只要上下文能明确判断出终止即可。
-
要想在单行中写多个语句,就需要分号隔开。(不推荐使用)
~~~
if(n > 0) {r += 1; n += 1}
~~~
-
较长语句以不能用做语句结尾的符号结尾。
~~~
s = 4 + 3 * s0 - 8 + //这里的+号就告诉编译器,这不是行结尾
0.5 * 4.3 -2.3
~~~
### 3. 块表达式和赋值
-
`{}`块包含一系列表达式,块的值就是最后一个表达式的值。
-
赋值语句块的值返回的是`Unit类型`,写做`()`。
~~~
x = y = 2 //不要这么做,y=2返回的是一个Unit类型
~~~
### 4. 输入和输出
-
`print`和`println`不同之处是,`println`多输出一个换行符。还有一个带有C风格格式化的`printf`。
-
`readLine`从控制台读入一行(唯一可以带一个参数作为提示符字符串),readInt、readDouble、readBoolean、readChar、readLong、readByte等都是用于输入。
### 5. 循环
-
while和do循环与C++相同。
-
for循环结构如下。
~~~
//1 to n返回数字1到n的一个区间
//让变量遍历<-右边的表达式所有值
for(i <- 1 to n)
r = r * i
~~~
- 遍历字符串或数组时,用`until方法`,util方法返回一个并不包含上届的区间。
~~~
val s = "Hello"
var sum = 0
for(i <- 0 until s.length)//i的最后一个值为s.length-1
sum += s(i)
//上面等价于下面
for(ch <- "Hello") sum += ch
~~~
-
Scala没有提供break或continue
-
如果要用break可以使用下面三种方法:
![break方法](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-07_57061f739f033.jpg "")
### 6. 高级for循环和for推导式
- for可以提供多个生成器,用分号隔开。
~~~
for(i <- 1 to 3; j <- 1 to 3)
print(((i * 10) + j) + " ")
~~~
- 每个生成器可以带一个守卫,以if开头的Boolean表达式。(if前没有分号)
~~~
for(i <- 1 to 3; j <- 1 to 3 if i != j)
print(((i * 10) + j) + " ")
~~~
-
for循环中可以使用变量。
-
for推导式:for循环体以`yeild`开始,会构造一个集合,每次迭代生成集合中的一个值。
~~~
for(i <- 1 to 3)
i % 3//最后生成Vector(1,2,0,1,2,0,1,2,0,1)
~~~
- for推导式生成的集合与第一个生成式的类型兼容。
### 7. 函数
- 基本函数定义如下。
~~~
def fac(n : Int) = {//等号右边表达式的类型推出函数返回类型
var r = 1
for (i <- 1 to n) r = r * i
r//可以用return,但不常见
}
~~~
- 函数不是递归的,就不需要指定返回值。对于递归函数,必须指明返回类型。
~~~
def fac(n : Int) : Int= {//等号右边表达式的类型推出函数返回类型
if (n <= 0)
1
else
n * fac(n - 1)
}
~~~
### 8. 默认参数和带名参数
- 定义如下:
~~~
def f(str: String, left: String = "[", right: String = "]") = {
left + str + right
}
~~~
-
调用
~~~
f("Hello", right = "}") // 这里混合使用了未带名参数和带名参数
~~~
### 9. 变长参数
- 定义如下:
~~~
def sum(args: Int*) = {
var result = 0
for (arg <- args)
result += arg
result
~~~
- 调用
~~~
val s = sum(1, 3, 8, 9)
val s = sum(1 to 5: _*)//要使用一个值得序列,把1 to 5当成一个序列必须追加:_*
~~~
### 10. 过程
- 定义:有的函数不返回值,只需要去掉花括号前面的=号,它的返回类型是Unit。
~~~
def box(s: String) {
println("hello world" + s "\n")
}
等价于:
def box(s: String): Unit = {
println("hello world" + s "\n")
}
~~~
### 11. 懒值
-
当val被声明为`lazy`时,它的初始化将被推迟,直到我们首次取值。
~~~
lazy val words = "hello scala"
~~~
### 12. 异常
-
Scala异常工作机制跟Java和C++一样,抛出异常时,比如(throw表示式的类型为Nothing)。
~~~
throw new IllegalArgumentException("x should not be negative")
~~~
-
在if/else中,如果一个分支是Nothing类型,则if/else表达式的类型就是另一个分支的类型。
-
异常捕获用`try/catch`或`try/finally`或`try/catch/finally`。
【待续】
';
Chapter01 Scala基础知识
最后更新于:2022-04-01 20:27:54
### 1. Scala编译器
-
安装步骤:下载Scala –> 解压 –> 将Scala/bin的路径添加到PATH中 –> 打开命令行窗口 –> 输入scala
- 有Tab键补全功能
### 2. 声明值和变量
-
`val`定义的是一个常量,值无法改变,声明变量可以用`var`。
-
变量或函数的类型写在变量或函数后面。
~~~
val count:String = null
~~~
### 3. 算术和操作数重载
-
`+ - * / %`等操作符都是方法。
-
所有符号都是用方法命名,比如`4+5`等价于`4.+(5)`。通常,`a 方法 b = a.方法(b)`
-
Scala没有`++`和`--`操作,可以使用`+=1`或`-=1`等价操作。
### 4. 调用函数和方法
-
调用数学函数需要调用下面语句:
~~~
import scala.math._ //_字符是通配符,类似Java中的*,可以省略前面的scala.直接用import .math._
~~~
-
不带参数的Scala方法通常不使用圆括号。
### 5. apply方法
- `"hello"(4)` 等价于`"hello".apply(4)` // 输出’o’,是hello的第4个字符
### 6. Scaladoc
[http://www.scala-lang.org/api/2.11.0-M4/#package](http://www.scala-lang.org/api/2.11.0-M4/#package)
【待续】
';
前言
最后更新于:2022-04-01 20:27:52
> 原文出处:[Scala入门指南](http://blog.csdn.net/column/details/scala-for-begginner.html)
作者:[zcf1002797280](http://blog.csdn.net/zcf1002797280)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# Scala入门指南
> 大数据处理语言Scala入门指南,本课程可作为学习Spark内存计算框架的先导课程。从语言实际应用的层面由浅入深开始知道,并有大量案例代码。
';