1. 程式人生 > 實用技巧 >Java_02 面向物件:類與物件,變數,封裝,繼承,多型,抽象類,介面,異常

Java_02 面向物件:類與物件,變數,封裝,繼承,多型,抽象類,介面,異常

目錄

面向物件

Java面向物件:一種元件化的設計思想,萬物皆物件

c:面向過程;c++:半面向物件

定義:將我們客觀世界中的所有事物全部描述成物件,並通過抽象的思維方式將需要解決的實際問題分解成人們利於理解的物件模型,然後通過這些模型來構架應用程式功能。

面向物件最重要的一點是抽象的技術

面向物件跟符合人們說話辦事的思維方式,可以把複雜的事情簡單化

類與物件

類的定義:- Java中我們的每一個class檔案就代表一個類

​ -具有共同屬性的物件可以成為類,類是一個抽象的概念

物件:- 日常生活中所有的東西都是物件,物件是類的例項化表現

屬性:- 用來描述物件的特徵,例如張三的名字叫張三

方法:-代表的是每一個物件的一些行為,例如貓會吃飯

簡單的說:我們可以把類看成汽車的設計圖紙,根據圖紙可以生產出無數的汽車;我們也可以通過類產出無數物件,物件可以擁有類的一切資訊,而類不能有物件的一切資訊

思考:為什麼需要方法?

答:可以把簡單的程式碼抽離出來,以後使用的時候直接呼叫即可。

public int sum(int a, int b){return a+b ;}

訪問修飾符 返回的資料型別 方法名(引數列表){方法體,有返回值則return;}

  • 返回值型別:當方法執行完成後,想返回給呼叫者什麼資料就用什麼資料型別,如果方法執行完成後不需要返回值,就用void修飾,而如果有就用return把資料返回。

  • 方法名:自己定義,見名知意,符合命名規則

  • 引數列表:當別人呼叫方法時需要傳遞給方法的資料,需要什麼型別資料就定義什麼型別,型別後跟上變數名即可,中間用逗號隔開

    訪問修飾符的區別:
    public:完全公開,被其修飾代表任意包下的任意類都可以訪問
    protected:受保護的,只有同包及子類包可以訪問,同包直接訪問,不同包的子類必須通過子類物件訪問
    default:無訪問修飾符,只有本包可以訪問
    private:私有的,只有本類中可以訪問
  • 方法的種類:

    • 類(靜態)方法:public 後跟static就是類方法,類方法屬於類本身,可以直接通過類名.方法名();呼叫

    • 例項方法:[訪問修飾符] 返回值 方法名([引數型別 引數名...]) {方法的程式碼體}

    類方法與例項方法的區別:

    • 呼叫:類方法可以用類名直接呼叫,而例項方法必須先建立物件才可以呼叫

    • 例項物件可以呼叫類方法,但是類不能呼叫例項方法

    • 例項物件可以使用類變數,但是類方法不能使用例項變數

    Java記憶體劃分:

    • 棧記憶體:基本資料型別的值;引用資料型別的變數名(引用地址)

    • 堆記憶體:通過new關鍵字產生的物件

      方法區:儲存所有編譯過後的class資訊;類方法,類變數,例項方法的名字

  • 構造方法

    • 是給類建立物件的時候呼叫的方法,用於建立物件,無返回值,如果沒有在類中宣告構造方法,那麼 JDK 會給我們提供預設的構造方法
    • 注意:構造方法的方法名必須與類名一致;
    • 構造方法不能被static修飾;
    • 建立一個物件構造方法只執行一次;
    • 有參構造用來給物件初始化賦值;
    • 當我們顯示宣告構造方法時(有參或無參),系統都不會給我們提供預設構造了,所以我們在宣告有參構造後,要一起宣告無參。
  • 方法過載

    定義:在同類中,方法名一致,但是引數型別、引數個數不同叫方法過載,構造方法也可以被過載

    重點:方法過載時Java編譯時多型的一個重要體現,呼叫同樣的方法,但是傳遞的引數不同,執行不同的操作。

  • 程式碼塊

    • 普通程式碼塊,定義在方法中{},不加任何修飾符就是,一般較少使用,經常用於方法過長或變數名衝突
    • 構造塊,定義在類中{},不加任何修飾符,構造塊優先執行,每建立一個物件,構造塊就執行一次,可以執行一些簡單的邏輯操作,如列印日誌
    • 靜態塊,定義在類中,被static修飾,執行優於構造塊,但只執行一次,如果某些屬性需要在建立物件前處理,可以使用
    • 同步程式碼塊,synchronized修飾,只允許一個執行緒執行

