十三、java類的封裝、繼承、多型
面向物件的三個基本特性就是:封裝、繼承和多型。
前面講解了java 程式都是由類檔案組成的,java是面向物件的程式設計語言,在java中面向物件的思想就是由類的設計來體現的,也即類的繼承、封裝和多型。
一、封裝(encapsulation)
封裝性就是把類(物件)的屬性和行為結合成一個獨立的相同單位,並儘可能隱蔽類(物件)的內部細節,對外形成一個邊界,只保留有限的對外介面使之與外部發生聯絡。封裝的特性使得類(物件)以外的部分不能隨意存取類(物件)的內部資料(屬性),保證了程式和資料不受外部干擾且不被誤用。
這個怎麼理解呢?首先來看一個列子。
已知一個類Animal,該類的屬性和方法如下表所示:
屬性 |
說明 |
方法 |
說明 |
String name |
名稱 |
Animal() |
無參建構函式,為屬性設定初始值 |
Int age |
年齡 |
Animal(String name,int age) |
有參建構函式,為屬性設定變數值 |
根據該類的定義,編寫一個程式,輸出該類的初始值以及通過變數設定的初始值,程式程式碼如下
public class AnimalDemo{
public static void main(Stringargs[]){
Animal a=new Animal();
Animal b=new Animal("cat",5);
System.out.println(a.name+"is "+a.age+" years old");
System.out.println(b.name+"is "+b.age+" years old");
}
}
程式執行結果:
Dog is 3 years old
cat is 5 years old
由此可以知道,類Animal的無參建構函式為name屬性賦值為“Dog”,為age屬性賦值為“3”。因此,可以寫出類Animal的程式碼如下:
class Animal {
String name;
int age;
Animal(){
name="Dog";
age=3;
}
Animal(Stringname,int age){
this.name=name;
this.age=age;
}
}
實際上這就是上一篇文章中的例子,那麼封裝在這裡的含義如下:
l 類本身就實現了封裝功能,此處類Animal定義了兩個屬性,兩個建構函式,其只屬於Animal類。
l 通過訪問修飾符來限制對類的屬性和方法的訪問,各修飾符含義如下:
Private:成員變數和方法只能在類內被訪問,具有類可見性
預設: 成員變數和方法只能被同一個包裡的類訪問,具有包可見性。
Protected:可以被同一個包中的類訪問,被同一個專案中不同包中的子類訪問
Public:可以被同一個專案中所有的類訪問,具有專案可見性,這是最大的訪問許可權
l 只能通過類本身定義的方法來對該類所例項化的物件進行資料的訪問和處理。比如想對例項化的物件新增其它的一個方法和屬性是不可能的。這就體現的類的封裝性。這裡也可以理解一下為什麼類被稱之為模板或者藍圖。
二、繼承
1、繼承是面向物件的三大特徵之一,也是實現程式碼複用的重要手段。Java的繼承具有單繼承的特點,即只能繼承自一個父類,每個子類只有一個直接父類,但是其父類又可以繼承於另一個類,從而實現了子類可以間接繼承多個父類,但其本質上劃分仍然是一個父類和子類的關係。
2、Java的繼承通過extends關鍵字來實現,實現繼承的類被稱為子類,被繼承的類稱為父類(有的也稱其為基類、超類),父類和子類的關係,是一種一般和特殊的關係。就像是水果和蘋果的關係,蘋果繼承了水果,蘋果是水果的子類,水果是蘋果的父類,則蘋果是一種特殊的水果。
3、Java使用extends作為繼承的關鍵字,extends關鍵字在英文是擴充套件的意思,而不是繼承。為什麼國內把extends翻譯成繼承呢?除了與歷史原因有關外,把extends翻譯成為繼承也是有其道理的:子類擴充套件父類,將可以獲得父類的全部屬性和方法,這與漢語中得繼承(子輩從父輩那裡獲得一筆財富成為繼承)具有很好的類似性。
值得指出的是:Java的子類不能獲得父類的構造器。
建立子類一般形式如下:
class 類名 extends 父類名{
子類體
}
4、子類與父類的變數、方法關係
子類可以繼承父類的所有特性,但其可見性,由父類成員變數、方法的修飾符決定。對於被private修飾的類成員變數或方法,其子類是不可見的,也即不可訪問;對於定義為預設訪問(沒有修飾符修飾)的類成員變數或方法,只有與父類同處於一個包中的子類可以訪問;對於定義為public或protected 的類成員變數或方法,所有子類都可以訪問。
子類中可以宣告與父類同名的成員變數,這時父類的成員變數就被隱藏起來了,在子類中直接訪問到的是子類中定義的成員變數。
子類中也可以宣告與父類相同的成員方法,包括返回值型別、方法名、形式引數都應保持一致,稱為方法的覆蓋。
如果在子類中需要訪問父類中定義的同名成員變數或方法,需要用的關鍵字super。Java中通過super來實現對被隱藏或被覆蓋的父類成員的訪問。super 的使用有三種情況:
l 訪問父類被隱藏的成員變數和成員方法;
super.成員變數名;
l 呼叫父類中被覆蓋的方法,如:
super.成員方法名([引數列]);
l 呼叫父類的建構函式,如:
super([引數列表]);
super( )只能在子類的建構函式中出現,並且永遠都是位於子類建構函式中的第一條語句。
舉例:
class BaseClass{
public double weight;
public void info(){
System.out.println("我的體重是"+weight+"千克");
}
}
public class ExtendsDemo001 extends BaseClass{
public static void main(String[]args) {
//建立ExtendsDemo001物件
ExtendsDemo001 ed = new ExtendsDemo001();
//ExtendsDemo001本身沒有weight屬性,但是ExtendsDemo001的父類有weight屬性,也可以訪問ExtendsDemo001物件的屬性
ed.weight = 56;
//呼叫ExtendsDemo001物件的info()方法
ed.info();
}
}
舉例二:
class Animal {
String name="animal";
int age;
void move(){
System.out.println("animalmove");
}
}
classDog extends Animal{
String name="dog"; //隱藏了父類的name屬性;
float weight; //子類新增成員變數
void move(){ //覆蓋了父類的方法move()
super.move(); //用super呼叫父類的方法
System.out.println("Dog Move");
}
}
publicclass InheritDemo{
public static void main(String args[]){
Dog d=new Dog();
d.age=5;
d.weight=6;
System.out.println(d.name+" is"+d.age+" years old");
System.out.println("weight:"+d.weight);
d.move();
}
}
程式執行結果:
dog is5 years old
weight:6.0
animalmove
DogMove
舉例三:
classSuperClass {
SuperClass() {
System.out.println("呼叫父類無參建構函式");
}
SuperClass(int n) {
System.out.println("呼叫父類有參建構函式:" + n );
}
}
classSubClass extends SuperClass{
SubClass(int n) {
System.out.println("呼叫子類有參建構函式:" + n );
}
SubClass(){
super(200);
System.out.println("呼叫子類無參建構函式");
}
}
publicclass InheritDemo2{
public static void main(String arg[]) {
SubClass s1 = new SubClass();
SubClass s2 = new SubClass(100);
}
}
程式執行結果:
呼叫父類有參建構函式:200
呼叫子類無參建構函式
呼叫父類無參建構函式
呼叫子類有參建構函式:100
請自行分析程式執行的結果,體會繼承的用法。
三、多型(Polymorphism)
多型性是指在繼承關係中的父類中定義的屬性或方法被子類繼承之後,可以具有不同的資料型別或表現出不同的行為。這使得同一個屬性或方法在父類及其各子類類中具有不同的含義。
Java引用變數有兩個型別:一個是編譯時型別,一個是執行時型別。編譯時的型別由宣告該變數時使用的型別決定,執行時的型別由實際賦給該變數的物件決定。如果編譯時型別和執行時型別不一致,就會出現所謂的多型(Polymorphism)
舉例1:
class Animal2 {
void eat(){
System.out.println("animal eat");
}
}
class Dog extends Animal2 {
void eat(){
System.out.println("Dog eat bone");
}
}
class Cat extends Animal2 {
void eat(){
System.out.println("Cat eat fish");
}
}
public class PloyDemo{
public static void main(String args[]){
Animal2 a;
a=newAnimal2 (); //編譯時型別和執行時型別完全一樣,因此不存在多型
a.eat();
a=new Dog(); //下面編譯時型別和執行時型別不一樣,多型發生
a.eat();
a=new Cat(); //下面編譯時型別和執行時型別不一樣,多型發生
a.eat();
}
}
程式執行結果:
animal eat
Dog eat bone
Cat eat fish
例項2:
class SuperClass{
public int book= 6;
public void base(){
System.out.println("父類的普通方法base()");
}
public void test(){
System.out.println("父類中將被子類覆蓋的方法");
}
}
public class PloymorphismTest001 extends SuperClass{
//重新定義一個book例項屬性,覆蓋父類的book例項屬性
public Stringbook = "Java瘋狂講義";
public void test(){
System.out.println("子類中覆蓋父類的方法");
}
private void Dmeo() {
System.out.println("子類中普通的方法");
}
//主方法
public static void main(String[]args) {
//下面編譯時型別和執行時型別完全一樣,因此不存在多型
SuperClass sc = new SuperClass();
System.out.println("book1= "+sc.book);//列印結果為:6
//下面兩次呼叫將執行SuperClass的方法
sc.base();
sc.test();
//下面編譯時型別和執行時型別完全一樣,因此不存在多型
PloymorphismTest001 pt = new PloymorphismTest001();
System.out.println("book2= "+pt.book);//列印結果為:Java瘋狂講義
//下面呼叫將執行從父類繼承到的base方法
pt.base();
//下面呼叫將執行當前類的test方法
pt.test();
//下面編譯時型別和執行時型別不一樣,多型發生
SuperClass sscc = new PloymorphismTest001();
//結果表明訪問的是父類屬性
System.out.println("book3= "+sscc.book);//列印結果為:6
//下面呼叫將執行從父類繼承到得base方法
sscc.base();
//下面呼叫將執行當前類的test方法
sscc.test();
//因為sscc的編譯型別是SuperClass,SuperClass類沒有提供Demo()方法
//所以下面程式碼編譯時會出現錯誤
//sscc.Demo();
}
}
程式執行結果為:
book1=6
父類的普通方法base()
父類中將被子類覆蓋的方法
book2=Java瘋狂講義
父類的普通方法base()
子類中覆蓋父類的方法
book3=6
父類的普通方法base()
子類中覆蓋父類的方法