《scala 编程(第3版)》学习笔记3

第10章 组合和继承

  1. 组合是指一个类包含对另一个类的引用,并头通过引用类来帮助完成任务。继承只超类/子类的关系。P179
  2. 一个方法只要没有实现(没有等号和方法体),它就是抽象的。P181
  3. 包含抽象成员的类,要声明为抽象类。abstract class 类名 P181
  4. 抽象类不能被实例化。P181
scala> class A{def func:Int } // abstract class A{def func:Int } 才对
<console>:11: error: class A needs to be abstract, since method func is not defined
       class A{def func:Int }
       
//用了空方法体也不是抽象类
scala> class A{def func:Unit={} }
defined class A
  1. 无参方法、空括号方法、字段:
    • 无参方法:调用时只能用无参形式,建议无副作用的方法定义成无参方法。P182
    • 空括号方法:调用时可用无参形式和空括号形式,建议有副作用的调用使用空括号。P184
    • 字段:访问比方法调用快,但会为每个对象分配额外内存空间。用字段还是方法,取决于类的用法。P183
scala> def func:Int= 7
func: Int

scala> func
res0: Int = 7

scala> func()
<console>:13: error: Int does not take parameters
       func()
           ^

scala> def func1():Int= 7
func1: ()Int

scala> func1
res2: Int = 7

scala> func1()
res3: Int = 7
  1. 类的创建
class 类名[(参数列表)]{构造函数主体}//注意,区别于函数,没有=

//例子
class A{val a=1}

  1. 继承
class A(a:Int,..) extends B {
	...
}
  1. 类的参数问题
//定义时带参数
class A(a:Int,b:Int)={
	//直接使用a,b
}
val c1=new A(12)
val c2= new A //报错

//通过辅助构造函数
class A={
	def this(a:Int,b:Int)={
	this()//必须先调用主构造函数
	//直接使用a,b
	}
}
val c1= new A(1,2)
val c2= new A //也存在 
  1. 子类的指可以被用在任何需要超类的值的地方。P186
val b:B_super=new B_subtype(1,2,3)//b的表现通过多态自动匹配
  1. scala命名空间有两个P187:
    1. 值(字段,方法,包和单例对象):所以可用字段重写无参方法。
    2. 类型(类和特质名)
class A{
	def a:Int
}

class B extends A{
	val a=2//将方法重写成字段
	//override val a =2 //若A中a非抽象
}
  1. 参数化字段,类的参数中带val和var 前缀的字段,是同时定义参数和同名字段的简写方式。参数化字段前还可以加修饰符,如overide,private。P188
// 使用参数化字段
class A(val a:Int){
	...
}
//等价于:
class A(e1:Int){
	val a=e1
	...
}
  1. 要调用超类的构造方法,秩序将你打算传入的入参放在超类名称后的括号里即可。P190
class A1(a:Int) extends A(a-1){
	...
}
//若超类没有入参,则不传参,也自动调用其构造方法?
  1. override的使用:只要超类成员不是抽象的,子类在重写该成员时都要加上override,否则会报错。P190
  2. 多态和动态绑定。
    1. 多态是指超类的变量可以指向子类的对象。这一类多态也叫子类型多态。
    2. 动态绑定:变量调用的方法是随运行时的状态来定的。具体来说:如果子类对象有定义,则调用子类的方法,如果无定义,则向上寻找父类的方法。P193
//A有实现func_a
val a1:A=new A1//A1有实现func_a1
val a2:A=new A2//A2有实现func_a2

a1.func_a1
a1.func_a2//报错
a1.func_a
  1. final:声明成员不被子类重写(还是可以被继承),或者类不能被继承。P195
    1. 和private的区别:private不能被继承,且类的实例无法访问。而final声明的成员,可被继承,且实例可以访问。
scala> class A{final val a=10}
scala> class A1 extends A{val a1=2}
scala> val c=new A1
c: A1 = A1@6fd1660
scala> c.a
res2: Int = 10
scala> c.a1
res3: Int = 2
  1. 组合 or 继承?P196
    1. 追求代码复用,选组合。
    2. a. is-a的关系,即A1是一个A。 b. 要把子类当作超类来用(使用多态能力) ,选继承。
  2. 工厂对象:可以将创建对象的逻辑集中起来,不用new 来创建对象,而是用方法将new 包起来。工厂对象一般用伴生对象实现。
