第10章 组合和继承
- 组合是指一个类包含对另一个类的引用,并头通过引用类来帮助完成任务。继承只超类/子类的关系。P179
- 一个方法只要没有实现(没有等号和方法体),它就是抽象的。P181
- 包含抽象成员的类,要声明为抽象类。
abstract class 类名
P181 - 抽象类不能被实例化。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
- 无参方法、空括号方法、字段:
- 无参方法:调用时只能用无参形式,建议无副作用的方法定义成无参方法。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
- 类的创建
class 类名[(参数列表)]{构造函数主体}//注意,区别于函数,没有=
//例子
class A{val a=1}
- 继承
class A(a:Int,..) extends B {
...
}
- 类的参数问题
//定义时带参数
class A(a:Int,b:Int)={
//直接使用a,b
}
val c1=new A(1,2)
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 //也存在
- 子类的指可以被用在任何需要超类的值的地方。P186
val b:B_super=new B_subtype(1,2,3)//b的表现通过多态自动匹配
- scala命名空间有两个P187:
- 值(字段,方法,包和单例对象):所以可用字段重写无参方法。
- 类型(类和特质名)
class A{
def a:Int
}
class B extends A{
val a=2//将方法重写成字段
//override val a =2 //若A中a非抽象
}
- 参数化字段,类的参数中带val和var 前缀的字段,是同时定义参数和同名字段的简写方式。参数化字段前还可以加修饰符,如overide,private。P188
// 使用参数化字段
class A(val a:Int){
...
}
//等价于:
class A(e1:Int){
val a=e1
...
}
- 要调用超类的构造方法,秩序将你打算传入的入参放在超类名称后的括号里即可。P190
class A1(a:Int) extends A(a-1){
...
}
//若超类没有入参,则不传参,也自动调用其构造方法?
- override的使用:只要超类成员不是抽象的,子类在重写该成员时都要加上override,否则会报错。P190
- 多态和动态绑定。
- 多态是指超类的变量可以指向子类的对象。这一类多态也叫子类型多态。
- 动态绑定:变量调用的方法是随运行时的状态来定的。具体来说:如果子类对象有定义,则调用子类的方法,如果无定义,则向上寻找父类的方法。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
- final:声明成员不被子类重写(还是可以被继承),或者类不能被继承。P195
- 和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
- 组合 or 继承?P196
- 追求代码复用,选组合。
- a. is-a的关系,即A1是一个A。 b. 要把子类当作超类来用(使用多态能力) ,选继承。
- 工厂对象:可以将创建对象的逻辑集中起来,不用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的继承关系
- 顶部的Any类定义了:
==,!=,equals,##,hashCode,toString
等方法P208,有两个子类AnyVal和AnyRef。P210 - AnyVal包括Byte,Short…Boolean,Unit这9个内建类,前八个的实例在scala中写作字面量,运行时用java基本类的值来表示。不能用new创建(类定义为final抽象类实现)。P210
- Unit只有一个实例值,写作
()
。P210 - 对象的相等性。P210
- 值类空间时扁平的(即相互之间并没有子类关系)。不同的值类类型之间存在隐式转换(21章继续讨论)。
- AnyRef是所有引用类的基类。P212 补充:值类型(AnyVal)和引用类型(AnyRef)的不同。
- 底部类:scala.Null和scala.Nothing。
- Null的实例是
null
,所有引用类的子类。不能赋值给值类。val i:Int=null
报错。P215 - Nothing是所有类的子类,包括值类和引用类。用来兜底。P215
- Null的实例是
- 自定义值类:P216
- 有且仅有一个参数
- 在内部除了def之外,没有任何其他东西。
- 不能重新定义equals或hashCode。
- 自定义值类可用来防止参数顺序错误,错了会报错。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章 特质
- 特质的特点:
- 默认超类为AnyRef。P221
- 可以用extends或with混入: I. extends混入,则类隐式集成特质的超类。P221 II. with混人,对特质超类有要求吗?有,被混入的特质的超类,必须是要混入特质的类的超类(可以是其往上几层的超类)。P231
- 总结:混入特质的类,特质的超类也必须是其超类(可以是其往上几层的超类)。
- 定义特质的同时也定义了一个类型,所以特质也可以当做类型来使用。P221
- 多个特质有相同方法时,会发生什么?会冲突报错,可通过在新类中重写冲突函数解决。
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
- 特质 vs 类:几乎一样,除了以下亮点:
- 特质不能有"类"参数(但有办法绕过)P223
- 类的super是静态绑定,而特质的super是动态绑定的。P223
class A(a:Int,b:Int)
trait T(a:Int,b:Int)//报错,20.5节有怎样绕过这个限制
- Ordered特质,实现compare即可让混入的类使用
>,<,>=,<=
,但不会定义equals方法。
class YourClass extends Ordered[YourClass]{
//...
def compare(that:YourClass)= this.num-that.num//该函数返回结果:负数表示this小于that,0表示相等,正数表示this大于that
}
- 特质可叠加修改:
- super调用是动态绑定的
- 可叠加修改的特质,必须将叠加修改的方法标记位abstract override(仅限特质使用该标记),含义是该特质必须混入某个拥有该方法具体定义的类中。P231
- 可在定义或者new 实例化时混入特质。P232
- 混入顺序很重要,最右边的方法先被调用,然后依次往左。P233
- 线性化:同级特质,按定义先后顺序决定线性化顺序。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
- 用不用特质?P238
- 如果某个行为不会被服用,用具体的类。
- 如果某个行为可能被多个不同的类服用,则用特质
- 如果要从java代码中继承某个行为,则用抽象类。
- 如果计划将某个行为以编译好的方式分发,且预期会有外部的组织编写继承它的类,则倾向于用抽象类。
- 如果考虑完后没答案,从使用特质开始。
第13 章 包和引入
- 将代码放入包里有两种方式。P240
- 文件顶部放一个package子句。
- package子句后加
{}
代码块。这种方法可方便地把多个包放在一个文件中。 - 所有包的顶层包都为
__root__
- 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
}
}
- 引入包,import:
- 可出现在任何位置,作用域由位置决定。P247
- 除包外,还可引用对象(包括单例和常规对象)
- 可重命名或隐藏某些被引入的成员
- 可引入的是包本身(即可以把包名当作成员用),而不止是其中的成员
//全量引入
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,需要加前缀指明
}
- 隐式引入,".scala"的源码文件都默认在顶部添加了如下三行引入:
a. 这三个引入子句做了特殊处理,引入相同名称成员时,不会报错,而是后面的覆盖前面的。例如,scala.StringBuilder会覆盖java.lang.StringBuilder。P250
import java.lang._ //包含Thread等成员
import scala._ //包含List等成员
import Predef._ //包含assert等成员
- 包、类和对象的成员都可以标上访问修饰符“private”和“protected”。用“private”修饰的成员是私有的,只能被包含它的包、类或对象的内部代码访问。P253
- 用“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
- 假设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
}
- 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
}
- 伴生对象和伴生类共享访问权限,即两者可以互访对方的所有私有成员。在伴生对象里使用“protected”没有意义,因为伴生对象没有子类。特质使用“private”和“protected”修饰成员也没有意义。
- 包对象。P256
- 包里可直接包含的元素有类、特质和单例对象,但其实类内可定义的元素都能放在包级别。
- 每个包都可以有一个包对象,任何被放在包对象中的定义都会被当作这个包本身的成员。
- 包对象用关键字组合
package object
为开头来定义,其名称与关联的包名相同,有点类似伴生类与伴生对象的关系。 - 包对象会被编译成名为“package.class”(无论你定义包对象的脚本叫什么)的文件,该文件位于与它关联的包的对应文件夹里。为了保持路径同步,建议定义包对象的文件命名为“package.scala”,并和定义关联包的文件放在同一个目录下。
- 包对象不能定义
package object p0.p10
这样的多层包,所以包对象是对于package.scala所放位置决定的,放最顶层则对应的是__root__
下的包而言的。放p10目录下,则对应的是p10
下的包。且包对象和包应该有享有同样的成员使用权。(该结论待验证) - 包对象的作用:用于包级别的类型别名(第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