五 、面向对象高级
把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。
在Scala中创建对象的几种方式
-
new 对象
-
applay 方法,创建对象
-
动态混入
-
匿名子类创建对象
封装
封装(encapsulation)就是把抽象出的数据/属性和对数据的操作/方法封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作
继承
继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(比如Student),在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends语句来声明继承父类即可。
Scala继承给编程带来的便利
- 代码的复用性提高了
- 码的扩展性和维护性提高了【面试官问:当我们修改父类时,对应的子类就会继承相应的方法和属性】
重写:
-
override 在父类新方法跟子类冲突时能提供警告
-
Java中只有方法的重写,没有属性/字段的重写,准确的讲,是隐藏字段代替了重写
-
重写字段的三个注意点
- def只能重写另一个def(即:方法只能重写另一个方法)
- val只能重写另一个val 属性 或 重写不带参数的def
- var只能重写另一个抽象的var属性
-
字段重写的本质是variable variable.__方法的重写,适用Java动态绑定机制,如果要访问覆写的父类字段,需要在父类自定义方法。
① override 不能重写父类@BeanProperty **var **的属性(var只能重写另一个抽象的var属性)但可以重写父类@BeanProperty **val **的属性,所以子类重写父类的属性后:
要想父类属性既能读取又能改写,必须手动定义 类似gettersetter方法,
只想父类属性可以读不能改写 ,用 override@BeanProperty val variable
②重写字段导致方法重写的情况(本质还是方法重写方法)
class A { def sal(): Int = { //底层是 private final int sal = 1; public int sal() { return 10} return 10 }} class B extends A { override val sal : Int = 1 // 底层也是public int sal() { return this.sal} } def main(args: Array[String]): Unit = { val b = new B println(b.sal) // 调用的是sal() 得到1 val a=new A println(a.sal)// 调用的也是方法sal()得到10 if(b.isInstanceOf[A]){ val c=b.asInstanceOf[A] println(c.sal)} //多态使用bsal()得到1 }
动态绑定机制
-
当调用对象方法的时候,该方法会和该对象的内存地址绑定
-
当对象调用属性时,没有动态绑定机制,在哪里调用,就返回哪里的值
class A { //A是父类 A a = new B();10; public int sum() { return getI() + 10; //子类继承且未重写sum(),因为i的getter方法子类也有,所有绑定子类的getter方法 返回子类i值 } public int sum1() { return i + 10; //此处i是属性 在父类方法里调用 返回父类i的值 }
抽象属性、类
抽象属性:没有初始化的属性,只能存在于抽象类中。由于底层是两个抽象的variable variable.__方法,所以子类val 或var实现的时候可以不写override。
继承过程
- 子类继承了所有的属性(通过方法),只是私有的属性不能直接访问,需要通过公共的方法去访问。
- 继承的关键看权限。与java一样,子类可以继承父类的所有,但不一定都能访问。看修饰方法是否public,scala中protect 同包下及子类可以访问,scala中protect修饰的内容无论如何只能自己或子类访问,虽然底层方法是public,但编译器会禁止在其他地方使用。
- 与java不同,在Scala的构造器中,子类无法调用super(params) ,只能用空参的,只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。
类型检查和转换
要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。
if(p.isInstanceOf[Employee]) //如果p是Employee类或Employee的子类
vla s =p.asinstanceOf[Employee] //
if(p.getClass==classof[Employee]
多态
超类与java区别
- 类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用)
这个主要涉及到java里面一个字段隐藏的概念,父类和子类定义了一个同名的字段,不会报错。但对于同一个对象,用父类的引用去取值(字段),会取到父类的字段的值,用子类的引用去取值(字段),则取到子类字段的值。在实际的开发中,要尽量避免子类和父类使用相同的字段名,否则很容易引入一些不容易发现的bug。
类型投影
抽象类
- 抽象类不能被实例
- 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
- 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract
- 抽象方法不能有主体,不允许使用abstract修饰。
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为abstract类。
- 抽象方法和抽象属性不能使用private、final 来修饰,因为这些关键字都是和重写/实现相违背的。
- 抽象类中可以有实现的方法.
- 子类重写抽象方法不需要override,写上也不会错
- 抽象类本身不能直接实例化但可以动态混入
伴生对象
- 可用伴生对象创建匿名对象
- Spark中定义用来设置常量类