執行順序:靜態塊 > 構造塊 > 構造方法

package com.alpari;

public class Hello {

    {
        System.out.println("構造塊");
    }

    static {
        System.out.println("靜態塊");
    }
    public static void main(String[] args) {

        new Hello();
        System.out.println("-------------");
        new Hello();
    }
}
// 輸出:
靜態塊
構造塊
-------------
構造塊

變數

變數的種類:

  • 類變數(全域性變數):直接宣告在類中,並被static修飾來制定這個變數屬於類,其作用域在整個類中,變數聲明後初始值為其資料型別的預設值

  • 例項變數(全域性變數):直接宣告在類中(不能再方法中),不需要static修飾,其作用域在類的所有例項方法中,初始值是其型別的預設值

    package com.alpari;
    
    public class Hello {
    
        static int b;//類變數
        int c;//例項變數
        final int A = 2;//常量
        public static void main(String[] args) {
            for (int i =0; i<10; i++) {
                int d = 3;//區域性變數
            }
        }
    }
    
  • 區域性變數:只要沒宣告在類中,例如:for迴圈中等,其作用域在定義變數的對應作用域,相同作用域內變數名不能重複,變數聲明後沒有初始值,所以必須賦值後才能使用

  • 常量:使用final修飾,常量的值不會修改,所以在建立時必須賦值,常量的命名規則所有字母全部大寫,多個單詞用_分割,可以被static修飾,也可以不用

類變數與例項變數的區別:

  • 一個被static修飾,一個沒有
  • 類變數可以通過類名直接呼叫,而例項變數必須通過物件名呼叫
  • 屬於類的變數只有一份存放在Java記憶體中的方法區,不能建立多個,而例項變數存放在Java記憶體的堆記憶體中,new多少個實力就有多少個對應變數
  • 類變數作用於整個類的所有方法中,而例項變數只能作用於例項方法

為什麼類變數可以作用於例項方法中,而例項變數不能作用於類方法中?

答:由於一個類的例項物件會擁有類的全部屬性和方法,所以例項方法中可以使用類屬性,由於例項是根據類生成的,先有的類才有例項物件,所以類方法無法使用例項變數

封裝

目的:保護內部定義結構的安全性

1、把一個類的屬性及行為抽象到一起,例如:Pig類應該只有Pig的屬性以及行為,不應該有Dog的

2、把類的屬性私有化private,提供公開的方法訪問,setXxx()與getXxx()

3、對於一些不想讓人呼叫的方法用private修飾

package com.alpari;

public class Dog {
    private String name; // 屬性私有化
    public int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

class Test{
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.age = 2; //public可以直接賦值
        dog.setName("22"); //private只能通過提供的方法賦值
    }
}
  • this關鍵字

    • 代表當前物件,如this.name 代表的是當前物件的name
    • 當呼叫的是哪個物件,this就代表哪一個物件
    • 我們可以使用this.屬性呼叫當前物件的屬性,this.方法呼叫方法
    • 我們可以使用this()呼叫無參構造,也可以通過this(引數)呼叫有參,但是使用this關鍵字呼叫構造方法的時候,構造方法必須放在第一行,構造方法不能迴圈呼叫
  • Java中值傳遞

    • 值傳遞

      • 基本資料型別傳遞及常量池String的傳遞

        當我們定義的基本資料型別的變數傳遞到方法中後,方法會自動給我們的變數建立一個副本,然後在方法中操作的是副本的值,而不會影響我們在外面宣告的變數值

        package com.alpari;
        
        public class Dog {
        
            public static void main(String[] args) {
                int a = 2;
                new Dog().sum(a);
                System.out.println("執行完sum------"+a);
            }
        
            public void sum (int a) {
                a ++;
                System.out.println("這是sum中的:"+a);
            }
        }
        //輸出:
        這是sum中的:3
        執行完sum------2
        
      • 引用資料型別的傳遞

        傳遞引用資料型別的時候如果把地址傳過去,name由於雙方地址指向的是同一堆記憶體,所以只要任意一方修改了資料,那麼另一方的資料會同時產生變化

    • Java中不存在引用傳遞

繼承

