了解为何这门具有很强表达能力的语言最适合 Java 平台

级别: 初级

Andrew Glover
CTO, Vanward Technologies
2004 年 10 月 06 日

Column iconNice 是可兼容 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 系列的大多数读者都熟悉 Java 语言,以及它如何在跨平台的虚拟机上运行,但可能只有少数人知道 Java Runtime Environment 可以具有 Java 语言之外的语言。这里提到的大多数语言都是开放源代码,可以免费使用,只有少数是必须购买的商业产品。JRE 支持本系列文章中介绍的所有语言,作者相信这些语言可以增强 Java 平台的动态性和灵活性。

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 上运行也是如此。

清单 1. 简单 Nice 程序

void main(String[] args){ 
   assert 4 + 4 == 8;
}

正如一会将看到的,在 Nice 中,可以在类之外定义方法。在阅读本文之后,您就会对在包级别上定义方法的作用有更加清楚的认识。

Nice 显示更加安全
代码安全是 Nice 可以引入开发工具箱的最强大的功能之一。当处理两个非常普遍的 Java 异常时,Nice 特别有效。这两个异常是: ClassCastExceptionNullPointerException

这两个异常长久以来一直是从事 Java 平台工作的开发人员努力攻克的难题。Java 5.0 合并了常规类型,并以合并类型作为表达 ClassCastExceptions 的有效方式,但是这项更改对许多仍旧使用 Java 1.3 的公司不起作用。而另一方面,人们在考虑了 ClassCastExceptionNullPointerException 的情况下开发了 Nice。因此,Nice 语言还支持参数类和可选类型这两项功能,它们对阻止应用程序抛出异常大有帮助。而且,通过 Nice,现在可以在任何 Java 平台 1.2 或更高版本中使用这些功能。

参数类
在 Java 语言中,所有集合都有最少的共同类型,它就是 Object 。所以,Java 开发人员每次检索元素时,都必须转换类型,在较大的程序中,这会是一种负担。为了说明这个问题,我将向您展示 Java 语言是如何处理该类型,然后再向您展示 Nice 的参数类是如何简化这个问题的。

清单 2 显示了 IStack 接口,用它来表示用 Java 语言定义的堆栈数据结构。

清单 2. 用 Java 语言定义的 IStack 接口

public interface IStack {
  
  int size();
  
  boolean isEmpty();
 
  Object pop();
  
  Object peek();
  
  void push(Object obj);
}

IStack 的 Java 实现非常简单,但仍可用作堆栈数据结构,如清单 3 中所示。

清单 3. IStack 实现

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);
  }
}

虽然非常有效,但是,只要使用这个堆栈实现,就需要进行单调乏味的类型转换,如清单 4 中所示。

清单 4. 显示非常耗时的类型转换的 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 操作的结果转换为 Integer 。如果未能执行转换,则会产生 ClassCastException ,如 testPopClassCastException() 方法中所述。仔细查看 testSimpleIntegerPopsWillNotCompile 方法中的注释行。它们没有使用普通 Java 代码进行编译;但该代码看起来不 吗?

更好的集合处理
在其他功能中, 参数类或模板(如果从 C++ 后台开始)通常用于简化集合处理。参数类允许定义集合包含单一的、精确的类型。

要用 Nice 定义普通的 Stack 实现,可以在定义类以及预期参数和返回类型时使用 <Type> 语法。在清单 5 中,可以看到如何通过使用 Nice 的 <Type> 语法的 SimpleStack 类来重新定义和简化堆栈数据结构。

清单 5. Nice 参数堆栈

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();
 } 
}

现在,我们定义了 SimpleStack 类参数化的 Nice 来存放特定参数类型,这些类型是在 编译时间定义的,而不像在 Java 语言中那样,是在运行时定义的。注意清单 5 中的 pop()peek() 方法是如何返回 Type 的,以及 push() 方法是如何将 Type 用作参数。该 Type 与内部 items 集合将包含的 对象类型是相匹配的。

