1. 程式人生 > 其它 >面向物件的特徵(封裝、繼承、多型、抽象)

面向物件的特徵(封裝、繼承、多型、抽象)

面向物件(思想)

許可權修飾:

  • public: 公開的,所有不同的包,不同的類全都可以訪問得到。
  • private: 私有的,只有自己的類(同一個class)中可以訪問,其他的都訪問不到。
  • protected: 受保護的,除了不同包的其他類訪問不到,其他都可以訪問到,不同包的子類也可以 訪問的到。
  • 預設:只有同一個包中的類可以訪問到,不同包訪問不到。

封裝:

  • 實際含義是:該隱藏的隱藏,該暴露的暴露

  • 全域性變數:定義在類中的,所有方法都可以訪問到,叫全域性變數

  • 區域性變數:定義在一個方法裡的,只有本方法可以訪問的叫區域性變數

定義變數:

​ 許可權修飾符(public) + 型別(void、int、string、array[]...) + 變數名稱

注:變數名稱要用合法的JAVA識別符號,首單詞的第一個字母小寫,後面的每一個單詞首字母大寫

可變引數:
public static void test(int...a){
	for(int i=0;i<a.length;i++){
		System.out.println(a[i]);
	}
}
  • 當不確定引數內要傳多少個時,用int... 在呼叫test方法時,可以任意傳幾個引數,同時會把a當作陣列進行處理

  • 注:

    • 如果一個引數裡有可變引數,並且有多個引數,那麼可變引數一定要放在最後一個

    • public static void test(int b,int...a){
      	for(int i=0;i<a.length;i++){
      		System.out.println(a[i]);
      	}
      }
      
方法封裝:
  • -定義格式: 修飾符(private) + 返回值型別(void、int、string....) + 方法名(自定義)+

( 引數型別 引數名稱 + , + 引數型別 引數名稱) + { 程式碼體 + return 表示式 }

  • 形式引數:在引數名稱中,方法本身起的名字叫形式引數,只起一個代號的作用。

  • 實際引數:在通過 . 呼叫方法時,依據形式引數傳進的東西是實際引數。

  • 不需要new物件就可以訪問的方法封裝:

    ​ public static Person getPerson(){

    ​ return new Person();

    ​ }

  • 方法封裝的好處:1.方便維護 2.減少冗餘程式碼的出現

構造方法:
  • 作用:用於該類的例項物件的初始化

  • 格式: public Person(){ }

  • 示例: pubilc int size;

    ​ pubilec Person(){

    ​ size=18;

    ​ }

    • 這就是給物件做了初始化,只要在另一個類中呼叫了Person的方法,new出了Person,那麼當輸出size時,預設輸出18.
    • 構造方法中也可以傳引數,當傳入了引數後,再呼叫此方法是就必須傳進引數對應的型別,如果想既可以傳引數,也可以不傳引數,那麼就再構造一個同名的方法,該方法不加任何引數,這就叫構造方法的過載
This關鍵字
  • this關鍵字總是指向該方法的物件。

    • 當this出現在構造方法中,this指的是該構造器正在初始化的物件,(也就是最外層的)
    • 在普通方法中引用,this指的是呼叫該方法的物件
  • 作用:this關鍵字最大的作用就是讓類中一個方法,訪問該類裡的另一個方法或Field

final關鍵字
  • final關鍵字可以修飾類,可以修飾變數,可以修飾方法:

    • 修飾類,類不能被繼承

    • 修飾變數,變數就變成了常量,只能被賦值一次 當修飾的變數是一個物件時,該物件不能改變(即不能將變數更改為另一個物件),但是物件中的內容可以改變

      • 如:final StringBuffer a=new StringBuffer("abc");

        ​ 執行 a=new StringBuffer(""); 會報錯,因為又重新New了一個物件

        ​ 但是執行 a.append("123"); 不會報錯,因為物件沒有改變,物件的內容發生變化是不影響的

    • 修飾方法,方法不能被重寫

通過set方法對物件進行初始化:

程式碼: public void setSex(String sex){

​ this.sex=sex;

​ }

小結:
  • 變數—————— 類對應的屬性
  • 構造方法——————new物件的時候對物件初始化
  • 方法(函式)——————功能性的方法
