了解為何這門具有很強(qiáng)表達(dá)能力的語言最適合 Java 平臺
Andrew Glover
CTO, Vanward Technologies
2004 年 10 月 06 日
Nice 是可兼容 JRE 的、面向?qū)ο蟮恼Z言,為 Java 平臺提供了極強(qiáng)的語言表達(dá)能力。Nice 還允許在任何 Java 虛擬機(jī)上實(shí)現(xiàn)許多 Java 5 中的裁邊功能。本文是 alt.lang.jre系列的第 4 期,在這篇文章中,固定撰稿人,在各方面都很“Nice”的 Andrew Glover 將向您說明 Nice 的一些最令人激動(dòng)的功能。
Nice 是面向?qū)ο蟮摹⒖杉嫒?JRE 的編程語言,重要特點(diǎn)是模塊化、表達(dá)能力和安全。與純粹面向?qū)ο蟮?Java 語言不同,Nice 合并了一些實(shí)用靈活的開發(fā)技術(shù),其中包括面向方面編程中的一些技術(shù)。與許多較新的開發(fā)語言一樣,Nice 的優(yōu)點(diǎn)是通過改進(jìn)以前的語言(包括 Java 語言)的缺點(diǎn)而來的。而且,Nice 不但提供了 Java 1.5 中的許多功能,而且使這些功能在任何 JVM 中都可以使用。
關(guān)于本系列文章 雖然 alt.lang.jre 系列的大多數(shù)讀者都熟悉 Java 語言,以及它如何在跨平臺的虛擬機(jī)上運(yùn)行,但可能只有少數(shù)人知道 Java Runtime Environment 可以具有 Java 語言之外的語言。這里提到的大多數(shù)語言都是開放源代碼,可以免費(fèi)使用,只有少數(shù)是必須購買的商業(yè)產(chǎn)品。JRE 支持本系列文章中介紹的所有語言,作者相信這些語言可以增強(qiáng) Java 平臺的動(dòng)態(tài)性和靈活性。 |
在 alt.lang.jre專欄的第 4 期中,我將向您介紹 Nice 的一些最引人注意的和最有用的功能,其中包括參數(shù)類、契約式設(shè)計(jì)構(gòu)建、多方法等。
Nice 入門
在 Java 語言中,功能的中心單元包含在類中。然而,Nice 將這個(gè)概念上升到了包的某個(gè)級別上。Nice 允許在以 .nice
后綴結(jié)尾的文件中定義多個(gè)類、接口和方法。一旦這樣定義了文件,該文件通常就會(huì)變?yōu)橐粋€(gè)包。Nice 的編譯器 nicec
被用來編譯 .nice
文件,該編譯器也可以作為 Ant 任務(wù)或 Eclipse 插件使用(請參閱 參考資料)。
運(yùn)行 Nice 應(yīng)用程序需要定義 main()
方法。就像在 Java 語言中定義 main()
方法那樣定義該方法。但是 Nice 的 main
方法與 Java 語言的不同,因?yàn)樵摲椒ㄊ窃诎卸x的,不是在類中定義的。因此,該方法可以變?yōu)殪o態(tài)鉤(static hook)來跳過 Nice 應(yīng)用程序,與 Java 類中的 static main()
方法很像,它允許從命令行運(yùn)行類。
在清單 1 中,可以看到 Nice 的 main()
方法是如何工作的,清單 1 是驗(yàn)證一些基本匹配知識的簡單應(yīng)用程序。注意,Nice 支持 assert
關(guān)鍵字,即使是在 1.4 之前的 VM 上運(yùn)行也是如此。
清單 1. 簡單 Nice 程序
void main(String[] args){
assert 4 + 4 == 8;
}
|
正如一會(huì)將看到的,在 Nice 中,可以在類之外定義方法。在閱讀本文之后,您就會(huì)對在包級別上定義方法的作用有更加清楚的認(rèn)識。
Nice 顯示更加安全
代碼安全是 Nice 可以引入開發(fā)工具箱的最強(qiáng)大的功能之一。當(dāng)處理兩個(gè)非常普遍的 Java 異常時(shí),Nice 特別有效。這兩個(gè)異常是: ClassCastException
和 NullPointerException
。
這兩個(gè)異常長久以來一直是從事 Java 平臺工作的開發(fā)人員努力攻克的難題。Java 5.0 合并了常規(guī)類型,并以合并類型作為表達(dá) ClassCastExceptions
的有效方式,但是這項(xiàng)更改對許多仍舊使用 Java 1.3 的公司不起作用。而另一方面,人們在考慮了 ClassCastException
和 NullPointerException
的情況下開發(fā)了 Nice。因此,Nice 語言還支持參數(shù)類和可選類型這兩項(xiàng)功能,它們對阻止應(yīng)用程序拋出異常大有幫助。而且,通過 Nice,現(xiàn)在可以在任何 Java 平臺 1.2 或更高版本中使用這些功能。
參數(shù)類
在 Java 語言中,所有集合都有最少的共同類型,它就是 Object
。所以,Java 開發(fā)人員每次檢索元素時(shí),都必須轉(zhuǎn)換類型,在較大的程序中,這會(huì)是一種負(fù)擔(dān)。為了說明這個(gè)問題,我將向您展示 Java 語言是如何處理該類型,然后再向您展示 Nice 的參數(shù)類是如何簡化這個(gè)問題的。
清單 2 顯示了 IStack
接口,用它來表示用 Java 語言定義的堆棧數(shù)據(jù)結(jié)構(gòu)。
清單 2. 用 Java 語言定義的 IStack 接口
public interface IStack {
int size();
boolean isEmpty();
Object pop();
Object peek();
void push(Object obj);
}
|
IStack
的 Java 實(shí)現(xiàn)非常簡單,但仍可用作堆棧數(shù)據(jù)結(jié)構(gòu),如清單 3 中所示。
清單 3. IStack 實(shí)現(xiàn)
import java.util.ArrayList;
import java.util.List;
public class SimpleStack implements IStack {
private List items;
public SimpleStack(){
this.items = new ArrayList();
}
public int size(){
return this.items.size();
}
public boolean isEmpty(){
return this.items.isEmpty();
}
public Object pop(){
return this.items.remove(this.items.size() - 1);
}
public Object peek(){
return this.items.get(this.items.size() - 1);
}
public void push(Object obj){
this.items.add(obj);
}
}
|
雖然非常有效,但是,只要使用這個(gè)堆棧實(shí)現(xiàn),就需要進(jìn)行單調(diào)乏味的類型轉(zhuǎn)換,如清單 4 中所示。
清單 4. 顯示非常耗時(shí)的類型轉(zhuǎn)換的 JUnit 示例
import junit.framework.TestCase;
import org.age.nice.examples.stack.IStack;
import org.age.nice.examples.stack.SimpleStack;
public class SimpleStackUseTest extends TestCase {
private IStack stack;
public void testSimpleIntegerPops(){
this.stack.push(new Integer(1));
this.stack.push(new Integer(2));
Integer two = (Integer)this.stack.pop();
TestCase.assertEquals("value should be 2", 2,
two.intValue());
}
public void testSimpleIntegerPopsWillNotCompile(){
this.stack.push(new Integer(1));
this.stack.push(new Integer(2));
//Integer two = this.stack.pop();
//TestCase.assertEquals("value should be 2", 2, two.intValue());
}
public void testPopClassCastException(){
this.stack.push(new Integer(1));
this.stack.push(new Double(2.0));
try{
Integer two = (Integer)this.stack.pop();
TestCase.fail("top item was successfully cast to Integer!");
}catch(ClassCastException e){
//ignore expected behavior
}
}
protected void setUp() throws Exception {
this.stack = new SimpleStack();
}
}
|
在清單 4 中, testSimpleIntegerPops()
方法顯示了為何必須將 pop
操作的結(jié)果轉(zhuǎn)換為 Integer
。如果未能執(zhí)行轉(zhuǎn)換,則會(huì)產(chǎn)生 ClassCastException
,如 testPopClassCastException()
方法中所述。仔細(xì)查看 testSimpleIntegerPopsWillNotCompile
方法中的注釋行。它們沒有使用普通 Java 代碼進(jìn)行編譯;但該代碼看起來不 好嗎?
更好的集合處理
在其他功能中, 參數(shù)類或模板(如果從 C++ 后臺開始)通常用于簡化集合處理。參數(shù)類允許定義集合包含單一的、精確的類型。
要用 Nice 定義普通的 Stack
實(shí)現(xiàn),可以在定義類以及預(yù)期參數(shù)和返回類型時(shí)使用 <Type>
語法。在清單 5 中,可以看到如何通過使用 Nice 的 <Type>
語法的 SimpleStack
類來重新定義和簡化堆棧數(shù)據(jù)結(jié)構(gòu)。
清單 5. Nice 參數(shù)堆棧
class SimpleStack<Type> {
List<Type> items = new ArrayList();
void push(Type t) {
items.add(t);
}
Type pop(){
return items.removeAt(items.size() - 1);
}
Type peek(){
return items.get(items.size() - 1);
}
boolean isEmpty(){
return items.isEmpty();
}
int size(){
return items.size();
}
}
|
現(xiàn)在,我們定義了 SimpleStack
類參數(shù)化的 Nice 來存放特定參數(shù)類型,這些類型是在 編譯時(shí)間定義的,而不像在 Java 語言中那樣,是在運(yùn)行時(shí)定義的。注意清單 5 中的 pop()
和 peek()
方法是如何返回 Type
的,以及 push()
方法是如何將 Type
用作參數(shù)。該 Type
與內(nèi)部 items
集合將包含的 對象類型是相匹配的。
清單 6 顯示了在 Nice 中如何使用參數(shù)堆棧。注意,我已經(jīng)創(chuàng)建了只能存放 String
類型的對象的 SimpleStack
實(shí)例。注意在 Nice 中為何不對最后一行進(jìn)行編譯!這個(gè)代碼行試圖 push
一個(gè) Integer
到參數(shù)化堆棧中。
清單 6. 在 Nice 中使用參數(shù)堆棧
void main(String[] args){
SimpleStack<String> stk = new SimpleStack();
stk.push("Groovy");
stk.push("Ruby");
assert stk.pop() == "Ruby";
assert stk.peek() == "Groovy";
//following line won't compile!
//stk.push(new Integer(1));
}
|
可選類型
NullPointerException
可能是所有 Java 開發(fā)人員最熟悉的異常。實(shí)際上,空指針非常麻煩,甚至是最簡單的 Java 編譯器都可以將對象標(biāo)記為尚未初始化。不幸的是,您可能不時(shí)地發(fā)現(xiàn),那些警告并沒有捕獲 null
的每次可能出現(xiàn)。
為了與其標(biāo)準(zhǔn)安全屬性一致,Nice 提供了 可選類型概念。因?yàn)榭蛇x類型是 API 的作者使用 Java 語言定義的,常常難以確定,所以 Nice 為這些類型添加了問號( ?
)作為前綴。
如果您發(fā)現(xiàn)自己經(jīng)常開發(fā)使用大量參數(shù)的 API,那么可用選項(xiàng)將特別有用。確定不同的參數(shù)是可選的很可能是因?yàn)?JavaDoc
注釋就是這樣指定這些參數(shù)的,或者是因?yàn)槟鷤鬟f了 null
并指出其可以使用。
使用 Nice,可以僅在參數(shù)之前添加 ?
來表明變量 可能為 null
;相反地,沒有問號的變量不能直接設(shè)為 null
。在清單 7 中可以看到這是如何工作的,在該清單中,我在 Dog
類中定義了方法 walk()
。這表明類型 Location
的第二個(gè)參數(shù)可能為 null
。
清單 7. 在 Nice 中定義可選類型
class Leash{
int length;
}
class Location{
String place;
}
class Dog{
String name;
void walk(Leash leash, ?Location location){
var place = (location == null)?
" no where " : " to " location.place;
println("walking " name " with a leash " leash.length " inches long"
//won't compile-> location.place
place);
}
}
|
注意清單 7 中的 walk()
方法為何 必須說明 location
可以為 null
。在 println()
方法中,編譯器將不允許代碼實(shí)際引用 location.place
。
清單 8 例示了可選類型的作用,本例中將使用清單 7 中定義的 walk()
方法。注意,現(xiàn)在可以合法傳遞 null
,還可以合法傳遞 location
的有效值。
清單 8. Nice 中可選類型的示例
Leash lsh = new Leash(length:35);
Dog mollie = new Dog(name:"Mollie");
mollie.walk(lsh, null);
Location loc = new Location(place:"The Coffee Shop");
mollie.walk(leash:lsh, location:loc);
|
不要將空功能語法(null capability syntax)與 可選參數(shù)混淆,它們是完全不同的。
命名和可選參數(shù)
正如您在清單 8 中可能注意到的,在調(diào)用 walk()
方法時(shí),Nice 允許指定參數(shù)。當(dāng)我在 Dog
實(shí)例中調(diào)用 walk()
方法時(shí),我明確命名了參數(shù)。例如,將類型 Leash
的 lsh
變量設(shè)為第一個(gè)參數(shù) leash
。
Nice 還允許在構(gòu)造函數(shù)中指定參數(shù),與在 Groovy 和 Jython 等語言中的做法非常類似。在清單 8 中,當(dāng)創(chuàng)建新的 Dog
和 Leash
的實(shí)例時(shí),我在構(gòu)造函數(shù)中分別明確設(shè)置了每個(gè)實(shí)例的屬性、名稱和長度。
命名參數(shù)允許您以任何希望的順序傳遞這些參數(shù)。例如,在清單 9 中,這兩個(gè)調(diào)用基本相同;因?yàn)槲颐藚?shù),傳遞這些參數(shù)的順序沒有影響。
清單 9. Nice 中可選類型的進(jìn)一步說明
mollie.walk(leash:lsh, location:loc);
//same behavior from walk method
mollie.walk(location:loc, leash:lsh);
|
可選參數(shù)甚至比可選類型更有用。它們實(shí)際上可以替代可選類型。清單 10 顯示了如何使用可選參數(shù)重新定義 walk()
方法。
清單 10. Nice 中的可選參數(shù)
void walkAgain(Leash leash, Location location=new Location(place:"nowhere")){
println("walking (again) " name " with a leash " leash.length
" inches long to " location.place);
}
|
該代碼將第二個(gè)參數(shù) location
定義為 可選。調(diào)用 walk()
方法時(shí),不必傳送該參數(shù)。如果未傳送任何值,那么將使用默認(rèn)值。在本例中,默認(rèn)值是 Location
,其中 place
等于 nowhere。
與 清單 7 中所做相同,可選參數(shù)使您不必進(jìn)行防御性地編程。但與清單 7 中的 walk()
方法不同的是,編寫清單 10 中的 walkAgain()
方法需要考慮遇到 null
值的可能性。
對于可選參數(shù),最后要注意的一點(diǎn)是,可以使用其他值覆蓋這些參數(shù)。如清單 11 中所示,可以選擇一個(gè) 或兩個(gè)參數(shù)來調(diào)用 walkAgain
。
清單 11. 覆蓋 Nice 中的可選參數(shù)
mollie.walkAgain(lsh);
Location locBY = new Location(place:"the backyard");
mollie.walkAgain(lsh, locBY);
|
契約式設(shè)計(jì)
契約式設(shè)計(jì)(DBC)是一項(xiàng)技術(shù),它通過在每個(gè)組件的界面中明確說明該組件的預(yù)期功能和客戶機(jī)的異常,來確保系統(tǒng)中的所有組件執(zhí)行它們將進(jìn)行的操作。Eiffel(請參閱 參考資料)是一種使用 DBC 的流行語言。許多語言都已經(jīng)合并了契約式設(shè)計(jì)技術(shù),其中包括 Java 1.4,該技術(shù)引入了斷言的使用。Nice 使用關(guān)鍵字 requires
和 ensures
來合并程序和斷言中的契約信息,以便在執(zhí)行過程中確認(rèn)該程序的狀態(tài)。另外,Nice 甚至支持 1.4 之前的 JVM 使用 assert
關(guān)鍵字。
requires
和 ensures
關(guān)鍵字的作用與前條件和后條件相似,它們允許定義預(yù)期的方法將遵循和保證的條件。如果條件得不到滿足,將會(huì)在運(yùn)行時(shí)生成斷言異常。條件和斷言的組合已經(jīng)使許多應(yīng)用程序免于陷入由邏輯錯(cuò)誤引起的深淵中。我將在下面的實(shí)例中說明如何一起使用這兩種機(jī)制。
條件性使用
requires
子句說明了某個(gè)方法的客戶機(jī)必須滿足的要求。如果未滿足要求,那么將放棄該方法,并生成斷言異常。 ensures
子句將在方法的客戶機(jī)端起作用。該子句是將方法提交到其相關(guān)聯(lián)的調(diào)用者的保證。
清單 12 定義了包含 brew()
方法的 CoffeeMachine
類。在 brew()
的定義中,要求客戶機(jī) 必須傳遞 Coffee
實(shí)例,其中 beanAge
屬性小于 10。否則,將生成與使用子句“Beans are too old to brew”一致的斷言異常。而且,我還保證方法(本例中為 CoffeeCup
的實(shí)例)的 result
將具有大于 155 的 temp
屬性。
清單 12. DBC 確保理想 brew
class CoffeeMachine{
CoffeeCup brew(Coffee cfe)
requires cfe.beanAge < 10 : "Beans are too old to brew"
ensures result.temp > 155 : "Coffee isn't hot enough to serve" {
return new CoffeeCup(coffee: cfe, temp:160, isFull:true);
}
}
class Coffee{
int beanAge;
}
class CoffeeCup{
int temp;
boolean isFull;
Coffee coffee;
}
|
清單 13 顯示了新的 CoffeeMachine
實(shí)例和 Coffee
的新實(shí)例。注意,在本例中,已經(jīng)將 Coffee
的 beanAge
屬性設(shè)為 15,該值大于 10,因此不滿足 brew
的契約。
清單 13. 契約違反
CoffeeMachine machine = new CoffeeMachine();
Coffee cfe = new Coffee(beanAge:15);
CoffeeCup cup = machine.brew(cfe);
|
清單 14 說明了在清單 13 中調(diào)用 brew()
時(shí)生成的異常堆棧。正如您可以看到的,這里提供了定制消息“Beans are too old to brew”來幫助調(diào)試。
清單 14. These beans are too old to brew!
Exception in thread "main" nice.lang.AssertionFailed: Beans are too old to brew
at test.dispatch.brew(MoreCoffee.nice:6)
at test.fun.main(HelloWorld.nice:111)
at test.dispatch.main(MoreCoffee.nice:0)
|
默認(rèn)情況下,不啟用 Nice 斷言,所以必須開啟這些斷言。有關(guān) JVM 的指令,請參閱 Nice 的文檔。
重訪堆棧
在基本了解了 Nice 如何實(shí)現(xiàn)前條件和后條件之后,讓我們看一下將這些技術(shù)應(yīng)用于前面的 IStack
示例時(shí)會(huì)發(fā)生什么。在清單 15 中,用 Nice 定義了 IStack
接口,并添加了不同的 ensures
和 requires
子句。
清單 15. 添加了條件的 IStack 接口
interface IStack<T>{
int size() ensures result >= 0 : "size can not be less than one";
void push(T t) ensures size(this) > 0 : "pushing an item should increase the size";
boolean isEmpty() ensures result == (size(this) == 0) : "if size is zero, result should be false";
T pop() requires !isEmpty(this) : "Can not pop an empty stack";
T peek() requires !isEmpty(this) : "Can not pop an empty stack";
}
|
在清單 16 中,實(shí)現(xiàn)了 IStack
接口。注意,Nice 為定義方法本體提供了捷徑:只使用 =
語法。還要注意下例中 Nice 的 override
語法。
清單 16. 新的改進(jìn)的堆棧
class DBCStack<T> implements IStack{
ArrayList<T> contents = new ArrayList();
override void push(T t) = contents.add(t);
override T peek() = contents.get(contents.size() - 1);
override T pop() = contents.removeAt(contents.size() - 1);
override boolean isEmpty() = contents.size() == 0;
override int size() = contents.size();
}
|
在很大程度上,可以像以前那樣使用新的 DBCStack
。不過,如果試圖違反 IStack
契約的條款,將會(huì)引起斷言異常。例如,在清單 17 中,可以看到當(dāng)試圖對已經(jīng)確保兩個(gè)項(xiàng)安全的堆棧中推入第三個(gè)項(xiàng)時(shí)會(huì)發(fā)生什么。第三個(gè) pop()
的調(diào)用導(dǎo)致先條件 requires !isEmpty(this)
失敗。因此,將生成 AssertionFailed
異常以及定制消息:“cannot pop an empty stack”。
清單 17. 測試新堆棧的限制
let IStack<Dog> dbcStack = new DBCStack();
dbcStack.push(new Dog(name:"Stella"));
dbcStack.push(new Dog(name:"Mollie"));
println(dbcStack.pop().name);//mollie
println(dbcStack.pop().name);//stella
// throws assertion error -> println(dbcStack.pop().name);
// Exception in thread "main" nice.lang.AssertionFailed: Can not pop an empty stack
/ at test.dispatch.pop(StackImpl.nice:10)
// at test.fun.main(HelloWorld.nice:129)
// at test.dispatch.main(StackImpl.nice:0)
|
多方法的好處
Nice 最引人注意的獨(dú)特功能之一是 多方法,或在特定類定義之外定義類實(shí)例方法的能力。該方法獨(dú)自創(chuàng)建了許多擴(kuò)展,這些擴(kuò)展可與面向方面編程(AOP)的一些比較令人激動(dòng)的原則相媲美。
方法的語法相當(dāng)簡單,因?yàn)榈谝粋€(gè)參數(shù)是方法應(yīng)該附加的類型。剩余的參數(shù)則成為實(shí)例方法的標(biāo)準(zhǔn)參數(shù)。為了舉例說明,在清單 18 中,可以創(chuàng)建簡單的、無意義的方法,并將其附加到 java.lang.String
的實(shí)例。
清單 18. Nice 中的多方法
void laugh(String str){
println("haha, I'm holding an instance of " str);
}
|
在該代碼中,當(dāng)調(diào)用 laugh()
方法時(shí),它將只打印 String
。因?yàn)?laugh()
方法的第一個(gè)參數(shù)的類型為 String
,因此,要將該方法附加到 String
的實(shí)例中。在清單 19 中,創(chuàng)建 String
實(shí)例 myString
,并調(diào)用 laugh()
方法,該方法將打印“haha, I'm holding an instance of Andy”。
清單 19. 使用 Nice 中的多方法
let myString = new String("Andy");
myString.laugh();
|
雖然 laugh()
方法完全無用,但確實(shí)說明了幾個(gè)關(guān)鍵點(diǎn):
- Nice 使您能夠輕松地將新的行為附加到對象中。
- Nice 允許將該行為附加到任何事物,其中包括您無法訪問其源代碼的標(biāo)準(zhǔn)類。
- Nice 使您可以將行為添加到
final
對象。
高級多方法
將有用的行為添加到對象比使用無用的行為更有意義,所以讓我們將參數(shù)類和多方法的知識提高到另一個(gè)級別。在清單 20 中,您將看到當(dāng)將 join()
方法添加到 java.util.Collection
接口時(shí)會(huì)發(fā)生什么。 join()
方法在敏捷語言之間非常通用;它只將預(yù)期的 String
附加到集合的所有元素中,以創(chuàng)建大的 String
。
清單 20. 將 join 方法添加到 Collection 接口中
/**
* multi method, adds a join call to a collection.
* @return a string like 1-2-3-4.
*/
<T> String join (Collection<T> collection, String value = " "){
StringBuffer buff = new StringBuffer();
let size = collection.size();
var x = 0;
for (T elem : collection){
buff.append(elem);
if(++x < size){
buff.append(value);
}
}
return buff.toString();
}
|
清單 20 說明了如何使用 Nice 的多方法功能將 join()
方法附加到任何類型的 Collection
中。在本例中, join
方法包含可選參數(shù) String
,使用該參數(shù),可以連接在其上調(diào)用 join()
方法的 Collection
實(shí)例的元素。
清單 21 顯示,使用新的 join()
方法非常容易。它只傳遞預(yù)期的 join String
或使用默認(rèn)值。這項(xiàng)功能就像 AOP 中的靜態(tài)橫切(static crosscutting),但是 Nice 版本看起來 容易得多!
清單 21. 非常好的新連接方法!
Collection<int> nColl = new ArrayList();
nColl.add(1);
nColl.add(3);
nColl.add(3);
println(nColl.join("**")); //prints 1**3**3
println(nColl.join()); //prints 1 3 3
|
抽象接口
除了多方法之外,Nice 還提供了將其他行為附加到對象的第二種方法。 抽象接口與普通 Java 接口相似,但要靈活得多。抽象接口最好的地方是可以由任何對象實(shí)現(xiàn),即使定義了接口之后也可以。在這點(diǎn)上,抽象接口功能與 AOP 中的靜態(tài)橫切非常相像。
在清單 22 中,可以開始了解抽象接口如何工作。我先創(chuàng)建類型 TasteTest
的新抽象接口,它包含一個(gè)方法 taste()
。然后使 Mocha
和 Latte
類實(shí)現(xiàn)這個(gè)新類型。
清單 22. Nice 中的抽象接口
package test;
class Latte {
getPrice() = new BigDecimal(2.50);
}
class Mocha {
getPrice() = new BigDecimal(4.30);
}
abstract interface TasteTest{
void taste();
}
class test.Mocha implements TasteTest;
class test.Latte implements TasteTest;
taste(test.Mocha mcha) = println("Ohh this is good...");
taste(Latte lte) = println("Waking me up it's soooo good.");
|
在 Latte
的實(shí)例上使用新的功能非常簡單,如清單 23 中所示。只需調(diào)用 taste
方法即可!
清單 23. Latte 喚醒調(diào)用
let coffee = new Latte();
coffee.taste(); //prints Waking me up it's soooo good.
|
雖然使用多方法和抽象接口的實(shí)例相當(dāng)簡短,但它們確實(shí)說明了使用 Nice 可以達(dá)到的表達(dá)能力級別。實(shí)際上,一些人認(rèn)為,由 Nice 語法的簡易性所支持的 Nice 的表達(dá)能力能夠與 Java 編程中的 AOP 相媲美。
好用的枚舉類型
正如前面所說的,Nice 合并了 Java 5.0 中的一些功能,實(shí)際上,它現(xiàn)在允許在任何 Java 平臺上使用這些功能。其中一項(xiàng)功能是枚舉類型。與參數(shù)類相同,枚舉類型在編譯時(shí)而不是在運(yùn)行時(shí)幫助您進(jìn)行 bug 檢測。
為了了解枚舉類型是如何工作的,我將使用一個(gè)通用開發(fā)實(shí)例。常量和關(guān)鍵字通常以 static final
字段形式放置在接口和類中。然后,其他類可以引用這些字段,而不是引用本地變量,以限制發(fā)生的變化。定義常量的代碼與清單 24 中的相似。
清單 24. Java 常量示例
public class CoffeeBeans {
public static final int ESPRESSOROAST = 1;
public static final int KONA = 2;
public static final int FRENCHROAST = 3;
public static final int MOCHA = 4;
}
|
清單 25 顯示了使用中的常量類型的典型示例。如果仔細(xì)查看該代碼,您還會(huì)發(fā)現(xiàn)有損其有效性的地方。
清單 25. Java 常量的限制
public static void brew(int coffeeType){
if(coffeeType == CoffeeBeans.ESPRESSOROAST){
System.out.println("brewing espresso!");
}
//other if/else clauses....
}
|
清單 25 中代碼的問題在于:惡意的或無知的客戶機(jī)可以使用制定的限制之外的值來調(diào)用 brew()
方法。例如,如果使用 48
調(diào)用該方法,代碼的編譯將很完美。但不幸的是,當(dāng)運(yùn)行代碼時(shí),仍然必須處理這個(gè)問題。
使用 枚舉而不是使用常量會(huì)使代碼更加安全。實(shí)質(zhì)上,枚舉將強(qiáng)制編譯器確保使用的值在定義的限制內(nèi)。例如,在清單 26 中,可以看到為 CoffeeBeanType
定義枚舉時(shí)會(huì)發(fā)生什么,同時(shí)還定義了類型 ICoffee
的接口和兩個(gè)實(shí)現(xiàn): Latte
和 Mocha
。
正如您所看到的,這些 ICoffee
類型定義返回枚舉實(shí)例的 getType()
方法。您還可以看到的是,如果定義 CoffeeMachine
類并包含枚舉實(shí)例,而不是定義如清單 25 中所示的包含 int
的方法,那么將會(huì)發(fā)生什么。
清單 26. 在 Nice 中定義枚舉
enum CoffeeBeanType(String value){
ESPRESSOROAST("espresso"),
KONA("kona"),
FRENCHROAST("French Roast"),
MOCHA("Mocha Java")
}
interface ICoffee{
CoffeeBeanType getType();
BigDecimal getPrice();
}
class Latte implements ICoffee{
getType()= ESPRESSOROAST;
getPrice() = new BigDecimal(2.50);
}
class Mocha implements ICoffee{
getType()= FRENCHROAST;
getPrice() = new BigDecimal(4.30);
}
class CoffeeMachine{
}
void brew(CoffeeMachine machine, CoffeeBeanType type){
println("Brewing a coffee with " type.value " beans" );
}
|
在清單 27 中,當(dāng)對 CoffeeMachine
實(shí)例調(diào)用 brew()
方法時(shí),要使用枚舉類型。注意在 Nice 中枚舉是如何隱含 value
字段的。在 ESPRESSOROAST
中,該值為創(chuàng)建時(shí)傳遞的 String
: espresso
。
清單 27. 在 Nice 中使用枚舉
let cfe = new CoffeeMachine();
cfe.brew(ESPRESSOROAST);
//prints Brewing a coffee with espresso beans
let coffee = new Latte();
println("\n My latte cost me $" coffee.getPrice()
" and is brewed with " coffee.getType().value " beans");
|
高級集合處理
Nice 是一種避開轉(zhuǎn)換對象概念的強(qiáng)類型語言,所以必須將 Nice 中的所有集合都參數(shù)化。例如,下面的行通常將使用 Java 語言進(jìn)行編譯,但在 Nice 中,集合必須是更強(qiáng)類型的。
Collection noColl = new ArrayList(); //will not compile in Nice
|
如果因?yàn)槟撤N原因,需要將 Nice 集合呈現(xiàn)得更像 Java,那么可以僅使用 java.lang.Object
將該集合參數(shù)化,如下所示。
Collection<Object> collObj = new ArrayList();
|
多方法和集合
與其他一些語言類似,Nice 使用多方法向標(biāo)準(zhǔn)集合添加了其他許多方法。由于 Nice 的集合行為支持塊語法,所以這些行為與 Groovy 和 Ruby 中的類似。雖然這些代碼塊其實(shí)只是匿名方法,不像真實(shí)的閉包(true closures)那樣強(qiáng)大,但它們實(shí)際上更方便。
Nice 在集合中提供了靈活的類迭代器(iterator-like)方法,該方法名為 foreach()
,如清單 28 中所示。注意 foreach()
方法是如何擁有由 =>
表示的代碼塊的。在本例中,該代碼塊只打印 i
將包含的值。
清單 28. 集合中的 foreach 方法
Collection<Integer> coll = new ArrayList();
coll.add(new Integer(1));
coll.add(new Integer(2));
coll.add(new Integer(3));
coll.foreach(Integer i => {
println(i);
}); //prints 1 2 3
|
Nice 還通過一些非常好的方法增強(qiáng)了 Java 語言的 Set
接口,如清單 29 中所示。
清單 29. Nice 中非常好用的 Set 方法
Set<int> testSet = new HashSet();
Set<int> otherSet = new HashSet();
testSet.add(1);
testSet.add(2);
testSet.add(3);
otherSet.add(3);
otherSet.add(4);
otherSet.add(5);
Set<int> nSet = testSet.intersection(otherSet);
nSet.foreach(int i => println(i)); //prints 3
Set<int> uSet = testSet.union(otherSet);
uSet.foreach(int i => println(i)); //prints 1,2,3,4,5
Set<int> difSet = testSet.difference(otherSet);
difSet.foreach(int i => println(i)); //prints 1,2
Set<int> dSet = testSet.disjunction(otherSet);
dSet.foreach(int i => println(i)); //prints 1,2,4,5
|
正如在上面的代碼中可以看到的,Nice 提供了 intersection()
方法,該方法將查找兩個(gè)單獨(dú) Set
中的共同元素。 union()
方法合并這兩個(gè) Set
,而 difference()
方法查找這兩個(gè) Set
之間的差異。最后, disjunction()
方法合并了這兩個(gè) Set
,同時(shí)還刪除它們的共同元素。
其他功能
在要結(jié)束對 Nice 的介紹時(shí),我將講述一下它的三個(gè)比較引人注意的便利功能:值分發(fā)、高級 for
循環(huán)和范圍,以及更隨意的 String
使用。正如在前面一小節(jié)中介紹的那樣,您將注意到每項(xiàng)功能都提高了代碼的表達(dá)能力和模塊化,同時(shí)還增強(qiáng)了代碼安全。
值分發(fā)方法
值分發(fā)方法被用來確保實(shí)際調(diào)用或分發(fā)到哪個(gè)方法的運(yùn)行時(shí)決策不僅由參數(shù)的類型確定,還由參數(shù)的實(shí)際 值確定。該方法有助于避免代碼包含一系列 if
/ else
子句或 switch
語句。
在清單 30 中可以看到該方法是如何工作的。我先為音樂類型定義了一個(gè) enum
。然后創(chuàng)建了一系列有效模擬 switch
語句的值分發(fā)方法。如果這時(shí)傳遞 Genre
值 Celtic, variationName()
方法將返回“Irish”,如下所示。
清單 30. 詳細(xì)定義的值分發(fā)
enum Genre(String value) {
Celtic("Celtic"), Rock("Rock"), Folk("Folk"), Jazz("Jazz")
}
String variationName(Genre gre);
variationName(gre) = "No variations Available";
variationName(Celtic) = "Irish";
variationName(Folk) = "Acoustic";
variationName(Rock) = "Pop";
variationName(Jazz) = "Smooth Jazz";
|
如下面清單 31 中所示,當(dāng)傳遞 Genre enum
值 Folk
時(shí),由此得到的 variation
變量將被設(shè)為 Acoustic
。
清單 31. 在 Nice 中使用值分發(fā)
var variation = variationName(Folk);
println(variation); //prints Acoustic
|
增強(qiáng)的 for 循環(huán)和范圍
這時(shí),您可能已經(jīng)注意到,Nice 支持 for
循環(huán)標(biāo)準(zhǔn)的簡略概念,這與新的 Java 5 非常像。簡單的 for
循環(huán)構(gòu)造在敏捷語言之間非常通用,奇怪的是,Java 語言居然用了這么長時(shí)間來引入它!
如清單 32 中所示,Nice 使您可以通過 int
的集合輕松進(jìn)行迭代,而無需使用 Java 語言常用的 Iterator
接口。還要注意的是,Nice 支持自動(dòng)置入,與 Groovy 很像(請參閱 參考資料,以獲得關(guān)于 Groovy 的 alt.lang.jre期刊)。
清單 32. Nice 的 for 循環(huán)
Collection<int> iColl = new ArrayList();
iColl.add(11);
iColl.add(12);
for(int i : iColl){
println(i);
}
|
清單 33 說明了 Nice 中的范圍功能。Nice 僅支持包括的范圍;因此,在下面的代碼中,將打印從 1 到(且包括)20 的數(shù)字。
清單 33. Nice 中的包括范圍
for(int i : 1..20){
print(i);
}
|
隨意字符串
Nice 提供了普通 Java String
,但放寬了與這些字符串在 Java 語言中的使用相關(guān)聯(lián)的一些限制。如果曾使用過 Python,您將認(rèn)識 Nice 的多行字符串常量。在清單 34 中,可以看到使用 """
語法創(chuàng)建多行字符串非常容易。您還將注意到 '
如何自動(dòng)轉(zhuǎn)義。
清單 34. Nice 中的多行字符串
var poem = """
This is a multiline String.
Why wouldn't anyone want to make one?
""";
println(poem);
var line1 = "All roads lead to where you are";
var line2 = "Love don't need to find a way";
var concat = "Da da " line1 " da da " line2;
println(concat);
println("concat " line1 " with " line2);
|
Nice 還通過放寬 Java 語言標(biāo)準(zhǔn) +
語法的限制,使字符串連接更加容易。因此,可以調(diào)用 println()
并刪除相關(guān)聯(lián)的串聯(lián),如上面清單 34 中所示。
結(jié)束語
Java 語言的 5.0 版中包含本月 alt.lang.jre期刊中講述的許多最好的功能。不過,并不是每個(gè)人都可以立即使用 Java 5,其余一些人可以使用 Nice。除了使您可以實(shí)際地在任何 JVM 版本上使用參數(shù)類、多方法、契約式設(shè)計(jì)和其他許多便利功能之外,Nice 還使您初步了解了在所有開發(fā)平臺上的表達(dá)能力和敏捷度的優(yōu)點(diǎn)。正如這里所展示的,當(dāng)提到在 Java 平臺上構(gòu)造更安全的、更模塊化的代碼時(shí),Nice 是很好的選擇。請參閱 參考資料部分,以了解關(guān)于 Nice 的更多信息,同時(shí)還請耐心等待下月的 alt.lang.jre期刊,它將介紹 Rhino。