Scala学习笔记
语言基础
声明变量
// 不指定变量类型var s=12345 //可变变量val d=12345 //不可变量,如同java中的final,C/C++中的const// 指定变量类型var str: String = "字符串变量" val a: Int = 345 //整形变量var anyType: Any= "任何类型" // 任何类型变量// 同时声明多个变量var msg,note: String = null // 声名String型变量msg和note并初始化为null
常用类型
Byte Char Short Int Long Float Double 和 Boolean
这些基本类型都是类,与Java中不同的是Scala并不刻意区分基本类型和引用类型。
算术和操作符重载
// 基本算数符var s=1+2*4
由于常用类型都是类,所以Scala中基本操作符都是方法,如:
a 方法 ba.方法(b)
这里的方法带有两个参数(一个隐式和一个显式的) 如:
1 to 10 // 等价于1.to(10)//Array(1,2,3,4,5,6,7,8,9,10)
注:Scala 中没有 ++ 操作符,需使用 +=1 代替
调用函数和方法
Scala中导入包:import scala.math._
导入math包中的所有函数,_
是通配符和Java中的*
一样,Scala中不包含静态方法,但是提供了伴生对象,其作用和Java中的静态方法类似。
apply 方法
"Hello"(1) 实际调用的是 "Hello".apply(1),函数签名为 def apply(n: Int): Char
if 表达式
Scala中if表达式也有返回值,如:
val s = if (x > 0) 1 else -1 // 等价于 int s = x > 0 ? 1:-1
Scala的if表达式的两个分支可以不同类型的返回值,如:
val s = if ( x > 0 ) "x more than 0" else -1
当if表达式中的某一分支没有返回值时,默认返回Unit类型()如:
val s = if (x > 0) "x=" + x //当x < 0 时 s=()//等价于var s = if (x > 0) "x=" + x else ()
输入输出
输出
使用print、println 输出到控制台,和带有C风格的格式化字符串输出printf。
输入
readLine 从控制台读取一行输入,readInt 从控制台读取一个Int,readBoolean从控制台读取一个Bool型数据
循环
Scala 和Java、C++一样拥有 for、while 循环
while循环
while(n > 0){ r = r * n n -= 1}
for循环
for(i <- 1 to n){ r = r * i}
Scala 中的for循环类似Java的for遍历,1 to n
一个取值范围是 $[1,n]$ 的数组
// 表达式 是一个可迭代的对象for(i <- 列表){}
for ( i <- 1 until n){ r = r * i}
其中 1 until n
是一个取值范围是 $[1,n)$ 的数组
退出循环
Scala 中并没有提供break,或者continue来退出循环,如果需要退出循环可以有以下几种方法:
- 使用Boolean型变量
- 使用嵌套函数,从当前函数退出
- 使用Breaks对象中的break方法。
import scala.util.control.Breaks._breakable{ for ( i <- 1 until 10){ println(i) if( i == 8 ){ break; } }}
for循环推导式
变量 <- 列表
是一个生成器,每个for可以使用多个生成器,使用分号隔开。每个生成器都可以带有一个守卫(以if
开头的Boolean表达式)
for(i <- 1 to 3 ; from = 4 -i; j <- from to 3 ) print(i* 10 + j + " ")//13 22 23 31 32 33
Scala 中 for循环中的循环体如果以yield开始,则该循环会构建出一个集合,每次迭代生成集合中的一个值。(返回值和第一个迭代器的类型一致)
for(i <- 0 to 1;c <- "Hello") yield (c+i).toChar//Vector(H, e, l, l, o, I, f, m, m, p)for(c <- "Hello";i <- 0 to 1) yield (c+i).toChar//HIeflmlmop
函数
Scala 中除了支持方法外还支持函数。方法能对对象进行操作,而函数不是。在Java中可以使用静态方法来模拟
def abs(x: Double) = if (x>= 0) x else -x
- 在Scala中除了递归函数需要显示的指定返回类型,其他函数都不需要显示的指定返回类型,可由Scala编译器自动推断。
- 在Scala 不需使用return,Scala会自动返回最后一个表达式的值
def fac(n: Int): Int = if (n <=0 ) 1 else n * fac(n - 1)
默认参数和带名参数
Scala中支持调用函数时不显示的给出说有参数值,对于这些函数使用默认参数。例如:
def decorate(str: String , left: String = "{", right: String = "}") = left + str + rightdecorate("AAA")// {AAA}decorate("AAA",">>>")//>>>AAA}// 带名参数right,decorate("AAA",right=">>>")//{AAA>>>
变长参数
Scala中函数支持变长度的参数列表。定义如下:
def sum(args: Int*) = { var result = 0 for (arg <- args) result += arg result}sum(1,2,3,4,5,6,7)// 28sum(1 to 8: _ *)//36
表达式: _ *
将数组、列表拆分成函数参数
过程
Scala中如果函数体包含在 {}
(花括号)中,但前面没有 =
,那么该函数的返回值类型Unit,这样的函数被称作过程。
def writeToConsole(str: String) { print(str)}// 等价于def writeToConsole(str: String): Unit = { print(str)}
懒值
当val被声明为lazy时,它的初始化会推迟到调用时候进行。
lazy val x= scala.io.Source.fromFile("exmaple.txt").mkString
异常
Scala 中异常和Java中相似,使用throw
抛出异常,使用try/catch
捕获异常。与Java中不同的是,Scala不含有受捡异常。Scala 的异常表达式有特殊类型Nothing
var s= if (x > 0) x else throw new IllegalArgumentException("x 不能是负数")
try/finally
语句用于释放资源,无论是否发生异常。
Scala数组操作
Scala数组特点
- 使用
()
访问而不是[]
- 提供初值时不使用new 使用apply方法
- 长度固定时使用Array长度不定时使用ArrayBuffter
- 使用
for(a <- array)
来进行数组遍历 - 使用
yield
生成新的数组
定长数组
val arr=new Array[Int](10) //创建一个大小为10存储类型为Int的定长数组var nums=Array(1,2,3,4,5) //创建一个大小为5存储类型为Int,并初始化为 1,2,3,4,5的数组
变长数组:数组缓冲
import scala.collection. mutable.ArrayBufferval buf = ArrayBuffer[Int]() // 声明一个内容为Int的变长数组//追加元素buf+=1 //ArrayBuffer(1)//追加多个元素buf+=(2,3,4,5)//ArrayBuffer(1, 2, 3, 4, 5)//追加集合buf++=Array(6,7)//ArrayBuffer(1, 2, 3, 4, 5, 6, 7)buf.insert(7,8) // 在第7位后面插入一个数//ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8)buf.insert(8,9,10) //在第8位后插入2个数//ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)buf.trimEnd(8) // 移除后8个元素//ArrayBuffer(1, 2)buf.toArray // 生成定长数组//Array(1, 2)
遍历数组
for(i <- 0 until a.lenght) println(i + ": " + a(i))for(item <- a) println(item)//倒序访问for( item <- a.reverse) println(item)for( i <- (0 until a.lenght).reverse) println(i + ": " a(i))// 等距访问 间距为2for(i <- 0 until (a.lenght,2) println(i + ": " a(i))
多维数组
Scala中的多维数组和Java中一样,是使用数组的数组来实现的。类型为Array[Array[Double]]
要构造这样的数组也可以使用Array.ofDim方法:
val matrix=Array.ofDim[Double](3,4) //3行4列matrix(2)(1) // 使用双括号访问
映射和元组
映射
构建映射,Scala中默认构建的是不可变映射(immutable)
//不可变映射var map = Map("A"->"AAA","B"->"BBBB")//Map(A -> AAA, B -> BBBB)//不能添加新的元素,只能生成新的映射val map2=map+("2"->"2222")//Map(A -> AAA, B -> BBBB, 2 -> 2222)//可变映射val m=scala.collection.mutable.Map("A"->"AAA","B"->"BBB")m+=("1"-> "2")//Map(A -> AAA, 1 -> 2, B -> BBB)//取值m("A")// AAA//修改值m("A")="CCC"// Map(A -> CCC, 1 -> 2, B -> BBB)
映射迭代
for((k,v) <- 映射) println(k,v)//只访问keyfor(k <- map.keySet) println(k)//只访问值for(v <- map.values) println(v)
Map元素顺序
- SortedMap 遍历时按排序后的顺序排序
- LinkedHashMap 按插入的顺序排序
- Map 按Hash值顺序排序
元组
元组是不同类型的数据集合。例如: (1234,"AAA",22.2)
是一个 Tuple3[Int,String,Double]
的元组。
元组的访问 使用_位置
访问元素,如:
var t=(123,"ABC",3.14)t._1 // 123t _2 // ABCt._3 // 3.14var (first,secode,third)=t // 通常使用元组来获取元组的值var (first,_,third)=t //不需要的值用`_`
元组可用在返回值不只一个的情况
拉链操作
将两个数组一一对应构成元组数组,多出的元素忽略不计
val keys=Array("A","B","C")val values=Array(1,2,3)val pair = keys.zip(values)pair//Array((A,1), (B,2), (C,3))
类
要点:
- 类中 字段自动带有get和set方法
- 可以自定义get和set方法而替换掉字段的定义,二不必修改类的客户端——统一访问原则
- 使用@BeanProperty注解来生成JavaBean的getXxx和setXxx方法
- 每个类都有一个主要构造器,这个构造器和类的定义“交织”在一起,他的参数直接构成类的字段。
- 辅助构造器是可选的,名字是this
简单类和无参方法
class Counter{ private var value=0 // 必须初始化 def increment() = value +=1 //方法默认是公有的 def current() = value}val count=new Counter() // 创建对象 或 new Countercount.current // 调用方法 调用无参方法时可以不带`()`count.increment() // 值自增一
声明无参方法时可以不带
()
如果声明是没有带()
那么调用时也不能带
Scala中为每个字段都提供get/set方法
class Person{ var age = 0}
这个类中声明了一个私有的属性age和公有的get/set,因为age没有声明为private所以get/set 是公有的。在Scala中get/set分别叫做age和age_=。
自定义 get/set 方法
class Person{ private var privateAge = 0 def age = privateAge def age_= (age: Int) { if(age > privateAge) privateAge = age }}
只带有get的字段
当字段声明为val
时,Scala会默认只创建get方法。
对象私有属性
在Scala中不仅支持支持类级私有(只有类的内部方法才能访问私有字段),而且还支持对象级私有(只有对象本身才能够访问的私有字段)。
class Person{ private var age=0 private[this] var name = "" //只能由当前对象访问 不会生成get/set方法 def older(other: Person): Boolean = { if(age > other.age){ // 类的方法可以访问私有字段 true }else{ false } }}
Bean属性
Scala会自动生成get/set方法,但是并不满足JavaBeans规范定义的把Java的属性定义为一对getXxx/setXxx方法。许多的Java工具依赖这样的命名习惯。所以Scala提供了注解的方式来生成JavaBeans形式的方法。
import scala.reflect.BeanPropertyclass Person{ var age = 0 @BeanProperty var name: String = _}val person=new Person()println(person.age)person.setName("张三")person.getName
构造器
Scala中包含两种构造器分别是主构造器和辅助构造器
辅助构造器
辅助构造器,和Java中的构造器很相似,不同的是:
- Scala的辅助构造器的名字是固定的统一是
this
- 每个辅助构造器都应该调用之前定义的辅助或主构造器
class Person{ var name:String=_ var age:Int = 0 def this(name:String){ this() this.name=name } def this(name:String,age:Int){ this(name) this.age=age } }val person=new Person("Jack",20)println(person.name,person.age)//(Jack,20)
主构造器
在类名后面加`private`让主构造器变成私有方法,可用在单例模式。
主构造器参数 | 生成字段/方法 |
---|---|
name: String | 对象私有字段,如果没被使用则没有改字段 |
val/var name: String | 私有字段,公有get/set val没有set方法 |
private val/var name: String | 私有字段,私有get/set |
@BeanProperty val/var name: String | 私有字段,公有的Scala和JavaBean版的get/set方法 |
- 主构造器参数直接放在类名之后 主构造器中的参数被编译成类的字段
class Person(var name:String,var age:Int){ override def toString():String = name + " is " + age + " old!"}val person=new Person("Jack",20)person.name//Jack
- 主构造器会执行类定义中的所有语句
import scala.reflect.BeanPropertyclass Person{ println("do with primary constructor") var age = 0 @BeanProperty var name: String = _ def this(name:String){ this() this.name=name }}val person=new Person("张三")println(person.getName())//do with primary constructor//张三
嵌套类
在Scala中几乎所有的结构都可以嵌套,函数中嵌套函数,类中嵌套类。
import scala.collection.mutable.ArrayBufferclass Network{ class Host(val name:String){ val contacts=new ArrayBuffer[Host] } private val members=new ArrayBuffer[Host] def join(name:String): Host = { val host=new Host(name) members+=host host }}val net1=new Network()val net2=new Network()val h1 = new net1.Host("127.0.0.1")val h2 = new net2.Host("127.0.0.1")val ht=net1.join("192.168.1.1")ht.contacts+=h1 // 正确 ht.contacts 类型是net1.Hostht.contacts+=h2 // 错误
Scala 中每个实列都有自己的Host类,要想实现每个实列的嵌套类都相同,则应该将嵌套类放到伴生对象中。 或者使用类型投影Network#Host
,其含义是,任何Network的Host。如:
import scala.collection.mutable.ArrayBufferclass Network{ class Host(val name:String){ val contacts=new ArrayBuffer[Network#Host] } private val members=new ArrayBuffer[Host] def join(name:String): Host = { val host=new Host(name) members+=host host }}val net1=new Network()val net2=new Network()val h1 = new net1.Host("127.0.0.1")val h2 = new net2.Host("127.0.0.1")val ht=net1.join("192.168.1.1")ht.contacts+=h1ht.contacts+=h2
对象
- 用对象做为单例或者存放工具方法。
- 类可以拥有一同名的伴随对象
- 对象可以继承类或者特特质(Trait)
- 对象的apply方法通常用来构建伴生对象的实列
- 如不想定义main方法,可以继承App类
- 通过继承Enumeration来实现枚举
单例对象
Scala中没有静态的字段和方法,可以使用object
来实现同样的目的。
class Person(val name:String)object Person{ var id: Long=0 def apply(name:String):Person = { id+=1 new Person(name) } def show() { printf("use apply %d times",id) }}var m,n,p,k=Person("J")Person.show()
伴生对象
在Scala中,如果需要实现既有实例方法,又有静态方法,则可以使用伴生对象来实现,伴生对象就是和类同名的对象。
class Person(val name:String){ val id=Person.uuid() override def toString = name + " id is " + id}object Person{ private var id: Long=0 def uuid():Long = { id += 1 id }}val p= new Person("Jack")val p2= new Person("Sam")
枚举类型
Scala中没有提供枚举类型,不过标准库中提供了Enumeration
来实现枚举
object Light extends Enumeration{ val RED=Value("Red") val GREEN=Value("Green") val YELLOW=Value("Yellow")}
包和引入
- 包可以像内部类一样可以嵌套
- 包路径不是绝对路径
- 包声明
x.y.z
并不自动将中间包x和x.y变为可见 - 位于文件顶部的不带括号的包声在整个文件范围生效
- 包对象可以持有函数和变量
- 引入语句可以导入包、类和对象
- 引入语句可以出现在任何位置
- 引入语句可以重命名隐藏特定成员
java.lang
scala
和Predef
总是被引入
包
Scala 中包可以嵌套,文件绝对路径和包名没有关系。
包的引入
import java.awt.Color
引入java.awt.Color
包,使用通配符_
可以导入全部对象 import java.awt._
任何地方都可以声明包的导入。
隐藏和重命名
导入几个成员 import java.awt.{Color,Font}
重命名成员 import java.util.{HashMap => JavaHashMap}
隐藏成员 import java.util.{HashMap => _ , _}
继承
- extends、final 关键字和java中相同
- 重写方法必须用override
- 只有主构造器可以调用超类的主构造器
- 可以重写字段
超类的主构造器
- 可以重写字段
继承类
和Java中相同的是final关键字使类不能被继承,和Java中不同的是final 使字段不能被重写
class Employee extends Person{ var salary = 0.0}
重写方法
class Employee extends Person{ var salary = 0.0 override def toString = "salary is " + salary}
类型转换
if (p.isInstanceOf[Employee]) { //检查p是否是Employee类型 val s = p.asInstanceOf[Employee] // 将p转换成Employee类型}
如果p是Employee的一个对象,或是其子类的一对象那么isInstanceOf[Employee]
返回true 否则返回false 如果想测试p是不是Employee的一个对像,则
if (p.getClass == classOf[Employee]){}
超类构造
Scala中只有主构造器才能调用超类的主构造器
calss Employee(var name:String,var age:Int,var salary:Float) extends Person(name,age){}
重写字段
Scala中可以使用 val 重写父类的val字段或无参方法。
class Person(val name:String){ override def toString = getClass.getName + " name is " + name}class Employee(cname:String) extends Person(cname){ override val name = "Secret" override val toString = "Secret"}val p= new Employee("Jack")println(p) //Sercet
在Scala中可以使用val来重写抽象的方法,如:
abstract class Person{ def id:Int;}class Employee extends Person{ override val id=100}// 通过主构造器 重写class Student(override val id:Int) extends Personval p =new Employee()println(p.id)
重写注意
- def 只能重写def
- val 只能重写val或不带参数的def
- var只能重写抽象的var
匿名子类
class Person(val name:String)val p=new Person("Jack"){ def say= "My name is Jack"} println(p.say) // My name is Jack
抽象类
和Java中一样当类里面包含一个以上的抽象方法时,必须将类声明为abstract 重写抽象方法时可以省略 override
abstract class Person(val name:String){ def id:Int;}class Employee(name:String) extends Person(name){ override def id = name.hashCode}val p =new Employee("Jack")println(p.id) //2300927
抽象字段
在Scala中中为初始化的字段为抽象字段。
abstract class Person{ val name:String; var age:Int;}val p =new Person{ val name = "Jack" var age=10}println(p.name) //Jackprintln(p.age) //10
构造顺序和提前定义
在Scala中,如果重写了父类字段,而该字段又在父类的构造其中使用的话,会发生意想不到的问题
class Animal{ val range:Int=10 val env:Array[Int]=new Array[Int](range)}class Ant extends Animal{ override val range:Int = 2}val a=new Ant()a.env// Array() // 错误输出
改正方式:
class Ant extends { override val range:Int = 2 } with Animal{}val a=new Ant()a.env// Array(0,0)
注:经测试重写父类中使用该字段的方法和字段也可以达到预期结果,但是,尽量避免未初始化字段被使用
class Ant extends Animal{ override val range:Int = 2 override val env:Array[Int]=new Array[Int](range)}val a=new Ant()a.env// Array(0,0)
Scala对象继承树
Scala对象相等性
在Scala中,AnyRef的eq方法检查两个引用是否指向同一个对象。AnyRef的equals方法调用eq方法,当实现一个类是应当重写相应的eq方法。 当重写equals方法时,应当重写hashCode方法。确保当两个对象不相同时,其hashCode也不同,否则会有 也不同,否则会有意想不到的问题发生。
文件和正则表达式
Source.fromFile().getLines.toArray
输出文件所有的行Source.fromFile().getLines.mkString
以字符串形式输出文件内容- 使用Java的PrintWriter来写入文本文件
"正则".r
是一个Regex对象
集合操作
map
var list=List(1,2,3,4)list.map(_*2)// List(2,4,6,8)def ulcase(s:String) = Vector(s.toUpperCase,s.toLowerCase)val list=List("Apple","Moon","Test")list.map(ulcase)//List(Vector(APPLE, apple), Vector(MOON, moon), Vector(TEST, test))
flatMap
def ulcase(s:String) = Vector(s.toUpperCase,s.toLowerCase)val list=List("Apple","Moon","Test")list.flatMap(ulcase)//List(APPLE, apple, MOON, moon, TEST, test)