封裝打包學生類的程式碼:

Demo demo=new Demo();

Student student=new Student("tom",18,6,"3333","男");

demo.save(student);

public void save(Student student){

​ System.out.println(student.getName());

​ System.out.println(student.getAge());

}

匿名物件

如上:Demo demo=new Demo();

​ demo.save(new Student());

  • new Student只用到一次,沒有給他起名字,這就是匿名物件
值傳遞
  • JAVA的引數傳遞方式只有一種,那就是值傳遞

  • 值傳遞含義:就是將實際引數的複製品傳進方法,也就是重新建立一個一模一樣的引數,對這個複製品進行方法操作,而實際引數的本身並沒有受到影響。

    如:int a=5,b=5;

    ​ public void change(int a, int b){
    ​ a=100;

    ​ b=100;

    ​ }

    public void change(int a, int b){
    					a=100;
    					b=100;
    		}
    
    public static void main(){
    	int a=10;
    	change(a);
    		System.out.println(a);
    }
    //列印後a的值依舊是10,呼叫方法並沒有改變,因為傳進去的是基本型別的副本,只有引用型別才會傳進地址,最後的值會被改變,如果用change方法改變a的值的話,需要在後面return
    

    傳進的是引用型別時:

    • public void change(StringBuffer buffer){
      					buffer.append("123");
      		}
      public static void main(){
      	StringBuffer buffer=new StringBuffer("ABC");
      	change(buffer);
      	System.out.println(buffer);
      }
      //此時列印後結果為ABC123,因為傳進去的是地址,原本的值會被修改
      

    在此次方法操作中,a和b的值依舊是5,並沒有變成100,就是對a與b的複製品進行了操作,實際引數本身沒有改變

    如果想要將ab變成100的話,就需要返回a與b,在後面加return ,並將void改為Int

  • 只有基本型別的值傳遞才是將複製品傳進方法,其他型別的值傳遞是傳進地址值的複製品,所以是可以對實際引數進行修改的

過載
  • 方法名不可以重名,但是方法名相同時,引數列表不同時,如:方法一有兩個引數,方法二有三個引數,儘管兩個方法名字相同,但是可以同時存在,這叫方法的過載

  • 過載只與方法名和引數名稱有關,與許可權修飾符,返回值型別等無關

  • 方法名相同,引數名稱不同或引數數量不同時,才叫過載

遞迴
  • 含義:自己呼叫自己

  • 斐波那契數列:

    • 1 1 2 3 5 8 13 21 34

    • 假設要求第n位的值:

      public int fb(int n){

      ​ if(n1 || n2){

      ​ return 1;

      ​ }else{

      ​ return fb(n-1)+fb(n-2);

      ​ }

      }

  • 遞迴最重要的是找到第一個遞迴的出口

Static
  • 特點:

    • static是跟隨類的出現而出現,類名出現一次,那麼static後面的內容就會執行一次

    • 被類的所有物件(new一次就是一個物件)共享:在呼叫static修飾的變數時,不同物件呼叫的是同一個變數,物件一對變數b進行改變時,物件二中的變數b也會改變而不同的物件呼叫不被static修飾的變數時,不同的物件使用的是不同的變數,物件一修改變數a時,物件二的變數a是不會被改變的

    • 例如:

      • static int a=1;
        public static void main(){
        	Num num1=new Num();
        	Num num2=new Num();
        	num1.a=2;
        	System.out.println(num2.a)
        }
        //此時列印的num2的a,它的值為2,因為兩個物件共用的一個變數,num1做了修改,num2也會有改變
        
    • 可以通過類名直接呼叫

  • 注意事項

    • 在靜態方法中不可以使用this關鍵字 因為this關鍵字總是指向該方法的物件,靜態方法是隨著類的出現而出現,不需要New出物件,所以在靜態方法中,沒有物件是無法使用this的
    • 靜態方法裡不能訪問成員變數或成員方法 因為靜態方法是隨著類的出現而出現,成員變數需要先New出物件,才能訪問得到
  • 靜態變數和成員變數的區別

    • 所屬不同
      • 靜態變數屬於類,所以也稱為為類變數
        成員變數屬於物件,所以也稱為例項變數(物件變數)
      • 記憶體中位置不同
        靜態變數儲存於方法區的靜態區
        成員變數儲存於堆記憶體
      • 記憶體出現時間不同
        靜態變數隨著類的載入而載入,隨著類的消失而消失
        成員變數隨著物件的建立而存在,隨著物件的消失而消失
      • 呼叫不同
        靜態變數可以通過類名呼叫,也可以通過物件呼叫
  • 用武之地

    • 使用工具方法時,可以加static,因為每次使用工具都需要New出物件,過於麻煩,而加上static後,只需要使用類的名字直接用點就可以呼叫,不需要new物件
    • 只需載入或執行一次
