java面向對象基礎(上)

 面向對象基礎(上)

學習目標

  • 理解方法的概念和特點
  • 掌握靜態(tài)方法的聲明和調用
  • 知道形參與實參的關系
  • 理解方法參數傳遞機制的原理
  • 理解方法調用的入棧與出棧過程
  • 了解可變參數的聲明和使用
  • 掌握方法重載的概念和要求
  • 了解方法遞歸調用的概念
  • 理解類與對象的關系
  • 掌握用class聲明類的語法格式
  • 掌握用new創(chuàng)建對象的語法格式
  • 掌握靜態(tài)變量的聲明和使用
  • 掌握實例方法的聲明和調用
  • 掌握實例變量的聲明和使用
  • 掌握包的作用和創(chuàng)建
  • 知道靜態(tài)變量、實例變量、局部變量的區(qū)別

5.1 方法的基礎知識(重點掌握)

5.1.1 方法的概念

方法也叫函數,是一組代碼語句的封裝,從而實現代碼重用,從而減少冗余代碼,通常它是一個獨立功能的定義,方法是一個類中最基本的功能單元。

Math.random()的random()方法
Math.sqrt(x)的sqrt(x)方法
System.out.println(x)的println(x)方法
Scanner input = new Scanner(System.in);
input.nextInt()的nextInt()方法

5.1.2 方法的特點

  • (1)必須先聲明后使用

    類,變量,方法等都要先聲明后使用

  • (2)不調用不執(zhí)行,調用一次執(zhí)行一次。

5.1.3 如何聲明靜態(tài)方法

1、聲明方法的位置

聲明方法的位置==必須在類中方法外==,即不能在一個方法中直接定義另一個方法。

正確示例:

類{
    方法1(){
        
    }
    方法2(){
        
    }
}

錯誤示例:

類{
    方法1(){
        方法2(){  //位置錯誤
        
           }
    }
}

2、聲明方法的語法格式

【修飾符】 返回值類型 方法名(【形參列表 】)【throws 異常列表】{ 方法體的功能代碼 }

一個完整的方法 = 方法頭 + 方法體。

- 方法頭就是 【修飾符】 返回值類型 方法名(【形參列表 】)【throws 異常列表】,也稱為方法簽名,通常調用方法時只需要關注方法頭就可以,從方法頭可以看出這個方法的功能和調用格式。方法頭可能包含5個部分,但是有些部分是可能缺省的。

- 方法體就是方法被調用后要指定的代碼,也是完成方法功能的具體實現代碼,對于調用者來說,不了解方法體如何實現的,并不影響方法的使用。

3、方法每一個部分的含義

  • (1)**方法名(必選)**:給方法起一個名字,見名知意,能準確代表該方法功能的名字
  • (2)【**修飾符】(可選)**:會影響方法的調用方式,以及方法的可見性范圍等

    修飾符:可選的。方法的修飾符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等,后面會一一學習。其中根據是否有static,可以將方法分為靜態(tài)方法和非靜態(tài)方法。其中靜態(tài)方法又稱為類方法,非靜態(tài)方法又稱為實例方法。==接下來咱們先學習靜態(tài)方法==。

  • (3)**【throws 異常列表】(可選)**:這個部分在異常章節(jié)再講
  • (4)**(【參數列表】)(()必選,參數列表可選)**:表示完成方法體功能時需要“調用者”提供的數據列表

    - 無論是否有參數,()不能省略

    - 如果有參數,每一個參數都要指定數據類型和參數名,多個參數之間使用**逗號**分隔,例如:

    - 一個參數: (數據類型 參數名)

    - 二個參數: (數據類型1 參數1, 數據類型2 參數2)

    - 參數的類型可以是基本數據類型、引用數據類型

  • (5)**返回值類型(必選)**: 表示方法運行的結果的數據類型,方法執(zhí)行后將結果返回到調用者

    - 基本數據類型

    - 引用數據類型

    - 無返回值類型:void

  • (6)**{方法體}(必選)**:方法體必須有{}括起來,在{}中編寫完成方法功能的代碼,具有方法體的方法才能被調用執(zhí)行。

關于方法體中return語句的說明:

  • - return語句的作用是結束方法的執(zhí)行,并將方法的結果返回去
  • - 如果返回值類型不是void,方法體中必須保證一定有 return 返回值; 語句,并且要求該返回值結果的類型與聲明的返回值類型一致或兼容。
  • - 如果返回值類型為void時,方法體中可以沒有return語句,如果要用return語句提前結束方法的執(zhí)行,那么return后面不能跟返回值,直接寫return ; 就可以。
  • - return語句后面就不能再寫其他代碼了,否則會報錯:Unreachable code
package com.atguigu.test04.method;
/**
 * 方法定義案例演示
 */
public class MethodDefineDemo {
    /**
     * 無參無返回值方法的演示
     */
    public static void sayHello(){
        System.out.println("hello");
    }
    /**
     * 有參無返回值方法的演示
     * @param length int 第一個參數,表示矩形的長
     * @param width int 第二個參數,表示矩形的寬
     * @param sign char 第三個參數,表示填充矩形圖形的符號
     */
    public static void printRectangle(int length, int width, char sign){
        for (int i = 1; i <= length ; i++) {
            for(int j=1; j <= width; j++){
                System.out.print(sign);
            }
            System.out.println();
        }
    }
    /**
     * 無參有返回值方法的演示
     * @return
     */
    public static int getIntBetweenOneToHundred(){
        return (int)(Math.random()*100+1);
    }
    
    /**
     * 有參有返回值方法的演示
     * @param a int 第一個參數,要比較大小的整數之一
     * @param b int 第二個參數,要比較大小的整數之二
     * @return int 比較大小的兩個整數中較大者的值
     */
    public static int max(int a, int b){
        return a > b? a : b;
    }
}

5.1.4 如何調用靜態(tài)方法

1、本類中

【修飾符】 class 類名{
    【public】 static 返回值類型 靜態(tài)方法1(【形參列表】){
       ....
    }
    【public】 static 返回值類型 靜態(tài)方法2(【形參列表】){
        靜態(tài)方法1(【實參列表】);
    }
}

2、其他類中

【修飾符】 class A類名{
    【public】 static 返回值類型 靜態(tài)方法1(【形參列表】){
        B類名.靜態(tài)方法2(【實參列表】);
    }
}
【修飾符】 class B類名{
    【public】 static 返回值類型 靜態(tài)方法2(【形參列表】){
        ....
    }
}

例如:

package com.atguigu.test04.method;
/**
 * 方法調用案例演示
 */
