- 第8章 物件導向程式設計(中級部分)
- IDEA 常用快捷鍵
- 包
- 包的三大作用
- 包基本語法
- 包的本質分析
- 包的命名
- 常用的包
- 如何引入包
- 注意事項和使用細節
- 訪問修飾符
- 基本介紹
- 訪問修飾符的訪問範圍!
- 使用的注意事項
- 物件導向程式設計三大特徵
- 基本介紹
- 封裝介紹
- 封裝的理解和好處
- 封裝的實現步驟(三步)
- 快速入門案例
- 將構造器和setXxx 結合
- 物件導向程式設計-繼承
- 繼承的基本語法
- 繼承的深入討論/細節問題
- 繼承的本質分析!
- super 關鍵字
- 基本介紹
- 基本語法
- super 給程式設計帶來的便利/細節
- super 和this 的比較
- 方法重寫/覆蓋(override)
- 注意事項和使用細節
- 重寫和過載比較!
- 物件導向程式設計-多型
- 多[多種]態[狀態]基本介紹
- 多型的具體體現
- 方法的多型
- 物件的多型!
- 多型注意事項和細節討論
- 多型的向上轉型
- 多型向下轉型
- 屬性沒有重寫之說
- instanceOf 比較運算子
- java 的動態繫結機制!!
- 多型的應用
- 多型陣列
- 多型引數
- Object 類詳解
- equals 方法
- ==和equals 的對比!!!!!
- 如何重寫 equals 方法
- hashCode 方法
- 總結
- toString 方法
- finalize 方法
- 斷點除錯(debug)
- 斷點除錯介紹
- 斷點除錯的快捷鍵
- Idea debug進入 Jdk原始碼
- 專案-零錢通
- equals 方法
第8章 物件導向程式設計(中級部分)
IDEA 常用快捷鍵
- 刪除當前行, 預設是
ctrl + Y
自己配置ctrl + d
- 複製當前行, 自己配置
ctrl + alt + 向下游標
- 補全程式碼
alt + /
- 新增註釋和取消註釋
ctrl + /
- 匯入該行需要的類先配置auto import , 然後使用
alt+enter
即可 - 快速格式化程式碼
ctrl + alt + L
- 快速執行程式自己定義
alt + R
- 生成構造器等
alt + insert
[提高開發效率] - 檢視一個類的層級關係
ctrl + H
- 將游標放在一個方法上,輸入
ctrl + B
, 可以定位到方法 - 自動的分配變數名, 透過在後面加
.var
包
包的三大作用
區分相同名字的類
當類很多時,可以很好的管理類[看Java API文件]
控制訪問範圍
包基本語法
package com.hspedu;
說明:
package關鍵字,表示打包
com.hspedu:表示包名
包的本質分析
包的本質實際上就是建立不同的資料夾/目錄來儲存類檔案
包的命名
命名規則
只能包含數字、字母、下劃線、小圓點.,但不能用數字開頭,不能是關鍵字或保留字。
命名規範
一般是小寫字母+小圓點
一般是 com.公司名.專案名.業務模組名
例如:
com.sina.crm.user //使用者模組
com.sina.crm.order //訂單模組
com.sina.crm.utils //工具類
常用的包
一個包下,包含很多的類,java 中常用的包有:
- java.lang.* //lang 包是基本包,預設引入,不需要再引入.
- java.util.* //util 包,系統提供的工具包, 工具類,使用Scanner
- java.net.* //網路包,網路開發
- java.awt.* //是做java 的介面開發,GUI
如何引入包
語法: import 包;
我們引入一個包的主要目的是要使用該包下的類
比如
import java.util.Scanner; //就只是引入一個類
Scanner.import java.util.*;//表示將java.util包所有都引入
建議:我們需要使用到哪個類,就匯入哪個類即可,不建議使用*匯入
注意事項和使用細節
- package的作用是宣告當前類所在的包,需要放在類的最上面,一個類中最多隻有一句package
- import指令位置放在package的下面,在類定義前面,可以有多句且沒有順序要求。
//package的作用是宣告當前類所在的包,需要放在類(或者檔案)的最上面,
// 一個類中最多隻有一句package
package com.hspedu.pkg;
//import指令 位置放在package的下面,在類定義前面,可以有多句且沒有順序要求
import java.util.Scanner;
import java.util.Arrays;
訪問修飾符
基本介紹
java 提供四種訪問控制修飾符號,用於控制方法和屬性(成員變數)的訪問許可權(範圍):
- 公開級別: 用public 修飾,對外公開
- 受保護級別: 用protected 修飾, 對子類和同一個包中的類公開
- 預設級別: 沒有修飾符號,向同一個包的類公開.
- 私有級別: 用private修飾,只有類本身可以訪問,不對外公開.
訪問修飾符的訪問範圍!
使用的注意事項
-
修飾符可以用來修飾類中的屬性,成員方法以及類
-
只有預設的和public才能修飾類!,並且遵循上述訪問許可權的特點。
-
成員方法的訪問規則和屬性完全一樣.
物件導向程式設計三大特徵
基本介紹
物件導向程式設計有三大特徵:封裝、繼承和多型。
封裝介紹
封裝(encapsulation)就是把抽象出的資料 [屬性] 和對資料的操作 [方法] 封裝在一起,資料被保護在內部,程式的其它部分只有透過被授權的操作 [方法] ,才能對資料進行操作。
封裝的理解和好處
隱藏實現細節: 方法(連線資料庫) <-- 呼叫(傳入引數)
可以對資料進行驗證,保證安全合理
Person {name, age}
Person p = new Person();
p.name = "jack" ;
p.age= 1200;
封裝的實現步驟(三步)
-
將屬性進行私有化private【不能直接修改屬性】
-
提供一個公共的(public)set方法,用於對屬性判斷並賦值
public void setXxx(型別引數名){//Xxx表示某個屬性 //加入資料驗證的業務邏輯 屬性=引數名; }
-
提供一個公共的
(public)get
方法,用於獲取屬性的值public 資料型別 getXxx(){ //許可權判斷,Xxx某個屬性 return xx; }
快速入門案例
package com.hspedu.encap;
public class Encapsulation01 {
public static void main(String[] args) {
//如果要使用快捷鍵alt+r, 需要先配置主類
//第一次,我們使用滑鼠點選形式運算程式,後面就可以用
Person person = new Person();
person.setName("韓順平");
person.setAge(30);
person.setSalary(30000);
System.out.println(person.info());
System.out.println(person.getSalary());
//如果我們自己使用構造器指定屬性
Person smith = new Person("smith", 80, 50000);
System.out.println("====smith的資訊======");
System.out.println(smith.info());
}
}
/*
那麼在java中如何實現這種類似的控制呢?
請大家看一個小程式(com.hspedu.encap: Encapsulation01.java),
不能隨便檢視人的年齡,工資等隱私,並對設定的年齡進行合理的驗證。年齡合理就設定,否則給預設
年齡, 必須在 1-120, 年齡, 工資不能直接檢視 , name的長度在 2-6字元 之間
*/
class Person {
public String name; //名字公開
private int age; //age 私有化
private double salary; //..
public void say(int n,String name) {
}
//構造器 alt+insert
public Person() {
}
//有三個屬性的構造器
public Person(String name, int age, double salary) {
// this.name = name;
// this.age = age;
// this.salary = salary;
//我們可以將set方法寫在構造器中,這樣仍然可以驗證
setName(name);
setAge(age);
setSalary(salary);
}
// 自己寫setXxx 和 getXxx 太慢,我們使用快捷鍵 Generate --> Getter and Setter
// 然後根據要求來完善我們的程式碼.
public String getName() {
return name;
}
public void setName(String name) {
// 加入對資料的校驗,相當於增加了業務邏輯
if(name.length() >= 2 && name.length() <=6 ) {
this.name = name;
}else {
System.out.println("名字的長度不對,需要(2-6)個字元,預設名字");
this.name = "無名人";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
//判斷
if(age >= 1 && age <= 120) {//如果是合理範圍
this.age = age;
} else {
System.out.println("你設定年齡不對,需要在 (1-120), 給預設年齡18 ");
this.age = 18;//給一個預設年齡
}
}
public double getSalary() {
//可以這裡增加對當前物件的許可權判斷
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//寫一個方法,返回屬性資訊
public String info() {
return "資訊為 name=" + name + " age=" + age + " 薪水=" + salary;
}
}
將構造器和setXxx 結合
可以將set方法寫在構造器中,這樣可以保證驗證。
public Person(String name, int age, double salary) {
// this.name = name;
// this.age = age;
// this.salary = salary;
//我們可以將set 方法寫在構造器中,這樣仍然可以驗證
setName(name);
setAge(age);
setSalary(salary);
}
物件導向程式設計-繼承
繼承可以解決程式碼複用,讓我們的程式設計更加靠近人類思維.當多個類存在相同的屬性(變數)和方法時,可以從這些類中抽象出父類,在父類中定義這些相同的屬性和方法,所有的子類不需要重新定義這些屬性和方法,只需要透過extends 來宣告繼承父類即可。
繼承的基本語法
class 子類 extends 父類 {
}
1)子類就會自動擁有父類定義的屬性和方法
2)父類又叫超類,基類。
3)子類又叫派生類。
繼承的深入討論/細節問題
- 子類繼承了所有的屬性和方法,非私有的屬性和方法可以在子類直接訪問, 但是私有屬性和方法不能在子類直接訪問,要透過父類提供公共的方法去訪問
- 子類必須呼叫父類的構造器,完成父類的初始化。先呼叫父類構造器,再呼叫子類構造器。
- 當建立子類物件時,不管使用子類的哪個構造器,預設情況下總會去呼叫父類的無參構造器,如果父類沒有提供無參構造器,則必須在子類的構造器中用super 去指定使用父類的哪個構造器完成對父類的初始化工作,否則,編譯不會透過.
- 如果希望指定去呼叫父類的某個構造器,則顯式的呼叫一下:
super(引數列表)
- super 在使用時,必須放在構造器第一行( super 只能在構造器中使用 )
- super() 和this() 都只能放在構造器第一行,因此這兩個方法不能共存在一個構造器。
- java 所有類都是Object 類的子類, Object 是所有類的基類.
- 父類構造器的呼叫不限於直接父類!將一直往上追溯直到Object 類(頂級父類)
- 子類最多隻能繼承一個父類(指直接繼承),即java中是單繼承機制。
思考:如何讓A 類繼承B類和C類? 方法:A 繼承B, B 繼承C。 - 不能濫用繼承,子類和父類之間必須滿足is-a 的邏輯關係
package com.hspedu.extend_;
import java.util.Arrays;
//輸入ctrl + H 可以看到類的繼承關係
public class Sub extends Base { //子類
public Sub(String name, int age) {
//1. 要呼叫父類的無參構造器, 如下或者什麼都不寫,預設就是呼叫super()
//super();//父類的無參構造器
//2. 要呼叫父類的 Base(String name) 構造器
//super("hsp");
//3. 要呼叫父類的 Base(String name, int age) 構造器
super("king", 20);
//細節:super在使用時,必須放在構造器第一行
//細節: super() 和 this() 都只能放在構造器第一行,因此這兩個方法不能共存在一個構造器
//this() 不能再使用了
System.out.println("子類Sub(String name, int age)構造器被呼叫....");
}
public Sub() {//無參構造器
//super(); //預設呼叫父類的無參構造器
super("smith", 10);
System.out.println("子類Sub()構造器被呼叫....");
}
//當建立子類物件時,不管使用子類的哪個構造器,預設情況下總會去呼叫父類的無參構造器
public Sub(String name) {
super("tom", 30);
//do nothing...
System.out.println("子類Sub(String name)構造器被呼叫....");
}
public void sayOk() {//子類方法
//非私有的屬性和方法可以在子類直接訪問
//但是私有屬性和方法不能在子類直接訪問
System.out.println(n1 + " " + n2 + " " + n3);
test100();
test200();
test300();
//test400();錯誤
//要透過父類提供公共的方法去訪問
System.out.println("n4=" + getN4());
callTest400();//
}
}
繼承的本質分析!
我們著一個案例來分析當子類繼承父類,建立子類物件時,記憶體中到底發生了什麼?
當子類物件建立好後,建立查詢的關係
-
最先載入父類,分別是Object類,然後載入Grandpa,再Father,最後Son。
-
然後再分配堆空間:不同類的相同變數名不會衝突,堆中空間不同。
-
最後Son物件(0x11都是)返回給main中的引用。
那麼最後輸出什麼呢?(還是就近原則)
package com.hspedu.extend_;
/**
* 講解繼承的本質
*/
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//記憶體的佈局
//?-> 這時請大家注意,要按照查詢關係來返回資訊
//(1) 首先看子類是否有該屬性
//(2) 如果子類有這個屬性,並且可以訪問,則返回資訊
//(3) 如果子類沒有這個屬性,就看父類有沒有這個屬性(如果父類有該屬性,並且可以訪問,就返回資訊..)
//(4) 如果父類沒有就按照(3)的規則,繼續找上級父類,直到Object...
System.out.println(son.name);//返回就是大頭兒子
//System.out.println(son.age);//返回的就是39
//System.out.println(son.getAge());//返回的就是39
System.out.println(son.hobby);//返回的就是旅遊
}
}
class GrandPa { //爺類
String name = "大頭爺爺";
String hobby = "旅遊";
}
class Father extends GrandPa {//父類
String name = "大頭爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class Son extends Father { //子類
String name = "大頭兒子";
}
super 關鍵字
基本介紹
super 代表父類的引用,用於訪問父類的屬性、方法、構造器
基本語法
1.訪問父類的屬性,但不能訪問父類的private屬性[案例]
super.屬性名;
2.訪問父類的方法,不能訪問父類的private方法
super.方法名(引數列表);
3.訪問父類的構造器:
super(引數列表);只能放在構造器的第一句,只能出現一句!
package com.hspedu.super_;
public class A extends Base{
//4個屬性
//public int n1 = 100;
protected int n2 = 200;
int 3 = 300;
private int n4 = 400;
public A() {}
public A(String name) {}
public A(String name, int age) {}
// public void cal() {
// System.out.println("A類的cal() 方法...");
// }
public void test100() {
}
protected void test200() {
}
void test300() {
}
private void test400() {
}
}
cal() 和 this.cal() 相同,就近原則。
super.cal() 的順序是直接查詢父類,其他的規則一樣
package com.hspedu.super_;
public class B extends A {
public int n1 = 888;
//編寫測試方法
public void test() {
//super的訪問不限於直接父類,如果爺爺類和本類中有同名的成員,也可以使用super去訪問爺爺類的成員;
// 如果多個基類(上級類)中都有同名的成員,使用super訪問遵循就近原則。A->B->C
System.out.println("super.n1=" + super.n1);
super.cal();
}
//訪問父類的屬性 , 但不能訪問父類的private屬性 [案例]super.屬性名
public void hi() {
System.out.println(super.n1 + " " + super.n2 + " " + super.n3 );
}
public void cal() {
System.out.println("B類的cal() 方法...");
}
public void sum() {
System.out.println("B類的sum()");
//希望呼叫父類-A 的cal方法
//這時,因為子類B沒有cal方法,因此我可以使用下面三種方式
// !找cal方法時(cal() 和 this.cal()),順序是:
// (1)先找本類,如果有,則呼叫
// (2)如果沒有,則找父類(如果有,並可以呼叫,則呼叫)
// (3)如果父類沒有,則繼續找父類的父類,整個規則,就是一樣的,直到 Object類
// 提示:如果查詢方法的過程中,找到了,但是不能訪問, 則報錯, cannot access
// 如果查詢方法的過程中,沒有找到,則提示方法不存在
//cal();
this.cal(); //等價 cal
// !找cal方法(super.call()) 的順序是直接查詢父類,其他的規則一樣
//super.cal();
//演示訪問屬性的規則
// !n1 和 this.n1 查詢的規則是
//(1) 先找本類,如果有,則呼叫
//(2) 如果沒有,則找父類(如果有,並可以呼叫,則呼叫)
//(3) 如果父類沒有,則繼續找父類的父類,整個規則,就是一樣的,直到 Object類
// 提示:如果查詢屬性的過程中,找到了,但是不能訪問, 則報錯, cannot access
// 如果查詢屬性的過程中,沒有找到,則提示屬性不存在
System.out.println(n1);
System.out.println(this.n1);
// !找n1 (super.n1) 的順序是直接查詢父類屬性,其他的規則一樣
System.out.println(super.n1);
}
//訪問父類的方法,不能訪問父類的private方法 super.方法名(引數列表);
public void ok() {
super.test100();
super.test200();
super.test300();
//super.test400();//不能訪問父類private方法
}
//訪問父類的構造器(這點前面用過):super(引數列表);只能放在構造器的第一句,只能出現一句!
public B() {
//super();
//super("jack", 10);
super("jack");
}
}
super 給程式設計帶來的便利/細節
-
呼叫父類的構造器的好處(分工明確,父類屬性由父類初始化,子類的屬性由子
類初始化) -
當子類中有和父類中的成員(屬性和方法)重名時,為了訪問父類的成員,必須
透過super。如果沒有重名,使用super、this、直接訪問是一樣的效果! -
super的訪問不限於直接父類,如果爺爺類和本類中有同名的成員,也可以使用
super去訪問爺爺類的成員; 如果多個基類(上級類)中都有同名的成員,使用super訪問遵循就近原則。A->B->C,當然也需要遵守訪問許可權的相關規則
super 和this 的比較
方法重寫/覆蓋(override)
簡單的說:方法覆蓋(重寫)就是子類有一個方法, 和父類的某個方法的名稱、返回型別、引數一樣,那麼我們就說子類的這個方法覆蓋了父類的方法。
注意事項和使用細節
方法重寫也叫方法覆蓋,需要滿足下面的條件
-
子類的方法的形參列表,方法名稱,要和父類方法的形參列表方法名稱完全一樣。
-
子類方法的返回型別和父類方法返回型別一樣,或者是父類返回型別的子類
比如交類返回型別是Object,子類方法返回型別是String
public object getInfo()
public String getInfo() -
子類方法不能縮小父類方法的訪問許可權。最好大於等於父類的許可權。
public > protected > 預設>private
重寫和過載比較!
物件導向程式設計-多型
傳統的方法帶來的問題是什麼?
程式碼的複用性不高,而且不利於程式碼維護
解決方案: 引出我們要講解的多型
多[多種]態[狀態]基本介紹
方法或物件具有多種形態。是物件導向的第三大特徵,多型是建立在封裝和繼承基礎之上的。
多型的具體體現
方法的多型
重寫和過載就體現多型
物件的多型!
(1) 一個物件的編譯型別和執行型別可以不一致
(2) 編譯型別在定義物件時,就確定了,不能改變
(3) 執行型別是可以變化的.
(4) 編譯型別看定義時 = 號的左邊,執行型別看 = 號的右邊
Animal animal = new Dog() // animal編譯型別是Animal,執行型別Dog
animal = new Cat();// animal的執行型別變成了Cat,編譯型別仍然是 Animal
多型注意事項和細節討論
多型的前提是:兩個物件(類)存在繼承關係
多型的向上轉型
-
本質:父類的引用指向了子類的物件
-
語法:
父類型別引用名=new子類型別();
-
特點:編譯型別看左邊,執行型別看右邊。
可以呼叫父類中的所有成員(需遵守訪問許可權),不能呼叫子類中特有成員;
最終執行效果看子類的具體實現! (執行時看執行型別,例如找方法時就是採用就近原則)因為在編譯階段,能呼叫哪些成員,是由編譯型別決定的。
多型向下轉型
語法: 子類型別 引用名 = (子類型別) 父類引用;
-
只能強轉父類的引用,不能強轉父類的物件
-
要求父類的引用必須指向的是當前目標型別的物件
-
當向下轉型後,可以呼叫子類型別中所有的成員
package com.hspedu.poly_.detail_;
public class PolyDetail {
public static void main(String[] args) {
//向上轉型: 父類的引用指向了子類的物件
//語法:父類型別引用名 = new 子類型別();
Animal animal = new Cat();
Object obj = new Cat();//可以嗎? 可以 Object 也是 Cat的父類
//向上轉型呼叫方法的規則如下:
//(1)可以呼叫父類中的所有成員(需遵守訪問許可權)
//(2)但是不能呼叫子類的特有的成員
//(#)因為在編譯階段,能呼叫哪些成員,是由編譯型別來決定的
//animal.catchMouse();錯誤
//(4)最終執行效果看子類(執行型別)的具體實現, 即呼叫方法時,按照從子類(執行型別)開始查詢方法
//,然後呼叫,規則我前面我們講的方法呼叫規則一致。
animal.eat();//貓吃魚..
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡
//老師希望,可以呼叫Cat的 catchMouse方法
//多型的向下轉型
//(1)語法:子類型別 引用名 =(子類型別)父類引用;
//問一個問題? cat 的編譯型別 Cat,執行型別是 Cat
Cat cat = (Cat) animal;
cat.catchMouse();//貓抓老鼠
//(2)要求父類的引用必須指向的是當前目標型別的物件
// animal 本來建立時就指向 cat物件
// 後面 animal 向下轉型 cat 指向 cat物件
Dog dog = (Dog) animal;//可以嗎? 錯誤!
System.out.println("ok~~");
}
}
屬性沒有重寫之說
屬性沒有重寫之說!屬性的值看編譯型別
package com.hspedu.poly_.detail_;
public class PolyDetail02 {
public static void main(String[] args) {
//屬性沒有重寫之說!屬性的值看編譯型別
Base base = new Sub();//向上轉型
System.out.println(base.count);// ? 看編譯型別 10
Sub sub = new Sub();
System.out.println(sub.count);//? 20
}
}
class Base { //父類
int count = 10;//屬性
}
class Sub extends Base {//子類
int count = 20;//屬性
}
instanceOf 比較運算子
用於判斷物件的 執行型別 是否為 XX 型別 或 XX 型別的子型別
package com.hspedu.poly_.detail_;
public class PolyDetail03 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);// true
//aa 編譯型別 AA, 執行型別是BB
//BB是AA子類
AA aa = new BB();
System.out.println(aa instanceof AA); // true
System.out.println(aa instanceof BB); // true
Object obj = new Object();
System.out.println(obj instanceof AA);//false
String str = "hello";
//System.out.println(str instanceof AA);
System.out.println(str instanceof Object);//true
}
}
class AA {} //父類
class BB extends AA {}//子類
java 的動態繫結機制!!
1.當呼叫物件方法的時候,該方法會和該物件的記憶體地址/執行型別繫結
2.當呼叫物件屬性時,沒有動態繫結機制,哪裡宣告,哪裡使用,找不到再去父類中尋找。
package com.hspedu.poly_.dynamic_;
public class DynamicBinding {
public static void main(String[] args) {
//a 的編譯型別 A, 執行型別 B
A a = new B();//向上轉型
System.out.println(a.sum()); //?40 -> 30 (20 + 10)
System.out.println(a.sum1());//?30 -> 20 (10 + 10)
}
}
class A {//父類
public int i = 10;
//動態繫結機制:
public int sum() {//父類sum()
return getI() + 10;//20 + 10
}
public int sum1() {//父類sum1()
return i + 10;//10 + 10
}
public int getI() {//父類getI
return i;
}
}
class B extends A {//子類
public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI() {//子類getI()
return i;
}
// public int sum1() {
// return i + 10;
// }
}
多型的應用
多型陣列
陣列的定義型別為父類型別,裡面儲存的實際元素型別為子類型別。
應用例項:現有一個繼承結構如下:要求建立1 個Person 物件、2 個Student 物件和2 個Teacher 物件, 統一放在陣列中,並呼叫每個物件 say 方法.
應用例項升級:如何呼叫子類特有的方法,比如Teacher 有一個teach , Student 有一個study ,怎麼呼叫?
package com.hspedu.poly_.polyarr_;
public class PloyArray {
public static void main(String[] args) {
//應用例項:現有一個繼承結構如下:要求建立1個Person物件、
// 2個Student 物件和2個Teacher物件, 統一放在陣列中,並呼叫每個物件say方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
//迴圈遍歷多型陣列,呼叫say
for (int i = 0; i < persons.length; i++) {
//老師提示: person[i] 編譯型別是 Person ,執行型別是是根據實際情況有JVM來判斷
System.out.println(persons[i].say());//動態繫結機制
// 這裡聰明. 使用 型別判斷 + 向下轉型.!!!!
if(persons[i] instanceof Student) {//判斷person[i] 的執行型別是不是Student
Student student = (Student)persons[i];//向下轉型
student.study();
//小夥伴也可以使用一條語句 ((Student)persons[i]).study();
} else if(persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
} else if(persons[i] instanceof Person){
//System.out.println("你的型別有誤, 請自己檢查...");
} else {
System.out.println("你的型別有誤, 請自己檢查...");
}
}
}
}
多型引數
方法定義的形參型別為父類型別,實參型別允許為子類型別應用例項
定義員工類Employee,包含姓名和月工資[private],以及計算年工資getAnnual的方法。普通員工和經理繼承了員工,經理類多了獎金bonus屬性和管理manage方法,普通員工類多了work方法,普通員工和經理類要求分別重寫getAnnual方法
測試類中新增一個方法showEmpAnnual(Employee e),實現獲取任何員工物件的年工資,並在main方法中呼叫該方法[e.getAnnual()]
測試類中新增一個方法,testWork,如果是普通員工,則呼叫work方法,如果是經理,則呼叫manage方法
package com.hspedu.poly_.polyarr_;
public class Person {//父類
private String name;
private int age;
public Person(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;
}
public String say() {//返回名字和年齡
return name + "\t" + age;
}
}
package com.hspedu.poly_.polyarr_;
public class Student extends Person {
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
//重寫父類say
@Override
public String say() {
return "學生 " + super.say() + " score=" + score;
}
//特有的方法
public void study() {
System.out.println("學生 " + getName() + " 正在學java...");
}
}
package com.hspedu.poly_.polyarr_;
public class Teacher extends Person {
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//寫重寫父類的say方法
@Override
public String say() {
return "老師 " + super.say() + " salary=" + salary;
}
//特有方法
public void teach() {
System.out.println("老師 " + getName() + " 正在講java課程...");
}
}
package com.hspedu.poly_.polyarr_;
public class PloyArray {
public static void main(String[] args) {
//應用例項:現有一個繼承結構如下:要求建立1個Person物件、
// 2個Student 物件和2個Teacher物件, 統一放在陣列中,並呼叫每個物件say方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
//迴圈遍歷多型陣列,呼叫say
for (int i = 0; i < persons.length; i++) {
//老師提示: person[i] 編譯型別是 Person ,執行型別是是根據實際情況有JVM來判斷
System.out.println(persons[i].say());//動態繫結機制
//這裡大家聰明. 使用 型別判斷 + 向下轉型.
if(persons[i] instanceof Student) {//判斷person[i] 的執行型別是不是Student
Student student = (Student)persons[i];//向下轉型
student.study();
//小夥伴也可以使用一條語句 ((Student)persons[i]).study();
} else if(persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
} else if(persons[i] instanceof Person){
//System.out.println("你的型別有誤, 請自己檢查...");
} else {
System.out.println("你的型別有誤, 請自己檢查...");
}
}
}
}
Object 類詳解
equals 方法
==和equals 的對比!!!!!
==是一個比較運算子
==:既可以判斷基本型別,又可以判斷引用型別
-
==:如果判斷基本型別,判斷的是值是否相等。示例: int i=10; double d=10.0;
-
==:如果判斷引用型別,判斷的是地址是否相等,即判定是不是同一個物件
equals:是Object類中的方法,只能判斷引用型別,看Jdk原始碼。(方法:游標放在方法上。然後按 ctrl + B
進行檢視原始碼)
- 預設判斷的是地址是否相等,子類中往往重寫該方法,用於判斷內容是否相等。比如 Integer,String【看看String和 Integer的equals原始碼】
package com.hspedu.object_;
public class Equals01 {
public static void main(String[] args) {
A a = new A();
A b = a;
A c = b;
System.out.println(a == c);//true
System.out.println(b == c);//true
// 編譯型別是B,但是本質上還是一個地址指向a
B bObj = a;
System.out.println(bObj == c);//true
int num1 = 10;
double num2 = 10.0;
System.out.println(num1 == num2);// 基本資料型別,判斷值是否相等
//equals 方法,原始碼怎麼檢視.
//把游標放在equals方法,直接輸入ctrl+b
//如果你使用不了. 自己配置. 即可使用.
/*
//帶大家看看Jdk的原始碼 String類的 equals方法
//把Object的equals方法重寫了,變成了比較兩個字串值是否相同
public boolean equals(Object anObject) {
if (this == anObject) {//如果是同一個物件
return true;//返回true
}
if (anObject instanceof String) {//判斷型別
String anotherString = (String)anObject;//向下轉型
int n = value.length;
if (n == anotherString.value.length) {//如果長度相同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {//然後一個一個的比較字元
if (v1[i] != v2[i])
return false;
i++;
}
return true;//如果兩個字串的所有字元都相等,則返回true
}
}
return false;//如果比較的不是字串,則直接返回false
}
*/
"hello".equals("abc");
//看看Object類的 equals 是
/*
//即Object 的equals 方法預設就是比較物件地址是否相同
//也就是判斷兩個物件是不是同一個物件.
public boolean equals(Object obj) {
return (this == obj);
}
*/
/*
//從原始碼可以看到 Integer 也重寫了Object的equals方法,
//變成了判斷兩個值是否相同
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
*/
Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
System.out.println(integer1 == integer2);//false
System.out.println(integer1.equals(integer2));//true
String str1 = new String("hspedu");
String str2 = new String("hspedu");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
}
}
class B {}
class A extends B {}
如何重寫 equals 方法
應用例項: 判斷兩個Person 物件的內容是否相等,如果兩個Person 物件的各個屬性值都一樣,則返回true,反之false。
檢視:com.hspedu.object_ EqualsExercise01
hashCode 方法
public int hashCode()
返回該物件的雜湊碼值。支援此方法是為了提高雜湊表(例如java.util.Hashtable
提供的雜湊表)的效能。
hashCode
的常規協定是:
- 在 Java 應用程式執行期間,在對同一物件多次呼叫 hashCode 方法時,必須一致地返回相同的整數,前提是將物件進行 equals 比較時所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一致。
- 如果根據 equals(Object) 方法,兩個物件是相等的,那麼對這兩個物件中的每個物件呼叫
hashCode
方法都必須生成相同的整數結果。 - 如果根據
equals(java.lang.Object)
方法,兩個物件不相等,那麼對這兩個物件中的任一物件上呼叫 hashCode 方法不
要求一定生成不同的整數結果。但是,程式設計師應該意識到,為不相等的物件生成不同整數結果可以提高雜湊表的效能。
實際上,由 Object 類定義的 hashCode
方法確實會針對不同的物件返回不同的整數。(這一般是透過將該物件的內部地址轉換成一個整數來實現的,但是 JavaTM 程式語言不需要這種實現技巧。)
返回:
此物件的一個雜湊碼值。
另請參見:
[equals(java.lang.Object)
], [Hashtable
]
總結
- 提高具有雜湊結構的容器的效率!
- 兩個引用,如果指向的是同一個物件,則雜湊值肯定是一樣的!
- 兩個引用,如果指向的是不同物件,則雜湊值是不一樣的(當然也可能存在碰撞)
- 雜湊值主要根據地址號來的! 不能完全將雜湊值等價於地址。(java跑在JVM上,無法真正拿到其內部地址)。
- 後面在集合,中hashCode 如果需要的話,也會重寫, 在講解集合時,具體看如何重寫hashCode()程式碼。
toString 方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
-
基本介紹
預設返回:全類名+@+雜湊值的十六進位制,子類往往重寫toString 方法,用於返回物件的屬性資訊(全類名就是包名 + 類名) -
重寫toString 方法,列印物件或拼接物件時,都會自動呼叫該物件的toString 形式.
-
當直接輸出一個物件時, toString 方法會被預設的呼叫, 比如System.out.println(monster) ;// 就會預設呼叫monster.toString()
finalize 方法
- 當物件被回收時,系統自動呼叫該物件的finalize 方法。子類可以重寫該方法,做一些釋放資源(資料庫的連線,開啟或者關閉檔案)的操作。
- 什麼時候被回收:當某個物件沒有任何引用時,則jvm 就認為這個物件是一個垃圾物件,就會使用垃圾回收機制來銷燬該物件,在銷燬該物件前,會先呼叫finalize 方法。(當然並不是一有垃圾就立馬回收,有對應的垃圾回收GC演算法)。
- 垃圾回收機制的呼叫,是由系統來決定(即有自己的GC演算法), 也可以透過System.gc() 主動觸發垃圾回收機制。
- 我們在實際開發中,幾乎不會運用finalize , 所以更多就是為了應付面試。
package com.hspedu.object_;
//演示 Finalize的用法
public class Finalize_ {
public static void main(String[] args) {
Car bmw = new Car("寶馬");
//這時 car物件就是一個垃圾,垃圾回收器就會回收(銷燬)物件, 在銷燬物件前,會呼叫該物件的finalize方法
//,程式設計師就可以在 finalize中,寫自己的業務邏輯程式碼(比如釋放資源:資料庫連線,或者開啟檔案..)
//,如果程式設計師不重寫 finalize,那麼就會呼叫 Object類的 finalize, 即預設處理
//,如果程式設計師重寫了 finalize, 就可以實現自己的邏輯
bmw = null;
System.gc();//主動呼叫垃圾回收器
System.out.println("程式退出了....");
}
}
class Car {
private String name;
//屬性, 資源。。
public Car(String name) {
this.name = name;
}
//重寫finalize
@Override
protected void finalize() throws Throwable {
System.out.println("我們銷燬 汽車" + name );
System.out.println("釋放了某些資源...");
}
}
斷點除錯(debug)
在斷點除錯過程中,是執行狀態,是以物件的執行型別來執行的.
A extends B; Bb = new A(); b.xx();
斷點除錯介紹
斷點除錯是指在程式的某一行設定一個斷點,除錯時,程式執行到這一行就會停住,然後你可以一步一步往下除錯,除錯過程中可以看各個變數當前的值,出錯的話,除錯到出錯的程式碼行即顯示錯誤,停下。進行分析從而找到這個Bug。
斷點除錯也能幫助我們檢視java底層原始碼的執行過程。
斷點除錯的快捷鍵
F7(跳入) F8(跳過) shift+F8(跳出) F9(resume,執行到下一個斷點)
F7:跳入方法內
F8: 逐行執行程式碼.
shift+F8: 跳出方法
Idea debug進入 Jdk原始碼
方法1:
使用force step into : 快捷鍵 alt + shift + F7
方法2:
這個配置一下就好了:點選Setting --> Build,Execution,Deployment --> Debugger --> Stepping 把Do not step into the classes中的java.,javax.取消勾選。
斷點可以在debug 過程中,動態的下斷點。(看複雜邏輯時常用)
專案-零錢通
package com.hspedu.smallchange;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class SmallChangeSys {
//化繁為簡
//1. 先完成顯示選單,並可以選擇選單,給出對應提示
//2. 完成零錢通明細
//3. 完成收益入賬
//4. 消費
//5. 退出
//6. 使用者輸入4退出時,給出提示"你確定要退出嗎? y/n",必須輸入正確的y/n ,否則迴圈輸入指令,直到輸入y 或者 n
//7. 在收益入賬和消費時,判斷金額是否合理,並給出相應的提示
public static void main(String[] args) {
//定義相關的變數
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
//2. 完成零錢通明細
//老韓思路, (1) 可以把收益入賬和消費,儲存到陣列 (2) 可以使用物件 (3) 簡單的話可以使用String拼接
String details = "-----------------零錢通明細------------------";
//3. 完成收益入賬 完成功能驅動程式設計師增加新的變化和程式碼
//老韓思路, 定義新的變數
double money = 0;
double balance = 0;
Date date = null; // date 是 java.util.Date 型別,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); //可以用於日期格式化的
//4. 消費
//定義新變數,儲存消費的原因
String note = "";
do {
System.out.println("\n================零錢通選單===============");
System.out.println("\t\t\t1 零錢通明細");
System.out.println("\t\t\t2 收益入賬");
System.out.println("\t\t\t3 消費");
System.out.println("\t\t\t4 退 出");
System.out.print("請選擇(1-4): ");
key = scanner.next();
//使用switch 分支控制
switch (key) {
case "1":
System.out.println(details);
break;
case "2":
System.out.print("收益入賬金額:");
money = scanner.nextDouble();
//money 的值範圍應該校驗 -》 一會在完善
//老師思路, 程式設計思想
//找出不正確的金額條件,然後給出提示, 就直接break
if(money <= 0) {
System.out.println("收益入賬金額 需要 大於 0");
break;
}
//找出正確金額的條件
balance += money;
//拼接收益入賬資訊到 details
date = new Date(); //獲取當前日期
details += "\n收益入賬\t+" + money + "\t" + sdf.format(date) + "\t" + balance;
break;
case "3":
System.out.print("消費金額:");
money = scanner.nextDouble();
//money 的值範圍應該校驗 -》 一會在完善
//找出金額不正確的情況
//過關斬將 校驗方式.
if(money <= 0 || money > balance) {
System.out.println("你的消費金額 應該在 0-" + balance);
break;
}
System.out.print("消費說明:");
note = scanner.next();
balance -= money;
//拼接消費資訊到 details
date = new Date(); //獲取當前日期
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t" + balance;
break;
case "4":
//使用者輸入4退出時,給出提示"你確定要退出嗎? y/n",必須輸入正確的y/n ,
// 否則迴圈輸入指令,直到輸入y 或者 n
// 老韓思路分析
// (1) 定義一個變數 choice, 接收使用者的輸入
// (2) 使用 while + break, 來處理接收到的輸入時 y 或者 n
// (3) 退出while後,再判斷choice是y還是n ,就可以決定是否退出
// (4) 建議一段程式碼,完成一個小功能,儘量不要混在一起
String choice = "";
while (true) { //要求使用者必須輸入y/n ,否則就一直迴圈
System.out.println("你確定要退出嗎? y/n");
choice = scanner.next();
if ("y".equals(choice) || "n".equals(choice)) {
break;
}
//第二個方案
// if("y".equals(choice)) {
// loop = false;
// break;
// } else if ("n".equals(choice)) {
// break;
// }
}
//當使用者退出while ,進行判斷
if (choice.equals("y")) {
loop = false;
}
break;
default:
System.out.println("選擇有誤,請重新選擇");
}
} while (loop);
System.out.println("-----退出了零錢通專案-----");
}
}
改成OOP 版本,體會OOP 程式設計帶來的好處
package com.hspedu.smallchange.oop;
/**
* 這裡我們直接呼叫SmallChangeSysOOP 物件,顯示主選單即可
*/
public class SmallChangeSysApp {
public static void main(String[] args) {
System.out.println("====hello公司====");
new SmallChangeSysOOP().mainMenu();
}
}
package com.hspedu.smallchange.oop;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/**
* 該類是完成零錢通的各個功能的類
* 使用OOP(物件導向程式設計)
* 將各個功能對應一個方法.
*/
public class SmallChangeSysOOP {
//屬性..
//定義相關的變數
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
//2. 完成零錢通明細
//老韓思路, (1) 可以把收益入賬和消費,儲存到陣列 (2) 可以使用物件 (3) 簡單的話可以使用String拼接
String details = "-----------------零錢通明細------------------";
//3. 完成收益入賬 完成功能驅動程式設計師增加新的變化和程式碼
//老韓思路, 定義新的變數
double money = 0;
double balance = 0;
Date date = null; // date 是 java.util.Date 型別,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); //可以用於日期格式化的
//4. 消費
//定義新變數,儲存消費的原因
String note = "";
//先完成顯示選單,並可以選擇
public void mainMenu() {
do {
System.out.println("\n================零錢通選單(OOP)===============");
System.out.println("\t\t\t1 零錢通明細");
System.out.println("\t\t\t2 收益入賬");
System.out.println("\t\t\t3 消費");
System.out.println("\t\t\t4 退 出");
System.out.print("請選擇(1-4): ");
key = scanner.next();
//使用switch 分支控制
switch (key) {
case "1":
this.detail();
break;
case "2":
this.income();
break;
case "3":
this.pay();
break;
case "4":
this.exit();
break;
default:
System.out.println("選擇有誤,請重新選擇");
}
} while (loop);
}
//完成零錢通明細
public void detail() {
System.out.println(details);
}
//完成收益入賬
public void income() {
System.out.print("收益入賬金額:");
money = scanner.nextDouble();
//money 的值範圍應該校驗 -》 一會在完善
//老師思路, 程式設計思想
//找出不正確的金額條件,然後給出提示, 就直接return
if(money <= 0) {
System.out.println("收益入賬金額 需要 大於 0");
return; //退出方法,不在執行後面的程式碼。
}
//找出正確金額的條件
balance += money;
//拼接收益入賬資訊到 details
date = new Date(); //獲取當前日期
details += "\n收益入賬\t+" + money + "\t" + sdf.format(date) + "\t" + balance;
}
//消費
public void pay() {
System.out.print("消費金額:");
money = scanner.nextDouble();
//money 的值範圍應該校驗 -》 一會在完善
//找出金額不正確的情況
//過關斬將 校驗方式.
if(money <= 0 || money > balance) {
System.out.println("你的消費金額 應該在 0-" + balance);
return;
}
System.out.print("消費說明:");
note = scanner.next();
balance -= money;
//拼接消費資訊到 details
date = new Date(); //獲取當前日期
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t" + balance;
}
//退出
public void exit() {
//使用者輸入4退出時,給出提示"你確定要退出嗎? y/n",必須輸入正確的y/n ,
// 否則迴圈輸入指令,直到輸入y 或者 n
// 老韓思路分析
// (1) 定義一個變數 choice, 接收使用者的輸入
// (2) 使用 while + break, 來處理接收到的輸入時 y 或者 n
// (3) 退出while後,再判斷choice是y還是n ,就可以決定是否退出
// (4) 建議一段程式碼,完成一個小功能,儘量不要混在一起
String choice = "";
while (true) { //要求使用者必須輸入y/n ,否則就一直迴圈
System.out.println("你確定要退出嗎? y/n");
choice = scanner.next();
if ("y".equals(choice) || "n".equals(choice)) {
break;
}
//第二個方案
// if("y".equals(choice)) {
// loop = false;
// break;
// } else if ("n".equals(choice)) {
// break;
// }
}
//當使用者退出while ,進行判斷
if (choice.equals("y")) {
loop = false;
}
}
}