對象(Object):包含一定的數據結構和狀態的實體。
操作(Operation):作用于對象的行為,如訪問和處理對象的狀態。
封裝(Encapsulation):定義對象和操作,只提供抽象的接口,并隱藏它們的具體實現。
繼承(Inheritance):通過繼承現有類型的性質,創建新的數據類型,而不影響原有數據類型。
多態性(Polymorphism):判定數據類型集合中各類型的區別,使程序可以按照它們的共同特性來書寫。
為便于說明,我們先定義一個簡單的類:
class Vehicle { int passengers; int fuelcap; int mpg; }
有了這個模板,就可以用它來創建對象:
Vehicle veh1 = new Vehicle();
通常把這條語句的動作稱之為創建一個對象,其實,它包含了四個動作。
1)右邊的“new Vehicle”,是以Vehicle類為模板,在堆空間里創建一個Vehicle類對象(也簡稱為Vehicle對象)。
2)末尾的()意味著,在對象創建后,立即調用Vehicle類的構造函數,對剛生成的對象進行初始化。構造函數是肯定有的。如果你沒寫,java會給你補上一個默認的構造函數。
3)左邊的“Vehicle veh 1”創建了一個Vehicle類引用變量。所謂Vehicle類引用,就是以后可以用來指向Vehicle對象的對象引用。
4)“=”操作符使對象引用指向剛創建的那個Vehicle對象。
我們可以把這條語句拆成兩部分:
Vehicle veh1;
veh1 = new Vehicle();
效果是一樣的。這樣寫,就比較清楚了,有兩個實體:一是對象引用變量,一是對象本身。
在堆空間里創建的實體,與在數據段以及??臻g里創建的實體不同。盡管它們也是確確實實存在的實體,但是,我們看不見,也摸不著。不僅如此,
我們仔細研究一下第二句,找找剛創建的對象叫什么名字?有人說,它叫“Vehicle”。不對,“Vehicle”是類(對象的創建模板)的名字。
一個Vehicle類可以據此創建出無數個對象,這些對象不可能全叫“Vehicle”。
對象連名都沒有,沒法直接訪問它。我們只能通過對象引用來間接訪問對象。
為了形象地說明對象、引用及它們之間的關系,可以做一個或許不很妥當的比喻。對象好比是一只很大的氣球,大到我們抓不住它。引用變量是一根繩, 可以用來系汽球。
如果只執行了第一條語句,還沒執行第二條,此時創建的引用變量veh1還沒指向任何一個對象,它的值是null。引用變量可以指向某個對象,或者為null。
它是一根繩,一根還沒有系上任何一個汽球的繩。執行了第二句后,一只新汽球做出來了,并被系在veh1這根繩上。我們抓住這根繩,就等于抓住了那只汽球。
再來一句:
Vehicle veh2;
就又做了一根繩,還沒系上汽球。如果再加一句:
veh2 = veh1;
系上了。這里,發生了復制行為。但是,要說明的是,對象本身并沒有被復制,被復制的只是對象引用。結果是,veh2也指向了veh1所指向的對象。兩根繩系的是同一只汽球。
如果用下句再創建一個對象:
veh2 = new Vehicle();
則引用變量veh2改指向第二個對象。
從以上敘述再推演下去,我們可以獲得以下結論:
(1)一個對象引用可以指向0個或1個對象(一根繩子可以不系汽球,也可以系一個汽球);
(2)一個對象可以有N個引用指向它(可以有N條繩子系住一個汽球)。
如果再來下面語句:
veh1 = veh2;
按上面的推斷,veh1也指向了第二個對象。這個沒問題。問題是第一個對象呢?沒有一條繩子系住它,它飛了。多數書里說,它被Java的垃圾回收機制回收了。
這不確切。正確地說,它已成為垃圾回收機制的處理對象。至于什么時候真正被回收,那要看垃圾回收機制的心情了。
由此看來,下面的語句應該不合法吧?至少是沒用的吧?
new Vehicle();
不對。它是合法的,而且可用的。譬如,如果我們僅僅為了打印而生成一個對象,就不需要用引用變量來系住它。最常見的就是打印字符串:
System.out.println(“I am Java!”);
字符串對象“I am Java!”在打印后即被丟棄。有人把這種對象稱之為臨時對象。
對象與引用的關系將持續到對象回收。
封裝從字面上來理解就是包裝的意思,專業點就是信息隱藏,是指利用抽象數據類型將數據和基于數據的操作封裝在一起,使其構成一個不可分割的獨立實體,數據被保護在抽象數據類型的內部,盡可能地隱藏內部的細節,只保留一些對外接口使之與外部發生聯系。系統的其他對象只能通過包裹在數據外面的已經授權的操作來與這個封裝的對象進行交流和交互。也就是說用戶是無需知道對象內部的細節(當然也無從知道),但可以通過該對象對外的提供的接口來訪問該對象。
對于封裝而言,一個對象它所封裝的是自己的屬性和方法,所以它是不需要依賴其他對象就可以完成自己的操作。
使用封裝有三大好處:
1、良好的封裝能夠減少耦合。
2、類內部的結構可以自由修改。
3、可以對成員進行更精確的控制。
4、隱藏信息,實現細節。
現在我們從程序的角度來分析封裝帶來的好處。如果我們不使用封裝(該對象就沒有setter()和getter()),那么Husband類應該這樣寫:
public class Husband { public String name ; public String sex ; public int age ; public Wife wife;}
我們應該這樣來使用它:
Husband husband = new Husband(); husband.age = 30; husband.name = "張三"; husband.sex = "男"; //貌似有點兒多余
但是那天如果我們需要修改Husband,例如將age修改為String類型的呢?你只有一處使用了這個類還好,如果你有幾十個甚至上百個這樣地方,你是不是要改到崩潰。如果使用了封裝,我們完全可以不需要做任何修改,只需要稍微改變下Husband類的setAge()方法即可。
public class Husband { /* * 對屬性的封裝 * 一個人的姓名、性別、年齡、妻子都是這個人的私有屬性 */ private String name ; private String sex ; private String age ; /* 改成 String類型的*/ private Wife wife; public String getAge() { return age; } public void setAge(int age) { //轉換即可 this.age = String.valueOf(age); } /** 省略其他屬性的setter、getter **/
其他的地方依然那樣引用(husband.setAge(22))保持不變。
到了這里我們確實可以看出:封裝確實可以使我們容易地修改類的內部實現,而無需修改使用了該類的客戶代碼。
我們再看另一個好處:可以對成員變量進行更精確的控制。
還是那個Husband,一般來說我們在引用這個對象的時候是不容易出錯的,但是有時你迷糊了,寫成了這樣:
Husband husband = new Husband(); husband.age = 300;
也許你是因為粗心寫成了,你發現了還好,如果沒有發現那就麻煩大了,逼近誰見過300歲的老妖怪??!
但是使用封裝我們就可以避免這個問題,我們對age的訪問入口做一些控制(setter)如:
public class Husband { /* * 對屬性的封裝 * 一個人的姓名、性別、年齡、妻子都是這個人的私有屬性 */ private String name ; private String sex ; private int age ; /* 改成 String類型的*/ private Wife wife; public int getAge() { return age; } public void setAge(int age) { if(age > 120){ System.out.println("ERROR:error age input...."); //提示錯誤信息 }else{ this.age = age; } } /** 省略其他屬性的setter、getter **/ }
上面都是對setter方法的控制,其實通過使用封裝我們也能夠對對象的出口做出很好的控制。例如性別我們在數據庫中一般都是已1、0方式來存儲的,但是在前臺我們又不能展示1、0,這里我們只需要在getter()方法里面做一些轉換即可。
public String getSexName() { if("0".equals(sex)){ sexName = "女"; } else if("1".equals(sex)){ sexName = "男"; } else{ sexName = "人妖???"; } return sexName; }
在使用的時候我們只需要使用sexName即可實現正確的性別顯示。同理也可以用于針對不同的狀態做出不同的操作。
public String getCzHTML(){ if("1".equals(zt)){ czHTML = "<a href='javascr; } else{ czHTML = "<a href='Javascript:void(0)' onclick='jy("+id+")'>禁用</a>"; } return czHTML; }
我們可以把JAVA中的類分為以下三種:
類:使用class定義且不含有抽象方法的類。
抽象類:使用abstract class定義的類,它可以含有,也可以不含有抽象方法。
接口:使用interface定義的類。
在這三種類型之間存在下面的繼承規律:
類可以繼承(extends)類,可以繼承(extends)抽象類,可以繼承(implements)接口。
抽象類可以繼承(extends)類,可以繼承(extends)抽象類,可以繼承(implements)接口。
接口只能繼承(extends)接口。
繼承是使用已存在的類的定義作為基礎建立新類的技術,新類的定義可以增加新的數據或新的功能,也可以用父類的功能,但不能選擇性地繼承父類。這種技術使得復用以前的代碼非常容易,能夠大大縮短開發周期,降低開發費用。
繼承是為了重用父類代碼,同時為實現多態性作準備。
繼承是所有OOP語言不可缺少的部分,在java中使用extends關鍵字來表示繼承關系。當創建一個類時,總是在繼承,如果沒有明確指出要繼承的類,就總是隱式地從根類Object進行繼承。比如下面這段代碼:
class Person { public Person() { }} class Man extends Person { public Man() { }}
類Man繼承于Person類,這樣一來的話,Person類稱為父類(基類),Man類稱為子類(導出類)。如果兩個類存在繼承關系,則子類會自動繼承父類的方法和變量,在子類中可以調用父類的方法和變量。在java中,只允許單繼承,也就是說 一個類最多只能顯示地繼承于一個父類。但是一個類卻可以被多個類繼承,也就是說一個類可以擁有多個子類。
1.子類繼承父類的成員變量
當子類繼承了某個類之后,便可以使用父類中的成員變量,但是并不是完全繼承父類的所有成員變量。具體的原則如下:
1)能夠繼承父類的public和protected成員變量;不能夠繼承父類的private成員變量;
2)對于父類的包訪問權限成員變量,如果子類和父類在同一個包下,則子類能夠繼承;否則,子類不能夠繼承;
3)對于子類可以繼承的父類成員變量,如果在子類中出現了同名稱的成員變量,則會發生隱藏現象,即子類的成員變量會屏蔽掉父類的同名成員變量。如果要在子類中訪問父類中同名成員變量,需要使用super關鍵字來進行引用。
2.子類繼承父類的方法
同樣地,子類也并不是完全繼承父類的所有方法。
1)能夠繼承父類的public和protected成員方法;不能夠繼承父類的private成員方法;
2)對于父類的包訪問權限成員方法,如果子類和父類在同一個包下,則子類能夠繼承;否則,子類不能夠繼承;
3)對于子類可以繼承的父類成員方法,如果在子類中出現了同名稱的成員方法,則稱為覆蓋,即子類的成員方法會覆蓋掉父類的同名成員方法。如果要在子類中訪問父類中同名成員方法,需要使用super關鍵字來進行引用。
注意:隱藏和覆蓋是不同的。隱藏是針對成員變量和靜態方法的,而覆蓋是針對普通方法的。(后面會講到)
3.構造器
子類是不能夠繼承父類的構造器,但是要注意的是,如果父類的構造器都是帶有參數的,則必須在子類的構造器中顯示地通過super關鍵字調用父類的構造器并配以適當的參數列表。如果父類有無參構造器,則在子類的構造器中用super關鍵字調用父類構造器不是必須的,如果沒有使用super關鍵字,系統會自動調用父類的無參構造器??聪旅孢@個例子就清楚了:
class Shape { protected String name; public Shape(){ name = "shape"; } public Shape(String name) { this.name = name; }} class Circle extends Shape { private double radius; public Circle() { radius = 0; } public Circle(double radius) { this.radius = radius; } public Circle(double radius,String name) { this.radius = radius; this.name = name; }}
這樣的代碼是沒有問題的,如果把父類的無參構造器去掉,則下面的代碼必然會出錯:
改成下面這樣就行了:
4.super
super主要有兩種用法:
1)super.成員變量/super.成員方法;
2)super(parameter1,parameter2....)
第一種用法主要用來在子類中調用父類的同名成員變量或者方法;第二種主要用在子類的構造器中顯示地調用父類的構造器,要注意的是,如果是用在子類構造器中,則必須是子類構造器的第一個語句。
方法的重寫、重載與動態連接構成多態性。Java之所以引入多態的概念,原因之一是它在類的繼承問題上和C++不同,后者允許多繼承,這確實給其帶來的非常強大的功能,但是復雜的繼承關系也給C++開發者帶來了更大的麻煩,為了規避風險,Java只允許單繼承,派生類與基類間有IS-A的關 系(即“貓”is a “動物”)。這樣做雖然保證了繼承關系的簡單明了,但是勢必在功能上有很大的限制,所以,Java引入了多態性的概念以彌補這點的不足,此外,抽象類和接口也是解決單繼承規定限制的重要手段。同時,多態也是面向對象編程的精髓所在。
多態又分為設計時多態和運行時多態,例如重載又被稱為設計時多態,而對于覆蓋或繼承的方法,JAVA運行時系統根據調用該方法的實例的類型來決定選擇調用哪個方法則被稱為運行時多態。總而言之,面向對象的設計的典型特點就是繼承,封裝和多態,這些特點也是面向對象之所以能如此盛行的關鍵所在。
對于多態,可以總結它為:
一、使用父類類型的引用指向子類的對象;該引用只能調用父類中定義的方法和變量;
二、如果子類中重寫了父類中的一個方法,那么在調用這個方法的時候,將會調用子類中的這個方法;(動態連接、動態調用)
三、變量不能被重寫(覆蓋),”重寫“的概念只針對方法。
重寫,英文名是overriding,是指在繼承情況下,子類中定義了與其基類中方法具有相同型構的新方法,就叫做子類把基類的方法重寫了。這是實現多態必須的步驟。
重載,英文名是overloading,是指在同一個類中定義了一個以上具有相同名稱,但是型構不同的方法。在同一個類中,是不允許定義多于一個的具有相同型構的方法的。
新聞熱點
疑難解答