对于所有开发人员来说,NetBeans 下一个版本中的新功能和改进使其成为更好的 IDE 选择。从编辑到浏览、版本控制、构建、调试、性能分析或可视化设计,对于所有人来说,新版本的 NetBeans 将面貌一新。
新版本又快问世了。不久以后 —— 自 5.0 版本问世以来大约一年半的时间 —— 就可以使用新的NetBeans 首发版了。NetBeans 5.0 版中引入了重要的新功能(如 Matisse GUI builder),并在 CVS 集成、web services和模块开发方面进行了大量改进。与之相比,5.5 版的重点在核心 IDE 以外,它支持一些新 Pack,大大提升了 NetBeans 的整体功能,其他任何开源 IDE 都难以与之抗衡。现在,NetBeans 6.0 能够取代主版本号的位置吗?当然可以,本文将着眼于核心 IDE 中的某些最重要且感兴趣的新功能。
增强的 Javac
我们将不考虑终端用户功能,而是着眼于为其他增强提供基础的核心 IDE 技术。像其他编程工具一样,以前版本的 NetBeans 中包含了定制代码,用于解析 Java 源并为代码理解和操作任务(如重构、提示和修补、概述)提供帮助。有时得到的结果是受限的功能:简单的高亮显示、并非无懈可击的重构、不支持某些功能(比如并非所有出现 Java 代码的地方都支持代码完成)。
显而易见的解决方案是重用成熟的 javac 编译器技术来完成所有 Java 源处理。但是 javac 不支持当代 IDE 的要求:javac 是按照批执行的要求进行编写和调整的,而且接受完全编译单元作为输入、执行完全编译并生成 .class 文件作为输出。
IDE 的要求是完全不同的,其中最关键的是仅在内存中运行。假定每次键入一个字符后,IDE 希望再次分析整个类,以便能够更新语法错误指示、执行高亮显示并提供取决于代码结构的其他功能。一种选择是将编辑器的当前内容写入一个临时文件中,调用 javac 并解析所得到的 .class 文件。但是这种做法的效率是非常低的。
更好的解决方案是在同一个过程(如本地库)中调用 javac,然后将当前源作为内存参数进行传递,并接收包含了类文件中所呈现信息的数据结构(类文件不必进行创建)。直到 Java SE 5,这种解决方案都是可以接受的,但是仅使用了 Java 编译器的专用的(通常不稳定的)内部 API。
该解决方案因 Java SE 6 的问世而发生改变,Java SE 6 引入了 JSR 199 (Java Compiler API) 和 JSR 269 (Pluggable Annotation Processing API)。Java Compiler API 能够与 javac(以及其他 Java 源编译器)紧密有效地集成,而 JSR 269 —— 尽管最初是为注释处理而设计的 —— 能从源代码完全反射元数据。同时使用这些新的 API 将使得 IDE 和其他工具可以深入挖掘 javac 从源代码中提取出来的结构化信息。另外,增强并调整了 javac 的实现,可以用于嵌入和交互式使用。
对 NetBeans 进行了大量更新以集成这些新功能,并在 IDE 中进行了很多改进(见下文)。这些更改还预示了未来的受益:随着带有新语言增强集的 Java SE 7 的问世,NetBeans 的工具套件很快就能满足需要。
新编辑器
一般说来,没有任何产品是十全十美的,但是 NetBeans 正日趋完美。该 IDE 覆盖了全部 Java 平台(从 ME 到 EE)、支持有效的 GUI 构建、具有直观 UI 和开放架构,为此 NetBeans 用户曾经引以为豪。另一方面,NetBeans IDE 在某些领域是比较落后的,例如在代码编辑器或重构方面。这可能会使非常重视源代码的程序员分心…毫无疑问他们将选择 emacs 取代可视化设计器。但是,NetBeans 6.0 中不再存在这些问题。
基于 AST 的选择
对于文本编辑器说来,选择几个词语或几行代码就够用了;但是使用源时,通常需要使用(组成连贯代码片段的)一系列文本。假定您想要复制一个 for 循环体内的全部代码,以便将它粘贴到具有相同逻辑的另一个循环中。那么只需要将鼠标放在循环体内的任何空白位置上,然后按下 Alt+Shift+Up 即可。编辑器将选择包含了鼠标所在位置的最内部的系列文本,并定义源的抽象语法树 (Abstract Syntax Tree) 节点。
注意:Java 编译器(像大多数编译器一样)将源代码解析到中间表示形式中,中间表示形式采用树状结构。这个数据结构(被称为 Abstract Syntax Tree)中的每个节点代表一个代码元素,如类、方法、语句、块、标识符、操作符、文字等等。虽然代码处理工具通常将程序作为 AST 进行处理,但是多数工具将使用只产生基本树的简单解析器。“完整”AST 是由完全编辑器(如 javac)产生的,完全编辑器能够进行语义分析和代码生成,因此“完整”AST将包含非常详细且可靠的节点信息。例如,标识符节点不仅包含它的名称,还包含它的类型及“明确赋值”状态(如果可用)。运行在完整 AST 上的工具将更加强大并且更加可靠。这种差别在简单选择功能上并不显著,但是对于重构这样的高级功能来说是非常重要的。
再次按下 Alt+Shift+Up将选择下一个外部节点,本例中全部是 for 语句;然后,重复上述按键操作,将选择整个方法,以此类推。按下 Alt+Shift+Down 会将选择缩小到内部节点。图 1 展示了使用该功能方便准确地选择多行语句。我敢打赌您很快就会着迷于该功能,从而忘记所有其他选择快捷方式!没有什么能比得上能够全面理解代码(而不是文本)的代码编辑器。
语义高亮组件
编辑器的语义高亮组件升级为语义感知高亮组件。它不仅可以基于标记类型(如标识符、操作符或注释)来应用样式,还可以基于类似标记的不同含义来应用样式 —— 例如,标识符可以是类名或本地变量名、参数、常量字段等。
语义高亮显示的一个好处在于协助您格外注意 static 字段的赋值(因为很多线程安全和内存泄露错误将涉及到静态值)。图 1 展示了语义高亮显示;请注意 static 字段(以及对它们的引用)是以斜体显示的。
新的高亮显示引擎还有其他用途:
§ 识别用法 —— 选择任何标识符,编辑器将在同一个编译单元中高亮显示该标识符的全部用法。同样,图 1 也给出了示范:单击方法名称,将高亮显示对该方法的所有调用。
§ 标记“出错代码(Smelly code)” —— 新编辑器将高亮显示未使用的变量和输入,以及不赞成的类和方法。您再也不必执行构建操作或运行代码 lint 工具来检测这些简单(但频繁出现的)问题。
§ 推出和抛出点 —— 如果选择方法的返回类型,则会高亮显示所有 return 语句。如果选择方法的 throws 列表中的异常,则会标记该异常类型的所有 throw。还会对可能抛出相同异常的所有其他方法调用进行标记。
更好的代码完成
现在我们不得不使用大量令人迷惑的 API,这使得代码完成成为任何当代代码编辑器的最重要功能之一。NetBeans 6.0 添加了很多新技巧:
§ 关键字完成 —— 例如,如果刚刚在新的源文件中键入 package 声明,则 Alt+Space 将只产生在该位置上合法的关键字:abstract、class、enum、fina、import、interface 和 public。图 1 展示了另一个示例:在方法声明的开始括号之后,首选的关键字完成是所有的基本类型。
§ 基于类型的变量名称 —— 在“ConfigurationFile _”之后,编辑器将提供变量名称 cf、configurationFile 和 file。(我使用“_”表示鼠标位置。)
§ 泛型感知完成 —— 将泛型类型变量赋值给 new 表达式时,编辑器将提供所有兼容类型,包括泛型参数。例如,在“Map<String, Integer> m = new _”中,代码完成将列出所有 Map 实现,每个都带有相同的 <String, Integer> 参数。
§ 注释感知完成 —— 在“@”之后,将提供可用于给定范围的所有注释。如果所选的注释需要参数,则编辑器还将提供参数完成。
§ 传递参数 —— 在“x = m(_”中,顶部完成将是与 m() 第一个参数兼容的范围内的值。如果方法的参数名是可用的且上述范围内存在具有相同名称的变量,则这用来进一步对完成进行排序。将提供带有(用那些变量填写的)参数列表的完整完成。
§ 普通构造方法 —— 将鼠标放在类成员之间来调用代码完成时,将创建一个不带参数的构造方法和一个接收所有字段初始值的构造方法(如果这些构造方法不存在的话)。
§ 捕获异常 —— “catch (_”之后的完成将仅提供那些在相应 try 块中抛出但尚未被前一个 catch 块处理的异常。
新的浏览视图
编辑器引入了一些用于源代码浏览的新视图。Members 视图展示了 Java 类型成员及其 javadoc,可以方便地查找特定方法、字段或内部类。Hierarchy 视图展示了 Java 类型的继承树。图 1 演示了该视图;请注意过滤器按钮,它允许在超类或子类之间以及简单类名和完全限定类名之间进行切换。还可以选择是否显示内部类和接口。
Declaration 视图概述了所选择的 Java 元素(类型、方法或字段)的声明。尽管叫做Declaration 视图,但是它还将展示所选择元素的原代码(如果可用)。Declaration 视图在调用仍处于开发中但尚未写成 javadoc 文档的代码时格外有用。最后,Javadoc 视图展示了所选择的 Java 元素的 javadoc。
可编辑的 Diff 和内联 Diff
改进的编辑器架构使得各种功能可以方便地操作源代码来集成编辑器功能。这一点在新 Diff 中是显而易见的(例如,通过选择源文件并选择 Subversion>Diff 打开 Diff)。当显示本地文件时,右窗格是可编辑的,其中提供了完整的编辑器功能集 —— 包括语义高亮显示和代码完成。
新 Diff 增加了其他令人感兴趣的技巧,如单击合并和单词级差别(即如果更改了某行中一个单词,则只会高亮显示该单词)。请在图 2 中查看这些改进。

|
|
|
图 2. Local History 和新的 Diff:编辑功能、语义高亮显示和单词级差别。
|
还可以启用 Inline Diff 功能,它将创建 Diff 侧边栏,高亮显示带版本号的文件的更新部分。侧边栏可以可视化或回滚更改,并打开完整的 Diff 视图。
Javadoc 提示
您总是将所有代码写入文档,对吗?哦,如果不是的话,NetBeans 将提示丢失的 javadoc 标记和错误的 javadoc 标记。IDE 可以协助您使用自动修补程序来添加丢失的标记,您需要的仅仅是填写空白。并且在进行上述操作时,还可以使用新的 Javadoc 视图方便地进行预览。
默认情况下,Javadoc 检查处于启用状态,但它不是侵入式的:编辑器仅报告选定行中丢失的 javadoc 标记;只有错误的标记才在所有行进行报告。可以通过 Tools|Options>Java Code>Hints 来定制这些选项及相关选项。
其他功能
新编辑器及其框架包括其他普通功能,如可重用的编辑器选项卡。它们对于调试器是很有用的,避免环境中堆满杂乱无章的通过断点或进入操作打开的编辑器。还有一个新的 Generate Code 对话框,将自动创建构造方法、获取方法和设置方法、equals() 和 hashCode()、以及委托 (delegate) 方法。
重构和 Jackpot
NetBeans 6.0 大大改进了现有重构的支持范围。新的与语言无关的内部重构 API 可以对常用 .java 源代码(如 XML 或 JSF 文件)之外的代码实现重构。新的 API 还允许 Java 重构准确地更新相关的非 Java 元素。这使得当前重构更加安全并且更易于使用。
然而,最大的看点在于对项目 Jackpot 中新技术的突破,新技术已经可以使用,但是刚刚成熟。在 NetBeans 6.0 中,Jackpot 将升级为标准功能并与 IDE 更加紧密地集成。
您也许听说 Jackpot 是一个新的重构工具,但事实上这种说法并不正确。实际上 Jackpot 是一个用于普通代码理解和操作的综合框架。可以将它用作某些功能的替换或基础:重构支持、高级搜索和浏览、质量检查、复杂编辑任务的类似宏的自动操作以及其他功能。.
使用 Jackpot
在深入研究 Jackpot 之前,我们将展示如何方便地使用它。新的 Query and Refactor 命令将展示一个对话框,如图 3 所示,其中可以选择 Jackpot 查询或查询集。一些查询中的选项可以设置为首选值。单击 Query,所选查询的所有匹配都将显示在一个视图中,其中详细描述了每一个匹配。同时,如果查询包含代码更改,则通过单击 Do Refactoring 按钮可以预览并确认这些更改。

|
|
|
图 3. Jackpot 的 Query and Refactor 对话框。
|
Jackpot 规则
Jackpot 的所有功能来自于它的开放性。这需要学习一种新语言,当您认识到 Jackpot 的全部潜力时,就会明白学习曲线很快就会带来受益。
例如,下面是一个 Jackpot 查询,将检测低效代码模式—— 使用 equals(“”) 来检查 String 是否为空 —— 并重写相匹配的代码:
$s.equals(“”) => ($s.length() == 0) :: $s instanceof java.lang.String;
语法是 pattern => replacement :: condition,其中 $ 字符用来识别绑定到任何 Java 程序元素(标识符、语句、操作符、文字等)的 meta 变量。下面来分析一下每个子句:
1. 模式 $s.equals(“”) 将匹配对 equals() 方法的调用,将空字符串作为参数进行传递。
2. 在 Jackpot 规则语言中,条件是规则中惟一可以选择的部分,但也是特殊规则 $s instanceof java.lang.String 中的重要部分,它确保仅当 $s 是 String 时才激活规则。这是一个重要的限制,因为该规则是专门用于 java.lang.String.equals() 的,而不是 equals() 的任何实现。
3. 最后,可替换部分 —— ($s.length() == 0) —— 将重写匹配的代码。
这个看似简单的行为背后存在着大量复杂行为。首先,研究一下 Jackpot 的 instanceof 操作符。它看起来像是 Java 的 instanceof,其实并不是同一个事物。Java 的 instanceof 是一个运行时操作符,左边操作数是对象引用。而 Jackpot 的 instanceof 是一个编辑时 (static) 操作符,左边操作数是程序 AST 的任何节点。
因为 Jackpot 像新编辑器一样依赖于 javac 的源分析引擎,它能够分析处理节点中的所有类型。这包括最复杂的情况,如推测的泛型类型。其他代码分析工具通常借助于启发方式来模拟类型,但是可能无法计算某些表达式的类型。
注意:您甚至可以使用普通正则表达式进行重构 (replacing s.equals(“”) by s.length() == 0):搜索 (\w*)\.equals\(\”\”\) 并用 $1.length() == 0 取代它。但正则表达式是严格且非智能的;它们甚至不能删除注释或字符串文字内部的文本,而且一个简单的分行就可以阻止检测操作。这显然是个无足轻重的例子(其他工具如 PMD 和 FindBugs,都比正则表达式更加智能 —— 尽管它们无法达到类似 javac 的精度),不过它体现了智能化工具/功能的价值。
存在不带 Java 操作符的 Jackpot 操作符,包括简单操作符(如匹配布尔表达式的 isTrue(node),可以静态地证实它的值始终是 true)及功能更加强大的操作符(如 isSideEffectFree(node))。后者将匹配(在相应范围之外不修改任何变量的)语句、块或方法。
此类检测类似于现有的代码检查工具,将检测类似“死代码”的问题。但是由于 Jackpot 依赖于完整