返回介绍

数学基础

统计学习

深度学习

工具

Scala

四、继承

发布于 2023-07-17 23:38:23 字数 14114 浏览 0 评论 0 收藏 0

  1. 类的组合:一个类可以包含对另一个类的引用,利用这个被引用类来帮助它完成任务。组合代表了两个类的关系是:has-a

    类的继承:超类与子类的关系。继承代表了两个类的关系是:is-a

  2. 组合和继承是两种用已有的类来定义新类的两种方式。如果追求代码复用,通常优先选择组合而不是继承。因为继承会遇到脆弱基类问题:在修改超类时会不小心破坏子类的代码。

4.1 继承

4.1.1 抽象类

  1. 通过修饰符abstract 可以表明类是抽象的,这种类称作抽象类。不可以直接实例化一个抽象类。

    
    
    xxxxxxxxxx
    abstract class Element { def contents: Array[String] }

    其中contents 方法是一个没有实现的方法,这种方法称作抽象成员。一个包含抽象成员的类必须声明为抽象类。这与Java 不同,Java 中抽象成员也需要添加修饰符abstract

4.1.2 继承

  1. Scala 通过关键字extends 来声明类的继承。

    
    
    xxxxxxxxxx
    class ArrayElement(conts:Array[String]) extends Element{ def contents: Array[String] = conts }

    extends 有两个作用:

    • 子类从父类中继承所有的非私有成员。
    • 子类成为父类的子类型subtype 。子类型的意思是:任何需要超类对象的地方,都可以用子类对象来替代。

    这里ArrayElementElement 的子类,ElementArrayElement 的超类。

  2. 如果没有extends 语句,则Scala 的类默认继承自scala.AnyRef,这对应于Javajava.lang.Object 类。

  3. 类的继承:子类从父类中继承所有成员,除了以下两个例外:

    • 子类并不会继承父类的私有成员。

    • 如果子类已经实现了父类中相同名称和参数的成员,则该成员不会被继承。这种情况称作:子类的成员重写override 了父类的成员。

      更进一步的,如果子类的成员是具体的,而父类的成员是抽象的,则称子类的具体的成员实现implement 了那个抽象的成员。