就是在已有的程式結構上繼續擴充新功能,一個類可以從現有的類派生類,這樣的過程叫繼承,新類被稱為子類,現有的類被稱為父類,子類將繼承父類的屬性及方法

  • 繼承關鍵字extends,我們可以把一些共有的屬性及行為抽取出來,然後子類繼承即可擁有;

  • 子類無法繼承父類的私有屬性及方法

  • 子類無法繼承父類的構造方法

  • Java中只支援單繼承

  • Java中所有的類都有父類,沒宣告預設是Object,叫做超類

    package com.alpari;
    // 父類
    public class Animal {
    
        public void eat(){
            System.out.println("這是父類的eat");
        }
        public void say(){
            System.out.println("這是父類的say");
        }
    }
    
    package com.alpari;
    
    public class Dog extends Animal {
    
        @Override // 代表重寫
        public void say() {
            System.out.println("不滿意父類的方法,自己的say");
        }
    
        public static void main(String[] args) {
            new Dog().eat();
            new Dog().say();
        }
    }
    // 輸出:
    這是父類的eat
    不滿意父類的方法,自己的say
    
  • 重寫

    子類可以繼承父類的方法,如果對父類的方法不滿意,可以重寫父類的方法,對其功能進行擴充套件,當子類重寫父類的方法,預設呼叫子類的方法實現

    注意點:

    • 訪問修飾符必須與父類相同或大於父類
    • 返回值型別必須相同
    • 方法名必須相同
    • 引數列表要求一致
    • 子類可以擁有自己的屬性及方法,子類重寫父類方法屬性後,可以使用super.方法名();呼叫父類被隱藏的方法
    • 在建立子類物件前,會先呼叫父類的無參構造建立父類物件,如果父類沒有無參構造,需要子類在構造方法中使用super(引數)呼叫父類的有參構造
    • 重寫是Java執行時的多型
  • final關鍵字

    修飾在變數上叫常量;修飾在上,代表這個類不能被繼承,如String類;修飾在方法上,代表這個方法不能被重寫

多型

多型就是指呼叫相同的方法,但根據傳入的引數不同,或順序不同執行不同的操作:傳送訊息給某個物件,讓物件自行決定採用哪種行為來響應訊息子類物件的引用賦值給了父類引用變數來實現動態方法的呼叫

形成多型的前提:繼承,實現,方法過載,方法重寫,向上轉型

基於過載:呼叫同樣的方法名,但是根據傳遞的引數不同卻執行不同的操作

基於重寫:通過java物件的向上自動轉型,把子類物件的例項引用賦值給父類的宣告,然後實現雖然我們宣告的是父類,但根據例項化子類的不同,呼叫相同的方法卻執行不同的操作

向上轉型:父類的引用指向了子類的實際物件,或子類的物件賦值給了父類的引用,自動完成

package com.alpari;

public class Dog extends Animal {

    @Override // 代表重寫
    public void say() {
        System.out.println("不滿意父類的方法,自己的say");
    }

    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.say();
        Animal dog2 = new Dog();
        dog2.say();
    }
}
// 輸出
不滿意父類的方法,自己的say
不滿意父類的方法,自己的say

向上轉型的用處:完成資料型別的統一

向上轉型的缺點:當我們子類向上轉型後,會失去子類特有的方法,如果想使用子類特有的方法,需要對其向下轉型

instanceof關鍵字

a instanceof b 來判斷a是否為b類的物件,是返回true,否返回false; 來判斷其是否屬於這個型別,如果是則進行向下轉型操作,否則不進行

