Scala入门-继承


继承

Scala 只支持单继承,这点和 Java 一样。

object ExtendDemo extends App {

  val stu = new Stu
  stu.name = "cris"
  stu.age = 23
  stu.study() // cris is studying!!!
  stu.info() // Student(cris,23)
}

class Person {
  var name: String = ""
  var age: Int = 18

  def info(): Unit = {
    println(toString)
  }

  override def toString = s"Person($name, $age)"
}

class Stu extends Person {
  def study(): Unit = {
    println(this.name + " is studying!!!")
  }

  override def toString = s"Student($name,$age)"
}

注意:子类继承了父类所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问。

方法重写

Scala 中重写一个非抽象方法需要使用 override 修饰符。调用超类的方法使用 super 关键字。

类型检查和转换(多态)

classOf[String] 就如同 Java 的 String.class

obj.isInstanceOf[T] 就如同 Java 的 obj instanceof T 判断 obj 是不是 T 类型

obj.asInstanceOf[T] 就如同 Java 的 (T)obj 将 obj 强转成 T 类型

示例代码:

  def main(args: Array[String]): Unit = {

    println(classOf[String]) // class java.lang.String

    // 使用反射实现
    val string = "cris"
    println(string.getClass.getName) // java.lang.String

    // 类型判断
    println(string.isInstanceOf[String]) // true

    // 类型转换(向上转型)
    val any: AnyRef = string
    // 类型转换(向下转型)
    println(any.asInstanceOf[String].charAt(0)) // c
  }
}

类型转换最佳示例:

object TypeConverse {
  def main(args: Array[String]): Unit = {
    val dog = new Dog02
    val fish = new Fish02
    func(dog) // dog is eating bone
    func(fish) // fish is swimming
  }

  def func(p: Pet02): Unit = {
    if (p.isInstanceOf[Dog02]) p.asInstanceOf[Dog02].eatBone()
    else if (p.isInstanceOf[Fish02]) p.asInstanceOf[Fish02].swimming()
    else println("类型错误!")
  }

}

class Pet02 {

}

class Dog02 extends Pet02 {
  var name = "dog"

  def eatBone(): Unit = {
    println(s"$name is eating bone")
  }
}

class Fish02 extends Pet02 {
  var name = "fish"

  def swimming(): Unit = {
    println(s"$name is swimming")
  }
}

超类构造

在 Java 中,创建子类对象时,子类的构造器总是去调用一个父类的构造器(显式或者隐式调用)。

下面看看 scala 中的超类构造。

object SuperDemo {
  def main(args: Array[String]): Unit = {
    var b = new B("cris")
  }
}

class A {

  var name = "A"
  println(s"A's name is $name")

}

class B extends A {

  println(s"B's name is $name")

  def this(name: String) {
    this()
    this.name = name
    println(s"finally, B's name is $name")
  }
}

执行结果如下:

总结一下执行顺序:

  1. 调用 B 的辅助构造函数时,先要调用 B 的主构造器。
  2. 调用 B 的主构造器之前,调用父类 A 的主构造器。
  3. 最后才是调用 B 的辅助构造。

注意点:在 Scala 的构造器中,不能使用 super 来调用父类的构造器。

注意点:在 Scala 中只有主构造器才可以直接调用父类的构造器(主构造器和辅助构造器),子类的辅助够造器无法直接调用父类的构造器。

object SuperDemo2 {
  def main(args: Array[String]): Unit = {
    val worker = new Worker("cris")
    worker.info()
  }
}

class People(pName: String) {
  var name: String = this.pName

  def info(): Unit = println(s"name = $name")
}


// 在子类的主构造器中直接调用父类的主构造器
// 通过将子类主构造器中的参数直接传递给父构造器
class Worker(name: String) extends People(name) {
  var age = 20

  override def info(): Unit = {
    super.info()
    println(s"age = $age")
  }

}

输出结果

name = cris
age = 20

属性重写

回想:Java 中父类的属性可以被重写吗?

public class Demo {
    public static void main(String[] args) {
        Sub s = new Sub();
        // james
        System.out.println(s.name);

        Super s2 = new Sub();
        // cris
        System.out.println(s2.name);

    }
}

class Super {
    String name = "cris";
}

class Sub extends Super {
    String name = "james";
}

答案是:不会!Java 给出的解释是:隐藏字段代替了重写。Java 中成员变量不能像方法一样被重写。当一个子类定义了一个跟父类相同名字的字段,子类就是定义了一个新的字段。这个字段在父类中被隐藏的,是不可重写的。

如果想要访问父类的隐藏字段:

  1. 采用父类的引用类型,这样隐藏的字段就能被访问了,像上面所给出的例子一样。
  2. 将子类强制类型转化为父类类型,也能访问到隐藏的字段。

再回顾下 Java 动态绑定:

public class Demo {
    public static void main(String[] args) {
        Super s = new Sub();
        System.out.println("s.getI() = " + s.getI());
        System.out.println("s.sum() = " + s.sum());
        System.out.println("s.sum1() = " + s.sum1());
    }
}