public class MethodInvokeDemo {
    public static void main(String[] args) {
        System.out.println("-----------------------方法調用演示-------------------------");
        //調用MethodDefineDemo類中無參無返回值的方法sayHello
        MethodDefineDemo.sayHello();
        MethodDefineDemo.sayHello();
        MethodDefineDemo.sayHello();
        //調用一次,執(zhí)行一次,不調用不執(zhí)行
        System.out.println("------------------------------------------------");
        //調用MethodDefineDemo類中有參無返回值的方法printRectangle
        MethodDefineDemo.printRectangle(5,10,'@');
        System.out.println("------------------------------------------------");
        //調用MethodDefineDemo類中無參有返回值的方法getIntBetweenOneToHundred();
        MethodDefineDemo.getIntBetweenOneToHundred();//語法沒問題,就是結果丟失
        int num = MethodDefineDemo.getIntBetweenOneToHundred();
        System.out.println("num = " + num);
        System.out.println(MethodDefineDemo.getIntBetweenOneToHundred());
        //上面的代碼調用了getIntBetweenOneToHundred三次,這個方法執(zhí)行了三次
        System.out.println("------------------------------------------------");
        //調用MethodDefineDemo類中有參有返回值的方法max
        MethodDefineDemo.max(3,6);//語法沒問題,就是結果丟失
        
        int bigger = MethodDefineDemo.max(5,6);
        System.out.println("bigger = " + bigger);
        System.out.println("8,3中較大者是:" + MethodDefineDemo.max(8,9));
    }
}

5.1.5 方法調用內存分析

方法不調用不執(zhí)行,調用一次執(zhí)行一次,每次調用會在棧中有一個入棧動作,即給當前方法開辟一塊獨立的內存區(qū)域,用于存儲當前方法的局部變量的值,當方法執(zhí)行結束后,會釋放該內存,稱為出棧,如果方法有返回值,就會把結果返回調用處,如果沒有返回值,就直接結束,回到調用處繼續(xù)執(zhí)行下一條指令。

棧結構:先進后出,后進先出。

5.1.6 方法的參數傳遞機制

方法無論是否有參數,聲明方法和調用方法是==()都不能丟失==

* 形參(formal parameter):在定義方法時方法名后面括號中聲明的變量稱為形式參數(簡稱形參)即形參出現在方法定義時。

* 實參(actual parameter):調用方法時方法名后面括號中的使用的值/變量/表達式稱為實際參數(簡稱實參)即實參出現在方法調用時。

* 實參的作用就是給形參賦值。調用時,實參的個數、類型、順序順序要與形參列表一一對應。如果方法沒有形參,就不需要也不能傳實參。

方法的參數傳遞機制:實參給形參賦值,那么反過來形參會影響實參嗎?

* 方法的形參是基本數據類型時,形參值的改變不會影響實參;

* 方法的形參是引用數據類型時,形參地址值的改變不會影響實參,但是形參地址值里面的數據的改變會影響實參,例如,修改數組元素的值,或修改對象的屬性值。

* 注意:String、Integer等特殊類型容易錯

1、形參是基本數據類型

案例:編寫方法,交換兩個整型變量的值

public class PrimitiveTypeParam {
    public static void swap(int a, int b){//交換兩個形參的值
        int temp = a;
        a = b;
        b = temp;
    }
    public static void main(String[] args) {
        int x = 1;
        int y = 2;
        System.out.println("交換之前:x = " + x +",y = " + y);//1,2
        swap(x,y);//實參x,y是基本數據類型,給形參的是數據的“副本”,調用完之后,x與y的值不變
        System.out.println("交換之后:x = " + x +",y = " + y);//1,2
    }
}

2、形參是引用數據類型

public class ArrayTypeParam {
    public static void sort(int[] arr){//這里對arr數組進行排序,就相當于對nums數組進行排序
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length - i; j++) {
                if(arr[j] > arr[j+1]){
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
    public static void iterate(int[] arr){//輸出數組的元素,元素之間使用空格分隔,元素打印完之后換行
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]+" ");
        }
        System.out.println();
    }
    public static void main(String[] args) {
        int[] nums = {4,3,1,6,7};
        System.out.println("排序之前:");
        iterate(nums);//實參nums把數組的首地址給形參arr,這個調用相當于輸出nums數組的元素
        sort(nums);//對nums數組進行排序
        System.out.println("排序之后:");
        iterate(nums);//輸出nums數組的元素
        //上面的代碼,從頭到尾,堆中只有一個數組,沒有產生新數組,無論是排序還是遍歷輸出都是同一個數組
    }
}

5.1.7 返回值問題

一個方法最多只能有一個返回值。如果沒有值需要返回,則用void表示。如果要返回多個值,可以用數組(或其他容器,后面再講)將多個結果裝起來,然后返回數組。

1、void類型

如果被調用方法的返回值類型是void,表示沒有返回值。那么調用時不需要也不能接收和處理返回值結果,即方法調用表達式==只能==直接加;成為一個獨立語句。

2、非void類型

如果被調用方法的返回值類型不是void,表示有返回值。那么調用時可以選擇接收也可以選擇不接收返回值結果。

  • - 被調用方法有結果返回,但是調用時沒有接收和使用返回值,返回值就會丟失。即方法調用表達式直接加;成為一個獨立的語句。
  • - 被調用方法有結果返回,調用時可以接收和使用返回的值。返回的結果怎么用,取決于返回值的類型是什么。

    - 方法調用表達式的結果可以作為賦值表達式的值

    - 方法調用表達式的結果可以作為計算表達式的一個操作數

    - 方法調用表達式的結果可以作為另一次方法調用的實參

    - ......

package com.atguigu.test04.method;
public class MethodReturnValue {
    public static void main(String[] args) {
        //無返回值的都只能單獨加;成一個獨立語句
        //調用MethodDefineDemo類中無參無返回值的方法sayHello
        MethodDefineDemo.sayHello();
        //調用MethodDefineDemo類中有參無返回值的方法printRectangle
        MethodDefineDemo.printRectangle(5,10,'@');
        //有返回值的
        //(1)方法調用表達式可以作為賦值表達式的值
        int bigger = MethodDefineDemo.max(7,3);
        System.out.println("bigger = " + bigger);
        //(2)方法調用表達式可以作為計算表達式的一個操作數
        //隨機產生兩個[1,10]之間的整數,并求和
        int sum = MethodDefineDemo.getIntBetweenOneToHundred() + MethodDefineDemo.getIntBetweenOneToHundred();
        System.out.println("sum = " + sum);
        //(3)方法調用表達式可以作為另一次方法調用的實參
        int x = 4;
        int y = 5;
        int z = 2;
        int biggest = MethodDefineDemo.max(MethodDefineDemo.max(x,y),z);
        System.out.println("biggest = " + biggest);
        //(4)方法調用表達式直接加;成為一個獨立的語句,這種情況,返回值丟失
        MethodDefineDemo.getIntBetweenOneToHundred();
    }
}

(1)基本數據類型

返回基本數據類型的數據值;

package com.atguigu.method;
public class TestMethodReturnValue1 {
    public static void main(String[] args) {
        int sum = add(5, 3);
        System.out.println("sum = " + sum);
    }
    public static int add(int a, int b){
        return a + b;
    }
}

(2)引用數據類型

返回對象,本質上返回的是對象的首地址。

package com.atguigu.method;
public class TestMethodReturnValue2 {
    public static void main(String[] args) {
        int[] numbers = getNumbersInScope(5, 10, 100);
        for (int i = 0; i < numbers.length; i++) {
            System.out.println(numbers[i]+" ");
        }
    }
    public static int[] getNumbersInScope(int length,int start, int end){
        int[] arr = new int[length];
        for (int i = 0; i < arr.length; i++) {
            arr[i]  = (int)(Math.random()*(end-start)+start);
        }
        return arr;
    }
}

