JavaSE面向物件(上)
Java面向物件(上)
Java是面向物件的程式語言,所有Java也就是支援 封裝、繼承和多型當然具體是如何實現的我們就簡單談談。
首先我們來看看它的封裝
封裝
為什麼要有封裝?
在剛開始學習時程我們可能會經常出現通過某個物件直接訪問其內部成員的情形,這就肯能引起一些潛在的問題,比如將Student的age成員變數設定為1000,這個語法上沒有問題,但是顯然不符合實際情況,所以就有了封裝。
定義:
封裝值得是將物件的狀態資訊隱藏在物件內部,不允許外部程式直接訪問物件內部資訊,而是通過類所提供的方法來實現對內部資訊的操作和訪問的。
封裝的目的
- 隱藏類的實現細節;
- 讓使用者只能從事先預定的方法來訪問資料,從而可以在方法里加入控制邏輯,限制隊成員變數的不合理的訪問;
- 可進行資料檢查,從而有利於保護物件資訊的完整性;
- 便於修改,提高程式碼的可維護性。
實現封裝的良好性需要從兩個方面去考慮。
1.將物件的成員變數和實現細節隱藏起來,不允許外部直接去訪問。
2.把方法暴露出來,讓方法來控制對這些成員變數進行訪問或者操作。
總之一句話就是把該隱藏的隱藏起來,把暴露的暴露出來。
封裝的實現
封裝的具體實現是靠訪問控制符來實現的,Java提供了三個訪問控制符private、protect和public,分別代表了3個訪問控制級別。另外還有一預設訪問控制級別。他們的控制級別大小為private
其中default並沒有對應的訪問控制符去控制,當不加任何訪問控制符時系統預設使用該訪問控制級別
我們在詳細看一下這些訪問控制符的許可權
private | default | protect | public | |
---|---|---|---|---|
同一個類中 | Y | Y | Y | Y |
同一個包中 | Y | Y | Y | |
子類中 | Y | Y | ||
全域性範圍內 | Y |
注意事項
1.訪問控制符是用於控制一個類的成員是否可以被其他類訪問,而對於區域性變數而言,其作用域就是他所在的方法,不可能被其它類訪問,因此不能用訪問許可權控制符修飾。
2.對於外部類而言也不能用private和protected,因為外部類不處於任何類的內部,這樣做沒有意義。
3.另外在一個Java的原始檔中如果你定義了多個類(當然不是很推薦這麼作),它可以是一切合法的檔名,但是如果有一個public修飾的類,則這個檔案必須與public修飾的類名相同。
接下來我們來看看程式碼中是怎麼實現的吧。
package com.company;
public class Person {
//隱藏成員變數
private int age;
private String name;
//提供以下方法來操作
public int getAge() {
return age;
}
public void setAge(int age) {
//設定合理性0 < age < 100
if(age < 0 || age > 100){
System.out.println("輸入年齡不合法");
return;
}
else
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
//設定的合理性判斷
if(name.length()> 8 || name.length() < 2){
System.out.println("輸入名稱不合理");
return;
}
else
this.name = name;
}
}
這樣我們就實現了一個person類的封裝。接下來我們再在程式中呼叫來看看具體效果
package com.company;
public class Test {
public static void main(String[] args) {
Person p = new Person();
//age已被隱藏所以下列語句將出現編譯錯誤
//p.age = 1000;
p.setAge(101);
//上面的語句設定不會錯誤但是會提示“不合法”
p.setAge(15);
//上面的語句會將年齡設定為15
System.out.println("age = " + p.getAge());
}
}
從上面的簡單的例子我們可以看出了Java中是如何具體實現封裝的了,
接下來我們來談談繼承。
繼承
概述:當多個類中存在相同的屬性和行為時,經這些內容抽取到單獨的一個類中。期中實現繼承的類被稱為子類,被繼承的類被稱為父類。通俗點來講繼承是一種一般和特殊的關係,就好像人類和學生,動物和狗。
實現:
Java是通過extends關鍵字來實現繼承的語法如下
修飾符 Class SubClass extends SuperClass{
//類的定義部分
}
其中SubClass是子類名SuperClass是父類類名。
其實extends在英文中其實是拓展的意思(這也就是我們所說的繼承其實也是對父類功能的一個拓展)。
我們來通過一段程式碼來了解一下繼承的基本語法。
首先我們定義一個父類
package com.company;
public class SuperClass {
public int num;
public void show(){
System.out.println("我是父類");
}
}
我們在定義一個子類
package com.company;
public class SubClass extends SuperClass{
}
定義測試類
package com.company;
public class Test1 {
public static void main(String[] args) {
//建立子類物件
SubClass s = new SubClass();
//設定繼承下來的成員變數
s.num = 1;
System.out.println("SubClass num = " + s.num);
//呼叫繼承下來的成員方法
s.show();
}
}
我們來看一下執行結果
從執行結果中我們可以看出雖然說子類沒有num的成員變數和show方法但是他通過繼承獲得了該成員變數和方法。這就是繼承的作用。
繼承的好處
1.提高了程式碼的複用性
2.提高了程式碼的拓展性
繼承的弊端
繼承這一特點強調了類與類之間的關係這就違背了開發的一個原則——高內聚,低耦合。那什麼是內聚呢?就是說自己完成事情的能力,什麼又是耦合呢?就是類與類之間的關係。
繼承的特點
在Java中摒棄了C++語言中難以理解的多繼承的特徵。每一個類只能有一個直接父類。但是,Java支援多曾繼承。
class Fruit extends Plant{...}
class Apple extends Fruit{...}
當然繼承也不是父類中的所有東西子類都可以繼承的比如以下幾點不能被繼承:
1.父類中的私有成員變數和方法。
2.父類的構造方法。(可通過Super關鍵字呼叫)
另外:繼承中成員變數的訪問遵循(就近原則)訪問順序如下(以a為例):
1.查詢該方法中是否有名為a的區域性變數。
2.查詢當前類中是否包含a的成員變數
3.查詢a的直接父類中是否包含成員變數a,依次上溯a的所有父類。直到java.lang.Object類,如果沒有則編譯報錯。
有時候我們會遇到一些情況比如鳥類中都包含飛翔我們可以通過繼承來獲得,但是鴕鳥是一種特殊的鳥類它只會奔跑,所以這個時候我們就要重寫父類方法了
重寫父類方法
//鳥類
package com.company;
public class Bird{
public void fly(){
System.out.println("我能飛!");
}
}
//鴕鳥類
package com.company;
public class Ostrich extends Bird{
@Override
public void fly(){
System.out.println("我只能跑!");
}
}
//測試類
package com.company;
public class Test2 {
public static void main(String[] args) {
Ostrich os = new Ostrich();
//執行重寫的fly方法:輸出我只能跑!
os.fly();
}
}
從上面的程式碼中我們可以看出子類不再執行父類原有的方法了,而是執行重寫過後的方法,其中 的@Override標記代表的是該方法為重寫過後的方法。
這就相當於父類方法被覆蓋掉了,但是如果我們想在本類中繼續呼叫父類被重寫的方法,我們可以用super限定符來實現。
###super限定
我們給剛才的Ostrich中新增一個方法。
public void callOverrideMethod(){
//通過super呼叫父類被重寫的方法
super.fly();
}
這樣我們就可以通過super呼叫父類被重寫的方法了構造器同樣可以,當然成員變數同名也會覆蓋掉父類中的成員變數我們通過super也可以來呼叫。
重寫注意事項
1.父類中的私有方法不能被重寫(子類也不能繼承);
2.子類重寫父類方法時訪問許可權必須大於或等於父類方法;
3.靜態方法的重寫必須通過靜態方法來重寫(算不上重寫,只是表現一樣)。
多型
表現:
Java的引用變數有兩個型別一個是編譯時型別,一個是執行時型別。如果編譯和執行時型別不一致,就有可能表現出多型。
前提:
1.多型必須以繼承作為前提。
2.要有方法重寫。
3.要有父類物件指向子類物件的引用
多型舉例
首先看下面的程式碼:
//父類
public class Animal {
int num=100;
public Animal() {
System.out.println("父類的空參構造執行了");
}
public void eat(){
System.out.println("吃飯");
}
public void sleep(){
System.out.println("睡覺");
}
public void show() {
System.out.println("abc");
}
public static void jingMethod(){
System.out.println("父類的靜態方法");
}
}
//子類貓繼承父類Animal
public class Cat extends Animal{
int num = 50;
public Cat() {
System.out.println("子類的構造方法執行了");
}
@Override
public void eat() {
System.out.println("貓愛吃魚");
}
@Override
public void sleep() {
System.out.println("貓愛白天睡覺");
}
@Override
public void show() {
System.out.println("zishow");
}
public static void jingMethod() {
System.out.println("子類的靜態方法");
}
}
//測試類
public class MyDemo {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
cat.sleep();
System.out.println("------------------------");
Animal an = new Cat();
an.show();
an.eat();
an.sleep();
System.out.println(an.num);
an.jingMethod();
Animal.jingMethod();
Cat.jingMethod();
}
}
在上述程式碼中貓既是一個貓類也是Animal類所以可以通過父類Animal引用去指向子類貓的物件。這時候在呼叫時出現的是子類方法的行為特質 而不是父類方法的這兒就是多型。其中的Animal an = new Cat();中編譯時為Animal但是在執行時確是Cat類。所以當你在呼叫方法時它會執行子類重寫過後的方法。但是靜態方法不同在呼叫時它依舊會執行父類中的方法。
多型提高程式碼的可拓展性
//定義一個父類Animal
public class Animal {
public void eat(){
System.out.println("吃飯");
}
}
//定義一個子類貓
public class Cat extends Animal{
//重寫父類方法
@Override
public void eat() {
System.out.println("貓愛吃魚");
}
}
//定義一個子類狗
public class Dog extends Animal{
//重寫父類方法
@Override
public void eat() {
System.out.println("狗吃骨頭");
}
}
//定義一個工具類並私有其構造方法
public class MyUtils {
//私有構造方法,外界不能就建立該類物件
private MyUtils() {
}
public static void method(Animal an) {
an.eat();
}
}
//測試類
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
//輸出夠愛吃骨頭
MyUtils.method(a);
System.out.println("----------------------------------");
a = new Cat();
//輸出貓愛吃魚
MyUtils.method(a);
}
}
從上述的例子中我們可以看出貓和狗既屬於貓和狗類也屬於Animal類這時我們用一個父類的物件來分別指向貓狗類的物件時,再呼叫工具類中的靜態方法,這樣救輸出了子類中重寫過後的方法。
多型的弊端
多型的弊端就是不能呼叫子類中特有的方法如果需要呼叫則需要向下轉型,所以多型就相當於是向上轉型。
比如在上一個程式碼中如過Catlei中包含有一個Sleep()方法,這是父類中所沒有的,當你在取用如下方法呼叫時就會出現錯誤:
Animal an = new Cat();
//an.sleep(); //編譯報錯
//向下轉型
Cat c = (Cat)an;
c.sleep();
多型型別轉換異常
比如說你有一個父類Animal、兩個子類Cat和Dog
當你用多型形式來用一個父類引用指向子類物件時就可能出現下列情況
Animal an = new Dog()
Dog dog = (Dog)an;
an = new Cat();//少了這行程式碼就會出現型別轉換異常ClassCastException
Cat cat = (Cat)an;
為什麼會出現這種情況呢?
通俗點來看你的第一、二行程式碼說狗屬於是動物類沒有什麼問題但是如果沒有第三行程式碼,第四行程式碼又說狗是貓類這當然是說不通的所以就出現了型別轉換異常。
instanceof運算子
那麼如何避免這種錯誤的出現呢?
Java給出了一個instanceof運算子語法格式如下
引用型別變數 instanceof 類(或者介面);
它用於判斷前面的物件是否是後面的類或者其子類、實現類和實現例。如果是返回true,如果不是返回false。
Aniaml an = new Dog();
Dog dog = (Dog) an;
if(an instanceof Cat) {
Cat cat = (Cat) an;
}
else {
an=new Cat();
Cat cat = (Cat) an;
}