<p> 抽空把java基礎部分內容通過自己的理解整理如下,心想著等我技藝升級后在回過頭來可能會有不一樣的收獲呢 ,這也是極好的。 同時也想請大家慷慨指出下面錯誤、遺漏、或不準確的地方,謝謝大家!</br>
如果大家對某一塊有不一樣的理解,也請一并留言!</br>
<b>再次感謝!</b>
</p>
java基礎筆記
1. java 關鍵字(也稱為保留字)
這里只是羅列出我們工作可能用到的.
public | class | new | import | package | static | final |
---|---|---|---|---|---|---|
synchronized | private | default | protected | return | break | continue |
for | while | do | goto[1] | enum | interface | extends |
abstract | implements | int | long | double | float | short |
byte | char | boolean | super | if | else | case |
switch | finally | instanceof | this | throws | transient | try |
catch | void | const | throw | - | - | - |
2. java 的CLASSPATH
java 的classpath 用來設置JVM加載類(class) 的路徑,一般設置值為.
表示從當前路徑開始加載。JVM默認為從當前路徑開始加載.但是我們依然需要設置這個值,因為,如果此路徑被指定為其他路徑則當前路徑下class 就不會被加載了。 比如我們設置CLASSPATH='/home/test/javatest/'
, 則我們將javatest下生成的class文件移動到home
目錄下,執行java
命令會提示找不到xxx class
。
3.java 的path
path
在系統里用來幫助系統找到應用程序可執行文件.這里我們是為了方便找到javac
和java
命令. 包含jdk/bin/和jre/bin/ 目錄下的命令
4. 關于類文件名和public 修飾的類名一致
我們要求類文件名要和public
修飾的類名一致,這是為什么呢?我們可以嘗試如下操作:
我們嘗試將文件名和public
修飾的類名不一致,編譯后我們觀察class
文件.我們會發現class
文件名稱是我們public
修飾的名子,不會編譯成為這個類所在的文件的名.所以會有找不到編寫類的可能. 同時也是為了方便找到對應文件所在的類。
5.標識符
標識符是我們平常編寫代碼經常使用到的.我們給類、變量等去名子的時候需要遵守java標識符規范。
標識符規范我們:只能用數字、字母、下劃線(_) 、$組成,且不能以數字開頭或使用java關鍵字.
6.數據類型
java 數據類型大致分為以下兩大類型:
-
基本數據類型:
- 整型數據類型:byte、int、short、long -> 默認值 : 0
- 浮點數據類型(實型): float、double -> 默認值:0.0
- 字符型: char -> 默認值:‘\u0000’
- 布爾型: boolean -> 默認值: false
引用類型:
數組、類、接口 -> 默認值: null
這里有一個需要記住的是
char
的取值范圍是-128 ~ 127
。基本數據類型中,數據最大值和最小值是相鄰
的,比如說: int 數據最大值加一則變為最小值. 同理,最小值減一則變為最大值。這個特性很重要 。
說明
Byte、Short、Integer、Long、Character、Boolean 這五種包裝類默認創建了數值[-128 ~ 127] 對應類型的緩存數據,但是超出范圍后仍然會創建新的對象。Float 、Double 并沒有實現常量池技術。
比如:
Integer i1 = 40; //在編譯的時候會直接放入常量池中
Integer i2 = new Intege r(40);//會創建新的對象
Integer i5 = new Integer(0);
System.out.println("i1 == i2 "+ (i1 == i2)); //false
System.out.println("i1 == i2+i5 "+ (i1 == i2+i5)); //true
System.out.println("i1 == i2 "+ (40 == i2)); //true
Integer i3 = 129;
Integer i4 = 129;//new Integer(129);
System.out.println("i3== i4 "+ (i3 == i4));// false
但是有一個特性,普通操作符不使用于包裝類,所以包裝類做普通運算的時候會自動拆箱進行運算。例如 Integer 和int 比較 或者Integer 和int 做 + - * /運算都會自動拆箱后運算。所以結果會跟我想的不一樣,我們需要注意.
7. 運算符
- 邏輯運算符
& 、&& 、 | 、|| 解釋:
&
: 邏輯運算的時候會判斷所有表達式的值,其結果是,如果有一個為false 則結果為false。
|
: 邏輯運算的時候會判斷所有表達式的值,其結果是,如果有一個為true 則結果為true。
&&
: 稱為短路與,其運算規則為:如果遇到一個false 值,則不再計算之后的表達式的值,返回其結果為false。
短路與的意義可以用一下程序片段解釋:
//
int n =12;
if (n < 0 && n/0> 2){
System.out.println("滿足條件.");
}
顯然這段程序不會出錯,因為地一個表達式值為false,則不會再判斷之后的表達式。也就不會出錯。
||
同理.
||
: 也稱為短路或,其運算規則為: 如果遇到一個true值,則不再計算之后的表達式的值,返回其結果為true。
- 位運算
&、| 解釋:
& 在位運算中,規則為:其中一個為0
則結果就為0
, 兩個都為1
結果才為1
。
| 在位運算中, 規則為:其中一個為1
則結果就為1
,兩個都為0
結果才為0
。
ex:
**& **
10010
&
10011
-------------
10010
|
10010
|
10011
-----------
10011
- 三目運算符
三目運算格式為: 類型 變量 = 邏輯表達式 ? 值1 : 值2;
解釋:
當邏輯表達式的值為true
時 則將值1賦值給變量。否則將值2賦值給變量。
此多運用于簡單的if ...else .....
判斷。簡化代碼。 但是不可以濫用。
8. 程序結構
- 順序結構
按照代碼編寫順序執行。
- 分支結構
可以直接理解為
if
判斷 或者多個if
或者if ... else ....
或者if ...else if ... else ... .....
等等.
這里需要重視的是分支結構里當有重疊條件匹配時,只會進入首次匹配條件,不會再進入第二次匹配上的條件。
- 循環結構
使用
while
、do ...while
、for
來控制代碼執行順序的代碼結構。
這里我們可以使用
break
、continue
加標簽的形式控制多層循環。代碼實例如下:
outer:
for(int i=0;i<10;i++){
inner:
for(int y=0;y<8;y++){
for(int n=0;n<5;n++){
if(n ==3 ){
continue outer;
}else if(n==4){
break inner;
}
}
}
}
以上代碼順序執行解釋如: 當n 為3時 則從最外層的下一次循環開始執行。則i值為1的這次循環。
當n 為4時,則跳出y變量所在for循環進入i 變量所在for循環的下一次循環。
- 多分支結構
switch 語句用于多分支結構,使用基本類型值匹配條件,執行語句,避免編寫復雜的if...else ....
使代碼更易維護和查看。
格式為:
switch(值){
case v1: [代碼塊;]
[break];
case v2: [代碼塊;]
[break];
......
default:代碼塊;
}
以上 []
里面的內容代表可選。我們可以不用做任何操作,語法上是允許的。
- 假如我們沒有在沒一個匹配上的
case
之后加上break
組織程序繼續執行,則程序會在首個匹配上的case
之后繼續執行之后的所有case
. 導致得到你預想之外的結果。 - 假如程序沒有匹配上任何一個
case
如果有編寫default
語句,則自動執行default
代碼塊,如果沒有則走完所有case
后退出switch
。
關于for'
和while
使用上的選擇: 一般,我們在知道循環次數的時候首先選擇 ‘for' 控制循環,在不知道循環次數,但是知道循環結束條件的時候考慮使用while
類循環,
9. 方法
- 方法的重載
平時我們可能需要某一類的功能,區別只是參數類型或者個數有所區別,那么我們可以考慮方法的重載,不用編寫多個方法。例如: 我們需求如下:
- 需要兩個int 型數據相加的方法;
- 需要三個int 型數據相加的方法;
- 需要兩個double型數據相加的方法;
以上需求我們實現最原始的方式是給三個方法取三個不同的方法名以適應以上三個需求。假如我們考慮重載(Overload) 則只需要同一個方法名,不同參數就可以了,如下:
public int add(int x,int y){
return x + y;
}
public int add(int x,int y,int z){
return x + y + z;
}
public double add(double x,double y){
return x + y;
}
以上為方法重載,在我們調用的時候直接調用
add(..)
即可,不用如此調用add1(...)
/add2(..)
以上使用了不同的返回值類型,我們一般在編寫重載方法的時候不建議編寫返回值類型不同的重載方法。雖然jdk沒有如此強調。重載方法統一返回類型,有利用使用。
10. String 一些事兒
String 不是基本數據類型。String 是一個特殊的類。首先我們實例化對象有以下兩種方式:
- 直接賦值實例化
String str = "字符串"; - 使用
new
實例化Stirng對象
String str = new ("字符串"); 這種實例化方式在內存分配上有如下問題:
首先,在堆內存中會分配一塊內存存放匿名字符串對象 --- "字符串"。然后,關鍵字new
再次堆內存再一次分配內存給"字符串"; 然后使用str
引用指向剛才分配的堆內存。
至此開始分配的內存成為垃圾資源,等待GC回收。
需要注意String 字符比較中的==
和equlas
。前者是比較字符串內存地址,是數值比較,后者比較字符內容。
關于字符比較注意點: 我們使用equlas
比較字符串的時候應該避免未實例化對象調用方法。應該將未知字符對象作為參數使用。如:不應該 這樣:
String var = "hello";
if(var.equlas("hello")){
//
}
**10.1 String 方法 **
善于查詢JavaSE API。我可以打開在線java文檔查詢。
如圖:
我們找到java.lang包下面的String 類.找到 Method Summary
這是普通方法表.
需要記住平常工作中比較常用的幾類方法.
- 字符串查找:
方法名 | 返回值 | 描述 | 備注 |
---|---|---|---|
public int indexOf(String str) | 返回所查找的字符串的索引值,如果沒有匹配上則返回-1 | 這個索引值是從0 開始的 |
只會返回第一次匹配上的字符串所在索引,從前往后查找 |
public int indexOf(String str,int fromIndex) | 返回所查找的字符串的索引值,如果沒有匹配上則返回-1 | 這個索引值是從0 開始的 |
只會返回第一次匹配上的字符串所在索引,從指定索引處由前向后開始查找 |
public int lastIndexOf(String str) | 返回所查找的字符串的索引值,如果沒有匹配上則返回-1 | 這個索引值是從0 開始的 |
需要注意的是如果匹配字符串為空串則依然會返回值 ,對于這個方法會返回this.length() 注意這里的 this 代表的含義 |
public int lastIndexOf(String str,int fromIndex) | 返回所查找的字符串的索引值,如果沒有匹配上則返回-1 | 這個索引值是從0 開始的 |
從指定索引處開始 |
- 字符串截取
方法名 | 返回值 & 描述 |
---|---|
public String substring(int beginIndex) | 返回字符串,從指定索引處開始截取到末尾 |
public String substring(int beginIndex,int endIndex) | 從索引beginIndex開始截取到索引endIndex處結束,返回字符串 |
- 字符串拆分
方法名 | 返回值 & 描述 |
---|---|
public String[] split(String regex) | 返回字符串數組,這里支持正則表達式,所以需要注意轉義,比如:. 等 需要"\\\\."
|
public String[] split(String regex,int limit) |
limit 指定拆分長度,默認從前向后拆分。即達到指定長度后,即使后面匹配拆分條件也不再拆分 |
-
字符串轉換
- 字符串轉字符
public char charAt(int index) 一般使用charAt(0) 將字符串轉換為字符.返回char
。 - 字符串轉字符數組
public char[] toCharArray() 將一個字符串轉換為字符數組。 - 字符串轉字節數組
public byte[] getBytes() 將一個字符串轉換為字節數組。
public byte[] getBytes(Charset charset) 將一個字符串按照指定編碼格式轉換為字節數組。我們常用這個方法來轉換編碼。這個方法為JDK1.6版本新增。 - 其他類型到字符串的轉換
public static String valueOf(boolean b) 將布爾值轉換為字符串,如果為true
則返回"true"
。
public static String valueOf(char c) 將字符轉換為字符串.
public static String valueOf(char[] data) 將字符數組轉換為字符串.
public static String valueOf(char[] data,int offset,int count) 將指定位置的字符數組轉換為字符串。
public static String valueOf(double d) 將double 轉換為字符串。
public static String valueOf(float f) 將float 轉換為字符串。
public static String valueOf(int i) 將int 轉換為字符串。
public static String valueOf(long l) 將Long 轉換為字符串。
public static String valueOf(Object obj) 將Object子類轉換為字符串。參數可以是null。
public String toString() 從Object 繼承而來。
- 字符串轉字符
其他字符串方法
public String trim() 去掉字符串左右空字符串。
public String toUpperCase() 轉大寫字母
public String toLowerCase()轉小寫字母
public boolean startsWith(String prefix) 是否以指定字符串開始。
public boolean startsWith(String prefix,int toffset) 從指定索引開始,是否以指定字符串開始。
public String intern() 將來字符串入池,返回字符串,返回的字符串為入池后的字符串,為共享字符串。
public boolean endsWith(String suffix) 是否以指定字符串結尾。
public boolean isEmpty() 判斷是否空字符串. JDK1.6新增 。
public int length() 返回字符串長度.不包含左右空字符串。
public boolean contains(CharSequence s) 是否包含指定字符串。字符串比較
public boolean equals(Object anObject) 比較兩個字符串內容是否相等。字符串必須完全一致。
public boolean equalsIgnoreCase(String anotherString) 忽略大小寫的字符串內容比較。
public int compareTo(String anotherString) 比較兩個字符串的大小,按照unicode 編碼值比較。
public int compareToIgnoreCase(String str) 忽略大小寫比較兩個字符串的大小,按照unicode 編碼值比較。字符串替換
public String replace(char oldChar,char newChar) 將所有的oldChar
替換為newChar
。
public String replace(CharSequence target,CharSequence replacement) 將所有的target
替換為replacement
。 這里需要注意CharSequence
是String
的接口,不同的是不能使用new
實例化.可以直接賦值。如:CharSequence ch = "abc";
public String replaceAll(String regex,String replacement) 全部替換,同地一個方法,只是參數不同。
public String replaceFirst(String regex,String replacement)只替換第一次出現的匹配。同樣支持正則。
11. 類與對象
面向對象有顯著的三大特征:
封裝
繼承
多態
封裝: 簡單的來說就是內部實現對外不可見。常用的就是我們在簡單java 類中經常使用private 修飾屬性權限。這就是封裝的典型使用。這樣我們就不能在非本類中直接操作類信息,需要通過類對象操作類屬性。
類實例化兩種方式:
-
類型名稱 對象名稱 = new 類型名稱();
//聲明并實例化對象 -
類型名稱 對象名稱;
//聲明對象
對象名稱 = new 類型名稱();
//實例化對象
在java中對內存分配有棧
和堆
之分。 棧 存儲基本類型數據和引用類型數據變量。堆存儲引用類型數據內容。引用類型數據操作都存在內存關系處理。需要謹慎。
比如,現在有一個類A 則以下表達式內存關系解釋:
A a ;
在棧內存塊中,聲明一個變量a
沒有任何指向,也可以認為a
現在指向null
。
a = new A();
在堆內存塊中開辟一塊內存空間,內容為A
中屬性的初始值。并將a
指向它。
簡單java類的編寫:
- 屬性全部使用 private 封裝。
- 屬性必須有setter & getter方法。
- 構造方法安裝參數升序編寫,必須有至少一個無參構造。
- 保留一個能輸出本類全部信息的方法。
12. this 、super關鍵字
this
和super
的區別:
- 使用this 操作屬性或方法,優先從本類查找,否則再從父類查找。this 可以構造方法互調用,但是必須留一個出口否則形成循環調用,必須放在構造方法的首行。this 指當前對象。
- super 存在繼承關系中,指子類直接操作父類屬性或方法。在實例化子類對象的時候必須先實例化其父類,默認會調用父類無參構造.
super();
必須放在構造方法的首行。
13. 方法的多態
- 重載(Overloading) : 發生在一個類中,方法名稱相同,方法參數類型及個數不同可以稱為方法重載,方法重載能根據不同的參數完成不同的功能。 重載可以改變方法的返回值,但是根據經驗,要求重載方法不要改變其返回值。
- 覆寫(Override) : 發生在繼承關系中,方法名稱、方法參數類型及個數相同、返回值相同可以稱為覆寫。覆寫的方法在繼承關系中,需要使用super 才可以調用父類方法,否則調用的是其子類覆寫過的方法。
14.繼承
繼承是為了擴展新特性。在已存在類的基礎上擴展。一般的,普通類之間不應該出現繼承關系,因為已經實現的類不適合描述更抽象的事物,因為父類的描述范圍要大于子類的描述范圍。而且,普通類之間的繼承在方法覆寫上沒有強制要求,可以選擇性覆寫。即可以選擇不覆寫普通父類中的方法。
- 抽象方法: 沒有方法體的方法叫抽象方法。
- 包含至少一個抽象方法的類必須聲明的為抽象類,且其子類必須覆其抽象方法。
- 一個類只能繼承一個抽象或普通類,不能像C++一樣可以實現多繼承。如果需要實現多繼承可以使用接口.
interface
。 - 抽象類不能直接實例化對象,它需要其子類來幫助實例化。利用類多態性向上轉型實現抽象類實例化.
- 抽象類可以包含以下成員:普通方法、抽象方法、普通屬性、全局成員變量、構造方法。
- 抽象類不能使用final 定義,因為抽象類必須被繼承。final標識類,則該類不能被繼承。
- 繼承后,子類覆寫的方法的訪問權限不能比父類更嚴格。
- 子類可以繼承父類的全部操作,父類的私有操作屬于隱式繼承,非私有操作屬于顯示繼承。
- 父類的構造方法不能被子類繼承。
- 抽象父類引用只能調用子類從父類繼承來并覆寫過的方法。普通父類引用調用(指向子類實例)調用子類沒有覆寫的父類方法時,直接調用父類方法。否則調用子類覆寫過的方法。
- 不管是抽象類還是普通類,如果自定義了有參構造,且忽略了無參構造,那么子類繼承后在構造方法中沒有指定調用父類有參構造則不能編譯通過,提示找不到父類無參構造。所以我們可以知道子類實例化之前會先調用父類無參構造(默認)實例化父類。
- 抽象類可以提供一個默認的實現,子類可以直接使用,也可以選擇自己實現,覆蓋父類對應實現。
抽象類定義 - 抽象類: 包含至少一個抽象方法的類為抽象類,包含至少一個抽象方法的類必須定義為抽象類。
關鍵字 :abstract
定義一個抽象類
abstract class A {
}
使用abstract
定義的類可以沒有抽象方法。但是沒有多少實際意義,如果不需要這個規范則打都不必要定義為抽象類。
- 抽象類組成結構:
- 成員變量(屬性)
- 全局常量 - 構造方法
- 普通方法
抽象類不是用來直接實現業務功能的,是為了提供一組信息的抽象,規范行為的歸屬,即這一組抽象信息下具體信息的行為和此具體信息是完全符合的。因為抽象類中的抽象方法子類必須全部覆寫,從而實現規范。利用向上轉型達到信息統一。從表面上看,我們看到的都是抽象類而非具體類。因為抽象類中包含有抽象方法所以,不能直接實例化。
其實能用抽象類的地方基本都可以使用接口。且接口沒有單繼承局限。可能我們有些時候還需要成員變量,此時接口就組能為力了,因為接口不能出想全局常量以外的變量.
15. 線程
在Java中,實現多線程的方式有兩種:
- 繼承Thread 類,該類是Runnable接口的子類。并覆寫
run()
方法. - 直接實現Runnable 接口,覆寫接口的方法
run()
.
以上兩種方式表面上看基本沒有區別,不過,需要注意的是啟動線程的功能并不在Runnable 接口,而在Thread類中,我們覆寫的方法并不能啟動多線程.只是一個普通的方法,我們稱為線程體,run()
只是功能實現方法,且run()
是存在于線程內的,簡單的說,run()
是在資源得到分配后才會執行的,而start()
不是,我們調用了start()
并不意味著這個線程就會立馬執行,只是就緒狀態,待搶占到資源后才會有機會調用run()
執行。所以,真正實現多線程的是Thread 的 start()方法,我們可以查看Thread 源碼的start()方法,我們可以看到在start()方法實現中,調用各個平臺的cpu資源調度本地方法實現多線程. 如下代碼:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0 || this != me)
throw new IllegalThreadStateException();
group.add(this);
start0();
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
再看 內部調用的start0()
方法詳情:
private native void start0();
從關鍵字native
可以知道這是一個本地方法,也就是非java語言實現方法,這是一個抽象方法,具體實現交給了JVM 調用各個平臺實現。
從關鍵字synchronized
我們也可以看出,同一個線程在存活的時候不能再啟動第二次.
多線程使用實例:
- 繼承Thread:
class MyThread extends Thread {
public void run(){
System.out.println(" this is thread.");
}
}
public class TestThread{
public static void main(String[] args){
MyThread mythread = new MyThread();
mythread.run();
mythread.start();//啟動線程
}
}
- 實現Runnable接口
class MyThread implements Runnable{
public void run(){
System.out.println("this is thread implements Runnable.");
}
}
public class TestThread{
public static void main(String[] args){
MyThread mythread = new MyThread();
new Thread(mythread).start();
}
}
從上面代碼可以看出,我們不能像繼承Thread類一樣直接調用start()
方法,因為Runnable接口并沒有start()
方法。所以我們需要借助Thread
類,因為我們前面說過,Runnable接口沒有啟動線程的功能,Thread 有。
new Thread(mythread).start();
這段代碼我們可以看出,Thread 類的構造方法中接收了Runnbale接口實現類,那么我們看下API:
public Thread(Runnable target)
接收Runnable接口對象構造,我們大概能猜測到,我們覆寫的run()
方法一直都是接口的,啟動線程后調用的run()
方法也是接口的。
MyThread 、Thread、Runnable三者的關系如下:
其中MyThread 實現Runnable接口
Thread 也實現了Runnable 接口
覆寫run()方法。
我們使用的時候代碼如下:
new Thread(mythread).start();
也就是說真正實現類是Thread類,有點像代理設計模式,但是又似乎不是。正常的代理設計,我們調用真實業務操作應該是run()
方法。可是這里不是。所以,它并不是真正的代理設計模式,據說這是歷史遺留的問題。從API可以看出Runnable接口和Thread都是是JDK1.0版本就有的,所以這個說法待考證。
- 總結
- Thread 是Runnable 的實現類
- 真正啟動線程是Thread類
- run()方法只是線程體,并不是啟動線程的方法。
- start()是啟動線程的方法,同一個線程存活的狀態下,不可以重復啟動。
- 線程同步
- 需要線程同步,可以使用同步代碼塊或者使用同步方法,將共享資源操作方法進行同步。
class MyThread implements Runnable{
private int count = 7;
public void run(){
for (int i =0 ; i<20;i++){
if (count >0) {
System.out.println("this is thread implements Runnable."+ count--);
}
}
}
}
public class TestThread{
public static void main(String[] args){
MyThread mythread = new MyThread();
MyThread my1 = new MyThread();
new Thread(mythread).start();
new Thread(my1).start();
//
}
}
以上操作就出現了共享資源不同步的問題。我們可以看打印日志:
this is thread implements Runnable.7
this is thread implements Runnable.7
this is thread implements Runnable.6
this is thread implements Runnable.6
this is thread implements Runnable.5
this is thread implements Runnable.5
this is thread implements Runnable.4
this is thread implements Runnable.4
this is thread implements Runnable.3
this is thread implements Runnable.3
this is thread implements Runnable.2
this is thread implements Runnable.2
this is thread implements Runnable.1
this is thread implements Runnable.1
我們可以看到出現了兩個線程搶到了同一個資源的問題。所以我們可以使用如下方法解決這個問題。看代碼:
class MyThread implements Runnable{
private int count = 8;
public void run(){
for (int i =0 ; i<300;i++){
/**try{
Thread.sleep(1000);
}catch(InterruptedException e){
//
}**/
synchronized(this){
if (count >0) {
System.out.println("this is thread implements Runnable. threadName "+Thread.currentThread().getName()+" --- "+ count--);
}
}
}
}
}
public class TestThread{
public static void main(String[] args){
MyThread mythread = new MyThread();
new Thread(mythread,"A").start();
new Thread(mythread,"B").start();
new Thread(mythread,"C").start();
new Thread(mythread,"D").start();
new Thread(mythread,"E").start();
//
}
}
我們工作中,不到萬不得已建議不要使用同步,通過其他方式或者方法繞過同步或者間接實現同步。
在實現線程的整體結構上很像代理設計模式,但是不完全是。
16. 反射
反射核心類Class
,通過Class
可以獲得反射需要的全部操作。此亦是反射的源頭。有下列三種方式獲得Class
實例:
- 通過
Object
提供方法:
Date date = new Date() ;
Class<?> cls = date.getClass();
Object obj = cls.newInstance();//獲得反射的實例對象, equals new Date();
//次代碼會拋出異常.需要捕捉或者方法拋出.
- 通過
Class
提供方法:
Date date = new Date() ;
Class<?> cls = Date.class ;
Object obj = cls.newInstance() ;
- 通過
Class
提供方法:
Date date = new Date() ;
Class<?> cls = Class.forName("java.util.Date") ;//
Object obj = cls.newInstance() ;
這里我們可以根據第三種方法改造以往工廠模式:
老的工廠模式:
interface Person {
void say() ;
}
class Student implements Person {
public void say() {
System.out.println("我是一個學生.") ;
}
}
class Work implements Person {
public void say() {
System.out.println("我是一個工人.") ;
}
}
class Factory {
public static Person getInstance(String className) {
if ("student".equals(className)){
return new Student() ;
}
if ("work".equals(className)){
return new Work() ;
}
return null ;
}
}
public class RefDemo {
public static void main(String[] args) throws Exception{
Person per = Factory.getInstance("student") ;
//Person per = Factory.getInstance("work") ;
per.say() ;
}
}
以上代碼明顯耦合嚴重。如果我們新添加一個Person
子類,則不僅僅需要修改客戶端代碼,同時也需要在Factory
內部添加Person
新子類獲得實例實現。而且,Factory
各種if
判斷條件和客戶端絕對一致,否則不能實現通過工廠獲取子類實例.
下面我們通過反射實現工廠進一步解耦合:
package cn.palm.test;
interface Person {
void say() ;
}
class Student implements Person {
public void say() {
System.out.println("我是一個學生.") ;
}
}
class Work implements Person {
public void say() {
System.out.println("我是一個工人.") ;
}
}
class Factory {
public static Person getInstance(String className) {
Person per = null ;
try{
Class<?> cls = Class.forName(className) ;
per = (Person)cls.newInstance() ;
}catch(Exception e){
e.printStackTrace() ;
}
return per ;
}
}
public class RefDemo {
public static void main(String[] args){
Person per = Factory.getInstance("cn.palm.test.Student") ;
//Person per = Factory.getInstance("work") ;
per.say() ;
}
}
以上可以通過反射達到不修改工廠類添加新子類的目的。從而使代碼更加靈活、健壯.
反射還有很多獲取類結構中全部的信息。如 屬性 、 構造方法、普通方法。
也可以獲取繼承來的屬性或者方法。這些全部需要通過Class
獲取.
-
break lable\continue lable 可以用到 ?