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

面向Java開發人員的Scala指南: 包和訪問修飾符

2022-06-13   來源: JSP教程 

  在現實生活中代碼一定要引用並打包在本期(第七期) 面向 Java 開發人員的 Scala 指南 系列中Ted Neward 介紹了 Scala 的包和訪問修飾符功能糾正了以前的疏忽然後他繼續探討了 Scale 中的函數內容apply 機制

  最近讀者的反饋讓我意識到在制作本系列的過程中我遺漏了 Scala 的語言的一個重要方面Scala 的包和訪問修飾符功能所以在研究該語言的函數性元素 apply 機制前我將先介紹包和訪問修飾符

  打包

  為了有助於隔離代碼使其不會相互沖突Java&#; 代碼提供了 package 關鍵詞由此創建了一個詞法命名空間用以聲明類本質上將類 Foo 放置到名為 comtednewardutil 包中就將正式類名修改成了 comtednewardutilFoo同理必須按該方法引用類如果沒有Java 編程人員會很快指出他們會 import 該包避免鍵入正式名的麻煩的確如此但這僅意味著根據正式名引用類的工作由編譯器和字節碼完成快速浏覽一下 javap 的輸出這點就會很明了

   關於本系列

  Ted Neward 將和您一起深入探討 Scala 編程語言在這個新的 developerWorks 系列 中您將深入了解 Sacla並在實踐中看到 Scala 的語言功能進行比較時Scala 代碼和 Java 代碼將放在一起展示但(您將發現)Scala 中的許多內容與您在 Java 編程中發現的任何內容都沒有直接關聯而這正是 Scala 的魅力所在!如果用 Java 代碼就能夠實現的話又何必再學習 Scala 呢?

  然而Java 語言中的包還有幾個特殊的要求一定要在包所作用的類所在的 java 文件的頂端聲明包(在將注釋應用於包時這一點會引發很嚴重的語言問題)該聲明的作用域為整個文件這意味著兩個跨包進行緊密耦合的類一定要在跨文件時分離這會致使兩者間的緊密耦合很容易被忽略

  Scala 在打包方面所采取的方法有些不同它結合使用了 Java 語言的 declaration 方法和 C# 的 scope(限定作用域)方法了解了這一點Java 開發人員就可以使用傳統的 Java 方法並將 package 聲明放在 scala 文件的頂部就像普通的 Java 類一樣包聲明的作用域為整個文件就像在 Java 代碼中一樣而 Scala 開發人員則可以使用 Scala 的包 (scoping)限定作用域 方法用大括號限制 package 語句的作用域如清單 所示

  清單 簡化的打包

  package com { package tedneward { package scala { package demonstration { object App { def main(args : Array[String]) : Unit = { Systemoutprintln(Howdy from packaged code!) argsforeach((i) => Systemoutprintln(Got + i) ) } } } } } }


  這個代碼有效地聲明了類 App或者更確切的說是一個稱為 comtednewardscalademonstrationApp 的單個類注意 Scala 還允許用點分隔包名所以清單 中的代碼可以更簡潔如清單 所示

  清單 簡化了的打包(redux)

  package comtednewardscalademonstration { object App { def main(args : Array[String]) : Unit = { Systemoutprintln(Howdy from packaged code!) argsforeach((i) => Systemoutprintln(Got + i) ) } } }


  用哪一種樣式看起來都比較合適因為它們都編譯出一樣的代碼構造(Scala 將繼續編譯並和 javac 一樣在聲明包的子目錄中生成 class 文件)

  導入

  與包相對的當然就是 import 了Scala 使用它將名稱放入當前詞法名稱空間本系列的讀者已經在此前的很多例子中見到過 import 了但現在我將指出一些讓 Java 開發人員大吃一驚的 import 的特性

  首先import 可以用於客戶機 Scala 文件內的任何地方並非只可以用在文件的頂部這樣就有了作用域的關聯性因此在清單 javamathBigInteger 導入的作用域被完全限定到了在 App 對象內部定義的方法其他地方都不行如果 mathfun 內的其他類或對象要想使用 javamathBigInteger就需要像 App 一樣導入該類如果 mathfun 的幾個類都想使用 javamathBigInteger可以在 App 的定義以外的包級別導入該類這樣在包作用域內的所有類就都導入 BigInteger 了

  清單 導入的作用域

  package com { package tedneward { package scala { // package mathfun { object App { import javamathBigInteger def factorial(arg : BigInteger) : BigInteger = { if (arg == BigIntegerZERO) BigIntegerONE else arg multiply (factorial (arg subtract BigIntegerONE)) } def main(args : Array[String]) : Unit = { if (argslength > ) Systemoutprintln(factorial + args() + = + factorial(new BigInteger(args()))) else Systemoutprintln(factorial = ) } } } } } }


  不只如此Scala 還不區分高層成員和嵌套成員所以您不僅可以使用 import 將嵌套類型的成員置於詞法作用域中其他任何成員均可例如您可以通過導入 javamathBigInteger 內的所有名稱使對 ZERO 和 ONE 的限定了作用域的引用縮小為清單 中的名稱引用

  清單 靜態導入

  package com { package tedneward { package scala { // package mathfun { object App { import javamathBigInteger import BigInteger_ def factorial(arg : BigInteger) : BigInteger = { if (arg == ZERO) ONE else arg multiply (factorial (arg subtract ONE)) } def main(args : Array[String]) : Unit = { if (argslength > ) Systemoutprintln(factorial + args() + = + factorial(new BigInteger(args()))) else Systemoutprintln(factorial = ) } } } } } }


  您可以使用下劃線(還記得 Scala 中的通配符吧?)有效地告知 Scala 編譯器 BigInteger 內的所有成員都需要置入作用域由於 BigInteger 已經被先前的導入語句導入到作用域中因此無需顯式地使用包名限定類名實際上可以將所有這些都結合到一個語句中因為 import 可以同時導入多個目標目標間用逗號隔開(如清單 所示)

  清單 批量導入

  package com { package tedneward { package scala { // package mathfun { object App { import javamathBigInteger BigInteger_ def factorial(arg : BigInteger) : BigInteger = { if (arg == ZERO) ONE else arg multiply (factorial (arg subtract ONE)) } def main(args : Array[String]) : Unit = { if (argslength > ) Systemoutprintln(factorial + args() + = + factorial(new BigInteger(args()))) else Systemoutprintln(factorial = ) } } } } } }


  這樣您可以節省一兩行代碼注意這兩個導入過程不能結合先導入 BigInteger 類本身再導入該類中的各種成員

  也可以使用 import 來引入其他非常量的成員例如考慮一下清單 中的數學工具庫(或許不一定有什麼價值)

  清單 Enron 的記帳代碼

  package com { package tedneward { package scala { // package mathfun { object BizarroMath { def bizplus(a : Int b : Int) = { a b } def bizminus(a : Int b : Int) = { a + b } def bizmultiply(a : Int b : Int) = { a / b } def bizdivide(a : Int b : Int) = { a * b } } } } } }


  使用這個庫會越來越覺得麻煩因為每請求它的一個成員都需要鍵入 BizarroMath但是 Scala 允許將 BizarroMath 的每一個成員導入最高層的詞法空間因此簡直就可以把它們當成全局函數來使用(如清單 所示)

  清單 計算 Enron的開支

  package com { package tedneward { package scala { package demonstration { object App { def main(args : Array[String]) : Unit = { import comtednewardscalamathfunBizarroMath_ Systemoutprintln( + = + bizplus()) } } } } } }


  還有其他的一些構造很有趣它們允許 Scala 開發人員寫出更自然的 bizplus 但是這些內容本文不予討論(想了解 Scala 潛在的可以用於其他用途的特性的讀者可以看一下 OderskySpoon 和 Venners 所著的 Programming in Scala 中談到的 Scala implicit 構造)

  訪問

  打包(和導入)是 Scala 封裝的一部分和在 Java 代碼中一樣在 Scala 中打包很大一部分在於以選擇性方式限定訪問特定成員的能力 — 換句話說在於 Scala 將特定成員標記為 公有(public)private(私有) 或介於兩者之間的成員的能力

  Java 語言有四個級別的訪問公有(public)私有(private)受保護的(protected )和包級別(它沒有任何關鍵詞)訪問Scala


廢除了包級別的限制(在某種程度上)
默認使用 公有
指定 私有 表示 只有此作用域可訪問

  相反Scala 定義 protected 的方式與在 Java 代碼中不同Java protected 成員對於子類和在其中定義成員的包來說是可訪問的Scala 中則僅有子類可訪問這意味著 Scala 版本的 protected 限制性要比 Java 版本更嚴格(雖然按理說更加直觀)

  然而Scala 真正區別於 Java 代碼的地方是 Scala 中的訪問修飾符可以用包名來 限定用以表明直到 哪個訪問級別才可以訪問成員例如如果 BizarroMath 包要將成員訪問權限授權給同一包中的其他成員(但不包括子類)可以用清單 中的代碼來實現

  清單 Enron 的記帳代碼

  package com { package tedneward { package scala { // package mathfun { object BizarroMath { def bizplus(a : Int b : Int) = { a b } def bizminus(a : Int b : Int) = { a + b } def bizmultiply(a : Int b : Int) = { a / b } def bizdivide(a : Int b : Int) = { a * b } private[mathfun] def bizexp(a : Int b: Int) = } } } } }

  注意此處的 private[mathfun] 表達本質上這裡的訪問修飾符是說該成員直到 包 mathfun 為止都是私有的這意味著包 mathfun 的任何成員都有權訪問 bizexp但任何包以外的成員都無權訪問它包括子類

  這一點的強大意義就在於任何包都可以使用 private 或者 protected 聲明甚至 com(乃至 _root_它是根名稱空間的別名因此本質上 private[_root_] 等效於 public 同)進行聲明這使得 Scala 能夠為訪問規范提供一定程度的靈活性遠遠高於 Java 語言所提供的靈活性

  實際上Scala 提供了一個更高程度的訪問規范對象私有 規范用 private[this] 表示它規定只有被同一對象調用的成員可以訪問有關成員其他對象裡的成員都不可以即使對象的類型相同(這彌合了 Java 訪問規范系統中的一個缺口這個缺口除對 Java 編程問題有用外別無他用

  注意訪問修飾符必須在某種程度上在 JVM 之上映射這致使定義中的細枝末節會在從正規 Java 代碼中調用或編譯時丟失例如上面的 BizarroMath 示例(用 private[mathfun] 聲明的成員 bizexp)將會生成清單 中的類定義(當用 javap 來查看時)

  Listing Enron 的記帳庫JVM 視圖

  

  Compiled from packagingscala public final class comtednewardscalamathfunBizarroMath extends javalangObject { public static final int $tag(); public static final int bizexp(int int); public static final int bizdivide(int int); public static final int bizmultiply(int int); public static final int bizminus(int int); public static final int bizplus(int int); }

  在編譯的 BizarroMath 類的第二行很容易看出bizexp() 方法被賦予了 JVM 級別的 public 訪問修飾符這意味著一旦 Scala 編譯器結束訪問檢查細微的 private[mathfun] 區別就會丟失因此對於那些要從 Java 代碼使用的 Scala 代碼我寧願堅持傳統的 privatepublic 的定義(甚至 protected 的定義有時最終映射到 JVM 級別的 public所有不確定的時候請對照實際編譯的字節碼參考一下 javap以確認其訪問級別

  應用

  在本系列上一期的文章中(集合類型當談及 Scala 中的數組時(確切地說是 Array[T])我說過獲取數組的第 i 個元素 實際上是 那些名稱很有趣的方法中的一種……盡管當時是因為我不想深入細節但不管怎麼說事實證明這種說法嚴格來說 是不對的

  好吧我承認我說謊了

  技術上講在 Array[T] 類上使用圓括號要比使用 名稱有趣的方法 復雜一點Scala 為特殊的字符序列(即那些有左右括號的序列)保留了一個特殊名稱關聯因為它有著特殊的使用意圖 ……(或按函數來說將……應用 到……)

  換句話說Scala 有一個特殊的語法(更確切一些是一個特殊的語法關系)來代替 應用 操作符 ()更精確地說當用 () 作為方法調用來調用所述對象時Scala 將稱為 apply() 的方法作為調用的方法例如一個想充當仿函數(functor)的類(一個充當函數的對象)可以定義一個 apply 方法來提供類似於函數或方法的語義

  清單 使用 Functor!

  class ApplyTest { import orgjunit_ Assert_ @Test def simpleApply = { class Functor { def apply() : String = { Doing something without arguments } def apply(i : Int) : String = { if (i == ) Done else Applying + apply(i ) } } val f = new Functor assertEquals(Doing something without arguments f() ) assertEquals(Applying Applying Applying Done f()) } }


  好奇的讀者會想是什麼使仿函數不同於匿名函數或閉包呢?事實證明它們之間的關系相當明顯標准 Scala 庫中的 Function 類型(指包含一個參數的函數)在其定義上有一個 apply 方法快速浏覽一些為 Scala 匿名函數生成的 Scala 匿名類您就會明白生成的類是 Function(或者 Function 或 Function這要看該函數使用了幾個參數)的後代

  這意味著當匿名的或者命名的函數不一定適合期望設計方法時Scala 開發人員可以創建一個 functor 類提供給它一些初始化數據保存在字段中然後通過 () 執行它無需任何通用基類(傳統的策略模式實現需要這個類)

  清單 使用 Functor!

  class ApplyTest { import orgjunit_ Assert_ // @Test def functorStrategy = { class GoodAdder { def apply(lhs : Int rhs : Int) : Int = lhs + rhs } class BadAdder(inflateResults : Int) { def apply(lhs : Int rhs : Int) : Int = lhs + rhs * inflateResults } val calculator = new GoodAdder assertEquals( calculator( )) val enronAccountant = new BadAdder() assertEquals( enronAccountant( )) } }


  任何提供了被適當賦予了參數的 apply 方法的類只要這些參數都按數字和類型排列了起來它們都會在被調用時運行

  結束語

  Scala 的打包導入和訪問修飾符機制提供了傳統 Java 編程人員從未享受過的更高級的控制和封裝例如它們提供了導入一個對象的選擇方法的能力使它們看起來就像全局方法一樣而且還克服了全局方法的傳統的缺點它們使得使用那些方法變得極其簡單尤其是當這些方法提供了諸如本系列早期文章(Scala 控制結構內部揭密)引入的虛構的 tryWithLogging 函數這樣的高級功能時

  同樣應用 機制允許 Scala 隱藏函數部分的執行細節這樣編程人員可能會不知道(或不在乎)他們正調用的東西 事實上不是一個函數而是一個非常復雜的對象該機制為 Scala 機制的函數特性提供了另一個方面當然 Java 語言(或者 C# 或 C++)也提供了這個方面但是它們提供的語法純度沒有 Scala 的高


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