熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Javascript >> 正文

面向Java開發人員的Scala指南: 實現繼承

2022-06-13   來源: Javascript 

  Scala 對實現繼承的支持與 Java&#;語言一樣豐富 — 但 Scala 的繼承帶來了一些驚喜這個月Ted Neward 介紹了以 Scala 方式完成的多態還介紹了混合函數與面向對象的語言風格同時使您依然能夠完美地映射到 Java 平台的繼承模型

  近十幾年來面向對象語言設計的要素一直是繼承的核心不支持繼承的語言(如 Visual Basic)被嘲諷是 玩具語言 不適合真正的工作與此同時支持繼承的語言所采用的支持方法五花八門導致了許多爭論多重繼承是否真的必不可少(就像 C++ 的創作者認定的那樣)它是否不必要而丑陋的(就像 C# 和 Java 的創作者堅信的那樣)?Ruby 和 Scala 是兩種較新的語言采取了多重繼承的這種方法 — 正如我在上期介紹 Scala 的特征時所討論的那樣

  與所有 傑出的語言一樣Scala 也支持實現繼承在 Java 語言中單一實現繼承模型允許您擴展基類添加新方法和字段等盡管存在某些句法變更Scala 的實現繼承依然類似於 Java 語言中的實現不同的是 Scala 融合了對象和函數語言設計這非常值得我們在本期文章中進行討論

  普通 Scala 對象

  與本系列之前的文章類似我將使用 Person 類作為起點探索 Scala 的繼承系統清單 展示了 Person 的類定義

  清單 我是人類

  清單 我是人類

   // This is Scala
class Person(val firstName:String val lastName:String val age:Int)
{
  def toString = [Person: firstName=+firstName+ lastName=+lastName+
                         age=+age+]
}
    Person 是一個非常簡單的 POSO(普通 Scala 對象Plain Old Scala Object)具有三個只讀字段您可能會想起要使這些字段可以讀寫只需將主構造函數聲明中的 val 更改為 var 即可

  無論如何使用 Person 類型也非常簡單如清單 所示

  清單 PersonApp

   // This is Scala
object PersonApp
{
  def main(args : Array[String]) : Unit =
  {
    val bindi = new Person(Tabinda Khan )
    Systemoutprintln(bindi)
  }
}

  這算不上什麼令人驚訝的代碼但給我們提供了一個起點

  Scala 中的抽象方法

  隨著該系統的發展越來越明顯地意識到 Person 類缺乏一個成為 Person 的重要部分這個部分是做些事情 的行為許多人都會根據我們在生活中的作為來定義自己而不是根據現有和占用的空間因此我會添加一個新方法如清單 所示這賦予了 Person 一些意義

  清單 很好做些事情!

   // This is Scala
class Person(val firstName:String val lastName:String val age:Int)
{
  override def toString = [Person: firstName=+firstName+ lastName=+lastName+
                          age=+age+]

  def doSomething = // uh what?
}

  這帶來了一個問題Person 的用途究竟是什麼?有些 Person 繪畫有些唱歌有些編寫代碼有些玩視頻游戲有些什麼也不做(問問十幾歲青少年的父母)因此我會為 Person 創建 子類而不是嘗試去將這些活動直接整合到 Person 本身之中如清單 所示

  清單 這個人做的事情很少

   // This is Scala
class Person(val firstName:String val lastName:String val age:Int)
{
  override def toString = [Person: firstName=+firstName+ lastName=+lastName+
                          age=+age+]

  def doSomething = // uh what?
}

class Student(firstName:String lastName:String age:Int)
  extends Person(firstName lastName age)
{
  def doSomething =
  {
    Systemoutprintln(Im studying hard Ma I swear! (Pass the beer guys!))
  }
}

  當嘗試編譯代碼時我發現無法編譯這是因為 PersondoSomething 方法的定義無法工作這個方法需要一個完整的主體(或許可拋出異常來表示它應在繼承類中被覆蓋)或者不需要主體類似於 Java 代碼中抽象方法的工作方式我在清單 中嘗試使用抽象的方法

  清單 抽象類 Person

   // This is Scala
abstract class Person(val firstName:String val lastName:String val age:Int)
{
  override def toString = [Person: firstName=+firstName+ lastName=+lastName+
                          age=+age+]

  def doSomething; // note the semicolon which is still optional
                   // but stylistically I like having it here
}

class Student(firstName:String lastName:String age:Int)
  extends Person(firstName lastName age)
{
  def doSomething =
  {
    Systemoutprintln(Im studying hard Ma I swear! (Pass the beer guys!))
  }
}

  請注意我如何使用 abstract 關鍵字裝飾 Person 類abstract 為編譯器指出是的這個類應該是抽象的在這方面Scala 與 Java 語言沒有區別

  對象遇到函數

  由於 Scala 融合了對象和函數語言風格我實際上建模了 Person(如上所述)但並未創建子類型這有些古怪但強調了 Scala 對於這兩種設計風格的整合以及隨之而來的有趣理念

  回憶 前幾期文章Scala 將函數作為值處理就像處理語言中的其他值一樣例如 IntFloat 或 Double在建模 Person 時我可以利用這一點來獲得 doSomething不僅將其作為一種繼承類中覆蓋的方法還將其作為可調用替換擴展的 函數值清單 展示了這種方法

  清單 努力工作的人

   // This is Scala   
class Person(val firstName:String val lastName:String val age:Int)
{
  var doSomething : (Person) => Unit =
    (p:Person) => Systemoutprintln(Im + p + and I dont do anything yet!);
   
  def work() =
    doSomething(this)
   
  override def toString = [Person: firstName=+firstName+ lastName=+lastName+
                          age=+age+]
}

object App
{
  def main(args : Array[String]) =
  {
    val bindi = new Person(Tabinda Khan )
    Systemoutprintln(bindi)
   
    bindiwork()
   
    bindidoSomething =
      (p:Person) => Systemoutprintln(I edit textbooks)
     
    bindiwork()
   
    bindidoSomething =
      (p:Person) => Systemoutprintln(I write HTML books)
     
    bindiwork()
  }
}

  將函數作為第一建模工具是 RubyGroovy 和 ECMAScript(也就是 JavaScript)等動態語言以及許多函數語言的常用技巧盡管其他語言也可以用函數作為建模工具(C++ 通過函數指針和/或成員函數指針實現Java 代碼中通過接口引用的匿名內部類實現)但所需的工作比 Scala(以及 RubyGroovyECMAScript 和其他語言)多得多這是函數語言使用的 高階函數 概念的擴展

  多虧 Scala 將函數視為值這樣您就可以在運行時需要切換功能的時候利用函數值可將這種方法視為角色模式 —— Gang of Four 戰略模式的一種變體在這種模式中對象角色(例如 Person 的當前就職狀態)作為運行時值得到了更好的表現比靜態類型的層次結構更好

  層次結構上層的構造函數

  回憶一下編寫 Java 代碼的日子有時繼承類需要從構造函數傳遞參數至基類構造函數從而使基類字段能夠初始化在 Scala 中由於主構造函數出現在類聲明中不再是類的 傳統 成員因而將參數傳遞到基類將成為一個全新維度的問題

  在 Scala 中主構造函數的參數在 class 行傳遞但您也可以為這些參數使用 val 修飾符以便在類本身上輕松引入讀值器(對於 var則為寫值器)

  因此清單 中的 Scala 類 Person 轉變為清單 中的 Java 類使用 javap 查看

  清單 請翻譯一下

   // This is javap
C:\Projects\scalainheritance\code>javap classpath classes Person
Compiled from personscala
public abstract class Person extends javalangObject implements scalaScalaObje
ct{
    public Person(javalangString javalangString int);
    public javalangString toString();
    public abstract void doSomething();
    public int age();
    public javalangString lastName();
    public javalangString firstName();
    public int $tag();
}

  JVM 的基本規則依然有效Person 的繼承類在構造時向基類傳遞某些內容而不管語言強調的是什麼(實際上這並非完全 正確但在語言嘗試規避此規則時JVM 會表現失常因此大多數語言仍然堅持通過某種方法為其提供支持)當然Scala 需要堅守此規則因為它不僅需要保持 JVM 正常運作而且還要保持 Java 基類正常運作這也就是說無論如何Scala 必須實現一種語法允許繼承類調用基類同時保留允許我們在基類上引入讀值器和寫值器的語法

  為了將此放到更具體的上下文中假設我通過以下方式編寫了 清單 中的 Student 類

  清單 壞學生!

   // This is Scala
// This WILL NOT compile
class Student(val firstName:String val lastName:String val age:Int)
  extends Person(firstName lastName age)
{
  def doSomething =
  {
    Systemoutprintln(Im studying hard Ma I swear! (Pass the beer guys!))
  }
}

  本例中的編譯器將運行很長一段時間因為我嘗試為 Student 類引入一組新方法(firstNamelastName 和 age)這些方法將與 Person 類上名稱類似的方法彼此沖突Scala 編譯器不一定了解我是否正在嘗試覆蓋基類方法(這很糟糕因為我可以在這些基類方法後隱藏實現和字段)或者引入相同名稱的新方法(這也很糟糕因為我可以在這些基類方法後隱藏實現和字段)簡而言之您將看到如何成功覆蓋來自基類的方法但那並不是我們目前要追求的目標

  您還應注意到在 Scala 中Person 構造函數的參數不必一對一地與傳遞給 Student 的參數聯系起來這裡的規則實際上與 Java 構造函數的規則完全相同我們這樣做只是為了便於閱讀同樣Student 可要求額外的構造函數參數與在 Java 語言中一樣如清單 所示

  清單 苛求的學生!

   // This is Scala
class Student(firstName:String lastName:String age:Int val subject:String)
  extends Person(firstName lastName age)
{
  def doSomething =
  {
    Systemoutprintln(Im studying hard Ma I swear! (Pass the beer guys!))
  }
}

  您又一次看到了 Scala 代碼與 Java 代碼有多麼的相似至少涉及繼承和類關系時是這樣

  語法差異

  至此您可能會對語法的細節感到迷惑畢竟 Scala 並未像 Java 語言那樣將字段與方法區分開來這實際上是一項深思熟慮的設計決策允許 Scala 程序員輕而易舉地向使用基類的用戶 隱藏 字段和方法之間的差異考慮清單

  清單 我是什麼?

   // This is Scala
abstract class Person(val firstName:String val lastName:String val age:Int)
{
  def doSomething
 
  def weight : Int
   
  override def toString = [Person: firstName=+firstName+ lastName=+lastName+
                          age=+age+]
}

class Student(firstName:String lastName:String age:Int val subject:String)
  extends Person(firstName lastName age)
{
  def weight : Int =
    age // students are notoriously skinny

  def doSomething =
  {
    Systemoutprintln(Im studying hard Ma I swear! (Pass the beer guys!))
  }
}

class Employee(firstName:String lastName:String age:Int)
  extends Person(firstName lastName age)
{
  val weight : Int = age * // Employees are not skinny at all

  def doSomething =
  {
    Systemoutprintln(Im working hard hon I swear! (Pass the beer guys!))
  }
}

  注意查看如何定義 weight 使其不帶有任何參數並返回 Int這是 無參數方法因為它看上去與 Java 語言中的 專有 方法極其相似Scala 實際上允許將 weight 定義為一種方法(如 Student 中所示)也允許將其定義為字段/存取器(如 Employee 中所示)這種句法決策使您在抽象類繼承的實現方面有一定的靈活性請注意在 Java 中即便是在同一個類中只有通過 get/set 方法來訪問各字段時才能獲得類似的靈活性不知道判斷正確與否但我認為只有少數 Java 程序員會用這種方式編寫代碼因此不經常使用靈活性此外Scala 的方法可像處理公共成員一樣輕松地處理隱藏/私有成員

  從 @Override 到 override

  繼承類經常需要更改在其某個基類內定義的方法的行為在 Java 代碼中我們通過為繼承類添加相同名稱相同簽名的新方法來處理這個問題這種方法的缺點在於簽名錄入的錯誤或含糊不清可能會導致沒有征兆的故障這也就意味著代碼可以編譯但在運行時無法正確完成操作

  為解決這個問題Java 編譯器引入了 @Override 注釋@Override 驗證引入繼承類的方法實際上已經覆蓋了基類方法在 Scala 中override 已經成為語言的一部分幾乎可以忘記它會生成編譯器錯誤因而繼承 toString() 方法應如清單 所示

  清單 這是繼承的結果

   // This is Scala
class Student(firstName:String lastName:String age:Int val subject:String)
  extends Person(firstName lastName age)
{
  def weight : Int =
    age // students are notoriously skinny

  def doSomething =
  {
    Systemoutprintln(Im studying hard Ma I swear! (Pass the beer guys!))
  }
 
  override def toString = [Student: firstName=+firstName+
                          lastName=+lastName+ age=+age+
                          subject=+subject+]
}

  非常簡單明了

  敲定

  當然允許繼承覆蓋的反面就是采取措施防止它基類需要禁止子類更改其基類行為或禁止任何類型的繼承類在 Java 語言中我們通過為方法應用修飾符 final 來實現這一點確保它不會被覆蓋此外也可以為類整體應用 final防止繼承實現層次結構在 Scala 中的效果是相同的我們可以向方法應用 final 來防止子類覆蓋它也可應用於類聲明本身來防止繼承

  牢記所有這些關於 abstractfinal 和 override 的討論都同樣適用於 名字很有趣的方法(Java 或 C# 或 C++ 程序員可能會這樣稱呼運算符)與應用於常規名稱方法的效果相同因此我們常常會定義一個基類或特征為數學函數設定某些預期(可以稱之為 Mathable這些函數定義抽象成員函數 +*/另外還有其他一些應該支持的數學運算例如 pow 或 abs隨後其他程序員可創建其他類型 — 可能是一個 Matrix 類實現或擴展 Mathable定義一些成員看上去就像 Scala 以開箱即用的方式提供的內置算術類型

  差別在於……

  如果 Scala 能夠如此輕松地映射到 Java 繼承模型(就像本文至此您看到的那樣)就應該能夠從 Java 語言繼承 Scala 類或反之實際上這必須 可行因為 Scala 與其他編譯為 Java 字節碼的語言相似必須生成繼承自 javalangObject 的對象請注意Scala 類可能也要繼承自其他內容例如特征因此實際繼承的解析和代碼生成的工作方式可能有所不同但最終我們必須能夠以某種形式繼承 Java 基類(切記特征類似於有行為的接口Scala 編譯器將特征分成接口並將實現推入特征編譯的目標類中通過這種方式來使之運作

  但結果表明Scala 的類型層次結構與 Java 語言中的對應結構略有不同從技術上來講所有 Scala 類繼承的基類(包括 IntFloatDouble 和其他數字類型)都是 scalaAny 類型這定義了一組核心方法可在 Scala 內的任意類型上使用==!=equalshashCodetoStringisInstanceOf 和 asInstanceOf大多數方法通過名稱即可輕松理解在這裡Scala 劃分為兩大分支原語類型 繼承自 scalaAnyVal類類型 繼承自 scalaAnyRe(scalaScalaObject 又繼承自 scalaAnyRef

  通常這並不是您要直接去操心的方面但在考慮跨兩種語言的繼承時可能會帶來某些非常有趣的副作用例如考慮清單 中的 ScalaJavaPerson

  清單 混合!

   // This is Scala
class ScalaJavaPerson(firstName:String lastName:String age:Int)
  extends JavaPerson(firstName lastName age)
{
  val weight : Int = age * // Who knows what Scala/Java people weigh?

  override def toString = [SJPerson: firstName=+firstName+
                          lastName=+lastName+ age=+age+]
}

  ……它繼承自 JavaPerson

  清單 看起來是否眼熟?

   // This is Java
public class JavaPerson
{
    public JavaPerson(String firstName String lastName int age)
    {
        thisfirstName = firstName;
        thislastName = lastName;
        thisage = age;
    }
   
    public String getFirstName()
    {
        return thisfirstName;
    }
    public void setFirstName(String value)
    {
        thisfirstName = value;
    }
   
    public String getLastName()
    {
        return thislastName;
    }
    public void setLastName(String value)
    {
        thislastName = value;
    }
   
    public int getAge()
    {
        return thisage;
    }
    public void setAge(int value)
    {
        thisage = value;
    }
   
    public String toString()
    {
        return [Person: firstName + firstName + lastName: + lastName +
            age: + age + ];
    }
   
    private String firstName;
    private String lastName;
    private int age;
}

  在編譯 ScalaJavaPerson 時它將照常擴展 JavaPerson但按照 Scala 的要求它還會實現 ScalaObject 接口並照例支持繼承自 JavaPerson 的方法因為 ScalaJavaPerson 是一種 Scala 類型我們可以期望它支持 Any 引用的指派根據 Scala 的規則

  清單 使用 ScalaJavaPerson

   // This is Scala   
    val richard = new ScalaJavaPerson(Richard Campbell )
    Systemoutprintln(richard)
    val host : Any = richard
    Systemoutprintln(host)

  但在 Scala 中創建 JavaPerson 並將其指派給 Any 引用時會發生什麼?

  清單 使用 JavaPerson

   // This is Scala   
    val carl = new JavaPerson(Carl Franklin )
    Systemoutprintln(carl)
    val host : Any = carl
    Systemoutprintln(host)

  結果顯示這段代碼如期編譯並運行因為 Scala 能確保 JavaPerson 做正確的事情這要歸功於 Any 類型與 javalangObject 類型的相似性實際上幾乎可以說所有擴展 javalangObject 的內容都支持存儲到 Any 引用之中(存在一些極端情況我聽說過但我自己還從未遇到過這樣的極端情況

  最終結果?出於實踐的目的我們可以跨 Java 語言和 Scala 混搭繼承而無需過分擔心(最大的麻煩將是試圖了解如何覆蓋 名字很有趣的 Scala 方法例如 ^=!# 或類似方法

  結束語

  在本月的文章中我為您介紹了 Scala 代碼和 Java 代碼之間的高度相似性意味著 Java 開發人員可以輕松理解並使用 Scala 的繼承模型方法覆蓋的工作方式相同成員可見性的工作方式相同還有更多相同的地方對於 Scala 中的所有功能繼承或許與 Java 開發中的對應部分最為相似惟一需要技巧的部分就是 Scala 語法這有著明顯的差異

  習慣兩種語言中繼承方法的相似之處和細微的差異您就可以輕松編寫您自己的 Java 程序的 Scala 實現例如考慮流行的 Java 基類和框架的 Scala 實現如 JUnitServletsSwing 或 SWT實際上Scala 團隊已經提供了一個 Swing 應用程序名為 OOPScala它使用 JTable通過相當少的幾行代碼(數量級遠遠低於傳統 Java 的對應實現)提供了簡單的電子表格功能

  因此如果您想知道如何在您的生產代碼中應用 Scala就應該准備好邁出探索的第一步考慮在 Scala 中編寫下一個程序的一小部分正如您在這期文章中所了解到的那樣從恰當的基類繼承采用與 Java 程序中相同的方式提供覆蓋您就不會遇到任何麻煩


From:http://tw.wingwit.com/Article/program/Java/Javascript/201311/25481.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.