本文我们聊聊Scala类的单继承/多继承、private/public关键字问题。
1、Scala的类是单继承的,但特质可以多继承
1.1 首先,来看下多继承、单继承是什么意思,区别是什么?
多继承:A类、B类是两个没有继承关系的类,C类即继承了A类又继承了B类。
print('---------------------多继承------------------------')
class A:
pass
class B:
pass
class C(A, B):
pass
单继承:C类继承了B类,B类继承了A类,C、B、A是层次继承的关系。
print('---------------------单继承------------------------')
class Animal:
pass
class Animal1(Animal):
pass
1.2 多继承相比于单继承有什么问题?为什么Scala采用单继承,而不是多继承?
举一个多重继承的例子。蝙蝠即有鸟类的一些特征,又有哺乳动物类的一些特征。鸟类和哺乳类都有“繁殖”这个方法。
如果蝙蝠采用的是多重继承的方法,继承了鸟类和哺乳类,那么当我们调用“繁殖”这个方法的时候,冲突就出现了。它不知道该是用鸟类的“繁殖”方法,还是哺乳类的“繁殖”方法。
这就是多重继承的冲突问题。
我们知道Scala基于Java的开发语言,很多设计都来源于JAVA。在JAVA中,为了解决多重继承的冲突问题,给出的解决方法是:一个物体的本质只能有一个。这就是单继承背后的核心思想。
一个子类只能继承一个父类,但可以继承多个接口 或特质。
我们上面蝙蝠的例子就是它只能是鸟类或哺乳类中的一种,但你可以创造一个接口来满足这个父类中没有的方法。或者在蝙蝠这个类中新增父类中没有的方法。当然你也可以对父类中已有的方法进行重写override,比如可飞行但没有羽毛。通过这样的方式来得到即有鸟类,又有哺乳类特征的蝙蝠这个类。
所以,单继承即实现了需要的类,又能避免多重继承的冲突问题,这就是Scala采用单继承而不是多重继承的原因。
1.3 抽象类abstract class 和 特质trait(或接口interface)的区别,及其在类继承中的应用
以下4条基线可以参考:
- 如果运行效率是你关注的一个指标,那么建议使用抽象类。 因为Traits 被编译为接口,可能会有轻微的性能开销。
- 如果你需要继承一些JAVA代码,那么最好使用抽象类,因为特征没有 Java 模拟(analog),从特征继承就会很奇怪。
- 如果行为不可重用,建议使用具体类。
- 如果行为在多个不相关的类中重用,则可以使用特征。
1.4 特质trait的多继承顺序问题 (注:查看我的博文 10、Scala特质trait)
准则:
- 如果有超类,则先调用超类的函数。
- 如果混入的trait有父trait,它会按照继承层次先调用父trait的构造函数。
- 如果有多个父trait,则按顺序从左到右执行。
- 所有父类构造函数和父trait被构造完之后,才会构造本类的构造函数。
2、Scala类中的关键字private
2.1 关键字private / public 代表什么意思?不写的时候默认是指哪个?
类中成员默认是公有(public
)的。使用private
修饰则定义的是私有成员(属性、方法),这样它们在类外部就不能被调用,即只能在类内部调用,外部调用会报错。
另外,类的主构造方法中带有val
和var
的参数是公有的。
class Point(val x: Int, val y: Int)
类的主构造方法中不带val
或var
的参数是私有的,仅在类中可见。
class Point(x: Int, y: Int)
val point = new Point(1, 2)
point.x // <-- does not compile
关键字的代码示例如下:
(1)私有属性和方法不能在外部被调用,但可以通过内部的方法调用。
(2)类中公有的、可变的属性可以通过外部调用,可以被修改。
(3)类中公有的、不可变的属性可以外部调用,但不能修改
注意: 如果代码里的class名称改成Test,使之变成对象Test的伴生类,那么还是可以调用类的私有变量和私有方法的,因为定义里类和它的伴生对象可以互相访问其私有成员。
一般使用伴生对象来定义那些在伴生类中不依赖于实例化对象而存在的成员变量或者方法。
2.3 什么时候使用关键字?
取决于你是否希望这个属性或方法在类的外部被调用。
如果我们写的类不是提供给其他人用的接口这种用途,是可以不用私有变量的。但如果是作为接口提供或被多处引用,则就要考虑哪些是内部私有属性/方法,只允许内部访问,不允许外部访问;哪些是外部公有的属性/方法,允许外部调用和方法。
同时也要思考,哪些公有属性是可变的,允许外部重新赋值修改,哪些公有属性是不可变的,只允许外部访问,但不能修改。
3、Scala类中的getter/setter方法
scala为每个字段生成getter与setter方法。这样属性或方法可以被外部调用、或者被重新赋值/重写等。
- 如果字段是私有的,那么setter与getter也是私有的
- 如果字段是val,那么只有getter方法被生成
- 如果不需要任何getter与setter,那么将字段声明为private
- 如果需要只读属性,那么声明为val,Scala会生成一个私有的final字段和一个getter方法,但是没有setter。