程式碼塊:
  • 區域性程式碼塊:

    • 出現在方法的內部,用大括號括起來的就是區域性程式碼塊
    • 定義在區域性程式碼塊內的變數,出了區域性程式碼塊後,外面的訪問不到
    • 限定變數生命週期,及早釋放,提高記憶體利用率
  • 構造程式碼塊

    • 在類中方法外出現;多個構造方法中相同的程式碼存放到一起,每次呼叫構造都執行時就可以用構造程式碼塊,並且在構造方法前執行
    • 運用:當所有的構造方法中都有相同的部分時,可以把他放進構造程式碼塊裡,這樣可以減少構造方法中相同的部分出現
  • 靜態程式碼塊

    • 在構造程式碼塊前加上static,構造程式碼塊是New一次,程式碼塊的內容出現一次,靜態程式碼塊是類出現一次,就執行一次

繼承:

概述:
  • 多個類存在相同的屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為(也叫方法),只要繼承這個類就可以了,這個類就是父類

  • 父類與子類:

    • 父類又叫超類或者基類
    • 子類又叫派生類
  • 通過extends關鍵字實現繼承

    • class 子類名 extends 父類名 {}
繼承的好處:
  • 提高了程式碼的複用性

    • 多個類相同的成員可以放到同一個類中
    • 如:老師和學生同屬於人,那麼人這個類中定義的姓名,年齡,身高體重這些屬性老師和學生都可以使用
  • 提高了程式碼的維護性

    • 如果功能的程式碼需要修改,只需修改父類的程式碼即可
  • 讓類與類之間產生了關係,是多型的前提

    • 其實這也是繼承的弊端,類的耦合性很強
    • 耦合性:在A類中new一個B類,一旦B類出現問題,那麼A類中使用的B類也不能用,或者想要將B類換成C類,要將A類中所有B類的部分全都替換
特點:
  • JAVA只支援單繼承,不支援多繼承

    • 一個類只能有一個父類
  • JAVA支援多層繼承(爺爺類)

    • A繼承B B繼承C
注意事項:
  • 子類只能繼承父類非私有的成員(成員方法和成員變數)

    • 這也是繼承的另一個弊端:打破了封裝性
  • 子類不能繼承父類的構造方法,但是可以通過super關鍵字去訪問父類構造方法

    • 要訪問父類的構造就只能通過子類的構造方法去訪問

      • //父類的空參構造:
        public Pet(){
        }
        
        //通過子類訪問父類構造:
        public Dog(){
        	super();
        }
        
        
        //注:儘管在子類的構造方法中沒有寫super(); 但是已經預設寫上了父類的空參構造
        
        
        
        
        //父類的有參構造:
        public name;
        publci Pet(String name){
        	this.name=name;
        }
        
        //通過子類訪問父類的有參構造:
        public Dog(String name){
        	super(name);
        }
        
        
        
    • 子類中所有的構造方法預設都會訪問父類空參的構造方法

      • 因為要實現子類的初始化,首先要實現父類的初始化,子類使用的是父類的變數,父類如果沒有構造,子類不能使用
    • 不能通過 子類=new 父類

    • 可以通過 父類=new 子類

子類中訪問變數的順序
  • 首先在子類區域性範圍找
  • 然後在子類成員範圍找
  • 最後在父類成員範圍找 不能訪問父類區域性範圍
  • 最後還沒有就會報錯 是不會找爺爺類的
