一起学习
原文
by Janice J. Heiss
May 8, 2003
随着预期Java 2 Platform, Standard Edition 1.5 (J2SE 1.5) beta版本在2003年晚一些时候的发布(众所周知的Tiger项目),开发人员非常关注即将到来的新的变化。没有谁能比Joshua Bloch更适合清晰地解释J2SE 1.5了。他是Sun公司的资深工程师,是Java平台核心工作组的系统架构师。他成功地设计并实现了获奖的Java Collections Framework,java.math包,而且对Java平台的其他部分的设计和实现也做出了卓越的贡献。他发表过无数的论文,并且出版过一本书《Effective Java Programming Language Guide》,这本书获得了软件开发杂志的Jolt奖。他获得了Carnegie-Mellon 大学的计算机博士学位。今天我们跟他交流一下,来看看Java程序设计语言的未来。
正在研发的Tiger项目试图增强J2SE,在不损失兼容性的基础上使Java程序更清晰、更简短、更安全以及更易于使用。您能告诉我们在J2SE平台中Java语言怎么样更容易使用吗?
新语言特性都有一个共同特征:它们都包含一些通用的语法并且提供了语言上的支持。换句话说,它们把编写与语义代码的责任从程序员转移到了编译器上。 因为源代码摆脱了语义相关代码,它更容易读写。同时编译器不是程序员,永远不可能犯错误,所以导致新代码更可能摆脱bug的困扰。
另外,整体的功能大于组成整体的各个部分之和,所有的新特性在设计的时候都是充分考虑其它部分的。它们一起协调作用,显著地提升了语言的表现力和安全性。
您估计,哪一个变化是最难让开发者适应的?还有,开发者需要做什么样的调整?
我希望,所有的新特性都不会让开发者感到难以适应。不过,如果我非要从里面选一个出来的话,那么我觉得是泛型技术。因为开发者必须在声明中提供一些额外的信息,而不是仅仅说:
List words = new ArrayList();
你必须这样写:
List
words = new ArrayList();
这样做的好处就是如果你想插入一个其他什么对象而不是String的话,在编译的时候你就可以发现并修正你的错误。如果不使用泛型技术的话,只有当你们的客户向你们抱怨他们所依赖的软件Down掉了,原因是一个什么类型转化异常(ClassCastException)的时候,你才可能发现你程序中的这个bug。
这样做的另外一个好处就是当你从集合(Collection)中取出一个元素的时候不用作强制类型转转。本来,你会这样写:
String title = ((String) words.get(i)).toUppercase();
现在很简单:
String title = words.get(i).toUppercase();
Java 2 Platform, Standard Edition 1.5 (J2SE 1.5) beta版本将在2003年晚一些时候的发布,这些变化也都通过了JCP的审核。两个问题:如何确保当前的构想会反映到最终的改变?开发者怎样参与到这种改变的过程?
相关JSRs (14, 201, 和175)的专家组将会推敲当前的草案,但是我确信大体的轮廓在最终的版本中不会改变太多。当然,最终还是要专家组说了算。
开发者参与的最好的途径就是阅读最新的草案,并把意见和建议发给相关的专家组。最新的泛型草案可以在下面的地址中找到:
你也可以下载一个预览版的编译器,尝试理解泛型技术: http://developer.java.sun.com/developer/earlyAccess/adding_generics/.
其他方面建议的早期草案在这里: 。 请注意3.1节,大约在这一页的底部。
当前还没有关于JSR-175 (metadata)元数据的草案,不过很快就会有了。开发的进一步信息请在这里找:
您能把这六个方面的改进概括一下吗?
那好,我做一个简述:
泛型Generic – 提供了collection操作的编译期类型安全,并且避免了类型转换的苦差事。
For循环增强Enhanced for loop – 避免使用容易引起错误的迭代器.
自动置入/自动取出Autoboxing/unboxing – 避免了在基本类型(如int)和包装类型(如Integer)之间人工转换类型的苦差事。
类型安全的枚举Typesafe enums – 提供了类型安全的枚举模式((Effective Java, Item 21))的好处,却没有冗长和易错的代码。
静态导入Static import – 让你可以避免使用类的静态变量而必须前缀类名;而且避免了常数接口的缺点(Effective Java, Item 17)。
元数据 – 让你避免写样本代码。你在程序中做标记然后工具自动帮你生成样本代码。这会导致一种声明式的程序设计方格,你说应该做什么,然后工具就会产生代码完成这个任务。
如果遵循新规范并且开始使用泛型,那么现在使用集合的方式和使用带泛型的集合有什么不同之处?
我们一般这样使用集合:
/**
* 在由String构成的集合中删除所有长度为4的元素
* 传入的集合必须由String构成
*/
static void expurgate(Collection c) {
for (Iterator i = c.iterator(); i.hasNext(); ) {
String s = (String) i.next();
if(s.length() == 4)
i.remove();
}
}
这个类型转换并不完美,而且更重要的是程序可能会在运行时发生错误。假设用户不小心传入一个由StringBuffer构成的集合而不是注释中说明的String,那么就可能会有意外发生。注释说客户端必须传入一个由String构成的集合,但是并不能要求编译器编译时一定满足注释。
下面是同样的代码使用泛型的例子 :
/**
*在由String构成的集合中删除所有长度为4的元素
*/
static void expurgate(Collection c) {
for (Iterator i = c.iterator(); i.hasNext(); )
if (i.next().length() == 4)
i.remove();
}
现在从代码的签名就可以看出,输入参数必须是仅仅由String构成的集合。如果客户试图传入一个由StringBuffer构成的集合,程序就不会通过编译。而且请注意我们上面的代码中并没有类型转换。它只有短短的一行,而且阅读使用泛型集合的代码也会非常清晰。
请您给我们介绍一下 "for增强"?
我们可以使用更优雅的方法遍历一个集合。你一般遍历集合的时候,都只是取元素,很少用到其他的方法。"for增强"可以让编译器代替你管理你的迭代器。例如,这是一个使用迭代器遍历一个由TimeTask构成的集合:
void cancelAll(Collection c) {
for (Iterator i = c.iterator(); i.hasNext(); ) {
TimerTask tt = (TimerTask) i.next();
tt.cancel();
}
}
现在我们用"for增强"重写一下这个方法:
void cancelAll(Collection c) {
for (Object o : c)
((TimerTask)o).close();
}
当你读这个语句的时候,其中的分号读作“在其中”。如果我们使用两个新关键字foreach和in将会更加自然,不过引入新关键字会比较麻烦。比如如果我们现有的代码中如果正好使用这两个字作为标志符的话,那么新的编译系统就会破坏我们的原有代码。我们的方法是在保留兼容性的基础扩充语言。
您能把泛型和"for增强"结合在一起吗?
当然没问题。你看,我们把泛型技术融入到上面的代码中:
void cancelAll(Collection c) {
for (TimerTask task : c)
task.cancel();
}
我觉得这是更优雅的代码。现在代码可以准确地表明他要做什么,而且提供了编译期类型安全的保证。
请给我们讲讲自动置入(autoboxing)。
大家都知道,Java语言有两种类型的变量:一种是基本类型,一种是对象的索引类型(object references)。基本类型的变量不能置入到集合中,所以如果你想把基本变量存入集合或从集合中取出的话,你不得不在基本类型(如int)和对应的包装类(如Integer)之间转来转去。每个这样做的人都会觉得这不是好的做法。
比如,我们考虑一个程序它用来统计输入命令行中的词的频率。我们使用一个Map,它的关键字是输入的词,值是这个词在命令行中出现的次数:
public class Freq {
private static final Integer ONE = new Integer(1);
public static void main(String args[]) {
// Maps word (String) to frequency (Integer)
Map m = new TreeMap();
for (int i=0; i m = new TreeMap();
for (String word : args)
m.put(word, m.get(word) 1);
System.out.println(m);
}
}
是不是好多了? 有一点值得注意:程序假设当你自动取出null的时候,你会得到0。是否是这种情况还是一个问题。另外一个可选择的就是抛出一个NullPointerException 异常。这两种选择都有其各自的优势。“自动取出null得到0”可以美化像上面这样的程序,但是它也可能隐蔽了真正的错误。如果谁有强烈的意见或者有能令人信服的关于这个问题更好的解决方案,请与JSR-201专家组联系。
新的“类型安全的枚举”(typesafe enums)比用整数枚举(int enum)有什么优势?
这个问题在我的书第21项中有详细的论述。简要地说:
它提供了编译期类型安全,而整数枚举根本没有提供任何类型安全。
它们对被枚举的类型提供了一个适当的命名空间――在整数枚举中你必须前置一个常数来得到命名空间。
它更强大――整数枚举被编译进了程序,如果你增加、删除或者重置了常数的顺序你必须重新编译你的程序。
输出的值是包含各种信息的――如果你打印一个整数枚举,你只会得到一个数字。
它们是对象,你可以把他们放入集合中。
它们本质上是类,所以你能增加任意的属性和方法。
哇,这么厉害!那么新的类型安全的枚举语言特性和你书中类型安全的模式有什么关系?
这个特性是对类型安全模式的首次语言上的简单支持。那些有差不多半页、冗长的、易错的支持模式的代码现在看起来很像C/C 中枚举的声明:
enum Season { winter, spring, summer, fall }
不过它用起来却跟C/C 中的枚举大相径庭,你可以使用我们刚才讨论的所有的强大的功能。它甚至修正了类型安全模式的主要的缺点:你可以把这个新的语言特性和Switch语句用在一起。
你能给我们举例说明一下类型安全的枚举的强大功能吗?
当然可以。下面的这个枚举代表了一个美国硬币:
public enum Coin {
penny(1), nickel(5), dime(10), quarter(25);
Coin(int value) { this.value = value; }
private final int value;
public int value() { return value; }
}
看看,这个常数声明怎样调用了一个构造函数,输入一个整数代表分?还有,这个值怎样存放在一个带有公共访问方法的私有变量里面?这个就可以勾画出你能做的事情的轮廓。
您能给我一个类使用您上面定义的enum吗?
我正准备要说到这个。下面是一个小的例子,它用来输出分币表中各个分币的值和颜色。
public class CoinTest {
public static void main(String[] args) {
for (Coin c : Coin.VALUES)
System.out.println(c ": "
c.value() "¢ " color(c));
}
private enum CoinColor { copper, nickel, silver }
private static CoinColor color(Coin c) {
switch(c) {
case Coin.penny: return CoinColor.copper;
case Coin.nickel: return CoinColor.nickel;
case Coin.dime:
case Coin.quarter: return CoinColor.silver;
default: throw new AssertionError("Unknown coin: " c);
}
}
}
看看我们如何声明另外一个关于颜色的枚举?一个叫做nickeld的硬币和一个叫做nickel的颜色根本不会引起什么冲突,因为硬币和颜色都有它们自己的命名空间。同时你可以看到我们可以把枚举常数和Switch语句用在一起。当你希望给一个枚举类增加一个方法而不想修改你的应用程序的时候,这个性质非常有用。
看起来真的很好。您乐意跟我们介绍一下静态导入(static import)会给开发者带来什么吗?
它让程序员不必在使用类的静态成员时前缀类名。人们确实希望这样,所以他们经常实现所谓的常数接口来达到这个效果:
// "Constant Interface" antipattern - do not use!
public interface Physics {
public static final double AVOGADROS_NUMBER = 6.02214199e23;
public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
public static final double ELECTRON_MASS = 9.10938188e-31;
}
public class Guacamole implements Physics {
public static void main(String[] args) {
double moles = ...;
double molecules = AVOGADROS_NUMBER * moles;
...
}
}
这是一个非常糟糕的做法:接口是用来定义类型的,不是用来定义常量的。Guacamole 使用Physics常数仅仅是一个实现细节,不应该对外开放公共的API。这样做的结果不仅让这个类的使用者糊涂,而且它还做了长期的承诺。即使你准备重写Guacamole 这个类,其中不再用到这些常数,你依然必须implements这个接口。因为这个类的使用者依赖于你的类implements Physics这个事实。
静态导入(static import)提供了一个清晰的解决方案。它让程序员使用某个类的静态成员而不用成为它的子类的方法。它和package import的功能很相似,不过他只是导入一个类的静态成员,而package import则是从包中导入一个类。下面是一个例子:
import static org.iso.Physics.*;
class Guacamole {
public static void main(String[] args) {
double molecules = AVOGADROS_NUMBER * moles;
...
}
}
注意,无论Physics是一个接口还是一个类,这个程序都可以正常地工作。如果它只是定义常数,那么它就应该定义为一个类而不是一个接口。
我懂了。现在您能告诉我关于元数据(metadata)的一些便利吗?
这与我们讨论的其它的一些特性不太相同。它同样也可以让开发变得更容易,不过需要工具开发商的支持。
现在许多API需要相当数量的样板文件。比如,当你定义一个JAX-RPC Web Service的时候 ,你要提供一个接口和一个实现类:
public interface CoffeeOrderIF extends java.rmi.Remote {
public Coffee [] getPriceList()
throws java.rmi.RemoteException;
public String orderCoffee(String name, int quantity)
throws java.rmi.RemoteException;
}
public class CoffeeOrderImpl implements CoffeeOrderIF {
public Coffee [] getPriceList() {
...
}
public String orderCoffee(String name, int quantity) {
...
}
}
这个例子是直接从我们Web Services Tutorial里面拷贝的。
如果有元数据功能的话,你就不必要手工写所有的这些东西。你只要标记代码让工具知道那些代码是可以远程访问的,工具就会自动生成上面的代码。下面是一个有元数据功能支持的代码的例子:
import javax.xml.rpc.*;
public class CoffeeOrder {
@Remote public Coffee [] getPriceList() {
...
}
@Remote public String orderCoffee(String name, int quantity) {
...
}
}
所有的样本文件都没有了!
嗯,确实简洁多了。但是您不可能定义所有有用的性质并且构造所有的工具,不是吗?
对,JSR-175只是提供了一个框架给其他的开发者,让他们可以定义性质和构造工具。其他一些JSR诸如JSR-181定义了Web Services 的元数据,是定义属性的。我们期望这个领域有更多的发展。
向前看,您期望未来的Java语言会有什么新的改变?
这个不好说。我现在所有的精力都花在Tiger上面了,没有时间考虑下一个变化是什么。
您还想留给我们一些其他什么信息吗?
其他最重要的就是,Tiger 是一个以开发者为中心的版本。当James Gosling 和他的团队引入Java语言之后,Java的发展速度就像是一个起飞的火箭一样,原因就在于它触动了开发者的心灵,点燃了他们的热情。这个版本的开发依然是基于这个思想。
我很幸运能有机会最早接触这些语言的新特性,发现用它们工作是一种乐趣。我敢确信其他开发者也会这样。在现有的各种开发语言中,Java本来就是一个非常好用而且富有生产力的语言,而所有的即将到来的新特性又会大大地增强使用它的乐趣,提高使用它进行开发的生产力。
下载本文示例代码
J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录J2SE1.5便于开发的新语言特性:Joshua Bloch的访谈录
阅读(201) | 评论(0) | 转发(0) |