一輩子的程序員?

          愛你一生不變-芳芳!
          posts - 27, comments - 15, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          使用Windows操作系統的朋友對Excel(電子表格)一定不會陌生,但是要使用Java語言來操縱Excel文件并不是一件容易的事。在Web應用日益盛行的今天,通過Web來操作Excel文件的需求越來越強烈,目前較為流行的操作是在JSP或Servlet?中創建一個CSV?(comma?separated?values)文件,并將這個文件以MIME,text/csv類型返回給瀏覽器,接著瀏覽器調用Excel并且顯示CSV文件。這樣只是說可以訪問到?Excel文件,但是還不能真正的操縱Excel文件,本文將給大家一個驚喜,向大家介紹一個開放源碼項目,Java?Excel?API,使用它大家就可

          ????
          以方便地操縱Excel文件了。

            Java?Excel?API簡介

            Java?Excel是一開放源碼項目,通過它Java開發人員可以讀取Excel文件的內容、創建新的Excel文件、更新已經存在的Excel文件。使用該?API非Windows操作系統也可以通過純Java應用來處理Excel數據表。因為是使用Java編寫的,所以我們在Web應用中可以通過JSP、?Servlet來調用API實現對Excel數據表的訪問。

            現在發布的穩定版本是V2.0,提供以下功能:

             從Excel?95、97、2000等格式的文件中讀取數據;

             讀取Excel公式(可以讀取Excel?97以后的公式);

             生成Excel數據表(格式為Excel?97);

             支持字體、數字、日期的格式化;

             支持單元格的陰影操作,以及顏色操作;

             修改已經存在的數據表;

            現在還不支持以下功能,但不久就會提供了:

             不能夠讀取圖表信息;

             可以讀,但是不能生成公式,任何類型公式最后的計算值都可以讀出;

             

             應用示例

            1、從Excel文件讀取數據表

            Java?Excel?API既可以從本地文件系統的一個文件(.xls),也可以從輸入流中讀取Excel數據表。讀取Excel數據表的第一步是創建Workbook(術語:工作薄),下面的代碼片段舉例說明了應該如何操作:(完整代碼見ExcelReading.java)

            import?java.io.*;

            import?jxl.*;

            …?…?…?…

            try

            {

            //構建Workbook對象,?只讀Workbook對象

            //直接從本地文件創建Workbook

            //從輸入流創建Workbook

            InputStream?is?=?new?FileInputStream(sourcefile);

            jxl.Workbook?rwb?=?Workbook.getWorkbook(is);

            }

            catch?(Exception?e)

            {

            e.printStackTrace();

            }

            一旦創建了Workbook,我們就可以通過它來訪問Excel?Sheet(術語:工作表)。參考下面的代碼片段:

            //獲取第一張Sheet表

            Sheet?rs?=?rwb.getSheet(0);

            我們既可能通過Sheet的名稱來訪問它,也可以通過下標來訪問它。如果通過下標來訪問的話,要注意的一點是下標從0開始,就像數組一樣。

            一旦得到了Sheet,我們就可以通過它來訪問Excel?Cell(術語:單元格)。參考下面的代碼片段:

            //獲取第一行,第一列的值

            Cell?c00?=?rs.getCell(0,?0);

            String?strc00?=?c00.getContents();

            //獲取第一行,第二列的值

            Cell?c10?=?rs.getCell(1,?0);

            String?strc10?=?c10.getContents();

            //獲取第二行,第二列的值

            Cell?c11?=?rs.getCell(1,?1);

            String?strc11?=?c11.getContents();

            System.out.println("Cell(0,?0)"?+?"?value?:?"?+?strc00?+?";?type?:?"?+?c00.getType());

            System.out.println("Cell(1,?0)"?+?"?value?:?"?+?strc10?+?";?type?:?"?+?c10.getType());

            System.out.println("Cell(1,?1)"?+?"?value?:?"?+?strc11?+?";?type?:?"?+?c11.getType());?如果僅僅是取得Cell的值,我們可以方便地通過getContents()方法,它可以將任何類型的Cell值都作為一個字符串返回。示例代碼中Cell(0,?0)是文本型,Cell(1,?0)是數字型,Cell(1,1)是日期型,通過getContents(),三種類型的返回值都是字符型。
          ????
          ????

            如果有需要知道Cell內容的確切類型,API也提供了一系列的方法。參考下面的代碼片段:

            String?strc00?=?null;

            double?strc10?=?0.00;

            Date?strc11?=?null;

            Cell?c00?=?rs.getCell(0,?0);

            Cell?c10?=?rs.getCell(1,?0);

            Cell?c11?=?rs.getCell(1,?1);

            if(c00.getType()?==?CellType.LABEL)

            {

            LabelCell?labelc00?=?(LabelCell)c00;

            strc00?=?labelc00.getString();

            }

            if(c10.getType()?==?CellType.NUMBER)

            {

            NmberCell?numc10?=?(NumberCell)c10;

            strc10?=?numc10.getvalue();

            }

            if(c11.getType()?==?CellType.DATE)

            {

            DateCell?datec11?=?(DateCell)c11;

            strc11?=?datec11.getDate();

            }

            System.out.println("Cell(0,?0)"?+?"?value?:?"?+?strc00?+?";?type?:?"?+?c00.getType());

            System.out.println("Cell(1,?0)"?+?"?value?:?"?+?strc10?+?";?type?:?"?+?c10.getType());

            System.out.println("Cell(1,?1)"?+?"?value?:?"?+?strc11?+?";?type?:?"?+?c11.getType());

            在得到Cell對象后,通過getType()方法可以獲得該單元格的類型,然后與?API提供的基本類型相匹配,強制轉換成相應的類型,最后調用相應的取值方法getXXX(),就可以得到確定類型的值。API提供了以下基本類型,與?Excel的數據格式相對應,如下圖所示:

            每種類型的具體意義,請參見Java?Excel?API?document.

            當你完成對Excel電子表格數據的處理后,一定要使用close()方法來關閉先前創建的對象,以釋放讀取數據表的過程中所占用的內存空間,在讀取大量數據時顯得尤為重要。參考如下代碼片段:

            //操作完成時,關閉對象,釋放占用的內存空間

            rwb.close();

            Java?Excel?API提供了許多訪問Excel數據表的方法,在這里我只簡要地介紹幾個常用的方法,其它的方法請參考附錄中的Java?Excel?API?document.

            Workbook類提供的方法

            1.?int?getNumberOfSheets()

            獲得工作薄(Workbook)中工作表(Sheet)的個數,示例:

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            int?sheets?=?rwb.getNumberOfSheets();

            2.?Sheet[]?getSheets()

            返回工作薄(Workbook)中工作表(Sheet)對象數組,示例:

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            Sheet[]?sheets?=?rwb.getSheets();

            3.?String?getVersion()

            返回正在使用的API的版本號,好像是沒什么太大的作用。

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            String?apiVersion?=?rwb.getVersion();

            Sheet接口提供的方法

            1)?String?getName()

            獲取Sheet的名稱,示例:

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            jxl.Sheet?rs?=?rwb.getSheet(0);

            String?sheetName?=?rs.getName();

            2)?int?getColumns()

            獲取Sheet表中所包含的總列數,示例:

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            jxl.Sheet?rs?=?rwb.getSheet(0);

            int?rsColumns?=?rs.getColumns();

            3)?Cell[]?getColumn(int?column)

            獲取某一列的所有單元格,返回的是單元格對象數組,示例:

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            jxl.Sheet?rs?=?rwb.getSheet(0);

            Cell[]?cell?=?rs.getColumn(0);

            4)?int?getRows()

            獲取Sheet表中所包含的總行數,示例:

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            jxl.Sheet?rs?=?rwb.getSheet(0);

            int?rsRows?=?rs.getRows();

            5)?Cell[]?getRow(int?row)

            獲取某一行的所有單元格,返回的是單元格對象數組,示例子:

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            jxl.Sheet?rs?=?rwb.getSheet(0);

            Cell[]?cell?=?rs.getRow(0);

            6)?Cell?getCell(int?column,?int?row)

            獲取指定單元格的對象引用,需要注意的是它的兩個參數,第一個是列數,第二個是行數,這與通常的行、列組合有些不同。

            jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            jxl.Sheet?rs?=?rwb.getSheet(0);

            Cell?cell?=?rs.getCell(0,?0);?2、生成新的Excel工作薄

            下面的代碼主要是向大家介紹如何生成簡單的Excel工作表,在這里單元格的內容是不帶任何修飾的(如:字體,顏色等等),所有的內容都作為字符串寫入。(完整代碼見ExcelW

          ????
          riting.java)

            與讀取Excel工作表相似,首先要使用Workbook類的工廠方法創建一個可寫入的工作薄(Workbook)對象,這里要注意的是,只能通過API提供的工廠方法來創建Workbook,而不能使用?WritableWorkbook的構造函數,因為類WritableWorkbook的構造函數為protected類型。示例代碼片段如下:

            import?java.io.*;

            import?jxl.*;

            import?jxl.write.*;

            …?…?…?…

            try

            {

            //構建Workbook對象,?只讀Workbook對象

            //Method?1:創建可寫入的Excel工作薄

            jxl.write.WritableWorkbook?wwb?=?Workbook.createWorkbook(new?File(targetfile));

            //Method?2:將WritableWorkbook直接寫入到輸出流

            /*

            OutputStream?os?=?new?FileOutputStream(targetfile);

            jxl.write.WritableWorkbook?wwb?=?Workbook.createWorkbook(os);

            */

            }

            catch?(Exception?e)

            {

            e.printStackTrace();

            }

            API提供了兩種方式來處理可寫入的輸出流,一種是直接生成本地文件,如果文件名不帶全路徑的話,缺省的文件會定位在當前目錄,如果文件名帶有全路徑的話,則生成的Excel文件則會定位在相應的目錄;另外一種是將Excel對象直接寫入到輸出流,例如:用戶通過瀏覽器來訪問Web服務器,如果HTTP頭設置正確的話,瀏覽器自動調用客戶端的Excel應用程序,來顯示動態生成的?Excel電子表格。

            接下來就是要創建工作表,創建工作表的方法與創建工作薄的方法幾乎一樣,同樣是通過工廠模式方法獲得相應的對象,該方法需要兩個參數,一個是工作表的名稱,另一個是工作表在工作薄中的位置,參考下面的代碼片段:

            //創建Excel工作表

            jxl.write.WritableSheet?ws?=?wwb.createSheet("Test?Sheet?1",?0);

            "這鍋也支好了,材料也準備齊全了,可以開始下鍋了!",現在要做的只是實例化API所提供的Excel基本數據類型,并將它們添加到工作表中就可以了,參考下面的代碼片段:

            //1.添加Label對象

            jxl.write.Label?labelC?=?new?jxl.write.Label(0,?0,?"This?is?a?Label?cell");

            ws.addCell(labelC);

            //添加帶有字型Formatting的對象

            jxl.write.WritableFont?wf?=?new?jxl.write.WritableFont(WritableFont.TIMES,?18,?WritableFont.BOLD,?true);

            jxl.write.WritableCellFormat?wcfF?=?new?jxl.write.WritableCellFormat(wf);

            jxl.write.Label?labelCF?=?new?jxl.write.Label(1,?0,?"This?is?a?Label?Cell",?wcfF);

            ws.addCell(labelCF);

            //添加帶有字體顏色Formatting的對象

            jxl.write.WritableFont?wfc?=?new?jxl.write.WritableFont(WritableFont.ARIAL,?10,?WritableFont.NO_BOLD,?false,

            Underlinestyle.NO_UNDERLINE,?jxl.format.Colour.RED);

            jxl.write.WritableCellFormat?wcfFC?=?new?jxl.write.WritableCellFormat(wfc);

            jxl.write.Label?labelCFC?=?new?jxl.write.Label(1,?0,?"This?is?a?Label?Cell",?wcfFC);

            ws.addCell(labelCF);

            //2.添加Number對象

            jxl.write.Number?labelN?=?new?jxl.write.Number(0,?1,?3.1415926);

            ws.addCell(labelN);

            //添加帶有formatting的Number對象

            jxl.write.NumberFormat?nf?=?new?jxl.write.NumberFormat("#.##");

            jxl.write.WritableCellFormat?wcfN?=?new?jxl.write.WritableCellFormat(nf);

            jxl.write.Number?labelNF?=?new?jxl.write.Number(1,?1,?3.1415926,?wcfN);

            ws.addCell(labelNF);

            //3.添加Boolean對象

            jxl.write.Boolean?labelB?=?new?jxl.write.Boolean(0,?2,?false);

            ws.addCell(labelB);

            //4.添加DateTime對象

            jxl.write.DateTime?labelDT?=?new?jxl.write.DateTime(0,?3,?new?java.util.Date());

            ws.addCell(labelDT);

            //添加帶有formatting的DateFormat對象

            jxl.write.DateFormat?df?=?new?jxl.write.DateFormat("dd?MM?yyyy?hh:mm:ss");

            jxl.write.WritableCellFormat?wcfDF?=?new?jxl.write.WritableCellFormat(df);

            jxl.write.DateTime?labelDTF?=?new?jxl.write.DateTime(1,?3,?new?java.util.Date(),?wcfDF);

            ws.addCell(labelDTF);?這里有兩點大家要引起大家的注意。第一點,在構造單元格時,單元格在工作表中的位置就已經確定了。一旦創建后,單元格的位置是不能夠變更的,盡管單元格的內容是可以改變的。第二點,單元格的定位是按照下面這樣的規律(column,?row),而且下標都是從0開始,例如,A1被存儲在(0,?0),B1被存儲在(1,?0)。

          ????

            最后,不要忘記關閉打開的Excel工作薄對象,以釋放占用的內存,參見下面的代碼片段:

            //寫入Exel工作表

            wwb.write();

            //關閉Excel工作薄對象

            wwb.close();

            這可能與讀取Excel文件的操作有少少不同,在關閉Excel對象之前,你必須要先調用write()方法,因為先前的操作都是存儲在緩存中的,所以要通過該方法將操作的內容保存在文件中。如果你先關閉了Excel對象,那么只能得到一張空的工作薄了。

            3、拷貝、更新Excel工作薄

            接下來簡要介紹一下如何更新一個已經存在的工作薄,主要是下面二步操作,第一步是構造只讀的Excel工作薄,第二步是利用已經創建的Excel工作薄創建新的可寫入的Excel工作薄,參考下面的代碼片段:(完整代碼見ExcelModifying.java)

            //創建只讀的Excel工作薄的對象

            jxl.Workbook?rw?=?jxl.Workbook.getWorkbook(new?File(sourcefile));

            //創建可寫入的Excel工作薄對象

            jxl.write.WritableWorkbook?wwb?=?Workbook.createWorkbook(new?File(targetfile),?rw);

            //讀取第一張工作表

            jxl.write.WritableSheet?ws?=?wwb.getSheet(0);

            //獲得第一個單元格對象

            jxl.write.WritableCell?wc?=?ws.getWritableCell(0,?0);

            //判斷單元格的類型,?做出相應的轉化

            if(wc.getType()?==?CellType.LABEL)

            {

            Label?l?=?(Label)wc;

            l.setString("The?value?has?been?modified.");

            }

            //寫入Excel對象

            wwb.write();

            //關閉可寫入的Excel對象

            wwb.close();

            //關閉只讀的Excel對象

            rw.close();?之所以使用這種方式構建Excel對象,完全是因為效率的原因,因為上面的示例才是?API的主要應用。為了提高性能,在讀取工作表時,與數據相關的一些輸出信息,所有的格式信息,如:字體、顏色等等,是不被處理的,因為我們的目的是獲得行數據的值,既使沒有了修飾,也不會對行數據的值產生什么影響。唯一的不利之處就是,在內存中會同時保存兩
          ????
          個同樣的工作表,這樣當工作表體積比較大時,會占用相當大的內存,但現在好像內存的大小并不是什么關鍵因素了。

            一旦獲得了可寫入的工作表對象,我們就可以對單元格對象進行更新的操作了,在這里我們不必調用API提供的add()方法,因為單元格已經于工作表當中,所以我們只需要調用相應的setXXX()方法,就可以完成更新的操作了。

            盡單元格原有的格式化修飾是不能去掉的,我們還是可以將新的單元格修飾加上去,以使單元格的內容以不同的形式表現。

            新生成的工作表對象是可寫入的,我們除了更新原有的單元格外,還可以添加新的單元格到工作表中,這與示例2的操作是完全一樣的。

            最后,不要忘記調用write()方法,將更新的內容寫入到文件中,然后關閉工作薄對象,這里有兩個工作薄對象要關閉,一個是只讀的,另外一個是可寫入的。

          posted @ 2006-09-04 14:17 boddi 閱讀(262) | 評論 (0)編輯 收藏

          jxl的一些總結

          要往xls文件里面寫入數據的時候需要注意的是第一要新建一個xls文件
          OutputStream os=new FileOutputStream("c:\\excel2.xls");

          再建完這個文件的時候再建立工作文件
          jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(os));

          如果這個文件已經存在,那么我們可以在這個文件里面加入一個sheet為了和以前的數據進行分開;
          jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);
          在createSheet方法里前面的參數是sheet名,后面是要操作的sheet號

          接下來就可以往這個文件里面寫入數據了


          寫入數據的時候注意的格式


          (1)添加的字體樣式
          jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
          WritableFont()方法里參數說明:
          這個方法算是一個容器,可以放進去好多屬性
          第一個: TIMES是字體大小,他寫的是18
          第二個: BOLD是判斷是否為斜體,選擇true時為斜體
          第三個: ARIAL
          第四個: UnderlineStyle.NO_UNDERLINE 下劃線
          第五個: jxl.format.Colour.RED 字體顏色是紅色的

          jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);

          jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell",wcfF);
          ws.addCell(labelC);
          在Label()方法里面有三個參數
          第一個是代表列數,
          第二是代表行數,
          第***要寫入的內容
          第四個是可選項,是輸入這個label里面的樣式
          然后通過寫sheet的方法addCell()把內容寫進sheet里面。

          (2)添加帶有formatting的Number對象
          jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");


          (3)添加Number對象
          (3.1)顯示number對象數據的格式

          jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
          jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);

          jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
          ws.addCell(labelNF);
          Number()方法參數說明:
          前兩上表示輸入的位置
          第三個表示輸入的內容


          (4)添加Boolean對象
          jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
          ws.addCell(labelB);


          (5)添加DateTime對象
          jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date());
          ws.addCell(labelDT);
          DateTime()方法的參數說明
          前兩個表示輸入的位置
          第三個表示輸入的當前時間


          (6)添加帶有formatting的DateFormat對象
          這個顯示當前時間的所有信息,包括年月日小時分秒
          jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
          jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
          jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF);
          ws.addCell(labelDTF);

          (7)添加帶有字體顏色Formatting的對象
          jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.RED);
          jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);

          import="jxl.format.*
          jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL,20,WritableFont.BOLD,false,UnderlineStyle.NO_UNDERLINE,jxl.format.Colour.GREEN);

          (8)設置單元格樣式

          jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
          wcfFC.setBackGround(jxl.format.Colour.RED);//設置單元格的顏色為紅色
          wcfFC = new jxl.write.Label(6,0,"i love china",wcfFC);


          Jxl在寫excel文件時使用的方法比較怪,也可以說jxl不支持修改excel文件。它的處理方式是每次打開舊excel文件,然后創建一個該excel文件的可寫的副本,所有的修改都是在這個副本上做的。下面是一個例子。

          posted @ 2006-09-04 14:17 boddi 閱讀(1046) | 評論 (0)編輯 收藏

          Java中static、this、super、final用法
          作者:未知??來源:未知??發布時間:2006-7-14 21:33:56??發布人:sany

          減小字體 增大字體

          一、static?
            請先看下面這段程序:
          ??public?class?Hello{
          ????public?static?void?main(String[]?args){?//(1)
          ??????System.out.println("Hello,world!");???//(2)
          ????}
          ??}
            看過這段程序,對于大多數學過Java?的從來說,都不陌生。即使沒有學過Java,而學過其它的高級語言,例如C,那你也應該能看懂這段代碼的意思。它只是簡單的輸出“Hello,world”,一點別的用處都沒有,然而,它卻展示了static關鍵字的主要用法。
            在1處,我們定義了一個靜態的方法名為main,這就意味著告訴Java編譯器,我這個方法不需要創建一個此類的對象即可使用。你還得你是怎么運行這個程序嗎?一般,我們都是在命令行下,打入如下的命令(加下劃線為手動輸入):
          javac?Hello.java
          java?Hello
          Hello,world!
            這就是你運行的過程,第一行用來編譯Hello.java這個文件,執行完后,如果你查看當前,會發現多了一個Hello.class文件,那就是第一行產生的Java二進制字節碼。第二行就是執行一個Java程序的最普遍做法。執行結果如你所料。在2中,你可能會想,為什么要這樣才能輸出。好,我們來分解一下這條語句。(如果沒有安裝Java文檔,請到Sun的官方網站瀏覽J2SE?API)首先,System是位于java.lang包中的一個核心類,如果你查看它的定義,你會發現有這樣一行:public?static?final?PrintStream?out;接著在進一步,點擊PrintStream這個超鏈接,在METHOD頁面,你會看到大量定義的方法,查找println,會有這樣一行:
          public?void?println(String?x)。
            好了,現在你應該明白為什么我們要那樣調用了,out是System的一個靜態變量,所以可以直接使用,而out所屬的類有一個println方法。
          靜態方法
            通常,在一個類中定義一個方法為static,那就是說,無需本類的對象即可調用此方法。如下所示:
          class?Simple{
          ???static?void?go(){
          ?????System.out.println("Go...");
          ???}
          }
          public?class?Cal{
          ??public?static?void?main(String[]?args){
          ????Simple.go();
          ??}
          }
            調用一個靜態方法就是“類名.方法名”,靜態方法的使用很簡單如上所示。一般來說,靜態方法常常為應用程序中的其它類提供一些實用工具所用,在Java的類庫中大量的靜態方法正是出于此目的而定義的。
          靜態變量
            靜態變量與靜態方法類似。所有此類實例共享此靜態變量,也就是說在類裝載時,只分配一塊存儲空間,所有此類的對象都可以操控此塊存儲空間,當然對于final則另當別論了。看下面這段代碼:
          class?Value{
          ??static?int?c=0;
          ??static?void?inc(){
          ????c++;
          ??}
          }
          class?Count{
          ??public?static?void?prt(String?s){
          ????System.out.println(s);
          ??}
          ??public?static?void?main(String[]?args){
          ????Value?v1,v2;
          ????v1=new?Value();
          ????v2=new?Value();
          ????prt("v1.c="+v1.c+"??v2.c="+v2.c);
          ????v1.inc();
          ????prt("v1.c="+v1.c+"??v2.c="+v2.c);??
          ??}
          }
            結果如下:
          v1.c=0??v2.c=0
          v1.c=1??v2.c=1
            由此可以證明它們共享一塊存儲區。static變量有點類似于C中的全局變量的概念。值得探討的是靜態變量的初始化問題。我們修改上面的程序:
          class?Value{
          ??static?int?c=0;
          ??Value(){
          ????c=15;
          ??}
          ??Value(int?i){
          ????c=i;
          ??}
          ??static?void?inc(){
          ????c++;
          ??}
          }
          class?Count{
          ??public?static?void?prt(String?s){
          ????System.out.println(s);
          ??}
          ????Value?v=new?Value(10);
          ????static?Value?v1,v2;
          ????static{
          ??????prt("v1.c="+v1.c+"??v2.c="+v2.c);
          ??????v1=new?Value(27);
          ??????prt("v1.c="+v1.c+"??v2.c="+v2.c);
          ??????v2=new?Value(15);
          ??????prt("v1.c="+v1.c+"??v2.c="+v2.c);
          ????}
          ??public?static?void?main(String[]?args){
          ????Count?ct=new?Count();
          ????prt("ct.c="+ct.v.c);
          ????prt("v1.c="+v1.c+"??v2.c="+v2.c);
          ????v1.inc();
          ????prt("v1.c="+v1.c+"??v2.c="+v2.c);
          ????prt("ct.c="+ct.v.c);
          ??}
          }
          運行結果如下:
          v1.c=0??v2.c=0
          v1.c=27??v2.c=27
          v1.c=15??v2.c=15
          ct.c=10
          v1.c=10??v2.c=10
          v1.c=11??v2.c=11
          ct.c=11
            這個程序展示了靜態初始化的各種特性。如果你初次接觸Java,結果可能令你吃驚。可能會對static后加大括號感到困惑。首先要告訴你的是,static定義的變量會優先于任何其它非static變量,不論其出現的順序如何。正如在程序中所表現的,雖然v出現在v1和v2的前面,但是結果卻是v1和v2的初始化在v的前面。在static{后面跟著一段代碼,這是用來進行顯式的靜態變量初始化,這段代碼只會初始化一次,且在類被第一次裝載時。如果你能讀懂并理解這段代碼,會幫助你對static關鍵字的認識。在涉及到繼承的時候,會先初始化父類的static變量,然后是子類的,依次類推。非靜態變量不是本文的主題,在此不做詳細討論,請參考Think?in?Java中的講解。
          靜態類
            通常一個普通類不允許聲明為靜態的,只有一個內部類才可以。這時這個聲明為靜態的內部類可以直接作為一個普通類來使用,而不需實例一個外部類。如下代碼所示:
          public?class?StaticCls{
          ??public?static?void?main(String[]?args){
          ????OuterCls.InnerCls?oi=new?OuterCls.InnerCls();
          ??}
          }
          class?OuterCls{
          ??public?static?class?InnerCls{
          ????InnerCls(){
          ??????System.out.println("InnerCls");
          ????}
          ???}
          }
            輸出結果會如你所料:
          InnerCls
            和普通類一樣。內部類的其它用法請參閱Think?in?Java中的相關章節,此處不作詳解。
          二、this?&?super
            在上一篇拙作中,我們討論了static的種種用法,通過用static來定義方法或成員,為我們編程提供了某種便利,從某種程度上可以說它類似于C語言中的全局函數和全局變量。但是,并不是說有了這種便利,你便可以隨處使用,如果那樣的話,你便需要認真考慮一下自己是否在用面向對象的思想編程,自己的程序是否是面向對象的。好了,現在開始討論this&super這兩個關鍵字的意義和用法。
            在Java中,this通常指當前對象,super則指父類的。當你想要引用當前對象的某種東西,比如當前對象的某個方法,或當前對象的某個成員,你便可以利用this來實現這個目的,當然,this的另一個用途是調用當前對象的另一個構造函數,這些馬上就要討論。如果你想引用父類的某種東西,則非super莫屬。由于this與super有如此相似的一些特性和與生俱來的某種關系,所以我們在這一塊兒來討論,希望能幫助你區分和掌握它們兩個。
          在一般方法中
            最普遍的情況就是,在你的方法中的某個形參名與當前對象的某個成員有相同的名字,這時為了不至于混淆,你便需要明確使用this關鍵字來指明你要使用某個成員,使用方法是“this.成員名”,而不帶this的那個便是形參。另外,還可以用“this.方法名”來引用當前對象的某個方法,但這時this就不是必須的了,你可以直接用方法名來訪問那個方法,編譯器會知道你要調用的是那一個。下面的代碼演示了上面的用法:
          public?class?DemoThis{
          ??private?String?name;
          ??private?int?age;
          ??DemoThis(String?name,int?age){
          ????setName(name);?//你可以加上this來調用方法,像這樣:this.setName(name);但這并不是必須的
          ????setAge(age);
          ????this.print();
          ??}???
          ??public?void?setName(String?name){
          ????this.name=name;//此處必須指明你要引用成員變量
          ??}
          ??public?void?setAge(int?age){
          ????this.age=age;
          ??}
          ??public?void?print(){
          ????System.out.println("Name="+name+"?Age="+age);//在此行中并不需要用this,因為沒有會導致混淆的東西
          ??}
          ??public?static?void?main(String[]?args){
          ????DemoThis?dt=new?DemoThis("Kevin","22");
          ??}
          }
            這段代碼很簡單,不用解釋你也應該能看明白。在構造函數中你看到用this.print(),你完全可以用print()來代替它,兩者效果一樣。下面我們修改這個程序,來演示super的用法。
          class?Person{
          ??public?int?c;
          ??private?String?name;
          ??private?int?age;
          ??protected?void?setName(String?name){
          ????this.name=name;
          ??}
          ??protected?void?setAge(int?age){
          ????this.age=age;
          ??}
          ??protected?void?print(){
          ????System.out.println("Name="+name+"?Age="+age);
          ??}
          }
          public?class?DemoSuper?extends?Person{
          ??public?void?print(){
          ????System.out.println("DemoSuper:");
          ????super.print();
          ??}
          ??public?static?void?main(String[]?args){
          ????DemoSuper?ds=new?DemoSuper();
          ????ds.setName("kevin");
          ????ds.setAge(22);
          ????ds.print();
          ??}
          }
            在DemoSuper中,重新定義的print方法覆寫了父類的print方法,它首先做一些自己的事情,然后調用父類的那個被覆寫了的方法。輸出結果說明了這一點:
          DemoSuper:
          Name=kevin?Age=22
            這樣的使用方法是比較常用的。另外如果父類的成員可以被子類訪問,那你可以像使用this一樣使用它,用“super.父類中的成員名”的方式,但常常你并不是這樣來訪問父類中的成員名的。
          在構造函數中
            構造函數是一種特殊的方法,在對象初始化的時候自動調用。在構造函數中,this和super也有上面說的種種使用方式,并且它還有特殊的地方,請看下面的例子:
          class?Person{
          ??public?static?void?prt(String?s){
          ????System.out.println(s);
          ??}
          ??Person(){
          ????prt("A?Person.");
          ??}
          ??Person(String?name){
          ????prt("A?person?name?is:"+name);
          ??}
          }
          public?class?Chinese?extends?Person{
          ??Chinese(){
          ????super();??//調用父類構造函數(1)
          ????prt("A?chinese.");//(4)
          ??}
          ??Chinese(String?name){
          ????super(name);//調用父類具有相同形參的構造函數(2)
          ????prt("his?name?is:"+name);
          ??}
          ??Chinese(String?name,int?age){
          ????this(name);//調用當前具有相同形參的構造函數(3)
          ????prt("his?age?is:"+age);
          ??}
          ??public?static?void?main(String[]?args){
          ????Chinese?cn=new?Chinese();
          ????cn=new?Chinese("kevin");
          ????cn=new?Chinese("kevin",22);
          ??}
          }
            在這段程序中,this和super不再是像以前那樣用“.”連接一個方法或成員,而是直接在其后跟上適當的參數,因此它的意義也就有了變化。super后加參數的是用來調用父類中具有相同形式的構造函數,如1和2處。this后加參數則調用的是當前具有相同參數的構造函數,如3處。當然,在Chinese的各個重載構造函數中,this和super在一般方法中的各種用法也仍可使用,比如4處,你可以將它替換為“this.prt”(因為它繼承了父類中的那個方法)或者是“super.prt”(因為它是父類中的方法且可被子類訪問),它照樣可以正確運行。但這樣似乎就有點畫蛇添足的味道了。
            最后,寫了這么多,如果你能對“this通常指代當前對象,super通常指代父類”這句話牢記在心,那么本篇便達到了目的,其它的你自會在以后的編程實踐當中慢慢體會、掌握。另外關于本篇中提到的繼承,請參閱相關Java教程。
          三、final
            final在Java中并不常用,然而它卻為我們提供了諸如在C語言中定義常量的功能,不僅如此,final還可以讓你控制你的成員、方法或者是一個類是否可被覆寫或繼承等功能,這些特點使final在Java中擁有了一個不可或缺的地位,也是學習Java時必須要知道和掌握的關鍵字之一。
          final成員
            當你在類中定義變量時,在其前面加上final關鍵字,那便是說,這個變量一旦被初始化便不可改變,這里不可改變的意思對基本類型來說是其值不可變,而對于對象變量來說其引用不可再變。其初始化可以在兩個地方,一是其定義處,也就是說在final變量定義時直接給其賦值,二是在構造函數中。這兩個地方只能選其一,要么在定義時給值,要么在構造函數中給值,不能同時既在定義時給了值,又在構造函數中給另外的值。下面這段代碼演示了這一點:
          import?java.util.List;
          import?java.util.ArrayList;
          import?java.util.LinkedList;
          public?class?Bat{
          ????final?PI=3.14;??????????//在定義時便給址值
          ????final?int?i;????????????//因為要在構造函數中進行初始化,所以此處便不可再給值
          ????final?List?list;????????//此變量也與上面的一樣
          ????Bat(){
          ????????i=100;
          ????????list=new?LinkedList();
          ????}
          ????Bat(int?ii,List?l){
          ????????i=ii;
          ????????list=l;
          ????}
          ????public?static?void?main(String[]?args){
          ????????Bat?b=new?Bat();
          ????????b.list.add(new?Bat());
          ????????//b.i=25;
          ????????//b.list=new?ArrayList();
          ????????System.out.println("I="+b.i+"?List?Type:"+b.list.getClass());
          ????????b=new?Bat(23,new?ArrayList());
          ????????b.list.add(new?Bat());
          ????????System.out.println("I="+b.i+"?List?Type:"+b.list.getClass());
          ????}
          }
            此程序很簡單的演示了final的常規用法。在這里使用在構造函數中進行初始化的方法,這使你有了一點靈活性。如Bat的兩個重載構造函數所示,第一個缺省構造函數會為你提供默認的值,重載的那個構造函數會根據你所提供的值或類型為final變量初始化。然而有時你并不需要這種靈活性,你只需要在定義時便給定其值并永不變化,這時就不要再用這種方法。在main方法中有兩行語句注釋掉了,如果你去掉注釋,程序便無法通過編譯,這便是說,不論是i的值或是list的類型,一旦初始化,確實無法再更改。然而b可以通過重新初始化來指定i的值或list的類型,輸出結果中顯示了這一點:
          I=100?List?Type:class?java.util.LinkedList
          I=23?List?Type:class?java.util.ArrayList
            還有一種用法是定義方法中的參數為final,對于基本類型的變量,這樣做并沒有什么實際意義,因為基本類型的變量在調用方法時是傳值的,也就是說你可以在方法中更改這個參數變量而不會影響到調用語句,然而對于對象變量,卻顯得很實用,因為對象變量在傳遞時是傳遞其引用,這樣你在方法中對對象變量的修改也會影響到調用語句中的對象變量,當你在方法中不需要改變作為參數的對象變量時,明確使用final進行聲明,會防止你無意的修改而影響到調用方法。
          另外方法中的內部類在用到方法中的參變量時,此參變也必須聲明為final才可使用,如下代碼所示:
          public?class?INClass{
          ???void?innerClass(final?String?str){
          ????????class?IClass{
          ????????????IClass(){
          ????????????????System.out.println(str);
          ????????????}
          ????????}
          ????????IClass?ic=new?IClass();
          ????}
          ??public?static?void?main(String[]?args){
          ??????INClass?inc=new?INClass();
          ??????inc.innerClass("Hello");
          ??}
          }
          final方法
            將方法聲明為final,那就說明你已經知道這個方法提供的功能已經滿足你要求,不需要進行擴展,并且也不允許任何從此類繼承的類來覆寫這個方法,但是繼承仍然可以繼承這個方法,也就是說可以直接使用。另外有一種被稱為inline的機制,它會使你在調用final方法時,直接將方法主體插入到調用處,而不是進行例行的方法調用,例如保存斷點,壓棧等,這樣可能會使你的程序效率有所提高,然而當你的方法主體非常龐大時,或你在多處調用此方法,那么你的調用主體代碼便會迅速膨脹,可能反而會影響效率,所以你要慎用final進行方法定義。
          final類
            當你將final用于類身上時,你就需要仔細考慮,因為一個final類是無法被任何人繼承的,那也就意味著此類在一個繼承樹中是一個葉子類,并且此類的設計已被認為很完美而不需要進行修改或擴展。對于final類中的成員,你可以定義其為final,也可以不是final。而對于方法,由于所屬類為final的關系,自然也就成了final型的。你也可以明確的給final類中的方法加上一個final,但這顯然沒有意義。
            下面的程序演示了final方法和final類的用法:
          final?class?final{
          ????final?String?str="final?Data";
          ????public?String?str1="non?final?data";
          ????final?public?void?print(){
          ????????System.out.println("final?method.");
          ????}
          ????public?void?what(){
          ????????System.out.println(str+"\n"+str1);
          ????}
          }
          public?class?FinalDemo?{???//extends?final?無法繼承?
          ????public?static?void?main(String[]?args){
          ????????final?f=new?final();
          ????????f.what();
          ????????f.print();
          ????}
          }
            從程序中可以看出,final類與普通類的使用幾乎沒有差別,只是它失去了被繼承的特性。final方法與非final方法的區別也很難從程序行看出,只是記住慎用。
          final在設計模式中的應用
            在設計模式中有一種模式叫做不變模式,在Java中通過final關鍵字可以很容易的實現這個模式,在講解final成員時用到的程序Bat.java就是一個不變模式的例子。如果你對此感興趣,可以參考閻宏博士編寫的《Java與模式》一書中的講解。
            到此為止,this,static,supert和final的使用已經說完了,如果你對這四個關鍵字已經能夠大致說出它們的區別與用法,那便說明你基本已經掌握。然而,世界上的任何東西都不是完美無缺的,Java提供這四個關鍵字,給程序員的編程帶來了很大的便利,但并不是說要讓你到處使用,一旦達到濫用的程序,便適得其反,所以在使用時請一定要認真考慮。

          posted @ 2006-08-30 14:16 boddi 閱讀(199) | 評論 (0)編輯 收藏

          Java中this、super用法簡談
           通過用static來定義方法或成員,為我們編程提供了某種便利,從某種程度上可以說它類似于C語言中的全局函數和全局變量。但是,并不是說有了這種便利,你便可以隨處使用,如果那樣的話,你便需要認真考慮一下自己是否在用面向對象的思想編程,自己的程序是否是面向對象的。好了,現在開始討論this&super這兩個關鍵字的意義和用法。
            在Java中,this通常指當前對象,super則指父類的。當你想要引用當前對象的某種東西,比如當前對象的某個方法,或當前對象的某個成員,你便可以利用this來實現這個目的,當然,this的另一個用途是調用當前對象的另一個構造函數,這些馬上就要討論。如果你想引用父類的某種東西,則非super莫屬。由于this與super有如此相似的一些特性和與生俱來的某種關系,所以我們在這一塊兒來討論,希望能幫助你區分和掌握它們兩個。
            在一般方法中
            最普遍的情況就是,在你的方法中的某個形參名與當前對象的某個成員有相同的名字,這時為了不至于混淆,你便需要明確使用this關鍵字來指明你要使用某個成員,使用方法是“this.成員名”,而不帶this的那個便是形參。另外,還可以用“this.方法名”來引用當前對象的某個方法,但這時this就不是必須的了,你可以直接用方法名來訪問那個方法,編譯器會知道你要調用的是那一個。下面的代碼演示了上面的用法:
            public class DemoThis{ 
          private String name; 
          private int age; 
          DemoThis(String name,int age){  
          setName(name);
          //你可以加上this來調用方法,像這樣:this.setName(name);但這并不是必須的  
          setAge(age);  
          this.print(); br> }  
          public void setName(String name){  
          this.name=name;//此處必須指明你要引用成員變量 
          }
          public void etAge(int age){ 
          this.age=age; 
          } 
          public void print(){  
          System.out.println("Name="+name+" ge="+age);
          //在此行中并不需要用this,因為沒有會導致混淆的東西 
          } 
          public static void main(String[] args){  
          DemoThis dt=new DemoThis("Kevin","22");
            這段代碼很簡單,不用解釋你也應該能看明白。在構造函數中你看到用this.print(),你完全可以用print()來代替它,兩者效果一樣。下面我們修改這個程序,來演示super的用法。
            class Person{ 
          public int c; 
          private String name; 
          private int age;
          protected void setName(String name){  
          this.name=name; 
          } 
          protected void setAge(int age){ 
          this.age=age;
           }
          protected void print(){  
          System.out.println("Name="+name+" Age="+age);
          }
          }
          public class DemoSuper extends Person{ 
          public void print(){  
          System.out.println("DemoSuper:"); 
          super.print();
          } 
          public static void main(String[] args){ 
          DemoSuper ds=new DemoSuper(); 
          ds.setName("kevin"); 
          ds.setAge(22); 
          ds.print();
          }
          }
            在DemoSuper中,重新定義的print方法覆寫了父類的print方法,它首先做一些自己的事情,然后調用父類的那個被覆寫了的方法。輸出結果說明了這一點:
            DemoSuper:
          Name=kevin Age=22

            這樣的使用方法是比較常用的。另外如果父類的成員可以被子類訪問,那你可以像使用this一樣使用它,用“super.父類中的成員名”的方式,但常常你并不是這樣來訪問父類中的成員名的。
            在構造函數中構造函數是一種特殊的方法,在對象初始化的時候自動調用。在構造函數中,this和super也有上面說的種種使用方式,并且它還有特殊的地方,請看下面的例子:

            
          class Person{ 

          public static void prt(String s){  
          System.out.println(s); 
          } 
          Person(){ 
          prt("A Person."); 
          }
          Person(String name){ 
           prt("A person name is:"+name); 

          }
          }
          public class Chinese extends Person{
           Chinese(){  
          super(); //調用父類構造函數(1) 
          prt("A chinese.");//(4)
          } 
          Chinese(String name){  
          super(name);//調用父類具有相同形參的構造函數(2)  
          prt("his name is:"+name);
          }
          Chinese(String name,int age){  
          this(name);//調用當前具有相同形參的構造函數(3) 
          prt("his age is:"+age);
          }
          public static void main(String[] args){ 
          Chinese cn=new Chinese();  
          cn=new Chinese("kevin"); 
          cn=new Chinese("kevin",22);
          }
          }
            在這段程序中,this和super不再是像以前那樣用“.”連接一個方法或成員,而是直接在其后跟
            上適當的參數,因此它的意義也就有了變化。super后加參數的是用來調用父類中具有相同形式的
            構造函數,如1和2處。this后加參數則調用的是當前具有相同參數的構造函數,如3處。當然,在
            Chinese的各個重載構造函數中,this和super在一般方法中的各種用法也仍可使用,比如4處,你
            可以將它替換為“this.prt”(因為它繼承了父類中的那個方法)或者是“super.prt”(因為它
            是父類中的方法且可被子類訪問),它照樣可以正確運行。但這樣似乎就有點畫蛇添足的味道
            了。
            最后,寫了這么多,如果你能對“this通常指代當前對象,super通常指代父類”這句話牢記在
            心,那么本篇便達到了目的,其它的你自會在以后的編程實踐當中慢慢體會、掌握。另外關于本
            篇中提到的繼承,請參閱相關Java教程。
            

          posted @ 2006-08-30 12:16 boddi 閱讀(150) | 評論 (0)編輯 收藏

          泊船瓜洲 @ 2005-08-30 18:08

          Java中的類反射機制 (轉帖)
          一、反射的概念 :
          反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力。這一概念的提出很快引發了計算機科學領域關于應用反射性的研究。它首先被程序語言的設計領域所采用,并在Lisp和面向對象方面取得了成績。其中LEAD/LEAD++ 、OpenC++ 、 MetaXa和OpenJava等就是基于反射機制的語言。最近,反射機制也被應用到了視窗系統、操作系統和文件系統中。

          反射本身并不是一個新概念,它可能會使我們聯想到光學中的反射概念,盡管計算機科學賦予了反射概念新的含義,但是,從現象上來說,它們確實有某些相通之處,這些有助于我們的理解。在計算機科學領域,反射是指一類應用,它們能夠自描述和自控制。也就是說,這類應用通過采用某種機制來實現對自己行為的描述(self-representation)和監測(examination),并能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。可以看出,同一般的反射概念相比,計算機科學領域的反射不單單指反射本身,還包括對反射結果所采取的措施。所有采用反射機制的系統(即反射系統)都希望使系統的實現更開放。可以說,實現了反射機制的系統都具有開放性,但具有開放性的系統并不一定采用了反射機制,開放性是反射系統的必要條件。一般來說,反射系統除了滿足開放性條件外還必須滿足原因連接(Causally-connected)。所謂原因連接是指對反射系統自描述的改變能夠立即反映到系統底層的實際狀態和行為上的情況,反之亦然。開放性和原因連接是反射系統的兩大基本要素。13700863760

          Java中,反射是一種強大的工具。它使您能夠創建靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代表鏈接。反射允許我們在編寫與執行時,使我們的程序代碼能夠接入裝載到JVM中的類的內部信息,而不是源代碼中選定的類協作的代碼。這使反射成為構建靈活的應用的主要工具。但需注意的是:如果使用不當,反射的成本很高。

          二、Java中的類反射:
          Reflection 是 Java 程序開發語言的特征之一,它允許運行中的 Java 程序對自身進行檢查,或者說“自審”,并能直接操作程序的內部屬性。Java 的這一能力在實際應用中也許用得不是很多,但是在其它的程序設計語言中根本就不存在這一特性。例如,Pascal、C 或者 C++ 中就沒有辦法在程序中獲得函數定義相關的信息。

          1.檢測類:

          1.1 reflection的工作機制

          考慮下面這個簡單的例子,讓我們看看 reflection 是如何工作的。

          import java.lang.reflect.*;
          public class DumpMethods {
          ???public static void main(String args[]) {
          ???????try {
          ???????????Class c = Class.forName(args[0]);
          ???????????Method m[] = c.getDeclaredMethods();
          ???????????for (int i = 0; i < m.length; i++)
          ???????????????System.out.println(m.toString());
          ???????} catch (Throwable e) {
          ???????????System.err.println(e);
          ???????}
          ???}
          }

          按如下語句執行:

          java DumpMethods java.util.Stack

          它的結果輸出為:

          public java.lang.Object java.util.Stack.push(java.lang.Object)

          public synchronized java.lang.Object java.util.Stack.pop()

          public synchronized java.lang.Object java.util.Stack.peek()

          public boolean java.util.Stack.empty()

          public synchronized int java.util.Stack.search(java.lang.Object)

          這樣就列出了java.util.Stack 類的各方法名以及它們的限制符和返回類型。

          這個程序使用 Class.forName 載入指定的類,然后調用 getDeclaredMethods 來獲取這個類中定義了的方法列表。java.lang.reflect.Methods 是用來描述某個類中單個方法的一個類。

          1.2 Java類反射中的主要方法

          對于以下三類組件中的任何一類來說 -- 構造函數、字段和方法 -- java.lang.Class 提供四種獨立的反射調用,以不同的方式來獲得信息。調用都遵循一種標準格式。以下是用于查找構造函數的一組反射調用:

          l ????????Constructor getConstructor(Class[] params) -- 獲得使用特殊的參數類型的公共構造函數,

          l ????????Constructor[] getConstructors() -- 獲得類的所有公共構造函數

          l ????????Constructor getDeclaredConstructor(Class[] params) -- 獲得使用特定參數類型的構造函數(與接入級別無關)

          l ????????Constructor[] getDeclaredConstructors() -- 獲得類的所有構造函數(與接入級別無關)

          獲得字段信息的Class 反射調用不同于那些用于接入構造函數的調用,在參數類型數組中使用了字段名:

          l ????????Field getField(String name) -- 獲得命名的公共字段

          l ????????Field[] getFields() -- 獲得類的所有公共字段

          l ????????Field getDeclaredField(String name) -- 獲得類聲明的命名的字段

          l ????????Field[] getDeclaredFields() -- 獲得類聲明的所有字段

          用于獲得方法信息函數:

          l ????????Method getMethod(String name, Class[] params) -- 使用特定的參數類型,獲得命名的公共方法

          l ????????Method[] getMethods() -- 獲得類的所有公共方法

          l ????????Method getDeclaredMethod(String name, Class[] params) -- 使用特寫的參數類型,獲得類聲明的命名的方法

          l ????????Method[] getDeclaredMethods() -- 獲得類聲明的所有方法



          1.3開始使用 Reflection:

          用于 reflection 的類,如 Method,可以在 java.lang.relfect 包中找到。使用這些類的時候必須要遵循三個步驟:第一步是獲得你想操作的類的 java.lang.Class 對象。在運行中的 Java 程序中,用 java.lang.Class 類來描述類和接口等。

          下面就是獲得一個 Class 對象的方法之一:

          Class c = Class.forName("java.lang.String");

          這條語句得到一個 String 類的類對象。還有另一種方法,如下面的語句:

          Class c = int.class;

          或者

          Class c = Integer.TYPE;

          它們可獲得基本類型的類信息。其中后一種方法中訪問的是基本類型的封裝類 (如 Integer) 中預先定義好的 TYPE 字段。

          第二步是調用諸如 getDeclaredMethods 的方法,以取得該類中定義的所有方法的列表。

          一旦取得這個信息,就可以進行第三步了——使用 reflection API 來操作這些信息,如下面這段代碼:

          Class c = Class.forName("java.lang.String");

          Method m[] = c.getDeclaredMethods();

          System.out.println(m[0].toString());

          它將以文本方式打印出 String 中定義的第一個方法的原型。

          2.處理對象:

          如果要作一個開發工具像debugger之類的,你必須能發現filed values,以下是三個步驟:

          a.創建一個Class對象
          b.通過getField 創建一個Field對象
          c.調用Field.getXXX(Object)方法(XXX是Int,Float等,如果是對象就省略;Object是指實例).

          例如:
          import java.lang.reflect.*;
          import java.awt.*;

          class SampleGet {

          ??public static void main(String[] args) {
          ?????Rectangle r = new Rectangle(100, 325);
          ?????printHeight(r);

          ??}

          ??static void printHeight(Rectangle r) {
          ?????Field heightField;
          ?????Integer heightvalue;
          ?????Class c = r.getClass();
          ?????try {
          ???????heightField = c.getField("height");
          ???????heightvalue = (Integer) heightField.get(r);
          ???????System.out.println("Height: " + heightvalue.toString());
          ?????} catch (NoSuchFieldException e) {
          ?????????System.out.println(e);
          ?????} catch (SecurityException e) {
          ?????????System.out.println(e);
          ?????} catch (IllegalAccessException e) {
          ?????????System.out.println(e);
          ?????}
          ??}
          }



          三、安全性和反射:
          在處理反射時安全性是一個較復雜的問題。反射經常由框架型代碼使用,由于這一點,我們可能希望框架能夠全面接入代碼,無需考慮常規的接入限制。但是,在其它情況下,不受控制的接入會帶來嚴重的安全性風險,例如當代碼在不值得信任的代碼共享的環境中運行時。

          由于這些互相矛盾的需求,Java編程語言定義一種多級別方法來處理反射的安全性。基本模式是對反射實施與應用于源代碼接入相同的限制:

          n ????????從任意位置到類公共組件的接入

          n ????????類自身外部無任何到私有組件的接入

          n ????????受保護和打包(缺省接入)組件的有限接入

          不過至少有些時候,圍繞這些限制還有一種簡單的方法。我們可以在我們所寫的類中,擴展一個普通的基本類 java.lang.reflect.AccessibleObject 類。這個類定義了一種setAccessible方法,使我們能夠啟動或關閉對這些類中其中一個類的實例的接入檢測。唯一的問題在于如果使用了安全性管理器,它將檢測正在關閉接入檢測的代碼是否許可了這樣做。如果未許可,安全性管理器拋出一個例外。

          下面是一段程序,在TwoString 類的一個實例上使用反射來顯示安全性正在運行:

          public class ReflectSecurity {

          ???public static void main(String[] args) {

          ???????try {

          ???????????TwoString ts = new TwoString("a", "b");

          ???????????Field field = clas.getDeclaredField("m_s1");

          // ?????????field.setAccessible(true);

          ???????????System.out.println("Retrieved value is " +

          ???????????????field.get(inst));

          ???????} catch (Exception ex) {

          ???????????ex.printStackTrace(System.out);

          ???????}

          ???}

          }

          如果我們編譯這一程序時,不使用任何特定參數直接從命令行運行,它將在field .get(inst)調用中拋出一個 IllegalAccessException異常。如果我們不注釋field.setAccessible(true)代碼行,那么重新編譯并重新運行該代碼,它將編譯成功。最后,如果我們在命令行添加了JVM參數-Djava.security.manager以實現安全性管理器,它仍然將不能通過編譯,除非我們定義了ReflectSecurity類的許可權限。

          四、反射性能:
          反射是一種強大的工具,但也存在一些不足。一個主要的缺點是對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執行相同的操作。

          下面的程序是字段接入性能測試的一個例子,包括基本的測試方法。每種方法測試字段接入的一種形式 -- accessSame 與同一對象的成員字段協作,accessOther 使用可直接接入的另一對象的字段,accessReflection 使用可通過反射接入的另一對象的字段。在每種情況下,方法執行相同的計算 -- 循環中簡單的加/乘順序。

          程序如下:

          public int accessSame(int loops) {

          ???m_value = 0;

          ???for (int index = 0; index < loops; index++) {

          ???????m_value = (m_value + ADDITIVE_value) *

          ???????????MULTIPLIER_value;

          ???}

          ???return m_value;

          }



          public int accessReference(int loops) {

          ???TimingClass timing = new TimingClass();

          ???for (int index = 0; index < loops; index++) {

          ???????timing.m_value = (timing.m_value + ADDITIVE_value) *

          ???????????MULTIPLIER_value;

          ???}

          ???return timing.m_value;

          }



          public int accessReflection(int loops) throws Exception {

          ???TimingClass timing = new TimingClass();

          ???try {

          ???????Field field = TimingClass.class.

          ???????????getDeclaredField("m_value");

          ???????for (int index = 0; index < loops; index++) {

          ???????????int value = (field.getInt(timing) +

          ???????????????ADDITIVE_value) * MULTIPLIER_value;

          ???????????field.setInt(timing, value);

          ???????}

          ???????return timing.m_value;

          ???} catch (Exception ex) {

          ???????System.out.println("Error using reflection");

          ???????throw ex;

          ???}

          }

          在上面的例子中,測試程序重復調用每種方法,使用一個大循環數,從而平均多次調用的時間衡量結果。平均值中不包括每種方法第一次調用的時間,因此初始化時間不是結果中的一個因素。下面的圖清楚的向我們展示了每種方法字段接入的時間:

          圖 1:字段接入時間 :

          我們可以看出:在前兩副圖中(Sun JVM),使用反射的執行時間超過使用直接接入的1000倍以上。通過比較,IBM JVM可能稍好一些,但反射方法仍舊需要比其它方法長700倍以上的時間。任何JVM上其它兩種方法之間時間方面無任何顯著差異,但IBM JVM幾乎比Sun JVM快一倍。最有可能的是這種差異反映了Sun Hot Spot JVM的專業優化,它在簡單基準方面表現得很糟糕。反射性能是Sun開發1.4 JVM時關注的一個方面,它在反射方法調用結果中顯示。在這類操作的性能方面,Sun 1.4.1 JVM顯示了比1.3.1版本很大的改進。

          如果為為創建使用反射的對象編寫了類似的計時測試程序,我們會發現這種情況下的差異不象字段和方法調用情況下那么顯著。使用newInstance()調用創建一個簡單的java.lang.Object實例耗用的時間大約是在Sun 1.3.1 JVM上使用new Object()的12倍,是在 IBM 1.4.0 JVM的四倍,只是Sun 1.4.1 JVM上的兩部。使用Array.newInstance(type, size)創建一個數組耗用的時間是任何測試的JVM上使用new type[size]的兩倍,隨著數組大小的增加,差異逐步縮小。

          結束語:
          Java語言反射提供一種動態鏈接程序組件的多功能方法。它允許程序創建和控制任何類的對象(根據安全性限制),無需提前硬編碼目標類。這些特性使得反射特別適用于創建以非常普通的方式與對象協作的庫。例如,反射經常在持續存儲對象為數據庫、XML或其它外部格式的框架中使用。 Java reflection 非常有用,它使類和數據結構能按名稱動態檢索相關信息,并允許在運行著的程序中操作這些信息。Java 的這一特性非常強大,并且是其它一些常用語言,如 C、C++、Fortran 或者 Pascal 等都不具備的。

          但反射有兩個缺點。第一個是性能問題。用于字段和方法接入時反射要遠慢于直接代碼。性能問題的程度取決于程序中是如何使用反射的。如果它作為程序運行中相對很少涉及的部分,緩慢的性能將不會是一個問題。即使測試中最壞情況下的計時圖顯示的反射操作只耗用幾微秒。僅反射在性能關鍵的應用的核心邏輯中使用時性能問題才變得至關重要。

          許多應用中更嚴重的一個缺點是使用反射會模糊程序內部實際要發生的事情。程序人員希望在源代碼中看到程序的邏輯,反射等繞過了源代碼的技術會帶來維護問題。反射代碼比相應的直接代碼更復雜,正如性能比較的代碼實例中看到的一樣。解決這些問題的最佳方案是保守地使用反射——僅在它可以真正增加靈活性的地方 ——記錄其在目標類中的使用。





          利用反射實現類的動態加載


          Bromon原創 請尊重版權

          最近在成都寫一個移動增值項目,俺負責后臺server端。功能很簡單,手機用戶通過GPRS打開Socket與服務器連接,我則根據用戶傳過來的數據做出響應。做過類似項目的兄弟一定都知道,首先需要定義一個類似于MSNP的通訊協議,不過今天的話題是如何把這個系統設計得具有高度的擴展性。由于這個項目本身沒有進行過較為完善的客戶溝通和需求分析,所以以后肯定會有很多功能上的擴展,通訊協議肯定會越來越龐大,而我作為一個不那么勤快的人,當然不想以后再去修改寫好的程序,所以這個項目是實踐面向對象設計的好機會。

          首先定義一個接口來隔離類:

          package org.bromon.reflect;

          public interface Operator

          {

          public java.util.List act(java.util.List params)

          }

          根據設計模式的原理,我們可以為不同的功能編寫不同的類,每個類都繼承Operator接口,客戶端只需要針對Operator接口編程就可以避免很多麻煩。比如這個類:

          package org.bromon.reflect.*;

          public class Success implements Operator

          {

          public java.util.List act(java.util.List params)

          {

          List result=new ArrayList();

          result.add(new String(“操作成功”));

          return result;

          }

          }

          我們還可以寫其他很多類,但是有個問題,接口是無法實例化的,我們必須手動控制具體實例化哪個類,這很不爽,如果能夠向應用程序傳遞一個參數,讓自己去選擇實例化一個類,執行它的act方法,那我們的工作就輕松多了。

          很幸運,我使用的是Java,只有Java才提供這樣的反射機制,或者說內省機制,可以實現我們的無理要求。編寫一個配置文件emp.properties:

          #成功響應

          1000=Success

          #向客戶發送普通文本消息

          2000=Load

          #客戶向服務器發送普通文本消息

          3000=Store

          文件中的鍵名是客戶將發給我的消息頭,客戶發送1000給我,那么我就執行Success類的act方法,類似的如果發送2000給我,那就執行Load 類的act方法,這樣一來系統就完全符合開閉原則了,如果要添加新的功能,完全不需要修改已有代碼,只需要在配置文件中添加對應規則,然后編寫新的類,實現act方法就ok,即使我棄這個項目而去,它將來也可以很好的擴展。這樣的系統具備了非常良好的擴展性和可插入性。

          下面這個例子體現了動態加載的功能,程序在執行過程中才知道應該實例化哪個類:

          package org.bromon.reflect.*;

          import java.lang.reflect.*;

          public class TestReflect

          {

          //加載配置文件,查詢消息頭對應的類名

          private String loadProtocal(String header)

          {

          String result=null;

          try

          {

          Properties prop=new Properties();

          FileInputStream fis=new FileInputStream("emp.properties");

          prop.load(fis);

          result=prop.getProperty(header);

          fis.close();

          }catch(Exception e)

          {

          System.out.println(e);

          }

          return result;

          }

          //針對消息作出響應,利用反射導入對應的類

          public String response(String header,String content)

          {

          String result=null;

          String s=null;

          try

          {

          /*

          * 導入屬性文件emp.properties,查詢header所對應的類的名字

          * 通過反射機制動態加載匹配的類,所有的類都被Operator接口隔離

          * 可以通過修改屬性文件、添加新的類(繼承MsgOperator接口)來擴展協議

          */

          s="org.bromon.reflect."+this.loadProtocal(header);

          //加載類

          Class c=Class.forName(s);

          //創建類的事例

          Operator mo=(Operator)c.newInstance();

          //構造參數列表

          Class params[]=new Class[1];

          params[0]=Class.forName("java.util.List");

          //查詢act方法

          Method m=c.getMethod("act",params);

          Object args[]=new Object[1];

          args[0]=content;

          //調用方法并且獲得返回

          Object returnObject=m.invoke(mo,args);

          }catch(Exception e)

          {

          System.out.println("Handler-response:"+e);

          }

          return result;

          }

          public static void main(String args[])

          {

          TestReflect tr=new TestReflect();

          tr.response(args[0],”消息內容”);

          }

          }

          測試一下:java TestReflect 1000

          這個程序是針對Operator編程的,所以無需做任何修改,直接提供Load和Store類,就可以支持2000、3000做參數的調用。

          有了這樣的內省機制,可以把接口的作用發揮到極至,設計模式也更能體現出威力,而不僅僅供我們飯后閑聊。

          posted @ 2006-08-30 10:52 boddi 閱讀(148) | 評論 (0)編輯 收藏

          array(數組)和Vector是十分相似的Java構件(constructs),兩者全然不同,在選擇使用時應根據各自的功能來確定。

          1、數組:Java arrays的元素個數不能下標越界,從很大程度上保證了Java程序的安全性,而其他一些語言出現這一問題時常導致災難性的后果。
          ??????? Array可以存放Object和基本數據類型,但創建時必須指定數組的大小,并不能再改變。值得注意的是:當Array中的某一元素存放的是Objrct reference?時,Java不會調用默認的構造函數,而是將其初值設為null,當然這跟Java對各類型數據賦默認值的規則是一樣的,對基本數據類型同樣適用。

          2、Vector:對比于Array,當更多的元素被加入進來以至超出其容量時,Vector的size會動態增長,而Array容量是定死的。同時,Vector在刪除一些元素后,其所有下標大于被刪除元素的元素都依次前移,并獲得新下標比原來的小了)。注意:當調用Vector的size()方法時,返回Vector中實際元素的個數。
          ???? Vector內部實際是以Array實現的,也通過元素的整數索引來訪問元素,但它只能存放java.lang.Object對象,不能用于存放基本類型數據,比如要存放一個整數10,得用new Integer(10)構造出一個Integer包裝類對象再放進去。當Vector中的元素個數發生變化時, 其內部的Array必須重新分配并進行拷貝,因此這是一點值得考慮的效率問題。
          ???? Vetor同時也實現了List接口,所以也可以算作Colletion了,只是它還特殊在:Vector is synchronized。即Vetor對象自身實現了同步機制。

          3、ArrayList:實現了List接口,功能與Vetor一樣,只是沒有同步機制,當然元素的訪問方式為從List中繼承而來,可存放任何類型的對象。

          4、HashMap:繼承了Map接口,實現用Keys來存儲和訪問Values,Keys和Values都可以為空,它與Hashtable類的區別在于Hashtable類的Keys不能為null,并Hashtable類有同步機制控制,而HashMap類沒有。
          ????? 在Struts類庫中實現了一個LableValueBean,用Lable(Key)來存儲和訪問Value,很方便。

          posted @ 2006-08-30 10:40 boddi 閱讀(584) | 評論 (0)編輯 收藏

           本文提供一個項目中的錯誤實例,提供對其觀察和分析,揭示出Java語言實例化一個對象具體過程,最后總結出設計Java類的一個重要規則。通過閱讀本文,可以使Java程序員理解Java對象的構造過程,從而設計出更加健壯的代碼。本文適合Java初學者和需要提高的Java程序員閱讀。

            程序擲出了一個異常

            作者曾經在一個項目里面向項目組成員提供了一個抽象的對話框基類,使用者只需在子類中實現基類的一個抽象方法來畫出顯示數據的界面,就可使項目內的對話框具有相同的風格。具體的代碼實現片斷如下(為了簡潔起見,省略了其他無關的代碼):

          public abstract class BaseDlg extends JDialog {
          public BaseDlg(Frame frame, String title) {
          super(frame, title, true);
          this.getContentPane().setLayout(new BorderLayout());
          this.getContentPane().add(createHeadPanel(), BorderLayout.NORTH);
          this.getContentPane().add(createClientPanel(), BorderLayout.CENTER);
          this.getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
          }

          private JPanel createHeadPanel() {
          ... // 創建對話框頭部
          }

          // 創建對話框客戶區域,交給子類實現
          protected abstract JPanel createClientPanel();

          private JPanel createButtonPanel {
          ... // 創建按鈕區域
          }
          }

            這個類在有的代碼中工作得很好,但一個同事在使用時,程序卻擲出了一個NullPointerException違例!經過比較,找出了工作正常和不正常的程序的細微差別,代碼片斷分別如下:

            一、正常工作的代碼:

          public class ChildDlg1 extends BaseDlg {
           JTextField jTextFieldName;
           public ChildDlg1() {
            super(null, "Title");
           }
           public JPanel createClientPanel() {
            jTextFieldName = new JTextField();
            JPanel panel = new JPanel(new FlowLayout());
            panel.add(jTextFieldName);
            ... // 其它代碼
            return panel;
           }
           ...
          }
          ChildDlg1 dlg = new ChildDlg1(frame, "Title"); // 外部的調用

            二、工作不正常的代碼:

          public class ChildDlg2 extends BaseDlg {
           JTextField jTextFieldName = new JTextField();
           public ChildDlg2() {
            super(null, "Title");
           }
           public JPanel createClientPanel() {
            JPanel panel = new JPanel(new FlowLayout());
            panel.add(jTextFieldName);
            ... // 其它代碼
            return panel;
           }
           ...
          }
          ChildDlg2 dlg = new ChildDlg2(); // 外部的調用

            你看出來兩段代碼之間的差別了嗎?對了,兩者的差別僅僅在于類變量jTextFieldName的初始化時間。經過跟蹤,發現在執行panel.add(jTextFieldName)語句之時,jTextFieldName確實是空值。

            我們知道,Java允許在定義類變量的同時給變量賦初始值。系統運行過程中需要創建一個對象的時候,首先會為對象分配內存空間,然后在“先于調用任何方法之前”根據變量在類內的定義順序來初始化變量,接著再調用類的構造方法。那么,在本例中,為什么在變量定義時便初始化的代碼反而會出現空指針違例呢?

            對象的創建過程和初始化

            實際上,前面提到的“變量初始化發生在調用任何方法包括構造方法之前”這句話是不確切的,當我們把眼光集中在單個類上時,該說法成立;然而,當把視野擴大到具有繼承關系的兩個或多個類上時,該說法不成立。

            對象的創建一般有兩種方式,一種是用new操作符,另一種是在一個Class對象上調用newInstance方法;其創建和初始化的實際過程是一樣的:

            首先為對象分配內存空間,包括其所有父類的可見或不可見的變量的空間,并初始化這些變量為默認值,如int類型為0,boolean類型為false,對象類型為null;

            然后用下述5個步驟來初始化這個新對象:

            1)分配參數給指定的構造方法;

            2)如果這個指定的構造方法的第一個語句是用this指針顯式地調用本類的其它構造方法,則遞歸執行這5個步驟;如果執行過程正常則跳到步驟5;

            3)如果構造方法的第一個語句沒有顯式調用本類的其它構造方法,并且本類不是Object類(Object是所有其它類的祖先),則調用顯式(用super指針)或隱式地指定的父類的構造方法,遞歸執行這5個步驟;如果執行過程正常則跳到步驟5;

            4)按照變量在類內的定義順序來初始化本類的變量,如果執行過程正常則跳到步驟5;

            5)執行這個構造方法中余下的語句,如果執行過程正常則過程結束。

            這一過程可以從下面的時序圖中獲得更清晰的認識:

            通過實例學習Java對象的構造過程

            對分析本文的實例最重要的,用一句話說,就是“父類的構造方法調用發生在子類的變量初始化之前”。可以用下面的例子來證明:

          // Petstore.java
          class Animal {
           Animal() {
            System.out.println("Animal");
           }
          }
          class Cat extends Animal {
           Cat() {
            System.out.println("Cat");
           }
          }
          class Store {
           Store() {
            System.out.println("Store");
           }
          }
          public class Petstore extends Store{
           Cat cat = new Cat();
           Petstore() {
            System.out.println("Petstore");
           }
           public static void main(String[] args) {
            new Petstore();
           }
          }

            運行這段代碼,它的執行結果如下:

            Store
            Animal
            Cat
            Petstore

            從結果中可以看出,在創建一個Petstore類的實例時,首先調用了它的父類Store的構造方法;然后試圖創建并初始化變量cat;在創建cat時,首先調用了Cat類的父類Animal的構造方法;其后才是Cat的構造方法主體,最后才是Petstore類的構造方法的主體。
          尋找程序產生例外的原因

            現在回到本文開始提到的實例中來,當程序創建一個ChildDlg2的實例時,根據super(null, “Title”)語句,首先執行其父類BaseDlg的構造方法;在BaseDlg的構造方法中調用了createClientPanel()方法,
          這個方法是抽象方法并且被子類ChildDlg2實現了,因此,實際調用的方法是ChildDlg2中的createClientPanel()方法(因為Java里面采用“動態綁定”來綁定所有非final的方法);createClientPanel()方法使用了ChildDlg2類的實例變量jTextFieldName,而此時ChildDlg2的變量初始化過程尚未進行,jTextFieldName是null值!所以,ChildDlg2的構造過程擲出一個NullPointerException也就不足為奇了。

            再來看ChildDlg1,它的jTextFieldName的初始化代碼寫在了createClientPanel()方法內部的開始處,這樣它就能保證在使用之前得到正確的初始化,因此這段代碼工作正常。

            解決問題的兩種方式

            通過上面的分析過程可以看出,要排除故障,最簡單的方法就是要求項目組成員在繼承使用BaseDlg類,實現createClientPanel()方法時,凡方法內部要使用的變量必須首先正確初始化,就象ChildDlg1一樣。然而,把類變量放在類方法內初始化是一種很不好的設計行為,它最適合的地方就是在變量定義塊和構造方法中。

            在本文的實例中,引發錯誤的實質并不在ChildDlg2上,而在其父類BaseDlg上,是它在自己的構造方法中不適當地調用了一個待實現的抽象方法。

            從概念上講,構造方法的職責是正確初始化類變量,讓對象進入可用狀態。而BaseDlg卻賦給了構造方法額外的職責。

            本文實例的更好的解決方法是修改BaseDlg類:

          public abstract class BaseDlg extends JDialog {
           public BaseDlg(Frame frame, String title) {
            super(frame, title, true);
            this.getContentPane().setLayout(new BorderLayout());
            this.getContentPane().add(createHeadPanel(), BorderLayout.NORTH);
            this.getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
           }

           /** 創建對話框實例后,必須調用此方法來布局用戶界面
           */
           public void initGUI() {
            this.getContentPane().add(createClientPanel(), BorderLayout.CENTER);
           }

           private JPanel createHeadPanel() {
            ... // 創建對話框頭部
           }

           // 創建對話框客戶區域,交給子類實現
           protected abstract JPanel createClientPanel();

           private JPanel createButtonPanel {
            ... // 創建按鈕區域
           }
          }

            新的BaseDlg類增加了一個initGUI()方法,程序員可以這樣使用這個類:

          ChildDlg dlg = new ChildDlg();
          dlg.initGUI();
          dlg.setVisible(true);

            總結

            類的構造方法的基本目的是正確初始化類變量,不要賦予它過多的職責。

            設計類構造方法的基本規則是:用盡可能簡單的方法使對象進入就緒狀態;如果可能,避免調用任何方法。在構造方法內唯一能安全調用的是基類中具有final屬性的方法或者private方法(private方法會被編譯器自動設置final屬性)。final的方法因為不能被子類覆蓋,所以不會產生問題。
          所以那些有子類實現的方法盡量不要放在父類的構造函數中,一定要注意設計好構造函數。

          posted @ 2006-08-29 10:24 boddi 閱讀(182) | 評論 (0)編輯 收藏

          這個問題一直困擾我很久,一直沒有完全的理解:

          abstractclass和interface是Java語言中對于抽象類定義進行支持的兩種機制,正是由于這兩種機制的存在,才賦予了Java強大的面向對象能力。

          abstractclass和interface之間在對于抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發者在進行抽象類定義時對于abstractclass和interface的選擇顯得比較隨意。其實,兩者之間還是有很大的區別的,對于它們的選擇甚至反映出對于問題領域本質的理解、對于設計意圖的理解是否正確、合理。本文將對它們之間的區別進行一番剖析,試圖給開發者提供一個在二者之間進行選擇的依據。

          理解抽象類

          abstractclass和interface在Java語言中都是用來進行抽象類(本文中的抽象類并非從abstractclass翻譯而來,它表示的是一個抽象體,而abstractclass為Java語言中用于定義抽象類的一種方法,請讀者注意區分)定義的,那么什么是抽象類,使用抽象類能為我們帶來什么好處呢?

          在面向對象的概念中,我們知道所有的對象都是通過類來描繪的,但是反過來卻不是這樣。并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類往往用來表征我們在對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。比如:如果我們進行一個圖形編輯軟件的開發,就會發現問題領域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬于形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是因為抽象的概念在問題領域沒有對應的具體概念,所以用以表征抽象概念的抽象類是不能夠實例化的。

          在面向對象領域,抽象類主要用來進行類型隱藏。我們可以構造出一個固定的一組行為的抽象描述,但是這組行為卻能夠有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現為所有可能的派生類。模塊可以操作一個抽象體。由于模塊依賴于一個固定的抽象體,因此它可以是不允許修改的;同時,通過從這個抽象體派生,也可擴展此模塊的行為功能。熟悉OCP的讀者一定知道,為了能夠實現面向對象設計的一個最核心的原則OCP(Open-ClosedPrinciple),抽象類是其中的關鍵所在。

          從語法定義層面看abstractclass和interface

          在語法層面,Java語言對于abstractclass和interface給出了不同的定義方式,下面以定義一個名為Demo的抽象類為例來說明這種不同。

          使用abstractclass的方式定義Demo抽象類的方式如下:

          abstractclassDemo{

          abstractvoidmethod1();

          abstractvoidmethod2();

          使用interface的方式定義Demo抽象類的方式如下:

          interfaceDemo{

          voidmethod1();

          voidmethod2();

          }

          在abstractclass方式中,Demo可以有自己的數據成員,也可以有非abstarct的成員方法,而在interface方式的實現中,Demo只能夠有靜態的不能被修改的數據成員(也就是必須是staticfinal的,不過在interface中一般不定義數據成員),所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstractclass。

          從編程的角度來看,abstractclass和interface都可以用來實現"designbycontract"的思想。但是在具體的使用上面還是有一些區別的。

          首先,abstractclass在Java語言中表示的是一種繼承關系,一個類只能使用一次繼承關系。但是,一個類卻可以實現多個interface。也許,這是Java語言的設計者在考慮Java對于多重繼承的支持方面的一種折中考慮吧。

          其次,在abstractclass的定義中,我們可以賦予方法的默認行為。但是在interface的定義中,方法卻不能擁有默認行為,為了繞過這個限制,必須使用委托,但是這會增加一些復雜性,有時會造成很大的麻煩。

          在抽象類中不能定義默認行為還存在另一個比較嚴重的問題,那就是可能會造成維護上的麻煩。因為如果后來想修改類的界面(一般通過abstractclass或者interface來表示)以適應新的情況(比如,添加新的方法或者給已用的方法中添加新的參數)時,就會非常的麻煩,可能要花費很多的時間(對于派生類很多的情況,尤為如此)。但是如果界面是通過abstractclass來實現的,那么可能就只需要修改定義在abstractclass中的默認行為就可以了。

          同樣,如果不能在抽象類中定義默認行為,就會導致同樣的方法實現出現在該抽象類的每一個派生類中,違反了"onerule,oneplace"原則,造成代碼重復,同樣不利于以后的維護。因此,在abstractclass和interface間進行選擇時要非常的小心。

          從設計理念層面看abstractclass和interface

          上面主要從語法定義和編程的角度論述了abstractclass和interface的區別,這些層面的區別是比較低層次的、非本質的。本小節將從另一個層面:abstractclass和interface所反映出的設計理念,來分析一下二者的區別。作者認為,從這個層面進行分析才能理解二者概念的本質所在。

          前面已經提到過,abstarctclass在Java語言中體現了一種繼承關系,要想使得繼承關系合理,父類和派生類之間必須存在"isa"關系,即父類和派生類在概念本質上應該是相同的(參考文獻〔3〕中有關于"isa"關系的大篇幅深入的論述,有興趣的讀者可以參考)。對于interface來說則不然,并不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已。為了使論述便于理解,下面將通過一個簡單的實例進行說明。

          考慮這樣一個例子,假設在我們的問題領域中有一個關于Door的抽象概念,該Door具有執行兩個動作open和close,此時我們可以通過abstractclass或者interface來定義一個表示該抽象概念的類型,定義方式分別如下所示:

          使用abstractclass方式定義Door:

          abstractclassDoor{

          abstractvoidopen();

          abstractvoidclose();

          }

          使用interface方式定義Door:

          interfaceDoor{

          voidopen();

          voidclose();

          }

          其他具體的Door類型可以extends使用abstractclass方式定義的Door或者implements使用interface方式定義的Door。看起來好像使用abstractclass和interface沒有大的區別。

          收藏此頁】【】【打印】【關閉

          如果現在要求Door還要具有報警的功能。

          我們該如何設計針對該例子的類結構呢(在本例中,主要是為了展示abstractclass和interface反映在設計理念上的區別,其他方面無關的問題都做了簡化或者忽略)?下面將羅列出可能的解決方案,并從設計理念層面對這些不同的方案進行分析。

          解決方案一:

          簡單的在Door的定義中增加一個alarm方法,如下:

          abstractclassDoor{

          abstractvoidopen();

          abstractvoidclose();

          abstractvoidalarm();

          }

          或者

          interfaceDoor{

          voidopen();

          voidclose();

          voidalarm();

          }

          那么具有報警功能的AlarmDoor的定義方式如下:

          classAlarmDoorextendsDoor{

          voidopen(){…}

          voidclose(){…}

          voidalarm(){…}

          }

          或者

          classAlarmDoorimplementsDoor{

          voidopen(){…}

          voidclose(){…}

          voidalarm(){…}

          這種方法違反了面向對象設計中的一個核心原則ISP(InterfaceSegregationPriciple),在Door的定義中把Door概念本身固有的行為方法和另外一個概念"報警器"的行為方法混在了一起。這樣引起的一個問題是那些僅僅依賴于Door這個概念的模塊會因為"報警器"這個概念的改變(比如:修改alarm方法的參數)而改變,反之依然。

          解決方案二:

          既然open、close和alarm屬于兩個不同的概念,根據ISP原則應該把它們分別定義在代表這兩個概念的抽象類中。定義方式有:這兩個概念都使用abstractclass方式定義;兩個概念都使用interface方式定義;一個概念使用abstractclass方式定義,另一個概念使用interface方式定義。

          顯然,由于Java語言不支持多重繼承,所以兩個概念都使用abstractclass方式定義是不可行的。后面兩種方式都是可行的,但是對于它們的選擇卻反映出對于問題領域中的概念本質的理解、對于設計意圖的反映是否正確、合理。我們一一來分析、說明。

          如果兩個概念都使用interface方式來定義,那么就反映出兩個問題:1、我們可能沒有理解清楚問題領域,AlarmDoor在概念本質上到底是Door還是報警器?2、如果我們對于問題領域的理解沒有問題,比如:我們通過對于問題領域的分析發現AlarmDoor在概念本質上和Door是一致的,那么我們在實現時就沒有能夠正確的揭示我們的設計意圖,因為在這兩個概念的定義上(均使用interface方式定義)反映不出上述含義。

          如果我們對于問題領域的理解是:AlarmDoor在概念本質上是Door,同時它有具有報警的功能。我們該如何來設計、實現來明確的反映出我們的意思呢?前面已經說過,abstractclass在Java語言中表示一種繼承關系,而繼承關系在本質上是"isa"關系。所以對于Door這個概念,我們應該使用abstarctclass方式來定義。另外,AlarmDoor又具有報警功能,說明它又能夠完成報警概念中定義的行為,所以報警概念可以通過interface方式定義。如下所示:

          abstractclassDoor{

          abstractvoidopen();

          abstractvoidclose();

          }

          interfaceAlarm{

          voidalarm();

          }

          classAlarmDoorextendsDoorimplementsAlarm{

          voidopen(){…}

          voidclose(){…}

          voidalarm(){…}

          }

          這種實現方式基本上能夠明確的反映出我們對于問題領域的理解,正確的揭示我們的設計意圖。其實abstractclass表示的是"isa"關系,interface表示的是"likea"關系,大家在選擇時可以作為一個依據,當然這是建立在對問題領域的理解上的,比如:如果我們認為AlarmDoor在概念本質上是報警器,同時又具有Door的功能,那么上述的定義方式就要反過來了。

          posted @ 2006-08-29 09:13 boddi 閱讀(163) | 評論 (0)編輯 收藏

          好多同志對 iframe 是如何控制的,并不是十分了解,基本上還處于一個模糊的認識狀態.

          注意兩個事項,ifr 是一個以存在的 iframe 的 ID 和 NAME 值:
          &#61548;??? document.getElementById(“ifr”);
          &#61548;??? window.frames[“ifr”];

          要想使用iframe內的函數,變量就必須通過第二種方法.因為它取的是一個完整的DOM模型(不知道這樣說對不對).第一種方法只是取出了一個OBJECT而已.

          如果只想改變iframe的 src 或者 border , scrolling 等 attributes(與property不是一個概念,property是不能寫在標簽內的,比如:scrollHeight,innerHTML等),就需要用到第一種方法.

          如果想取得iframe的頁面(不是iframe本身),就需要使用第二種方法,因為它取得的是一個完整的DOM模型,比如想得到iframe的document.body的內容,就只能用第二種方法.

          還要注意的是,如果在iframe的頁面未完全裝入的時候,調用iframe的DOM模型,會發生很嚴重的錯誤,所以,你要準備一個容錯模式.

          下面是示

          posted @ 2006-08-22 15:11 boddi 閱讀(1106) | 評論 (0)編輯 收藏

          網頁頭部標簽meta詳解

          關鍵詞收集meta資料 ?? ??????????????????????????????????????

          網頁頭部標簽meta詳解

          ??? 網頁頭部有兩個標簽titelmeta, title比較簡單,就是網頁標題,meta的內容還是蠻多了,為了自己今后能夠比較好的參考,我收集了些資料,也希望對大家有用。

          一、meta標簽的組成

          ???? meta標簽共有兩個屬性,它們分別是http-equiv屬性和name屬性,不同的屬性又有不同的參數值,這些不同的參數值就實現了不同的網頁功能。

          1、name屬性

          name屬性主要用于描述網頁,與之對應的屬性值為contentcontent中的內容主要是便于搜索引擎機器人查找信息和分類信息用的。meat標簽的name屬性語法格式是:<meta name="參數" content="具體的參數值"> 。

          ????其中name屬性主要有以下幾種參數:

          ???? AKeywords(關鍵字)

          ???? 說明:keywords用來告訴搜索引擎你網頁的關鍵字是什么。

          ???? 舉例:<meta name ="keywords" content="science, education,culture,politics,ecnomicsrelationships, entertaiment, human"

          ????? Bdescription(網站內容描述)

          ???? 說明:description用來告訴搜索引擎你的網站主要內容。

          ???? 舉例:<meta name="description" content="This page is about the meaning of science, education,culture."

          ???? Crobots(機器人向導)

          ???? 說明:robots用來告訴搜索機器人哪些頁面需要索引,哪些頁面不需要索引。

          ???? content的參數有all,none,index,noindex,follow,nofollow。默認是all

          ???? 舉例:<meta name="robots" content="none"

          ???? Dauthor(作者)

          ???? 說明:標注網頁的作者

          ???? 舉例:<meta name="author" content="***@yahoo.com.con"

          ??? 2http-equiv屬性

          ???? http-equiv顧名思義,相當于http的文件頭作用,它可以向瀏覽器傳回一些有用的信息,以幫助正確和精確地顯示網頁內容,與之對應的屬性值為contentcontent中的內容其實就是各個參數的變量值。

          ???? meat標簽的http-equiv屬性語法格式是:<meta http-equiv="參數" content="參數變量值"> ;其中http-equiv屬性主要有以下幾種參數:

          ???? AExpires(期限)

          ???? 說明:可以用于設定網頁的到期時間。一旦網頁過期,必須到服務器上重新傳輸。

          ???? 用法:<meta http-equiv="expires" content="Fri, 12 Jan 2001 18:18:18 GMT"

          ???? 注意:必須使用GMT的時間格式。

          ???? BPragma(cache模式)

          ???? 說明:禁止瀏覽器從本地計算機的緩存中訪問頁面內容。

          ???? 用法:<meta http-equiv="Pragma" content="no-cache"

          ???? 注意:這樣設定,訪問者將無法脫機瀏覽。

          ???? CRefresh(刷新)

          ???? 說明:自動刷新并指向新頁面。

          ???? 用法:<meta http-equiv="Refresh" content="2URL=http://phigo.bokee.com"

          ??? 注意:其中的2是指停留2秒鐘后自動刷新到URL網址。

          ???? DSet-Cookie(cookie設定)

          ??? 說明:如果網頁過期,那么存盤的cookie將被刪除。

          ???? 用法:<meta http-equiv="Set-Cookie" content="cookievalue=xxx;?expires=Friday, 8-Aug -2008 18:18:18 GMT path=/"

          ???? 注意:必須使用GMT的時間格式。

          ???? EWindow-target(顯示窗口的設定)

          ???? 說明:強制頁面在當前窗口以獨立頁面顯示。

          ???? 用法:<meta http-equiv="Window-target" content="_top"

          ???? 注意:用來防止別人在框架里調用自己的頁面。

          ???? Fcontent-Type(顯示字符集的設定)

          ???? 說明:設定頁面使用的字符集。

          ???? 用法:<meta http-equiv="content-Type" content="text/html; charset=gb2312"

          GPage-Exit /Page-Enter(進入頁面、離開頁面動畫效果)

          使用meta標簽,我們還可以在進入網頁或者離開網頁的一剎那實現動畫效果,我們只要在頁面的html代碼中的<head></head>標簽之間添加如下代碼就可以了:

          meta http-equiv="Page-Enter" content="revealTrans(duration=5, transition=23)"
          meta http-equiv="Page-Exit" content="revealTrans(duration=5, transition=23)"

          一旦上述代碼被加到一個網頁中后,我們再進出頁面時就會看到一些特殊效果,這個功能其實與FrontPage2000中的Format/Page Transition一樣,但我們要注意的是所加網頁不能是一個Frame;Duration的值為網頁動態過渡的時間,單位為秒。Transition是過渡方式,它的值為023,分別對應24種過渡方式。如下表:

          0 盒狀收縮

          1 盒狀放射

          2 圓形收縮

          3 圓形放射

          4 由下往上

          5 由上往下

          6 從左至右

          7 從右至左

          8 垂直百葉窗

          9 水平百葉窗

          10 水平格狀百葉窗

          11垂直格狀百葉窗

          12 隨意溶解

          13從左右兩端向中間展開

          14從中間向左右兩端展開

          15從上下兩端向中間展開

          16從中間向上下兩端展開

          17 從右上角向左下角展開

          18 從右下角向左上角展開

          19 從左上角向右下角展開

          20 從左下角向右上角展開

          21 水平線狀展開

          22 垂直線狀展開

          23 隨機產生一種過渡方式

          posted @ 2006-08-22 14:45 boddi 閱讀(173) | 評論 (0)編輯 收藏

          僅列出標題
          共3頁: 上一頁 1 2 3 下一頁 
          主站蜘蛛池模板: 金山区| 临沂市| 汽车| 涞源县| SHOW| 光山县| 永昌县| 屏边| 内乡县| 宜春市| 离岛区| 安多县| 大足县| 伊川县| 体育| 获嘉县| 客服| 兖州市| 恭城| 万宁市| 乡宁县| 衡阳县| 荥经县| 调兵山市| 富顺县| 依安县| 原阳县| 百色市| 板桥市| 左权县| 集安市| 四子王旗| 桦南县| 兴城市| 梅河口市| 宽城| 英吉沙县| 双峰县| 含山县| 保定市| 香港 |