Eclipse 中的重構功能使其成為了一個現代的 Java 集成開發環境 (IDE),而不再是一個 普通的文本編輯器。使用重構,您可以輕松更改您的代碼,而不必擔心對別處造成破壞。有 了重構,您可以只關注於所編寫代碼的功能,而不必分心去考慮代碼的外觀如何,因為之後 您可以使用重構工具來快捷地將代碼變成整潔而高度模塊化的代碼。本文將向您介紹如何使 用 Eclipse 中的一些功能強大的重構函數。
重構類型
重命名
Rename 應該是 Eclipse 中重常用的重構。利用這個重構,可以對變量、類、方法、包、 文件夾及幾乎任何的 Java 標識符進行重命名。當重命名某標識符時,對該標識符的所有引 用也將被重命名。調用 Rename 重構的快捷方式是 Alt+Shift+R。當在 Eclipse 編輯器中對 某標識符調用這個快捷方式時,在此編輯器中會出現一個小對話框,可以在這個對話框中修 改這個標識符的名字。在按下 Enter 鍵時,所有對該標識符的引用也將被相應更改。
Move
使用 Move,可以將一個類從一個包移動到另一個包。這個類被物理地移動到目的包所對 應的文件夾中,並且對這個類的所有引用也被更改為指向新的包。
如果將一個類拖放到 Package Explorer 視圖中的一個新包中,這個重構將會自動發生。
Extract Local Variable
使用 Extract Local Variable 重構,可以將一個 Java 表達式的結果分配給一個新的本 地變量。這個重構的一個用途就是通過將一個復雜的 Java 表達式分成多行來簡化該表達式 。或者,在編輯代碼時,先鍵入此表達式並使用這種重構自動創建一個新本地變量來指定表 達式的結果。當返回值的類型很復雜時,這個重構將很有用,因為變量的類型是自動生成的 。
此重構可以從編輯器調用。在鍵入想要將其分配給某變量的表達式後,按下 Ctrl+1 並選 擇 Assign statement to a local variable。這樣一個具有適當類型的新變量就創建好了。
Extract Constant
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
Extract Interface 重構可以從一個類的已定義的方法生成一個接口。
要使用這個重構,請從菜單中選擇 Refactor > Extract Interface。這時會顯示出一 個要求輸入新接口名稱的對話框。可以復選來自這個類且要在此接口內聲明的那些方法。此 對話框也允許您將所有對這個類的有效引用轉換為對這個接口的引用。請注意:這個重構只 會將對這個類的有效引用轉換為新的接口類型。這就意味著:如果沒有選擇這個類中的某個 方法作為接口的一部分並且 Eclipse 檢測到有一個對類的引用使用了該方法,那麼這個引用 將不會被轉換成新的接口類型。請記住這一點,不要錯誤地認為對這個類的所有引用都會被 轉換為新的接口類型。
Extract Superclass
Extract Superclass 重構與前面介紹過的 Extract Interface 重構很相似。只不過 Extract Superclass 重構抽取的是一個超類而不是一個接口。如果這個類已經使用了一個超 類,那麼新生成的超類將把該類作為它的超類,並會保持類的層次結構。
要使用這個重構,請確保光標位於這個類的方法聲明或字段上,然後選擇 Refactor > Extract Superclass。一個與 Extract Interface 相似的對話框會出現,可以在這個對話框 中給這個新的超類命名並選擇要放入這個超類的方法和字段。
抽取超類與抽取接口的最大區別在於放入超類中的方法是被實際移到那裡的。所以,如果 這些方法中的任何一個方法含有對原始類中的任何字段的引用,就會得到一個編譯錯誤,因 為它們對超類是不可見的。這種情況下,最好的補救辦法就是將這些被引用的字段也移到這 個超類中。
Extract Method
Extract Method 重構允許您選擇一塊代碼並將其轉換為一個方法。Eclipse 會自動地推 知方法參數及返回類型。
如果一個方法太大並且您想要把此方法再細分為不同的方法,這個重構將很有用。如果有 一段代碼在很多方法中反復使用,這個重構也能派上用場。當選擇這些代碼塊中的某一個代 碼塊進行重構時,Eclipse 將找到出現這個代碼塊的其他地方,並用一個對這個新方法的調 用替代它。
要使用這個重構,請選擇編輯器中的一個代碼塊,然後按下 Alt+Shift+M。這時會出現一 個對話框,要求輸入這個新方法的名稱及可見性(公開的、私有的、保護的或是默認的)。 甚至可以更改參數和返回類型。當重構了新方法內的所選代碼塊以便恰當使用新方法的參數 和返回類型後,新方法就創建完成了。首先完成重構的那個方法現在包括了一個對新方法的 調用。
例如,假設我想要在調用了清單 3 中的 map.get() 後,將代碼塊移到另外一個方法。
清單 3. Extract Method 重構前
@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 中的代碼,並會自動地計算出正確的參數 和返回值。
清單 4. Extract Method 重構後
@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
Inline 重構可以內聯 對變量或方法的引用。當使用這個重構後,它會用分配給此變量的 值或此方法的實現來分別替代對這個變量或方法的引用。這個重構在下列情形中將對於清理 代碼十分有用:
當一個方法只被另一個方法調用一次,並且作為一個代碼塊更有意義時。
與把值分配給不同變量而將表達式分成多行相比較,將一個表達式放在一行上看著更整齊 時。
要使用這個重構,請將光標放在一個變量或方法上,並按下 Alt+Shift+I。這時會出現一 個對話框,要求確認這個重構。如果重構的是一個方法,那麼對話框還會給出一個選項,即 在執行完這個重構後一並刪除此方法。
例如,清單 5 中的第二行只是將一個表達式的值分配給了 timedKey 變量。
清單 5. Inline 重構前
public Object put(Object key, Object value)
{
TimedKey timedKey = new TimedKey(System.currentTimeMillis(), key);
return map.put(timedKey, value);
}
清單 6 顯示執行了 Inline 重構的代碼。請注意,以前的兩行代碼現在變成了整潔的一 行代碼。
清單 6. Inline 重構後
@Override
public Object put(Object key, Object value)
{
return map.put(new TimedKey(System.currentTimeMillis(), key), value);
}
Change Method Signature
利用 Change Method Signature 重構可以更改一個方法簽名。同時它還將修改所有對該 方法的調用以使用這個新簽名。
要使用這個重構,請選擇 Refactor > Change Method Signature。這時會出現一個如 圖 1 所示的對話框,可以在這個對話框中任意地修改這個方法,包括添加或刪除參數、更改 參數的順序、更改返回值的類型、添加對此方法聲明的例外,甚至更改方法的名稱。
圖 1. Change Method Signature 對話框
請注意,對這個方法的某些修改,例如添加一個參數或更改一個返回類型,可能會導致重 構代碼的編譯錯誤,這是因為 Eclipse 並不知道要為這些新參數輸入什麼。
Infer Generic Type Arguments
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<TimedKey, Object> map =
new ConcurrentHashMap<TimedKey, Object>();
Migrate JAR File
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 生成這個腳本。
圖 2. Create Script 窗口
要將一個已有的重構腳本應用於工作區,請選擇 Refactor > Apply Script。在出現的對話框中選擇腳本的位置。單擊 Next 以查看腳本將要執行的那些重構, 然後單擊 Finish 來應用這些重構。
舉個例子,假設在 JAR 文件的版本 2 中,com.A 類被重命名為 com.B。由於使用 JAR 文件版本 1 的人在其代碼中還存在對 com.A 的引用,如果只是簡單地升級成新版本的庫, 無疑會破壞他們現有的代碼。不過,現在可以在發布 JAR 文件的同時發布一個重構腳本,它 可以自動地將對 com.A 類的引用重命名為對 com.B 的引用,這樣人們就可以輕松地升級到 JAR 文件的新版本了。
結束語
有了 Eclipse 中各種各樣的重構,將丑陋的代碼轉換成漂亮優美的代碼就變得易如反掌 。重構腳本可以讓您輕松地進行應用程序的升級,而不必擔心客戶需要花上幾小時的時間去 遍覽文檔以找出代碼被破壞的原因。Eclipse 的重構功能確實優於其他的文件編輯器和 IDE 。