|
使用Windows操作系統(tǒng)的朋友對Excel(電子表格)一定不會陌生,但是要使用Java語言來操縱Excel文件并不是一件容易的事。在Web應用日益盛行的今天,通過Web來操作Excel文件的需求越來越強烈,目前較為流行的操作是在JSP或Servlet?中創(chuàng)建一個CSV?(comma?separated?values)文件,并將這個文件以MIME,text/csv類型返回給瀏覽器,接著瀏覽器調(diào)用Excel并且顯示CSV文件。這樣只是說可以訪問到?Excel文件,但是還不能真正的操縱Excel文件,本文將給大家一個驚喜,向大家介紹一個開放源碼項目,Java?Excel?API,使用它大家就可
???? 以方便地操縱Excel文件了。
Java?Excel?API簡介
Java?Excel是一開放源碼項目,通過它Java開發(fā)人員可以讀取Excel文件的內(nèi)容、創(chuàng)建新的Excel文件、更新已經(jīng)存在的Excel文件。使用該?API非Windows操作系統(tǒng)也可以通過純Java應用來處理Excel數(shù)據(jù)表。因為是使用Java編寫的,所以我們在Web應用中可以通過JSP、?Servlet來調(diào)用API實現(xiàn)對Excel數(shù)據(jù)表的訪問。
現(xiàn)在發(fā)布的穩(wěn)定版本是V2.0,提供以下功能:
從Excel?95、97、2000等格式的文件中讀取數(shù)據(jù);
讀取Excel公式(可以讀取Excel?97以后的公式);
生成Excel數(shù)據(jù)表(格式為Excel?97);
支持字體、數(shù)字、日期的格式化;
支持單元格的陰影操作,以及顏色操作;
修改已經(jīng)存在的數(shù)據(jù)表;
現(xiàn)在還不支持以下功能,但不久就會提供了:
不能夠讀取圖表信息;
可以讀,但是不能生成公式,任何類型公式最后的計算值都可以讀出;
應用示例
1、從Excel文件讀取數(shù)據(jù)表
Java?Excel?API既可以從本地文件系統(tǒng)的一個文件(.xls),也可以從輸入流中讀取Excel數(shù)據(jù)表。讀取Excel數(shù)據(jù)表的第一步是創(chuàng)建Workbook(術(shù)語:工作薄),下面的代碼片段舉例說明了應該如何操作:(完整代碼見ExcelReading.java)
import?java.io.*;
import?jxl.*;
…?…?…?…
try
{
//構(gòu)建Workbook對象,?只讀Workbook對象
//直接從本地文件創(chuàng)建Workbook
//從輸入流創(chuàng)建Workbook
InputStream?is?=?new?FileInputStream(sourcefile);
jxl.Workbook?rwb?=?Workbook.getWorkbook(is);
}
catch?(Exception?e)
{
e.printStackTrace();
}
一旦創(chuàng)建了Workbook,我們就可以通過它來訪問Excel?Sheet(術(shù)語:工作表)。參考下面的代碼片段:
//獲取第一張Sheet表
Sheet?rs?=?rwb.getSheet(0);
我們既可能通過Sheet的名稱來訪問它,也可以通過下標來訪問它。如果通過下標來訪問的話,要注意的一點是下標從0開始,就像數(shù)組一樣。
一旦得到了Sheet,我們就可以通過它來訪問Excel?Cell(術(shù)語:單元格)。參考下面的代碼片段:
//獲取第一行,第一列的值
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)是數(shù)字型,Cell(1,1)是日期型,通過getContents(),三種類型的返回值都是字符型。 ???? ????
如果有需要知道Cell內(nèi)容的確切類型,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提供的基本類型相匹配,強制轉(zhuǎn)換成相應的類型,最后調(diào)用相應的取值方法getXXX(),就可以得到確定類型的值。API提供了以下基本類型,與?Excel的數(shù)據(jù)格式相對應,如下圖所示:
每種類型的具體意義,請參見Java?Excel?API?document.
當你完成對Excel電子表格數(shù)據(jù)的處理后,一定要使用close()方法來關(guān)閉先前創(chuàng)建的對象,以釋放讀取數(shù)據(jù)表的過程中所占用的內(nèi)存空間,在讀取大量數(shù)據(jù)時顯得尤為重要。參考如下代碼片段:
//操作完成時,關(guān)閉對象,釋放占用的內(nèi)存空間
rwb.close();
Java?Excel?API提供了許多訪問Excel數(shù)據(jù)表的方法,在這里我只簡要地介紹幾個常用的方法,其它的方法請參考附錄中的Java?Excel?API?document.
Workbook類提供的方法
1.?int?getNumberOfSheets()
獲得工作薄(Workbook)中工作表(Sheet)的個數(shù),示例:
jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));
int?sheets?=?rwb.getNumberOfSheets();
2.?Sheet[]?getSheets()
返回工作薄(Workbook)中工作表(Sheet)對象數(shù)組,示例:
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表中所包含的總列數(shù),示例:
jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));
jxl.Sheet?rs?=?rwb.getSheet(0);
int?rsColumns?=?rs.getColumns();
3)?Cell[]?getColumn(int?column)
獲取某一列的所有單元格,返回的是單元格對象數(shù)組,示例:
jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));
jxl.Sheet?rs?=?rwb.getSheet(0);
Cell[]?cell?=?rs.getColumn(0);
4)?int?getRows()
獲取Sheet表中所包含的總行數(shù),示例:
jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));
jxl.Sheet?rs?=?rwb.getSheet(0);
int?rsRows?=?rs.getRows();
5)?Cell[]?getRow(int?row)
獲取某一行的所有單元格,返回的是單元格對象數(shù)組,示例子:
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)
獲取指定單元格的對象引用,需要注意的是它的兩個參數(shù),第一個是列數(shù),第二個是行數(shù),這與通常的行、列組合有些不同。
jxl.Workbook?rwb?=?jxl.Workbook.getWorkbook(new?File(sourcefile));
jxl.Sheet?rs?=?rwb.getSheet(0);
Cell?cell?=?rs.getCell(0,?0);?2、生成新的Excel工作薄
下面的代碼主要是向大家介紹如何生成簡單的Excel工作表,在這里單元格的內(nèi)容是不帶任何修飾的(如:字體,顏色等等),所有的內(nèi)容都作為字符串寫入。(完整代碼見ExcelW
???? riting.java)
與讀取Excel工作表相似,首先要使用Workbook類的工廠方法創(chuàng)建一個可寫入的工作薄(Workbook)對象,這里要注意的是,只能通過API提供的工廠方法來創(chuàng)建Workbook,而不能使用?WritableWorkbook的構(gòu)造函數(shù),因為類WritableWorkbook的構(gòu)造函數(shù)為protected類型。示例代碼片段如下:
import?java.io.*;
import?jxl.*;
import?jxl.write.*;
…?…?…?…
try
{
//構(gòu)建Workbook對象,?只讀Workbook對象
//Method?1:創(chuàng)建可寫入的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頭設(shè)置正確的話,瀏覽器自動調(diào)用客戶端的Excel應用程序,來顯示動態(tài)生成的?Excel電子表格。
接下來就是要創(chuàng)建工作表,創(chuàng)建工作表的方法與創(chuàng)建工作薄的方法幾乎一樣,同樣是通過工廠模式方法獲得相應的對象,該方法需要兩個參數(shù),一個是工作表的名稱,另一個是工作表在工作薄中的位置,參考下面的代碼片段:
//創(chuàng)建Excel工作表
jxl.write.WritableSheet?ws?=?wwb.createSheet("Test?Sheet?1",?0);
"這鍋也支好了,材料也準備齊全了,可以開始下鍋了!",現(xiàn)在要做的只是實例化API所提供的Excel基本數(shù)據(jù)類型,并將它們添加到工作表中就可以了,參考下面的代碼片段:
//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);?這里有兩點大家要引起大家的注意。第一點,在構(gòu)造單元格時,單元格在工作表中的位置就已經(jīng)確定了。一旦創(chuàng)建后,單元格的位置是不能夠變更的,盡管單元格的內(nèi)容是可以改變的。第二點,單元格的定位是按照下面這樣的規(guī)律(column,?row),而且下標都是從0開始,例如,A1被存儲在(0,?0),B1被存儲在(1,?0)。
????
最后,不要忘記關(guān)閉打開的Excel工作薄對象,以釋放占用的內(nèi)存,參見下面的代碼片段:
//寫入Exel工作表
wwb.write();
//關(guān)閉Excel工作薄對象
wwb.close();
這可能與讀取Excel文件的操作有少少不同,在關(guān)閉Excel對象之前,你必須要先調(diào)用write()方法,因為先前的操作都是存儲在緩存中的,所以要通過該方法將操作的內(nèi)容保存在文件中。如果你先關(guān)閉了Excel對象,那么只能得到一張空的工作薄了。
3、拷貝、更新Excel工作薄
接下來簡要介紹一下如何更新一個已經(jīng)存在的工作薄,主要是下面二步操作,第一步是構(gòu)造只讀的Excel工作薄,第二步是利用已經(jīng)創(chuàng)建的Excel工作薄創(chuàng)建新的可寫入的Excel工作薄,參考下面的代碼片段:(完整代碼見ExcelModifying.java)
//創(chuàng)建只讀的Excel工作薄的對象
jxl.Workbook?rw?=?jxl.Workbook.getWorkbook(new?File(sourcefile));
//創(chuàng)建可寫入的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);
//判斷單元格的類型,?做出相應的轉(zhuǎn)化
if(wc.getType()?==?CellType.LABEL)
{
Label?l?=?(Label)wc;
l.setString("The?value?has?been?modified.");
}
//寫入Excel對象
wwb.write();
//關(guān)閉可寫入的Excel對象
wwb.close();
//關(guān)閉只讀的Excel對象
rw.close();?之所以使用這種方式構(gòu)建Excel對象,完全是因為效率的原因,因為上面的示例才是?API的主要應用。為了提高性能,在讀取工作表時,與數(shù)據(jù)相關(guān)的一些輸出信息,所有的格式信息,如:字體、顏色等等,是不被處理的,因為我們的目的是獲得行數(shù)據(jù)的值,既使沒有了修飾,也不會對行數(shù)據(jù)的值產(chǎn)生什么影響。唯一的不利之處就是,在內(nèi)存中會同時保存兩 ???? 個同樣的工作表,這樣當工作表體積比較大時,會占用相當大的內(nèi)存,但現(xiàn)在好像內(nèi)存的大小并不是什么關(guān)鍵因素了。
一旦獲得了可寫入的工作表對象,我們就可以對單元格對象進行更新的操作了,在這里我們不必調(diào)用API提供的add()方法,因為單元格已經(jīng)于工作表當中,所以我們只需要調(diào)用相應的setXXX()方法,就可以完成更新的操作了。
盡單元格原有的格式化修飾是不能去掉的,我們還是可以將新的單元格修飾加上去,以使單元格的內(nèi)容以不同的形式表現(xiàn)。
新生成的工作表對象是可寫入的,我們除了更新原有的單元格外,還可以添加新的單元格到工作表中,這與示例2的操作是完全一樣的。
最后,不要忘記調(diào)用write()方法,將更新的內(nèi)容寫入到文件中,然后關(guān)閉工作薄對象,這里有兩個工作薄對象要關(guān)閉,一個是只讀的,另外一個是可寫入的。
jxl的一些總結(jié)
要往xls文件里面寫入數(shù)據(jù)的時候需要注意的是第一要新建一個xls文件 OutputStream os=new FileOutputStream("c:\\excel2.xls");
再建完這個文件的時候再建立工作文件 jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(os));
如果這個文件已經(jīng)存在,那么我們可以在這個文件里面加入一個sheet為了和以前的數(shù)據(jù)進行分開; jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0); 在createSheet方法里前面的參數(shù)是sheet名,后面是要操作的sheet號
接下來就可以往這個文件里面寫入數(shù)據(jù)了
寫入數(shù)據(jù)的時候注意的格式
(1)添加的字體樣式 jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true); WritableFont()方法里參數(shù)說明: 這個方法算是一個容器,可以放進去好多屬性 第一個: 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()方法里面有三個參數(shù) 第一個是代表列數(shù), 第二是代表行數(shù), 第***要寫入的內(nèi)容 第四個是可選項,是輸入這個label里面的樣式 然后通過寫sheet的方法addCell()把內(nèi)容寫進sheet里面。
(2)添加帶有formatting的Number對象 jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
(3)添加Number對象 (3.1)顯示number對象數(shù)據(jù)的格式
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()方法參數(shù)說明: 前兩上表示輸入的位置 第三個表示輸入的內(nèi)容
(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()方法的參數(shù)說明 前兩個表示輸入的位置 第三個表示輸入的當前時間
(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)設(shè)置單元格樣式
jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc); wcfFC.setBackGround(jxl.format.Colour.RED);//設(shè)置單元格的顏色為紅色 wcfFC = new jxl.write.Label(6,0,"i love china",wcfFC);
Jxl在寫excel文件時使用的方法比較怪,也可以說jxl不支持修改excel文件。它的處理方式是每次打開舊excel文件,然后創(chuàng)建一個該excel文件的可寫的副本,所有的修改都是在這個副本上做的。下面是一個例子。
Java中static、this、super、final用法 |
作者:未知??來源:未知??發(fā)布時間:2006-7-14 21:33:56??發(fā)布人:sany |
減小字體
增大字體
一、static? 請先看下面這段程序: ??public?class?Hello{ ????public?static?void?main(String[]?args){?//(1) ??????System.out.println("Hello,world!");???//(2) ????} ??} 看過這段程序,對于大多數(shù)學過Java?的從來說,都不陌生。即使沒有學過Java,而學過其它的高級語言,例如C,那你也應該能看懂這段代碼的意思。它只是簡單的輸出“Hello,world”,一點別的用處都沒有,然而,它卻展示了static關(guān)鍵字的主要用法。 在1處,我們定義了一個靜態(tài)的方法名為main,這就意味著告訴Java編譯器,我這個方法不需要創(chuàng)建一個此類的對象即可使用。你還得你是怎么運行這個程序嗎?一般,我們都是在命令行下,打入如下的命令(加下劃線為手動輸入): javac?Hello.java java?Hello Hello,world! 這就是你運行的過程,第一行用來編譯Hello.java這個文件,執(zhí)行完后,如果你查看當前,會發(fā)現(xiàn)多了一個Hello.class文件,那就是第一行產(chǎn)生的Java二進制字節(jié)碼。第二行就是執(zhí)行一個Java程序的最普遍做法。執(zhí)行結(jié)果如你所料。在2中,你可能會想,為什么要這樣才能輸出。好,我們來分解一下這條語句。(如果沒有安裝Java文檔,請到Sun的官方網(wǎng)站瀏覽J2SE?API)首先,System是位于java.lang包中的一個核心類,如果你查看它的定義,你會發(fā)現(xiàn)有這樣一行:public?static?final?PrintStream?out;接著在進一步,點擊PrintStream這個超鏈接,在METHOD頁面,你會看到大量定義的方法,查找println,會有這樣一行: public?void?println(String?x)。 好了,現(xiàn)在你應該明白為什么我們要那樣調(diào)用了,out是System的一個靜態(tài)變量,所以可以直接使用,而out所屬的類有一個println方法。 靜態(tài)方法 通常,在一個類中定義一個方法為static,那就是說,無需本類的對象即可調(diào)用此方法。如下所示: class?Simple{ ???static?void?go(){ ?????System.out.println("Go..."); ???} } public?class?Cal{ ??public?static?void?main(String[]?args){ ????Simple.go(); ??} } 調(diào)用一個靜態(tài)方法就是“類名.方法名”,靜態(tài)方法的使用很簡單如上所示。一般來說,靜態(tài)方法常常為應用程序中的其它類提供一些實用工具所用,在Java的類庫中大量的靜態(tài)方法正是出于此目的而定義的。 靜態(tài)變量 靜態(tài)變量與靜態(tài)方法類似。所有此類實例共享此靜態(tài)變量,也就是說在類裝載時,只分配一塊存儲空間,所有此類的對象都可以操控此塊存儲空間,當然對于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);?? ??} } 結(jié)果如下: v1.c=0??v2.c=0 v1.c=1??v2.c=1 由此可以證明它們共享一塊存儲區(qū)。static變量有點類似于C中的全局變量的概念。值得探討的是靜態(tài)變量的初始化問題。我們修改上面的程序: 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); ??} } 運行結(jié)果如下: 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 這個程序展示了靜態(tài)初始化的各種特性。如果你初次接觸Java,結(jié)果可能令你吃驚。可能會對static后加大括號感到困惑。首先要告訴你的是,static定義的變量會優(yōu)先于任何其它非static變量,不論其出現(xiàn)的順序如何。正如在程序中所表現(xiàn)的,雖然v出現(xiàn)在v1和v2的前面,但是結(jié)果卻是v1和v2的初始化在v的前面。在static{后面跟著一段代碼,這是用來進行顯式的靜態(tài)變量初始化,這段代碼只會初始化一次,且在類被第一次裝載時。如果你能讀懂并理解這段代碼,會幫助你對static關(guān)鍵字的認識。在涉及到繼承的時候,會先初始化父類的static變量,然后是子類的,依次類推。非靜態(tài)變量不是本文的主題,在此不做詳細討論,請參考Think?in?Java中的講解。 靜態(tài)類 通常一個普通類不允許聲明為靜態(tài)的,只有一個內(nèi)部類才可以。這時這個聲明為靜態(tài)的內(nèi)部類可以直接作為一個普通類來使用,而不需實例一個外部類。如下代碼所示: 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"); ????} ???} } 輸出結(jié)果會如你所料: InnerCls 和普通類一樣。內(nèi)部類的其它用法請參閱Think?in?Java中的相關(guān)章節(jié),此處不作詳解。 二、this?&?super 在上一篇拙作中,我們討論了static的種種用法,通過用static來定義方法或成員,為我們編程提供了某種便利,從某種程度上可以說它類似于C語言中的全局函數(shù)和全局變量。但是,并不是說有了這種便利,你便可以隨處使用,如果那樣的話,你便需要認真考慮一下自己是否在用面向?qū)ο蟮乃枷?a class="wordstyle" target="_blank">編程,自己的程序是否是面向?qū)ο蟮摹:昧耍F(xiàn)在開始討論this&super這兩個關(guān)鍵字的意義和用法。 在Java中,this通常指當前對象,super則指父類的。當你想要引用當前對象的某種東西,比如當前對象的某個方法,或當前對象的某個成員,你便可以利用this來實現(xiàn)這個目的,當然,this的另一個用途是調(diào)用當前對象的另一個構(gòu)造函數(shù),這些馬上就要討論。如果你想引用父類的某種東西,則非super莫屬。由于this與super有如此相似的一些特性和與生俱來的某種關(guān)系,所以我們在這一塊兒來討論,希望能幫助你區(qū)分和掌握它們兩個。 在一般方法中 最普遍的情況就是,在你的方法中的某個形參名與當前對象的某個成員有相同的名字,這時為了不至于混淆,你便需要明確使用this關(guān)鍵字來指明你要使用某個成員,使用方法是“this.成員名”,而不帶this的那個便是形參。另外,還可以用“this.方法名”來引用當前對象的某個方法,但這時this就不是必須的了,你可以直接用方法名來訪問那個方法,編譯器會知道你要調(diào)用的是那一個。下面的代碼演示了上面的用法: public?class?DemoThis{ ??private?String?name; ??private?int?age; ??DemoThis(String?name,int?age){ ????setName(name);?//你可以加上this來調(diào)用方法,像這樣: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"); ??} } 這段代碼很簡單,不用解釋你也應該能看明白。在構(gòu)造函數(shù)中你看到用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方法,它首先做一些自己的事情,然后調(diào)用父類的那個被覆寫了的方法。輸出結(jié)果說明了這一點: DemoSuper: Name=kevin?Age=22 這樣的使用方法是比較常用的。另外如果父類的成員可以被子類訪問,那你可以像使用this一樣使用它,用“super.父類中的成員名”的方式,但常常你并不是這樣來訪問父類中的成員名的。 在構(gòu)造函數(shù)中 構(gòu)造函數(shù)是一種特殊的方法,在對象初始化的時候自動調(diào)用。在構(gòu)造函數(shù)中,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();??//調(diào)用父類構(gòu)造函數(shù)(1) ????prt("A?chinese.");//(4) ??} ??Chinese(String?name){ ????super(name);//調(diào)用父類具有相同形參的構(gòu)造函數(shù)(2) ????prt("his?name?is:"+name); ??} ??Chinese(String?name,int?age){ ????this(name);//調(diào)用當前具有相同形參的構(gòu)造函數(shù)(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不再是像以前那樣用“.”連接一個方法或成員,而是直接在其后跟上適當?shù)膮?shù),因此它的意義也就有了變化。super后加參數(shù)的是用來調(diào)用父類中具有相同形式的構(gòu)造函數(shù),如1和2處。this后加參數(shù)則調(diào)用的是當前具有相同參數(shù)的構(gòu)造函數(shù),如3處。當然,在Chinese的各個重載構(gòu)造函數(shù)中,this和super在一般方法中的各種用法也仍可使用,比如4處,你可以將它替換為“this.prt”(因為它繼承了父類中的那個方法)或者是“super.prt”(因為它是父類中的方法且可被子類訪問),它照樣可以正確運行。但這樣似乎就有點畫蛇添足的味道了。 最后,寫了這么多,如果你能對“this通常指代當前對象,super通常指代父類”這句話牢記在心,那么本篇便達到了目的,其它的你自會在以后的編程實踐當中慢慢體會、掌握。另外關(guān)于本篇中提到的繼承,請參閱相關(guān)Java教程。 三、final final在Java中并不常用,然而它卻為我們提供了諸如在C語言中定義常量的功能,不僅如此,final還可以讓你控制你的成員、方法或者是一個類是否可被覆寫或繼承等功能,這些特點使final在Java中擁有了一個不可或缺的地位,也是學習Java時必須要知道和掌握的關(guān)鍵字之一。 final成員 當你在類中定義變量時,在其前面加上final關(guān)鍵字,那便是說,這個變量一旦被初始化便不可改變,這里不可改變的意思對基本類型來說是其值不可變,而對于對象變量來說其引用不可再變。其初始化可以在兩個地方,一是其定義處,也就是說在final變量定義時直接給其賦值,二是在構(gòu)造函數(shù)中。這兩個地方只能選其一,要么在定義時給值,要么在構(gòu)造函數(shù)中給值,不能同時既在定義時給了值,又在構(gòu)造函數(shù)中給另外的值。下面這段代碼演示了這一點: import?java.util.List; import?java.util.ArrayList; import?java.util.LinkedList; public?class?Bat{ ????final?PI=3.14;??????????//在定義時便給址值 ????final?int?i;????????????//因為要在構(gòu)造函數(shù)中進行初始化,所以此處便不可再給值 ????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的常規(guī)用法。在這里使用在構(gòu)造函數(shù)中進行初始化的方法,這使你有了一點靈活性。如Bat的兩個重載構(gòu)造函數(shù)所示,第一個缺省構(gòu)造函數(shù)會為你提供默認的值,重載的那個構(gòu)造函數(shù)會根據(jù)你所提供的值或類型為final變量初始化。然而有時你并不需要這種靈活性,你只需要在定義時便給定其值并永不變化,這時就不要再用這種方法。在main方法中有兩行語句注釋掉了,如果你去掉注釋,程序便無法通過編譯,這便是說,不論是i的值或是list的類型,一旦初始化,確實無法再更改。然而b可以通過重新初始化來指定i的值或list的類型,輸出結(jié)果中顯示了這一點: I=100?List?Type:class?java.util.LinkedList I=23?List?Type:class?java.util.ArrayList 還有一種用法是定義方法中的參數(shù)為final,對于基本類型的變量,這樣做并沒有什么實際意義,因為基本類型的變量在調(diào)用方法時是傳值的,也就是說你可以在方法中更改這個參數(shù)變量而不會影響到調(diào)用語句,然而對于對象變量,卻顯得很實用,因為對象變量在傳遞時是傳遞其引用,這樣你在方法中對對象變量的修改也會影響到調(diào)用語句中的對象變量,當你在方法中不需要改變作為參數(shù)的對象變量時,明確使用final進行聲明,會防止你無意的修改而影響到調(diào)用方法。 另外方法中的內(nèi)部類在用到方法中的參變量時,此參變也必須聲明為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,那就說明你已經(jīng)知道這個方法提供的功能已經(jīng)滿足你要求,不需要進行擴展,并且也不允許任何從此類繼承的類來覆寫這個方法,但是繼承仍然可以繼承這個方法,也就是說可以直接使用。另外有一種被稱為inline的機制,它會使你在調(diào)用final方法時,直接將方法主體插入到調(diào)用處,而不是進行例行的方法調(diào)用,例如保存斷點,壓棧等,這樣可能會使你的程序效率有所提高,然而當你的方法主體非常龐大時,或你在多處調(diào)用此方法,那么你的調(diào)用主體代碼便會迅速膨脹,可能反而會影響效率,所以你要慎用final進行方法定義。 final類 當你將final用于類身上時,你就需要仔細考慮,因為一個final類是無法被任何人繼承的,那也就意味著此類在一個繼承樹中是一個葉子類,并且此類的設(shè)計已被認為很完美而不需要進行修改或擴展。對于final類中的成員,你可以定義其為final,也可以不是final。而對于方法,由于所屬類為final的關(guān)系,自然也就成了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方法的區(qū)別也很難從程序行看出,只是記住慎用。 final在設(shè)計模式中的應用 在設(shè)計模式中有一種模式叫做不變模式,在Java中通過final關(guān)鍵字可以很容易的實現(xiàn)這個模式,在講解final成員時用到的程序Bat.java就是一個不變模式的例子。如果你對此感興趣,可以參考閻宏博士編寫的《Java與模式》一書中的講解。 到此為止,this,static,supert和final的使用已經(jīng)說完了,如果你對這四個關(guān)鍵字已經(jīng)能夠大致說出它們的區(qū)別與用法,那便說明你基本已經(jīng)掌握。然而,世界上的任何東西都不是完美無缺的,Java提供這四個關(guān)鍵字,給程序員的編程帶來了很大的便利,但并不是說要讓你到處使用,一旦達到濫用的程序,便適得其反,所以在使用時請一定要認真考慮。
|
Java中this、super用法簡談 通過用static來定義方法或成員,為我們編程提供了某種便利,從某種程度上可以說它類似于C語言中的全局函數(shù)和全局變量。但是,并不是說有了這種便利,你便可以隨處使用,如果那樣的話,你便需要認真考慮一下自己是否在用面向?qū)ο蟮乃枷刖幊蹋约旱某绦蚴欠袷敲嫦驅(qū)ο蟮摹:昧耍F(xiàn)在開始討論this&super這兩個關(guān)鍵字的意義和用法。 在Java中,this通常指當前對象,super則指父類的。當你想要引用當前對象的某種東西,比如當前對象的某個方法,或當前對象的某個成員,你便可以利用this來實現(xiàn)這個目的,當然,this的另一個用途是調(diào)用當前對象的另一個構(gòu)造函數(shù),這些馬上就要討論。如果你想引用父類的某種東西,則非super莫屬。由于this與super有如此相似的一些特性和與生俱來的某種關(guān)系,所以我們在這一塊兒來討論,希望能幫助你區(qū)分和掌握它們兩個。 在一般方法中 最普遍的情況就是,在你的方法中的某個形參名與當前對象的某個成員有相同的名字,這時為了不至于混淆,你便需要明確使用this關(guān)鍵字來指明你要使用某個成員,使用方法是“this.成員名”,而不帶this的那個便是形參。另外,還可以用“this.方法名”來引用當前對象的某個方法,但這時this就不是必須的了,你可以直接用方法名來訪問那個方法,編譯器會知道你要調(diào)用的是那一個。下面的代碼演示了上面的用法: public class DemoThis{ private String name; private int age; DemoThis(String name,int age){ setName(name); //你可以加上this來調(diào)用方法,像這樣: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"); 這段代碼很簡單,不用解釋你也應該能看明白。在構(gòu)造函數(shù)中你看到用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方法,它首先做一些自己的事情,然后調(diào)用父類的那個被覆寫了的方法。輸出結(jié)果說明了這一點: DemoSuper: Name=kevin Age=22
這樣的使用方法是比較常用的。另外如果父類的成員可以被子類訪問,那你可以像使用this一樣使用它,用“super.父類中的成員名”的方式,但常常你并不是這樣來訪問父類中的成員名的。 在構(gòu)造函數(shù)中構(gòu)造函數(shù)是一種特殊的方法,在對象初始化的時候自動調(diào)用。在構(gòu)造函數(shù)中,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(); //調(diào)用父類構(gòu)造函數(shù)(1) prt("A chinese.");//(4) } Chinese(String name){ super(name);//調(diào)用父類具有相同形參的構(gòu)造函數(shù)(2) prt("his name is:"+name); } Chinese(String name,int age){ this(name);//調(diào)用當前具有相同形參的構(gòu)造函數(shù)(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不再是像以前那樣用“.”連接一個方法或成員,而是直接在其后跟 上適當?shù)膮?shù),因此它的意義也就有了變化。super后加參數(shù)的是用來調(diào)用父類中具有相同形式的 構(gòu)造函數(shù),如1和2處。this后加參數(shù)則調(diào)用的是當前具有相同參數(shù)的構(gòu)造函數(shù),如3處。當然,在 Chinese的各個重載構(gòu)造函數(shù)中,this和super在一般方法中的各種用法也仍可使用,比如4處,你 可以將它替換為“this.prt”(因為它繼承了父類中的那個方法)或者是“super.prt”(因為它 是父類中的方法且可被子類訪問),它照樣可以正確運行。但這樣似乎就有點畫蛇添足的味道 了。 最后,寫了這么多,如果你能對“this通常指代當前對象,super通常指代父類”這句話牢記在 心,那么本篇便達到了目的,其它的你自會在以后的編程實踐當中慢慢體會、掌握。另外關(guān)于本 篇中提到的繼承,請參閱相關(guān)Java教程。
泊船瓜洲
@ 2005-08-30 18:08
Java中的類反射機制 (轉(zhuǎn)帖) 一、反射的概念 : 反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力。這一概念的提出很快引發(fā)了計算機科學領(lǐng)域關(guān)于應用反射性的研究。它首先被程序語言的設(shè)計領(lǐng)域所采用,并在Lisp和面向?qū)ο蠓矫嫒〉昧顺煽儭F渲蠰EAD/LEAD++ 、OpenC++ 、 MetaXa和OpenJava等就是基于反射機制的語言。最近,反射機制也被應用到了視窗系統(tǒng)、操作系統(tǒng)和文件系統(tǒng)中。
反射本身并不是一個新概念,它可能會使我們聯(lián)想到光學中的反射概念,盡管計算機科學賦予了反射概念新的含義,但是,從現(xiàn)象上來說,它們確實有某些相通之處,這些有助于我們的理解。在計算機科學領(lǐng)域,反射是指一類應用,它們能夠自描述和自控制。也就是說,這類應用通過采用某種機制來實現(xiàn)對自己行為的描述(self-representation)和監(jiān)測(examination),并能根據(jù)自身行為的狀態(tài)和結(jié)果,調(diào)整或修改應用所描述行為的狀態(tài)和相關(guān)的語義。可以看出,同一般的反射概念相比,計算機科學領(lǐng)域的反射不單單指反射本身,還包括對反射結(jié)果所采取的措施。所有采用反射機制的系統(tǒng)(即反射系統(tǒng))都希望使系統(tǒng)的實現(xiàn)更開放。可以說,實現(xiàn)了反射機制的系統(tǒng)都具有開放性,但具有開放性的系統(tǒng)并不一定采用了反射機制,開放性是反射系統(tǒng)的必要條件。一般來說,反射系統(tǒng)除了滿足開放性條件外還必須滿足原因連接(Causally-connected)。所謂原因連接是指對反射系統(tǒng)自描述的改變能夠立即反映到系統(tǒng)底層的實際狀態(tài)和行為上的情況,反之亦然。開放性和原因連接是反射系統(tǒng)的兩大基本要素。13700863760
Java中,反射是一種強大的工具。它使您能夠創(chuàng)建靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代表鏈接。反射允許我們在編寫與執(zhí)行時,使我們的程序代碼能夠接入裝載到JVM中的類的內(nèi)部信息,而不是源代碼中選定的類協(xié)作的代碼。這使反射成為構(gòu)建靈活的應用的主要工具。但需注意的是:如果使用不當,反射的成本很高。
二、Java中的類反射: Reflection 是 Java 程序開發(fā)語言的特征之一,它允許運行中的 Java 程序?qū)ψ陨磉M行檢查,或者說“自審”,并能直接操作程序的內(nèi)部屬性。Java 的這一能力在實際應用中也許用得不是很多,但是在其它的程序設(shè)計語言中根本就不存在這一特性。例如,Pascal、C 或者 C++ 中就沒有辦法在程序中獲得函數(shù)定義相關(guān)的信息。
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); ???????} ???} }
按如下語句執(zhí)行:
java DumpMethods java.util.Stack
它的結(jié)果輸出為:
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 載入指定的類,然后調(diào)用 getDeclaredMethods 來獲取這個類中定義了的方法列表。java.lang.reflect.Methods 是用來描述某個類中單個方法的一個類。
1.2 Java類反射中的主要方法
對于以下三類組件中的任何一類來說 -- 構(gòu)造函數(shù)、字段和方法 -- java.lang.Class 提供四種獨立的反射調(diào)用,以不同的方式來獲得信息。調(diào)用都遵循一種標準格式。以下是用于查找構(gòu)造函數(shù)的一組反射調(diào)用:
l ????????Constructor getConstructor(Class[] params) -- 獲得使用特殊的參數(shù)類型的公共構(gòu)造函數(shù),
l ????????Constructor[] getConstructors() -- 獲得類的所有公共構(gòu)造函數(shù)
l ????????Constructor getDeclaredConstructor(Class[] params) -- 獲得使用特定參數(shù)類型的構(gòu)造函數(shù)(與接入級別無關(guān))
l ????????Constructor[] getDeclaredConstructors() -- 獲得類的所有構(gòu)造函數(shù)(與接入級別無關(guān))
獲得字段信息的Class 反射調(diào)用不同于那些用于接入構(gòu)造函數(shù)的調(diào)用,在參數(shù)類型數(shù)組中使用了字段名:
l ????????Field getField(String name) -- 獲得命名的公共字段
l ????????Field[] getFields() -- 獲得類的所有公共字段
l ????????Field getDeclaredField(String name) -- 獲得類聲明的命名的字段
l ????????Field[] getDeclaredFields() -- 獲得類聲明的所有字段
用于獲得方法信息函數(shù):
l ????????Method getMethod(String name, Class[] params) -- 使用特定的參數(shù)類型,獲得命名的公共方法
l ????????Method[] getMethods() -- 獲得類的所有公共方法
l ????????Method getDeclaredMethod(String name, Class[] params) -- 使用特寫的參數(shù)類型,獲得類聲明的命名的方法
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 字段。
第二步是調(diào)用諸如 getDeclaredMethods 的方法,以取得該類中定義的所有方法的列表。
一旦取得這個信息,就可以進行第三步了——使用 reflection API 來操作這些信息,如下面這段代碼:
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[0].toString());
它將以文本方式打印出 String 中定義的第一個方法的原型。
2.處理對象:
如果要作一個開發(fā)工具像debugger之類的,你必須能發(fā)現(xiàn)filed values,以下是三個步驟:
a.創(chuàng)建一個Class對象 b.通過getField 創(chuàng)建一個Field對象 c.調(diào)用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); ?????} ??} }
三、安全性和反射: 在處理反射時安全性是一個較復雜的問題。反射經(jīng)常由框架型代碼使用,由于這一點,我們可能希望框架能夠全面接入代碼,無需考慮常規(guī)的接入限制。但是,在其它情況下,不受控制的接入會帶來嚴重的安全性風險,例如當代碼在不值得信任的代碼共享的環(huán)境中運行時。
由于這些互相矛盾的需求,Java編程語言定義一種多級別方法來處理反射的安全性。基本模式是對反射實施與應用于源代碼接入相同的限制:
n ????????從任意位置到類公共組件的接入
n ????????類自身外部無任何到私有組件的接入
n ????????受保護和打包(缺省接入)組件的有限接入
不過至少有些時候,圍繞這些限制還有一種簡單的方法。我們可以在我們所寫的類中,擴展一個普通的基本類 java.lang.reflect.AccessibleObject 類。這個類定義了一種setAccessible方法,使我們能夠啟動或關(guān)閉對這些類中其中一個類的實例的接入檢測。唯一的問題在于如果使用了安全性管理器,它將檢測正在關(guān)閉接入檢測的代碼是否許可了這樣做。如果未許可,安全性管理器拋出一個例外。
下面是一段程序,在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);
???????}
???}
}
如果我們編譯這一程序時,不使用任何特定參數(shù)直接從命令行運行,它將在field .get(inst)調(diào)用中拋出一個 IllegalAccessException異常。如果我們不注釋field.setAccessible(true)代碼行,那么重新編譯并重新運行該代碼,它將編譯成功。最后,如果我們在命令行添加了JVM參數(shù)-Djava.security.manager以實現(xiàn)安全性管理器,它仍然將不能通過編譯,除非我們定義了ReflectSecurity類的許可權(quán)限。
四、反射性能: 反射是一種強大的工具,但也存在一些不足。一個主要的缺點是對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執(zhí)行相同的操作。
下面的程序是字段接入性能測試的一個例子,包括基本的測試方法。每種方法測試字段接入的一種形式 -- accessSame 與同一對象的成員字段協(xié)作,accessOther 使用可直接接入的另一對象的字段,accessReflection 使用可通過反射接入的另一對象的字段。在每種情況下,方法執(zhí)行相同的計算 -- 循環(huán)中簡單的加/乘順序。
程序如下:
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;
???}
}
在上面的例子中,測試程序重復調(diào)用每種方法,使用一個大循環(huán)數(shù),從而平均多次調(diào)用的時間衡量結(jié)果。平均值中不包括每種方法第一次調(diào)用的時間,因此初始化時間不是結(jié)果中的一個因素。下面的圖清楚的向我們展示了每種方法字段接入的時間:
圖 1:字段接入時間 :
我們可以看出:在前兩副圖中(Sun JVM),使用反射的執(zhí)行時間超過使用直接接入的1000倍以上。通過比較,IBM JVM可能稍好一些,但反射方法仍舊需要比其它方法長700倍以上的時間。任何JVM上其它兩種方法之間時間方面無任何顯著差異,但IBM JVM幾乎比Sun JVM快一倍。最有可能的是這種差異反映了Sun Hot Spot JVM的專業(yè)優(yōu)化,它在簡單基準方面表現(xiàn)得很糟糕。反射性能是Sun開發(fā)1.4 JVM時關(guān)注的一個方面,它在反射方法調(diào)用結(jié)果中顯示。在這類操作的性能方面,Sun 1.4.1 JVM顯示了比1.3.1版本很大的改進。
如果為為創(chuàng)建使用反射的對象編寫了類似的計時測試程序,我們會發(fā)現(xiàn)這種情況下的差異不象字段和方法調(diào)用情況下那么顯著。使用newInstance()調(diào)用創(chuàng)建一個簡單的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)創(chuàng)建一個數(shù)組耗用的時間是任何測試的JVM上使用new type[size]的兩倍,隨著數(shù)組大小的增加,差異逐步縮小。
結(jié)束語: Java語言反射提供一種動態(tài)鏈接程序組件的多功能方法。它允許程序創(chuàng)建和控制任何類的對象(根據(jù)安全性限制),無需提前硬編碼目標類。這些特性使得反射特別適用于創(chuàng)建以非常普通的方式與對象協(xié)作的庫。例如,反射經(jīng)常在持續(xù)存儲對象為數(shù)據(jù)庫、XML或其它外部格式的框架中使用。 Java reflection 非常有用,它使類和數(shù)據(jù)結(jié)構(gòu)能按名稱動態(tài)檢索相關(guān)信息,并允許在運行著的程序中操作這些信息。Java 的這一特性非常強大,并且是其它一些常用語言,如 C、C++、Fortran 或者 Pascal 等都不具備的。
但反射有兩個缺點。第一個是性能問題。用于字段和方法接入時反射要遠慢于直接代碼。性能問題的程度取決于程序中是如何使用反射的。如果它作為程序運行中相對很少涉及的部分,緩慢的性能將不會是一個問題。即使測試中最壞情況下的計時圖顯示的反射操作只耗用幾微秒。僅反射在性能關(guān)鍵的應用的核心邏輯中使用時性能問題才變得至關(guān)重要。
許多應用中更嚴重的一個缺點是使用反射會模糊程序內(nèi)部實際要發(fā)生的事情。程序人員希望在源代碼中看到程序的邏輯,反射等繞過了源代碼的技術(shù)會帶來維護問題。反射代碼比相應的直接代碼更復雜,正如性能比較的代碼實例中看到的一樣。解決這些問題的最佳方案是保守地使用反射——僅在它可以真正增加靈活性的地方 ——記錄其在目標類中的使用。
利用反射實現(xiàn)類的動態(tài)加載
Bromon原創(chuàng) 請尊重版權(quán)
最近在成都寫一個移動增值項目,俺負責后臺server端。功能很簡單,手機用戶通過GPRS打開Socket與服務器連接,我則根據(jù)用戶傳過來的數(shù)據(jù)做出響應。做過類似項目的兄弟一定都知道,首先需要定義一個類似于MSNP的通訊協(xié)議,不過今天的話題是如何把這個系統(tǒng)設(shè)計得具有高度的擴展性。由于這個項目本身沒有進行過較為完善的客戶溝通和需求分析,所以以后肯定會有很多功能上的擴展,通訊協(xié)議肯定會越來越龐大,而我作為一個不那么勤快的人,當然不想以后再去修改寫好的程序,所以這個項目是實踐面向?qū)ο笤O(shè)計的好機會。
首先定義一個接口來隔離類:
package org.bromon.reflect;
public interface Operator
{
public java.util.List act(java.util.List params)
}
根據(jù)設(shè)計模式的原理,我們可以為不同的功能編寫不同的類,每個類都繼承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;
}
}
我們還可以寫其他很多類,但是有個問題,接口是無法實例化的,我們必須手動控制具體實例化哪個類,這很不爽,如果能夠向應用程序傳遞一個參數(shù),讓自己去選擇實例化一個類,執(zhí)行它的act方法,那我們的工作就輕松多了。
很幸運,我使用的是Java,只有Java才提供這樣的反射機制,或者說內(nèi)省機制,可以實現(xiàn)我們的無理要求。編寫一個配置文件emp.properties:
#成功響應
1000=Success
#向客戶發(fā)送普通文本消息
2000=Load
#客戶向服務器發(fā)送普通文本消息
3000=Store
文件中的鍵名是客戶將發(fā)給我的消息頭,客戶發(fā)送1000給我,那么我就執(zhí)行Success類的act方法,類似的如果發(fā)送2000給我,那就執(zhí)行Load 類的act方法,這樣一來系統(tǒng)就完全符合開閉原則了,如果要添加新的功能,完全不需要修改已有代碼,只需要在配置文件中添加對應規(guī)則,然后編寫新的類,實現(xiàn)act方法就ok,即使我棄這個項目而去,它將來也可以很好的擴展。這樣的系統(tǒng)具備了非常良好的擴展性和可插入性。
下面這個例子體現(xiàn)了動態(tài)加載的功能,程序在執(zhí)行過程中才知道應該實例化哪個類:
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所對應的類的名字
* 通過反射機制動態(tài)加載匹配的類,所有的類都被Operator接口隔離
* 可以通過修改屬性文件、添加新的類(繼承MsgOperator接口)來擴展協(xié)議
*/
s="org.bromon.reflect."+this.loadProtocal(header);
//加載類
Class c=Class.forName(s);
//創(chuàng)建類的事例
Operator mo=(Operator)c.newInstance();
//構(gòu)造參數(shù)列表
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;
//調(diào)用方法并且獲得返回
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],”消息內(nèi)容”);
}
}
測試一下:java TestReflect 1000
這個程序是針對Operator編程的,所以無需做任何修改,直接提供Load和Store類,就可以支持2000、3000做參數(shù)的調(diào)用。
有了這樣的內(nèi)省機制,可以把接口的作用發(fā)揮到極至,設(shè)計模式也更能體現(xiàn)出威力,而不僅僅供我們飯后閑聊。
array(數(shù)組)和Vector是十分相似的Java構(gòu)件(constructs),兩者全然不同,在選擇使用時應根據(jù)各自的功能來確定。
1、數(shù)組:Java arrays的元素個數(shù)不能下標越界,從很大程度上保證了Java程序的安全性,而其他一些語言出現(xiàn)這一問題時常導致災難性的后果。 ??????? Array可以存放Object和基本數(shù)據(jù)類型,但創(chuàng)建時必須指定數(shù)組的大小,并不能再改變。值得注意的是:當Array中的某一元素存放的是Objrct reference?時,Java不會調(diào)用默認的構(gòu)造函數(shù),而是將其初值設(shè)為null,當然這跟Java對各類型數(shù)據(jù)賦默認值的規(guī)則是一樣的,對基本數(shù)據(jù)類型同樣適用。
2、Vector:對比于Array,當更多的元素被加入進來以至超出其容量時,Vector的size會動態(tài)增長,而Array容量是定死的。同時,Vector在刪除一些元素后,其所有下標大于被刪除元素的元素都依次前移,并獲得新下標比原來的小了)。注意:當調(diào)用Vector的size()方法時,返回Vector中實際元素的個數(shù)。 ???? Vector內(nèi)部實際是以Array實現(xiàn)的,也通過元素的整數(shù)索引來訪問元素,但它只能存放java.lang.Object對象,不能用于存放基本類型數(shù)據(jù),比如要存放一個整數(shù)10,得用new Integer(10)構(gòu)造出一個Integer包裝類對象再放進去。當Vector中的元素個數(shù)發(fā)生變化時, 其內(nèi)部的Array必須重新分配并進行拷貝,因此這是一點值得考慮的效率問題。 ???? Vetor同時也實現(xiàn)了List接口,所以也可以算作Colletion了,只是它還特殊在:Vector is synchronized。即Vetor對象自身實現(xiàn)了同步機制。
3、ArrayList:實現(xiàn)了List接口,功能與Vetor一樣,只是沒有同步機制,當然元素的訪問方式為從List中繼承而來,可存放任何類型的對象。
4、HashMap:繼承了Map接口,實現(xiàn)用Keys來存儲和訪問Values,Keys和Values都可以為空,它與Hashtable類的區(qū)別在于Hashtable類的Keys不能為null,并Hashtable類有同步機制控制,而HashMap類沒有。 ????? 在Struts類庫中實現(xiàn)了一個LableValueBean,用Lable(Key)來存儲和訪問Value,很方便。
本文提供一個項目中的錯誤實例,提供對其觀察和分析,揭示出 Java語言實例化一個對象具體過程,最后總結(jié)出設(shè)計Java類的一個重要規(guī)則。通過閱讀本文,可以使Java程序員理解Java對象的構(gòu)造過程,從而設(shè)計出更加健壯的代碼。本文適合Java初學者和需要提高的Java程序員閱讀。 程序擲出了一個異常 作者曾經(jīng)在一個項目里面向項目組成員提供了一個抽象的對話框基類,使用者只需在子類中實現(xiàn)基類的一個抽象方法來畫出顯示數(shù)據(jù)的界面,就可使項目內(nèi)的對話框具有相同的風格。具體的代碼實現(xiàn)片斷如下(為了簡潔起見,省略了其他無關(guān)的代碼): 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() { ... // 創(chuàng)建對話框頭部 }
// 創(chuàng)建對話框客戶區(qū)域,交給子類實現(xiàn) protected abstract JPanel createClientPanel();
private JPanel createButtonPanel { ... // 創(chuàng)建按鈕區(qū)域 } } |
這個類在有的代碼中工作得很好,但一個同事在使用時,程序卻擲出了一個NullPointerException違例!經(jīng)過比較,找出了工作正常和不正常的程序的細微差別,代碼片斷分別如下: 一、正常工作的代碼: 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"); // 外部的調(diào)用 |
二、工作不正常的代碼: 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(); // 外部的調(diào)用 |
你看出來兩段代碼之間的差別了嗎?對了,兩者的差別僅僅在于類變量jTextFieldName的初始化時間。經(jīng)過跟蹤,發(fā)現(xiàn)在執(zhí)行panel.add(jTextFieldName)語句之時,jTextFieldName確實是空值。 我們知道,Java允許在定義類變量的同時給變量賦初始值。系統(tǒng)運行過程中需要創(chuàng)建一個對象的時候,首先會為對象分配內(nèi)存空間,然后在“先于調(diào)用任何方法之前”根據(jù)變量在類內(nèi)的定義順序來初始化變量,接著再調(diào)用類的構(gòu)造方法。那么,在本例中,為什么在變量定義時便初始化的代碼反而會出現(xiàn)空指針違例呢? 對象的創(chuàng)建過程和初始化 實際上,前面提到的“變量初始化發(fā)生在調(diào)用任何方法包括構(gòu)造方法之前”這句話是不確切的,當我們把眼光集中在單個類上時,該說法成立;然而,當把視野擴大到具有繼承關(guān)系的兩個或多個類上時,該說法不成立。 對象的創(chuàng)建一般有兩種方式,一種是用new操作符,另一種是在一個Class對象上調(diào)用newInstance方法;其創(chuàng)建和初始化的實際過程是一樣的: 首先為對象分配內(nèi)存空間,包括其所有父類的可見或不可見的變量的空間,并初始化這些變量為默認值,如int類型為0,boolean類型為false,對象類型為null; 然后用下述5個步驟來初始化這個新對象: 1)分配參數(shù)給指定的構(gòu)造方法; 2)如果這個指定的構(gòu)造方法的第一個語句是用this指針顯式地調(diào)用本類的其它構(gòu)造方法,則遞歸執(zhí)行這5個步驟;如果執(zhí)行過程正常則跳到步驟5; 3)如果構(gòu)造方法的第一個語句沒有顯式調(diào)用本類的其它構(gòu)造方法,并且本類不是Object類(Object是所有其它類的祖先),則調(diào)用顯式(用super指針)或隱式地指定的父類的構(gòu)造方法,遞歸執(zhí)行這5個步驟;如果執(zhí)行過程正常則跳到步驟5; 4)按照變量在類內(nèi)的定義順序來初始化本類的變量,如果執(zhí)行過程正常則跳到步驟5; 5)執(zhí)行這個構(gòu)造方法中余下的語句,如果執(zhí)行過程正常則過程結(jié)束。 這一過程可以從下面的時序圖中獲得更清晰的認識:  對分析本文的實例最重要的,用一句話說,就是“父類的構(gòu)造方法調(diào)用發(fā)生在子類的變量初始化之前”。可以用下面的例子來證明: // 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(); } } |
運行這段代碼,它的執(zhí)行結(jié)果如下: Store Animal Cat Petstore 從結(jié)果中可以看出,在創(chuàng)建一個Petstore類的實例時,首先調(diào)用了它的父類Store的構(gòu)造方法;然后試圖創(chuàng)建并初始化變量cat;在創(chuàng)建cat時,首先調(diào)用了Cat類的父類Animal的構(gòu)造方法;其后才是Cat的構(gòu)造方法主體,最后才是Petstore類的構(gòu)造方法的主體。 尋找程序產(chǎn)生例外的原因
現(xiàn)在回到本文開始提到的實例中來,當程序創(chuàng)建一個ChildDlg2的實例時,根據(jù)super(null, “Title”)語句,首先執(zhí)行其父類BaseDlg的構(gòu)造方法;在BaseDlg的構(gòu)造方法中調(diào)用了createClientPanel()方法,
這個方法是抽象方法并且被子類ChildDlg2實現(xiàn)了,因此,實際調(diào)用的方法是ChildDlg2中的createClientPanel()方法(因為Java里面采用“動態(tài)綁定”來綁定所有非final的方法);createClientPanel()方法使用了ChildDlg2類的實例變量jTextFieldName,而此時ChildDlg2的變量初始化過程尚未進行,jTextFieldName是null值!所以,ChildDlg2的構(gòu)造過程擲出一個NullPointerException也就不足為奇了。
再來看ChildDlg1,它的jTextFieldName的初始化代碼寫在了createClientPanel()方法內(nèi)部的開始處,這樣它就能保證在使用之前得到正確的初始化,因此這段代碼工作正常。
解決問題的兩種方式
通過上面的分析過程可以看出,要排除故障,最簡單的方法就是要求項目組成員在繼承使用BaseDlg類,實現(xiàn)createClientPanel()方法時,凡方法內(nèi)部要使用的變量必須首先正確初始化,就象ChildDlg1一樣。然而,把類變量放在類方法內(nèi)初始化是一種很不好的設(shè)計行為,它最適合的地方就是在變量定義塊和構(gòu)造方法中。
在本文的實例中,引發(fā)錯誤的實質(zhì)并不在ChildDlg2上,而在其父類BaseDlg上,是它在自己的構(gòu)造方法中不適當?shù)卣{(diào)用了一個待實現(xiàn)的抽象方法。
從概念上講,構(gòu)造方法的職責是正確初始化類變量,讓對象進入可用狀態(tài)。而BaseDlg卻賦給了構(gòu)造方法額外的職責。
本文實例的更好的解決方法是修改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); }
/** 創(chuàng)建對話框?qū)嵗螅仨氄{(diào)用此方法來布局用戶界面 */ public void initGUI() { this.getContentPane().add(createClientPanel(), BorderLayout.CENTER); }
private JPanel createHeadPanel() { ... // 創(chuàng)建對話框頭部 }
// 創(chuàng)建對話框客戶區(qū)域,交給子類實現(xiàn) protected abstract JPanel createClientPanel();
private JPanel createButtonPanel { ... // 創(chuàng)建按鈕區(qū)域 } } |
新的BaseDlg類增加了一個initGUI()方法,程序員可以這樣使用這個類:
ChildDlg dlg = new ChildDlg(); dlg.initGUI(); dlg.setVisible(true); |
總結(jié)
類的構(gòu)造方法的基本目的是正確初始化類變量,不要賦予它過多的職責。
設(shè)計類構(gòu)造方法的基本規(guī)則是:用盡可能簡單的方法使對象進入就緒狀態(tài);如果可能,避免調(diào)用任何方法。在構(gòu)造方法內(nèi)唯一能安全調(diào)用的是基類中具有final屬性的方法或者private方法(private方法會被編譯器自動設(shè)置final屬性)。final的方法因為不能被子類覆蓋,所以不會產(chǎn)生問題。 | |
所以那些有子類實現(xiàn)的方法盡量不要放在父類的構(gòu)造函數(shù)中,一定要注意設(shè)計好構(gòu)造函數(shù)。
這個問題一直困擾我很久,一直沒有完全的理解: abstractclass和interface是Java語言中對于抽象類定義進行支持的兩種機制,正是由于這兩種機制的存在,才賦予了Java強大的面向?qū)ο竽芰Α?
abstractclass和interface之間在對于抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發(fā)者在進行抽象類定義時對于abstractclass和interface的選擇顯得比較隨意。其實,兩者之間還是有很大的區(qū)別的,對于它們的選擇甚至反映出對于問題領(lǐng)域本質(zhì)的理解、對于設(shè)計意圖的理解是否正確、合理。本文將對它們之間的區(qū)別進行一番剖析,試圖給開發(fā)者提供一個在二者之間進行選擇的依據(jù)。理解抽象類 abstractclass和interface在Java語言中都是用來進行抽象類(本文中的抽象類并非從abstractclass翻譯而來,它表示的是一個抽象體,而abstractclass為Java語言中用于定義抽象類的一種方法,請讀者注意區(qū)分)定義的,那么什么是抽象類,使用抽象類能為我們帶來什么好處呢? 在面向?qū)ο蟮母拍钪校覀冎浪械膶ο蠖际峭ㄟ^類來描繪的,但是反過來卻不是這樣。并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類往往用來表征我們在對問題領(lǐng)域進行分析、設(shè)計中得出的抽象概念,是對一系列看上去不同,但是本質(zhì)上相同的具體概念的抽象。比如:如果我們進行一個圖形編輯軟件的開發(fā),就會發(fā)現(xiàn)問題領(lǐng)域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬于形狀這樣一個概念,形狀這個概念在問題領(lǐng)域是不存在的,它就是一個抽象概念。正是因為抽象的概念在問題領(lǐng)域沒有對應的具體概念,所以用以表征抽象概念的抽象類是不能夠?qū)嵗摹?/p> 在面向?qū)ο箢I(lǐng)域,抽象類主要用來進行類型隱藏。我們可以構(gòu)造出一個固定的一組行為的抽象描述,但是這組行為卻能夠有任意個可能的具體實現(xiàn)方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現(xiàn)則表現(xiàn)為所有可能的派生類。模塊可以操作一個抽象體。由于模塊依賴于一個固定的抽象體,因此它可以是不允許修改的;同時,通過從這個抽象體派生,也可擴展此模塊的行為功能。熟悉OCP的讀者一定知道,為了能夠?qū)崿F(xiàn)面向?qū)ο笤O(shè)計的一個最核心的原則OCP(Open-ClosedPrinciple),抽象類是其中的關(guān)鍵所在。 從語法定義層面看abstractclass和interface 在語法層面,Java語言對于abstractclass和interface給出了不同的定義方式,下面以定義一個名為Demo的抽象類為例來說明這種不同。 使用abstractclass的方式定義Demo抽象類的方式如下: abstractclassDemo{ abstractvoidmethod1(); abstractvoidmethod2(); … } 使用interface的方式定義Demo抽象類的方式如下: interfaceDemo{ voidmethod1(); voidmethod2(); … } 在abstractclass方式中,Demo可以有自己的數(shù)據(jù)成員,也可以有非abstarct的成員方法,而在interface方式的實現(xiàn)中,Demo只能夠有靜態(tài)的不能被修改的數(shù)據(jù)成員(也就是必須是staticfinal的,不過在interface中一般不定義數(shù)據(jù)成員),所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstractclass。 從編程的角度來看,abstractclass和interface都可以用來實現(xiàn)"designbycontract"的思想。但是在具體的使用上面還是有一些區(qū)別的。 首先,abstractclass在Java語言中表示的是一種繼承關(guān)系,一個類只能使用一次繼承關(guān)系。但是,一個類卻可以實現(xiàn)多個interface。也許,這是Java語言的設(shè)計者在考慮Java對于多重繼承的支持方面的一種折中考慮吧。 其次,在abstractclass的定義中,我們可以賦予方法的默認行為。但是在interface的定義中,方法卻不能擁有默認行為,為了繞過這個限制,必須使用委托,但是這會增加一些復雜性,有時會造成很大的麻煩。 在抽象類中不能定義默認行為還存在另一個比較嚴重的問題,那就是可能會造成維護上的麻煩。因為如果后來想修改類的界面(一般通過abstractclass或者interface來表示)以適應新的情況(比如,添加新的方法或者給已用的方法中添加新的參數(shù))時,就會非常的麻煩,可能要花費很多的時間(對于派生類很多的情況,尤為如此)。但是如果界面是通過abstractclass來實現(xiàn)的,那么可能就只需要修改定義在abstractclass中的默認行為就可以了。 同樣,如果不能在抽象類中定義默認行為,就會導致同樣的方法實現(xiàn)出現(xiàn)在該抽象類的每一個派生類中,違反了"onerule,oneplace"原則,造成代碼重復,同樣不利于以后的維護。因此,在abstractclass和interface間進行選擇時要非常的小心。 從設(shè)計理念層面看abstractclass和interface 上面主要從語法定義和編程的角度論述了abstractclass和interface的區(qū)別,這些層面的區(qū)別是比較低層次的、非本質(zhì)的。本小節(jié)將從另一個層面:abstractclass和interface所反映出的設(shè)計理念,來分析一下二者的區(qū)別。作者認為,從這個層面進行分析才能理解二者概念的本質(zhì)所在。 前面已經(jīng)提到過,abstarctclass在Java語言中體現(xiàn)了一種繼承關(guān)系,要想使得繼承關(guān)系合理,父類和派生類之間必須存在"isa"關(guān)系,即父類和派生類在概念本質(zhì)上應該是相同的(參考文獻〔3〕中有關(guān)于"isa"關(guān)系的大篇幅深入的論述,有興趣的讀者可以參考)。對于interface來說則不然,并不要求interface的實現(xiàn)者和interface定義在概念本質(zhì)上是一致的,僅僅是實現(xiàn)了interface定義的契約而已。為了使論述便于理解,下面將通過一個簡單的實例進行說明。 考慮這樣一個例子,假設(shè)在我們的問題領(lǐng)域中有一個關(guān)于Door的抽象概念,該Door具有執(zhí)行兩個動作open和close,此時我們可以通過abstractclass或者interface來定義一個表示該抽象概念的類型,定義方式分別如下所示: 使用abstractclass方式定義Door: abstractclassDoor{ abstractvoidopen(); abstractvoidclose(); } 使用interface方式定義Door: interfaceDoor{ voidopen(); voidclose(); } 其他具體的Door類型可以extends使用abstractclass方式定義的Door或者implements使用interface方式定義的Door。看起來好像使用abstractclass和interface沒有大的區(qū)別。 | |
如果現(xiàn)在要求Door還要具有報警的功能。
我們該如何設(shè)計針對該例子的類結(jié)構(gòu)呢(在本例中,主要是為了展示abstractclass和interface反映在設(shè)計理念上的區(qū)別,其他方面無關(guān)的問題都做了簡化或者忽略)?下面將羅列出可能的解決方案,并從設(shè)計理念層面對這些不同的方案進行分析。解決方案一: 簡單的在Door的定義中增加一個alarm方法,如下: abstractclassDoor{ abstractvoidopen(); abstractvoidclose(); abstractvoidalarm(); } 或者 interfaceDoor{ voidopen(); voidclose(); voidalarm(); } 那么具有報警功能的AlarmDoor的定義方式如下: classAlarmDoorextendsDoor{ voidopen(){…} voidclose(){…} voidalarm(){…} } 或者 classAlarmDoorimplementsDoor{ voidopen(){…} voidclose(){…} voidalarm(){…} } 這種方法違反了面向?qū)ο笤O(shè)計中的一個核心原則ISP(InterfaceSegregationPriciple),在Door的定義中把Door概念本身固有的行為方法和另外一個概念"報警器"的行為方法混在了一起。這樣引起的一個問題是那些僅僅依賴于Door這個概念的模塊會因為"報警器"這個概念的改變(比如:修改alarm方法的參數(shù))而改變,反之依然。 解決方案二: 既然open、close和alarm屬于兩個不同的概念,根據(jù)ISP原則應該把它們分別定義在代表這兩個概念的抽象類中。定義方式有:這兩個概念都使用abstractclass方式定義;兩個概念都使用interface方式定義;一個概念使用abstractclass方式定義,另一個概念使用interface方式定義。 顯然,由于Java語言不支持多重繼承,所以兩個概念都使用abstractclass方式定義是不可行的。后面兩種方式都是可行的,但是對于它們的選擇卻反映出對于問題領(lǐng)域中的概念本質(zhì)的理解、對于設(shè)計意圖的反映是否正確、合理。我們一一來分析、說明。 如果兩個概念都使用interface方式來定義,那么就反映出兩個問題:1、我們可能沒有理解清楚問題領(lǐng)域,AlarmDoor在概念本質(zhì)上到底是Door還是報警器?2、如果我們對于問題領(lǐng)域的理解沒有問題,比如:我們通過對于問題領(lǐng)域的分析發(fā)現(xiàn)AlarmDoor在概念本質(zhì)上和Door是一致的,那么我們在實現(xiàn)時就沒有能夠正確的揭示我們的設(shè)計意圖,因為在這兩個概念的定義上(均使用interface方式定義)反映不出上述含義。 如果我們對于問題領(lǐng)域的理解是:AlarmDoor在概念本質(zhì)上是Door,同時它有具有報警的功能。我們該如何來設(shè)計、實現(xiàn)來明確的反映出我們的意思呢?前面已經(jīng)說過,abstractclass在Java語言中表示一種繼承關(guān)系,而繼承關(guān)系在本質(zhì)上是"isa"關(guān)系。所以對于Door這個概念,我們應該使用abstarctclass方式來定義。另外,AlarmDoor又具有報警功能,說明它又能夠完成報警概念中定義的行為,所以報警概念可以通過interface方式定義。如下所示: abstractclassDoor{ abstractvoidopen(); abstractvoidclose(); } interfaceAlarm{ voidalarm(); } classAlarmDoorextendsDoorimplementsAlarm{ voidopen(){…} voidclose(){…} voidalarm(){…} } 這種實現(xiàn)方式基本上能夠明確的反映出我們對于問題領(lǐng)域的理解,正確的揭示我們的設(shè)計意圖。其實abstractclass表示的是"isa"關(guān)系,interface表示的是"likea"關(guān)系,大家在選擇時可以作為一個依據(jù),當然這是建立在對問題領(lǐng)域的理解上的,比如:如果我們認為AlarmDoor在概念本質(zhì)上是報警器,同時又具有Door的功能,那么上述的定義方式就要反過來了。 | |
好多同志對 iframe 是如何控制的,并不是十分了解,基本上還處于一個模糊的認識狀態(tài).
注意兩個事項,ifr 是一個以存在的 iframe 的 ID 和 NAME 值: ??? document.getElementById(“ifr”); ??? window.frames[“ifr”];
要想使用iframe內(nèi)的函數(shù),變量就必須通過第二種方法.因為它取的是一個完整的DOM模型(不知道這樣說對不對).第一種方法只是取出了一個OBJECT而已.
如果只想改變iframe的 src 或者 border , scrolling 等 attributes(與property不是一個概念,property是不能寫在標簽內(nèi)的,比如:scrollHeight,innerHTML等),就需要用到第一種方法.
如果想取得iframe的頁面(不是iframe本身),就需要使用第二種方法,因為它取得的是一個完整的DOM模型,比如想得到iframe的document.body的內(nèi)容,就只能用第二種方法.
還要注意的是,如果在iframe的頁面未完全裝入的時候,調(diào)用iframe的DOM模型,會發(fā)生很嚴重的錯誤,所以,你要準備一個容錯模式.
下面是示
網(wǎng)頁頭部標簽meta詳解
關(guān)鍵詞: 收集meta資料 ?? ??????????????????????????????????????
網(wǎng)頁頭部標簽meta詳解 ??? 網(wǎng)頁頭部有兩個標簽titel和meta, title比較簡單,就是網(wǎng)頁標題,meta的內(nèi)容還是蠻多了,為了自己今后能夠比較好的參考,我收集了些資料,也希望對大家有用。 一、meta標簽的組成 ???? meta標簽共有兩個屬性,它們分別是http-equiv屬性和name屬性,不同的屬性又有不同的參數(shù)值,這些不同的參數(shù)值就實現(xiàn)了不同的網(wǎng)頁功能。 1、name屬性 name屬性主要用于描述網(wǎng)頁,與之對應的屬性值為content,content中的內(nèi)容主要是便于搜索引擎機器人查找信息和分類信息用的。meat標簽的name屬性語法格式是:<meta name="參數(shù)" content="具體的參數(shù)值"> 。 ????其中name屬性主要有以下幾種參數(shù): ???? A、Keywords(關(guān)鍵字) ???? 說明:keywords用來告訴搜索引擎你網(wǎng)頁的關(guān)鍵字是什么。 ???? 舉例:<meta name ="keywords" content="science, education,culture,politics,ecnomics,relationships, entertaiment, human"> ????? B、description(網(wǎng)站內(nèi)容描述) ???? 說明:description用來告訴搜索引擎你的網(wǎng)站主要內(nèi)容。 ???? 舉例:<meta name="description" content="This page is about the meaning of science, education,culture."> ???? C、robots(機器人向?qū)?span lang="EN-US">) ???? 說明:robots用來告訴搜索機器人哪些頁面需要索引,哪些頁面不需要索引。 ???? content的參數(shù)有all,none,index,noindex,follow,nofollow。默認是all。 ???? 舉例:<meta name="robots" content="none"> ???? D、author(作者) ???? 說明:標注網(wǎng)頁的作者 ???? 舉例:<meta name="author" content="***@yahoo.com.con"> ??? 2、http-equiv屬性 ???? http-equiv顧名思義,相當于http的文件頭作用,它可以向瀏覽器傳回一些有用的信息,以幫助正確和精確地顯示網(wǎng)頁內(nèi)容,與之對應的屬性值為content,content中的內(nèi)容其實就是各個參數(shù)的變量值。 ???? meat標簽的http-equiv屬性語法格式是:<meta http-equiv="參數(shù)" content="參數(shù)變量值"> ;其中http-equiv屬性主要有以下幾種參數(shù): ???? A、Expires(期限) ???? 說明:可以用于設(shè)定網(wǎng)頁的到期時間。一旦網(wǎng)頁過期,必須到服務器上重新傳輸。 ???? 用法:<meta http-equiv="expires" content="Fri, 12 Jan 2001 18:18:18 GMT"> ???? 注意:必須使用GMT的時間格式。 ???? B、Pragma(cache模式) ???? 說明:禁止瀏覽器從本地計算機的緩存中訪問頁面內(nèi)容。 ???? 用法:<meta http-equiv="Pragma" content="no-cache"> ???? 注意:這樣設(shè)定,訪問者將無法脫機瀏覽。 ???? C、Refresh(刷新) ???? 說明:自動刷新并指向新頁面。 ???? 用法:<meta http-equiv="Refresh" content="2;URL=http://phigo.bokee.com"> ??? 注意:其中的2是指停留2秒鐘后自動刷新到URL網(wǎng)址。 ???? D、Set-Cookie(cookie設(shè)定) ??? 說明:如果網(wǎng)頁過期,那么存盤的cookie將被刪除。 ???? 用法:<meta http-equiv="Set-Cookie" content="cookievalue=xxx;?expires=Friday, 8-Aug -2008 18:18:18 GMT; path=/"> ???? 注意:必須使用GMT的時間格式。 ???? E、Window-target(顯示窗口的設(shè)定) ???? 說明:強制頁面在當前窗口以獨立頁面顯示。 ???? 用法:<meta http-equiv="Window-target" content="_top"> ???? 注意:用來防止別人在框架里調(diào)用自己的頁面。 ???? F、content-Type(顯示字符集的設(shè)定) ???? 說明:設(shè)定頁面使用的字符集。 ???? 用法:<meta http-equiv="content-Type" content="text/html; charset=gb2312"> G、Page-Exit /Page-Enter(進入頁面、離開頁面動畫效果) 使用meta標簽,我們還可以在進入網(wǎng)頁或者離開網(wǎng)頁的一剎那實現(xiàn)動畫效果,我們只要在頁面的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)"> 一旦上述代碼被加到一個網(wǎng)頁中后,我們再進出頁面時就會看到一些特殊效果,這個功能其實與FrontPage2000中的Format/Page Transition一樣,但我們要注意的是所加網(wǎng)頁不能是一個Frame頁;Duration的值為網(wǎng)頁動態(tài)過渡的時間,單位為秒。Transition是過渡方式,它的值為0到23,分別對應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 隨機產(chǎn)生一種過渡方式 |
|