清单 6 显示了在 Nice 中如何使用参数堆栈。注意,我已经创建了只能存放 String 类型的对象的 SimpleStack 实例。注意在 Nice 中为何不对最后一行进行编译!这个代码行试图 push 一个 Integer 到参数化堆栈中。

清单 6. 在 Nice 中使用参数堆栈

 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 开发人员最熟悉的异常。实际上,空指针非常麻烦,甚至是最简单的 Java 编译器都可以将对象标记为尚未初始化。不幸的是,您可能不时地发现,那些警告并没有捕获 null 的每次可能出现。

为了与其标准安全属性一致,Nice 提供了 可选类型概念。因为可选类型是 API 的作者使用 Java 语言定义的,常常难以确定,所以 Nice 为这些类型添加了问号( ? )作为前缀。

如果您发现自己经常开发使用大量参数的 API,那么可用选项将特别有用。确定不同的参数是可选的很可能是因为 JavaDoc 注释就是这样指定这些参数的,或者是因为您传递了 null 并指出其可以使用。

使用 Nice,可以仅在参数之前添加 ? 来表明变量 可能null ;相反地,没有问号的变量不能直接设为 null 。在清单 7 中可以看到这是如何工作的,在该清单中,我在 Dog 类中定义了方法 walk() 。这表明类型 Location 的第二个参数可能为 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() 方法中,编译器将不允许代码实际引用 location.place

清单 8 例示了可选类型的作用,本例中将使用清单 7 中定义的 walk() 方法。注意,现在可以合法传递 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)与 可选参数混淆,它们是完全不同的。

命名和可选参数
正如您在清单 8 中可能注意到的,在调用 walk() 方法时,Nice 允许指定参数。当我在 Dog 实例中调用 walk() 方法时,我明确命名了参数。例如,将类型 Leashlsh 变量设为第一个参数 leash

Nice 还允许在构造函数中指定参数,与在 Groovy 和 Jython 等语言中的做法非常类似。在清单 8 中,当创建新的 DogLeash 的实例时,我在构造函数中分别明确设置了每个实例的属性、名称和长度。

命名参数允许您以任何希望的顺序传递这些参数。例如,在清单 9 中,这两个调用基本相同;因为我命名了参数,传递这些参数的顺序没有影响。

清单 9. Nice 中可选类型的进一步说明

mollie.walk(leash:lsh, location:loc);
//same behavior from walk method
mollie.walk(location:loc, leash:lsh);

可选参数甚至比可选类型更有用。它们实际上可以替代可选类型。清单 10 显示了如何使用可选参数重新定义 walk() 方法。

清单 10. Nice 中的可选参数

void walkAgain(Leash leash, Location location=new Location(place:"nowhere")){        
  println("walking (again) " name " with a leash " leash.length 
     " inches long to " location.place);
}

该代码将第二个参数 location 定义为 可选。调用 walk() 方法时,不必传送该参数。如果未传送任何值,那么将使用默认值。在本例中,默认值是 Location ,其中 place 等于 nowhere

清单 7 中所做相同,可选参数使您不必进行防御性地编程。但与清单 7 中的 walk() 方法不同的是,编写清单 10 中的 walkAgain() 方法需要考虑遇到 null 值的可能性。

对于可选参数,最后要注意的一点是,可以使用其他值覆盖这些参数。如清单 11 中所示,可以选择一个 两个参数来调用 walkAgain

清单 11. 覆盖 Nice 中的可选参数

mollie.walkAgain(lsh);
Location locBY = new Location(place:"the backyard");
mollie.walkAgain(lsh, locBY);

