Eclipse 中的重构功能
Eclipse 中的重构功能使其成为了一个现代的 Java 集成开发环境 (IDE),而不再是一个普通的文本编辑器。使用重构,您可以轻松更改您的代码,而不必担心对别处造成破坏。有了重构,您可以只关注于所编写代码的功能, 而不必分心去考虑代码的外观如何,因为之后您可以使用重构工具来快捷地将代码变成整洁而高度模块化的代码。本文将向您介绍如何使用 Eclipse 中的一些功能强大的重构函数。
Rename 应该是 Eclipse 中重常用的重构。利用这个重构,可以对变量、类、方法、包、文件夹及几乎任何的 Java 标识符进行重命名。当重命名某标识符时,对该标识符的所有引用也将被重命名。调用 Rename 重构的快捷方式是 Alt+Shift+R。 当在 Eclipse 编辑器中对某标识符调用这个快捷方式时,在此编辑器中会出现一个小对话框,可以在这个对话框中修改这个标识符的名字。在按下 Enter 键时,所有对该标识符的引用也将被相应更改。
使用 Move,可以将一个类从一个包移动到另一个包。这个类被物理地移动到目的包所对应的文件夹中,并且对这个类的所有引用也被更改为指向新的包。
如果将一个类拖放到 Package Explorer 视图中的一个新包中,这个重构将会自动发生。
使用 Extract Local Variable 重构,可以将一个 Java 表达式的结果分配给一个新的本地变量。这个重构的一个用途就是通过将一个复杂的 Java 表达式分成多行来简化该表达式。或者,在编辑代码时,先键入此表达式并使用这种重构自动创建一个新本地变量来指定表达式的结果。当返回值的类型很复杂时, 这个重构将很有用,因为变量的类型是自动生成的。
此重构可以从编辑器调用。在键入想要将其分配给某变量的表达式后,按下 Ctrl+1 并选择 Assign statement to a local variable。这样一个具有适当类型的新变量就创建好了。
Extract Constant 重构可以将代码中的任何数字或字符串文字转换成一个静态终态字段(final field)。在重构后,所有对这个类中的数字或字符串文字的使用都将指向该字段,而不是指向数字或字符串文字本身。这样,在一个位置(字段的值)就可以 实现对所有数字或字符串文字的修改,再也无需在整篇代码中执行查询和替代了。
要使用这个重构,请选择编辑器中的数字或字符串文字,然后按下 Ctrl+1 并选择 Extract to Constant。
Convert Local Variable to Field
正如其名称所示,这个 Convert Local Variable to Field 重构能够获取一个本地变量并将这个变量转换为此类的一个私有字段。此后,所有对这个本地变量的引用也将指向该字段。
要使用这个重构,请选择一个本地变量,然后按下 Ctrl+1 并选择 Convert Local Variable to Field。
Convert Anonymous Class to Nested
Convert Anonymous Class to Nested 重构能够接受一个匿名类并将其转换为最初包含这个匿名类的方法的一个嵌套类。
要使用这个重构,请将光标放入这个匿名类并从菜单中选择 Refactor > Convert Anonymous Class to Nested。这时会出现一个对话 框,要求输入新类的名称。此外,还可以设置类的属性,比如指定对这个类的访问是公共的、受保护的、私有的还是默认的。也可以指定这个类是终态的、静态的还 是两者都是。
例如,清单 1 所示的代码使用一个匿名类创建了一个 Thread Factory。
清单 1. 在执行 Convert Anonymous Class to Nested 重构前
void createPool() { threadPool = Executors.newFixedThreadPool(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("Worker thread"); t.setPriority(Thread.MIN_PRIORITY); t.setDaemon(true); return t; } }); } |
如果这个匿名类可被作为一个内部类单独放置,那么清单 1 中的代码将会简洁很多。因此,我执行 Convert Anonymous Class to Nested 重构,并将这个新类命名为
MyThreadFactory
。 结果更为简洁,如清单 2 中的代码所示。
清单 2. 执行 Convert Anonymous Class to Nested 重构后
private final class MyThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("Worker thread"); t.setPriority(Thread.MIN_PRIORITY); t.setDaemon(true); return t; } } void createPool(){ threadPool = Executors.newFixedThreadPool(1, new MyThreadFactory()); } |
Convert Member Type to Top Level
Convert Member Type to Top Level 重构可以接受一个嵌套类并将其转换为一个包含其自已的 Java 文件的*类。
要使用这个重构,请将光标放在一个嵌套类中并选择 Refactor > Convert Member Type to Top Level。如果这个嵌套类是一个静态类,那么就会立即出现一个框,显示这个重构的预览。如果它不是一个静态类,那么需要首先声明保存有 对此嵌套类的父类的引用的那个字段的名称,之后才能看到这个预览框。此外,也可以在这个框中将此字段声明为终态。
Extract Interface 重构可以从一个类的已定义的方法生成一个接口。
要使用这个重构,请从菜单中选择 Refactor > Extract Interface。这时会显示出一个要求输 入新接口名称的对话框。可以复选来自这个类且要在此接口内声明的那些方法。此对话框也允许您将所有对这个类的有效引用转换为对这个接口的引用。请注意:这 个重构只会将对这个类的有效引用转换为新的接口类型。这就意味着:如果没有选择这个类中的某个方法作为接口的一部分并且 Eclipse 检测到有一个对类的引用使用了该方法,那么这个引用将不会被转换成新的接口类型。请记住这一点,不要错误地认为对这个类的所有引用都会被转换为新的接口类 型。
Extract Superclass 重构与前面介绍过的 Extract Interface 重构很相似。只不过 Extract Superclass 重构抽取的是一个超类而不是一个接口。如果这个类已经使用了一个超类,那么新生成的超类将把该类作为它的超类,并会保持类的层次结构。
要使用这个重构,请确保光标位于这个类的方法声明或字段上,然后选择 Refactor > Extract Superclass。一个与 Extract Interface 相似的对话框会出现,可以在这个对话框中给这个新的超类命名并选择要放入这个超类的方法和字段。
抽取超类与抽取接口的最大区别在于放入超类中的方法是被实际移到那里的。所以,如果这些方法中的任何一个方法含有对原始类中的任何字段的引 用,就会得到一个编译错误,因为它们对超类是不可见的。这种情况下,最好的补救办法就是将这些被引用的字段也移到这个超类中。
Extract Method 重构允许您选择一块代码并将其转换为一个方法。Eclipse 会自动地推知方法参数及返回类型。
如果一个方法太大并且您想要把此方法再细分为不同的方法,这个重构将很有用。如果有一段代码在很多方法中反复使用,这个重构也能派上用场。当 选择这些代码块中的某一个代码块进行重构时,Eclipse 将找到出现这个代码块的其他地方,并用一个对这个新方法的调用替代它。
要使用这个重构,请选择编辑器中的一个代码块,然后按下 Alt+Shift+M。这时会出现一个对话框,要求输入这个新方法 的名称及可见性(公开的、私有的、保护的或是默认的)。甚至可以更改参数和返回类型。当重构了新方法内的所选代码块以便恰当使用新方法的参数和返回类型 后,新方法就创建完成了。首先完成重构的那个方法现在包括了一个对新方法的调用。
例如,假设我想要在调用了清单 3 中的 map.get()
后,将代码块移到另外一个方法。
@Override public Object get(Object key) { TimedKey timedKey = new TimedKey(System.currentTimeMillis(), key); Object object = map.get(timedKey); if (object != null) { /** * if this was removed after the 'get' call by the worker thread * put it back in */ map.put(timedKey, object); return object; } return null; } |
要做到这一点,请选择编辑器中的这个代码块并按下 Alt+Shift+M。将这个新方法的名称设置为 putIfNotNull()
,Eclipse 会生成清单 4 中的代码,并会自动地计算出正确的参数和返回值。
@Override public Object get(Object key) { TimedKey timedKey = new TimedKey(System.currentTimeMillis(), key); Object object = map.get(timedKey); return putIfNotNull(timedKey, object); } private Object putIfNotNull(TimedKey timedKey, Object object) { if (object != null) { /** * if this was removed after the 'get' call by the worker thread * put it back in */ map.put(timedKey, object); return object; } return null; } |
Inline 重构可以内联 对变量或方法的引用。当使用这个重构后,它会用分配给此变量的值或此方法的实现来分别替代对这个变量或方法的引用。这个重构在下列情形中将对于清理代码十 分有用:
- 当一个方法只被另一个方法调用一次,并且作为一个代码块更有意义时。
- 与把值分配给不同变量而将表达式分成多行相比较,将一个表达式放在一行上看着更整齐时。
要使用这个重构,请将光标放在一个变量或方法上,并按下 Alt+Shift+I。这时会出现一个对话框,要求确认这个重构。 如果重构的是一个方法,那么对话框还会给出一个选项,即在执行完这个重构后一并删除此方法。
例如,清单 5 中的第二行只是将一个表达式的值分配给了 timedKey
变量。
public Object put(Object key, Object value) { TimedKey timedKey = new TimedKey(System.currentTimeMillis(), key); return map.put(timedKey, value); } |
清单 6 显示执行了 Inline 重构的代码。请注意,以前的两行代码现在变成了整洁的一行代码。
@Override public Object put(Object key, Object value) { return map.put(new TimedKey(System.currentTimeMillis(), key), value); } |
利用 Change Method Signature 重构可以更改一个方法签名。同时它还将修改所有对该方法的调用以使用这个新签名。
要使用这个重构,请选择 Refactor > Change Method Signature。这时会出现一个如图 1 所示的对话框,可以在这个对话框中任意地修改这个方法,包括添加或删除参数、更改参数的顺序、更改返回值的类型、添加对此方法声明的例外,甚至更改方法的 名称。
图 1. Change Method Signature 对话框
请注意,对这个方法的某些修改,例如添加一个参数或更改一个返回类型,可能会导致重构代码的编译错误,这是因为 Eclipse 并不知道要为这些新参数输入什么。
Infer Generic Type Arguments 重构会自动地为原始形式的那些类推测恰当的泛型类型(generic type)。这个重构通常被用于将 Java 5 以前的代码转换为 Java 5 或更新的代码。
这个重构甚至可以从 Package Explorer 调用。只需右键单击 Package Explorer 中的任何一个项目、包或类,然后选择 Refactor > Infer Generic Type Arguments。
清单 7 中的代码显示了一个可以接受 Generic Type Arguments 的 ConcurrentHashMap
。 然而,清单 7 中的代码并不指定类型参数。
清单 7. Infer Generic Type Arguments 重构前
private final ConcurrentHashMap map = new ConcurrentHashMap(); |
在使用了 Infer Generic Type Arguments 重构后,Eclipse 会自动地确定正确的类型参数并生成清单 8 中的代码。
清单 8. Infer Generic Type Arguments 重构后
private final ConcurrentHashMap map = new ConcurrentHashMap(); |
Migrate JAR File 重构可被用来方便地更新在一个项目构建路径上的 Java Archive (JAR) 文件。要用一个新版本更新构建路径上的 JAR 文件,最常用的方法是:
- 进入项目的属性并将现存的 JAR 文件从这个构建路径中删除。
- 手动地从其文件夹中删除 JAR 文件。
- 复制新的 JAR 文件,并将其重新命名以便反映其在所有构建脚本中被引用时所用的那个名字。
- 手动地向构建路径添加新的 JAR 文件。
然而,用 Migrate JAR File 重构,以上这些工作只需一步就可以完成。要调用这个重构,请选择 Refactor > Migrate Jars。在出现的对话框中,选择新 JAR 文件所在的位置。在下面的树中,从项目中选择需要更新为新版本的 JAR。如果选择了 Replace Jar file contents but preserve existing filename 复选框,那么这个新 JAR 文件将被重命名以匹配旧 JAR 文件的名称,因而不会破坏任何以该名称引用这个 JAR 文件的构建脚本。在任何情况下单击 Finish 时,之前的 JAR 文件都将被删除,同时新的 JAR 文件会被复制到原 JAR 文件所在的位置,并会自动地被添加到这个项目的构建路径,以便项目能够使用这个新的 JAR 文件。
重构脚本可以让您导出并共享重构动作。当打算发布某个库的一个新版本并且人们在使用旧版本会导致错误时,重构脚本就显得很有用了。通过在发布 此库的同时发布一个重构脚本,使用旧版本的人只需将这个脚本应用于其项目,就可以使其代码使用这个新版本的库了。
要创建一个重构脚本,请选择 Refactor > Create Script。这时会出现一个如图 2 所示的窗口,显示了在这个工作区所执行过的所有重构的历史记录。选择需要的那些重构,然后为将要生成的脚本指定一个位置,再单击 Create 生成这个脚本。
要将一个已有的重构脚本应用于工作区,请选择 Refactor > Apply Script。在出现的对话框中选择 脚本的位置。单击 Next 以查看脚本将要执行的那些重构,然后单击 Finish 来应用这些重构。
举个例子,假设在 JAR 文件的版本 2 中,com.A
类被重命名为 com.B
。 由于使用 JAR 文件版本 1 的人在其代码中还存在对
com.A
的引用,如果只是简单地升级成新版本的库,无疑会破坏他们现有的代码。不过,现在可以在发布 JAR 文件的同时发布一个重构脚本,它可以自动地将对
com.A
类的引用重命名为对 com.B
的引用,这样人们就可以轻松地升级到 JAR 文件的新版本了。
有了 Eclipse 中各种各样的重构,将丑陋的代码转换成漂亮优美的代码就变得易如反掌。重构脚本可以让您轻松地进行应用程序的升级,而不必担心客户需要花上几小时的时间去 遍览文档以找出代码被破坏的原因。Eclipse 的重构功能确实优于其他的文件编辑器和 IDE。