第一章 抽象類
1.1 概述
1.1.1 抽象類引入
父類中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那麼父類的方法宣告和方法主體,只有宣告還有意義,而方法主體則沒有存在的意義了(因為子類物件會呼叫自己重寫的方法)。換句話說,父類可能知道子類應該有哪個功能,但是功能具體怎麼實現父類是不清楚的(由子類自己決定),父類只需要提供一個沒有方法體的定義即可,具體實現交給子類自己去實現。我們把沒有方法體的方法稱為抽象方法。Java語法規定,包含抽象方法的類就是抽象類。
- 抽象方法 : 沒有方法體的方法。
- 抽象類:包含抽象方法的類。
1.2 abstract使用格式
abstract是抽象的意思,用於修飾方法方法和類,修飾的方法是抽象方法,修飾的類是抽象類。
1.2.1 抽象方法
使用abstract
關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。
定義格式:
修飾符 abstract 返回值型別 方法名 (引數列表);
程式碼舉例:
public abstract void run();
1.2.2 抽象類
如果一個類包含抽象方法,那麼該類必須是抽象類。注意:抽象類不一定有抽象方法,但是有抽象方法的類必須定義成抽象類。
定義格式:
abstract class 類名字 {
}
程式碼舉例:
public abstract class Animal {
public abstract void run();
}
1.2.3 抽象類的使用
要求:繼承抽象類的子類必須重寫父類所有的抽象方法。否則,該子類也必須宣告為抽象類。
程式碼舉例:
// 父類,抽象類
abstract class Employee {
private String id;
private String name;
private double salary;
public Employee() {
}
public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// 抽象方法
// 抽象方法必須要放在抽象類中
abstract public void work();
}
// 定義一個子類繼承抽象類
class Manager extends Employee {
public Manager() {
}
public Manager(String id, String name, double salary) {
super(id, name, salary);
}
// 2.重寫父類的抽象方法
@Override
public void work() {
System.out.println("管理其他人");
}
}
// 定義一個子類繼承抽象類
class Cook extends Employee {
public Cook() {
}
public Cook(String id, String name, double salary) {
super(id, name, salary);
}
@Override
public void work() {
System.out.println("廚師炒菜多加點鹽...");
}
}
// 測試類
public class Demo10 {
public static void main(String[] args) {
// 建立抽象類,抽象類不能建立物件
// 假設抽象類讓我們建立物件,裡面的抽象方法沒有方法體,無法執行.所以不讓我們建立物件
// Employee e = new Employee();
// e.work();
// 3.建立子類
Manager m = new Manager();
m.work();
Cook c = new Cook("ap002", "庫克", 1);
c.work();
}
}
此時的方法重寫,是子類對父類抽象方法的完成實現,我們將這種方法重寫的操作,也叫做實現方法。
1.3 抽象類的特徵
抽象類的特徵總結起來可以說是 有得有失
有得:抽象類得到了擁有抽象方法的能力。
有失:抽象類失去了建立物件的能力。
其他成員(構造方法,例項方法,靜態方法等)抽象類都是具備的。
1.4 抽象類的細節
不需要背,只要當idea報錯之後,知道如何修改即可。
關於抽象類的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。
抽象類不能建立物件,如果建立,編譯無法透過而報錯。只能建立其非抽象子類的物件。
理解:假設建立了抽象類的物件,呼叫抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
抽象類中,可以有構造方法,是供子類建立物件時,初始化父類成員使用的。
理解:子類的構造方法中,有預設的super(),需要訪問父類構造方法。
抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
理解:未包含抽象方法的抽象類,目的就是不想讓呼叫者建立該類物件,通常用於某些特殊的類結構設計。
抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則子類也必須定義成抽象類,編譯無法透過而報錯。
理解:假設不重寫所有抽象方法,則類中可能包含抽象方法。那麼建立物件後,呼叫抽象的方法,沒有意義。
抽象類存在的意義是為了被子類繼承。
理解:抽象類中已經實現的是模板中確定的成員,抽象類不確定如何實現的定義成抽象方法,交給具體的子類去實現。
1.5 抽象類存在的意義
抽象類存在的意義是為了被子類繼承,否則抽象類將毫無意義。抽象類可以強制讓子類,一定要按照規定的格式進行重寫。
第二章 介面
2.1 概述
我們已經學完了抽象類,抽象類中可以用抽象方法,也可以有普通方法,構造方法,成員變數等。那麼什麼是介面呢?介面是更加徹底的抽象,JDK7之前,包括JDK7,介面中全部是抽象方法。介面同樣是不能建立物件的。
2.2 定義格式
//介面的定義格式:
interface 介面名稱{
// 抽象方法
}
// 介面的宣告:interface
// 介面名稱:首字母大寫,滿足“駝峰模式”
2.3 介面成分的特點
在JDK7,包括JDK7之前,介面中的只有包含:抽象方法和常量
2.3.1.抽象方法
注意:介面中的抽象方法預設會自動加上public abstract修飾程式設計師無需自己手寫!!
按照規範:以後介面中的抽象方法建議不要寫上public abstract。因為沒有必要啊,預設會加上。
2.3.2 常量
在介面中定義的成員變數預設會加上: public static final修飾。也就是說在介面中定義的成員變數實際上是一個常量。這裡是使用public static final修飾後,變數值就不可被修改,並且是靜態化的變數可以直接用介面名訪問,所以也叫常量。常量必須要給初始值。常量命名規範建議字母全部大寫,多個單詞用下劃線連線。
2.3.3 案例演示
public interface InterF {
// 抽象方法!
// public abstract void run();
void run();
// public abstract String getName();
String getName();
// public abstract int add(int a , int b);
int add(int a , int b);
// 它的最終寫法是:
// public static final int AGE = 12 ;
int AGE = 12; //常量
String SCHOOL_NAME = "黑馬程式設計師";
}
2.4 基本的實現
2.4.1 實現介面的概述
類與介面的關係為實現關係,即類實現介面,該類可以稱為介面的實現類,也可以稱為介面的子類。實現的動作類似繼承,格式相仿,只是關鍵字不同,實現使用 implements
關鍵字。
2.4.2 實現介面的格式
/**介面的實現:
在Java中介面是被實現的,實現介面的類稱為實現類。
實現類的格式:*/
class 類名 implements 介面1,介面2,介面3...{
}
從上面格式可以看出,介面是可以被多實現的。大家可以想一想為什麼呢?
2.4.3 類實現介面的要求和意義
- 必須重寫實現的全部介面中所有抽象方法。
- 如果一個類實現了介面,但是沒有重寫完全部介面的全部抽象方法,這個類也必須定義成抽象類。
- 意義:介面體現的是一種規範,介面對實現類是一種強制性的約束,要麼全部完成介面申明的功能,要麼自己也定義成抽象類。這正是一種強制性的規範。
2.4.4 類與介面基本實現案例
假如我們定義一個運動員的介面(規範),程式碼如下:
/**
介面:介面體現的是規範。
* */
public interface SportMan {
void run(); // 抽象方法,跑步。
void law(); // 抽象方法,遵守法律。
String compittion(String project); // 抽象方法,比賽。
}
接下來定義一個乒乓球運動員類,實現介面,實現介面的實現類程式碼如下:
package com.itheima._03介面的實現;
/**
* 介面的實現:
* 在Java中介面是被實現的,實現介面的類稱為實現類。
* 實現類的格式:
* class 類名 implements 介面1,介面2,介面3...{
*
*
* }
* */
public class PingPongMan implements SportMan {
@Override
public void run() {
System.out.println("乒乓球運動員稍微跑一下!!");
}
@Override
public void law() {
System.out.println("乒乓球運動員守法!");
}
@Override
public String compittion(String project) {
return "參加"+project+"得金牌!";
}
}
測試程式碼:
public class TestMain {
public static void main(String[] args) {
// 建立實現類物件。
PingPongMan zjk = new PingPongMan();
zjk.run();
zjk.law();
System.out.println(zjk.compittion("全球乒乓球比賽"));
}
}
2.4.5 類與介面的多實現案例
類與介面之間的關係是多實現的,一個類可以同時實現多個介面。
首先我們先定義兩個介面,程式碼如下:
/** 法律規範:介面*/
public interface Law {
void rule();
}
/** 這一個運動員的規範:介面*/
public interface SportMan {
void run();
}
然後定義一個實現類:
/**
* Java中介面是可以被多實現的:
* 一個類可以實現多個介面: Law, SportMan
*
* */
public class JumpMan implements Law ,SportMan {
@Override
public void rule() {
System.out.println("尊長守法");
}
@Override
public void run() {
System.out.println("訓練跑步!");
}
}
從上面可以看出類與介面之間是可以多實現的,我們可以理解成實現多個規範,這是合理的。
2.5 介面與介面的多繼承
Java中,介面與介面之間是可以多繼承的:也就是一個介面可以同時繼承多個介面。大家一定要注意:
類與介面是實現關係
介面與介面是繼承關係
介面繼承介面就是把其他介面的抽象方法與本介面進行了合併。
案例演示:
public interface Abc {
void go();
void test();
}
/** 法律規範:介面*/
public interface Law {
void rule();
void test();
}
*
* 總結:
* 介面與類之間是多實現的。
* 介面與介面之間是多繼承的。
* */
public interface SportMan extends Law , Abc {
void run();
}
2.6擴充套件:介面的細節
不需要背,只要當idea報錯之後,知道如何修改即可。
關於介面的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。
- 當兩個介面中存在相同抽象方法的時候,該怎麼辦?
只要重寫一次即可。此時重寫的方法,既表示重寫1介面的,也表示重寫2介面的。
- 實現類能不能繼承A類的時候,同時實現其他介面呢?
繼承的父類,就好比是親爸爸一樣
實現的介面,就好比是乾爹一樣
可以繼承一個類的同時,再實現多個介面,只不過,要把介面裡面所有的抽象方法,全部實現。
- 實現類能不能繼承一個抽象類的時候,同時實現其他介面呢?
實現類可以繼承一個抽象類的同時,再實現其他多個介面,只不過要把裡面所有的抽象方法全部重寫。
- 實現類Zi,實現了一個介面,還繼承了一個Fu類。假設在介面中有一個方法,父類中也有一個相同的方法。子類如何操作呢?
處理辦法一:如果父類中的方法體,能滿足當前業務的需求,在子類中可以不用重寫。
處理辦法二:如果父類中的方法體,不能滿足當前業務的需求,需要在子類中重寫。
- 如果一個介面中,有10個抽象方法,但是我在實現類中,只需要用其中一個,該怎麼辦?
可以在介面跟實現類中間,新建一箇中間類(介面卡類)
讓這個介面卡類去實現介面,對介面裡面的所有的方法做空重寫。
讓子類繼承這個介面卡類,想要用到哪個方法,就重寫哪個方法。
因為中間類沒有什麼實際的意義,所以一般會把中間類定義為抽象的,不讓外界建立物件
第三章 內部類
3.1 概述
3.1.1 什麼是內部類
將一個類A定義在另一個類B裡面,裡面的那個類A就稱為內部類,B則稱為外部類。可以把內部類理解成寄生,外部類理解成宿主。
3.1.2 什麼時候使用內部類
一個事物內部還有一個獨立的事物,內部的事物脫離外部的事物無法獨立使用
- 人裡面有一顆心臟。
- 汽車內部有一個發動機。
- 為了實現更好的封裝性。
3.2 內部類的分類
按定義的位置來分
- 成員內部內,類定義在了成員位置 (類中方法外稱為成員位置,無static修飾的內部類)
- 靜態內部類,類定義在了成員位置 (類中方法外稱為成員位置,有static修飾的內部類)
- 區域性內部類,類定義在方法內
- 匿名內部類,沒有名字的內部類,可以在方法中,也可以在類中方法外。
3.3 成員內部類
成員內部類特點:
- 無static修飾的內部類,屬於外部類物件的。
- 宿主:外部類物件。
內部類的使用格式:
外部類.內部類。 // 訪問內部類的型別都是用 外部類.內部類
獲取成員內部類物件的兩種方式:
方式一:外部直接建立成員內部類的物件
外部類.內部類 變數 = new 外部類().new 內部類();
方式二:在外部類中定義一個方法提供內部類的物件
案例演示
方式一:
public class Test {
public static void main(String[] args) {
// 宿主:外部類物件。
// Outer out = new Outer();
// 建立內部類物件。
Outer.Inner oi = new Outer().new Inner();
oi.method();
}
}
class Outer {
// 成員內部類,屬於外部類物件的。
// 擴充:成員內部類不能定義靜態成員。
public class Inner{
// 這裡面的東西與類是完全一樣的。
public void method(){
System.out.println("內部類中的方法被呼叫了");
}
}
}
方式二:
public class Outer {
String name;
private class Inner{
static int a = 10;
}
public Inner getInstance(){
return new Inner();
}
}
public class Test {
public static void main(String[] args) {
Outer o = new Outer();
System.out.println(o.getInstance());
}
}
3.4 成員內部類的細節
編寫成員內部類的注意點:
- 成員內部類可以被一些修飾符所修飾,比如: private,預設,protected,public,static等
- 在成員內部類裡面,JDK16之前不能定義靜態變數,JDK16開始才可以定義靜態變數。
- 建立內部類物件時,物件中有一個隱含的Outer.this記錄外部類物件的地址值。(請參見3.6節的記憶體圖)
詳解:
內部類被private修飾,外界無法直接獲取內部類的物件,只能透過3.3節中的方式二獲取內部類的物件
被其他許可權修飾符修飾的內部類一般用3.3節中的方式一直接獲取內部類的物件
內部類被static修飾是成員內部類中的特殊情況,叫做靜態內部類下面單獨學習。
內部類如果想要訪問外部類的成員變數,外部類的變數必須用final修飾,JDK8以前必須手動寫final,JDK8之後不需要手動寫,JDK預設加上。
3.5 成員內部類面試題
請在?地方向上相應程式碼,以達到輸出的內容
注意:內部類訪問外部類物件的格式是:外部類名.this
public class Test {
public static void main(String[] args) {
Outer.inner oi = new Outer().new inner();
oi.method();
}
}
class Outer { // 外部類
private int a = 30;
// 在成員位置定義一個類
class inner {
private int a = 20;
public void method() {
int a = 10;
System.out.println(???); // 10 答案:a
System.out.println(???); // 20 答案:this.a
System.out.println(???); // 30 答案:Outer.this.a
}
}
}
3.6 成員內部類記憶體圖
3.7 靜態內部類
靜態內部類特點:
- 靜態內部類是一種特殊的成員內部類。
- 有static修飾,屬於外部類本身的。
- 總結:靜態內部類與其他類的用法完全一樣。只是訪問的時候需要加上外部類.內部類。
- 擴充1:靜態內部類可以直接訪問外部類的靜態成員。
- 擴充2:靜態內部類不可以直接訪問外部類的非靜態成員,如果要訪問需要建立外部類的物件。
- 擴充3:靜態內部類中沒有銀行的Outer.this。
內部類的使用格式:
外部類.內部類。
靜態內部類物件的建立格式:
外部類.內部類 變數 = new 外部類.內部類構造器;
呼叫方法的格式:
- 呼叫非靜態方法的格式:先建立物件,用物件呼叫
- 呼叫靜態方法的格式:外部類名.內部類名.方法名();
案例演示:
// 外部類:Outer01
class Outer01{
private static String sc_name = "黑馬程式";
// 內部類: Inner01
public static class Inner01{
// 這裡面的東西與類是完全一樣的。
private String name;
public Inner01(String name) {
this.name = name;
}
public void showName(){
System.out.println(this.name);
// 擴充:靜態內部類可以直接訪問外部類的靜態成員。
System.out.println(sc_name);
}
}
}
public class InnerClassDemo01 {
public static void main(String[] args) {
// 建立靜態內部類物件。
// 外部類.內部類 變數 = new 外部類.內部類構造器;
Outer01.Inner01 in = new Outer01.Inner01("張三");
in.showName();
}
}
3.8 區域性內部類
- 區域性內部類 :定義在方法中的類。
定義格式:
class 外部類名 {
資料型別 變數名;
修飾符 返回值型別 方法名(引數列表) {
// …
class 內部類 {
// 成員變數
// 成員方法
}
}
}
3.9 匿名內部類【重點】
3.9.1 概述
匿名內部類 :是內部類的簡化寫法。他是一個隱含了名字的內部類。開發中,最常用到的內部類就是匿名內部類了。
3.9.2 格式
new 類名或者介面名() {
重寫方法;
};
包含了:
- 繼承或者實現關係
- 方法重寫
- 建立物件
所以從語法上來講,這個整體其實是匿名內部類物件
3.9.2 什麼時候用到匿名內部類
實際上,如果我們希望定義一個只要使用一次的類,就可考慮使用匿名內部類。匿名內部類的本質作用
是為了簡化程式碼。
之前我們使用介面時,似乎得做如下幾步操作:
- 定義子類
- 重寫介面中的方法
- 建立子類物件
- 呼叫重寫後的方法
interface Swim {
public abstract void swimming();
}
// 1. 定義介面的實現類
class Student implements Swim {
// 2. 重寫抽象方法
@Override
public void swimming() {
System.out.println("狗刨式...");
}
}
public class Test {
public static void main(String[] args) {
// 3. 建立實現類物件
Student s = new Student();
// 4. 呼叫方法
s.swimming();
}
}
我們的目的,最終只是為了呼叫方法,那麼能不能簡化一下,把以上四步合成一步呢?匿名內部類就是做這樣的快捷方式。
3.9.3 匿名內部類前提和格式
匿名內部類必須繼承一個父類或者實現一個父介面。
匿名內部類格式
new 父類名或者介面名(){
// 方法重寫
@Override
public void method() {
// 執行語句
}
};
3.9.4 使用方式
以介面為例,匿名內部類的使用,程式碼如下:
interface Swim {
public abstract void swimming();
}
public class Demo07 {
public static void main(String[] args) {
// 使用匿名內部類
new Swim() {
@Override
public void swimming() {
System.out.println("自由泳...");
}
}.swimming();
// 介面 變數 = new 實現類(); // 多型,走子類的重寫方法
Swim s2 = new Swim() {
@Override
public void swimming() {
System.out.println("蛙泳...");
}
};
s2.swimming();
s2.swimming();
}
}
3.9.5 匿名內部類的特點
- 定義一個沒有名字的內部類
- 這個類實現了父類,或者父類介面
- 匿名內部類會建立這個沒有名字的類的物件
3.9.6 匿名內部類的使用場景
通常在方法的形式引數是介面或者抽象類時,也可以將匿名內部類作為引數傳遞。程式碼如下:
interface Swim {
public abstract void swimming();
}
public class Demo07 {
public static void main(String[] args) {
// 普通方式傳入物件
// 建立實現類物件
Student s = new Student();
goSwimming(s);
// 匿名內部類使用場景:作為方法引數傳遞
Swim s3 = new Swim() {
@Override
public void swimming() {
System.out.println("蝶泳...");
}
};
// 傳入匿名內部類
goSwimming(s3);
// 完美方案: 一步到位
goSwimming(new Swim() {
public void swimming() {
System.out.println("大學生, 蛙泳...");
}
});
goSwimming(new Swim() {
public void swimming() {
System.out.println("小學生, 自由泳...");
}
});
}
// 定義一個方法,模擬請一些人去游泳
public static void goSwimming(Swim s) {
s.swimming();
}
}