契约式设计
契约式设计(DBC)是一项技术,它通过在每个组件的界面中明确说明该组件的预期功能和客户机的异常,来确保系统中的所有组件执行它们将进行的操作。Eiffel(请参阅 参考资料)是一种使用 DBC 的流行语言。许多语言都已经合并了契约式设计技术,其中包括 Java 1.4,该技术引入了断言的使用。Nice 使用关键字 requiresensures 来合并程序和断言中的契约信息,以便在执行过程中确认该程序的状态。另外,Nice 甚至支持 1.4 之前的 JVM 使用 assert 关键字。

requiresensures 关键字的作用与前条件和后条件相似,它们允许定义预期的方法将遵循和保证的条件。如果条件得不到满足,将会在运行时生成断言异常。条件和断言的组合已经使许多应用程序免于陷入由逻辑错误引起的深渊中。我将在下面的实例中说明如何一起使用这两种机制。

条件性使用
requires 子句说明了某个方法的客户机必须满足的要求。如果未满足要求,那么将放弃该方法,并生成断言异常。 ensures 子句将在方法的客户机端起作用。该子句是将方法提交到其相关联的调用者的保证。

清单 12 定义了包含 brew() 方法的 CoffeeMachine 类。在 brew() 的定义中,要求客户机 必须传递 Coffee 实例,其中 beanAge 属性小于 10。否则,将生成与使用子句“Beans are too old to brew”一致的断言异常。而且,我还保证方法(本例中为 CoffeeCup 的实例)的 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 实例和 Coffee 的新实例。注意,在本例中,已经将 CoffeebeanAge 属性设为 15,该值大于 10,因此不满足 brew 的契约。

清单 13. 契约违反

CoffeeMachine machine = new CoffeeMachine();
Coffee cfe = new Coffee(beanAge:15);
CoffeeCup cup = machine.brew(cfe);

清单 14 说明了在清单 13 中调用 brew() 时生成的异常堆栈。正如您可以看到的,这里提供了定制消息“Beans are too old to brew”来帮助调试。

清单 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)

默认情况下,不启用 Nice 断言,所以必须开启这些断言。有关 JVM 的指令,请参阅 Nice 的文档。

重访堆栈
在基本了解了 Nice 如何实现前条件和后条件之后,让我们看一下将这些技术应用于前面的 IStack 示例时会发生什么。在清单 15 中,用 Nice 定义了 IStack 接口,并添加了不同的 ensuresrequires 子句。

清单 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 中,实现了 IStack 接口。注意,Nice 为定义方法本体提供了捷径:只使用 = 语法。还要注意下例中 Nice 的 override 语法。

清单 16. 新的改进的堆栈

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 契约的条款,将会引起断言异常。例如,在清单 17 中,可以看到当试图对已经确保两个项安全的堆栈中推入第三个项时会发生什么。第三个 pop() 的调用导致先条件 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 最引人注意的独特功能之一是 多方法,或在特定类定义之外定义类实例方法的能力。该方法独自创建了许多扩展,这些扩展可与面向方面编程(AOP)的一些比较令人激动的原则相媲美。

方法的语法相当简单,因为第一个参数是方法应该附加的类型。剩余的参数则成为实例方法的标准参数。为了举例说明,在清单 18 中,可以创建简单的、无意义的方法,并将其附加到 java.lang.String 的实例。

清单 18. Nice 中的多方法

void laugh(String str){
  println("haha, I'm holding an instance of " str);
}

在该代码中,当调用 laugh() 方法时,它将只打印 String 。因为 laugh() 方法的第一个参数的类型为 String ,因此,要将该方法附加到 String 的实例中。在清单 19 中,创建 String 实例 myString ,并调用 laugh() 方法,该方法将打印“haha, I'm holding an instance of Andy”。

清单 19. 使用 Nice 中的多方法

let myString = new String("Andy");
myString.laugh();

虽然 laugh() 方法完全无用,但确实说明了几个关键点:

  • Nice 使您能够轻松地将新的行为附加到对象中。
  • Nice 允许将该行为附加到任何事物,其中包括您无法访问其源代码的标准类。
  • Nice 使您可以将行为添加到 final 对象。

