scala学习笔记(1-8)
包含内容
- 1基础
- 2控制结构和函数
- 3数组相关操作
- 4映射和元组
- 5类
- 6对象
- 7.包和引入(x)
- 8继承
1基础
- sqrt(2)、pow这些数学函数在scala.math包中定义,当然该包中还包含一些类,不仅有函数。
- import scala.math._ 等于import math._,scala可以省略,代表引入该包下所有东西。
- import java.util.{HashMap => JavaHashMap} //重命名
- import scala.math.{BigInt =>_ , _} 引入除BigInt外的所有成员
- "_" 是scala中通配符,类似java的"*"
- RichInt、RichDouble、RichChar比Int、Double、Char提供了更丰富功能
- apply方法:伴生对象构建对象的常用方法。
- a.方法(b) 等于 a 方法 b 。如a + b = a.+(b)
- StringOps的def count (p :(Char) => Boolean):Int 该方法需要传入一个接受单个Char并返回true或false的函数,用于指定哪些字符串应当被清点。
- scala底层用java.lang.String类来表示字符串,不过通过StirngOps类给字符串追加了上百种操作,
- "hello".intersect("world") ,这个表达式中java.lang.String对象"hello"被隐式转换成StringOps对象,接着调用StringOps类的intersect方法。
- Unit类型等同于java或c++中的void
- ScalaDoc文档中,每个类旁边的O和C,分别对应类(C)或伴生对象(O)
- scala中没有++操作,如value++,不允许
2控制结构和函数
REPL
- 在命令行键入scala进入REPL模式
- REPL中键入:paste可以把代码粘贴进去,然后Ctrl+D,REPL就会把代码块当做一个整体来分析。
- Tab键提示
表达式、块表达式和赋值
-
scala中表达式(eg:3+4)和语句(eg:if)都有值。
-
val s = if( x>5) 22 else 2 //if语句给变量赋值
-
块表达式{},包含一些了表达式,块中最后一个表达式就是块的值。
- val distance={ val ds = x-x0; val dy =y -y0; sqrt( dx * dx + dy * dy) } //{}的值取最后一个表达式的值
-
赋值语句的值为Unit,val d = { r = 5 } //此时块表达式的值为Unit,所以d的值为Unit。
- var y = 3; val x = y = 1 ;//别这样做,结果:y = 1,但是x = Unit.
-
输出:print、println、printf("hello,%s! you are %d years old.\n","liang",23)
-
输入:readLine、readInt、readFloat、readBoolean、readChar etc.
-
循环:
-
基本语法结构:for( 变量 <- 表达式) ,eg:for( i<- 0.until(s.length) )、for(ch <- "Hello" )
-
高级for循环:以变量 <- 表达式的形式提供多个生成器,用分号隔开它们,每个生成器都可以带一个守卫:以if开头的Boolean表达式。
-
for( i < 1 to 3 ;j <- 1 to 3 ) print( (10* i +j ) + " ")
//打印:11 12 13 21 22 23 31 32 33 ,每个i都会经历j<-1 to 3 -
for( i < 1 to 3 ; from = 4 -i ;j <- from to 3 ) print( (10* i +j ) + " ")
//打印: 13 22 23 31 32 33 ,每个i都会经历from = 4 -i ;每个form都会经过j <- from to 3 -
for( i < 1 to 3 ;j <- 1 to 3 if i != j) print( (10* i +j ) + " ") //带守卫,注守卫没有分号
//打印: 12 13 21 23 31 33 ,每个i都会经历j <- 1 to 3 if i != j -
for推导式:循环体以yield开始,则循环会构造出一个集合,每次迭代生成集合中的一个值。且集合与它的第一个生成是类型兼容的。
- for( c <- "Hello";i<-0 to 1) yield (c+i).toChar
// String = "HIeflmlmop" - 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
-
没有break语句,但是Breaks对象提供了break方法。
函数
-
方法对对象(不是类)进行操作,函数不是。在java中我们用静态方法来模拟函数。scala.math中的sqrt就是函数。
-
定义函数:函数名、参数、函数体
-
def abs( x:Double ) = if( x >= 0 ) x else -x;
-
参数类型必须给出。
-
函数体有多个表达式,可以使用代码块。
-
返回类型:函数不是递归的就不需要指定返回类型,可以根据=符号右侧表达推断出来。
-
默认参数
定义: def decorate(str :String, left: String ="[", right: String ="]") = left + str +right
调用:decorate("hello") //返回"[hell0" -
变长参数:语法
def sum(args: Int* )={
var result = 0
for (arg <- args ) result += arg
result
}
- sum函数被调用时传入的单个参数必须是整数,而不是一个整数区间,如果传一个区间需追加:_*,告诉编译器你希望这个参数被当做参数序列处理。
- val s = sum(1,4,3,3) 或 sum( 1 to 5:_*)//将1 to 5当做参数序列处理。
- 当调用变长参数且参数类型为Object的java方法,如MessageFormat的format方法,需要手工对基本类型进行转换,如:
val ss = MessageFormat.format("the answer to {0} is{1}","everything",42.asInstanceOf[AnyRef]);
public static String format(String pattern, Object ... arguments)
过程
- 过程:如果函数体包含在花括号中单没有前面的=号,那么返回类型就是Unit,这样的函数称作过程。过程没有返回值。
def procedure(){
print("ddd");
}
或者
def procedure():Unit={
print("ddd");
}
懒值
- 当val被声明为lazy时,他的初始化将被推迟,直到我们首次对他取值。
- 对于开销较大的初始化语句十分有用
- 懒值介于val和def的中间状态。
异常
- scala没有受检异常,不需要声明函数或方法可能会抛出某种异常。
- throw表达式的返回类型是Nothing。
- 对于if/else,如果有一个分支是Nothing,那么if/else表达式类型就是另一个分支。
- if( x >= 0 ) { sqrt(x) } else throw new IllegalArgumentException("x should not be negative" ) 类型为Double
-
捕获异常采用模式匹配。
捕获异常 - 如果不使用异常则可用_来代替变量名
- try/finally可以释放资源
快学scala第三章习题答案
3数组相关操作
- 长度固定使用Array,长度变化用ArrayBuffer。用toBuffer、toArray方法相互转化。
- val nums = new ArrayInt //数组初始化为0
- val b = ArrayBufferInt
- b +=1 , b+=(1,2,5),b++= Array(1,2,3,8)
- 提供初始值是不要用new
- val s = Array("hello","world")
- 用()来访问元素
- nums(2)
- 用for( elem <- arr )来变量元素
- 用for( ele<-arr if ...) ...yield ...来将原数组转型为新数组,原数组不变。
- if( elem <- a if( elem % 2 == 0 ) yield 2 * elem //对每个偶数翻倍,并存在新集合中
- a.filter ( _ % 2 ==0 ).map(2 * _) 另一个写法
- scala数组和java数据可以互操作,用ArrayBuff,使用scala.collection.javaConversions中的转函数。
常用算法
- Array(1,2,3).sum 求和,元素是数值类型
- Array("dd","a","zh").max、min //求最大最小元素
- Array("dd","a","zh").sorted返回排序的数组或缓冲数组,原数组不变
- scala.util.Sorting.quickSort(a) :对数组排序,但不适用缓冲数组
- min、max、quickSort操作,元素必须支持比较操作,包括数字、字符串、及带Ordered特质的类型
- mkString:显示数组或缓冲数组内容,允许指定元素间的分隔符
解读Scaladoc
-
对Array类的操作方法列在ArrayOps相关条目下,在数组上应用这些操作之前,数组都会被转化成ArrayOps对象。
数组函数解读
数组函数解读
二维数组
- Double的二维数组类型为:Array[Array[Double]]。
- 构造方法:val matrix = Array.ofDim[Double](3,4) //三行,四列
- 访问元素:matrix(row)(column)
与java的互操作
- scala数组是由java数组实现的,你可以在java和scala之间来回传递。
- 引入import scala.collection.JavaConversions.bufferAsJavaList 可以实现Scala到Java的转换
import scala.collection.JavaConversions.bufferAsJavaList
val command = ArrayBuffer("ls","-al");
val pb = new ProcessBuilder(command);
其中ProcessBuilder为java中的类, 构造函数为public ProcessBuilder(List<String> command)
- 引入import scala.collection.JavaConversions.asScalaBuffer 可以实现Java集合到Scala的转换
import scala.collection.JavaConversions.asScalaBuffer
val pb = new ProcessBuilder(command);
val cmd:Buffer[String] = pb.command();
其中ProcessBuilder为java中的类, public List<String> command()
4映射和元组
- 不可变映射和可变映射
- 默认情况下得到的是一个哈希映射,不过可以指明要树形映射
创建映射
- val scores = scala.collection.immutable.Map("a"->10,"b"->5) //不可变的Map[String,Int],其值不可变
- val s = scala.collection.mutable.Map("a"->20) //可变映射
- val d = new scala.collection.mutable.HashMap[String,Int] //空映射
- 映射是对偶的集合
- ->操作符和(key,value)来创建对偶 : "dd"->2 或者 ("dd",2)
获取映射值
- 使用()符号,s("a")
-
如果映射不存在请求使用的健,会抛出异常
- 检测映射中是否包含键:contains方法
- s.getOrElse("b",0) //如果不存在b键则返回0.
更新映射值
- 针对可变映射
- s("a") = 6 //更新a键的值位6,或者是增加键值对"a"->6
- s+= ("w"->3,"ss"->9) //增加多个
- s-= "a" //移除键a
迭代映射
- 语法:
for( (k,v)<- 映射 ) 处理k和v
-
for( v <- s.values ) 处理v
//只访问值,键用keySet -
for( (k,v) <- 映射 ) yield (v,k)
//交换键和值得位置
已排序映射
- 默认情况下映射的实现是一个哈希映射,不过可以指明要树形映射。
- 输出的内容是有序的而不是根据输入顺序,按照字典序排序
- 树形映射同样有可变和不可变的。
var s = collection.mutable.SortedMap("bc"->5) //可变映射,可变变量s
s("b")=3 // b->3 ,bc- > 5
s+=("a"->2) //a->2 , b->3 ,bc- > 5
与java互操作
- 之前是引入scala
- 引入import scala.collection.JavaConverters._对象
val source = new java.util.HashMap[String,Int]
val s:scala.collection.mutable.Map[String,Int] = source.asScala
val source = new scala.collection.mutable.ListBuffer[Int]
val target: java.util.List[Int] = source.asJava
val other: scala.collection.mutable.Buffer[Int] = target.asScala
assert(source eq other)
- 过时方法:
import scala.collection.JavaConversions.mapAsScalaMap
import scala.collection.JavaConversions.propertiesAsScalaMap val
val map:Map[String,Int] = new TreeMap[String,Int]
props:scala.collection.Map[String,String] = System.getProperties()
元组(tuple)
- 不同类型值的聚集
- 如:val s =(1, 2.3, "ddd") 对应类型为:Tuple3[Int,Double,java.lang.String]或(Int,Double,java.lang.String)
- 获取元组值
- _序号 访问组元,元组的各组元从1开始,而不是0
- 如: s._2 或 s _2(空格) //2.3
- 模式匹配获取
- val (first,second,_) = s // 在不需要的位置上用_,
拉链操作zip
-
把key和value组合在一起
拉链操作
"Hello".zip("World")
res0: scala.collection.immutable.IndexedSeq[(Char, Char)] = Vector((H,W), (e,o), (l,r), (l,l), (o,d))
5类
- 一个主构造器,多个辅助构造器,辅助构造器叫做this
getter和setter属性
- 类中字段自动带有getter、setter方法,并且对应的getter和setter方法为:age和age_=。(假设属性为age)。属性getter、setter方法的公有和私有性与字段有关。
- 可通过javap -c 字节码文件:查看生成的信息。
class Person(val name:String,var sex:Boolean){ //公有name的get,公有sex的get、set方法
var age =0 //公有字段,公有的getter、setter方法
private var address="china" //私有字段,私有的getter、setter方法
val school="cnu" //只getter方法
private[this] var tel="1134423" //对象私有字段,不会生产getter和setter方法
}
- 定制getter、setter方法
class Person(){
private var myAge =0 //公有字段,公有的age、age_=方法
def age = myAge //为myAge定制get、set方法
def age_=(newAge:Int){
if( newValue > myAge ) myAge = newAge
}
}
字段生成方法
字段访问权限
Bean属性
- JavaBeans规范把java属性定义为一对getFoo/setFoo方法(或者对只读属性而言单个getFoo方法),而scala提供的getter、setter方法的名称并不是java所预期的。但是许多java工作都依赖javaBeans的名称习惯,所以通过将字段标注为@BeanProperty时,就会生产javaBeans版的getter、setter方法。
辅助构造器
- 名称为this
- 每一个辅助构造器必须以一个对先前已定义的其它辅助构造器或主构造器的调用开始。
主构造器
-
主构造器参数直接放置在类名之后
image.png -
主构造器参数生成字段和方法
主构造器 -
注意:class Person(name :String){}//对于这种name没有被方法使用的情况,Person不会生成该字段。
嵌套类
- Java中的内部类从属于外部类。但是Scala中,每个实例都有自己的内部类,就像自己的字段一样。
class Person{
private val members = new ArrayBuffer[Member]
class Member(val name:String){
}
}
val zhangsan = new Person
val lisi = new Person
- 其中zhansan.Member和lisi.Member是两个不同的类。
- 如果不希望这种效果:(即zhansan.Member和lisi.Member是两个不同的类。),有2种方式解决:
- 1.将Member类移到别处:推荐位置是伴生对象,即Person的伴生对象中。
object Person{
class Member(val name:String){
}
}
class Person{
private val members = new ArrayBuffer[Person.Member] //注意
}
- 2.类型投影:Person#Member, 含义:“任何Person的Member”
- 内嵌类中,可以通过 外部类.this的方式来访问外部类的this引用。
- 也可以使用class Person { outer=>语法来使outer变量指向Person.this。
快学scala第五章习题答案
6对象
用对象作为单例或存放工具方法
object Accounts{
private var lastNumber =0
def newUniqueNumber() ={lastNumber += 1 ;lastNumber }
}
- scala没有静态方法和字段,用object来达到同样目的,对象定义了某个类的单个实例。对象的构造器在该对象呗第一次使用时调用,如果一个对象从未被使用,那么构造器不会执行。
类可以拥有一个同名的伴生对象(在同一个源文件中)
- java中经常会用到既有实例方法又有静态方法的类,在Scala中通过类与同名的“伴生”对象来达到同样目的。
class Accounts{
val id = Accounts.newUniqueNumber()
private var balance =0.0
def deposit(amount: Double){balance += amount}
}
object Accounts{
private var lastNumber =0
def newUniqueNumber() ={lastNumber += 1 ;lastNumber }
}
- 类和它的伴生对象可以相互访问私有特性,他们必须存在同一个源文件中
对象可以扩展类或特质
- object对象可扩展类及多个特质
- image.png
- val acions = Map("open"->DoNothingAction, "save"-> DoNothingAction)
对象的apply方法通常用来构造伴生类的新实例
- Object(参数1,...,参数N) 表达式就会调用apply方法。
- 因为Account定义了apply方法,val acct = Account(1000.0)就变得可行了。
如果不想显示定义main方法,可以扩展App特质的对象
你可以通过扩展Enumeration对象来实现枚举
7.包和引入
包
- 同一个包可以定义在多个文件当中,源文件目录和包也没有强制关联关系:Utils.scala可以不在com/horstmann目录下。
package com {
package horstmann{
class Utils{}
pacakge impatient{
class Employee{ } //作用域:可以访问Utils类中方法
}
}
}
等同于
package com.horstmann
class Utils{}
package impatient
class Employee{}
串联式包语句
- 这样的包语句限定了可见成员,com.horstmann、com的成员在这里不在可见
package com.horstmann.impatient{
package people{
class Person
}
}
包对象
package com.horstmann.impatient
package object people{
val defaultName = "John Public"
}
package people{
class Man{
val name = defaultName //从包对象拿到常量值,因为在同一个包中不需要使用com....people.defaultName访问
}
}
- 可以把工具函数或常量添加到包而不是某个Utils对象。
- 每个包都可以有一个包对象,你需要在父包中定义它,且名称与子包一样。
- 使用:不在同一个包中使用 com.horstmann.impatient.people.defaultName访问
包可见性
-
Java中被default(默认)修饰的成员在包含该类的包中是可见的。Scala中可以通过修饰符可以使类中成员在包中可见。
类成员可见度
类私有字段、对象私有字段
- scala中方法默认可以访问该类的所有对象的私有字段。
class Counter{
private var value =0 //对象私有字段,Counter类方法可以访问Counter类所有对象的value字段
private[this] var age =0 //对象私有字段,Counter类方法只能访问到当前对象的age字段
def increment() = {value+=1}
//可以访问other.value字段,因为other也是Counter类。
def isLess(other:Counter ) = value < other.value
//下面other.age访问是错误的,因为age是对象私有的
def isLessAge(other:Counter ) = age < other.age
}
引入
- 重命名和隐藏
import java.util.{HashMap => JavaHashMap} //重命名
import java.util.{HashMap =>_,_} //隐藏,第二个“_”符号是java.util._的意思
- 隐式引入
- 但是scala下的类会覆盖java.lang下的类,而不是报错
import java.lang._
import scala._
import Predef._
- 在任何地方都可以声明引入,import的效果一直延伸到包含该语句块的末尾。
class Manager{
import scala.collection.mutable._ //该引入只在Manager{}语句块中有用
val sub = new ArrayBuffer[Employee]
}
8继承
- extends、final关键字和java中相同
- 类、字段、方法声明为final就不能被继承和重写。(而java中字段final只是表明不可变,可以被重写)
- 重写方法、字段时必须用override,重写超类抽象方法则不需要
- 调用超类方法同样用super。eg:super.toString()
- 只有主构造器可以调用超类的主构造器
class Employee(name:String,age:Int,val salary:Double) extends Person(name,age)
- 与java不同protected的成员对于类所属包是不可见的,可以通过protected[包]实现
类型检查和转换
Scala | Java | 说明 |
---|---|---|
obj.isInstanceOf[CI] | obj instanceof CI | obj是否属于CI类及其子类 |
obj.asInstanceOf[CI] | (CI)obj | 将obj转换成CI类,如果obj是CI子类会出错 |
obj.getClass == classOf[CI] | CI.class | 测试obj是否是CI类,但又不是其子类 |
抽象类
- abstract关键字,抽象方法省去其方法体,存在至少一个抽象方法,则必须声明为abstract
- 重写超类抽象方法则不需要override
构造顺序和提前定义
- 当你在子类中重写val并且在超类的构造器中使用该值的话,会出现不符合预期的结果。(和java一样)
- 举例:下面的Ant类继承Creature,并且重写range字段,getter方法也被重写。在实例化Ant之前会先实例化父类Creature,在执行Createture构造函数中,先设置range字段为10 ,然后为了初始化env数组,用到了range的getter方法,getter方法被重写,所以调用子类getter方法,此时Ant类还未进行实例化,range字段的值为零值,所以返回0,所以env的数组长度为0.
class Creature{
val range:Int = 10
val env:Array[Int] = new Array[Int](range)
}
class Ant extends Creature{
override val range = 2
}
//下面代码写在main函数中
val ant = new Ant
print(ant.env.length) //返回0
- 解决上面问题明白2个原理:1.类加载中,父类先与子类之前执行初始化动作。类加载的初始化阶段会执行<cinit>函数,即对类变量(static修饰的)进行赋值。2.父类的的实例化先于子类实例化,非类变量在实例化中会被赋值。
- 解决方法:
- 将超类val声明为final,则在scala中该字段就不能被重写(注:java中可以),将子类重写的字段声明为final可以得到正确的值,对于本例就是2
- 将超类的env声明为lazy,即env在使用时才初始化,而此时ant早已实例化完成。
- 在子类使用提前定义语法:让你可以在超类的构造器执行之前初始化子类的val字段。
- class Ant extends { override val range = 2 } with Creature
Scala继承层级
image.png对象相等性
- AnyRef的eq方法检查两个引用是否指向同一个对象,AnyRef的equals调用eq。
- 重写equals,提供一个自然的与实际相称的相等性判断,语法定义:加上final,参数类型必须为Any.
- final override def equals(other: Any)={}
- 在程序中,只要用==就可以,对于应用类型而言,他会在做完必要的null检查后调用equals方法。