class Super {
    public int i = 10;

    public int sum() {
        return getI() + 10;
    }

    public int sum1() {
        return i + 10;
    }

    public int getI() {
        return i;
    }
}

class Sub extends Super {
    public int i = 20;

    @Override
    public int sum() {
        return i + 20;
    }

    @Override
    public int getI() {
        return i;
    }

    @Override
    public int sum1() {
        return i + 10;
    }
}

结果如下

如果我们将子类的 getI() 和 sum1() 方法注释掉,再执行,结果如下:

总结 Java 的动态绑定机制:

  • 当调用对象方法的时候,该方法会和该对象的内存地址绑定。
  • 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用。

Scala 的属性重写

object OverrideDemo {
  def main(args: Array[String]): Unit = {
    val a: AA = new BB
    val b: BB = new BB
    println(a.i)    // 实质调用的是 BB 的 i()方法
    println(b.i)    // 实质调用的是 BB 的 i()方法
  }

}

class AA {
  // AA 编译后的文件会生成一个 i() 方法用于读取该属性
  val i = 10
}

class BB extends AA {
  // BB 编译后的文件会重写 AA 中的 i() 方法
  override val i = 20
}

输出:

20
20

看看编译后的源代码:

注意点:val 属性只能重写另一个 val 属性或重写不带参数的同名方法。

object OverrideDemo {
  def main(args: Array[String]): Unit = {
    val a: AA = new BB
    val b: BB = new BB


    println(a.func())    // 实质都是调用的 BB 中的 func()
    println(b.func())    // 实质都是调用的 BB 中的 func()
  }

}

class AA {
  // AA 编译后的文件会生成一个 i() 方法用于读取该属性
  val i = 10

  def func(): Int = i
}

class BB extends AA {
  // BB 编译后的文件会重写 AA 中的 i() 方法
  override val i = 20
  // 看着很奇怪,从格式上像是定义了一个属性的重写,其实编译后的文件会生成 func() 方法,重写了 AA 中的 func() 方法。
  override val func: Int = i
}

输出:

20
20

查看编译后的字节码

小结:从编译后的字节码可以看出,val 修饰的属性会自动生成一个对应的 get 方法。重写该属性其实是重写对应的 get 方法。

注意点:var 只能重写另一个抽象的 var 属性。

先看看什么是抽象属性:未初始化的变量就是抽象的属性,抽象属性需要定义在抽象类中。

然后再看看编译后的字节码:

本质上是在子类中实现了父类中的抽象方法。因此 BBB 中的 override 关键字也可以省略。

重写抽象的 var 属性小结:

  • 一个 var 属性没有初始化,那么这个 var 属性就是抽象属性。
  • 抽象的 var 属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类。
  • 如果是重写一个父类的抽象 var 属性,那么 override 关键字可省略。

抽象类

在 Scala 中,通过 abstract 关键字标记不能被实例化的类。方法不用标记 abstract,只要省掉方法体即可。抽象类可以拥有抽象字段,抽象字段就是没有初始值的字段。

小结:

  • 抽象类不能被实例化。
  • 抽象类不一定要包含 abstract 方法。
  • 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为 abstract。
  • 抽象方法不能有主体,不允许使用 abstract 修饰。
  • 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为 abstract 类。
  • 抽象方法和抽象属性不能使用 private、final 来修饰,因为这些关键字都是和重写/实现相违背的。
  • 子类重写抽象方法不需要 override ,写上也不会错。

匿名子类

回顾 Java 匿名子类

public class Demo {
    public static void main(String[] args) {
        Man man = new Man() {
            @Override
            void work() {
                System.out.println("厨师炒菜挣钱");
            }
        };
//        厨师炒菜挣钱
        man.work();
    }
}

abstract class Man {
    /**
     * 挣钱的方法
     */
    abstract void work();
}

Scala 匿名子类:

object SubDemo2 {
  def main(args: Array[String]): Unit = {
    val monkey = new Monkey {
      override var name: String = "金丝猴"

      override def eat(): Unit = {
        println("吃桃子")
      }
    }
    monkey.eat()
    println(monkey.name)
  }
}

abstract class Monkey {
  var name: String
  def eat()
}

文章作者: Tianny
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Tianny !
评论
 上一篇
UML 图示 UML 图示
UML 是分析程序和理解程序间的调用关系时,不可多得的利器。 这里简单介绍几种程序开发中经常使用的 UML 视图,并辅以真实的 Java 案例。 类图UML 中的类图表示用于表示类、接口、实例之间的静态关系。 类的层次关系展示类的层次关系,
2020-03-26
下一篇 
Scala入门-封装 Scala入门-封装
提供 get 方法 def setXxx(参数名 : 类型) : Unit = { //加入数据验证的业务逻辑 属性 = 参数名 } 提供 set 方法 def getXxx() [: 返回类型]
2020-03-20
  目录