抽象類

  • 抽象類實際上也是一個類,被abstract修飾

  • 抽象類可以擁有已實現及未實現方法,未實現方法需要被abstract修飾

  • 由於抽象類中肯定存在未實現的方法,所以抽象類不能例項化物件

  • 抽象類也是類,所以可以使用子類繼承抽象類,然後例項化子類物件賦值給父類的引用

    package com.alpari;
    // 父類抽象類
    public abstract class Animal {
    
        public abstract void eat(); // 只有一個抽象方法
    }
    
    ======================================
    
    package com.alpari;
    
    public class Dog extends Animal {
    
        @Override
        public void eat() {
    
            System.out.println("這是子類的eat方法");
        }
    }
    
    class Test{
        public static void main(String[] args) {
            Animal a = new Dog();
            a.eat();
        }
    }
    // 輸出:
    這是子類的eat方法
    
  • 抽象類雖然不能例項化物件,但是也有構造方法的存在,在例項化子類物件的同時也需要先呼叫父類的構造方法

  • 抽象類不能被final修飾,因為程式認為抽象類中肯定包含抽象方法,而抽象方法必須依靠子類去繼承實現,但如果一個類被final修飾的話,那這個類就不能被繼承

  • 抽象類可以擁有靜態方法,並且可以直接通過類名.去呼叫

    abstract class A{
        public static void eat(){
            System.out.println("吃吃吃");
        }
    }
    public class Test {
        public static void main(String[] args) {
            A.eat();
        }
    }
    
  • 外部抽象類不能被static修飾,內部抽象類可以被static修飾,繼承的時候使用外部類名.內部類名

    package com.alpari;
    
    abstract class Animal{
        public static void eat(){
            System.out.println("吃吃吃");
        }
        public abstract void  ha();
        static abstract class B{
            public abstract void  print();
        }
    }
    class C extends Animal.B{
        public void print() {
            System.out.println("C的print方法");
        }
    }
    class Test1 {
        public static void main(String[] args) {
            Animal.B a=new C();
            a.print();
        }
    }
    
  • 構造方法是整個構造過程的最後一步,所有的屬性設定都是在構造完成之後才會把值設定上去,在類的構造方法完成前,所有的屬性都只是其型別的預設值,因為執行子類構造方法之前需要執行父類構造方法,在父類構造方法中呼叫子類的方法時,由於還沒完成子類構造,所以num=0

    package com.alpari;
    
    abstract class Animal{
        public Animal(){         //2:執行父類構造
            this.print(); //3:呼叫print方法
        }
        public abstract void print();
    }
    class B extends Animal{
        private int num=100;
        public B(int num){   //5.num傳遞過來執行子類構造方法結束後num=50
            this.num=num;
        }
        public void print() { //4:呼叫覆寫後的方法
            System.out.println("num="+num);
        }
    }
    class Test1 {
        public static void main(String[] args) {
            //new B(50);    //1:執行構造
            new B(50).print();
        }
    }
    // 輸出
    num=0
    num=50
    
  • 抽象方法不能被final,static,private修飾,因為抽象方法需要被子類重寫,所以不能被final和private修飾,因為抽象方法沒有方法體,所以無法通過類名直接呼叫,所以不能被static修飾

  • 內部類呼叫外部類的屬性:外部類.this.屬性

  • 內部類的例項化物件的建立:外部類.內部類 物件 = new 外部類().new 內部類();

介面

一個統一規定雙方互相協調的規範,是在抽象類的基礎上更深層次的抽象

  • 介面的宣告:使用interface,public interface 介面名 { };

  • 主要為了解決java中單繼承侷限,用關鍵字implement來實現,先繼承後實現

  • 抽象類可以有普通方法,但介面中不可以有普通方法,JDK8可以用default修飾普通方法

    package com.alpari;
    
    public interface IDao {
    
        default void add(){
            System.out.println("---");
        }
    }
    
    class IDaoImpl implements IDao{
        public static void main(String[] args) {
            IDao dao = new IDaoImpl();
            dao.add();
        }
    }
    
  • 所有方法預設被public abstract修飾,所有常量預設被public static final修飾

異常

package com.alpari;

public class Hello {
    
    public static void main(String[] args) {

        try {
            int a = 1/0; 
        }catch (Exception e) {
            System.out.println("Exception異常");
        }catch (Throwable e) {
            System.out.println("Throwable異常"); // 最大的異常要在最後
        } finally {
            System.out.println("finally執行"); // 都會執行
        }
    }
}
  • 異常的處理流程:

    1、當程式執行時出現異常,會由JVM自動根據異常例項化一個對應的例項物件

    2、物件產生完,會判斷我們當前語句是否存在異常處理,若沒有則由JVM輸出異常自信心結束程式

    3、若存在異常處理,那麼由try來捕獲異常例項物件,然後由catch來匹配,如果catch到了,則由當前catch處理

    4、不管有沒有匹配到,都向後執行,如果存在finally則執行其中程式碼,執行完後根據前面catch到的來處理,如果成功捕獲,則繼續執行finally之後的程式碼,反之由JVM處理

  • return與finally誰先執行

    在執行的時候,是return語句先把返回值寫到記憶體中,然後停下來執行finally,等finally執行完畢,return再去執行後面一段

  • 什麼情況下finally不執行:

    1、在執行try之前return 則不會執行finally程式碼

    2、在try中呼叫System.exit();

    3、斷電宕機

  • Exception與RuntimeException的區別:

    1、Exception是RuntimeException的父類

    2、使用Exception定義的異常必須處理(編譯型異常),而使用RuntimeException定義的可以選擇性處理(執行時異常)

    • ArithmeticException:算數異常

    • NumberFormatException:資料轉換異常

    • ArrayIndexOutOfBoundsException:陣列索引越界異常

    • NullPointerException:空指標異常

  • Throw與Throws:

    都不解決異常,throw:手動拋異常;throws:在方法之上拋異常,寫在方法引數列表的括號後,加上需要丟擲的異常名即可

  • 自定義異常:

    如果需要編譯型異常就繼承Exception,如果要執行時異常就繼承RuntimeExcepion即可。