<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    一輩子的程序員?

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

    使用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)閉,一個是只讀的,另外一個是可寫入的。

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

    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文件的可寫的副本,所有的修改都是在這個副本上做的。下面是一個例子。

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

    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)鍵字,給程序員的編程帶來了很大的便利,但并不是說要讓你到處使用,一旦達到濫用的程序,便適得其反,所以在使用時請一定要認真考慮。

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

    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教程。
      

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

    泊船瓜洲 @ 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)出威力,而不僅僅供我們飯后閑聊。

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

    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,很方便。

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

     本文提供一個項目中的錯誤實例,提供對其觀察和分析,揭示出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é)束。

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

      通過實例學習Java對象的構(gòu)造過程

      對分析本文的實例最重要的,用一句話說,就是“父類的構(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ù)。

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

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

    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ū)別。

    收藏此頁】【】【打印】【關(guān)閉

    如果現(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的功能,那么上述的定義方式就要反過來了。

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

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

    注意兩個事項,ifr 是一個以存在的 iframe 的 ID 和 NAME 值:
    &#61548;??? document.getElementById(“ifr”);
    &#61548;??? 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ā)生很嚴重的錯誤,所以,你要準備一個容錯模式.

    下面是示

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

    網(wǎng)頁頭部標簽meta詳解

    關(guān)鍵詞收集meta資料 ?? ??????????????????????????????????????

    網(wǎng)頁頭部標簽meta詳解

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

    一、meta標簽的組成

    ???? meta標簽共有兩個屬性,它們分別是http-equiv屬性和name屬性,不同的屬性又有不同的參數(shù)值,這些不同的參數(shù)值就實現(xiàn)了不同的網(wǎng)頁功能。

    1、name屬性

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

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

    ???? AKeywords(關(guān)鍵字)

    ???? 說明:keywords用來告訴搜索引擎你網(wǎng)頁的關(guān)鍵字是什么。

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

    ????? Bdescription(網(wǎng)站內(nèi)容描述)

    ???? 說明:description用來告訴搜索引擎你的網(wǎng)站主要內(nèi)容。

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

    ???? Crobots(機器人向?qū)?span lang="EN-US">)

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

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

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

    ???? Dauthor(作者)

    ???? 說明:標注網(wǎng)頁的作者

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

    ??? 2http-equiv屬性

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

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

    ???? AExpires(期限)

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

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

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

    ???? BPragma(cache模式)

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

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

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

    ???? CRefresh(刷新)

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

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

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

    ???? DSet-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的時間格式。

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

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

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

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

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

    ???? 說明:設(shè)定頁面使用的字符集。

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

    GPage-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是過渡方式,它的值為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 隨機產(chǎn)生一種過渡方式

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

    僅列出標題
    共3頁: 上一頁 1 2 3 下一頁 
    主站蜘蛛池模板: 日本成人免费在线| 中美日韩在线网免费毛片视频| 可以免费观看的毛片| 在线a亚洲v天堂网2019无码| 一级做a爰性色毛片免费| 亚洲fuli在线观看| 久久免费看黄a级毛片 | 亚洲jjzzjjzz在线观看| 亚洲人成图片小说网站| 18禁黄网站禁片免费观看不卡| 国产精品无码永久免费888| 亚洲AV第一页国产精品| 国产猛男猛女超爽免费视频| 日韩在线视频线视频免费网站| 亚洲美女又黄又爽在线观看| 免费a级毛片大学生免费观看| 两个人看的www免费高清| 久久久无码精品亚洲日韩按摩| 57PAO成人国产永久免费视频| 亚洲激情视频图片| 亚洲M码 欧洲S码SSS222| 中文永久免费观看网站| 乱爱性全过程免费视频| 爱爱帝国亚洲一区二区三区| 国产亚洲一区区二区在线| 一级特黄aa毛片免费观看| 最新亚洲精品国偷自产在线| 伊人久久亚洲综合影院| 69视频在线观看免费| 看全免费的一级毛片| 亚洲av日韩av高潮潮喷无码| 亚洲精品成人片在线播放 | 亚洲最大在线视频| 亚洲视频在线精品| 最近2019中文字幕mv免费看| 日本精品久久久久久久久免费 | 99爱视频99爱在线观看免费| 免费无码婬片aaa直播表情| 色妞www精品视频免费看| 亚洲免费观看网站| 亚洲中文字幕无码久久综合网|