java基礎 韓順平老師的 物件導向(基礎) 自己記的部分筆記

银河小船儿發表於2024-03-07

194,物件記憶體佈局

基本資料型別放在堆裡面,字串型別放在方法區。

棧:一般存放基本資料型別(區域性變數)

堆:存放物件(Cat cat,陣列等)

方法區:常量池(常量,比如字串),類載入資訊

196,屬性注意細節

1,屬性可以是基本資料型別,也可以是引用型別(物件,陣列)

2,屬性的定義語法同變數,示例:訪問修飾符 屬性型別 屬性名;這裡簡單介紹訪問修飾符:控制屬性的訪問範圍。有四種訪問修飾符 public,protected,預設,private。

3,屬性如果不賦值,有預設值,規則和陣列一致。具體說:int 0,short 0,byte 0,long 0,float 0.0,double 0.0,char \u0000,boolean false,String null。

public class test1{
    public static void main(String[] args){
        //建立Person物件
        //p1 是物件名(物件引用)
        Person p1 = new Person();//new Person() 建立的物件空間(資料)才是真正的物件

        System.out.println("\n當前這個人的資訊");
        System.out.println("age = " + p1.age + " name = " + p1.name + " sal = " + p1.sal + " isPass = " + p1.isPass);
    }
}
class Person{
    //四個屬性
    int age;
    String name;
    double sal;
    String isPass;
}

執行結果:

198,物件分配機制

引用賦值,即地址賦值

199,物件建立過程

1,先載入Person類資訊(屬性和方法資訊,只會載入一次)

2,在堆中分配空間,進行預設初始化(看規則)

3,把地址賦給 p ,p 就指向物件 Person p = new Person()

4,進行指定初始化,比如 p.name = "jack"

206,方法使用細節

1,一個方法最多有一個返回值 [思考,如何返回多個結果,返回陣列]

2,返回型別可以為任意型別,包含基本型別或引用型別(陣列,物件)

public class test1{
    public static void main(String[] args){
        AA a = new AA();
        int[] res = a.getSumAndSub(1,2);//用一個陣列接收
        System.out.println("和 = " + res[0]);
        System.out.println("差 = " + res[1]);
    }
}
class AA{
    public int[] getSumAndSub(int n1, int n2)
    {
        int[] resArr = new int[2];//建立一個陣列
        resArr[0] = n1 + n2;
        resArr[1] = n1 - n2;
        return resArr;
    }
}

執行結果:

3,如果方法要求有返回資料型別,則方法體中最後的執行語句必須為 return 值(值為常量或表示式);而且要求返回值型別必須和 return 的值型別一致或相容(會發生自動型別轉換)

4,如果方法是 void ,則方法體中可以沒有 return 語句,或者只寫 return;

5,接207;一個方法可以有0個引數,也可以有多個引數,中間用逗號隔開

6,呼叫帶引數的方法時,一定對應著引數列表傳入相同型別或相容型別的引數

7,方法定義時的引數稱為形式引數,簡稱形參;方法呼叫時的傳入引數稱為實際引數,簡稱實參,實參和形參的型別要一致或相容,個數,順序必須一致

8,方法體裡面寫完功能的具體的語句,可以為輸入,輸出,變數,運算,分支,迴圈,方法呼叫,但裡面不能再定義方法!即:方法不能巢狀定義。

9,同一個類中的方法呼叫:直接呼叫即可。(我們想在A類裡的一個方法裡去呼叫A類裡的另一個方法,直接呼叫即可)

public class test1{
    public static void main(String[] args){
        A a = new A();
        a.sayOk();
    }
}
class A{
   public void print(int n)
   {
       System.out.println("print()方法被呼叫 n = " + n);
   }
   public void sayOk()// sayOk 呼叫 print(直接呼叫即可)
   {
       print(10);
       System.out.println("繼續執行sayOk");
   }
}

執行結果:

10,跨類中的方法A類呼叫B類方法:需要透過物件名呼叫。(我們想在A類的一個方法裡呼叫B類的一個方法,需要在A類的這個方法裡建立B類的物件,再用B類的物件呼叫B類的一個方法)。

public class test1{
    public static void main(String[] args){
        A a = new A();
        a.m1();
    }
}
class A{
   public void m1()
   {
       System.out.println("m1() 方法被呼叫");
       B b = new B();//建立B物件
       b.hi();
       System.out.println("m1() 方法繼續執行");
   }
}
class B{
    public void hi()
    {
        System.out.println("B類中的 hi() 被執行");
    }
}

執行結果:

11,特別說明一下:跨類的方法呼叫和方法的訪問修飾符相關,後面會細說。