object A {
 def elem(a:Int):A={
 	new A1(a)
 }
 def elem(a:Array[String]):A={
 	new A2(a)
 }
}

//使用:
import A.elem
val a1=elem(2)
val a2=elem(Array("a","b"))

第11章 Scala的继承关系

在这里插入图片描述

  1. 顶部的Any类定义了:==,!=,equals,##,hashCode,toString等方法P208,有两个子类AnyVal和AnyRef。P210
  2. AnyVal包括Byte,Short…Boolean,Unit这9个内建类,前八个的实例在scala中写作字面量,运行时用java基本类的值来表示。不能用new创建(类定义为final抽象类实现)。P210
  3. Unit只有一个实例值,写作()。P210
  4. 对象的相等性。P210
  5. 值类空间时扁平的(即相互之间并没有子类关系)。不同的值类类型之间存在隐式转换(21章继续讨论)。
  6. AnyRef是所有引用类的基类。P212 补充:值类型(AnyVal)和引用类型(AnyRef)的不同
    在这里插入图片描述
  7. 底部类:scala.Null和scala.Nothing。
    1. Null的实例是null,所有引用类的子类。不能赋值给值类。 val i:Int=null报错。P215
    2. Nothing是所有类的子类,包括值类和引用类。用来兜底。P215
  8. 自定义值类:P216
    1. 有且仅有一个参数
    2. 在内部除了def之外,没有任何其他东西。
    3. 不能重新定义equals或hashCode。
  9. 自定义值类可用来防止参数顺序错误,错了会报错。P218
class Anchor(val v:String) extends Anyval
class Style(val v:String) extends Anyval
class Text(val v:String) extends Anyval
class Html(val v:String) extends Anyval

第12章 特质

  1. 特质的特点:
    1. 默认超类为AnyRef。P221
    2. 可以用extends或with混入: I. extends混入,则类隐式集成特质的超类。P221 II. with混人,对特质超类有要求吗?有,被混入的特质的超类,必须是要混入特质的类的超类(可以是其往上几层的超类)。P231
    3. 总结:混入特质的类,特质的超类也必须是其超类(可以是其往上几层的超类)。
    4. 定义特质的同时也定义了一个类型,所以特质也可以当做类型来使用。P221
    5. 多个特质有相同方法时,会发生什么?会冲突报错,可通过在新类中重写冲突函数解决。
trait Trait1{
	def echo()={
		println("echo Trait1")
		}
}
class Frog extends Trait{
	...
}
val t:Trait1=new Frog //其中Frog混入了特质Trait1,且t可以由任何混入了特质Trait1的类的初始化


//超类要求:               
scala> class A{def echo()=println("echo A")}
scala> trait T1 extends A{override def echo()=println("echo T1")}
scala> class A1(val a1:Int) extends A
scala> class AA1(val a2:Int) extends A1(2)
scala> class AA1(val a2:Int) extends A1(2) with T1 //T1的超类是AA1往上两层的超类

scala> val aa1=new AA1(2)
scala> aa1.echo
echo T1

// 相同函数冲突,会报错
scala> trait T1 extends A{def echo1()=println("echo T1")}
scala> trait T2 extends A{def echo1()=println("echo T2")}
scala> class AT extends A with T1 with T2
<console>:14: error: class AT inherits conflicting members:
  method echo1 in trait T1 of type ()Unit  and
  method echo1 in trait T2 of type ()Unit
(Note: this can be resolved by declaring an override in class AT.)
       class AT extends A with T1 with T2
scala> class AT extends A with T1 with T2{override def echo1()=println("echo AT")} //在新类中重写冲突函数即可。
scala> val at=new AT
scala> at.echo
echo A
  1. 特质 vs 类:几乎一样,除了以下亮点:
    1. 特质不能有"类"参数(但有办法绕过)P223
    2. 类的super是静态绑定,而特质的super是动态绑定的。P223
class A(a:Int,b:Int)
trait T(a:Int,b:Int)//报错,20.5节有怎样绕过这个限制
  1. Ordered特质,实现compare即可让混入的类使用>,<,>=,<=,但不会定义equals方法。