高级多方法
将有用的行为添加到对象比使用无用的行为更有意义,所以让我们将参数类和多方法的知识提高到另一个级别。在清单 20 中,您将看到当将 join() 方法添加到 java.util.Collection 接口时会发生什么。 join() 方法在敏捷语言之间非常通用;它只将预期的 String 附加到集合的所有元素中,以创建大的 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 方法包含可选参数 String ,使用该参数,可以连接在其上调用 join() 方法的 Collection 实例的元素。

清单 21 显示,使用新的 join() 方法非常容易。它只传递预期的 join String 或使用默认值。这项功能就像 AOP 中的静态横切(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 接口相似,但要灵活得多。抽象接口最好的地方是可以由任何对象实现,即使定义了接口之后也可以。在这点上,抽象接口功能与 AOP 中的静态横切非常相像。

在清单 22 中,可以开始了解抽象接口如何工作。我先创建类型 TasteTest 的新抽象接口,它包含一个方法 taste() 。然后使 MochaLatte 类实现这个新类型。

清单 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 的实例上使用新的功能非常简单,如清单 23 中所示。只需调用 taste 方法即可!

清单 23. Latte 唤醒调用

let coffee = new Latte();	
coffee.taste(); //prints Waking me up it's soooo good.

虽然使用多方法和抽象接口的实例相当简短,但它们确实说明了使用 Nice 可以达到的表达能力级别。实际上,一些人认为,由 Nice 语法的简易性所支持的 Nice 的表达能力能够与 Java 编程中的 AOP 相媲美。

好用的枚举类型
正如前面所说的,Nice 合并了 Java 5.0 中的一些功能,实际上,它现在允许在任何 Java 平台上使用这些功能。其中一项功能是枚举类型。与参数类相同,枚举类型在编译时而不是在运行时帮助您进行 bug 检测。

为了了解枚举类型是如何工作的,我将使用一个通用开发实例。常量和关键字通常以 static final 字段形式放置在接口和类中。然后,其他类可以引用这些字段,而不是引用本地变量,以限制发生的变化。定义常量的代码与清单 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 显示了使用中的常量类型的典型示例。如果仔细查看该代码,您还会发现有损其有效性的地方。

清单 25. Java 常量的限制

public static void brew(int coffeeType){    	    
  if(coffeeType == CoffeeBeans.ESPRESSOROAST){
    System.out.println("brewing espresso!");
  }  
  //other if/else clauses....
}

清单 25 中代码的问题在于:恶意的或无知的客户机可以使用制定的限制之外的值来调用 brew() 方法。例如,如果使用 48 调用该方法,代码的编译将很完美。但不幸的是,当运行代码时,仍然必须处理这个问题。

使用 枚举而不是使用常量会使代码更加安全。实质上,枚举将强制编译器确保使用的值在定义的限制内。例如,在清单 26 中,可以看到为 CoffeeBeanType 定义枚举时会发生什么,同时还定义了类型 ICoffee 的接口和两个实现: LatteMocha

正如您所看到的,这些 ICoffee 类型定义返回枚举实例的 getType() 方法。您还可以看到的是,如果定义 CoffeeMachine 类并包含枚举实例,而不是定义如清单 25 中所示的包含 int 的方法,那么将会发生什么。

清单 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 中,当对 CoffeeMachine 实例调用 brew() 方法时,要使用枚举类型。注意在 Nice 中枚举是如何隐含 value 字段的。在 ESPRESSOROAST 中,该值为创建时传递的 Stringespresso

清单 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 是一种避开转换对象概念的强类型语言,所以必须将 Nice 中的所有集合都参数化。例如,下面的行通常将使用 Java 语言进行编译,但在 Nice 中,集合必须是更强类型的。


Collection noColl = new ArrayList(); //will not compile in Nice

如果因为某种原因,需要将 Nice 集合呈现得更像 Java,那么可以仅使用 java.lang.Object 将该集合参数化,如下所示。


Collection<Object> collObj = new ArrayList();

多方法和集合
与其他一些语言类似,Nice 使用多方法向标准集合添加了其他许多方法。由于 Nice 的集合行为支持块语法,所以这些行为与 Groovy 和 Ruby 中的类似。虽然这些代码块其实只是匿名方法,不像真实的闭包(true closures)那样强大,但它们实际上更方便。

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 还通过一些非常好的方法增强了 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() 方法,该方法将查找两个单独 Set 中的共同元素。 union() 方法合并这两个 Set ,而 difference() 方法查找这两个 Set 之间的差异。最后, disjunction() 方法合并了这两个 Set ,同时还删除它们的共同元素。

其他功能
在要结束对 Nice 的介绍时,我将讲述一下它的三个比较引人注意的便利功能:值分发、高级 for 循环和范围,以及更随意的 String 使用。正如在前面一小节中介绍的那样,您将注意到每项功能都提高了代码的表达能力和模块化,同时还增强了代码安全。

值分发方法
值分发方法被用来确保实际调用或分发到哪个方法的运行时决策不仅由参数的类型确定,还由参数的实际 确定。该方法有助于避免代码包含一系列 if / else 子句或 switch 语句。

在清单 30 中可以看到该方法是如何工作的。我先为音乐类型定义了一个 enum 。然后创建了一系列有效模拟 switch 语句的值分发方法。如果这时传递 GenreCelticvariationName() 方法将返回“Irish”,如下所示。

清单 30. 详细定义的值分发

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 中所示,当传递 Genre enumFolk 时,由此得到的 variation 变量将被设为 Acoustic

清单 31. 在 Nice 中使用值分发

var variation = variationName(Folk);
println(variation); //prints Acoustic

增强的 for 循环和范围
这时,您可能已经注意到,Nice 支持 for 循环标准的简略概念,这与新的 Java 5 非常像。简单的 for 循环构造在敏捷语言之间非常通用,奇怪的是,Java 语言居然用了这么长时间来引入它!

如清单 32 中所示,Nice 使您可以通过 int 的集合轻松进行迭代,而无需使用 Java 语言常用的 Iterator 接口。还要注意的是,Nice 支持自动置入,与 Groovy 很像(请参阅 参考资料,以获得关于 Groovy 的 alt.lang.jre期刊)。

清单 32. Nice 的 for 循环

Collection<int> iColl = new ArrayList();
iColl.add(11);
iColl.add(12);

for(int i : iColl){
  println(i);
}	

清单 33 说明了 Nice 中的范围功能。Nice 仅支持包括的范围;因此,在下面的代码中,将打印从 1 到(且包括)20 的数字。

清单 33. Nice 中的包括范围

for(int i : 1..20){
  print(i);
}

随意字符串
Nice 提供了普通 Java String ,但放宽了与这些字符串在 Java 语言中的使用相关联的一些限制。如果曾使用过 Python,您将认识 Nice 的多行字符串常量。在清单 34 中,可以看到使用 """ 语法创建多行字符串非常容易。您还将注意到 ' 如何自动转义。

清单 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 语言标准 + 语法的限制,使字符串连接更加容易。因此,可以调用 println() 并删除相关联的串联,如上面清单 34 中所示。

结束语
Java 语言的 5.0 版中包含本月 alt.lang.jre期刊中讲述的许多最好的功能。不过,并不是每个人都可以立即使用 Java 5,其余一些人可以使用 Nice。除了使您可以实际地在任何 JVM 版本上使用参数类、多方法、契约式设计和其他许多便利功能之外,Nice 还使您初步了解了在所有开发平台上的表达能力和敏捷度的优点。正如这里所展示的,当提到在 Java 平台上构造更安全的、更模块化的代码时,Nice 是很好的选择。请参阅 参考资料部分,以了解关于 Nice 的更多信息,同时还请耐心等待下月的 alt.lang.jre期刊,它将介绍 Rhino。