看完<快学scala>的一些笔记.

该书的课后练习参考答案: https://github.com/vybae/scala-hello

  • 类型推断错误有时候ide检测不出来
  • ide联想到的api不能总是及时展示出来,有时候忘记语法api什么的,可能自己写的是对的,但是没写完之前ide判断是错误的
  • 定义变量用valvar,前者不可变量.后者可变量.推荐尽量使用val
  • 若val/var或者表达式未赋值,则默认值为Unit空
  • 操作符其实是方法,比如a+b是a.+(b)的简写
  • 可以使用几乎任何符号来为方法命名,
  • 没有三目运算符,但是可通过if else来替代
  • 可以返回不指定的类型,也就是返回的类型不是通过方法头来确定的,而是根据方法最终的结果决定.
  • 上面这条规定其实是由于valvar的引进,使得变量/常量的类型不需要提前声明,因而可在赋值时才确定.
  • 函数可以不声明返回类型(除了递归),但是函数的所有参数必须声明类型.
  • 引入了一个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声明,密封类的所有子类都必须在与该密封类相同的文件中定义.