Java運行時的子類識別

來源:互聯網
上載者:User

Java映像API(Reflection API)和Java 介面為編寫可重用的代碼提供了優秀的工具。以一個通用的命令啟動器為例:假設你有一組執行各種任務的類,比如關閉或開啟電燈,開啟、關閉或鎖上門,等等。這些類的名字分別是LightOn、LightOff、DoorOpen、DoorClose和DoorLock,所有這些類都實現了Command介面。

Command介面的定義如下:

public interface Command {

  public void process();

}

你可以編寫一個簡單的通用啟動器,如下所示:

public class Launcher{

  public static void main(String[] args){

        if (args.length>0) {

           try {

              Command command =

                 (Command)Class.forName(args[0]).newInstance();

              command.process();

           } catch (Exception ex) {

              System.out.println("Invalid command");

           }

        } else {

           System.out.println("Usage: Launcher <command>");

        }

     }

// Launcher


這個程式用Class.forName方法獲得參數中指定類的Class對象,然後用newInstance()方法建立該類的一個執行個體。根據要求,該類實現了Command介面,所以程式把對象定型(cast)成為Command,然後調用process()方法,由process方法執行實際任務。如果出現了異常,比如由於類的名字拼字錯誤或安全方面的問題,程式將顯示一個“Invalid command”資訊。

這個命令啟動器可以按照如下方式使用:

%java Launcher LightOn


以後如果實現了一些新的任務,命令啟動器也不需要修改。從程式員的角度來看,這確實很不錯。但是,它對於使用者來說又如何呢?假設一個使用者輸入了以下命令:

%java Launcher OpenDoor

Invalid command


“Invalid command”的意思是使用者不能開啟門嗎?不是,它只表示類命名錯誤(DoorOpen變成了OpenDoor)。所以,程式應該允許使用者查看可用命令的清單。要保證命令啟動器的通用性,使用者應該能夠在運行時尋找這些命令。

Java映像API能夠在運行時提供大量有關指定類的資訊:我們可以方便地獲知指定類的所有超類、它所實現的介面、方法、建構函式、域,等等。但在這裡,我們感興趣的是所有實現特定介面的類,這種資訊無法從Java映像API直接獲得。本文餘下的部分就為你介紹如何擷取實現了特定介面的類的資訊。

在Java中,包對應著目錄,通過File對象的list()方法擷取包含在包中的所有類是很容易的。我們的做法是利用instanceof語句進行檢查:對於包裡面的每一個類檔案,相應的類是否實現了Command介面。這意味著只檢查每一個類檔案的公用類,而且介面和它的實現必須在一個包裡面。下面是代碼:

public static void find(String pckgname) {

// 把包名字轉換成絕對路徑

String name = new String(pckgname);

if (!name.startsWith("/")) {

name = "/" + name;

}

name = name.replace('.','/');

// 獲得一個File對象

URL url = Launcher.class.getResource(name);

File directory = new File(url.getFile());

if (directory.exists()) {

// 獲得包裡面的檔案清單

String [] files = directory.list();

for (int i=0;I&lt;files.length;i++) {

// 我們只對.class檔案感興趣

if (files[i].endsWith(".class")) {

// 刪除.class副檔名

String classname = files[i].substring(0,files[i].length()-6);

try {

// 嘗試建立該對象的一個執行個體

Object o = Class.forName(pckgname+"."+classname).newInstance();

if (o instanceof Command) {

System.out.println(classname);

}

} catch (ClassNotFoundException cnfex) {

System.err.println(cnfex);

} catch (InstantiationException iex) {

// 我們試圖執行個體化一個介面或者

// 一個沒有預設建構函式的對象

} catch (IllegalAccessException iaex) {

// 該類不是公用類

}

}

}

}

}

要執行手頭的任務,我們只需稍微修改一下原來的啟動器。現在,我們可以設想介面和它的實現在一個commands包裡面:

public static void main(String[] args){

if (args.length&gt;0) {

try {

Command command = (Command)Class.forName("commands."+

args[0]).newInstance();

command.process();

} catch (Exception ex) {

System.out.println("Invalid command");

System.out.println("Available commands:");

find("commands");

}

} else {

System.out.println("Usage: Launcher &lt;command&gt;");

}

}


下面是執行錯誤的命令時,改進後的啟動器顯示的結果:

%java Launcher OpenDoor

Invalid command

Available commands:

LightOn

LightOff

DoorOpen

DoorClose

DoorLock


我們可以修改find()方法,讓它能夠尋找指定類的任何子類。為此,我們要用到instanceof的動態版本,即isInstance()。用(tosubclass.isInstance(o))替換(o instanceof Command),其中tosubclass是find()方法參數中指定的類。現在我們有了一個方法,它能夠在指定的包中找出指定類的任何子類。我們可以改進這個方法,讓它在當前已裝入的包中尋找子類。為此,我們要用到Package.getPackages()方法,這個方法精確地返回當前類裝載器裝入的各個包。然後,我們只需針對每一個包調用find()方法:

public static void find(String tosubclassname) {

try {

Class tosubclass = Class.forName(tosubclassname);

Package [] pcks = Package.getPackages();

for (int i=0;I&lt;pcks.length;i++) {

find(pcks[i].getName(),tosubclass);

}

} catch (ClassNotFoundException ex) {

System.err.println("Class "+tosubclassname+" not found!");

}

}

這個方法的返回結果主要依賴於它被調用的時間。對於本文的通用命令啟動器,調用find()方法時裝入記憶體的只有少量幾個包。例如,下面是在我的NT機器上調用find()之前裝入的包:

package java.util.zip,Java Platform API Specification,version 1.3

package java.security,Java Platform API Specification,version 1.3

package java.io,Java Platform API Specification,version 1.3

package sun.net.www.protocol.file,Java Platform API Specification,version 1.3

package sun.net.www.protocol.jar,Java Platform API Specification,version 1.3

package sun.net.www,Java Platform API Specification,version 1.3

package java.util.jar,Java Platform API Specification,version 1.3

package sun.security.action,Java Platform API Specification,version 1.3

package java.lang,Java Platform API Specification,version 1.3

package sun.io,Java Platform API Specification,version 1.3

package java.util,Java Platform API Specification,version 1.3

package sun.misc,Java Platform API Specification,version 1.3

package java.security.cert,Java Platform API Specification,version 1.3

package java.lang.reflect,Java Platform API Specification,version 1.3

package java.net,Java Platform API Specification,version 1.3

package sun.security.util,Java Platform API Specification,version 1.3

package java.lang.ref,Java Platform API Specification,version 1.3

package sun.security.provider,Java Platform API Specification,version 1.3

package com.sun.rsajca


因為在命令啟動器中,介面和它的所有實現都在同一個包裡面,裝入類之後就可以得到已經裝入的包。因此,我們可以在該包裡面搜尋子類。這是尋找相關包的唯一方法。RTSI類的完整原始碼可以在本文最後的參考資源找到。解開下載包的ZIP壓縮之後,你可以用下面的命令測試代碼:

% java -cp classes RTSI commands.Command


當包以作業系統目錄和檔案的形式存在時,前面討論的代碼能夠順利地運行;但如果類檔案在一個或者多個jar檔案裡面,這些代碼不再有效。在本文的下載代碼中,你將發現該問題的一個解決方案。你可以用下面的命令測試程式處理jar檔案的能力:

% java -jar RTSI.jar commands.Command

■ 結束語

在這篇文章中,我們討論了如何在一個指定的包裡面(或從已經裝入的包)動態地提取所有指定類的子類。這個功能不僅對於設計通用程式很有用,而且正如本文的命令啟動器執行個體所顯示的,它對使用者同樣有好處。

作者申明:一些讀者指出,本文的程式只能檢測擁有預設建構函式的子類。他們建議用Class的isAssignableFrom()方法替代isInstance()。

■ 參考資源:

  • 本文的完整原始碼
  • Java的Class文檔
  • Java的Package文檔

      (責任編輯 吳北 jiaoxq@staff.ccidnet.com)

    • 聯繫我們

      該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

      如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

      A Free Trial That Lets You Build Big!

      Start building with 50+ products and up to 12 months usage for Elastic Compute Service

      • Sales Support

        1 on 1 presale consultation

      • After-Sales Support

        24/7 Technical Support 6 Free Tickets per Quarter Faster Response

      • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.