class YourClass extends Ordered[YourClass]{
//...
def compare(that:YourClass)= this.num-that.num//该函数返回结果:负数表示this小于that,0表示相等,正数表示this大于that
}
  1. 特质可叠加修改:
    1. super调用是动态绑定的
    2. 可叠加修改的特质,必须将叠加修改的方法标记位abstract override(仅限特质使用该标记),含义是该特质必须混入某个拥有该方法具体定义的类中。P231
    3. 可在定义或者new 实例化时混入特质。P232
    4. 混入顺序很重要,最右边的方法先被调用,然后依次往左。P233
    5. 线性化:同级特质,按定义先后顺序决定线性化顺序。P237
abstract class A{
 def get()
 def put(x:Int)
}

trait Doubleing extends A {
	abstract override def put(x:Int)= {super.put(2*x)} 
}

trait Incrementing extends A {
	abstract override def put(x:Int)= {super.put(x+1)} 
}

trait Filtering extends A {
	abstract override def put(x:Int)= {if(x>=0) super.put(x)} 
}

class MyA extends A {
	val buf=new ArrayBuffer[Int]
	def get()=buf.remove(0)
	def put(x:Int)= {buf+=x}
}
val i= new MyA with Doubleling //创建时混入特质
i.put(10)
i.get()//20
val i1= new MyA with Incrementing with Filtering
i1.put(-1)
i1.get()//None
val i1= new MyA with Filtering with Incrementing 
i1.put(-1)
i1.get()//0
  1. 用不用特质?P238
    1. 如果某个行为不会被服用,用具体的类。
    2. 如果某个行为可能被多个不同的类服用,则用特质
    3. 如果要从java代码中继承某个行为,则用抽象类。
    4. 如果计划将某个行为以编译好的方式分发,且预期会有外部的组织编写继承它的类,则倾向于用抽象类。
    5. 如果考虑完后没答案,从使用特质开始。

第13 章 包和引入

  1. 将代码放入包里有两种方式。P240
    1. 文件顶部放一个package子句。
    2. package子句后加{}代码块。这种方法可方便地把多个包放在一个文件中。
    3. 所有包的顶层包都为__root__
    4. idea里面代码经过build之后,会在out文件夹下面按层级建立不同的文件夹(文件夹对应包)和.class文件
//pack1.scala
package level1.level2.packagename
class A

//pack2.scala 等价于pack1.scala
package level1.level2.packagename{
	class A
}

// pack3.scala 包含多个包
package p0{
	class P0A
	package p10{
		class P10A
		class P10B{
			// 同一个包内,可不带前缀访问包内其他成员。不需要前缀不用p0.p10.P10A
			val cb= new P10A
			val c2=new P0A //也可见
		}
		package p20 {
			class P20A{
				val c1=new P10A//不在同一个包,但包外作用域可访问的名称,内层可用同样精简方式访问,不用p10.P10A
				val c2=new P0A
				}
		}
	}
}
//包内的简洁使用规则,需要名称被{}打包在同一作用域内。
package p0.p11{
	class P11A{
		//报错,P0A不在作用域内
		val c=new P0A
	}
}

//支持减少一直引用使得包定义过于往右
//跟在pack3.scala后面或者放在新建的pack4.scala里都可以
package p0
package p11
class P11A{
  //注意,不报错,这是这种表达的一种作用
  val c=new P0A
}

//pack5.scala 包名相互遮挡,则需指明前缀,没前缀为包内的子包。顶层包用__root__指明
package launch{
	class A3
}
package p0{
	package p10{
		package launch{
			class A1
		}
		class Task{
			val a1=new launch.A1 //没前缀为包内的子包
			val a2=new p0.launch.A2 //需指名前缀
			val a3=new __root__.launch.A3 //所有包的顶层包都为__root__
		}	
	}
	package launch{
		class A2
	}
}
  1. 引入包,import:
    1. 可出现在任何位置,作用域由位置决定。P247
    2. 除包外,还可引用对象(包括单例和常规对象)
    3. 可重命名或隐藏某些被引入的成员
    4. 可引入的是包本身(即可以把包名当作成员用),而不止是其中的成员
//全量引入
import Fruit._
//部分引入
import Fruit.{Apple,Orange}
//重命名:
import Fruit.{Apple=>MyLove,Orange}//要使用Apple可写成:Fruit.Apple或MyLove
import Fruit.{Apple=>MyLove,_}//引入全部成员,并将Apple重命名
//隐藏(即不引入某类)
import Fruit.{Apple=>_,_} //不引入Apple

