看完<快学scala>的一些笔记.
该书的课后练习参考答案: https://github.com/vybae/scala-hello
- 类型推断错误有时候ide检测不出来
- ide联想到的api不能总是及时展示出来,有时候忘记语法api什么的,可能自己写的是对的,但是没写完之前ide判断是错误的
- 定义变量用
val
和var
,前者不可变量.后者可变量.推荐尽量使用val
- 若val/var或者表达式未赋值,则默认值为Unit空
- 操作符其实是方法,比如a+b是a.+(b)的简写
- 可以使用几乎任何符号来为方法命名,
- 没有三目运算符,但是可通过if else来替代
- 可以返回不指定的类型,也就是返回的类型不是通过方法头来确定的,而是根据方法最终的结果决定.
- 上面这条规定其实是由于
val
和var
的引进,使得变量/常量的类型不需要提前声明,因而可在赋值时才确定. - 函数可以不声明返回类型(除了递归),但是函数的所有参数必须声明类型.
- 引入了一个Unit类,写作
()
,相当于java中的void,像下面这条语句,没有else语句,如果if条件不成立,需要走else,那么else的返回值默认是Unit.
val a = if(n > 0) "Hello World"
-
不需要加分号表示语句结束,除非一行上有多条语句.
-
代码块也是一种表达式,有值,值为表达式的最后语句的返回值.
-
for循环分为for to和for until语句,区别在于最后一次是否执行
-
没有break和continue关键字,需要引入util.control.Breaks._包
-
for循环默认返回的是Unit空值,不过可以配合yield使用返回一个集合.yield的作用是把当前的元素记下来,保存在集合中,循环结束后将返回该集合.
-
函数比较灵活,没有参数时不用写括号,不过也看不出来调用的是变量还是方法.
-
//sortWith里面的"_"是参数的简略表示 val r1 = arr1.sortWith(_ < _)
-
object中有main方法的话就只会执行main方法,否则顺序执行object中全部代码块
-
对list,set,map的操作很灵活,可通过操作符而不是api来操作集合,并且它们之间的转化也很方便.
-
对list,set,map的操作一般都是返回新的集合,不会改变原来的集合.
-
所谓守卫,就是以if开头的Boolean表达式
-
没有返回值(实际返回的是Unit)的函数称为过程
-
元组的访问从1开始而不是0.元组常用于返回值不止一个的情况.
-
yield关键字好像不能放在大括号里面
-
可变的map创建时要注意有new关键字
-
如果没有给某个参数传递值,那么Scala将会传递一个默认值(仅限基本的Int,String等).但如果这个参数是自定义类型(抽象对象),Scala没有它的默认值,此时我们需要借助implicit给它传默认值,也就是隐式参数.在同一个上下文环境中,同一类型的隐式参数只能有一个.
有意思的???
override def deleteFinalPriceOfferFeedbackById(id: Int): Unit = ???
/** `???` can be used for marking methods that remain to be implemented.
* @throws NotImplementedError
*/
//也就是等待实现的方法,也就是java中的抽象方法?
def ??? : Nothing = throw new NotImplementedError
变长参数
def sum(args:Int*)={
var result=0
for (arg <- args ) {
result+=arg
}
result
}
println(sum(7,2,3))
for循环正常只能顺序遍历(也就是i++),如果要倒叙遍历,需要用reverse函数:
//实现i--打印
for (i <- (0 to 10).reverse) {
println(i)
}
Chapter5 类
-
调用无参方法时,可以写上圆括号,也可以不写.推荐对于
改值器
方法(即改变对象状态的方法)使用括号,对取值器
方法去掉括号. -
无参方法声明时可以不带(),这样调用的时候一定不能带()
-
Scala对于类中的每个字段都会设置成私有,并提供公有的getter和setter方法
-
val的字段不提供getter方法,var的字段setter和getter都提供
-
private的字段,其setter和getter都是private
-
一个类如果没有显式定义主构造器,那么它默认拥有一个无参的主构造器
-
辅助构造器的名称为this
-
val p1= new Person //主构造器 val p2=new Person("Fred") //第一个辅助构造器 val p3=new Person("Fred",42) //第二个辅助构造器
-
每个类都有主构造器,它与类定义交织在一起:
-
class Person(val name:String,val age: Int) { // (...)中的内容就是主构造器的参数 }
-
在Scala中,每个对象都有它自己的内部类,也就是a.Member和b.Member是不同的两个类
Chapter6 对象
-
对象(object)也就是类的单个实例
-
伴生对象也就是和类同名的对象,例如:
-
class Account{ ... } object Account{// 伴生对象 ... }
-
类和它的伴生对象可以相互访问private的字段/函数/构造方法,它们必须存在与同一个源文件中
-
一般都会定义apply()方法,类和对象都可以
-
假设有个Person类和它的伴生对象,声明了个该类型的person对象:
-
//显式调用apply Person.apply(...) //调用的是伴生对象定义的方法 person.apply(...) //调用的是类定义的方法 //上面的可以省去apply,效果是等价的 Person(...) person(...)
-
Scala的程序从一个对象的main方法开始,或者拓展App特性(extends App),这样就会执行对象内的所有代码块(不包含方法)
Chapter7 包和引入
-
一个文件可定义多个包.同一个包可以定义在多个文件当中(也就是要确认一个包里面有什么东西的话,在java中直接找到对应的目录即可,但是scala中可能得扫描全部的文件才能确认)
-
子包中可以访问父包内容,不需要写完整的包名.
-
在Java中,包名是绝对的;但是在Scala中,包名是相对的,因此引用错误的同名包/类的可能性较大,解决方法是使用绝对包名
-
每个包可以有唯一对应的包对象,可供包内访问调用
-
可以通过
private[类名]
来限制函数/字段的可见性,例如 -
package com.horstmann.impatient.people class Person{ private[people] def description1 ="...." private[impatient] def description2="..." }
-
import语句可以出现在哎任何地方(不限于文件顶部,也包括方法内部),作用域延伸到同一代码块的末尾
-
以下三个包总会被隐式引入:
-
import java.lang._ import scala._ import Predef._ //Predef里面有Map,新建一个map时需要小心你需要的Predef的Map还是immutable/mutable里面的Map
Chapter8 继承
-
重写方法必须使用override,重写抽象方法除外
-
在Java中,protected修饰的成员对于所在包和子类可见.但是在Scala中只对子类可见,如果需要包可见,可以用包修饰符(见chapter 7)
-
构造器内不应该依赖val的值,因为它使用的是val的初始值,来看个例子:
-
class Creature{ val range: Int = 10 val env : Array[Int] =new Array[Int] (range) } class Ant extends Creature{ override val range = 2 //此时有个隐式的env=new Array[Int] (0) //在父类中env依赖于range,可是构造器优先于字段的初始化,因此range还未初始化为10,而是有个默认的值0,于是env拿到这个0去为自己初始化了 //解决方法, 不太优雅 class Ant extends{ override val range = 2 } with Creature }
-
Null类型的唯一实例是null值,可以将null赋值给任何引用,但不能赋值给值类型的变量,比如Int.这决定了我们在使用int,long等基本类型时不可能发生空指针异常
-
Nothing类型没有实例.比如,空列表的类型是List[Nothing],它是List[T]的子类
-
判断两个对象是否相等可以直接使用==操作符,因为它会调用equals()
Chapter9 文件和正则表达式
- 如果字符串中含有\或者"“的话可以使用原始字符串"“““““来定义,这样比转义字符易读些
Chapter10 特质
类可以实现任意数量的特质
特质可以要求实现它们的类具备特定的字段/方法/超类
和Java接口不同,Scala特质可以提供方法和字段的实现
当你将多个特质叠加在一起时,顺序很重要--其方法先被执行的特质排在更后面
特质的关键词是 trait
- 特质中不需要将方法声明为abstract—因为这些方法默认就是抽象的
- 特质也可以有构造器.特质构造器的构造顺序从左往右进行(而特质执行顺序则从右往左进行)
- 构造器的执行顺序: 超类构造器 -> 父特质构造器 -> 从左往右的特质构造器 ->类
Chapter13 集合
-
+将元素添加到无先后次序的集合中
-
-和–移除元素
-
+:和:+向前或向后追加到序列
-
++将两个集合串接到一起
-
list要么是Nil(即空列表),要么由head和tail组成.head是头元素,tail也是一个list,由除了头元素以外的其它元素组成
-
注意map的声明,是(k->v)而不是(k,v)
-
注意区分map取值的两种方式,map.get(“key”)返回的是Some类型,map(“key”)返回的是单纯的value.
-
map(“key”)如果找不到对应的元素,就会报NoSuchElementException的异常,所以推荐用**map.get()或者map.getOrElse()**比较安全.
-
list转成map: list.groupby(_.key) 转换后的map使用key做键,值是一个list
-
zipWithIndex()方法是为集合的每个元素创建一个下标/索引:
-
val days = Array("Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday") days.zipWithIndex.foreach(println(_)) //(Sunday,0),(Monday,1),(Tuesday,2),(Wednesday,3),(Thursday,4),(Friday,5),(Saturday,6)
-
zipWithIndex主要的作用在于对list使用map()遍历时,可以获取元素的下标(在java中,fori循环可以获取下标,但在scala中可能是觉得这种循环写法太不优雅,所以用这个方法来替代)
-
request.priceOfferItems.zipWithIndex.map(x => FinancePriceRequestItem(x._1.price, logisticsFeeList(x._2).price)) //request.priceOfferItems是个list,这段的作用是实际跟fori是一样的.
-
flatMap = map + flatten 即先对集合中的每个元素进行map,再对map后的每个元素(map后的每个元素必须还是集合)中的每个元素进行flatten
Chapter14 模式匹配
-
模式匹配发生在运行期,此时泛型已经被擦除,所以不能用模式匹配来匹配特定类型的Map(如果匹配不上就会报错)
-
但是数组中的类型是支持匹配的
-
val n=1 //如果声明时ar的类型是具体确定的,那么根据类型匹配时就会报错 //val ar=1 //所以得用下面这种,声明为Any类型再赋值 val ar:Any = if(n> 0) 1 else "Hello" val s = ar match { case i:Int => "张三" case s:String => "李四" case _ => "王五"
-
在模式匹配里,如果case都没有匹配成功就会报错,所以最后都要用 case _来兜底
-
模式匹配列表和元组时,变量可以绑定到它们的不同部分,比如(0,…)以0开头的结构,或者(x,y)只包含x和y的结构等.这是通过提取器机制实现的.
-
BitInt和BigDecimal可以同时进行除法操作和取模操作,操作符是/%,但只有这种情况(可能是这两种运算的关联性强),其它的什么同时加减啊都是不支持的
-
在模式匹配中,匹配数组/列表/元组时, _* 操作符可以匹配剩余的全部元素
-
样例类是一种适合用于模式匹配的特殊类
-
样例class必须带括号,样例object必须不带括号
-
样例类好处:
- 创建实例时不需要new
- 免费得到toString,equals,hashCode和copy方法(浅克隆)
-
密封类通过关键字sealed声明,密封类的所有子类都必须在与该密封类相同的文件中定义.