- 第7章 物件導向程式設計(基礎部分)
- 類與物件
- 類和物件的區別和聯絡
- 物件在記憶體中存在形式!
- 屬性/成員變數/欄位
- 如何建立物件
- 如何訪問屬性
- 成員方法
- 方法的呼叫機制原理!
- 成員方法的好處
- 成員方法的定義
- 成員方法傳參機制
- 引用資料型別的傳參機制
- 成員方法返回型別是引用型別應用例項
- 方法遞迴呼叫
- 方法遞迴呼叫
- 遞迴重要規則
- 遞迴呼叫應用例項-漢諾塔
- 遞迴呼叫應用例項-八皇后問題
- 方法過載(OverLoad)
- 基本介紹
- 過載的好處
- 注意事項和使用細節
- 可變引數
- 基本概念
- 基本語法
- 注意事項和使用細節
- 作用域
- 基本使用
- 構造方法/構造器
- 基本介紹
- 注意事項和使用細節
- javap的使用
- 物件建立的流程分析
- 流程分析!
- this 關鍵字
- 深入理解this
- this 的注意事項和使用細節
- this 的案例
- 類與物件
第7章 物件導向程式設計(基礎部分)
類與物件
類和物件的區別和聯絡
- 類是抽象的,概念的,代表一類事物,比如人類,貓類.., 即它是資料型別.
- 物件是具體的,實際的,代表一個具體事物, 即是例項.
- 類是物件的模板,物件是類的一個個體,對應一個例項
物件在記憶體中存在形式!
字串本質上是一個引用型別,按照jvm的規則會把字串放在方法區的常量池中間。
棧中的是物件引用(物件名),實際上的物件在堆中。
// 建立Person 物件
// p1 是物件名(物件引用)
// new Person() 建立的物件空間(資料) 才是真正的物件
Person p1 = new Person();
// 物件的屬性預設值,遵守陣列規則:
屬性/成員變數/欄位
從概念或叫法上看: 成員變數 = 屬性 = field(欄位) (即成員變數是用來表示屬性的,統一叫屬性)
class Car {
String name;//屬性, 成員變數, 欄位field
double price;
String color;
String[] master;//屬性可以是基本資料型別,也可以是引用型別(物件,陣列)
}
屬性是類的一個組成部分,一般是基本資料型別, 也可是引用型別(物件,陣列)。比如前面定義貓類的 int age 就是屬性
注意事項和細節說明
- 屬性的定義語法同變數,示例:訪問修飾符屬性型別屬性名;
訪問修飾符: 控制屬性的訪問範圍
有四種訪問修飾符public, proctected, 預設, private ,後面我會詳細介紹 - 屬性如果不賦值,有預設值,規則和陣列一致。
如何建立物件
- 先宣告再建立
Cat cat ; //宣告物件cat
cat = new Cat(); //建立 - 直接建立
Cat cat = new Cat();
如何訪問屬性
基本語法
物件名.屬性名;
cat.name ;
cat.age;
cat.color;
Person p1=new Person0;
p1.age=10;
p1.name="小明";
Person p2=p1;//把p1賦給了p2,讓p2指向p1
System.out.println(p2.age);
請問:p2.age究竟是多少? 10 並畫出記憶體圖:
核心:引用傳遞傳遞的是地址。
成員方法
在某些情況下,我們要需要定義成員方法(簡稱方法)。
方法的呼叫機制原理!
成員方法的好處
- 提高程式碼的複用性
- 可以將實現的細節封裝起來,然後供其他使用者來呼叫即可
成員方法的定義
訪問修飾符 返回資料型別 方法名(形參列表..){//方法體
語句;
return 返回值;
}
// 如果方法是void,則方法體中可以沒有return 語句,或者只寫return;
訪問修飾符(作用是控制方法使用的範圍)
如果不寫預設訪問,[有四種: public, protected, 預設, private]
方法不能巢狀定義!
成員方法傳參機制
基本資料型別,傳遞的是值(值複製),形參的任何改變不影響實參!
引用資料型別的傳參機制
引用型別傳遞的是地址(傳遞也是值,但是值是地址),可以透過形參影響實參!
棧的值是地址,改的時候修改的是對應堆中的值。
例子:
public class MethodParameter02 {
//編寫一個main方法
public static void main(String[] args) {
//測試
B b = new B();
// int[] arr = {1, 2, 3};
// b.test100(arr);//呼叫方法
// System.out.println(" main的 arr陣列 ");
// //遍歷陣列
// for(int i = 0; i < arr.length; i++) {
// System.out.print(arr[i] + "\t");
// }
// System.out.println();
//測試
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
//測試題, 如果 test200 執行的是 p = null ,下面的結果是 10
//測試題, 如果 test200 執行的是 p = new Person();..., 下面輸出的是10
System.out.println("main 的p.age=" + p.age);//10000
}
}
class Person {
String name;
int age;
}
class B {
public void test200(Person p) {
//p.age = 10000; //修改物件屬性
//思考
p = new Person();
p.name = "tom";
p.age = 99;
//思考
//p = null;
}
//B類中編寫一個方法test100,
//可以接收一個陣列,在方法中修改該陣列,看看原來的陣列是否變化
public void test100(int[] arr) {
arr[0] = 200;//修改元素
//遍歷陣列
System.out.println(" test100的 arr陣列 ");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println();
}
}
B 類中編寫一個方法test100,可以接收一個陣列,在方法中修改該陣列,看看原來的陣列是否變化?會變化
B 類中編寫一個方法test200,可以接收一個Person(age,sal)物件,在方法中修改該物件屬性,看看原來的物件是否變化?會變化.
p=null 和p = new Person(); 對應示意圖
這裡再對class B中的p進行修改,由於在Class B中重新new 了一個p,因此p的指標發生了改變,指向堆中的一個新空間,因此這時修改p的引數,不對main中物件造成影響。
成員方法返回型別是引用型別應用例項
透過這種方式可以編寫方法複製物件。
public class MethodExercise02 {
//編寫一個main方法
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);
//這裡老師提示: 可以同 物件比較看看是否為同一個物件
System.out.println(p == p2);//false
}
}
class Person {
String name;
int age;
}
class MyTools {
//編寫一個方法copyPerson,可以複製一個Person物件,返回複製的物件。克隆物件,
//注意要求得到新物件和原來的物件是兩個獨立的物件,只是他們的屬性相同
//
//編寫方法的思路
//1. 方法的返回型別 Person
//2. 方法的名字 copyPerson
//3. 方法的形參 (Person p)
//4. 方法體, 建立一個新物件,並複製屬性,返回即可
public Person copyPerson(Person p) {
//建立一個新的物件
Person p2 = new Person();
p2.name = p.name; //把原來物件的名字賦給p2.name
p2.age = p.age; //把原來物件的年齡賦給p2.age
return p2;
}
}
方法遞迴呼叫
方法遞迴呼叫
列舉兩個小案例,來幫助大家理解遞迴呼叫機制
- 列印問題
- 階乘問題
public class Recursion01 {
//編寫一個main方法
public static void main(String[] args) {
T t1 = new T();
t1.test(4);//輸出什麼? n=2 n=3 n=4
int res = t1.factorial(5);
System.out.println("5的階乘 res =" + res);
}
}
class T {
//分析
public void test(int n) {
if (n > 2) {
test(n - 1);
}
System.out.println("n=" + n);
}
//factorial 階乘
public int factorial(int n) {
if (n == 1) {
return 1;
} else {
return factorial(n - 1) * n;
}
}
}
遞迴重要規則
1.執行一個方法時,就建立一個新的受保護的獨立空間(錢空間)
2.方法的區域性變數是獨立的,不會相互影響,比如n變數
3.如果方法中使用的是引用型別變數(比如陣列,物件),就會共享該引
用型別的資料。
4.遞迴必須向退出遞迴的條件逼近,否則就是無限遞迴,出現StackOverflowError
5.當一個方法執行完畢,或者遇到return,就會返回,遵守誰呼叫,就
將結果返回給誰,同時當方法執行完畢或者返回時,該方法也就執行完畢。
遞迴呼叫應用例項-漢諾塔
public class HanoiTower {
//編寫一個main方法
public static void main(String[] args) {
Tower tower = new Tower();
tower.move(64, 'A', 'B', 'C');
}
}
class Tower {
//方法
//num 表示要移動的個數, a, b, c 分別表示A塔,B 塔, C 塔
public void move(int num , char a, char b ,char c) {
//如果只有一個盤 num = 1
if(num == 1) {
System.out.println(a + "->" + c);
} else {
//如果有多個盤,可以看成兩個 , 最下面的和上面的所有盤(num-1)
//(1)先移動上面所有的盤到 b, 藉助 c
move(num - 1 , a, c, b);
//(2)把最下面的這個盤,移動到 c
System.out.println(a + "->" + c);
//(3)再把 b塔的所有盤,移動到c ,藉助a
move(num - 1, b, a, c);
}
}
}
遞迴呼叫應用例項-八皇后問題
八皇后問題,是一個古老而著名的問題,是回溯演算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848 年提出:在8×8 格的國際象棋上擺放八個皇后,使其不能互相攻擊,即:任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。
-
第一個皇后先放第一行第一列
-
第二個皇后放在第二行第一列、然後判斷是否OK,如果不OK,繼續放在第二列、第三列、依次把所有列都放完,找到一個合適
-
繼續第三個皇后,還是第一列、第二列...….直到第8個皇后也能放在一個不衝突的位置,算是找到了一個正確解
-
當得到一個正確解時,在棧回退到上一個棧時,就會開始回溯,即將第一個皇后,放到第一列的所有正確解,全部得到.
-
然後回頭繼續第一個皇后放第二列,後面繼續迴圈執行1,2,3.4的步驟
-
說明:理論上應該建立一個二維陣列來表示棋盤,但是實際上可以透過演算法,用一個一維陣列即可解決問題. arr[8]={0,4,7,5.2, 6,1.3)//對應arr下標表示第幾行,即第幾個皇后,arr[i]= val , val表示第i+1個皇后,放在第i+1行的第val+1列
方法過載(OverLoad)
基本介紹
java 中允許同一個類中,多個同名方法的存在,但要求形參列表不一致!
過載的好處
- 減輕了起名的麻煩
- 減輕了記名的麻煩
注意事項和使用細節
1)方法名: 必須相同
2)形參列表: 必須不同(形參型別或個數或順序,至少有一樣不同,引數名無要求)
3)返回型別: 無要求
可變引數
基本概念
java 允許將同一個類中多個同名同功能但引數個數不同的方法,封裝成一個方法。就可以透過可變引數實現
基本語法
訪問修飾符返回型別方法名(資料型別... 形參名) {
}
看一個案例類HspMethod,方法sum。
public class VarParameter01 {
//編寫一個main方法
public static void main(String[] args) {
HspMethod m = new HspMethod();
System.out.println(m.sum(1, 5, 100)); //106
System.out.println(m.sum(1,19)); //20
}
}
class HspMethod {
//可以計算 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 + n3;
// }
// public int sum(int n1, int n2, int n3, int n4) {//4個數的和
// return n1 + n2 + n3 + n4;
// }
//.....
//上面的三個方法名稱相同,功能相同, 引數個數不同-> 使用可變引數最佳化
//老韓解讀
//1. int... 表示接受的是可變引數,型別是int ,即可以接收多個int(0-多)
//2. 使用可變引數時,可以當做陣列來使用 即 nums 可以當做陣列
//3. 遍歷 nums 求和即可
public int sum(int... nums) {
//System.out.println("接收的引數個數=" + nums.length);
int res = 0;
for(int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
}
注意事項和使用細節
1)可變引數的實參可以為0個或任意多個。
2)可變引數的實參可以為陣列。
3)可變引數的本質就是陣列。
4)可變引數可以和普通型別的引數一起放在形參列表,但必須保證可變引數在最後。
5)一個形參列表中只能出現一個可變引數。
public void f3(int... nums1, double... nums2) (X錯誤)
作用域
基本使用
1.在java程式設計中,主要的變數就是屬性(成員變數) 和 區域性變數。
2.我們說的區域性變數一般是指在成員方法中定義的變數。
3.java中作用域的分類
全域性變數:也就是屬性,作用域為整個類體 (Cat類:cry eat等方法使用屬性)
區域性變數:也就是除了屬性之外的其他變數,作用域為定義它的程式碼塊中!
4.全域性變數(屬性)可以不賦值,直接使用,因為有預設值,區域性變數必須賦值後,
才能使用,因為沒有預設值。
public class VarScope {
//編寫一個main方法
public static void main(String[] args) {
}
}
class Cat {
//全域性變數:也就是屬性,作用域為整個類體 Cat類:cry eat 等方法使用屬性
//屬性在定義時,可以直接賦值
int age = 10; //指定的值是 10
//全域性變數(屬性)可以不賦值,直接使用,因為有預設值,
double weight; //預設值是0.0
public void hi() {
//區域性變數必須賦值後,才能使用,因為沒有預設值
int num = 1;
String address = "北京的貓";
System.out.println("num=" + num);
System.out.println("address=" + address);
System.out.println("weight=" + weight);//屬性
}
public void cry() {
//1. 區域性變數一般是指在成員方法中定義的變數
//2. n 和 name 就是區域性變數
//3. n 和 name的作用域在 cry方法中
int n = 10;
String name = "jack";
System.out.println("在cry中使用屬性 age=" + age);
}
public void eat() {
System.out.println("在eat中使用屬性 age=" + age);
//System.out.println("在eat中使用 cry的變數 name=" + name);//錯誤
}
}
1.屬性和區域性變數可以重名,訪問時遵循就近原則。
2.在同一個作用域中,比如在同一個成員方法中,兩個區域性變數,不能重名。
3.屬性生命週期較長,伴隨著物件的建立而建立,伴隨著物件的銷燬而銷燬。區域性變
量,生命週期較短,伴隨著它的程式碼塊的執行而建立,伴隨著程式碼塊的結束而銷燬。
4.作用域範圍不同
全域性變數/屬性:可以被本類使用,或其他類使用(透過物件呼叫)
區域性變數:只能在本類中對應的方法中使用
5.修飾符不同
全域性變數/屬性可以加修飾符
區域性變數不可以加修飾符
構造方法/構造器
[修飾符] 方法名(形參列表){
方法體;
}
- 構造器的修飾符可以預設, 也可以是public protected private
- 構造器沒有返回值
- 方法名和類名字必須一樣
- 引數列表和成員方法一樣的規則
- 構造器的呼叫, 由系統完成
基本介紹
構造方法又叫構造器(constructor),是類的一種特殊的方法,它的主要作用是完成對新物件的初始化。它有幾個特點:
- 方法名和類名相同
- 沒有返回值
- 在建立物件時,系統會自動的呼叫該類的構造器完成物件的初始化。
注意事項和使用細節
1.一個類可以定義多個不同的構造器,即構造器過載
比如:我們可以再給Person類定義一個構造器,用來建立物件的時候,只指定人名,不需要指定年齡。
2.構造器名和類名要相同
3.構造器沒有返回值
4.構造器是完成物件的初始化,並不是建立物件
5.在建立物件時,系統自動的呼叫該類的構造方法
6.如果程式設計師沒有定義構造器,系統會自動給類生成一個預設無參構造器(也
叫預設構造器). 比如Dog(){}, 使用javap指令反編譯看看
可以使用javap Dog.class 檢視
7.一旦定義了自己的構造器,預設的構造器就覆蓋了,就不能再使用預設的無
參構造器,除非顯式的定義一下,即:Dog(){}
javap的使用
javap是JDK提供的一個命令列工具,javap能對給定的class檔案提供的位元組程式碼進行反編譯。 透過它,可以對照原始碼和位元組碼,從而瞭解很多編譯器內部的工作。
使用格式
javap <options> <classes>
常用
javap -c -v 類名
-help --help -? 輸出此用法訊息
-version 版本資訊
-v -verbose 輸出附加資訊
-l 輸出行號和本地變數表
-public 僅顯示公共類和成員
-protected 顯示受保護的/公共類和成員
-package 顯示程式包/受保護的/公共類
和成員 (預設)
-p -private 顯示所有類和成員
-c 對程式碼進行反彙編
-s 輸出內部型別簽名
-sysinfo 顯示正在處理的類的
系統資訊 (路徑, 大小, 日期, MD5 雜湊)
-constants 顯示最終常量
-classpath <path> 指定查詢使用者類檔案的位置
-cp <path> 指定查詢使用者類檔案的位置
-bootclasspath <path> 覆蓋引導類檔案的位置
物件建立的流程分析
class Person{//類Person
int age=90;
String name;
Person(String n,int a){//構造器
name=n;//給屬性賦值
age=a;//..
}
}
Person p=new Person("TIMERRING",20);
流程分析!
1.載入Person類資訊(Person.class) 到方法區,只會載入一次
2.在堆中分配空間(地址)
3.完成物件初始化[3.1預設初始化 age=0 name=null 3.2顯式初始化age=90,name=null,3.3構造器的初始化 age =20, name=TIMERRING]
4.在物件在堆中的地址,返回給p(p是物件名,也可以理解成是物件的引用)
this 關鍵字
可以正常執行,但是是否可以將建構函式的形參改為屬性值呢?可以用this。
public class This01 {
//編寫一個main方法
public static void main(String[] args) {
Dog dog1 = new Dog("大壯", 3);
System.out.println("dog1的hashcode=" + dog1.hashCode());
//dog1呼叫了 info()方法
dog1.info();
System.out.println("============");
Dog dog2 = new Dog("大黃", 2);
System.out.println("dog2的hashcode=" + dog2.hashCode());
dog2.info();
}
}
class Dog{ //類
String name;
int age;
// public Dog(String dName, int dAge){//構造器
// name = dName;
// age = dAge;
// }
//如果我們構造器的形參,能夠直接寫成屬性名,就更好了
//但是出現了一個問題,根據變數的作用域原則
//構造器的 name 是區域性變數,而不是屬性
//構造器的 age 是區域性變數,而不是屬性
//==> 引出this關鍵字來解決
public Dog(String name, int age){//構造器
//this.name 就是當前物件的屬性name
this.name = name;
//this.age 就是當前物件的屬性age
this.age = age;
System.out.println("this.hashCode=" + this.hashCode());
}
public void info(){//成員方法,輸出屬性x資訊
System.out.println("this.hashCode=" + this.hashCode());
System.out.println(name + "\t" + age + "\t");
}
}
java虛擬機器會給每個物件分配this, 代表當前物件。
this.name 就是當前物件的屬性name。
深入理解this
隱藏的this指向自己的堆地址。
this 的注意事項和使用細節
-
this 關鍵字可以用來訪問本類的屬性、方法、構造器
-
this 用於區分當前類的屬性和區域性變數
-
訪問成員方法的語法:this.方法名(引數列表);
-
訪問構造器語法:this(引數列表); 注意只能在構造器中使用(即只能在構造器中訪問另外一個構造器, 必須放在第一條語句)
public class ThisDetail { //編寫一個main方法 public static void main(String[] args) { // T t1 = new T(); // t1.f2(); T t2 = new T(); t2.f3(); } } class T { String name = "jack"; int num = 100; /* 細節: 訪問構造器語法:this(引數列表); 注意只能在構造器中使用(即只能在構造器中訪問另外一個構造器) 注意: 訪問構造器語法:this(引數列表); 必須放置第一條語句!!! */ public T() { //這裡去訪問 T(String name, int age) 構造器 this("jack", 100); System.out.println("T() 構造器"); } public T(String name, int age) { System.out.println("T(String name, int age) 構造器"); } //this關鍵字可以用來訪問本類的屬性 public void f3() { String name = "smith"; //傳統方式(按照就近原則,有區域性變數先訪問區域性變數) System.out.println("name=" + name + " num=" + num);//smith 100 //也可以使用this訪問屬性(準確地就訪問屬性) System.out.println("name=" + this.name + " num=" + this.num);//jack 100 } //細節: 訪問成員方法的語法:this.方法名(引數列表); public void f1() { System.out.println("f1() 方法.."); } public void f2() { System.out.println("f2() 方法.."); //呼叫本類的 f1 //第一種方式 f1(); //第二種方式 this.f1(); } }
-
this 不能在類定義的外部使用,只能在類定義的方法中使用。
this 的案例
public class TestPerson {
//編寫一個main方法
public static void main(String[] args) {
Person p1 = new Person("mary", 20);
Person p2 = new Person("mary", 20);
System.out.println("p1和p2比較的結果=" + p1.compareTo(p2));
}
}
/*
定義Person類,裡面有name、age屬性,並提供compareTo比較方法,
用於判斷是否和另一個人相等,提供測試類TestPerson用於測試,
名字和年齡完全一樣,就返回true, 否則返回false
*/
class Person {
String name;
int age;
//構造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//compareTo比較方法
public boolean compareTo(Person p) {
return this.name.equals(p.name) && this.age == p.age;
}
}