一、OOP(Object-oriented Programming)面向對象程序編程
初談類和對象,所謂萬物皆對象,類和對象有什麼區別和聯系?
類,是對某一種類型的定義,比如字符串,動物,人,飛機等等,而對象是指具體的字符串,動物,人...
如:豬是類,定義了,豬,有體重,有年齡,可以吃飯,可以睡覺,而對象是這只豬,那只豬,或者某只豬起個名字,叫小白,那麼這種具體的豬,就是對象,而小白這個名字,則稱為對象的引用,因為通過小白,我們可以找到指定的那頭豬。
package com.yq; public class Pig { private int height; private int age; void eat() { System.out.println("eat()"); } void sleep() { System.out.println("sleep()"); } public String toString() { return getClass().getName() + "===@===" + Integer.toHexString(hashCode()); } // public String toString() { // return getClass().getName() + "@" + Integer.toHexString(hashCode()); // } public static void main(String[] args) { Pig xiaoBai = new Pig(); Pig xiaoHei = new Pig(); xiaoBai.eat(); xiaoHei.sleep(); System.out.println(xiaoBai); System.out.println(xiaoHei); System.out.println("xiaoBai==xiaoHei?" + (xiaoBai == xiaoHei)); System.out.println(xiaoBai.getClass()); System.out.println(xiaoHei.getClass()); System.out.println("xiaoBai.getClass()==xiaoHei.getClass()?" + (xiaoBai.getClass() == xiaoHei.getClass())); } }
eat() sleep() com.yq.Pig===@===74a14482 com.yq.Pig===@===1540e19d xiaoBai==xiaoHei?false class com.yq.Pig class com.yq.Pig xiaoBai.getClass()==xiaoHei.getClass()?true
關於 '=='
對於引用變量而言,比較的時候兩個引用變量引用的是不是同一個對象,即比較的是兩個引用中存儲的對象地址是不是一樣的。
對於基本數據類型而言,比較的就是兩個數據是不是相等。
對於String類型,它是特殊而有趣的。
package com.yq; public class Test { public static void main(String[] args) { //String作為一個對象來使用 System.out.println("String作為一個對象來使用"); String str = new String("hello"); String str2 = "he" + new String("llo"); System.out.println("str==str2?" + (str == str2)); System.out.println(str + "@" + str.hashCode()); System.out.println(str2 + "@" + str2.hashCode()); //String作為一個基本類型來使用 //如果String緩沖池內不存在與其指定值相同的String對象,那麼此時虛擬機將為此創建新的String對象,並存放在String緩沖池內。 //如果String緩沖池內存在與其指定值相同的String對象,那麼此時虛擬機將不為此創建新的String對象,而直接返回已存在的String對象的引用。 System.out.println("String作為一個基本類型來使用"); String s1 = "java"; String s2 = "java"; System.out.println("s1==s2?" + (s1 == s2)); System.out.println(s1 + "@" + s1.hashCode()); System.out.println(s2 + "@" + s2.hashCode()); System.out.println("String混合使用"); String st1 = "hello java"; String st2 = new String("hello java"); System.out.println("st1==s2?" + (st1 == st2)); System.out.println(st1 + "@" + st1.hashCode()); System.out.println(st2 + "@" + st2.hashCode()); } }
String作為一個對象來使用 str==str2?false hello@99162322 hello@99162322 String作為一個基本類型來使用 s1==s2?true java@3254818 java@3254818 String混合使用 st1==s2?false hello java@-1605094224 hello java@-1605094224
輸出結果中,明明hashCode相等,為何對象不相同?
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
上述String源碼中,可以看出,String類是使用它的 value值作為參數然後進行運算得出hashcode的,
換句話說, 只要值相同的String不管是不是一個對象,hash值全部相等。
在Object超類中,hashCode()是一個native本地方法,看不到實現。
對象存放在內存的堆中,而基本類型則存放在內存的棧中。
二、操作符
移位操作符<<,>>,>>>
移位操作符操作的對象是二進制的"位",只能用來處理整數類型,如果對char、byte、或者short類型的數值進行移位處理,那麼他們在移位之前會自動轉換為int類型,結果也是int,對long處理,結果也是long。
<< : 左移運算符,低位補0,num << 1,相當於num乘以2
>> : 右移運算符,若符號為正,高位補0,若符號為負,高位補1,num >> 1,相當於num除以2
>>> : 無符號右移,忽略符號位,空位都以0補齊。
三元操作符 exp?value1:value2
若exp表達式為true,返回value1的值,否則返回value2的值,三元表達式可以嵌套。
'+'操作符,即字符串操作符,用來連接字符串。
'()type'類型轉換操作符,用來強制類型轉換,一般自動向上轉型,不需要強轉,向下轉型才需要強轉。
','逗號操作符,java裡面唯一用到逗號操作符的地方就是for循環的控制表達式,在表達式的初始化和步進控制部分,可以使用逗號分隔的語句,但是初始化時,使用逗號表達式,必須具有想同的類型。
三、流程控制
if-else、while、do-while、for、foreach、switch。
if語句不加{},默認范圍為下一條語句。
else if不是關鍵字,只是else後面加了一個if語句。
do-while相比while只是會先執行一次,再判斷條件。
for(;;)效果和while(true)一樣。
foreach語法用於數組和容器。
package com.yq; import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; for (int i : arr) System.out.print(i + " "); System.out.println(); List<String> ls = new ArrayList(); ls.add("a"); ls.add("b"); ls.add("c"); ls.add("d"); ls.add("e"); for (String str : ls) System.out.print(str + " "); } } //out 1 2 3 4 5 a b c d e
關鍵字return、break、continue。
return結束整個方法,開始下一個方法。
break結束當前層的循環,開始當前層的外層的下一次循環。
continue結束當前層的當次循環,開始當前層的下一次循環。
switch的選擇因子,是一個能夠產生整數值的表達式,char本身就是整數。
四、類初始化
this關鍵字
1、表示當前類對象的引用
2、表示對調用當前方法的那個對象的引用
3、構造器調用類中的其他構造器,但是不能調用兩次,並且必須放在構造器的第一行。
static關鍵字
static靜態方法,可以通過類本身直接調用,當然類對象也可以調用static方法,但是static方法裡面不能調用非靜態方法,也不能出現this。
對於static修飾的類屬性來說,內存只為該屬性第一次初始化時分配一個地址,不管該類new了多少個對象,該屬性都是同一個引用。
雖然地址是唯一的,即引用是唯一的,但是引用的值還是可以改變的。
final關鍵字
final作用於方式,則該方法不能被繼承時覆蓋,作用於參數,如果是引用類型,則該引用不能改變,但是引用的對象可以改變,如果是基本類型,則只能被賦值一次,並且不能改變。
對於一個類說來,該類的成員屬性,可以不初始化,因為系統會為各成員屬性設置默認值,String、對象等默認為null,數值類型為0,對應folat或者double,也有相應的小數點,char也為0,顯示為空白,boolean為false,String、對象則為null。
只有類成員屬性會被默認初始化,方法中必須對定義的屬性或變量初始化。
初始化順序
構造器不指定時,系統將會設置一個默認無參構造器,若指定了一個構造器,無論是否有參數,系統都不會自動設置默認構造器。
下面的例子,詳細分析了,各種情況的初始化順序:
package com.yq; class Animal { static int level; int type; static { System.out.println("Animal static"); level = 1; System.out.println("Animal level:" + level); } { System.out.println("Animal"); type = 10; } Animal(int i) { System.out.println("Animal constructor"); } } class Pig extends Animal { static int level; int height; Eye eye = new Eye(2); static { System.out.println("Pig static"); level = 2; System.out.println("Pig level:" + level); } { System.out.println("Pig"); height = 20; super.level = 3; } Pig(int i) { super(i); System.out.println("Pig constructor"); } } class Eye { Eye(int i) { System.out.println(i + "只眼睛"); } } public class Test { public static void main(String[] args) { //測試1 Pig pg1 = new Pig(2); System.out.println(Animal.level); //測試2 Pig pg2 = new Pig(2); Pig pg3 = new Pig(2); System.out.println(Animal.level); //測試3 System.out.println(Pig.level); } }
//測試1輸出 Animal static Animal level:1 Pig static Pig level:2 Animal Animal constructor 2只眼睛 Pig Pig constructor 3
//測試2輸出 Animal static Animal level:1 Pig static Pig level:2 Animal Animal constructor 2只眼睛 Pig Pig constructor Animal Animal constructor 2只眼睛 Pig Pig constructor 3
//測試3輸出 Animal static Animal level:1 Pig static Pig level:2 2
由測試1可知,初始化順序為:父類靜態成員、父類靜態代碼塊、子類靜態成員,子類靜態代碼塊、父類成員,父類代碼塊,父類構造器、子類成員、子類代碼塊、子類構造器。
由測試2可知,靜態域只初始化一次。
由測試3可知,只調用靜態域時,只初始化靜態域,和對象無關,靜態域只由類決定。
若父類沒有無參構造器,則必須用super顯式調用父類構造器,不然會報錯。
可變參數列表
package com.yq; public class Pig { static void test(String... args) { for (String str : args) System.out.print(str + " "); System.out.println(); } public static void main(String[] args) { test("小明", "小紅", "小軍", "小華"); } } //輸出 小明 小紅 小軍 小華
其實可變參數列表,是編譯器自動填充了數組。
五、訪問控制權限
public、protected、包訪問權限(沒有關鍵字,默認權限)、private
對於類來說,如果一個java文件,存在public類,則java文件名必須和類型一致,而且這個文件只能存在一個public類,若整個文件都不存在public類,則java文件名和類名沒有關系。
對於成員屬性和方法來說,
public是最大權限,任何類都可以訪問。
protected,繼承訪問權限,在該類的繼承類中可以訪問,同一個包下可以訪問。
包訪問權限,當沒有指明權限時,默認是包訪問權限,只有在同一個包下的類才能訪問。
private是最小的權限修飾符,只能在本類中訪問。
六、復用類。
組合:在新的類中放置任意對象,一般是private修飾該對象
繼承:在新的類中隱式的放置父級對象。
組合技術通常用於想在新類中使用現有類的功能,而非他的接口。
繼承技術通常用於保持現有類的形式,並可以添加新代碼。
一般來說判斷是否使用繼承,問一問自己是否需要從新類像父類進行向上轉型,如果需要則繼承是必須的,否則考慮組合技術應該更合適。
七、多態
因為多態的存在,程序有了可擴展性。
package com.yq; class Animal { public void eat(String args) { System.out.println("Animal eat():" + args); } } class Pig extends Animal { public void eat(String args) { System.out.println("Pig eat():" + args); } } class Cat extends Animal { public void eat(String args) { System.out.println("Cat eat():" + args); } } public class Test { public static void action(Animal anl) { anl.eat("蘋果"); } public static void main(String[] args) { Pig pg = new Pig(); Cat ct = new Cat(); action(pg); action(ct); } } //輸出 Pig eat():蘋果 Cat eat():蘋果
如果程序中,需要新加動物種類,則只需定義這個類,繼承Animal類即可,主程序不用做任何修改。
那麼為什麼編譯器能這麼智能的找到對應的子類對象,並且正確的調用方法呢?
原因就是:方法後期綁定,也叫作動態綁定。
意思是,在運行時根據對象的類型進行綁定,而不是在定義方法就進行前期綁定。
java中除了static和final方法(private方法也屬於final方法,因為final是隱式申明的)之外,其他所有的方法都是後期綁定。
八、抽象類和接口
包含抽象方法的類叫抽象類。
接口定義了方法的方法名,參數列表和返回類型,可以顯式的申明為public的,也可以不申明,因為默認就是public權限,另外接口中的成員屬性,也是有域的,默認是final、static的。