5.2 命令行參數(可選拓展)

給main方法傳遞的實參稱為命令行參數。

public class TestCommandParam{
    //形參:String[] args
    public static void main(String[] args){
        System.out.println(args);
        System.out.println(args.length);
        
        for(int i=0; i

命令行:

java TestCommandParam
java TestCommandParam 1 2 3
java TestCommandParam hello atguigu

IDEA工具:

  • (1)配置運行參數
image-20211228101828718.png
image-20211228102022216.png
  • (2)運行程序
image-20211228102059327.png

5.3 可變參數(簡單了解)

在**JDK1.5**之后,當定義一個方法時,形參的類型可以確定,但是形參的個數不確定,那么可以考慮使用可變參數。

1、可變參數的格式:

【修飾符】 返回值類型 方法名(【非可變參數部分的形參列表,】參數類型... 形參名){  }

2、可變參數的聲明:

  • (1)一個方法最多只能有一個可變參數
  • (2)如果一個方法包含可變參數,那么可變參數必須是形參列表的最后一個

3、可變參數的使用:

  • (1)在聲明它的方法中,可變參數當成數組使用
  • (2)調用帶可變參數的方法時:    

    - 非可變參數部分必須傳入對應類型和個數的實參;

       

    - 可變參數部分按照可變參數的規(guī)則傳入0~n個對應類型的實參或傳入1個對應類型的數組實參;

     

4、對比可變參數與數組類型的參數:

其實”數據類型...“約等于”數據類型[]“ ,只是”數據類型[]“ 這種定義,在調用時必須傳遞數組,而”數據類型...“更靈活,既可以傳遞數組,又可以直接傳遞數組的元素。

案例:求n個整數的和

public class VarParamTest {
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        System.out.println(add(arr));
        System.out.println(add(new int[]{1,2,3,4,5}));
        System.out.println(add(new int[]{1}));
//        System.out.println(add());//報錯,必須new一個數組
        System.out.println(add(new int[0]));//數組的長度為,表示沒有元素
        System.out.println(sum(1));
        System.out.println(sum(1,2,3));
        System.out.println(sum(1,2,3,4,5));
        System.out.println(sum());
        System.out.println(sum(new int[]{1,2,3,4,5}));
    }
    /*
    聲明一個方法,可以求任意個整數的和
     */
    public static int add(int[] arr){
        int result = 0;
        for (int i = 0; i < arr.length; i++) {
            result += arr[i];
        }
        return result;
    }
    public static int sum(int... arr){//把arr當成數組 int[]即可
        int result = 0;
        for (int i = 0; i < arr.length; i++) {
            result += arr[i];
        }
        return result;
    }
}

5.4 方法的重載(必知必會)

**方法重載**:指在同一個類中,允許存在一個以上的同名方法,只要它們的參數列表不同即可,與修飾符和返回值類型無關。

- 參數列表:數據類型個數不同,數據類型不同(按理來說數據類型順序不同也可以,但是很少見,也不推薦,邏輯上容易有歧義)。

- 重載方法調用:JVM通過方法的參數列表,調用匹配的方法。  

  • 先看是否有個數、類型最匹配的
  • 沒有最匹配的,再看個數和類型可以兼容的,找不到兼容的將會報錯

 

案例,用重載實現:

  • (1)定義方法求兩個整數的最大值
  • (2)定義方法求三個整數的最大值
  • (3)定義方法求兩個小數的最大值
  • (4)定義方法求n個整數最大值
public class MathTools {
    //求兩個整數的最大值
    public static int max(int a,int b){
        return a>b?a:b;
    }
    //求兩個小數的最大值
    public static double max(double a, double b){
        return a>b?a:b;
    }
    //求三個整數的最大值
    public static int max(int a, int b, int c){
        return max(max(a,b),c);
    }
    //求n整數的最大值
    public static int max(int[] nums){
        int max = nums[0];//如果沒有傳入整數,或者傳入null,這句代碼會報異常
        for (int i = 1; i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }
}

1、找最匹配的

package com.atguigu.test06.overload;
public class MethodOverloadMosthMatch {
    public static void main(String[] args) {
        System.out.println(MathTools.max(5,3));
        System.out.println(MathTools.max(5,3,8));
        System.out.println(MathTools.max(5.7,2.5));
        System.out.println(MathTools.max(new int[]{5,6,8,9,7,2}));
    }
}

2、找可以兼容的

public class MethodOverloadMostCompatible {
    public static void main(String[] args) {
        System.out.println(MathTools.max(5.7,9));
        System.out.println(MathTools.max(5,6,8,3));
//        System.out.println(MathTools.max(5.7,9.2,6.9)); //沒有兼容的
    }
}

3、多個方法可以匹配或兼容

package com.atguigu.test06.overload;
public class MathTools {
    //求兩個整數的最大值
    public static int max(int a,int b){
        return a>b?a:b;
    }
    //求兩個小數的最大值
    public static double max(double a, double b){
        return a>b?a:b;
    }
    //求三個整數的最大值
    public static int max(int a, int b, int c){
        return max(max(a,b),c);
    }
    //求n整數的最大值
    public static int max(int... nums){
        int max = nums[0];//如果沒有傳入整數,或者傳入null,這句代碼會報異常
        for (int i = 1; i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }
/*    //求n整數的最大值
    public static int max(int[] nums){  //編譯就報錯,與(int... nums)無法區(qū)分
        int max = nums[0];//如果沒有傳入整數,或者傳入null,這句代碼會報異常
        for (int i = 1, i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }*/
/*    //求n整數的最大值
    public static int max(int first, int... nums){  //當前類不報錯,但是調用時會引起多個方法同時匹配
        int max = first;
        for (int i = 0; i < nums.length; i++) {
            if(nums[i] > max){
                max = nums[i];
            }
        }
        return max;
    }*/

4、方法的重載和返回值類型無關

public class MathTools {
    public static int getOneToHundred(){
        return (int)(Math.random()*100);
    }
    
    public static double getOneToHundred(){
        return Math.random()*100;
    }
}
//以上方法不是重載

5.5 方法的遞歸調用(簡單了解)

**遞歸調用**:方法自己調用自己的現象就稱為遞歸。

**遞歸的分類:**

  • * 遞歸分為兩種,直接遞歸和間接遞歸。
  • * 直接遞歸稱為方法自身調用自己。
  • * 間接遞歸可以A方法調用B方法,B方法調用C方法,C方法調用A方法。

**注意事項**:

  • * 遞歸一定要有條件限定,保證遞歸能夠停止下來,否則會發(fā)生棧內存溢出。
  • * 在遞歸中雖然有限定條件,但是遞歸深度不能太深,否則效率低下,或者也會發(fā)生棧內存溢出。
  •   * 能夠使用循環(huán)代替的,盡量使用循環(huán)代替遞歸

案例:計算斐波那契數列(Fibonacci)的第n個值,斐波那契數列滿足如下規(guī)律,

1,1,2,3,5,8,13,21,....

即從第三個數開始,一個數等于前兩個數之和。假設f(n)代表斐波那契數列的第n個值,那么f(n)滿足:

f(n) = f(n-2) + f(n-1); 

package com.atguigu.test07.recursion;
public class FibonacciTest {
    public static void main(String[] args) {
        System.out.println(f(20));//6765
        System.out.println(fValue(20));//6765
        
        System.out.println("-----------------------------");
        
        for(int i=1; i<=10; i++){
            System.out.println("斐波那契數列第" +i +"個數:" + f(i));
        }
        System.out.println("-----------------------------");
        for(int i=1; i<=10; i++){
            System.out.println("斐波那契數列第" +i +"個數:" + fValue(i));
        }
        
    }
    //使用遞歸的寫法
   public static int f(int n){//計算斐波那契數列第n個值是多少
        if(n<1){//負數是返回特殊值1,表示不計算負數情況
            return 1;
        }
        if(n==1 || n==2){
            return 1;
        }
        return f(n-2) + f(n-1);
    }
    //不用遞歸
    public static int fValue(int n){//計算斐波那契數列第n個值是多少
        if(n<1){//負數是返回特殊值1,表示不計算負數情況
            return 1;
        }
        if(n==1 || n==2){
            return 1;
        }
        //從第三個數開始,  等于 前兩個整數相加
        int beforeBefore = 1; //相當于n=1時的值
        int before = 1;//相當于n=2時的值
        int current = beforeBefore + before; //相當于n=3的值
        //再完后
        for(int i=4; i<=n; i++){
            beforeBefore = before;
            before = current;
            current = beforeBefore + before;
            /*
            假設i=4
                beforeBefore = before; //相當于n=2時的值
                before = current; //相當于n=3的值
                current = beforeBefore + before; //相當于n = 4的值
            假設i=5
                beforeBefore = before; //相當于n=3的值
                before = current; //相當于n = 4的值
                current = beforeBefore + before; //相當于n = 5的值
               ....
             */
        }
        return current;
    }
}

5.6 面向對象的概念

5.6.1 面向對象編程思想概述(簡單了解)

1、C語言和Java語言

C語言是一種面向過程的程序設計語言,因為C語言是在面向過程思想的指引下去設計、開發(fā)計算機程序的。

Java語言是一種面向對象的程序設計語言,因為Java語言是在面向對象思想的指引下去設計、開發(fā)計算機程序的。

其中面向對象和面向過程都是一種編程思想,基于不同的思想會產生不同的程序設計方法。

  • 1. 面向過程的程序設計思想(Process-Oriented Programming),簡稱POP    

    - 關注的焦點是過程:過程就是操作數據的步驟。面向過程是分析出解決問題所需要的步驟,不同的步驟可以抽象為一個一個的函數。

       

    - 代碼結構:以函數為組織單位。獨立于函數之外的數據稱為全局數據,在函數內部的稱為局部數據。

     

  • 2. 面向對象的程序設計思想( Object Oriented Programming),簡稱OOP    

    - 關注的焦點是類和對象:面向對象思想就是在計算機程序設計過程中,參照現實中事物,將事物的屬性特征、行為特征抽象出來,用類來表示。某個事物的一個具體個體稱為實例或對象。面向對象是把構成問題事務分解成各個對象,關注的是解決問題需要哪些對象。

       

    - 代碼結構:以類為組織單位。每種事物都具備自己的**屬性**(即表示和存儲數據,在類中用成員變量表示)和**行為/功能**(即操作數據,在類中用成員方法表示)。

     

2、面向過程與面向對象的區(qū)別

面向過程:

  • - 優(yōu)點是性能比面向對象高,因為類調用時需要實例化,開銷比較大,比較消耗資源。而系統(tǒng)軟件(例如各種操作系統(tǒng))等一般采用面向過程開發(fā),性能是最重要的因素。
  • - 缺點是沒有面向對象易維護,易復用,易擴展??删S護性差,不易修改。

面向對象:

  • - 優(yōu)點是易維護,易復用,易擴展。由于面向對象由封裝,繼承,多態(tài)性的特性,可以設計出耦合度低的系統(tǒng),使系統(tǒng)更加靈活,更加易于維護。 
  • - 缺點是性能比面向過程低。

舉例說明:

3 故事:非活字印刷與活字印刷

故事:略 《短歌行》曹操 對酒當歌,人生幾何! 譬如朝露,去日苦多。 慨當以慷,憂思難忘。 何以解憂?唯有杜康。 青青子衿,悠悠我心。 但為君故,沉吟至今。 呦呦鹿鳴,食野之蘋。 我有嘉賓,鼓瑟吹笙。 明明如月,何時可掇? 憂從中來,不可斷絕。 越陌度阡,枉用相存。 契闊談讌,心念舊恩。 月明星稀,烏鵲南飛。 繞樹三匝,何枝可依? 山不厭高,海不厭深。 周公吐哺,天下歸心。 如果是有了活字印刷,則只需更改幾個字就可,其余工作都未白做。 一、要改,只需更改要改之字,此為可維護; 二、這些字并非用完這次就無用,完全可以在后來的印刷中重復使用,此乃可復用; 三、此詩若要加字,只需另刻字加入即可,這是可擴展; 四、字的排列其實可能是豎排可能是橫排,此時只需將活字移動就可做到滿足排列需求,此是靈活性好?!?在活字印刷術出現之前,上面的四種特性都無法滿足,要修改,必須重刻,要加字,必須重刻,要重新排列,必須重刻,印完這本書后,此版已無任何可再利用價值。

5.6.2 類和對象的概念和關系(必知必會)

1、什么是類

**類**是一類具有相同特性的事物的抽象描述,是一組相關**屬性**和**行為**的集合。

  • * **屬性**:就是該事物的狀態(tài)信息。
  • * **行為**:就是在你這個程序中,該狀態(tài)信息要做什么操作,或者基于事物的狀態(tài)能做什么。

2、什么是對象

**對象**是一類事物的一個具體個體(對象并不是找個女朋友)。即對象是類的一個**實例**,必然具備該類事物的屬性和行為。

3、類與對象的關系

  • - 類是對一類事物的描述,是**抽象的**。
  • - 對象是一類事物的實例,是**具體的**。
  • - **類是對象的模板,對象是類的實體**。
1.jpg

5.6.3 如何定義類和創(chuàng)建對象(重點掌握)

1、類的定義格式

關鍵字:class(小寫)

【修飾符】 class 類名{ }

類的定義格式舉例:

public class Student{
    
}

2、對象的創(chuàng)建

關鍵字:new

new 類名()//也稱為匿名對象 //給創(chuàng)建的對象命名 //或者說,把創(chuàng)建的對象用一個引用數據類型的變量保存起來,這樣就可以反復使用這個對象了 類名 對象名 = new 類名();

那么,對象名中存儲的是什么呢?答:對象地址

public class TestStudent{
    public static void main(String[] args){
        System.out.println(new Student());//Student@7852e922
        Student stu = new Student();
        System.out.println(stu);//Student@4e25154f
        
        int[] arr = new int[5];
        System.out.println(arr);//[I@70dea4e
    }
}

發(fā)現學生對象和數組對象類似,直接打印對象名和數組名都是顯示“類型@對象的hashCode值",所以說類、數組都是引用數據類型,引用數據類型的變量中存儲的是對象的地址,或者說指向堆中對象的首地址。

那么像“Student@4e25154f”是對象的地址嗎?不是,因為Java是對程序員隱藏內存地址的,不暴露內存地址信息,所以打印對象時不直接顯示內存地址,而是JVM幫你調用了對象的toString方法,將對象的基本信息轉換為字符串并返回,默認toString方法返回的是“對象的運行時類型@對象的hashCode值的十六進制值”,程序員可以自己改寫toString方法的代碼(后面會講如何改寫)。

5.7 Package包

5.7.1 包的作用(簡單了解)

  • (1)可以避免類重名:有了包之后,類的全名稱就變?yōu)椋喊?類名
  • (2)可以控制某些類型或成員的可見范圍    

    如果某個類型或者成員的權限修飾缺省的話,那么就僅限于本包使用。

     

  • (3)分類組織管理眾多的類

例如:

  • * java.lang----包含一些Java語言的核心類,如String、Math、Integer、 System和Thread等,提供常用功能
  • * java.net----包含執(zhí)行與網絡相關的操作的類和接口。
  • * java.io ----包含能提供多種輸入/輸出功能的類。
  • * java.util----包含一些實用工具類,如集合框架類、日期時間、數組工具類Arrays,文本掃描儀Scanner,隨機值產生工具Random。
  • * java.text----包含了一些java格式化相關的類
  • * java.sql和javax.sql----包含了java進行JDBC數據庫編程的相關類/接口
  • * java.awt和java.swing----包含了構成抽象窗口工具集(abstract window toolkits)的多個類,這些類被用來構建和管理應用程序的圖形用戶界面(GUI)。

5.7.2 如何聲明包(重點掌握)

關鍵字:package

package 包名;

注意:

  • (1)必須在源文件的代碼首行
  • (2)一個源文件只能有一個聲明包的package語句

包的命名規(guī)范和習慣:

  • (1)所有單詞都小寫,每一個單詞之間使用.分割
  • (2)習慣用公司的域名倒置開頭
  • (3)不能以"java."開頭

例如:com.atguigu.xxx;

5.7.3 如何跨包使用類(重點掌握)

注意:只有public的類才能被跨包使用

  • (1)使用類型的全名稱    

    例如:java.util.Scanner input = new java.util.Scanner(System.in);

     

  • (2)使用import 語句之后,代碼中使用簡名稱    

    import語句告訴編譯器到哪里去尋找類。

       

    import語句的語法格式:

       

    import 包.類名; import 包.*;    

       

    注意:

       

    • 使用java.lang包下的類,不需要import語句,就直接可以使用簡名稱
    • import語句必須在package下面,class的上面
    • 當使用兩個不同包的同名類時,例如:java.util.Date和java.sql.Date。一個使用全名稱,一個使用簡名稱

     

package com.atguigu.test02.pkg;
import com.atguigu.test01.oop.Student;
import java.util.Date;
import java.util.Scanner;
public class TestPackage {
    public static void main(String[] args) {
/*        java.util.Scanner input = new java.util.Scanner(System.in);
        com.atguigu.test01.oop.Student stu = new com.atguigu.test01.oop.Student();*/
        Scanner input = new Scanner(System.in);
        Student student = new Student();
        Date d1 = new Date();
        java.sql.Date d2 = new java.sql.Date(0);
    }
}

5.7.4 靜態(tài)導入(簡單了解)

如果大量使用另一個類的靜態(tài)成員,可以使用靜態(tài)導入,簡化代碼。

import static 包.類名.靜態(tài)成員名; import static 包.類名.*;

演示:

package com.atguigu.keyword;
import static java.lang.Math.*;
public class TestStaticImport {
    public static void main(String[] args) {
        //使用Math類的靜態(tài)成員
        System.out.println(Math.PI);
        System.out.println(Math.sqrt(9));
        System.out.println(Math.random());
        System.out.println("----------------------------");
        System.out.println(PI);
        System.out.println(sqrt(9));
        System.out.println(random());
    }
}

5.8 類的成員之一:成員變量(重點掌握)

5.8.1 成員變量的分類

Java類的成員變量分為兩大類:

  • - 靜態(tài)變量(加staitc修飾),又稱為類變量
  • - 非靜態(tài)變量(不加static修飾),又稱為實例變量或者屬性。

5.8.2 成員變量之實例變量

1、實例變量的聲明格式

【修飾符】 class 類名{    【修飾符】 數據類型  成員變量名; }

位置要求:必須在類中,方法外

類型要求:可以是Java的任意類型,包括基本數據類型、引用數據類型(類、接口、數組等)

修飾符:暫時寫public。

public class Person{
    public String name;
    public char gender;
    public int age;
}

2、實例變量的特點

  • (1)實例變量的值是屬于某個對象的    

    - 在類的外面必須通過對象才能訪問實例變量

       

    - 每個對象的實例變量的值是獨立的

     

  • (2)實例變量有默認值
分類數據類型默認值
基本類型整數(byte,short,int,long)0
 浮點數(float,double)0.0
 字符(char)'\u0000'
 布爾(boolean)false
引用類型數組,類,接口null

3、實例變量的訪問

對象.實例變量

例如:

package com.atguigu.test03.field;
public class TestPerson {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "張三";
        p1.age = 23;
        p1.gender = '男';
        Person p2 = new Person();
        /*
        (1)實例變量的值是屬于某個對象的
        - 必須通過對象才能訪問實例變量
        - 每個對象的實例變量的值是獨立的
        (2)實例變量有默認值
         */
        System.out.println("p1對象的實例變量:");
        System.out.println("p1.name = " + p1.name);
        System.out.println("p1.age = " + p1.age);
        System.out.println("p1.gender = " + p1.gender);
        System.out.println("p2對象的實例變量:");
        System.out.println("p2.name = " + p2.name);
        System.out.println("p2.age = " + p2.age);
        System.out.println("p2.gender = " + p2.gender);
    }
}

4、實例變量的內存分析

內存是計算機中重要的部件之一,它是與CPU進行溝通的橋梁。其作用是用于暫時存放CPU中的運算數據,以及與硬盤等外部存儲器交換的數據。只要計算機在運行中,CPU就會把需要運算的數據調到內存中進行運算,當運算完成后CPU再將結果傳送出來。我們編寫的程序是存放在硬盤中的,在硬盤中的程序是不會運行的,必須放進內存中才能運行,運行完畢后會清空內存。Java虛擬機要運行程序,必須要對內存進行空間的分配和管理,每一片區(qū)域都有特定的處理數據方式和內存管理方式。

JVM的運行時內存區(qū)域分為:方法區(qū)、堆、虛擬機棧、本地方法棧、程序計數器幾大塊。
 

區(qū)域名稱作用
程序計數器程序計數器是CPU中的寄存器,它包含每一個線程下一條要執(zhí)行的指令的地址
本地方法棧當程序中調用了native的本地方法時,本地方法執(zhí)行期間的內存區(qū)域
方法區(qū)存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數據。
堆內存存儲對象(包括數組對象),new來創(chuàng)建的,都存儲在堆內存。
虛擬機棧用于存儲正在執(zhí)行的每個Java方法的局部變量表等。局部變量表存放了編譯期可知長度的各種基本數據類型、對象引用,方法執(zhí)行完,自動釋放。

Java對象保存在內存中時,由以下三部分組成:

  • - 對象頭    

      - Mark Word:記錄了和當前對象有關的GC、鎖等信息。(Java高級再詳細講)

     

      - 指向類的指針:每一個對象需要記錄它是由哪個類創(chuàng)建出來的,而Java對象的類數據保存在方法區(qū),指向類的指針就是記錄創(chuàng)建該對象的類數據在方法區(qū)的首地址。該指針在32位JVM中的長度是32bit,在64位JVM中長度是64bit。

     

      - 數組長度(只有數組對象才有)

     

  • - 實例數據    

      - 即實例變量的值

     

  • - 對齊填充    

      - 因為JVM要求Java對象占的內存大小應該是8bit的倍數,如果不滿足該大小,則需要補齊至8bit的倍數,沒有特別的功能。
     

5.8.3 成員變量之靜態(tài)變量

1、靜態(tài)變量聲明格式

有static修飾的成員變量就是靜態(tài)變量。

【修飾符】 class 類{    【其他修飾劑】 static 數據類型  靜態(tài)變量名; }

public class Chinese{
    public static String country; //靜態(tài)變量
    public String name; //實例變量
}

2、靜態(tài)變量的特點

  • - 靜態(tài)變量的默認值規(guī)則和實例變量一樣。
  • - 靜態(tài)變量值是所有對象共享。

3、靜態(tài)變量的訪問

在其他類中可以通過“類名.靜態(tài)變量”直接訪問,也可以通過“對象.靜態(tài)變量”的方式訪問(但是更推薦使用類名.靜態(tài)變量的方式)。

類名.靜態(tài)變量

public class TestChinese{
    public static void main(String[] args){
        Chinese.country = "中國";
        
        Chinese c1 = new Chinese();
        c1.name = "張三";
        
        Chinese c2 = new Chinese();
        
        System.out.println(Chinese.country);
        System.out.println(c1.country + "," + c1.name);
        System.out.println(c2.country + "," + c2.name);
        System.out.println("=======================");
        
        c1.country = "china";
        System.out.println(Chinese.country);
        System.out.println(c1.country + "," + c1.name);
        System.out.println(c2.country + "," + c2.name);
    }
}

4、靜態(tài)變量內存分析

- 靜態(tài)變量的值存儲在方法區(qū)。

具體內存分析圖請看PPT。

5.8.4 成員變量是引用數據類型

成員變量也是變量,數據類型可以是8種基本數據類型,也可以是引用數據類型(數組、類等)。如果是引用數據類型,請警惕空指針異常。

class Husband{
    String name;
    Wife wife;
}
class Wife{
    String name;
    Husband husband;
}
class TestMarry{
    public static void main(String[] args){
        Husband h = new Husband();
        h.name = "張三";
        Wife w = new Wife();
        w.name = "翠花";
        System.out.println("丈夫的姓名:" + h.name + ",妻子:" + h.wife);
         h.wife = w;
        System.out.println("丈夫的姓名:" + h.name + ",妻子:" + h.wife.name);//警惕空指針異常
        System.out.println("妻子的姓名:" + w.name + ",丈夫:" + w.husband);
        w.husband = h;
        System.out.println("妻子的姓名:" + w.name + ",丈夫:" + w.husband.name);//警惕空指針異常
        System.out.println("---------------------------------------");
        //離婚
        h.wife = null;
        w.husband = null;
        h.wife = new Wife();
        h.wife.name = "小何";
        h.wife.husband = h;
        System.out.println("丈夫的姓名:" + h.name + ",妻子:" + h.wife.name);
        System.out.println("妻子的姓名:" + h.wife.name + ",丈夫:" + h.wife.husband.name);
    }
}

總結:

編譯時:一個引用數據類型的變量,可以.出什么,和這個變量的類型有關,這個類型中有什么成員,就可以.出什么成員。

運行時:.操作是不是會發(fā)生空指針異常,要看.前面的變量有沒有“引用”一個對象,即有沒有給它賦值一個對象。

5.9 類的成員之二:成員方法(重點掌握)

5.9.1 類的靜態(tài)方法

靜態(tài)方法不依賴于對象。

1、在靜態(tài)方法中直接使用本類的靜態(tài)變量

當靜態(tài)變量與局部變量同名時,可以用“類名.靜態(tài)變量”進行區(qū)分。

public class Demo1{
    public static int a;
    public static int b;
    
    public static void main(String[] args){
        System.out.println("a = " + a);
        
        int b = 1;
        System.out.println("b = " + b);
        System.out.println("Demo1.b = " + Demo1.b);
    }
}

2、在靜態(tài)方法中直接調用本類的靜態(tài)方法

public class Demo2{
    public static void say(){
        System.out.println("hello");
    }
    
    public static void main(String[] args){
        say();
    }
}

3、在其他類中調用靜態(tài)方法

public class Demo3{
    public static void show(){
        System.out.println("hello");
    }
}
public class Demo3Test{ 
    public static void main(String[] args){
        Demo3.show();
    }
}

5.9.2 對象的實例方法

1、在實例方法中直接使用當前對象的實例變量

實例方法依賴于對象,因為實例方法必須由對象來調用,那么調用當前方法的對象稱為當前對象,在實例方法中使用this表示。

如果當前實例方法中沒有局部變量與實例變量重名,也可以省略this.,如果有局部變量與實例變量重名,就必須加this.。

package com.atguigu.exer3;
//(1)聲明一個MyDate類型,有屬性:年,月,日
public class MyDate {
    public int year;
    public int month;
    public int day;
    public void setValue(int year, int month,int day){
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public String getInfo(){
        return year+"年" + month+"月" + day+"日";
    }
}

2、在實例方法中直接使用本類的靜態(tài)變量

public class Chinese{
    public static String country; //靜態(tài)變量
    public String name; //實例變量
    
    public String getInfo(){
        return "country = " + country +",name = " + name;
    }
}

靜態(tài)變量,又稱為類變量,是當前所有類的對象共享的。

3、在實例方法中直接調用本類的靜態(tài)方法

public class Chinese{
    public static String country; //靜態(tài)變量
    public String name; //實例變量
    
    public static void sayCountry(){
        System.out.println("我是一個中國人");
    }
    
    public void say(){
        sayCountry();
        System.out.println("我的名字是:" + name);
    }
}

4、在實例方法中直接調用本類的實例方法

public class Chinese{
    public static String country; //靜態(tài)變量
    public String name; //實例變量
    
    public static void sayCountry(){
        System.out.println("我是一個中國人");
    }
    
    public void sayName(){
        System.out.println("我的名字是:" + name);
    } 
    
    public void say(){
        sayCountry();
        sayName();
    }
}

5、其他類中調用實例方法

當出現某個類的多個對象都要進行相同操作時,這些操作的重復性代碼,就可以封裝為實例方法。

在其他類中調用實例方法:

對象名.實例方法(【實參列表】)

例如:

//1、創(chuàng)建Scanner的對象
Scanner input = new Scanner(System.in);//System.in默認代表鍵盤輸入
//2、提示輸入xx
System.out.print("請輸入一個整數:"); //對象.非靜態(tài)方法(實參列表)
//3、接收輸入內容
int num = input.nextInt();  //對象.非靜態(tài)方法()

案例演示:

package com.atguigu.exer3;
public class MyDate {
    public int year;
    public int month;
    public int day;
    public void setValue(int year, int month,int day){
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public String getInfo(){
        return year+"年" + month+"月" + day+"日";
    }
}
package com.atguigu.exer3;
public class Employee {
    public String name;
    public MyDate birthday;
    public void setBirthday(int year, int month, int day){
        birthday = new MyDate();
//        birthday.year = year;
//        birthday.month = month;
//        birthday.day = day;
        birthday.setValue(year, month, day);
    }
    public String getInfo(){
//        return "姓名:" + name +",生日:" + birthday.year+"年" + birthday.month+"月" + birthday.day+"日";
        return "姓名:" + name +",生日:" + birthday.getInfo();
    }
}
public class EmployeeTest {
    public static void main(String[] args) {
        Employee e1 = new Employee();
        e1.name = "張三";
//        e1.birthday = new MyDate();
//        e1.birthday.year = 1990;
//        e1.birthday.month = 5;
//        e1.birthday.day = 1;
        e1.setBirthday(1990,5,1);
//        System.out.println("e1的姓名:" + e1.name +",生日:" + e1.birthday.year+"年" + e1.birthday.month+"月" + e1.birthday.day+"日");
        System.out.println("e1的" + e1.getInfo());
        Employee e2 = new Employee();
        e2.name = "李四";
//        e2.birthday = new MyDate();
//        e2.birthday.year = 1995;
//        e2.birthday.month = 6;
//        e2.birthday.day = 1;
        e2.setBirthday(1996,6,1);
//        System.out.println("e2的姓名:" + e2.name +",生日:" + e2.birthday.year+"年" + e2.birthday.month+"月" + e2.birthday.day+"日");
        System.out.println("e2的" + e2.getInfo());
    }
}

6、實例方法的內存分析

實例方法是由實例對象調用的,每一個實例方法中默認有一個this變量用來記錄當前對象(即調用該方法的實例對象)的首地址。

調用過程分析請看PPT。

5.9.3 成員互訪原則

  靜態(tài)變量實例變量靜態(tài)方法實例方法
本類中在靜態(tài)方法中直接使用×直接使用×
 在實例方法中直接使用直接使用直接使用直接使用
在其他類中 類名.靜態(tài)變量(推薦)對象名.實例變量類名.靜態(tài)方法(推薦)對象名.實例方法
  對象名.靜態(tài)變量(不推薦) 對象名.靜態(tài)方法(不推薦) 

5.9.4 各種變量小結

- 靜態(tài)類變量(簡稱靜態(tài)變量):存儲在方法區(qū),有默認值,所有對象共享,生命周期和類相同,還可以有權限修飾符等其他修飾符

- 非靜態(tài)實例變量(簡稱實例變量):存儲在堆中,有默認值,每一個對象獨立,生命周期每一個對象也獨立,還可以有權限修飾符等其他修飾符

- 局部變量:存儲在棧中,沒有默認值,每一次方法調用都是獨立的,有作用域,不能加權限修飾符

請看PPT。

5.10 構造器(重點掌握)

我們發(fā)現我們new完對象時,所有成員變量都是默認值,如果我們需要賦別的值,需要挨個為它們再賦值,太麻煩了。我們能不能在new對象時,直接為當前對象的某個或所有成員變量直接賦值呢。

可以,Java給我們提供了構造器(Constructor)。

5.10.1 構造器的作用

new對象,并在new對象的時候為實例變量賦值。

5.10.2 聲明構造器

構造器又稱為構造方法,構造函數,那是因為它長的很像方法。但是和方法還是有所區(qū)別的。

1、構造器語法格式和要求

【修飾符】 class 類名{    【修飾符】 構造器名(){        // 實例初始化代碼    }    【修飾符】 構造器名(參數列表){        // 實例初始化代碼    } }

注意事項:

  • 1. 構造器名必須與它所在的類名必須相同。
  • 2. 它沒有返回值,所以不需要返回值類型,甚至不需要void
  • 3. 如果你不提供構造器,系統(tǒng)會給出無參數構造器,并且該構造器的修飾符默認與類的修飾符相同
  • 4. 如果你提供了構造器,系統(tǒng)將不再提供無參數構造器,除非你自己定義。
  • 5. 構造器是可以重載的,既可以定義參數,也可以不定義參數。
  • 6. 構造器的修飾符只能是權限修飾符,不能被其他任何修飾
package com.atguigu.constructor;
public class Student {
    public String name;
    public int age;
    // 無參構造
    public Student() {}
    // 有參構造
    public Student(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }@Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2、構造器的調用時機

當使用new關鍵字創(chuàng)建對象時,就會自動調用對應的構造器來完成對象的初始化工作。例如:

public class TestStudent {
    public static void main(String[] args) {
        // 調用無參構造器創(chuàng)建對象
        Student s1 = new Student();
        s1.name = "張三";
        s1.age = 20;
        // 調用有參構造器創(chuàng)建對象
        Student s2 = new Student("李四", 22);
        System.out.println(s1);
        System.out.println(s2);
    }
}

5.10.3 構造器的重載

與方法重載類似,在同一個類中可以定義多個構造器,只要它們的參數列表不同即可,目的是可以通過不同的參數組合來創(chuàng)建對象并進行不同的初始化操作。比如:

public class Person {
    private String name;
    private int age;
    private String address;
    // 無參構造器
    public Person() {}
    // 帶有姓名和年齡參數的構造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 帶有姓名、年齡和地址參數的構造器
    public Person(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

調用示例:

public class TestPerson {
    public static void main(String[] args) {
        // 使用無參構造器創(chuàng)建對象
        Person p1 = new Person();
        // 使用帶有姓名和年齡參數的構造器創(chuàng)建對象
        Person p2 = new Person("王五", 30);
        // 使用帶有姓名、年齡和地址參數的構造器創(chuàng)建對象
        Person p3 = new Person("趙六", 35, "北京");
        // 后續(xù)可以對這些對象進行相應操作
    }
}

5.10.4 構造器的執(zhí)行順序(當存在繼承關系時)

如果類之間存在繼承關系,那么在創(chuàng)建子類對象時,構造器的調用順序是先調用父類的構造器(默認調用無參構造器,如果父類沒有無參構造器,需要在子類構造器中通過super關鍵字顯式調用父類有參構造器),再調用子類的構造器。例如:

class Animal {
    public Animal() {
        System.out.println("父類Animal的構造器被調用");
    }
}
class Dog extends Animal {
    public Dog() {
        System.out.println("子類Dog的構造器被調用");
    }
}

測試代碼:

public class TestConstructorOrder {
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

運行結果會先輸出“父類Animal的構造器被調用”,再輸出“子類Dog的構造器被調用”,這體現了在對象初始化時,先完成父類部分的初始化,再進行子類部分的初始化。

5.10.5 用構造器進行初始化的優(yōu)勢

相較于手動一個個給對象的成員變量賦值,使用構造器進行初始化有以下好處:

  • 1. 代碼更加簡潔和規(guī)范,集中在構造器中完成對象初始值的設置,便于代碼的閱讀和維護。例如,當創(chuàng)建一個復雜對象,有多個成員變量需要賦值時,通過構造器可以清晰地看到初始化邏輯。
  • 2. 可以保證對象在創(chuàng)建出來后處于一個合理的初始狀態(tài),避免因忘記給某些關鍵成員變量賦值而導致后續(xù)程序出現錯誤。比如,一個表示學生的類,通過構造器可以確保每個學生對象創(chuàng)建時,姓名、學號等重要信息都有初始值。
  • 3. 利用構造器的重載,可以靈活地根據不同場景創(chuàng)建具有不同初始值的對象,滿足多樣化的業(yè)務需求。

5.11 代碼塊(可選拓展)

5.11.1 代碼塊的分類

在Java中代碼塊主要分為以下幾種類型:

  • 1. **靜態(tài)代碼塊**:用static關鍵字修飾的代碼塊,在類加載的時候執(zhí)行,且只執(zhí)行一次,通常用于初始化靜態(tài)變量等操作。例如:
public class StaticBlockDemo {
    static int num;
    static {
        num = 10;
        System.out.println("靜態(tài)代碼塊執(zhí)行,初始化靜態(tài)變量num的值為" + num);
    }
}
  • 2. **實例代碼塊**:也叫非靜態(tài)代碼塊,沒有static關鍵字修飾,每次創(chuàng)建對象時都會執(zhí)行,在構造器之前執(zhí)行,常用于初始化實例變量等操作。例如:
public class InstanceBlockDemo {
    private int count;
    {
        count = 5;
        System.out.println("實例代碼塊執(zhí)行,初始化實例變量count的值為" + count);
    }
    public InstanceBlockDemo() {
        System.out.println("構造器執(zhí)行");
    }
}
  • 3. **局部代碼塊**:定義在方法內部的代碼塊,用于限定變量的作用域,當代碼塊執(zhí)行結束后,其中定義的變量就會被銷毀,可節(jié)省內存空間等。例如:
public class LocalBlockDemo {
    public void testMethod() {
        {
            int localVar = 20;
            System.out.println("局部代碼塊內的變量localVar的值為" + localVar);
        }
        // 此處如果再訪問localVar會報錯,因為超出了其作用域
    }
}

5.11.2 代碼塊的執(zhí)行順序(當存在多種代碼塊及繼承關系時)

當一個類中既有靜態(tài)代碼塊,又有實例代碼塊,還有構造器,并且類之間存在繼承關系時,執(zhí)行順序如下:

  1. 1. 首先執(zhí)行父類的靜態(tài)代碼塊(只執(zhí)行一次,在類第一次被加載時執(zhí)行)。
  2. 2. 接著執(zhí)行子類的靜態(tài)代碼塊(同樣只執(zhí)行一次,在子類類加載時執(zhí)行)。
  3. 3. 然后在創(chuàng)建子類對象時,先執(zhí)行父類的實例代碼塊,再執(zhí)行父類的構造器。
  4. 4. 最后執(zhí)行子類的實例代碼塊,再執(zhí)行子類的構造器。

以下是示例代碼展示這種執(zhí)行順序:

class Parent {
    static {
        System.out.println("父類靜態(tài)代碼塊執(zhí)行");
    }
    {
        System.out.println("父類實例代碼塊執(zhí)行");
    }
    public Parent() {
        System.out.println("父類構造器執(zhí)行");
    }
}
class Child extends Parent {
    static {
        System.out.println("子類靜態(tài)代碼塊執(zhí)行");
    }
    {
        System.out.println("子類實例代碼塊執(zhí)行");
    }
    public Child() {
        System.out.println("子類構造器執(zhí)行");
    }
}

測試代碼:

public class BlockOrderTest {
    public static void main(String[] args) {
        Child child = new Child();
    }
}

運行上述代碼,會按照前面所述的順序依次輸出相應的執(zhí)行語句,清晰地展示了各代碼塊在對象創(chuàng)建及類加載過程中的執(zhí)行順序。

5.11.3 代碼塊的作用及應用場景

1. **靜態(tài)代碼塊的作用及應用場景**:

  •   - **作用**:主要用于初始化類級別的靜態(tài)資源,比如加載配置文件、初始化數據庫連接池等,因為這些操作只需要在類加載時進行一次即可。它還可以用于對靜態(tài)變量進行一些復雜的初始化邏輯設置,確保靜態(tài)變量在被使用前已經有合適的初始值。
  •   - **應用場景**:例如在一個數據庫操作類中,通過靜態(tài)代碼塊來加載數據庫驅動,代碼可能如下:
import java.sql.DriverManager;
import java.sql.SQLException;
public class DatabaseUtil {
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            System.out.println("數據庫驅動加載成功");
        } catch (ClassNotFoundException e) {
            System.out.println("數據庫驅動加載失敗");
            e.printStackTrace();
        }
    }
    // 后續(xù)可以定義其他數據庫操作相關的方法等
}

2. **實例代碼塊的作用及應用場景**:

  •   - **作用**:常用于初始化實例對象的一些通用屬性,尤其是那些每次創(chuàng)建對象都需要進行相同初始化操作的情況。它可以對實例變量進行賦值等操作,確保對象創(chuàng)建出來后部分屬性有初始值,而且可以在其中編寫一些相對獨立于構造器但又需要在構造器之前執(zhí)行的邏輯。
  •   - **應用場景**:比如創(chuàng)建一個表示商品的類,商品有默認的庫存數量等屬性,通過實例代碼塊可以在每次創(chuàng)建商品對象時將庫存數量初始化為一個固定值,示例如下:
public class Product {
    private int stock;
    {
        stock = 100;
        System.out.println("實例代碼塊執(zhí)行,將商品庫存初始化為" + stock);
    }
    public Product() {
        // 構造器中可以繼續(xù)其他初始化操作或者什么都不做
    }
    // 可以定義其他與商品相關的方法等
}

3. **局部代碼塊的作用及應用場景**:

  •   - **作用**:通過限定變量的作用域來節(jié)省內存空間,尤其是在方法內部對于一些臨時使用且占用較大內存空間的變量,使用局部代碼塊可以讓這些變量在使用完后及時釋放內存。同時也可以讓代碼的邏輯結構更加清晰,將一組相關的代碼和變量限定在一個局部范圍內,便于閱讀和理解。
  •   - **應用場景**:比如在一個循環(huán)中需要創(chuàng)建一個臨時的大容量數組來存儲數據,但這個數組在循環(huán)結束后就不再需要了,就可以將數組的定義和使用放在局部代碼塊內,示例如下:
public class LoopDemo {
    public void loopMethod() {
        for (int i = 0; i < 10; i++) {
            {
                int[] tempArray = new int[10000];
                // 使用tempArray進行一些數據處理等操作
            }
            // 此處tempArray已經超出作用域被銷毀,節(jié)省了內存空間
        }
    }
}