4.1.3 父类构造函数

  1. 如果需要调用超类的构造方法,则只需要将你打算传入的入参放在超类名称后的圆括号里即可。

    
    
    xxxxxxxxxx
    class Child(name: String) extends Parent(name){ // 类的定义 }

4.2 重载

  1. Scala 支持方法重载:同一个方法名可以用于多个方法,这些方法的参数列表不同。在方法调用时,Scala 根据入参类型来选取合适的重载版本。与Java 类似,Scala 根据最匹配的入参类型来选择。

    如果找不到合适的重载版本,则编译器提示ambiguous reference 错误。

    
    
    xxxxxxxxxx
    class C(n:Int,s:String) { private val num:Int = n private val name:String = s def f(num:Int) = this.num <= num // 版本一 def f(name:String) = this.name <= name // 版本二 }
  2. 类默认继承了java.lang.Object 类的toString 实现,该实现只是简单的打印出类名、一个@ 符、一个十六进制数字。

    通常会重写类的toString 方法来获取更有价值的信息,从而方便调试。重写方法通过关键字override 实现:

    
    
    xxxxxxxxxx
    class C(n:Int,s:String) { override def toString = n + ";" + s // 精简了花括号、return、空参数、返回类型 }

    override 表示父类的该方法被重写覆盖了。

  3. Scala 并没有操作符重载,因为它并没有传统意义上的操作符。类似+,-,*,/ 这样的字符可以被作为方法名。因此1+2 这种表达式实际上调用了Int 对象1 上的、一个叫做+ 的方法,2 是该方法的参数。

    你也可以显式写作:1.+(2)

  4. Scala 中,字段和方法属于同一个命名空间,这使得字段重写无参方法成为可能。

    • 可以通过val 字段重写父类同名的 val 字段或者无参方法。
    • 也可以通过无参方法重写父类同名的 val 字段或者无参方法。
  5. Scala 要求在所有重写了父类具体成员的成员之前加上 override 修饰符。

    • 如果重写的是父类的抽象成员,则可以加、也可以不加上 override
  • 如果没有重写父类成员,但是添加了 override,则编译报错。

    • 如果应该添加而未添加 override,则编译器也报错。
  1. 如果希望某个成员(方法或者字段)不能够被子类重载,则可以在成员之前添加 final 修饰符。

    如果子类 override 了父类的一个 final 成员,则编译报错。

    
    
    xxxxxxxxxx
    class C // 父类 { final def echo = println("This is C") // 不需要 echo 被子类重载 } ​ class Child extends C // 子类 { override def echo = println("This is Child") // 编译报错 }
  2. 如果希望整个类没有子类,则可以简单的将类声明为 final的,做法是在类声明之前添加 final 修饰符。

    
    
    xxxxxxxxxx
    final class C // 不希望 C 拥有子类 { def echo = println("This is C") }

4.3 多态和动态绑定

  1. 一个类型为C 的变量可以指向任何一个C 的子类型的对象,这种现象称作多态polymorphism

  2. 对变量和表达式的方法调用是动态绑定的:实际被调用的方法实现是在运行时基于对象的类型来决定的,而不是变量或者表达式的类型决定的。

    
    
    xxxxxxxxxx
    class C // 父类 { def echo = println("This is C") } ​ class Child extends C // 子类 { override def echo = println("This is Child") } ​ def f1():C = new C // 函数 f1 返回类型为 C def f2():C = new Child // 函数 f2 返回类型为 C ​ val c1 = f1() // c1 类型 C val c2 = f2() // c2 类型 C c1.echo // 输出:This is C c1.echo // 输出:This is Child

    例子中,虽然函数 f2 返回的静态类型是C,但是其动态类型是C 的子类 Child。其echo 方法调用的是子类中的echo 方法。

4.4 继承体系

  1. Scala 中,所有类都继承自同一个名为Any 的超类。由于任何类都是Any 的子类,因此Any 中定义的方法是“全类型”的:它们可以在任何对象上被调用。

  2. Scala 还在继承关系的底部定义了Null 类和Nothing 类,它们本质上是作为通用子类存在的。

    Nothing 类是每个其它类的子类。

4.4.1 Any 类

  1. Any 类是所有继承关系的顶部,它定义了如下方法:

    • 相等和不等比较:

      
      
      xxxxxxxxxx
      final def ==(that:Any) : Boolean final def !=(that:Any) : Boolean def equals(that:Any) : Boolean
      • ==!=final 的,因此不能被子类重写。
      • == 方法本质上等同于 equals,而 != 一定是 equals 的反义。因此可以通过重写 equals 方法来定制 ==!= 的含义。
      • Scala 的相等性== 被设计为:对类型的实际表现是透明的:对于值类型而言,它表示的是自然的数值相等性;对于引用类型而言,它表示的是从 Object 继承的 equals 方法的别名。
    • 哈希函数:

      
      
      xxxxxxxxxx
      def ## : Int def hasCode : Int
    • 格式化成字符串:

      
      
      xxxxxxxxxx
      def toString : String
  2. Scala 中,如果希望使用引用相等性而不是内容上的相等性,则使用eq 方法。AnyRef 类定义了eq,ne 方法来给出引用相等性和引用不等性。

  3. Any 类有两个子类:AnyValAnyRefAnyVal 是所有值类型的父类;AnyRef 是所有引用类型的父类。

    Java 平台上,AnyRef 事实上只是java.lang.Object 的一个别名,因此Java 编写的类和Scala 编写的类都集成自AnyRef 。虽然可以在面向Java 平台的Scala 程序中任意切换ObjectAnyRef,但是推荐采用AnyRef

4.4.2 AnyVal 类

  1. 可以通过继承AnyVal 来定义自己的值类型。

  2. Scala 提供了九个内建的值类型:Byte,Short,Char,Int,Long,FLoat,Double,Boolean,Unit

    • 其中前八个对应Java 的基本类型,它们的 runtime 是采用Java 基本类型来表示的。

      这些类的实例在Scala 中统统写作字面量,不能用 new 来创建这些类的实例。这是通过将这些值类型定义为 abstract 同时也是 final 这个技巧来完成的。

    • 最后一个Unit 粗略的对应到Javavoid 类型,它有且只有一个实例值,写作()

  3. 值类型以方法的形式支持通常的算术和布尔运算操作符,同时它们还支持Any 类的所有方法。

  4. 值类空间是扁平的:所有的值类型都是scala.AnyVal 的子类,但是它们之间并没有子类关系。

    而不同值类型之间存在隐式类型转换。

  5. 可以对Int 类型调用 min,max,until,to,abs 等方法。原理是:存在从Int 类到 RichInt 类的隐式转换。

    只要对Int 调用的方法没有在Int 类中定义,而RichInt类刚好定义了该方法,则隐式转换就自动应用。

    其它的值类型也有类似的辅助类和隐式转换。

4.4.3 底类型

  1. Null 类是 null 引用的类型,它是每个引用类的子类。

    Null 类并不兼容值类型,如:你无法将null 赋值给一个整数变量。

  2. Nothing 位于 scala 类继承关系的底部,它是任何其它类型的子类。

    实际上并不存在这个Nothing 类型的任何值,其用途之一是给出非正常终止信号。

    如:

    
    
    xxxxxxxxxx
    def error(message: String) : Nothing = throw new RuntimeException(message) // Nothing 表明该方法并不会正常返回 ​ def f(x:Int,y:Int): Int = if (y!=0) x/y else error("can't divide by 0")

    由于error 分支的类型为 Nothing,兼容于Int,因此能够成功编译。

4.4.4 自定义值类型

  1. 可以自定义值类型来对内建的值类型进行扩充。和内建的值类型一样,自定义的值类型通常也会编译成那种不使用包装类的Java 字节码。

    而在需要包装类的环境中(如泛型),值类型将被自动装箱和拆箱。

  2. 要使得某个类成为值类,该类必须满足以下条件:

    • 该类继承自AnyVal
    • 该类必须有且仅有一个参数,且在该唯一参数之前加上val(参数化字段)。这可以让该参数作为字段被外界访问。
    • 该类的内部除了def 之外不能有任何其它东西。
    • 该类不能有子类。
    • 该类不能重写equalshashCode
    
    
    xxxxxxxxxx
    class RMB(val amount:Int) extends AnyVal { override def toString() = "$" + amount }

    RMBScala 源码中的类型为RMB,但是编译后Java 字节码中直接使用Int

  3. 一个良好的习惯是:对每个领域概念定义一个新的类,哪怕复用相同的类来实现不同用途也是可以的。甚至这样的类是细微类:既没有方法,也没有字段。

    这个方法可以有助于编译器在更多的地方帮助你。如:

    
    
    xxxxxxxxxx
    def ROI(cost:Double, revenue:Double): Double = (revenue-cost)/cost

    这个函数用于计算企业的ROI。如果由于疏忽,将成本传给了revenue 参数、将收入传给了cost 参数,则计算结果是错误的。此时编译器并不会错误,因为它们都是Double 类型。

    此时可以定义值类:

    
    
    xxxxxxxxxx
    class Cost(val value: Double) extends AnyVal class Revenue(val value: Double) extends AnyVal def ROI(cost:Cost, revenue:Revenue) : Double = (revenue.value-cost.value)/cost.value

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文