Scala 对实现继承的支持与 Java 语言一样丰富 —— 但 Scala 的继承带来了一些惊喜。本文中,Ted Neward 介绍了以 Scala 方式完成的多态,还介绍了混合函数与面向对象的语言风格,同时使您依然能够完美地映射到 Java 平台的继承模型。

近十几年来,面向方针言语规划的要素一直是承继的中心。不支撑承继的言语(如 Visual Basic)被嘲讽是 “玩具言语” ,不适合真实的作业。与此一起,支撑承继的言语所选用的支撑办法形形色色,导致了许多争辩。多重承继是否真的必不可少(就像 C++ 的创作者确定的那样),它是否不用要而丑恶的(就像 C# 和 Java 的创作者深信的那样)?Ruby 和 Scala 是两种较新的言语,采取了多重承继的这种办法 — 正如我在上期介绍 Scala 的特征时所评论的那样。

从Java走进Scala:当承继中的目标遇到函数(scala继承的基本语法)  scala 继承模型 第1张

与一切 出色的言语相同,Scala 也支撑完结承继。在 Java 言语中,单一完结承继模型答应您扩展基类,增加新办法和字段等。虽然存在某些句法改变,Scala 的完结承继依然相似于 Java 言语中的完结。不同的是 Scala 交融了方针和函数言语规划,这十分值得咱们在本期文章中进行评论。

一般 Scala 方针

与本系列之前的文章相似,我将运用 Person 类作为起点,探究 Scala 的承继体系。清单 1 展现了 Person 的类界说:

清单 1. 嘿,我是人类

  1. //ThisisScala
  2. classPerson(valfirstName:String,vallastName:String,valage:Int)
  3. {
  4. deftoString="[Person:firstName="+firstName+"lastName="+lastName+
  5. "age="+age+"]"
  6. }

Person 是一个十分简略的 POSO(一般 Scala 方针,Plain Old Scala Object),具有三个只读字段。您可能会想起,要使这些字段能够读写,只需将主结构函数声明中的 val 更改为 var 即可

无论怎么,运用 Person 类型也十分简略,如清单 2 所示:

清单 2. PersonApp

  1. //ThisisScala
  2. objectPersonApp
  3. {
  4. defmain(args:Array[String]):Unit=
  5. {
  6. valbindi=newPerson("Tabinda","Khan",38)
  7. System.out.println(bindi)
  8. }
  9. }

这算不上什么令人惊奇的代码,但给咱们供给了一个起点。

Scala 中的笼统办法

跟着该体系的开展,越来越明显地意识到 Person 类缺少一个成为 Person 的重要部分,这个部分是做些作业 的行为。许多人都会依据咱们在生活中的作为来界说自己,而不是依据现有和占用的空间。因此,我会增加一个新办法,如清单 3 所示,这赋予了 Person 一些含义:

清单 3. 很好,做些作业!

  1. //ThisisScala
  2. classPerson(valfirstName:String,vallastName:String,valage:Int)
  3. {
  4. overridedeftoString="[Person:firstName="+firstName+"lastName="+lastName+
  5. "age="+age+"]"
  6. defdoSomething=//uh....what?
  7. }

这带来了一个问题:Person 的用处终究是什么?有些 Person 绘画,有些歌唱,有些编写代码,有些玩视频游戏,有些什么也不做(问问十几岁青少年的爸爸妈妈)。因此,我会为 Person 创立 子类,而不是测验去将这些活动直接整合到 Person 自身之中,如清单 4 所示:

清单 4. 这个人做的作业很少

  1. //ThisisScala
  2. classPerson(valfirstName:String,vallastName:String,valage:Int)
  3. {
  4. overridedeftoString="[Person:firstName="+firstName+"lastName="+lastName+
  5. "age="+age+"]"
  6. defdoSomething=//uh....what?
  7. }
  8. classStudent(firstName:String,lastName:String,age:Int)
  9. extendsPerson(firstName,lastName,age)
  10. {
  11. defdoSomething=
  12. {
  13. System.out.println("I'mstudyinghard,Ma,Iswear!(Passthebeer,guys!)")
  14. }
  15. }

当测验编译代码时,我发现无法编译。这是由于 Person.doSomething 办法的界说无法作业;这个办法需求一个完好的主体(或答应抛出异常来表明它应在承继类中被掩盖),或许不需求主体,相似于 Java 代码中笼统办法的作业办法。我在清单 5 中测验运用笼统的办法:

清单 5. 笼统类 Person

  1. //ThisisScala
  2. abstractclassPerson(valfirstName:String,vallastName:String,valage:Int)
  3. {
  4. overridedeftoString="[Person:firstName="+firstName+"lastName="+lastName+
  5. "age="+age+"]"
  6. defdoSomething;//notethesemicolon,whichisstilloptional
  7. //butstylisticallyIlikehavingithere
  8. }
  9. classStudent(firstName:String,lastName:String,age:Int)
  10. extendsPerson(firstName,lastName,age)
  11. {
  12. defdoSomething=
  13. {
  14. System.out.println("I'mstudyinghard,Ma,Iswear!(Passthebeer,guys!)")
  15. }
  16. }

请留意,我怎么运用 abstract 关键字装修 Person 类。abstract 为编译器指出,是的,这个类应该是笼统的。在这方面,Scala 与 Java 言语没有差异。

#p#

方针,遇到函数

由于 Scala 交融了方针和函数言语风格,我实际上建模了 Person(如上所述),但并未创立子类型。这有些乖僻,但着重了 Scala 关于这两种规划风格的整合,以及随之而来的风趣理念。

回想 前几期文章,Scala 将函数作为值处理,就像处理言语中的其他值相同,例如 Int、Float 或 Double。在建模 Person 时,我能够运用这一点来取得 doSomething,不只将其作为一种承继类中掩盖的办法,还将其作为可调用、替换、扩展的 函数值。清单 6 展现了这种办法:

清单 6. 努力作业的人

  1. //ThisisScala
  2. classPerson(valfirstName:String,vallastName:String,valage:Int)
  3. {
  4. vardoSomething:(Person)=>Unit=
  5. (p:Person)=>System.out.println("I'm"+p+"andIdon'tdoanythingyet!");
  6. defwork()=
  7. doSomething(this)
  8. overridedeftoString="[Person:firstName="+firstName+"lastName="+lastName+
  9. "age="+age+"]"
  10. }
  11. objectApp
  12. {
  13. defmain(args:Array[String])=
  14. {
  15. valbindi=newPerson("Tabinda","Khan",38)
  16. System.out.println(bindi)
  17. bindi.work()
  18. bindi.doSomething=
  19. (p:Person)=>System.out.println("Iedittextbooks")
  20. bindi.work()
  21. bindi.doSomething=
  22. (p:Person)=>System.out.println("IwriteHTMLbooks")
  23. bindi.work()
  24. }
  25. }

将函数作为***建模东西是 Ruby、Groovy 和 ECMAScript(也便是 JavaScript)等动态言语以及许多函数言语的常用技巧。虽然其他言语也能够用函数作为建模东西,(C++ 经过函数指针和/或成员函数指针完结,Java 代码中经过接口引证的匿名内部类完结),但所需的作业比 Scala(以及 Ruby、Groovy、ECMAScript 和其他言语)多得多。这是函数言语运用的 “高阶函数” 概念的扩展。(关于高阶函数的更多内容,请拜见 参考资料。)

幸亏 Scala 将函数视为值,这样您就能够在运转时需求切换功用的时分运用函数值。可将这种办法视为人物形式 —— Gang of Four 战略形式的一种变体,在这种形式中,方针人物(例如 Person 的当时上任状况)作为运转时值得到了更好的体现,比静态类型的层次结构更好。

层次结构上层的结构函数

回想一下编写 Java 代码的日子,有时承继类需求从结构函数传递参数至基类结构函数,从而使基类字段能够初始化。在 Scala 中,由于主结构函数出现在类声明中,不再是类的 “传统” 成员,因此将参数传递到基类将成为一个全新维度的问题。

在 Scala 中,主结构函数的参数在 class 行传递,但您也能够为这些参数运用 val 修饰符,以便在类自身上轻松引进读值器(关于 var,则为写值器)。

因此,清单 5 中的 Scala 类 Person 转变为清单 7 中的 Java 类,运用 javap 检查:

清单 7. 请翻译一下

  1. //Thisisjavap
  2. C:\Projects\scala-inheritance\code>javap-classpathclassesPerson
  3. Compiledfrom"person.scala"
  4. publicabstractclassPersonextendsjava.lang.Objectimplementsscala.ScalaObje
  5. ct{
  6. publicPerson(java.lang.String,java.lang.String,int);
  7. publicjava.lang.StringtoString();
  8. publicabstractvoiddoSomething();
  9. publicintage();
  10. publicjava.lang.StringlastName();
  11. publicjava.lang.StringfirstName();
  12. publicint$tag();
  13. }

JVM 的根本规矩依然有用:Person 的承继类在结构时向基类传递某些内容,而不论言语着重的是什么。(实际上,这并非彻底 正确,但在言语测验躲避此规矩时,JVM 会体现异常,因此大多数言语依然坚持经过某种办法为其供给支撑。)当然,Scala 需求据守此规矩,由于它不只需求坚持 JVM 正常运作,并且还要坚持 Java 基类正常运作。这也便是说,无论怎么,Scala 有必要完结一种语法,答应承继类调用基类,一起保存答应咱们在基类上引进读值器和写值器的语法。

为了将此放到更详细的上下文中,假定我经过以下办法编写了 清单 5 中的 Student 类:

清单 8. 坏学生!

  1. //ThisisScala
  2. //ThisWILLNOTcompile
  3. classStudent(valfirstName:String,vallastName:String,valage:Int)
  4. extendsPerson(firstName,lastName,age)
  5. {
  6. defdoSomething=
  7. {
  8. System.out.println("I'mstudyinghard,Ma,Iswear!(Passthebeer,guys!)")
  9. }
  10. }

本例中的编译器将运转很长一段时间,由于我测验为 Student 类引进一组新办法(firstName、lastName 和 age)。这些办法将与 Person 类上称号相似的办法互相抵触,Scala 编译器不必定了解我是否正在测验掩盖基类办法(这很糟糕,由于我能够在这些基类办法后躲藏完结和字段),或许引进相同称号的新办法(这也很糟糕,由于我能够在这些基类办法后躲藏完结和字段)。简而言之,您将看到怎么成功掩盖来自基类的办法,但那并不是咱们现在要寻求的方针。

您还应留意到,在 Scala 中,Person 结构函数的参数不用一对一地与传递给 Student 的参数联系起来;这儿的规矩实际上与 Java 结构函数的规矩彻底相同。咱们这样做仅仅为了便于阅览。相同,Student 可要求额定的结构函数参数,与在 Java 言语中相同,如清单 9 所示:

清单 9. 苛求的学生!

  1. //ThisisScala
  2. classStudent(firstName:String,lastName:String,age:Int,valsubject:String)
  3. extendsPerson(firstName,lastName,age)
  4. {
  5. defdoSomething=
  6. {
  7. System.out.println("I'mstudyinghard,Ma,Iswear!(Passthebeer,guys!)")
  8. }
  9. }

您又一次看到了 Scala 代码与 Java 代码有多么的相似,至少触及承继和类联系时是这样。

语法差异

至此,您可能会对语法的细节感到利诱。究竟 Scala 并未像 Java 言语那样将字段与办法区别开来。这实际上是一项深思熟虑的规划决议计划,答应 Scala 程序员垂手可得地向运用基类的用户 “躲藏” 字段和办法之间的差异。考虑清单 10:

清单 10. 我是什么?

  1. //ThisisScala
  2. abstractclassPerson(valfirstName:String,vallastName:String,valage:Int)
  3. {
  4. defdoSomething
  5. defweight:Int
  6. overridedeftoString="[Person:firstName="+firstName+"lastName="+lastName+
  7. "age="+age+"]"
  8. }
  9. classStudent(firstName:String,lastName:String,age:Int,valsubject:String)
  10. extendsPerson(firstName,lastName,age)
  11. {
  12. defweight:Int=
  13. age//studentsarenotoriouslyskinny
  14. defdoSomething=
  15. {
  16. System.out.println("I'mstudyinghard,Ma,Iswear!(Passthebeer,guys!)")
  17. }
  18. }
  19. classEmployee(firstName:String,lastName:String,age:Int)
  20. extendsPerson(firstName,lastName,age)
  21. {
  22. valweight:Int=age*4//Employeesarenotskinnyatall
  23. defdoSomething=
  24. {
  25. System.out.println("I'mworkinghard,hon,Iswear!(Passthebeer,guys!)")
  26. }
  27. }

留意检查怎么界说 weight 使其不带有任何参数并回来 Int。这是 “无参数办法”。由于它看上去与 Java 言语中的 “专有” 办法极端相似,Scala 实际上答应将 weight 界说为一种办法(如 Student 中所示),也答应将其界说为字段/存取器(如 Employee 中所示)。这种句法决议计划使您在笼统类承继的完结方面有必定的灵活性。请留意,在 Java 中,即便是在同一个类中,只要经过 get/set 办法来访问各字段时,才干取得相似的灵活性。不知道判别正确与否,但我以为只要少量 Java 程序员会用这种办法编写代码,因此不常常运用灵活性。此外,Scala 的办法可像处理公共成员相同轻松地处理躲藏/私有成员。

#p#

从 @Override 到 override

承继类常常需求更改在其某个基类内界说的办法的行为;在 Java 代码中,咱们经过为承继类增加相同称号、相同签名的新办法来处理这个问题。这种办法的缺陷在于签名录入的过错或含糊不清可能会导致没有预兆的毛病,这也就意味着代码能够编译,但在运转时无法正确完结操作。

为处理这个问题,Java 5 编译器引进了 @Override 注释。@Override 验证引进承继类的办法实际上现已掩盖了基类办法。在 Scala 中,override 现已成为言语的一部分,简直能够忘掉它会生成编译器过错。因此,承继 toString() 办法应如清单 11 所示:

清单 11. 这是承继的成果

  1. //ThisisScala
  2. objectPersonApp
  3. {
  4. defmain(args:Array[String]):Unit=
  5. {
  6. valbindi=newPerson("Tabinda","Khan",38)
  7. System.out.println(bindi)
  8. }
  9. }
0
转载请说明出处
知优网 » 从Java走进Scala:当承继中的目标遇到函数(scala继承的基本语法)

发表评论

您需要后才能发表评论