213,克隆物件

編寫一個方法 copyPerson,可以複製一個 Person 物件,返回複製的物件。克隆物件,注意要求得到新物件和原來的物件是兩個獨立的物件,只是他們的屬性相同。

public class test1{
    public static void main(String[] args){
        Person p = new Person();
        p.name = "milan";
        p.age = 100;
        //建立tools物件
        MyTools tools = new MyTools();
        Person p2 = tools.copyPerson(p);

        //到此 p 和 p2 是 Person物件,但是是兩個獨立的物件,屬性相同
        System.out.println("p的屬性 age = " + p.age + " 名字 = " + p.name);
        System.out.println("p2的屬性 age = " + p2.age + " 名字 = " + p2.name);
    }
}
class Person{
    String name;
    int age;
}
class MyTools{
    //方法的返回型別 Person
    // 方法的名字 copyPerson
    // 方法的形參(Person p)
    //方法體,建立一個新物件,並複製屬性,返回即可
    public Person copyPerson(Person p)
    {
        Person p2 = new Person();
        p2.name = p.name;//把原來物件的名字賦給p2.name
        p2.age = p.age;//把原來物件的年齡賦給p2.age
        return p2;
    }
}

執行結果:

218,遞迴執行機制

1,執行一個方法時,就建立一個新的受保護的獨立空間(棧空間)

2,方法的區域性變數是獨立的,不會相互影響,比如n變數

3,如果方法中使用的是引用型別變數(比如陣列),就會共享該引用型別的資料

4,遞迴必須向退出遞迴的條件逼近,否則就是無限遞迴,出現StackOverflowError

5,當一個方法執行完畢,或者遇到return,就會返回,遵守誰呼叫,就將結果返回給誰,同時當方法執行完畢或者返回時,該方法也就執行完畢。

220,猴子吃桃

有一堆桃子,猴子第一天吃了其中的一半,並再多吃了一個!以後每天猴子都吃其中的一半,然後再多吃一個。當到了第10天時,想再吃時(即還沒吃完),發現只有1個桃子了。問題:最初共多少個桃子?

(已知第10天只剩1個桃子,猴子每天都吃其中的一半,並多吃一個。假設第9天有 x 個桃子,第 10天有 y 個桃子,(x / 2) - 1 = y,x = (y + 1) * 2)

思路:逆推

1,day = 10 時,有 1 個桃子

2,day = 9 時,有 (day10 + 1)* 2 = 4

3,day = 8 時,有 (day9 + 1)* 2 = 10

4,規律就是 前一天的桃子 = (後一天的桃子 + 1)* 2

5,遞迴

public class test1{
    public static void main(String[] args){
       T t = new T();
       int day = 1;
       int peachNum = t.peach(day);
       if(peachNum != -1)
       {
           System.out.println("第" + day + "天有" + peachNum + "個桃子");
       }
    }
}
class T{
    public int peach(int day)
    {
        if(day == 10)
        {
            return 1;
        }else if(day >= 1 && day <= 9)
        {
            return (peach(day + 1) + 1) * 2;
        }
        else
        {
            System.out.println("day在1-10");
            return -1;
        }
    }
}

執行結果:

221,老鼠出迷宮

思路:

1,先建立迷宮,用二維陣列表示 int[][] map = new int[8][7]

2,先規定map 陣列的元素值:0 表示可以走;1 表示障礙物

3,將最上面的一行和最下面的一行,全部設定為1

4,將最右面的一列和最左面的一列,全部設定為1

使用遞迴回溯的思想來解決老鼠出迷宮

1,findWay方法就是專門來找出迷宮的路徑

2,如果找到,就返回 true,否則返回 false

3,map 就是二維陣列,即表示迷宮

4,i,j 就是老鼠的位置,初始化的位置為(1,1)

5,因為我們是遞迴的找路,所以先規定 map 陣列的各個值的含義:0 表示可以走;1 表示障礙物;2 表示可以走;3 表示走過,但是走不通,是死路

6,當 map[6][5] = 2 就說明找到通路,就可以結束,否則就繼續找

7,先確定老鼠找路策略:下 -> 右 -> 上 -> 左。(有不同的策略,先確定一個自己的策略)