super關鍵字
  • super的用法和this很像

    • this代表本類物件的引用
    • super代表父類物件的引用
  • super用法:

    • super.變數名 (父類的變數) super.name;
    • super.方法名 (父類的方法) super.eat();
重寫(Override):
  • 子類中出現了和父類一模一樣的方法宣告,也被稱為方法覆蓋,方法複寫

  • 規則:

    • 方法名相同,形參列表相同

    • 子類方法返回值型別應該比父類方法返回值相等或者更小。

      • 返回值型別比父類的要小,這兩個返回值一定要有父子關係才行
      • 基本型別之間沒有父子關係
    • 子類方法宣告丟擲的異常應該比父類方法更小或者相等

    • 子類許可權比父類許可權大或者相等

  • 補充:

    • 子類重寫了父類的方法後,子類物件無法訪問父類中被覆蓋的方法,但可以在子類方法中呼叫父類被重寫的方法,需要使用super關鍵字
    • 如果父類的方法啊具有private許可權修飾符,則該方法是對子類隱藏的,也就是無法重寫該方法
      • 如果子類中定義了一個與父類private方法 相同的方法名、形參列表、返回值型別,這依然不是重寫,只是在子類重新定義了一個新方法而已
    • 子類重寫父類方法時,訪問許可權不能更低
      • 如:父類方法用 protcted修飾,那麼子類的修飾符只能更大或者一致,也就是隻可以用public 或者protcted修飾
    • 父類靜態方法,子類也必須經過靜態方法進行重寫
      • 這個不算重寫,但是表現出的現象確實如此
      • 因為靜態不屬於繼承的範圍,靜態與類掛鉤,與物件無關
常量的許可權修飾:
  • 任何一個常量都是被public static final修飾
    • public 保證可以訪問到
    • static 表示是跟著類出來的 (通過類來選)
    • final 表示不能更改
一個類的初始化順序:
  • 先初始化靜態變數,靜態方法
  • 執行靜態程式碼塊
  • 執行構造程式碼塊
  • 執行構造方法

多型

  • 一個事物在不同時刻表現出來的不同狀態
多型的前提和體現:
  • 有繼承關係

  • 有方法的重寫

  • 有父類引用指向子類物件

  • 舉例:

    • Pet pet=new Dog();
      pet.eat();
      
      Pet pet=new Cat();
      pet.eat();
      
    • new Dog時打印出狗吃骨頭,new Cat時打印出貓吃魚

多型的好處:
  • 對外管理的是同樣的pet物件,具體New哪一個類,在後面改就可以

    • 在大型專案中,後面用父類具體new哪一個子類,是不用在程式碼裡寫的,用配置檔案配置就好了
    • 這叫做解耦,程式碼與程式碼之間的耦合性降低
  • 提高了程式的維護性

  • 提高了程式的擴充套件性

    • 在定義方法時,傳進的引數只需傳一個父類就代表隨意傳子類了,具體想要傳哪一個子類,只需在父類new子類的時候,改成想要new的那個子類就可以,形參是不需要動的
多型的弊端:
  • 不能訪問子類特有功能
  • 想要訪問子類特有功能只能轉型
instanceof關鍵字
  • A類 instanceof B類

    • A類是否是B類的子類
  • A類 instanceof A類

    • 這也是可以的
成員訪問的特點:
  • 訪問變數時,訪問的是父類的變數

  • 訪問方法時,訪問的是子類的方法

    • 但是父類中要有和子類方法一樣的方法名

      • 如: 子類Dog類中有play這個方法,但是父類Pet類中沒有該方法,所以是無法使用Dog類的play方法的

      • 這就是多型的弊端:無法訪問子類特有的功能

        • play方法是Dog類特有的功能,父類Pet類沒有,所以訪問不到
      • 解決方法:轉型

        • public static void change(Pet pet){
          	if(pet instanceof Dog ){
          		pet=(Dog)pet;
          		((Dog)pet).play();
          	}
          	if(pet instanceof Cat){
          		pet=(Cat)pet;
          		((Cat)pet).play();
          	}
          }
          
  • 訪問靜態方法時,訪問的是父類