//引入相同名称成员,使用时不区分的话,会出错
class test3 {
  import p0.P0A
  import p0.p11.P0A
  val c=new P0A//无法编译,不知道是哪个P0A,需要加前缀指明
}
  1. 隐式引入,".scala"的源码文件都默认在顶部添加了如下三行引入:
    a. 这三个引入子句做了特殊处理,引入相同名称成员时,不会报错,而是后面的覆盖前面的。例如,scala.StringBuilder会覆盖java.lang.StringBuilder。P250
import java.lang._ //包含Thread等成员
import scala._ //包含List等成员
import Predef._ //包含assert等成员
  1. 包、类和对象的成员都可以标上访问修饰符“private”和“protected”。用“private”修饰的成员是私有的,只能被包含它的包、类或对象的内部代码访问。P253
  2. 用“protected”修饰的成员是受保护的,除了能被包含它的包、类或对象的内部代码访问,还能被子类访问(只有类才有子类)。P253
class Diet {
  private val time = "0:00"
  protected val food = "Nothing"
}
 
class Breakfast extends Diet {
  override val time = "8:00"  // error
  override val food = "Apple"  // OK
}
(new Diet).time //error
  1. 假设X指代某个包、类或对象,那么如果在C类中使用private[X]和protected[X]就是在不加限定词的基础上(即包括C类内部也可用),把访问权限扩大到X的内部。即对于C和X外都是private或protected。
package A {
  package B {
    private[A] class JustA
  }
 
  class MakeA {
    val a = new B.JustA  // OK
  }
}
 
package C {
  class Error {
    val a = new A.B.JustA  // error
  }
  1. X还能是自引用关键字“this”。private[this]比private更严格,不仅只能由内部代码访问,还必须是调用方法的对象或构造方法正在构造的对象来访问;protected[this]则在private[this]的基础上扩展到定义时的子类。作用:保证成员不被该类的其他对象看到,对于文档或者型变注解有意义(当前不许要了解)。P254
class MyInt1(x: Int) {
     private val mi1 = x
     def add(m: MyInt1) = mi1 + m.mi1 //OK
}
class MyInt2(x: Int) {
     private[this] val mi2 = x
     def add(m: MyInt2) = mi2 + m.mi2 //m.mi2非法,无法访问,第一个mi2也可形成this.mi2
}
  1. 伴生对象和伴生类共享访问权限,即两者可以互访对方的所有私有成员。在伴生对象里使用“protected”没有意义,因为伴生对象没有子类。特质使用“private”和“protected”修饰成员也没有意义。
  2. 包对象。P256
    1. 包里可直接包含的元素有类、特质和单例对象,但其实类内可定义的元素都能放在包级别。
    2. 每个包都可以有一个包对象,任何被放在包对象中的定义都会被当作这个包本身的成员。
    3. 包对象用关键字组合package object为开头来定义,其名称与关联的包名相同,有点类似伴生类与伴生对象的关系。
    4. 包对象会被编译成名为“package.class”(无论你定义包对象的脚本叫什么)的文件,该文件位于与它关联的包的对应文件夹里。为了保持路径同步,建议定义包对象的文件命名为“package.scala”,并和定义关联包的文件放在同一个目录下。
    5. 包对象不能定义package object p0.p10这样的多层包,所以包对象是对于package.scala所放位置决定的,放最顶层则对应的是__root__下的包而言的。放p10目录下,则对应的是p10下的包。且包对象和包应该有享有同样的成员使用权。(该结论待验证)
    6. 包对象的作用:用于包级别的类型别名(第20章)和隐式转换(第21章)
//package.scala 放在p0层
package object p0{
  def echo(c:P0A)={}//ok
}
package object p10{
  def echo(c:P0A)={}//错误,访问不到P0A的定义,除非加import
}
package object p10{
  def echo(c:P10A)={}//错误,访问不到P10A的定义,除非加import
}
//原因是p10理应为p0.p10,但包对象不允许这样的定义,如上相当于定义了__root__.p10的包对象

参考:
https://blog.csdn.net/qq_34291505/article/details/86777542

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值