前言:
在我們學習Java的過程中,掌握其中的基本概念對我們的學習無論是J2SE,J2EE,J2ME都是很重要的,J2SE是Java的基礎,所以有必要對其中的基本概念做以歸納,以便大家在以后的學習過程中更好的理解java的精髓,在此我總結了30條基本的概念.
Java概述:
目前Java主要應用于中間件的開發(middleware)---處理客戶機于服務器之間的通信技術,早期的實踐證明,Java不適合pc應用程序的開發,其發展逐漸變成在開發手持設備,互聯網信息站,及車載計算機的開發.Java于其他語言所不同的是程序運行時提供了平臺的獨立性,稱許可以在windows,solaris,linux其他操作系統上使用完全相同的代碼.Java的語法與C++語法類似,C++/C程序員很容易掌握,而且Java是完全的徹底的面向對象的,其中提出了很好的GC(Garbage Collector)垃圾處理機制,防止內存溢出.
Java的白皮書為我們提出了Java語言的關鍵特性.
(1)Easy:Java的語法比C++的相對簡單,另一個方面就是Java能使軟件在很小的機器上運行,基礎解釋其和類庫的支持的大小約為40kb,增加基本的標準庫和線程支持的內存需要增加125kb.
(2)分布式:Java帶有很強大的TCP/IP協議族的例程庫,Java應用程序能夠通過URL來穿過網絡來訪問遠程對象,由于servlet機制的出現,使Java編程非常的高效,現在許多的大的web server都支持servlet.
(3)OO:面向對象設計是把重點放在對象及對象的接口上的一個編程技術.其面向對象和C++有很多不同,在與多重繼承的處理及Java的原類模型.
(4)健壯特性:Java采取了一個安全指針模型,能減小重寫內存和數據崩潰的可能型
(5)安全:Java用來設計網路和分布系統,這帶來了新的安全問題,Java可以用來構建防病毒和防攻擊的System.事實證明Java在防毒這一方面做的比較好.
(6)中立體系結構:Java編譯其生成體系結構中立的目標文件格式可以在很多處理器上執行,編譯器產生的指令字節碼(Javabytecode)實現此特性,此字節碼可以在任何機器上解釋執行.
(7)可移植性:Java中對基本數據結構類型的大小和算法都有嚴格的規定所以可移植性很好.
(8)多線程:Java處理多線程的過程很簡單,Java把多線程實現交給底下操作系統或線程程序完成.所以多線程是Java作為服務器端開發語言的流行原因之一
(9)Applet和servlet:能夠在網頁上執行的程序叫Applet,需要支持Java的瀏覽器很多,而applet支持動態的網頁,這是很多其他語言所不能做到的.
基本概念:
1.OOP中惟一關系的是對象的接口是什么,就像計算機的銷售商她不管電源內部結構是怎樣的,他只關系能否給你提供電就行了,也就是只要知道can or not而不是how and why.所有的程序是由一定的屬性和行為對象組成的,不同的對象的訪問通過函數調用來完成,對象間所有的交流都是通過方法調用,通過對封裝對象數據,很大限度上提高復用率.
2.OOP中最重要的思想是類,類是模板是藍圖,從類中構造一個對象,即創建了這個類的一個實例(instance)
3.封裝:就是把數據和行為結合起在一個包中)并對對象使用者隱藏數據的實現過程,一個對象中的數據叫他的實例字段(instance field)
4.通過擴展一個類來獲得一個新類叫繼承(inheritance),而所有的類都是由Object根超類擴展而得,根超類下文會做介紹.
5.對象的3個主要特性
behavior---說明這個對象能做什么.
state---當對象施加方法時對象的反映.
identity---與其他相似行為對象的區分標志.
每個對象有惟一的indentity 而這3者之間相互影響.
6.類之間的關系:
use-a :依賴關系
has-a :聚合關系
is-a :繼承關系--例:A類繼承了B類,此時A類不僅有了B類的方法,還有其自己的方法.(個性存在于共性中)
7.構造對象使用構造器:構造器的提出,構造器是一種特殊的方法,構造對象并對其初始化.
例:Data類的構造器叫Data
new Data()---構造一個新對象,且初始化當前時間.
Data happyday=new Data()---把一個對象賦值給一個變量happyday,從而使該對象能夠多次使用,此處要聲明的使變量與對象變量二者是不同的.new返回的值是一個引用.
構造器特點:構造器可以有0個,一個或多個參數構造器和類有相同的名字一個類可以有多個構造器,構造器沒有返回值,構造器總是和new運算符一起使用.
8.重載:當多個方法具有相同的名字而含有不同的參數時,便發生重載.編譯器必須挑選出調用哪個方法.
9.包(package)Java允許把一個或多個類收集在一起成為一組,稱作包,以便于組織任務,標準Java庫分為許多包.java.lang java.util java,net等,包是分層次的所有的java包都在java和javax包層次內.
10.繼承思想:允許在已經存在的類的基礎上構建新的類,當你繼承一個已經存在的類時,那么你就復用了這個類的方法和字段,同時你可以在新類中添加新的方法和字段.
11.擴展類:擴展類充分體現了is-a的繼承關系. 形式為:class (子類) extends (基類).
12.多態:在java中,對象變量是多態的.而java中不支持多重繼承.
13.動態綁定:調用對象方法的機制.
(1)編譯器檢查對象聲明的類型和方法名.
(2)編譯器檢查方法調用的參數類型.
(3)靜態綁定:若方法類型為priavte static final 編譯器會準確知道該調用哪個方法.
(4)當程序運行并且使用動態綁定來調用一個方法時,那么虛擬機必須調用x所指向的對象的實際類型相匹配的方法版本.
(5)動態綁定:是很重要的特性,它能使程序變得可擴展而不需要重編譯已存代碼.
14.final類:為防止他人從你的類上派生新類,此類是不可擴展的.
15.動態調用比靜態調用花費的時間要長,
16.抽象類:規定一個或多個抽象方法的類本身必須定義為abstract
例: public abstract string getDescripition
17.Java中的每一個類都是從Object類擴展而來的.
18.object類中的equal和toString方法.equal用于測試一個對象是否同另一個對象相等.toString返回一個代表該對象的字符串,幾乎每一個類都會重載該方法,以便返回當前狀態的正確表示.(toString 方法是一個很重要的方法)
19.通用編程:任何類類型的所有值都可以同object類性的變量來代替.
20.數組列表:ArrayList動態數組列表,是一個類庫,定義在java.uitl包中,可自動調節數組的大小.
21.class類 object類中的getClass方法返回class類型的一個實例,程序啟動時包含在main方法的類會被加載,虛擬機要加載他需要的所有類,每一個加載的類都要加載它需要的類.
22.class類為編寫可動態操縱java代碼的程序提供了強大的功能:反射,這項功能為JavaBeans特別有用,使用反射Java能支持VB程序員習慣使用的工具.
能夠分析類能力的程序叫反射器,Java中提供此功能的包叫Java.lang.reflect反射機制十分強大.
1.在運行時分析類的能力.
2.在運行時探察類的對象.
3.實現通用數組操縱代碼.
4.提供方法對象.
而此機制主要針對是工具者而不是應用及程序.
反射機制中的最重要的部分是允許你檢查類的結構.用到的API有:
java.lang.reflect.Field 返回字段.
java.reflect.Method 返回方法.
java.lang.reflect.Constructor 返回參數.
方法指針:java沒有方法指針,把一個方法的地址傳給另一個方法,可以在后面調用它,而接口是更好的解決方案.
23.接口(Interface)說明類該做什么而不指定如何去做,一個類可以實現一個或多個interface.
24.接口不是一個類,而是對符合接口要求的類的一套規范.
若實現一個接口需要2個步驟:
1.聲明類需要實現的指定接口.
2.提供接口中的所有方法的定義.
聲明一個類實現一個接口需要使用implements 關鍵字
class actionB implements Comparable 其actionb需要提供CompareTo方法,接口不是類,不能用new實例化一個接口.
25.一個類只有一個超類,但一個類能實現多個接口.Java中的一個重要接口Cloneable
26.接口和回調.編程一個常用的模式是回調模式,在這種模式中你可以指定當一個特定時間發生時回調對象上的方法.
例:ActionListener 接口監聽.
類似的API有:java.swing.JOptionPane
java.swing.Timer
java.awt.Tookit
27.對象clone:clone方法是object一個保護方法,這意味著你的代碼不能簡單的調用它.
28.內部類:一個內部類的定義是定義在另一個類的內部,原因是:1.一個內部類的對象能夠訪問創建它的對象的實現,包括私有數據
2.對于同一個包中的其他類來說,內部類能夠隱藏起來.
3.匿名內部類可以很方便的定義回調.
4.使用內部類可以非常方便的編寫事件驅動程序.
29.代理類(proxy):1.指定接口要求所有代碼
2.object類定義的所有的方法(toString equals)
30.數據類型:Java是強調類型的語言,每個變量都必須先申明它都類型,java中總共有8個基本類型.4種是整型,2種是浮點型,一種是字符型,被用于Unicode編碼中的字符,布爾型.(T111)
posted @
2005-11-17 10:51 安德爾斯 閱讀(276) |
評論 (0) |
編輯 收藏
文件I/O:文件流→序列化
★文件流
文件操作是最簡單最直接也是最容易想到的一種方式,我們說的文件操作不僅僅是通過FileInputStream/FileOutputStream這么“裸”的方式直接把數據寫入到本地文件(像我以前寫的一個掃雷的小游戲JavaMine就是這樣保存一局的狀態的),這樣就比較“底層”了。
主要類與方法和描述
FileInputStream.read() //從本地文件讀取二進制格式的數據
FileReader.read() //從本地文件讀取字符(文本)數據
FileOutputStream.write() //保存二進制數據到本地文件
FileWriter.write() //保存字符數據到本地文件
★XML
和上面的單純的I/O方式相比,XML就顯得“高檔”得多,以至于成為一種數據交換的標準。以DOM方式為例,它關心的是首先在內存中構造文檔樹,數據保存在某個結點上(可以是葉子結點,也可以是標簽結點的屬性),構造好了以后一次性的寫入到外部文件,但我們只需要知道文件的位置,并不知道I/O是怎么操作的,XML操作方式可能多數人也實踐過,所以這里也只列出相關的方法,供初學者預先了解一下。主要的包是javax.xml.parsers,org.w3c.dom,javax.xml.transform。
主要類與方法和描述
DocumentBuilderFactory.newDocumentBuilder().parse() //解析一個外部的XML文件,得到一個Document對象的DOM樹
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() //初始化一棵DOM樹
Document.getDocumentElement().appendChild() //為一個標簽結點添加一個子結點
Document.createTextNode() //生成一個字符串結點
Node.getChildNodes() //取得某個結點的所有下一層子結點
Node.removeChild() //刪除某個結點的子結點
Document.getElementsByTagName() 查找所有指定名稱的標簽結點
Document.getElementById() //查找指定名稱的一個標簽結點,如果有多個符合,則返回某一個,通常是第一個
Element.getAttribute() //取得一個標簽的某個屬性的的值
Element.setAttribute() //設置一個標簽的某個屬性的的值
Element.removeAttribute() //刪除一個標簽的某個屬性
TransformerFactory.newInstance().newTransformer().transform() //將一棵DOM樹寫入到外部XML文件
★序列化
使用基本的文件讀寫方式存取數據,如果我們僅僅保存相同類型的數據,則可以用同一種格式保存,譬如在我的JavaMine中保存一個盤局時,需要保存每一個方格的坐標、是否有地雷,是否被翻開等,這些信息組合成一個“復合類型”;相反,如果有多種不同類型的數據,那我們要么把它分解成若干部分,以相同類型(譬如String)保存,要么我們需要在程序中添加解析不同類型數據格式的邏輯,這就很不方便。于是我們期望用一種比較“高”的層次上處理數據,程序員應該花盡可能少的時間和代碼對數據進行解析,事實上,序列化操作為我們提供了這樣一條途徑。
序列化(Serialization)大家可能都有所接觸,它可以把對象以某種特定的編碼格式寫入或從外部字節流(即ObjectInputStream/ObjectOutputStream)中讀取。序列化一個對象非常之簡單,僅僅實現一下Serializable接口即可,甚至都不用為它專門添加任何方法:
public class MySerial implements java.io.Serializable
{
//...
}
但有一個條件:即你要序列化的類當中,它的每個屬性都必須是是“可序列化”的。這句話說起來有點拗口,其實所有基本類型(就是int,char,boolean之類的)都是“可序列化”的,而你可以看看JDK文檔,會發現很多類其實已經實現了Serializable(即已經是“可序列化”的了),于是這些類的對象以及基本數據類型都可以直接作為你需要序列化的那個類的內部屬性。如果碰到了不是“可序列化”的屬性怎么辦?對不起,那這個屬性的類還需要事先實現Serializable接口,如此遞歸,直到所有屬性都是“可序列化”的。
主要類與方法和描述
ObjectOutputStream.writeObject() //將一個對象序列化到外部字節流
ObjectInputStream.readObject() //從外部字節流讀取并重新構造對象
從實際應用上看來,“Serializable”這個接口并沒有定義任何方法,仿佛它只是一個標記(或者說像是Java的關鍵字)而已,一旦虛擬機看到這個“標記”,就會嘗試調用自身預定義的序列化機制,除非你在實現Serializable接口的同時還定義了私有的readObject()或writeObject()方法。這一點很奇怪。不過你要是不愿意讓系統使用缺省的方式進行序列化,那就必須定義上面提到的兩個方法:
public class MySerial implements java.io.Serializable
{
private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
//...
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
{
//...
}
//...
}
譬如你可以在上面的writeObject()里調用默認的序列化方法ObjectOutputStream.defaultWriteObject();譬如你不愿意將某些敏感的屬性和信息序列化,你也可以調用ObjectOutputStream.writeObject()方法明確指定需要序列化那些屬性。關于用戶可定制的序列化方法,我們將在后面提到。
★Bean
上面的序列化只是一種基本應用,你把一個對象序列化到外部文件以后,用notepad打開那個文件,只能從為數不多的一些可讀字符中猜到這是有關這個類的信息文件,這需要你熟悉序列化文件的字節編碼方式,那將是比較痛苦的(在《Core Java 2》第一卷里提到了相關編碼方式,有興趣的話可以查看參考資料),某些情況下我們可能需要被序列化的文件具有更好的可讀性。另一方面,作為Java組件的核心概念“JavaBeans”,從JDK 1.4開始,其規范里也要求支持文本方式的“長期的持久化”(long-term persistence)。
打開JDK文檔,java.beans包里的有一個名為“Encoder”的類,這就是一個可以序列化bean的實用類。和它相關的兩個主要類有XMLEcoder和XMLDecoder,顯然,這是以XML文件的格式保存和讀取bean的工具。他們的用法也很簡單,和上面ObjectOutputStream/ObjectInputStream比較類似。
主要類與方法和描述
XMLEncoder.writeObject() //將一個對象序列化到外部字節流
XMLDecoder.readObject() //從外部字節流讀取并重新構造對象
如果一個bean是如下格式:
public class MyBean
{
int i;
char[] c;
String s;
//...(get和set操作省略)...
}
那么通過XMLEcoder序列化出來的XML文件具有這樣的形式:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.4.0" class="java.beans.XMLDecoder">
<object class="MyBean">
<void property="i">
<int>1</int>
</void>
<void property="c">
<array class="char" length="3">
<void index="0">
<int>a</int>
</void>
<void index="1">
<int>b</int>
</void>
<void index="2">
<int>c</int>
</void>
</array>
</void>
<void property="s">
<string>fox jump!</string>
</void>
</object>
</java>
像AWT和Swing中很多可視化組件都是bean,當然也是可以用這種方式序列化的,下面就是從JDK文檔中摘錄的一個JFrame序列化以后的XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.0" class="java.beans.XMLDecoder">
<object class="javax.swing.JFrame">
<void property="name">
<string>frame1</string>
</void>
<void property="bounds">
<object class="java.awt.Rectangle">
<int>0</int>
<int>0</int>
<int>200</int>
<int>200</int>
</object>
</void>
<void property="contentPane">
<void method="add">
<object class="javax.swing.JButton">
<void property="label">
<string>Hello</string>
</void>
</object>
</void>
</void>
<void property="visible">
<boolean>true</boolean>
</void>
</object>
</java>
因此但你想要保存的數據是一些不是太復雜的類型的話,把它做成bean再序列化也不失為一種方便的選擇。
★Properties
在以前我總結的一篇關于集合框架的小文章里提到過,Properties是歷史集合類的一個典型的例子,這里主要不是介紹它的集合特性。大家可能都經常接觸一些配置文件,如Windows的ini文件,Apache的conf文件,還有Java里的properties文件等,這些文件當中的數據以“關鍵字-值”對的方式保存。“環境變量”這個概念都知道吧,它也是一種“key-value”對,以前也常常看到版上問“如何取得系統某某信息”之類的問題,其實很多都保存在環境變量里,只要用一條
System.getProperties().list(System.out);
就能獲得全部環境變量的列表:
-- listing properties --
java.runtime.name=Java(TM) 2 Runtime Environment, Stand...
sun.boot.library.path=C:\Program Files\Java\j2re1.4.2_05\bin
java.vm.version=1.4.2_05-b04
java.vm.vendor=Sun Microsystems Inc.
java.vendor.url=http://java.sun.com/
path.separator=;
java.vm.name=Java HotSpot(TM) Client VM
file.encoding.pkg=sun.io
user.country=CN
sun.os.patch.level=Service Pack 1
java.vm.specification.name=Java Virtual Machine Specification
user.dir=d:\my documents\項目\eclipse\SWTDemo
java.runtime.version=1.4.2_05-b04
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=C:\Program Files\Java\j2re1.4.2_05\li...
os.arch=x86
java.io.tmpdir=C:\DOCUME~1\cn2lx0q0\LOCALS~1\Temp\
line.separator=
java.vm.specification.vendor=Sun Microsystems Inc.
user.variant=
os.name=Windows XP
sun.java2d.fontpath=
java.library.path=C:\Program Files\Java\j2re1.4.2_05\bi...
java.specification.name=Java Platform API Specification
java.class.version=48.0
java.util.prefs.PreferencesFactory=java.util.prefs.WindowsPreferencesFac...
os.version=5.1
user.home=D:\Users\cn2lx0q0
user.timezone=
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=GBK
java.specification.version=1.4
user.name=cn2lx0q0
java.class.path=d:\my documents\項目\eclipse\SWTDemo\bi...
java.vm.specification.version=1.0
sun.arch.data.model=32
java.home=C:\Program Files\Java\j2re1.4.2_05
java.specification.vendor=Sun Microsystems Inc.
user.language=zh
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=mixed mode
java.version=1.4.2_05
java.ext.dirs=C:\Program Files\Java\j2re1.4.2_05\li...
sun.boot.class.path=C:\Program Files\Java\j2re1.4.2_05\li...
java.vendor=Sun Microsystems Inc.
file.separator=\
java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport...
sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.isalist=pentium i486 i386
主要類與方法和描述
load() //從一個外部流讀取屬性
store() //將屬性保存到外部流(特別是文件)
getProperty() //取得一個指定的屬性
setProperty() //設置一個指定的屬性
list() //列出這個Properties對象包含的全部“key-value”對
System.getProperties() //取得系統當前的環境變量
你可以這樣保存一個properties文件:
Properties prop = new Properties();
prop.setProperty("key1", "value1");
...
FileOutputStream out = new FileOutputStream("config.properties");
prop.store(out, "--這里是文件頭,可以加入注釋--");
★Preferences
如果我說Java里面可以不使用JNI的手段操作Windows的注冊表你信不信?很多軟件的菜單里都有“Setting”或“Preferences”這樣的選項用來設定或修改軟件的配置,這些配置信息可以保存到一個像上面所述的配置文件當中,如果是Windows平臺下,也可能會保存到系統注冊表中。從JDK 1.4開始,Java在java.util下加入了一個專門處理用戶和系統配置信息的java.util.prefs包,其中一個類Preferences是一種比較“高級”的玩意。從本質上講,Preferences本身是一個與平臺無關的東西,但不同的OS對它的SPI(Service Provider Interface)的實現卻是與平臺相關的,因此,在不同的系統中你可能看到首選項保存為本地文件、LDAP目錄項、數據庫條目等,像在Windows平臺下,它就保存到了系統注冊表中。不僅如此,你還可以把首選項導出為XML文件或從XML文件導入。
主要類與方法和描述
systemNodeForPackage() //根據指定的Class對象得到一個Preferences對象,這個對象的注冊表路徑是從“HKEY_LOCAL_MACHINE\”開始的
systemRoot() //得到以注冊表路徑HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs 為根結點的Preferences對象
userNodeForPackage() //根據指定的Class對象得到一個Preferences對象,這個對象的注冊表路徑是從“HKEY_CURRENT_USER\”開始的
userRoot() //得到以注冊表路徑HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs 為根結點的Preferences對象
putXXX() //設置一個屬性的值,這里XXX可以為基本數值型類型,如int、long等,但首字母大寫,表示參數為相應的類型,也可以不寫而直接用put,參數則為字符串
getXXX() //得到一個屬性的值
exportNode() //將全部首選項導出為一個XML文件
exportSubtree() //將部分首選項導出為一個XML文件
importPreferences() //從XML文件導入首選項
你可以按如下步驟保存數據:
Preferences myPrefs1 = Preferences.userNodeForPackage(this);// 這種方法是在“HKEY_CURRENT_USER\”下按當前類的路徑建立一個注冊表項
Preferences myPrefs2 = Preferences.systemNodeForPackage(this);// 這種方法是在“HKEY_LOCAL_MACHINE\”下按當前類的路徑建立一個注冊表項
Preferences myPrefs3 = Preferences.userRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個注冊表項
Preferences myPrefs4 = Preferences.systemRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個注冊表項
myPrefs1.putInt("key1", 10);
myPrefs1.putDouble("key2", -7.15);
myPrefs1.put("key3", "value3");
FileOutputStream out = new FileOutputStream("prefs.xml");
myPrefs1.exportNode(out);
網絡I/O:Socket→RMI
★Socket
Socket編程可能大家都很熟,所以就不多討論了,只是說通過socket把數據保存到遠端服務器或從網絡socket讀取數據也不失為一種值得考慮的方式。
★RMI
RMI機制其實就是RPC(遠程過程調用)的Java版本,它使用socket作為基本傳輸手段,同時也是序列化最重要的一個應用。現在網絡傳輸從編程的角度來看基本上都是以流的方式操作,socket就是一個例子,將對象轉換成字節流的一個重要目標就是為了方便網絡傳輸。
想象一下傳統的單機環境下的程序設計,對于Java語言的函數(方法)調用(注意與C語言函數調用的區別)的參數傳遞,會有兩種情況:如果是基本數據類型,這種情況下和C語言是一樣的,采用值傳遞方式;如果是對象,則傳遞的是對象的引用,包括返回值也是引用,而不是一個完整的對象拷貝!試想一下在不同的虛擬機之間進行方法調用,即使是兩個完全同名同類型的對象他們也很可能是不同的引用!此外對于方法調用過程,由于被調用過程的壓棧,內存“現場”完全被被調用者占有,當被調用方法返回時,才將調用者的地址寫回到程序計數器(PC),恢復調用者的狀態,如果是兩個虛擬機,根本不可能用簡單壓棧的方式來保存調用者的狀態。因為種種原因,我們才需要建立RMI通信實體之間的“代理”對象,譬如“存根”就相當于遠程服務器對象在客戶機上的代理,stub就是這么來的,當然這是后話了。
本地對象與遠程對象(未必是物理位置上的不同機器,只要不是在同一個虛擬機內皆為“遠程”)之間傳遞參數和返回值,可能有這么幾種情形:
值傳遞:這又包括兩種子情形:如果是基本數據類型,那么都是“可序列化”的,統統序列化成可傳輸的字節流;如果是對象,而且不是“遠程對象”(所謂“遠程對象”是實現了java.rmi.Remote接口的對象),本來對象傳遞的應該是引用,但由于上述原因,引用是不足以證明對象身份的,所以傳遞的仍然是一個序列化的拷貝(當然這個對象也必須滿足上述“可序列化”的條件)。
引用傳遞:可以引用傳遞的只能是“遠程對象”。這里所謂的“引用”不要理解成了真的只是一個符號,它其實是一個留在(客戶機)本地stub中的,和遠端服務器上那個真實的對象張得一模一樣的鏡像而已!只是因為它有點“特權”(不需要經過序列化),在本地內存里已經有了一個實例,真正引用的其實是這個“孿生子”。
由此可見,序列化在RMI當中占有多么重要的地位。
數據庫I/O:CMP、Hibernate
★什么是“Persistence”
用過VMWare的朋友大概都知道當一個guest OS正在運行的時候點擊“Suspend”將虛擬OS掛起,它會把整個虛擬內存的內容保存到磁盤上,譬如你為虛擬OS分配了128M的運行內存,那掛起以后你會在虛擬OS所在的目錄下找到一個同樣是128M的文件,這就是虛擬OS內存的完整鏡像!這種內存的鏡像手段其實就是“Persistence”(持久化)概念的由來。
★CMP和Hibernate
因為我對J2EE的東西不是太熟悉,隨便找了點材料看看,所以擔心說的不到位,這次就不作具體總結了,人要學習……真是一件痛苦的事情
序列化再探討
從以上技術的討論中我們不難體會到,序列化是Java之所以能夠出色地實現其鼓吹的兩大賣點??分布式(distributed)和跨平臺(OS independent)的一個重要基礎。TIJ(即“Thinking in Java”)談到I/O系統時,把序列化稱為“lightweight persistence”??“輕量級的持久化”,這確實很有意思。
★為什么叫做“序列”化?
開場白里我說更習慣于把“Serialization”稱為“序列化”而不是“串行化”,這是有原因的。介紹這個原因之前先回顧一些計算機基本的知識,我們知道現代計算機的內存空間都是線性編址的(什么是“線性”知道吧,就是一個元素只有一個唯一的“前驅”和唯一的“后繼”,當然頭尾元素是個例外;對于地址來說,它的下一個地址當然不可能有兩個,否則就亂套了),“地址”這個概念推廣到數據結構,就相當于“指針”,這個在本科低年級大概就知道了。注意了,既然是線性的,那“地址”就可以看作是內存空間的“序號”,說明它的組織是有順序的,“序號”或者說“序列號”正是“Serialization”機制的一種體現。為什么這么說呢?譬如我們有兩個對象a和b,分別是類A和B的實例,它們都是可序列化的,而A和B都有一個類型為C的屬性,根據前面我們說過的原則,C當然也必須是可序列化的。
import java.io.*;
...
class A implements Serializable
{
C c;
...
}
class B implements Serializable
{
C c;
...
}
class C implements Serializable
{
...
}
A a;
B b;
C c1;
...
注意,這里我們在實例化a和b的時候,有意讓他們的c屬性使用同一個C類型對象的引用,譬如c1,那么請試想一下,但我們序列化a和b的時候,它們的c屬性在外部字節流(當然可以不僅僅是文件)里保存的是一份拷貝還是兩份拷貝呢?序列化在這里使用的是一種類似于“指針”的方案:它為每個被序列化的對象標上一個“序列號”(serial number),但序列化一個對象的時候,如果其某個屬性對象是已經被序列化的,那么這里只向輸出流寫入該屬性的序列號;從字節流恢復被序列化的對象時,也根據序列號找到對應的流來恢復。這就是“序列化”名稱的由來!這里我們看到“序列化”和“指針”是極相似的,只不過“指針”是內存空間的地址鏈,而序列化用的是外部流中的“序列號鏈”。
使用“序列號”而不是內存地址來標識一個被序列化的對象,是因為從流中恢復對象到內存,其地址可能就未必是原來的地址了??我們需要的只是這些對象之間的引用關系,而不是死板的原始位置,這在RMI中就更是必要,在兩臺不同的機器之間傳遞對象(流),根本就不可能指望它們在兩臺機器上都具有相同的內存地址。
★更靈活的“序列化”:transient屬性和Externalizable
Serializable確實很方便,方便到你幾乎不需要做任何額外的工作就可以輕松將內存中的對象保存到外部。但有兩個問題使得Serializable的威力收到束縛:
一個是效率問題,《Core Java 2》中指出,Serializable使用系統默認的序列化機制會影響軟件的運行速度,因為需要為每個屬性的引用編號和查號,再加上I/O操作的時間(I/O和內存讀寫差的可是一個數量級的大小),其代價當然是可觀的。
另一個困擾是“裸”的Serializable不可定制,傻乎乎地什么都給你序列化了,不管你是不是想這么做。其實你可以有至少三種定制序列化的選擇。其中一種前面已經提到了,就是在implements Serializable的類里面添加私有的writeObject()和readObject()方法(這種Serializable就不裸了,),在這兩個方法里,該序列化什么,不該序列化什么,那就由你說了算了,你當然可以在這兩個方法體里面分別調用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()仍然執行默認的序列化動作(那你在代碼上不就做無用功了?呵呵),也可以用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()方法對你中意的屬性進行序列化。但虛擬機一看到你定義了這兩個方法,它就不再用默認的機制了。
如果僅僅為了跳過某些屬性不讓它序列化,上面的動作似乎顯得麻煩,更簡單的方法是對不想序列化的屬性加上transient關鍵字,說明它是個“暫態變量”,默認序列化的時候就不會把這些屬性也塞到外部流里了。當然,你如果定義writeObject()和readObject()方法的化,仍然可以把暫態變量進行序列化。題外話,像transient、violate、finally這樣的關鍵字初學者可能會不太重視,而現在有的公司招聘就偏偏喜歡問這樣的問題 :(
再一個方案就是不實現Serializable而改成實現Externalizable接口。我們研究一下這兩個接口的源代碼,發現它們很類似,甚至容易混淆。我們要記住的是:Externalizable默認并不保存任何對象相關信息!任何保存和恢復對象的動作都是你自己定義的。Externalizable包含兩個public的方法:
public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
乍一看這和上面的writeObject()和readObject()幾乎差不多,但Serializable和Externalizable走的是兩個不同的流程:Serializable在對象不存在的情況下,就可以僅憑外部的字節序列把整個對象重建出來;但Externalizable在重建對象時,先是調用該類的默認構造函數(即不含參數的那個構造函數)使得內存中先有這么一個實例,然后再調用readExternal方法對實例中的屬性進行恢復,因此,如果默認構造函數中和readExternal方法中都沒有賦值的那些屬性,特別他們是非基本類型的話,將會是空(null)。在這里需要注意的是,transient只能用在對Serializable而不是Externalizable的實現里面。
★序列化與克隆
從“可序列化”的遞歸定義來看,一個序列化的對象貌似對象內存映象的外部克隆,如果沒有共享引用的屬性的化,那么應該是一個深度克隆。關于克隆的話題有可以談很多,這里就不細說了,有興趣的話可以參考IBM developerWorks上的一篇文章:JAVA中的指針,引用及對象的clone
一點啟示
作為一個實際的應用,我在寫那個簡易的郵件客戶端JExp的時候曾經對比過好幾種保存Message對象(主要是幾個關鍵屬性和郵件的內容)到本地的方法,譬如XML、Properties等,最后還是選擇了用序列化的方式,因為這種方法最簡單, 大約可算是“學以致用”罷。這里“存取程序狀態”其實只是一個引子話題罷了,我想說的是??就如同前面我們討論的關于logging的話題一樣??在Java面前對同一個問題你可以有很多種solution:熟悉文件操作的,你可能會覺得Properties、XML或Bean比較方便,然后又發現了還有Preferences這么一個東東,大概又會感慨“天外有天”了,等到你接觸了很多種新方法以后,結果又會“殊途同歸”,重新反省Serialization機制本身。這不僅是Java,科學也是同樣的道理。
參考資料
Core Java 2. by Cay S. Horstmann, Gary Cornell
J2SE進階. by JavaResearch.org
Thinking in Java. by Bruce Eckel
J2SE 1.4.2 Documentation. by java.sun.com
Java Network Programming. by Elliotte R. Harold
Java分布式對象:RMI和CORBA. by IBM developerWorks
posted @
2005-11-17 10:37 安德爾斯 閱讀(300) |
評論 (0) |
編輯 收藏
1.document.write(""); 輸出語句
2.JS中的注釋為//
3.傳統的HTML文檔順序是:document->html->(head,body)
4.一個瀏覽器窗口中的DOM順序是:window->(navigator,screen,history,location,document)
5.得到表單中元素的名稱和值:document.getElementById("表單中元素的ID號").name(或value)
6.一個小寫轉大寫的JS: document.getElementById("output").value = document.getElementById("input").value.toUpperCase();
7.JS中的值類型:String,Number,Boolean,Null,Object,Function
8.JS中的字符型轉換成數值型:parseInt(),parseFloat()
9.JS中的數字轉換成字符型:(""+變量)
10.JS中的取字符串長度是:(length)
11.JS中的字符與字符相連接使用+號.
12.JS中的比較操作符有:==等于,!=不等于,>,>=,<.<=
13.JS中聲明變量使用:var來進行聲明
14.JS中的判斷語句結構:if(condition){}else{}
15.JS中的循環結構:for([initial expression];[condition];[upadte expression]) {inside loop}
16.循環中止的命令是:break
17.JS中的函數定義:function functionName([parameter],...){statement[s]}
18.當文件中出現多個form表單時.可以用document.forms[0],document.forms[1]來代替.
19.窗口:打開窗口window.open(), 關閉一個窗口:window.close(), 窗口本身:self
20.狀態欄的設置:window.status="字符";
21.彈出提示信息:window.alert("字符");
22.彈出確認框:window.confirm();
23.彈出輸入提示框:window.prompt();
24.指定當前顯示鏈接的位置:window.location.href="URL"
25.取出窗體中的所有表單的數量:document.forms.length
26.關閉文檔的輸出流:document.close();
27.字符串追加連接符:+=
28.創建一個文檔元素:document.createElement(),document.createTextNode()
29.得到元素的方法:document.getElementById()
30.設置表單中所有文本型的成員的值為空:
var form = window.document.forms[0]
for (var i = 0; i<form.elements.length;i++){
if (form.elements.type == "text"){
form.elements.value = "";
}
}
31.復選按鈕在JS中判斷是否選中:document.forms[0].checkThis.checked (checked屬性代表為是否選中返回TRUE或FALSE)
32.單選按鈕組(單選按鈕的名稱必須相同):取單選按鈕組的長度document.forms[0].groupName.length
33.單選按鈕組判斷是否被選中也是用checked.
34.下拉列表框的值:document.forms[0].selectName.options[n].value (n有時用下拉列表框名稱加上.selectedIndex來確定被選中的值)
35.字符串的定義:var myString = new String("This is lightsword");
36.字符串轉成大寫:string.toUpperCase(); 字符串轉成小寫:string.toLowerCase();
37.返回字符串2在字符串1中出現的位置:String1.indexOf("String2")!=-1則說明沒找到.
38.取字符串中指定位置的一個字符:StringA.charAt(9);
39.取出字符串中指定起點和終點的子字符串:stringA.substring(2,6);
40.數學函數:Math.PI(返回圓周率),Math.SQRT2(返回開方),Math.max(value1,value2)返回兩個數中的最在值,Math.pow(value1,10)返回value1的十次方,Math.round(value1)四舍五入函數,Math.floor(Math.random()*(n+1))返回隨機數
41.定義日期型變量:var today = new Date();
42.日期函數列表:dateObj.getTime()得到時間,dateObj.getYear()得到年份,dateObj.getFullYear()得到四位的年份,dateObj.getMonth()得到月份,dateObj.getDate()得到日,dateObj.getDay()得到日期幾,dateObj.getHours()得到小時,dateObj.getMinutes()得到分,dateObj.getSeconds()得到秒,dateObj.setTime(value)設置時間,dateObj.setYear(val)設置年,dateObj.setMonth(val)設置月,dateObj.setDate(val)設置日,dateObj.setDay(val)設置星期幾,dateObj.setHours設置小時,dateObj.setMinutes(val)設置分,dateObj.setSeconds(val)設置秒 [注意:此日期時間從0開始計]
43.FRAME的表示方式: [window.]frames[n].ObjFuncVarName,frames["frameName"].ObjFuncVarName,frameName.ObjFuncVarName
44.parent代表父親對象,top代表最頂端對象
45.打開子窗口的父窗口為:opener
46.表示當前所屬的位置:this
47.當在超鏈接中調用JS函數時用:(javascript :)來開頭后面加函數名
48.在老的瀏覽器中不執行此JS:<!-- //-->
49.引用一個文件式的JS:<script type="text/javascript" src="aaa.js"></script>
50.指定在不支持腳本的瀏覽器顯示的HTML:<noscript></noscript>
51.當超鏈和onCLICK事件都有時,則老版本的瀏覽器轉向a.html,否則轉向b.html.例:<a href="a.html" onclick="location.href='b.html';return false">dfsadf</a>
52.JS的內建對象有:Array,Boolean,Date,Error,EvalError,Function,Math,Number,Object,RangeError,ReferenceError,RegExp,String,SyntaxError,TypeError,URIError
53.JS中的換行:\n
54.窗口全屏大小:<script>function fullScreen(){ this.moveTo(0,0);this.outerWidth=screen.availWidth;this.outerHeight=screen.availHeight;}window.maximize=fullScreen;</script>
55.JS中的all代表其下層的全部元素
56.JS中的焦點順序:document.getElementByid("表單元素").tabIndex = 1
57.innerHTML的值是表單元素的值:如<p id="para">"how are <em>you</em>"</p>,則innerHTML的值就是:how are <em>you</em>
58.innerTEXT的值和上面的一樣,只不過不會把<em>這種標記顯示出來.
59.contentEditable可設置元素是否可被修改,isContentEditable返回是否可修改的狀態.
60.isDisabled判斷是否為禁止狀態.disabled設置禁止狀態
61.length取得長度,返回整型數值
62.addBehavior()是一種JS調用的外部函數文件其擴展名為.htc
63.window.focus()使當前的窗口在所有窗口之前.
64.blur()指失去焦點.與FOCUS()相反.
65.select()指元素為選中狀態.
66.防止用戶對文本框中輸入文本:onfocus="this.blur()"
67.取出該元素在頁面中出現的數量:document.all.tags("div(或其它HTML標記符)").length
68.JS中分為兩種窗體輸出:模態和非模態.window.showModaldialog(),window.showModeless()
69.狀態欄文字的設置:window.status='文字',默認的狀態欄文字設置:window.defaultStatus = '文字.';
70.添加到收藏夾:external.AddFavorite("http://www.dannyg.com";,"jaskdlf");
71.JS中遇到腳本錯誤時不做任何操作:window.onerror = doNothing; 指定錯誤句柄的語法為:window.onerror = handleError;
72.JS中指定當前打開窗口的父窗口:window.opener,支持opener.opener...的多重繼續.
73.JS中的self指的是當前的窗口
74.JS中狀態欄顯示內容:window.status="內容"
75.JS中的top指的是框架集中最頂層的框架
76.JS中關閉當前的窗口:window.close();
77.JS中提出是否確認的框:if(confirm("Are you sure?")){alert("ok");}else{alert("Not Ok");}
78.JS中的窗口重定向:window.navigate("http://www.sina.com.cn";);
79.JS中的打印:window.print()
80.JS中的提示輸入框:window.prompt("message","defaultReply");
81.JS中的窗口滾動條:window.scroll(x,y)
82.JS中的窗口滾動到位置:window.scrollby
83.JS中設置時間間隔:setInterval("expr",msecDelay)或setInterval(funcRef,msecDelay)或setTimeout
84.JS中的模態顯示在IE4+行,在NN中不行:showModalDialog("URL"[,arguments][,features]);
85.JS中的退出之前使用的句柄:function verifyClose(){event.returnValue="we really like you and hope you will stay longer.";}} window.onbeforeunload=verifyClose;
86.當窗體第一次調用時使用的文件句柄:onload()
87.當窗體關閉時調用的文件句柄:onunload()
88.window.location的屬性: protocol(http:),hostname(www.example.com),port(80),host(www.example.com:80),pathname("/a/a.html"),hash("#giantGizmo",指跳轉到相應的錨記),href(全部的信息)
89.window.location.reload()刷新當前頁面.
90.window.history.back()返回上一頁,window.history.forward()返回下一頁,window.history.go(返回第幾頁,也可以使用訪問過的URL)
91.document.write()不換行的輸出,document.writeln()換行輸出
92.document.body.noWrap=true;防止鏈接文字折行.
93.變量名.charAt(第幾位),取該變量的第幾位的字符.
94."abc".charCodeAt(第幾個),返回第幾個字符的ASCii碼值.
95.字符串連接:string.concat(string2),或用+=進行連接
96.變量.indexOf("字符",起始位置),返回第一個出現的位置(從0開始計算)
97.string.lastIndexOf(searchString[,startIndex])最后一次出現的位置.
98.string.match(regExpression),判斷字符是否匹配.
99.string.replace(regExpression,replaceString)替換現有字符串.
100.string.split(分隔符)返回一個數組存儲值.
101.string.substr(start[,length])取從第幾位到指定長度的字符串.
102.string.toLowerCase()使字符串全部變為小寫.
103.string.toUpperCase()使全部字符變為大寫.
104.parseInt(string[,radix(代表進制)])強制轉換成整型.
105.parseFloat(string[,radix])強制轉換成浮點型.
106.isNaN(變量):測試是否為數值型.
107.定義常量的關鍵字:const,定義變量的關鍵字:var
posted @
2005-11-17 09:54 安德爾斯 閱讀(471) |
評論 (0) |
編輯 收藏
Java Reflection (JAVA反射)
Reflection 是 Java 程序開發語言的特征之一,它允許運行中的 Java 程序對自身進行檢查,或者說“自審”,并能直接操作程序的內部屬性。例如,使用它能獲得 Java 類中各成員的名稱并顯示出來。
Java 的這一能力在實際應用中也許用得不是很多,但是在其它的程序設計語言中根本就不存在這一特性。例如,Pascal、C 或者 C++ 中就沒有辦法在程序中獲得函數定義相關的信息。
JavaBean 是 reflection 的實際應用之一,它能讓一些工具可視化的操作軟件組件。這些工具通過 reflection 動態的載入并取得 Java 組件(類) 的屬性。
1. 一個簡單的例子
考慮下面這個簡單的例子,讓我們看看 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[i].toString());
} catch (Throwable e) {
System.err.println(e);
}
}
}
按如下語句執行:
java DumpMethods java.util.Stack
它的結果輸出為:
public java.lang.Object java.util.Stack.push(java.lang.Object)
public synchronized java.lang.Object java.util.Stack.pop()
public synchronized java.lang.Object java.util.Stack.peek()
public boolean java.util.Stack.empty()
public synchronized int java.util.Stack.search(java.lang.Object)
這樣就列出了java.util.Stack 類的各方法名以及它們的限制符和返回類型。
這個程序使用 Class.forName 載入指定的類,然后調用 getDeclaredMethods 來獲取這個類中定義了的方法列表。java.lang.reflect.Methods 是用來描述某個類中單個方法的一個類。
2.開始使用 Reflection
用于 reflection 的類,如 Method,可以在 java.lang.relfect 包中找到。使用這些類的時候必須要遵循三個步驟:第一步是獲得你想操作的類的 java.lang.Class 對象。在運行中的 Java 程序中,用 java.lang.Class 類來描述類和接口等。
下面就是獲得一個 Class 對象的方法之一:
Class c = Class.forName("java.lang.String");
這條語句得到一個 String 類的類對象。還有另一種方法,如下面的語句:
Class c = int.class;
或者
Class c = Integer.TYPE;
它們可獲得基本類型的類信息。其中后一種方法中訪問的是基本類型的封裝類 (如 Integer) 中預先定義好的 TYPE 字段。
第二步是調用諸如 getDeclaredMethods 的方法,以取得該類中定義的所有方法的列表。
一旦取得這個信息,就可以進行第三步了??使用 reflection API 來操作這些信息,如下面這段代碼:
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[0].toString());
它將以文本方式打印出 String 中定義的第一個方法的原型。
在下面的例子中,這三個步驟將為使用 reflection 處理特殊應用程序提供例證。
模擬 instanceof 操作符
得到類信息之后,通常下一個步驟就是解決關于 Class 對象的一些基本的問題。例如,Class.isInstance 方法可以用于模擬 instanceof 操作符:
class A {
}
public class instance1 {
public static void main(String args[]) {
try {
Class cls = Class.forName("A");
boolean b1 = cls.isInstance(new Integer(37));
System.out.println(b1);
boolean b2 = cls.isInstance(new A());
System.out.println(b2);
} catch (Throwable e) {
System.err.println(e);
}
}
}
在這個例子中創建了一個 A 類的 Class 對象,然后檢查一些對象是否是 A 的實例。Integer(37) 不是,但 new A() 是。
3.找出類的方法
找出一個類中定義了些什么方法,這是一個非常有價值也非常基礎的 reflection 用法。下面的代碼就實現了這一用法:
import java.lang.reflect.*;
public class method1 {
private int f1(Object p, int x) throws NullPointerException {
if (p == null)
throw new NullPointerException();
return x;
}
public static void main(String args[]) {
try {
Class cls = Class.forName("method1");
Method methlist[] = cls.getDeclaredMethods();
for (int i = 0; i < methlist.length; i++) {
Method m = methlist[i];
System.out.println("name = " + m.getName());
System.out.println("decl class = " + m.getDeclaringClass());
Class pvec[] = m.getParameterTypes();
for (int j = 0; j < pvec.length; j++)
System.out.println("param #" + j + " " + pvec[j]);
Class evec[] = m.getExceptionTypes();
for (int j = 0; j < evec.length; j++)
System.out.println("exc #" + j + " " + evec[j]);
System.out.println("return type = " + m.getReturnType());
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
}
這個程序首先取得 method1 類的描述,然后調用 getDeclaredMethods 來獲取一系列的 Method 對象,它們分別描述了定義在類中的每一個方法,包括 public 方法、protected 方法、package 方法和 private 方法等。如果你在程序中使用 getMethods 來代替 getDeclaredMethods,你還能獲得繼承來的各個方法的信息。
取得了 Method 對象列表之后,要顯示這些方法的參數類型、異常類型和返回值類型等就不難了。這些類型是基本類型還是類類型,都可以由描述類的對象按順序給出。
輸出的結果如下:
name = f1
decl class = class method1
param #0 class java.lang.Object
param #1 int
exc #0 class java.lang.NullPointerException
return type = int
-----
name = main
decl class = class method1
param #0 class [Ljava.lang.String;
return type = void
-----
4.獲取構造器信息
獲取類構造器的用法與上述獲取方法的用法類似,如:
import java.lang.reflect.*;
public class constructor1 {
public constructor1() {
}
protected constructor1(int i, double d) {
}
public static void main(String args[]) {
try {
Class cls = Class.forName("constructor1");
Constructor ctorlist[] = cls.getDeclaredConstructors();
for (int i = 0; i < ctorlist.length; i++) {
Constructor ct = ctorlist[i];
System.out.println("name = " + ct.getName());
System.out.println("decl class = " + ct.getDeclaringClass());
Class pvec[] = ct.getParameterTypes();
for (int j = 0; j < pvec.length; j++)
System.out.println("param #" + j + " " + pvec[j]);
Class evec[] = ct.getExceptionTypes();
for (int j = 0; j < evec.length; j++)
System.out.println("exc #" + j + " " + evec[j]);
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
}
這個例子中沒能獲得返回類型的相關信息,那是因為構造器沒有返回類型。
這個程序運行的結果是:
name = constructor1
decl class = class constructor1
-----
name = constructor1
decl class = class constructor1
param #0 int
param #1 double
-----
5.獲取類的字段(域)
找出一個類中定義了哪些數據字段也是可能的,下面的代碼就在干這個事情:
import java.lang.reflect.*;
public class field1 {
private double d;
public static final int i = 37;
String s = "testing";
public static void main(String args[]) {
try {
Class cls = Class.forName("field1");
Field fieldlist[] = cls.getDeclaredFields();
for (int i = 0; i < fieldlist.length; i++) {
Field fld = fieldlist[i];
System.out.println("name = " + fld.getName());
System.out.println("decl class = " + fld.getDeclaringClass());
System.out.println("type = " + fld.getType());
int mod = fld.getModifiers();
System.out.println("modifiers = " + Modifier.toString(mod));
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
}
這個例子和前面那個例子非常相似。例中使用了一個新東西 Modifier,它也是一個 reflection 類,用來描述字段成員的修飾語,如“private int”。這些修飾語自身由整數描述,而且使用 Modifier.toString 來返回以“官方”順序排列的字符串描述 (如“static”在“final”之前)。這個程序的輸出是:
name = d
decl class = class field1
type = double
modifiers = private
-----
name = i
decl class = class field1
type = int
modifiers = public static final
-----
name = s
decl class = class field1
type = class java.lang.String
modifiers =
-----
和獲取方法的情況一下,獲取字段的時候也可以只取得在當前類中申明了的字段信息 (getDeclaredFields),或者也可以取得父類中定義的字段 (getFields) 。
6.根據方法的名稱來執行方法
文本到這里,所舉的例子無一例外都與如何獲取類的信息有關。我們也可以用 reflection 來做一些其它的事情,比如執行一個指定了名稱的方法。下面的示例演示了這一操作:
import java.lang.reflect.*;
public class method2 {
public int add(int a, int b) {
return a + b;
}
public static void main(String args[]) {
try {
Class cls = Class.forName("method2");
Class partypes[] = new Class[2];
partypes[0] = Integer.TYPE;
partypes[1] = Integer.TYPE;
Method meth = cls.getMethod("add", partypes);
method2 methobj = new method2();
Object arglist[] = new Object[2];
arglist[0] = new Integer(37);
arglist[1] = new Integer(47);
Object retobj = meth.invoke(methobj, arglist);
Integer retval = (Integer) retobj;
System.out.println(retval.intvalue());
} catch (Throwable e) {
System.err.println(e);
}
}
}
假如一個程序在執行的某處的時候才知道需要執行某個方法,這個方法的名稱是在程序的運行過程中指定的 (例如,JavaBean 開發環境中就會做這樣的事),那么上面的程序演示了如何做到。
上例中,getMethod 用于查找一個具有兩個整型參數且名為 add 的方法。找到該方法并創建了相應的 Method 對象之后,在正確的對象實例中執行它。執行該方法的時候,需要提供一個參數列表,這在上例中是分別包裝了整數 37 和 47 的兩個 Integer 對象。執行方法的返回的同樣是一個 Integer 對象,它封裝了返回值 84。
7.創建新的對象
對于構造器,則不能像執行方法那樣進行,因為執行一個構造器就意味著創建了一個新的對象 (準確的說,創建一個對象的過程包括分配內存和構造對象)。所以,與上例最相似的例子如下:
import java.lang.reflect.*;
public class constructor2 {
public constructor2() {
}
public constructor2(int a, int b) {
System.out.println("a = " + a + " b = " + b);
}
public static void main(String args[]) {
try {
Class cls = Class.forName("constructor2");
Class partypes[] = new Class[2];
partypes[0] = Integer.TYPE;
partypes[1] = Integer.TYPE;
Constructor ct = cls.getConstructor(partypes);
Object arglist[] = new Object[2];
arglist[0] = new Integer(37);
arglist[1] = new Integer(47);
Object retobj = ct.newInstance(arglist);
} catch (Throwable e) {
System.err.println(e);
}
}
}
根據指定的參數類型找到相應的構造函數并執行它,以創建一個新的對象實例。使用這種方法可以在程序運行時動態地創建對象,而不是在編譯的時候創建對象,這一點非常有價值。
8.改變字段(域)的值
reflection 的還有一個用處就是改變對象數據字段的值。reflection 可以從正在運行的程序中根據名稱找到對象的字段并改變它,下面的例子可以說明這一點:
import java.lang.reflect.*;
public class field2 {
public double d;
public static void main(String args[]) {
try {
Class cls = Class.forName("field2");
Field fld = cls.getField("d");
field2 f2obj = new field2();
System.out.println("d = " + f2obj.d);
fld.setDouble(f2obj, 12.34);
System.out.println("d = " + f2obj.d);
} catch (Throwable e) {
System.err.println(e);
}
}
}
這個例子中,字段 d 的值被變為了 12.34。
9.使用數組
本文介紹的 reflection 的最后一種用法是創建的操作數組。數組在 Java 語言中是一種特殊的類類型,一個數組的引用可以賦給 Object 引用。觀察下面的例子看看數組是怎么工作的:
import java.lang.reflect.*;
public class array1 {
public static void main(String args[]) {
try {
Class cls = Class.forName("java.lang.String");
Object arr = Array.newInstance(cls, 10);
Array.set(arr, 5, "this is a test");
String s = (String) Array.get(arr, 5);
System.out.println(s);
} catch (Throwable e) {
System.err.println(e);
}
}
}
例中創建了 10 個單位長度的 String 數組,為第 5 個位置的字符串賦了值,最后將這個字符串從數組中取得并打印了出來。
下面這段代碼提供了一個更復雜的例子:
import java.lang.reflect.*;
public class array2 {
public static void main(String args[]) {
int dims[] = new int[]{5, 10, 15};
Object arr = Array.newInstance(Integer.TYPE, dims);
Object arrobj = Array.get(arr, 3);
Class cls = arrobj.getClass().getComponentType();
System.out.println(cls);
arrobj = Array.get(arrobj, 5);
Array.setInt(arrobj, 10, 37);
int arrcast[][][] = (int[][][]) arr;
System.out.println(arrcast[3][5][10]);
}
}
例中創建了一個 5 x 10 x 15 的整型數組,并為處于 [3][5][10] 的元素賦了值為 37。注意,多維數組實際上就是數組的數組,例如,第一個 Array.get 之后,arrobj 是一個 10 x 15 的數組。進而取得其中的一個元素,即長度為 15 的數組,并使用 Array.setInt 為它的第 10 個元素賦值。
注意創建數組時的類型是動態的,在編譯時并不知道其類型。
posted @
2005-11-17 09:53 安德爾斯 閱讀(642) |
評論 (2) |
編輯 收藏
1.對象的復制
2.clone()的使用
3.對象實例的比較
////////////////////////////
1.對象的復制
-
- String str1 = "This is a string!" //這里是 "對象引用" 的復制
- String str2 = new String(str1); //這里是 "對象實例" 的復制
淺復制: 只復制復合對象本身.
深復制: 除了復制復合對象本身, 還復制了復合對象的引用的對象實例.
例如:
-
- class Pupil{
- public Pupil(String sno, String name, int age){
- this.sno = new String(sno);
- this.name = new String(name);
- this.age = age;
- }
-
- public String getSno() {
- return sno;
- }
-
- public String getName() {
- return name;
- }
-
- public int getAge() {
- return age;
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- private String sno;
- private String name;
- private int age;
- }
-
- public class CopyDemo {
- public static Pupil[] shallowCopy(Pupil[] aClass) {
- Pupil[] newClass = new Pupil[aClass.length];
-
- //此時newClass 與aClass 指向同一塊內存
- for(int i=0; i<aClass.length; i++)
- newClass[i] = aClass[i];
- return newClass;
- }
-
- public static Pupil[] deepCopy(Pupil[] aClass) {
- Pupil[] newClass = new Pupil[aClass.length];
-
- //此時newClass 與aClass 的相應sno , name 指向同一塊內存
- for(int i=0; i<aClass.length; i++) {
- String sno = aClass[i].getSno();
- String name = aClass[i].getName();
- int age = aClass[i].getAge();
- newClass[i] = new Pupil(sno, name, age);
- }
-
- return newClass;
- }
-
- public static Pupil[] deeperCopy(Pupil[] aClass) {
- Pupil[] newClass = new Pupil[aClass.length];
-
- //完全的復制
- for(int i=0; i<aClass.length; i++) {
- String sno = new String(aClass[i].getSno());
- String name = new String(aClass[i].getName());
- int age = aClass[i].getAge();
- newClass[i] = new Pupil(sno, name, age);
- }
-
- return newClass;
- }
- }
2.clone()的使用
* Object.clone()
* Cloneable 接口
* CloneNotSupportedException
a. 使用Object.clone 進行復制
兩個必須條件:
1.一定要將重定義后的clone() 方法定義為公有方法(在Object 類中, 它是受保護的成員, 不能直接使用)
2.該后代類聲明實現接口 Cloneable 接口(當類實現該接口, 其任何子類也會繼承該接口), 該接口實際上沒有任何
內容, 只是一個標識, 標志實現該接口的類提供clone() 方法.(這是接口的一種非典型用法)
b.重寫Object.clone()
例如對 private char[] cb; character buffer 進行復制
c.復制數組
數組是在方法調用重以引用的形式傳遞的對象. 下述情況下非常適合引用來傳遞數組:
*正在接收的方法不修改數組
*正在調用的方法不必關心是否修改數組
*正在調用的方法想要得到數組中的修改結果
否則, 就應該在方法調用中傳遞數組對象的副本. 只需調用 arrObj.clone() 方法即可完成數組arrObj 的復制操作. 隨后將該數組副本強制轉換為其正確類型:
(type[])arrObj.clone();
System.arraycopy 方法提供一種用于在數組間復制多個元素的有效方式.
System.arraycopy(source, i, target, j, len)
3.對象實例的比較
例如:
-
- Pupil p1 = new Pupil("99184001", "zhang3", 18);
- Pupil p2 = new Pupil("99184001", "zhang3", 18);
a. "=="
if(p1 == p2)...
此次測試的是對象引用, 其結果肯定是false, 只要兩個對象引用不是互為別名就不會相等.
b. 淺比較 false
-
- if(p1.getSno() == p2.getSno() && p1.getName() == p2.getName()
- && p1.getAge() == p2.getAge()) ...;
c. 深比較 true[/code]
if(p1.getSno().equals(p2.getSno()) && p1.getName().equals(p2.getName())
&& p1.getAge() == p2.getAge()) ...;[/code]
JAVA API 的跟類Object 也提供了equals() 方法, 但它只是比較兩個對象引用, 而非比較兩個對象實例.
不管怎樣, 如果需要比較Pupil 類的對象(例如要將它們放入對象容器), 應該為Pupil 類重定義equals() 方法:
-
- public boolean equals(Object otherobj) {
- //檢查otherobj 是否為空
- if(otherobj == null) return false;
-
- //檢查otherobj 是否就是當前對象
- if(otherobj == this) return true;
-
- //檢查otherobj 是否具有正確的類型, 即檢查是否可與當前對象比較
- if(!(otherobj instanceof Pupil)) return false;
-
- //將otherobj 轉換為Pupil 類的對象引用
- Pupil tmpObj = (Pupil)otherobj;
- //關于學生是否相等的邏輯檢查
- if(sno.equals(tmpObj.sno) && name.equals(tmpObj.name)
- && age == tmpObj.age) return true;
-
- return false;
- }
JAVA API 所提供的每個類幾乎都提供了采用深比較策略的equals() 方法, 例如String 類equals() 方法. 一般來說, 用戶自己定義的類也應當提供合適的equals() 方法, 特別是當程序要將其對象放入JAVA API 所提供的對象容器類的時候.
按照約定, 任何類所提供的equals() 方法所實現的相等比較應該是等價關系, 即滿足自反性, 對稱性和傳遞性. 另外一個類重定義了equals() 方法, 也應該重定義相應hashCode() 方法, 否則將這個類的對象放入映射對象容器時也會發生以外.
posted @
2005-11-17 09:47 安德爾斯 閱讀(962) |
評論 (1) |
編輯 收藏
??Java反射機制
侯捷觀點
Java反射機制
摘要
Reflection 是Java被視為動態(或準動態)語言的一個關鍵性質。這個機制允許程序在運行時
透過Reflection APIs取得任何一個已知名稱的class的內部信息,包括其modifiers(諸如
public, static 等等)、superclass(例如Object)、實現之interfaces(例如
Cloneable),也包括fields和methods的所有信息,并可于運行時改變fields內容或喚起
methods。本文借由實例,大面積示范Reflection APIs。
關于本文:
讀者基礎:具備Java 語言基礎。
本文適用工具:JDK1.5
關鍵詞:
Introspection(內省、內觀)
Reflection(反射)
有時候我們說某個語言具有很強的動態性,有時候我們會區分動態和靜態的不同技術與作法。我們
朗朗上口動態綁定(dynamic binding)、動態鏈接(dynamic linking)、動態加載
(dynamic loading)等。然而“動態”一詞其實沒有絕對而普遍適用的嚴格定義,有時候甚至
像對象導向當初被導入編程領域一樣,一人一把號,各吹各的調。
一般而言,開發者社群說到動態語言,大致認同的一個定義是:“程序運行時,允許改變程序結構
或變量類型,這種語言稱為動態語言”。從這個觀點看,Perl,Python,Ruby是動態語言,C+
+,Java,C#不是動態語言。
盡管在這樣的定義與分類下Java不是動態語言,它卻有著一個非常突出的動態相關機制:
Reflection。這個字的意思是“反射、映象、倒影”,用在Java身上指的是我們可以于運行時加
載、探知、使用編譯期間完全未知的classes。換句話說,Java程序可以加載一個運行時才得知名
稱的class,獲悉其完整構造(但不包括methods定義),并生成其對象實體、或對其fields設
值、或喚起其methods1。這種“看透class”的能力(the ability of the program to
examine itself)被稱為introspection(內省、內觀、反省)。Reflection和
introspection是常被并提的兩個術語。
Java如何能夠做出上述的動態特性呢?這是一個深遠話題,本文對此只簡單介紹一些概念。整個篇
幅最主要還是介紹Reflection APIs,也就是讓讀者知道如何探索class的結構、如何對某
個“運行時才獲知名稱的class”生成一份實體、為其fields設值、調用其methods。本文將談到
file:///H|/download/806.html(第 1/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等等
classes。
“Class”class
眾所周知Java有個Object class,是所有Java classes的繼承根源,其內聲明了數個應該在所
有Java class中被改寫的methods:hashCode()、equals()、clone()、toString()、
getClass()等。其中getClass()返回一個Class object。
Class class十分特殊。它和一般classes一樣繼承自Object,其實體用以表達Java程序運行時
的classes和interfaces,也用來表達enum、array、primitive Java types
(boolean, byte, char, short, int, long, float, double)以及關鍵詞void。當一
個class被加載,或當加載器(class loader)的defineClass()被JVM調用,JVM 便自動產
生一個Class object。如果您想借由“修改Java標準庫源碼”來觀察Class object的實際生成
時機(例如在Class的constructor內添加一個println()),不能夠!因為Class并沒有
public constructor(見圖1)。本文最后我會撥一小塊篇幅順帶談談Java標準庫源碼的改動辦
法。
Class是Reflection故事起源。針對任何您想探勘的class,唯有先為它產生一個Class
object,接下來才能經由后者喚起為數十多個的Reflection APIs。這些APIs將在稍后的探險
活動中一一亮相。
#001 public final
#002 class Class<T> implements java.io.Serializable,
#003 java.lang.reflect.GenericDeclaration,
#004 java.lang.reflect.Type,
#005 java.lang.reflect.AnnotatedElement {
#006 private Class() {}
#007 public String toString() {
#008 return ( isInterface() ? "interface " :
#009 (isPrimitive() ? "" : "class "))
#010 + getName();
#011 }
...
圖1:Class class片段。注意它的private empty ctor,意指不允許任何人經由編程方式產生Class object。是的,其object 只能由
JVM 產生。
“Class” object的取得途徑
Java允許我們從多種管道為一個class生成對應的Class object。圖2是一份整理。
Class object 誕生管道示例
運用getClass()
注:每個class 都有此函數
String str = "abc";
Class c1 = str.getClass();
file:///H|/download/806.html(第 2/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
運用
Class.getSuperclass()2
Button b = new Button();
Class c1 = b.getClass();
Class c2 = c1.getSuperclass();
運用static method
Class.forName()
(最常被使用)
Class c1 = Class.forName ("java.lang.
String");
Class c2 = Class.forName ("java.awt.Button");
Class c3 = Class.forName ("java.util.
LinkedList$Entry");
Class c4 = Class.forName ("I");
Class c5 = Class.forName ("[I");
運用
.class 語法
Class c1 = String.class;
Class c2 = java.awt.Button.class;
Class c3 = Main.InnerClass.class;
Class c4 = int.class;
Class c5 = int[].class;
運用
primitive wrapper
classes
的TYPE 語法
Class c1 = Boolean.TYPE;
Class c2 = Byte.TYPE;
Class c3 = Character.TYPE;
Class c4 = Short.TYPE;
Class c5 = Integer.TYPE;
Class c6 = Long.TYPE;
Class c7 = Float.TYPE;
Class c8 = Double.TYPE;
Class c9 = Void.TYPE;
圖2:Java 允許多種管道生成Class object。
Java classes 組成分析
首先容我以圖3的java.util.LinkedList為例,將Java class的定義大卸八塊,每一塊分別對
應圖4所示的Reflection API。圖5則是“獲得class各區塊信息”的程序示例及執行結果,它們
都取自本文示例程序的對應片段。
package java.util; //(1)
import java.lang.*; //(2)
public class LinkedList<E> //(3)(4)(5)
extends AbstractSequentialList<E> //(6)
implements List<E>, Queue<E>,
Cloneable, java.io.Serializable //(7)
{
private static class Entry<E> { … }//(8)
public LinkedList() { … } //(9)
public LinkedList(Collection<? extends E> c) { … }
public E getFirst() { … } //(10)
public E getLast() { … }
private transient Entry<E> header = …; //(11)
private transient int size = 0;
}
file:///H|/download/806.html(第 3/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
圖3:將一個Java class 大卸八塊,每塊相應于一個或一組Reflection APIs(圖4)。
Java classes 各成份所對應的Reflection APIs
圖3的各個Java class成份,分別對應于圖4的Reflection API,其中出現的Package、
Method、Constructor、Field等等classes,都定義于java.lang.reflect。
Java class 內
部模塊(參見圖
3)
Java class 內部模塊說明相應之Reflection
API,多半為Class
methods。
返回值類型
(return type)
(1) package class隸屬哪個package getPackage() Package
(2) import class導入哪些classes 無直接對應之API。
解決辦法見圖5-2。
(3) modifier class(或methods,
fields)的屬性
int getModifiers()
Modifier.toString
(int)
Modifier.
isInterface(int)
int
String
bool
(4) class
name or
interface
name
class/interface 名稱getName() String
(5) type
parameters
參數化類型的名稱getTypeParameters
()
TypeVariable
<Class>[]
(6) base
class
base class(只可能一個) getSuperClass() Class
(7)
implemented
interfaces
實現有哪些interfaces getInterfaces() Class[]
(8) inner
classes
內部classes getDeclaredClasses
()
Class[]
(8') outer
class
如果我們觀察的class 本身是
inner classes,那么相對它
就會有個outer class。
getDeclaringClass() Class
(9)
constructors
構造函數
getDeclaredConstructors
()
不論 public 或
private 或其它
access level,皆可獲
得。另有功能近似之取得
函數。
Constructor[]
file:///H|/download/806.html(第 4/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
(10) methods 操作函數
getDeclaredMethods()
不論 public 或
private 或其它
access level,皆可獲
得。另有功能近似之取得
函數。
Method[]
(11) fields 字段(成員變量) getDeclaredFields()
不論 public 或
private 或其它
access level,皆可獲
得。另有功能近似之取得
函數。
Field[]
圖4:Java class大卸八塊后(如圖3),每一塊所對應的Reflection API。本表并非
Reflection APIs 的全部。
Java Reflection API 運用示例
圖5示范圖4提過的每一個Reflection API,及其執行結果。程序中出現的tName()是個輔助函
數,可將其第一自變量所代表的“Java class完整路徑字符串”剝除路徑部分,留下class名
稱,儲存到第二自變量所代表的一個hashtable去并返回(如果第二自變量為null,就不儲存而只
是返回)。
#001 Class c = null;
#002 c = Class.forName(args[0]);
#003
#004 Package p;
#005 p = c.getPackage();
#006
#007 if (p != null)
#008 System.out.println("package "+p.getName()+";");
執行結果(例):
package java.util;
圖5-1:找出class 隸屬的package。其中的c將繼續沿用于以下各程序片段。
#001 ff = c.getDeclaredFields();
#002 for (int i = 0; i < ff.length; i++)
#003 x = tName(ff[i].getType().getName(), classRef);
#004
#005 cn = c.getDeclaredConstructors();
#006 for (int i = 0; i < cn.length; i++) {
#007 Class cx[] = cn[i].getParameterTypes();
#008 for (int j = 0; j < cx.length; j++)
#009 x = tName(cx[j].getName(), classRef);
#010 }
#011
#012 mm = c.getDeclaredMethods();
#013 for (int i = 0; i < mm.length; i++) {
#014 x = tName(mm[i].getReturnType().getName(), classRef);
#015 Class cx[] = mm[i].getParameterTypes();
file:///H|/download/806.html(第 5/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
#016 for (int j = 0; j < cx.length; j++)
#017 x = tName(cx[j].getName(), classRef);
#018 }
#019 classRef.remove(c.getName()); //不必記錄自己(不需import 自己)
執行結果(例):
import java.util.ListIterator;
import java.lang.Object;
import java.util.LinkedList$Entry;
import java.util.Collection;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
圖5-2:找出導入的classes,動作細節詳見內文說明。
#001 int mod = c.getModifiers();
#002 System.out.print(Modifier.toString(mod)); //整個modifier
#003
#004 if (Modifier.isInterface(mod))
#005 System.out.print(" "); //關鍵詞 "interface" 已含于modifier
#006 else
#007 System.out.print(" class "); //關鍵詞 "class"
#008 System.out.print(tName(c.getName(), null)); //class 名稱
執行結果(例):
public class LinkedList
圖5-3:找出class或interface 的名稱,及其屬性(modifiers)。
#001 TypeVariable<Class>[] tv;
#002 tv = c.getTypeParameters(); //warning: unchecked conversion
#003 for (int i = 0; i < tv.length; i++) {
#004 x = tName(tv[i].getName(), null); //例如 E,K,V...
#005 if (i == 0) //第一個
#006 System.out.print("<" + x);
#007 else //非第一個
#008 System.out.print("," + x);
#009 if (i == tv.length-1) //最后一個
#010 System.out.println(">");
#011 }
執行結果(例):
public abstract interface Map<K,V>
或 public class LinkedList<E>
圖5-4:找出parameterized types 的名稱
#001 Class supClass;
#002 supClass = c.getSuperclass();
#003 if (supClass != null) //如果有super class
#004 System.out.print(" extends" +
#005 tName(supClass.getName(),classRef));
執行結果(例):
public class LinkedList<E>
extends AbstractSequentialList,
file:///H|/download/806.html(第 6/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
圖5-5:找出base class。執行結果多出一個不該有的逗號于尾端。此非本處重點,為簡化計,不多做處理。
#001 Class cc[];
#002 Class ctmp;
#003 //找出所有被實現的interfaces
#004 cc = c.getInterfaces();
#005 if (cc.length != 0)
#006 System.out.print(", \r\n" + " implements "); //關鍵詞
#007 for (Class cite : cc) //JDK1.5 新式循環寫法
#008 System.out.print(tName(cite.getName(), null)+", ");
執行結果(例):
public class LinkedList<E>
extends AbstractSequentialList,
implements List, Queue, Cloneable, Serializable,
圖5-6:找出implemented interfaces。執行結果多出一個不該有的逗號于尾端。此非本處重點,為簡化計,不多做處
理。
#001 cc = c.getDeclaredClasses(); //找出inner classes
#002 for (Class cite : cc)
#003 System.out.println(tName(cite.getName(), null));
#004
#005 ctmp = c.getDeclaringClass(); //找出outer classes
#006 if (ctmp != null)
#007 System.out.println(ctmp.getName());
執行結果(例):
LinkedList$Entry
LinkedList$ListItr
圖5-7:找出inner classes 和outer class
#001 Constructor cn[];
#002 cn = c.getDeclaredConstructors();
#003 for (int i = 0; i < cn.length; i++) {
#004 int md = cn[i].getModifiers();
#005 System.out.print(" " + Modifier.toString(md) + " " +
#006 cn[i].getName());
#007 Class cx[] = cn[i].getParameterTypes();
#008 System.out.print("(");
#009 for (int j = 0; j < cx.length; j++) {
#010 System.out.print(tName(cx[j].getName(), null));
#011 if (j < (cx.length - 1)) System.out.print(", ");
#012 }
#013 System.out.print(")");
#014 }
執行結果(例):
public java.util.LinkedList(Collection)
public java.util.LinkedList()
圖5-8a:找出所有constructors
#004 System.out.println(cn[i].toGenericString());
執行結果(例):
file:///H|/download/806.html(第 7/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
public java.util.LinkedList(java.util.Collection<? extends E>)
public java.util.LinkedList()
圖5-8b:找出所有constructors。本例在for 循環內使用toGenericString(),省事。
#001 Method mm[];
#002 mm = c.getDeclaredMethods();
#003 for (int i = 0; i < mm.length; i++) {
#004 int md = mm[i].getModifiers();
#005 System.out.print(" "+Modifier.toString(md)+" "+
#006 tName(mm[i].getReturnType().getName(), null)+" "+
#007 mm[i].getName());
#008 Class cx[] = mm[i].getParameterTypes();
#009 System.out.print("(");
#010 for (int j = 0; j < cx.length; j++) {
#011 System.out.print(tName(cx[j].getName(), null));
#012 if (j < (cx.length - 1)) System.out.print(", ");
#013 }
#014 System.out.print(")");
#015 }
執行結果(例):
public Object get(int)
public int size()
圖5-9a:找出所有methods
#004 System.out.println(mm[i].toGenericString());
public E java.util.LinkedList.get(int)
public int java.util.LinkedList.size()
圖5-9b:找出所有methods。本例在for 循環內使用toGenericString(),省事。
#001 Field ff[];
#002 ff = c.getDeclaredFields();
#003 for (int i = 0; i < ff.length; i++) {
#004 int md = ff[i].getModifiers();
#005 System.out.println(" "+Modifier.toString(md)+" "+
#006 tName(ff[i].getType().getName(), null) +" "+
#007 ff[i].getName()+";");
#008 }
執行結果(例):
private transient LinkedList$Entry header;
private transient int size;
圖5-10a:找出所有fields
#004 System.out.println("G: " + ff[i].toGenericString());
private transient java.util.LinkedList.java.util.LinkedList$Entry<E> ??
java.util.LinkedList.header
private transient int java.util.LinkedList.size
圖5-10b:找出所有fields。本例在for 循環內使用toGenericString(),省事。
找出class參用(導入)的所有classes
file:///H|/download/806.html(第 8/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
沒有直接可用的Reflection API可以為我們找出某個class參用的所有其它classes。要獲得這
項信息,必須做苦工,一步一腳印逐一記錄。我們必須觀察所有fields的類型、所有methods(包
括constructors)的參數類型和回返類型,剔除重復,留下唯一。這正是為什么圖5-2程序代碼
要為tName()指定一個hashtable(而非一個null)做為第二自變量的緣故:hashtable可為我
們儲存元素(本例為字符串),又保證不重復。
本文討論至此,幾乎可以還原一個class的原貌(唯有methods 和ctors的定義無法取得)。接下
來討論Reflection 的另三個動態性質:(1) 運行時生成instances,(2) 執
行期喚起methods,(3) 運行時改動fields。
運行時生成instances
欲生成對象實體,在Reflection 動態機制中有兩種作法,一個針對“無自變量ctor”,
一個針對“帶參數ctor”。圖6是面對“無自變量ctor”的例子。如果欲調用的是“帶參數
ctor“就比較麻煩些,圖7是個例子,其中不再調用Class的newInstance(),而是調用
Constructor 的newInstance()。圖7首先準備一個Class[]做為ctor的參數類型(本例指定
為一個double和一個int),然后以此為自變量調用getConstructor(),獲得一個專屬ctor。
接下來再準備一個Object[] 做為ctor實參值(本例指定3.14159和125),調用上述專屬ctor
的newInstance()。
#001 Class c = Class.forName("DynTest");
#002 Object obj = null;
#003 obj = c.newInstance(); //不帶自變量
#004 System.out.println(obj);
圖6:動態生成“Class object 所對應之class”的對象實體;無自變量。
#001 Class c = Class.forName("DynTest");
#002 Class[] pTypes = new Class[] { double.class, int.class };
#003 Constructor ctor = c.getConstructor(pTypes);
#004 //指定parameter list,便可獲得特定之ctor
#005
#006 Object obj = null;
#007 Object[] arg = new Object[] {3.14159, 125}; //自變量
#008 obj = ctor.newInstance(arg);
#009 System.out.println(obj);
圖7:動態生成“Class object 對應之class”的對象實體;自變量以Object[]表示。
運行時調用methods
這個動作和上述調用“帶參數之ctor”相當類似。首先準備一個Class[]做為ctor的參數類型
(本例指定其中一個是String,另一個是Hashtable),然后以此為自變量調用getMethod(),
獲得特定的Method object。接下來準備一個Object[]放置自變量,然后調用上述所得之特定
Method object的invoke(),如圖8。知道為什么索取Method object時不需指定回返類型
嗎?因為method overloading機制要求signature(署名式)必須唯一,而回返類型并非
signature的一個成份。換句話說,只要指定了method名稱和參數列,就一定指出了一個獨一無
二的method。
file:///H|/download/806.html(第 9/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
#001 public String func(String s, Hashtable ht)
#002 {
#003 …System.out.println("func invoked"); return s;
#004 }
#005 public static void main(String args[])
#006 {
#007 Class c = Class.forName("Test");
#008 Class ptypes[] = new Class[2];
#009 ptypes[0] = Class.forName("java.lang.String");
#010 ptypes[1] = Class.forName("java.util.Hashtable");
#011 Method m = c.getMethod("func",ptypes);
#012 Test obj = new Test();
#013 Object args[] = new Object[2];
#014 arg[0] = new String("Hello,world");
#015 arg[1] = null;
#016 Object r = m.invoke(obj, arg);
#017 Integer rval = (String)r;
#018 System.out.println(rval);
#019 }
圖8:動態喚起method
運行時變更fields內容
與先前兩個動作相比,“變更field內容”輕松多了,因為它不需要參數和自變量。首先調用
Class的getField()并指定field名稱。獲得特定的Field object之后便可直接調用Field的
get()和set(),如圖9。
#001 public class Test {
#002 public double d;
#003
#004 public static void main(String args[])
#005 {
#006 Class c = Class.forName("Test");
#007 Field f = c.getField("d"); //指定field 名稱
#008 Test obj = new Test();
#009 System.out.println("d= " + (Double)f.get(obj));
#010 f.set(obj, 12.34);
#011 System.out.println("d= " + obj.d);
#012 }
#013 }
圖9:動態變更field 內容
Java 源碼改動辦法
先前我曾提到,原本想借由“改動Java標準庫源碼”來測知Class object的生成,但由于其
ctor原始設計為private,也就是說不可能透過這個管道生成Class object(而是由class
loader負責生成),因此“在ctor中打印出某種信息”的企圖也就失去了意義。
這里我要談點題外話:如何修改Java標準庫源碼并讓它反應到我們的應用程序來。假設我想修改
java.lang.Class,讓它在某些情況下打印某種信息。首先必須找出標準源碼!當你下載JDK 套
file:///H|/download/806.html(第 10/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
件并安裝妥當,你會發現jdk150\src\java\lang 目錄(見圖10)之中有Class.java,這就是
我們此次行動的標準源碼。備份后加以修改,編譯獲得Class.class。接下來準備將.class 搬移
到jdk150\jre\lib\endorsed(見圖10)。
這是一個十分特別的目錄,class loader將優先從該處讀取內含classes的.jar文件??成功的
條件是.jar內的classes壓縮路徑必須和Java標準庫的路徑完全相同。為此,我們可以將剛才做
出的Class.class先搬到一個為此目的而刻意做出來的\java\lang目錄中,壓縮為foo.zip(任
意命名,唯需夾帶路徑java\lang),再將這個foo.zip搬到jdk150\jre\lib\endorsed并改
名為foo.jar。此后你的應用程序便會優先用上這里的java.lang.Class。整個過程可寫成一個
批處理文件(batch file),如圖11,在DOS Box中使用。
圖10
圖10:JDK1.5 安裝后的目錄組織。其中的endorsed 是我新建。
del e:\java\lang\*.class //清理干凈
del c:\jdk150\jre\lib\endorsed\foo.jar //清理干凈
c:
cd c:\jdk150\src\java\lang
javac -Xlint:unchecked Class.java //編譯源碼
javac -Xlint:unchecked ClassLoader.java //編譯另一個源碼(如有必要)
move *.class e:\java\lang //搬移至刻意制造的目錄中
e:
cd e:\java\lang //以下壓縮至適當目錄
pkzipc -add -path=root c:\jdk150\jre\lib\endorsed\foo.jar *.class
cd e:\test //進入測試目錄
javac -Xlint:unchecked Test.java //編譯測試程序
java Test //執行測試程序
圖11:一個可在DOS Box中使用的批處理文件(batch file),用以自動化java.lang.Class
的修改動作。Pkzipc(.exe)是個命令列壓縮工具,add和path都是其命令。
更多信息
以下是視野所及與本文主題相關的更多討論。這些信息可以彌補因文章篇幅限制而帶來的不足,或
帶給您更多視野。
l "Take an in-depth look at the Java Reflection API -- Learn about
the new Java 1.1 tools forfinding out information about classes", by
Chuck McManis。此篇文章所附程序代碼是本文示例程序的主要依據(本文示例程序示范了更
多Reflection APIs,并采用JDK1.5 新式的for-loop 寫法)。
l "Take a look inside Java classes -- Learn to deduce properties of
a Java class from inside aJava program", by Chuck McManis。
l "The basics of Java class loaders -- The fundamentals of this key
component of the Javaarchitecture", by Chuck McManis。
l 《The Java Tutorial Continued》, Sun microsystems. Lesson58-61,
"Reflection".
file:///H|/download/806.html(第 11/12 頁)2005-9-8 12:03:22
侯捷觀點??Java反射機制
注1用過諸如MFC這類所謂 Application Framework的程序員也許知道,MFC有所謂的
dynamic creation。但它并不等同于Java的動態加載或動態辨識;所有能夠在MFC程序中起作用
的classes,都必須先在編譯期被編譯器“看見”。
注2如果操作對象是Object,Class.getSuperClass()會返回null。
本文程序源碼可至侯捷網站下載:
http://www.jjhou.com/javatwo-2004-reflection-and-generics-in-jdk15-sample.ZIP發表于 2004年10月27日 11:30 AM
posted @
2005-11-17 09:45 安德爾斯 閱讀(785) |
評論 (0) |
編輯 收藏
傳值?還是傳引用?
(Wang hailong)
關于編程的參數傳遞問題,總是存在著這樣的爭論。傳值?還是傳引用?(還是傳指針?還是傳地址?)這些提法,經常出現在C++, java, C#的編程技術文檔里面。這個問題也經常引起開發人員的爭論,徒耗人力物力。實際上,這根本不成為問題,只是由于人為加入的概念,混淆了人們的視聽。
從程序運行的角度來看,參數傳遞,只有傳值,從不傳遞其它的東西。只不過,值的內容有可能是數據,也有可能是一個內存地址。
開發人員應該了解程序的編譯結果是怎樣在計算機中運行的。程序運行的時候,使用的空間可以分為兩個部分,棧和堆。棧是指運行棧,局部變量,參數,都分配在棧上。程序運行的時候,新生成的對象,都分配在堆里,堆里分配的對象,棧里的數據參數,或局部變量。
下面舉一個C++的例子。
public class Object{
int i;
public Object(int i){
this.i = i;
}
public int getValue(){
return i;
}
public void setValue(int i){
this.i = i;
}
};
class A {
Void func1(int a, Object b){
Object * c = new Object( a );
b = c;
}
public void main(){
Object * param = new Object( 1 );
func1( 2, param );
// what is value of parram now ?
// it is still 1.
}
};
我們來看一下,當調用到func1函數時,運行到Object * c = new Object( a ); 棧和堆的狀態。不同編譯器生成的代碼運行的結果可能會稍有不同。但參數和局部變量的大致排放順序都是相同的。
這時候,我們來看,param變量被壓入運行棧的時候,只是進行了簡單的復制。把param里面的內容拷貝到b里面。這時候,b就指向了Object(1)。這里的參數傳遞,是把param的值傳遞給b。
下面我們來看,程序執行到b = c;時候的堆棧狀態。
我們可以看到,b現在指向了Object(2)。但是對param的值毫無影響。param的值還是Object(1)。
所以,我們說,對參數的賦值不會影響到外層函數的數據,但是,調用參數的操作方法,卻等于直接操作外層函數的數據。比如,如果我們在func1()函數中,不調用b=c;而調用b.setValue(3),那么Object(1)的數據就會變為3,param的數據也會改變為3。
在java和C#中的情況,也都是一樣。
C++還有一種變量定義方法,表面上看起來,不符合上面的說明,這里進行說明。
Object * a = new Object(1);
Object & * b = a;
這里的b就等于是a的另外一個別名,b就是a。對b賦值就等于對a賦值。甚至作為參數傳遞時,也是如此。對這種類型的參數的賦值,就等于對外層函數數據的賦值。
public class B{
void func1(Object & * b){
b = new Object(4);
}
public void main(){
Object * a = new Object(1);
func1(a);
// a is changed to Object(4) now.
}
}
當運行完func1(a);時,a的值變化為Object(4)。這是因為編譯器實際把參數Object & * b編譯為Object ** b_addr,b_addr的值是b的地址,也就是a的地址。
當調用func1()的時候,實際上是把b_addr作為參數壓到棧里,b_addr的值是a的地址。
當執行b = new Object(4); 時,實際執行了 b_addr->b = new Object(4); 也就是執行了 b_addr->a = new Object(4); a的值當然變化了。
還有一點需要說明,當使用COM,CORBA等中間件規范進行開發時,我們需要定義IDL語言。參數的類型分為,[in],[out],[in, out],其中的RPC遠程調用的參數打包規范,就更復雜了,但原理卻是一樣的。
posted @
2005-11-17 09:43 安德爾斯 閱讀(388) |
評論 (0) |
編輯 收藏
/*
* Created on 2004-8-4
*
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
package myclass.test;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
/**
* @author
*
* To change the template for this generated type comment go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
public class Image {
public String sRand="";
public Color getRandColor(int fc,int bc){//給定范圍獲得隨機顏色
Random random = new Random();
if(fc>255) fc=255;
if(bc>255) bc=255;
int r=fc+random.nextInt(bc-fc);
int g=fc+random.nextInt(bc-fc);
int b=fc+random.nextInt(bc-fc);
return new Color(r,g,b);
}
public BufferedImage creatImage(){
// 在內存中創建圖象
int width=60, height=20;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 獲取圖形上下文
Graphics g = image.getGraphics();
//生成隨機類
Random random = new Random();
// 設定背景色
g.setColor(getRandColor(200,250));
g.fillRect(0, 0, width, height);
//設定字體
g.setFont(new Font("Times New Roman",Font.PLAIN,18));
//畫邊框
//g.setColor(new Color());
//g.drawRect(0,0,width-1,height-1);
// 隨機產生155條干擾線,使圖象中的認證碼不易被其它程序探測到
g.setColor(getRandColor(160,200));
for (int i=0;i<155;i++)
{
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x,y,x+xl,y+yl);
}
// 取隨機產生的認證碼(4位數字)
//String rand = request.getParameter("rand");
//rand = rand.substring(0,rand.indexOf("."));
for (int i=0;i<4;i++){
String rand=String.valueOf(random.nextInt(10));
sRand+=rand;
// 將認證碼顯示到圖象中
g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));//調用函數出來的顏色相同,可能是因為種子太接近,所以只能直接生成
g.drawString(rand,13*i+6,16);
}
// 圖象生效
g.dispose();
return image;
}
}
======================================================================
image.jsp(對bean的引用)
<%@ page contentType="image/jpeg" import="javax.imageio.*" %>
<jsp:useBean id="image" scope="session" class="myclass.test.Image"/>
<%
//設置頁面不緩存
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
// 將認證碼存入SESSION
session.setAttribute("rand",image.sRand);
// 輸出圖象到頁面
ImageIO.write(image.creatImage(), "JPEG", response.getOutputStream());
%>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
大家經常在網上登陸的時候經常會看到讓你輸入驗證碼,有的是文字的,有的呢是圖片,比如chinaren.com校友錄中留言的時候,我們就會看到數字圖片驗證碼;網上關于數字文字驗證碼實現方法的相關資料很多,而我們這里介紹的是數字和字母隨機組成的并且生成圖片的驗證碼的實現方法。看起來很復雜、其實很簡單的,大家跟著我往下看:
首先,我們先介紹一下設計思路,數字和字母的隨機組合生成驗證碼,然后將驗證碼生成圖片,這里“數字和字母的組合”應該是隨機取出來的;如果是專門的數字驗證碼,我們可以這樣實現:
ycodenum=4 '驗證碼的位數,或者說成個數
for i=1 to ycodenum
Randomize '初始化隨機數發生器
ycode=ycode&Int((9*Rnd)) 'rnd是隨機數,從0到1之間的任意實數,這里獲得0到9之間的整數
next
response.write ycode '就可以輸出數字驗證碼(4位)
然而,我們要讓數字和字母同樣隨機生成,這里我們可以用到數組來實現這種效果,如下:
ychar="0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z" '將數字和大寫字母組成一個字符串
yc=split(char,",") '將字符串生成數組
ycodenum=4
for i=1 to ycodenum
Randomize
ycode=ycode&yc(Int((35*Rnd))) '數組一般從0開始讀取,所以這里為35*Rnd
next
response.write ycode
現在看看輸出結果是不是數字和字母隨機組合的呢?
下面看看怎樣生成圖片,這個也許有些朋友知道:asp不能生成圖片,必須使用asp組件。不錯,我們這里使用的是ASP圖象組件shotgraph。有一點大家注意,服務器不是自己的不能用哦,因為你裝不了這組件。
組件的下載地址:
Response.BinaryWrite (img)
針對以上代碼也就是說shotgraph普通的畫圖的原理請參考:http://www.pconline.com.cn/pcedu/empolder/wz/asp/10204/45207.html
posted @
2005-11-17 09:40 安德爾斯 閱讀(1755) |
評論 (3) |
編輯 收藏