抽象類

  • 抽象就是找出事物的相似和共性之處,然後將然後將這些事物歸為一個類,這個類只考慮這些
    事物的相似和共性之處,並且會忽略與當前主題和目標無關的那些方面,將注意力集中在與
    當前目標有關的方面

  • 當一個類沒有具體資訊,只是定義一些功能(就是要幹嘛能幹嘛),那麼就把這個類定義為抽象類

定義格式:
  • 抽象類和抽象方法必須用abstract關鍵字修飾
    • abstract class 類名 {}
    • public abstract void eat();
特點:
  • 抽象類不一定有抽象方法,有抽象方法的類一定是抽象類

    • 抽象類中有具體方法,是為了提高程式碼的複用性
      • 抽象類可以有一些共有的工具方法,這些方法對所繼承的所有子類來說都是一樣的,或者是不變的、唯一的,那麼就可以加在抽象類中大家一起去用
      • 子類所特有的一些東西,在抽象類中只定義一個抽象方法,具體的實現由子類自己去重寫
  • 抽象類不能夠被例項化

    • 抽象類的例項化是通過多型來例項化
    • 這也是多型的一種,抽象類多型
  • 抽象類的子類:

    • 抽象類的子類必須重寫抽象類的所有抽象方法
    • 如果子類沒有實現抽象父類的所有抽象方法,那麼子類也必須定義為抽象類
成員特點:
  • 成員變數:

    • 可以是變數
    • 可以是常量
  • 構造方法:

    • 抽象類可以有構造方法,但是不能夠被例項化
    • 構造方法的作用就是用於子類訪問父類資料的初始化
      • 抽象類的抽象方法是要被子類重寫的,是要有繼承關係的,所以抽象類的構造方法的作用就是讓子類去訪問的,但是抽象類本身是不能夠被例項化,不能new出抽象類這個物件,但是它的構造方法會被執行一下
    • 抽象類不能有抽象構造方法和抽象靜態方法
  • 成員方法:

    • 抽象方法:限定子類必須完成某些行為
    • 非抽象方法:提高程式碼的複用性
abstract關鍵字不能與哪些關鍵字共存:
  • private 衝突

    • 定義為抽象方法一定是要被子類重寫的,那麼如果將抽象方法定義為private子類都訪問不到,重寫個錘子
  • final 衝突

    • 與上同,final修飾的方法不可以被重寫
  • static 無意義

    • 抽象類要被繼承,繼承與物件掛鉤,物件是後出現的,而static是隨著類的出現而出現,繼承不到static方法

介面

  • 介面是在抽象類的基礎上出現的,介面限定的是行為,方便程式碼的擴充套件
格式:
  • 定義一個類為介面:

    • public interface UserService{
      	public void insert();
      	
      	public void delete();
      }
      
  • 用一個類實現一個介面:用 implements

    • public class UserService implements UserService{
      	public void insert(){
      	
      	}
      	
      	public void delete(){
      	
      	}
      }
      
特點:
  • 對於變數來說:

    • 只能是常量
    • 且常量的預設修飾符:public static final
      • 介面是一個規範,是不能被改變的,所以一定是常量,被final修飾,如果可以更改的話,這個規範沒有意義,就不叫規範
  • 對於方法來說:

    • 只能是抽象方法
    • 且抽象方法的預設修飾符:public abstract
  • 對於構造方法:

    • 介面沒有構造方法
    • 介面主要做功能拓展的,沒有具體實現
  • 介面不能被例項化

    • 要例項化介面,就要按照多型的方式
    • 這也是多型的一種,叫介面多型
  • 介面的子類

    • 必須重寫介面中的所有抽象方法

介面和抽象類的差異

  • 抽象類是一個類,子類要繼承(extends)它, 介面是要被子類實現(implements),關鍵字不同

  • 對於變數:

    • 抽象類可以有普通成員變數,介面沒有普通成員變數
      • 介面的變數只能為常量,預設被public static final修飾
  • 對於成員方法:

    • 抽象類裡可以有非抽象的方法,但是接口裡必須全都是抽象方法
    • 抽象類的方法可以包含靜態方法,介面不能包含靜態方法
      • 介面的抽象方法只能是,並且預設是:public abstract
  • 對於構造方法:

    • 抽象類可以有構造方法,介面不能有構造方法
  • 一個類可以實現多個介面,但只能繼承一個抽象類