級別: 初級 |
CTO, Vanward Technologies
2004 年 10 月 06 日
Nice 是可兼容 JRE 的、面向對象的語言,為 Java 平臺提供了極強的語言表達能力。Nice 還允許在任何 Java 虛擬機上實現許多 Java 5 中的裁邊功能。本文是 alt.lang.jre系列的第 4 期,在這篇文章中,固定撰稿人,在各方面都很“Nice”的 Andrew Glover 將向您說明 Nice 的一些最令人激動的功能。
Nice 是面向對象的、可兼容 JRE 的編程語言,重要特點是模塊化、表達能力和安全。與純粹面向對象的 Java 語言不同,Nice 合并了一些實用靈活的開發技術,其中包括面向方面編程中的一些技術。與許多較新的開發語言一樣,Nice 的優點是通過改進以前的語言(包括 Java 語言)的缺點而來的。而且,Nice 不但提供了 Java 1.5 中的許多功能,而且使這些功能在任何 JVM 中都可以使用。
關于本系列文章 |
在 alt.lang.jre專欄的第 4 期中,我將向您介紹 Nice 的一些最引人注意的和最有用的功能,其中包括參數類、契約式設計構建、多方法等。
Nice 入門
在 Java 語言中,功能的中心單元包含在類中。然而,Nice 將這個概念上升到了包的某個級別上。Nice 允許在以 .nice
后綴結尾的文件中定義多個類、接口和方法。一旦這樣定義了文件,該文件通常就會變為一個包。Nice 的編譯器 nicec
被用來編譯 .nice
文件,該編譯器也可以作為 Ant 任務或 Eclipse 插件使用(請參閱 參考資料)。
運行 Nice 應用程序需要定義 main()
方法。就像在 Java 語言中定義 main()
方法那樣定義該方法。但是 Nice 的 main
方法與 Java 語言的不同,因為該方法是在包中定義的,不是在類中定義的。因此,該方法可以變為靜態鉤(static hook)來跳過 Nice 應用程序,與 Java 類中的 static main()
方法很像,它允許從命令行運行類。
在清單 1 中,可以看到 Nice 的 main()
方法是如何工作的,清單 1 是驗證一些基本匹配知識的簡單應用程序。注意,Nice 支持 assert
關鍵字,即使是在 1.4 之前的 VM 上運行也是如此。
|
正如一會將看到的,在 Nice 中,可以在類之外定義方法。在閱讀本文之后,您就會對在包級別上定義方法的作用有更加清楚的認識。
Nice 顯示更加安全
代碼安全是 Nice 可以引入開發工具箱的最強大的功能之一。當處理兩個非常普遍的 Java 異常時,Nice 特別有效。這兩個異常是: ClassCastException
和 NullPointerException
。
這兩個異常長久以來一直是從事 Java 平臺工作的開發人員努力攻克的難題。Java 5.0 合并了常規類型,并以合并類型作為表達 ClassCastExceptions
的有效方式,但是這項更改對許多仍舊使用 Java 1.3 的公司不起作用。而另一方面,人們在考慮了 ClassCastException
和 NullPointerException
的情況下開發了 Nice。因此,Nice 語言還支持參數類和可選類型這兩項功能,它們對阻止應用程序拋出異常大有幫助。而且,通過 Nice,現在可以在任何 Java 平臺 1.2 或更高版本中使用這些功能。
參數類
在 Java 語言中,所有集合都有最少的共同類型,它就是 Object
。所以,Java 開發人員每次檢索元素時,都必須轉換類型,在較大的程序中,這會是一種負擔。為了說明這個問題,我將向您展示 Java 語言是如何處理該類型,然后再向您展示 Nice 的參數類是如何簡化這個問題的。
清單 2 顯示了 IStack
接口,用它來表示用 Java 語言定義的堆棧數據結構。
|
IStack
的 Java 實現非常簡單,但仍可用作堆棧數據結構,如清單 3 中所示。
|
雖然非常有效,但是,只要使用這個堆棧實現,就需要進行單調乏味的類型轉換,如清單 4 中所示。
清單 4. 顯示非常耗時的類型轉換的 JUnit 示例
|
在清單 4 中, testSimpleIntegerPops()
方法顯示了為何必須將 pop
操作的結果轉換為 Integer
。如果未能執行轉換,則會產生 ClassCastException
,如 testPopClassCastException()
方法中所述。仔細查看 testSimpleIntegerPopsWillNotCompile
方法中的注釋行。它們沒有使用普通 Java 代碼進行編譯;但該代碼看起來不 好嗎?
更好的集合處理
在其他功能中, 參數類或模板(如果從 C++ 后臺開始)通常用于簡化集合處理。參數類允許定義集合包含單一的、精確的類型。
要用 Nice 定義普通的 Stack
實現,可以在定義類以及預期參數和返回類型時使用 <Type>
語法。在清單 5 中,可以看到如何通過使用 Nice 的 <Type>
語法的 SimpleStack
類來重新定義和簡化堆棧數據結構。
|
現在,我們定義了 SimpleStack
類參數化的 Nice 來存放特定參數類型,這些類型是在 編譯時間定義的,而不像在 Java 語言中那樣,是在運行時定義的。注意清單 5 中的 pop()
和 peek()
方法是如何返回 Type
的,以及 push()
方法是如何將 Type
用作參數。該 Type
與內部 items
集合將包含的 對象類型是相匹配的。
清單 6 顯示了在 Nice 中如何使用參數堆棧。注意,我已經創建了只能存放 String
類型的對象的 SimpleStack
實例。注意在 Nice 中為何不對最后一行進行編譯!這個代碼行試圖 push
一個 Integer
到參數化堆棧中。
|
可選類型NullPointerException
可能是所有 Java 開發人員最熟悉的異常。實際上,空指針非常麻煩,甚至是最簡單的 Java 編譯器都可以將對象標記為尚未初始化。不幸的是,您可能不時地發現,那些警告并沒有捕獲 null
的每次可能出現。
為了與其標準安全屬性一致,Nice 提供了 可選類型概念。因為可選類型是 API 的作者使用 Java 語言定義的,常常難以確定,所以 Nice 為這些類型添加了問號( ?
)作為前綴。
如果您發現自己經常開發使用大量參數的 API,那么可用選項將特別有用。確定不同的參數是可選的很可能是因為 JavaDoc
注釋就是這樣指定這些參數的,或者是因為您傳遞了 null
并指出其可以使用。
使用 Nice,可以僅在參數之前添加 ?
來表明變量 可能為 null
;相反地,沒有問號的變量不能直接設為 null
。在清單 7 中可以看到這是如何工作的,在該清單中,我在 Dog
類中定義了方法 walk()
。這表明類型 Location
的第二個參數可能為 null
。
|
注意清單 7 中的 walk()
方法為何 必須說明 location
可以為 null
。在 println()
方法中,編譯器將不允許代碼實際引用 location.place
。
清單 8 例示了可選類型的作用,本例中將使用清單 7 中定義的 walk()
方法。注意,現在可以合法傳遞 null
,還可以合法傳遞 location
的有效值。
|
不要將空功能語法(null capability syntax)與 可選參數混淆,它們是完全不同的。
命名和可選參數
正如您在清單 8 中可能注意到的,在調用 walk()
方法時,Nice 允許指定參數。當我在 Dog
實例中調用 walk()
方法時,我明確命名了參數。例如,將類型 Leash
的 lsh
變量設為第一個參數 leash
。
Nice 還允許在構造函數中指定參數,與在 Groovy 和 Jython 等語言中的做法非常類似。在清單 8 中,當創建新的 Dog
和 Leash
的實例時,我在構造函數中分別明確設置了每個實例的屬性、名稱和長度。
命名參數允許您以任何希望的順序傳遞這些參數。例如,在清單 9 中,這兩個調用基本相同;因為我命名了參數,傳遞這些參數的順序沒有影響。
清單 9. Nice 中可選類型的進一步說明
|
可選參數甚至比可選類型更有用。它們實際上可以替代可選類型。清單 10 顯示了如何使用可選參數重新定義 walk()
方法。
|
該代碼將第二個參數 location
定義為 可選。調用 walk()
方法時,不必傳送該參數。如果未傳送任何值,那么將使用默認值。在本例中,默認值是 Location
,其中 place
等于 nowhere。
與 清單 7 中所做相同,可選參數使您不必進行防御性地編程。但與清單 7 中的 walk()
方法不同的是,編寫清單 10 中的 walkAgain()
方法需要考慮遇到 null
值的可能性。
對于可選參數,最后要注意的一點是,可以使用其他值覆蓋這些參數。如清單 11 中所示,可以選擇一個 或兩個參數來調用 walkAgain
。
|
契約式設計
契約式設計(DBC)是一項技術,它通過在每個組件的界面中明確說明該組件的預期功能和客戶機的異常,來確保系統中的所有組件執行它們將進行的操作。Eiffel(請參閱 參考資料)是一種使用 DBC 的流行語言。許多語言都已經合并了契約式設計技術,其中包括 Java 1.4,該技術引入了斷言的使用。Nice 使用關鍵字 requires
和 ensures
來合并程序和斷言中的契約信息,以便在執行過程中確認該程序的狀態。另外,Nice 甚至支持 1.4 之前的 JVM 使用 assert
關鍵字。
requires
和 ensures
關鍵字的作用與前條件和后條件相似,它們允許定義預期的方法將遵循和保證的條件。如果條件得不到滿足,將會在運行時生成斷言異常。條件和斷言的組合已經使許多應用程序免于陷入由邏輯錯誤引起的深淵中。我將在下面的實例中說明如何一起使用這兩種機制。
條件性使用requires
子句說明了某個方法的客戶機必須滿足的要求。如果未滿足要求,那么將放棄該方法,并生成斷言異常。 ensures
子句將在方法的客戶機端起作用。該子句是將方法提交到其相關聯的調用者的保證。
清單 12 定義了包含 brew()
方法的 CoffeeMachine
類。在 brew()
的定義中,要求客戶機 必須傳遞 Coffee
實例,其中 beanAge
屬性小于 10。否則,將生成與使用子句“Beans are too old to brew”一致的斷言異常。而且,我還保證方法(本例中為 CoffeeCup
的實例)的 result
將具有大于 155 的 temp
屬性。
|
清單 13 顯示了新的 CoffeeMachine
實例和 Coffee
的新實例。注意,在本例中,已經將 Coffee
的 beanAge
屬性設為 15,該值大于 10,因此不滿足 brew
的契約。
|
清單 14 說明了在清單 13 中調用 brew()
時生成的異常堆棧。正如您可以看到的,這里提供了定制消息“Beans are too old to brew”來幫助調試。
|
默認情況下,不啟用 Nice 斷言,所以必須開啟這些斷言。有關 JVM 的指令,請參閱 Nice 的文檔。
重訪堆棧
在基本了解了 Nice 如何實現前條件和后條件之后,讓我們看一下將這些技術應用于前面的 IStack
示例時會發生什么。在清單 15 中,用 Nice 定義了 IStack
接口,并添加了不同的 ensures
和 requires
子句。
|
在清單 16 中,實現了 IStack
接口。注意,Nice 為定義方法本體提供了捷徑:只使用 =
語法。還要注意下例中 Nice 的 override
語法。
|
在很大程度上,可以像以前那樣使用新的 DBCStack
。不過,如果試圖違反 IStack
契約的條款,將會引起斷言異常。例如,在清單 17 中,可以看到當試圖對已經確保兩個項安全的堆棧中推入第三個項時會發生什么。第三個 pop()
的調用導致先條件 requires !isEmpty(this)
失敗。因此,將生成 AssertionFailed
異常以及定制消息:“cannot pop an empty stack”。
|
多方法的好處
Nice 最引人注意的獨特功能之一是 多方法,或在特定類定義之外定義類實例方法的能力。該方法獨自創建了許多擴展,這些擴展可與面向方面編程(AOP)的一些比較令人激動的原則相媲美。
方法的語法相當簡單,因為第一個參數是方法應該附加的類型。剩余的參數則成為實例方法的標準參數。為了舉例說明,在清單 18 中,可以創建簡單的、無意義的方法,并將其附加到 java.lang.String
的實例。
|
在該代碼中,當調用 laugh()
方法時,它將只打印 String
。因為 laugh()
方法的第一個參數的類型為 String
,因此,要將該方法附加到 String
的實例中。在清單 19 中,創建 String
實例 myString
,并調用 laugh()
方法,該方法將打印“haha, I'm holding an instance of Andy”。
|
雖然 laugh()
方法完全無用,但確實說明了幾個關鍵點:
- Nice 使您能夠輕松地將新的行為附加到對象中。
- Nice 允許將該行為附加到任何事物,其中包括您無法訪問其源代碼的標準類。
- Nice 使您可以將行為添加到
final
對象。
高級多方法
將有用的行為添加到對象比使用無用的行為更有意義,所以讓我們將參數類和多方法的知識提高到另一個級別。在清單 20 中,您將看到當將 join()
方法添加到 java.util.Collection
接口時會發生什么。 join()
方法在敏捷語言之間非常通用;它只將預期的 String
附加到集合的所有元素中,以創建大的 String
。
|
清單 20 說明了如何使用 Nice 的多方法功能將 join()
方法附加到任何類型的 Collection
中。在本例中, join
方法包含可選參數 String
,使用該參數,可以連接在其上調用 join()
方法的 Collection
實例的元素。
清單 21 顯示,使用新的 join()
方法非常容易。它只傳遞預期的 join String
或使用默認值。這項功能就像 AOP 中的靜態橫切(static crosscutting),但是 Nice 版本看起來 容易得多!
|
抽象接口
除了多方法之外,Nice 還提供了將其他行為附加到對象的第二種方法。 抽象接口與普通 Java 接口相似,但要靈活得多。抽象接口最好的地方是可以由任何對象實現,即使定義了接口之后也可以。在這點上,抽象接口功能與 AOP 中的靜態橫切非常相像。
在清單 22 中,可以開始了解抽象接口如何工作。我先創建類型 TasteTest
的新抽象接口,它包含一個方法 taste()
。然后使 Mocha
和 Latte
類實現這個新類型。
|
在 Latte
的實例上使用新的功能非常簡單,如清單 23 中所示。只需調用 taste
方法即可!
|
雖然使用多方法和抽象接口的實例相當簡短,但它們確實說明了使用 Nice 可以達到的表達能力級別。實際上,一些人認為,由 Nice 語法的簡易性所支持的 Nice 的表達能力能夠與 Java 編程中的 AOP 相媲美。
好用的枚舉類型
正如前面所說的,Nice 合并了 Java 5.0 中的一些功能,實際上,它現在允許在任何 Java 平臺上使用這些功能。其中一項功能是枚舉類型。與參數類相同,枚舉類型在編譯時而不是在運行時幫助您進行 bug 檢測。
為了了解枚舉類型是如何工作的,我將使用一個通用開發實例。常量和關鍵字通常以 static final
字段形式放置在接口和類中。然后,其他類可以引用這些字段,而不是引用本地變量,以限制發生的變化。定義常量的代碼與清單 24 中的相似。
|
清單 25 顯示了使用中的常量類型的典型示例。如果仔細查看該代碼,您還會發現有損其有效性的地方。
清單 25. Java 常量的限制
|
清單 25 中代碼的問題在于:惡意的或無知的客戶機可以使用制定的限制之外的值來調用 brew()
方法。例如,如果使用 48
調用該方法,代碼的編譯將很完美。但不幸的是,當運行代碼時,仍然必須處理這個問題。
使用 枚舉而不是使用常量會使代碼更加安全。實質上,枚舉將強制編譯器確保使用的值在定義的限制內。例如,在清單 26 中,可以看到為 CoffeeBeanType
定義枚舉時會發生什么,同時還定義了類型 ICoffee
的接口和兩個實現: Latte
和 Mocha
。
正如您所看到的,這些 ICoffee
類型定義返回枚舉實例的 getType()
方法。您還可以看到的是,如果定義 CoffeeMachine
類并包含枚舉實例,而不是定義如清單 25 中所示的包含 int
的方法,那么將會發生什么。
|
在清單 27 中,當對 CoffeeMachine
實例調用 brew()
方法時,要使用枚舉類型。注意在 Nice 中枚舉是如何隱含 value
字段的。在 ESPRESSOROAST
中,該值為創建時傳遞的 String
: espresso
。
|
高級集合處理
Nice 是一種避開轉換對象概念的強類型語言,所以必須將 Nice 中的所有集合都參數化。例如,下面的行通常將使用 Java 語言進行編譯,但在 Nice 中,集合必須是更強類型的。
|
如果因為某種原因,需要將 Nice 集合呈現得更像 Java,那么可以僅使用 java.lang.Object
將該集合參數化,如下所示。
|
多方法和集合
與其他一些語言類似,Nice 使用多方法向標準集合添加了其他許多方法。由于 Nice 的集合行為支持塊語法,所以這些行為與 Groovy 和 Ruby 中的類似。雖然這些代碼塊其實只是匿名方法,不像真實的閉包(true closures)那樣強大,但它們實際上更方便。
Nice 在集合中提供了靈活的類迭代器(iterator-like)方法,該方法名為 foreach()
,如清單 28 中所示。注意 foreach()
方法是如何擁有由 =>
表示的代碼塊的。在本例中,該代碼塊只打印 i
將包含的值。
|
Nice 還通過一些非常好的方法增強了 Java 語言的 Set
接口,如清單 29 中所示。
|
正如在上面的代碼中可以看到的,Nice 提供了 intersection()
方法,該方法將查找兩個單獨 Set
中的共同元素。 union()
方法合并這兩個 Set
,而 difference()
方法查找這兩個 Set
之間的差異。最后, disjunction()
方法合并了這兩個 Set
,同時還刪除它們的共同元素。
其他功能
在要結束對 Nice 的介紹時,我將講述一下它的三個比較引人注意的便利功能:值分發、高級 for
循環和范圍,以及更隨意的 String
使用。正如在前面一小節中介紹的那樣,您將注意到每項功能都提高了代碼的表達能力和模塊化,同時還增強了代碼安全。
值分發方法
值分發方法被用來確保實際調用或分發到哪個方法的運行時決策不僅由參數的類型確定,還由參數的實際 值確定。該方法有助于避免代碼包含一系列 if
/ else
子句或 switch
語句。
在清單 30 中可以看到該方法是如何工作的。我先為音樂類型定義了一個 enum
。然后創建了一系列有效模擬 switch
語句的值分發方法。如果這時傳遞 Genre
值 Celtic, variationName()
方法將返回“Irish”,如下所示。
|
如下面清單 31 中所示,當傳遞 Genre enum
值 Folk
時,由此得到的 variation
變量將被設為 Acoustic
。
|
增強的 for 循環和范圍
這時,您可能已經注意到,Nice 支持 for
循環標準的簡略概念,這與新的 Java 5 非常像。簡單的 for
循環構造在敏捷語言之間非常通用,奇怪的是,Java 語言居然用了這么長時間來引入它!
如清單 32 中所示,Nice 使您可以通過 int
的集合輕松進行迭代,而無需使用 Java 語言常用的 Iterator
接口。還要注意的是,Nice 支持自動置入,與 Groovy 很像(請參閱 參考資料,以獲得關于 Groovy 的 alt.lang.jre期刊)。
|
清單 33 說明了 Nice 中的范圍功能。Nice 僅支持包括的范圍;因此,在下面的代碼中,將打印從 1 到(且包括)20 的數字。
清單 33. Nice 中的包括范圍
|
隨意字符串
Nice 提供了普通 Java String
,但放寬了與這些字符串在 Java 語言中的使用相關聯的一些限制。如果曾使用過 Python,您將認識 Nice 的多行字符串常量。在清單 34 中,可以看到使用 """
語法創建多行字符串非常容易。您還將注意到 '
如何自動轉義。
|
Nice 還通過放寬 Java 語言標準 +
語法的限制,使字符串連接更加容易。因此,可以調用 println()
并刪除相關聯的串聯,如上面清單 34 中所示。
結束語
Java 語言的 5.0 版中包含本月 alt.lang.jre期刊中講述的許多最好的功能。不過,并不是每個人都可以立即使用 Java 5,其余一些人可以使用 Nice。除了使您可以實際地在任何 JVM 版本上使用參數類、多方法、契約式設計和其他許多便利功能之外,Nice 還使您初步了解了在所有開發平臺上的表達能力和敏捷度的優點。正如這里所展示的,當提到在 Java 平臺上構造更安全的、更模塊化的代碼時,Nice 是很好的選擇。請參閱 參考資料部分,以了解關于 Nice 的更多信息,同時還請耐心等待下月的 alt.lang.jre期刊,它將介紹 Rhino。