public class test1{
    public static void main(String[] args){
       int[][] map = new int[8][7];//8行7列
       for(int i = 0; i < 7; i++) //i 是列,
       {
           map[0][i] = 1;//將最上面的一行和最下面的一行,全部設定為1
           map[7][i] = 1;
       }
       for(int i = 0; i < 8; i++)//i 是行
       {
           map[i][0] = 1;//將最右面的一列和最左面的一列,全部設定為1
           map[i][6] = 1;
       }
       map[3][1] = 1;
       map[3][2] = 1;
       //輸出當前地圖
        for(int i = 0; i < map.length; i++)
        {
            for(int j = 0; j < map[i].length; j++)
            {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
        //使用findWay給老鼠找路
        T t1 = new T();
        t1.findWay(map,1,1);//對陣列的修改,會把原陣列修改了,參考引用賦值
        System.out.println("\n====找路的情況如下=====");
        for(int i = 0; i < map.length; i++)
        {
            for(int j = 0; j < map[i].length; j++)
            {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }
}
class T{
    public boolean findWay(int[][] map, int i, int j)
    {
        //0 表示可以走,還沒走;1 表示障礙物;2 表示可以走;3 表示走過,但是走不通,是死路
        if (map[6][5] == 2) //說明已經找到,走到終點了
        {
            return true;
        }
        else
        {
            if (map[i][j] == 0)//當前位置為0,也是起點。說明可以走
            {
                map[i][j] = 2;//假定可以走通,沿著找路策略開始走
                //找路策略:下 -> 右 -> 上 -> 左
                if (findWay(map, i + 1, j))//
                {
                    return true;
                } else if (findWay(map, i, j + 1))//
                {
                    return true;
                } else if (findWay(map, i - 1, j))//
                {
                    return true;
                } else if (findWay(map, i, j - 1)) //
                {
                    return true;
                }
                else//四個方向都走不通了,就是死路
                {
                    map[i][j] = 3;
                    return false;
                }
            }
            else //map[i][j] = 1,2,3
            {
                return false;
            }
        }
    }
}

執行結果:

225,漢諾塔

思路見程式碼:

public class test1{
    public static void main(String[] args){
        T t = new T();
        t.move(3, 'A', 'B', 'C');
    }
}
class T{
    //num 表示要移動的個數,a,b,c分別表示A塔,B塔,C塔
   public void move(int num, char a, char b, char c)
   {
       if(num == 1)//只有一個盤子,就直接從A移到C
       {
           System.out.println(a + "->" + c);
       }
       else
       {
           //1,如果有多個盤,可以看成兩個,最下面的和上面的所有盤(num-1)
           move(num - 1,a, c, b);//先移動上面的所有的盤到 b,藉助 c,藉助是指我們不能把上面的所有盤整體移過去,需要藉助c
           System.out.println(a + "->" + c); //3,把最下面的這個盤,移動到 c。
           //4,再把 b 塔的所有盤,移動到 c,藉助 a
           move(num - 1, b, a, c);//把B塔的盤移動也當成兩個盤在移動,和上面的 1 同理
       }
   }
}

執行結果:

229,過載使用細節

1,方法名:必須相同

2,形參列表:必須不同(形參型別或個數或順序,至少有一樣不同,引數名無要求)

3,返回型別:無要求

233,可變引數使用

概念:java允許將同一個類中多個同名同功能但引數個數不同的方法,封裝成一個方法。就可以透過可變引數實現。

基本語法: 訪問修飾符 返回型別 方法名(資料型別... 形參名){}

public class test1{
    public static void main(String[] args){
        HspMethods m = new HspMethods();
        System.out.println(m.sum(1,2,3,4));
    }
}
class HspMethods{
    //可以計算 2個數的和,3個數的和,4,5....
    //可以使用方法過載
//    public int sum(int n1, int n2)//2個數的和
//    {
//        return n1 + n2;
//    }
//    public int sum(int n1, int n2, int n3)//3個數的和
//    {
//        return n1 + n2;
//    }
    //上面的兩個方法名稱相同,功能相同,引數個數不同 -> 使用可變引數最佳化
    //1,  int... 表示接受的是可變引數,型別是int,即可以接收多個int(0-多)
    //2,  使用可變引數是,可以當做陣列來使用,即 nums 可以當做陣列
    public int sum(int... nums)
    {
        int res = 0;
        for(int i = 0; i < nums.length; i++)
        {
            res += nums[i];
        }
        return res;
    }
}

執行結果:

234,可變引數細節

1,可變引數的實參可以為 0 個或任意多個(見 233 程式碼)

2,可變引數的實參可以為陣列

public class test1{
    public static void main(String[] args){
        int[] arr = {1, 2, 3};
        T t1 = new T();
        t1.f1(arr);
    }
}
class T{
   public void f1(int... nums)
   {
       System.out.println("長度 = " + nums.length);
   }
}

執行結果:

3,可變引數的本質就是陣列

4,可變引數可以和普通型別的引數一起放在形參列表,但必須保證可變引數在最後(否則報錯)

public void f2(String str, double... nums){}

5,一個形參列表中只能出現一個可變引數

235,可變引數練習

問題:有三個方法,分別實現返回姓名和兩門課成績(總分),返回姓名和三門課成績(總分),返回姓名和五門課成績(總分)。封裝成一個可變引數的方法。類名 HspMethod 方法名 showScore

分析:兩門課,三門課,五門課成績,理解為一個可以接收多個double 型別的可變引數,問題要求返回姓名和三門課成績(總分),所以方法的返回型別是 String,形參(String, double... )

public class test1{
    public static void main(String[] args){
        HspMethod t1 = new HspMethod();
        System.out.println(t1.showScore("milan", 60,80));
        System.out.println(t1.showScore("jack", 60,80,80));
        System.out.println(t1.showScore("lucy", 60,80,80,90,80));
    }
}
class HspMethod{
   public String showScore(String name, double... scores)
   {
       double totalScore = 0;
       for(int i = 0; i < scores.length; i++)
       {
           totalScore += scores[i];
       }
       return name + " 有" + scores.length + "門課的成績總分 = " + totalScore;
   }
}

執行結果:

236,作用域基本使用

1,在java程式設計中,主要的變數就是屬性(成員變數)和區域性變數

2,我們說的區域性變數一般是指在成員方法中定義的變數

3,java 中作用域的分類。

全域性變數:也就是屬性,作用域為整個類體;

區域性變數:也就是除了屬性之外的其他變數,作用域為定義它的程式碼塊中!

4,全域性變數(屬性)可以不賦值,直接使用,因為有預設值,區域性變數必須賦值後才能使用,因為沒有預設值。

237,作用域使用細節

1,屬性和區域性變數可以重名,訪問時遵循就近原則。

public class test1{
    public static void main(String[] args){
        Person p1 = new Person();
        p1.say();
    }
}
class Person{
    String name = "jack";
    public void say()
    {   
        String name = "king";
        System.out.println("say() name = " + name);
    }
}

執行結果:

2,在同一個作用域中,比如在同一個成員方法中,兩個區域性變數,不能重名

3,屬性生命週期較長,伴隨著物件的建立而建立,伴隨著物件的銷燬而銷燬。區域性變數,生命週期較短,伴隨著它的程式碼塊的執行而建立,伴隨著程式碼塊的結束而銷燬。即在一次方法呼叫過程中。

4,全域性變數/屬性:可以被本類使用,或其他類使用(透過物件呼叫)。 區域性變數:只能在本類中對應的方法中使用

public class test1{
    public static void main(String[] args){
        Person p1 = new Person();
        T t1 = new T();
        t1.test();//第1種跨類訪問物件屬性的方式
        t1.test(p1);//第2種跨類訪問物件屬性的方式

    }
}
class T{
    public void test()
    {
        Person p1 = new Person();//Person類中的全域性變數name,可以在T類中使用,透過物件呼叫
        System.out.println(p1.name);//jack
    }
    public void test(Person p)
    {
        System.out.println(p.name);//jack
    }
}
class Person{
    String name = "jack";
}

執行結果:

5,修飾符不同 :全域性變數/屬性可以加修飾符;區域性變數不可以加修飾符

239,構造器基本介紹

構造方法又叫構造器,是類的一種特殊方法,它的主要作用是完成對新物件的初始化

基本語法: 修飾符 方法名(形參列表) { 方法體 }

說明:1,構造器的修飾符可以預設,也可以是public protected private

2,構造器沒有返回值

3,方法名和類名字必須一樣

4,引數列表 和 成員方法 一樣的規則

5,在建立物件時,系統會自動呼叫該類的構造器完成對物件的初始化

public class test1{
    public static void main(String[] args){
        Person p1 = new Person("smith", 80);//當我們new 一個物件時,直接透過構造器指定名字和年齡
        System.out.println("p1的資訊如下");
        System.out.println("p1物件name = " + p1.name);
        System.out.println("p1物件age = " + p1.age);
    }
}
class Person{
    String name;
    int age;
    public Person(String pName, int pAge)
    {
        System.out.println("構造器被呼叫~~ 完成物件的屬性初始化");
        name = pName;
        age = pAge;
    }
}

執行結果:

6,一個類可以定義多個不同的構造器,即構造器過載

7,如果程式設計師沒有定義構造器,系統會自動給類生成一個預設無參構造器(也叫預設構造器),比如 Dog() { }

8,一旦定義了自己的構造器,預設的構造器就覆蓋了,就不能再使用預設的無參構造器,除非顯示的定義一下,即:Dog() {}

244,物件建立的流程分析

看一個案例

class Person{//類Person
    int age = 90;
    String name;
    Person(String n, int a)//構造器
    {
        name = n;//給屬性賦值
        age = a;
    }
}
Person p = new Person("小倩",20);

流程分析(面試題)

1,載入Person類資訊(Person.class),只會載入一次

2,在堆中分配空間(地址)

3,完成物件初始化

3.1,預設初始化 age = 0,name = null 3.2,顯示初始化 age = 90,name = null 3.3,構造器的初始化 age = 20,name = 小倩

4,在物件在堆中的地址,返回給 p(p是物件名,也可以理解成是物件的引用)

249,this 使用細節

1,哪個物件呼叫,this就代表哪個物件

2,this 關鍵字可以用來訪問本類的屬性,方法,構造器

3,this用於區分當前類的屬性和區域性變數

4,訪問成員方法的語法:this.方法名(引數列表)

public class test1{
    public static void main(String[] args){
        T t = new T();
        t.f2();
    }
}
class T{
    public void f1()
    {
        System.out.println("f1() 方法..");
    }
    public void f2()
    {
        System.out.println("f2() 方法..");
        //呼叫本類的 f1
        f1();//第一種方式
        this.f1();//第二種方式 this.方法名(引數列表)
    }
}

執行結果:

5,訪問構造器語法:this(引數列表);注意只能在構造器中使用(即只能在構造器中訪問另外一個構造器,必須放置第一條語句)

public class test1{
    public static void main(String[] args){
        T t = new T();
    }
}
class T{
    public T()
    {
        this("jack", 100);//這裡去訪問T(String name, int age) 構造器
        System.out.println("T() 構造器");
    }
    public T(String name, int age)
    {
        System.out.println("T(String name, int age) 構造器");
    }
}

執行結果:

6,this不能在類定義的外部使用,只能在類定義的方法中使用

250,this課堂練習

問題:定義Person類,裡面有name,age屬性,並提供compareTo比較方法,用於判斷是否和另一個人相等,提供測試類TestPerson 用於測試,名字和年齡完全一樣,就返回true,否則返回false

public class TestPerson{
    public static void main(String[] args){
        Person p1 = new Person("mary", 20);
        Person p2 = new Person("smith", 30);
        System.out.println(p1.compareTo(p2));

    }
}
class Person{
    String name;
    int age;
    public Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public boolean compareTo(Person p)//和另一個人的姓名年齡比較,所以形參是Person物件,還要知道這個物件的資訊(透過構造器)
    {
        return this.name.equals(p.name) && this.age == p.age;
    }
}

執行結果:

253,本章作業03

問題:編寫類Book,定義方法updatePrice,實現更改某本書的價格,具體:如果價格>150,則更改為150,如果價格>100,更改為100,否則不變。

分析:更改某本書的價格,可以理解為更改一個物件的價格屬性,需要先用構造器完成對這個物件的價格的初始化,再到方法中進行更改。

public class test1{
    public static void main(String[] args){
        Book book = new Book("小王子", 120);
        book.info();
        book.updatePrice();
        book.info();
    }
}
class Book
{
    String name;
    double price;
    public Book(String name, double price)
    {
        this.name = name;
        this.price = price;
    }
    public void updatePrice()
    { //如果方法中,沒有 price 區域性變數,this.price 等價 price
        if(price > 100)
        {
            price = 100;
        }else if(price > 150)
        {
            price = 150;
        }
    }
    public void info()
    {
        System.out.println("書名 = " + this.name + " 價格 = " + this.price);
    }
}

執行結果:

260,本章作業10

public class test1 {
    public static void main(String[] args) {
        Circle c = new Circle();
        PassObject po = new PassObject();
        po.printAreas(c,5);
    }
}
class Circle
{
    double radius;
    public Circle()//在第2問我們沒有辦法確認半徑值,在for迴圈那裡才知道半徑值是變化的,所以要重寫預設構造器
    {

    }
    public Circle(double radius)//第1問要用到這個
    {
        this.radius = radius;
    }
    public double findArea()//返回面積
    {
        return radius * radius * Math.PI;
    }
    public void setRadius(double radius)//新增方法 setRadius,修改物件的半徑值
    {
        this.radius = radius;
    }
}
class PassObject
{
    public void printAreas(Circle c, int times)
    {
        System.out.println("radius\tarea");
        for(int i = 1; i <= times; i++)
        {
            c.setRadius(i);//可以用到Circle物件,並能把i傳進去,如果每次都new 一個新物件(用構造器),就不划算
            System.out.println(i + "\t" + c.findArea());

        }
    }
}

執行結果:

相關文章