- 第10章 物件導向程式設計(高階部分)
- 類變數和類方法
- 類變數-提出問題
- 類變數記憶體佈局
- 什麼是類變數
- 如何定義類變數
- 如何訪問類變數
- 類變數使用注意事項
- 類方法基本介紹
- 類方法的呼叫
- 類方法經典的使用場景
- 類方法使用注意事項和細節討論
- 理解main 方法語法
- 深入理解main 方法
- 特別提示
- 程式碼塊
- 基本介紹
- 基本語法
- 程式碼塊的好處和案例演示
- 程式碼塊使用注意事項和細節討論!!!
- 單例設計模式
- 什麼是設計模式
- 什麼是單例模式
- 餓漢式
- 懶漢式
- 比較
- final 關鍵字
- 基本介紹
- final 使用注意事項和細節討論
- 抽象類
- 引出
- 抽象類的介紹
- 抽象類使用的注意事項和細節討論
- 抽象類最佳實踐-模板設計模式
- 基本介紹
- 模板設計模式能解決的問題
- 最佳實踐
- 介面
- 基本介紹
- 深入討論
- 注意事項和細節
- 實現介面VS繼承類
- 介面的多型特性
- 內部類
- 基本介紹
- 基本語法
- 內部類的分類
- 區域性內部類的使用
- 匿名內部類的使用!!!!!
- 匿名內部類的最佳實踐
- 成員內部類的使用
- 靜態內部類的使用
- 課堂測試題
- 類變數和類方法
第10章 物件導向程式設計(高階部分)
類變數和類方法
類變數-提出問題
在main方法中定義一個變數count,當一個小孩加入遊戲後count++,最後個count 就記錄有多少小孩玩遊戲 。
問題分析:
count是一個獨立於物件,很尷尬,以後我們訪問count很麻煩,沒有使用到OOP。因此,我們引出類變數/靜態變數。
package com.hspedu.static_;
public class ChildGame {
public static void main(String[] args) {
//定義一個變數 count, 統計有多少小孩加入了遊戲
int count = 0;
Child child1 = new Child("白骨精");
child1.join();
//count++;
child1.count++;
Child child2 = new Child("狐狸精");
child2.join();
//count++;
child2.count++;
Child child3 = new Child("老鼠精");
child3.join();
//count++;
child3.count++;
//===========
// 類變數,可以透過類名來訪問
System.out.println("共有" + Child.count + " 小孩加入了遊戲...");
// 下面的程式碼輸出什麼?
System.out.println("child1.count=" + child1.count);//3
System.out.println("child2.count=" + child2.count);//3
System.out.println("child3.count=" + child3.count);//3
}
}
class Child { //類
private String name;
// 定義一個變數 count ,是一個類變數(靜態變數) static 靜態!!!
// 該變數最大的特點就是會被 Child 類的所有的物件例項共享!!!
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + " 加入了遊戲..");
}
}
類變數記憶體佈局
https://blog.csdn.net/x_iya/article/details/81260154/
https://www.zhihu.com/question/59174759/answer/163207831
有些書說在方法區... jdk 版本有關係,記住兩點:
(1) static變數是同一個類所有物件共享
(2) static類變數,在類載入的時候就生成了.靜態變數是類載入的時候,就建立了,所以不用建立物件例項也能直接透過 類名.類變數名
訪問。
什麼是類變數
類變數也叫靜態變數/靜態屬性,是該類的所有物件共享的變數,任何一個該類的物件去訪問它時,取到的都是相同的值,同樣任何一個該類的物件去修改它時,修改的也是同一個變數。這個從前面的圖也可看出來。
如何定義類變數
定義語法:
訪問修飾符static資料型別變數名;[推薦]
static訪問修飾符資料型別變數名;
如何訪問類變數
類名.類變數名
或者物件名.類變數名【靜態變數的訪問修飾符的訪問許可權和範圍和普通屬性是一樣的】
推薦使用:類名.類變數名;
類變數使用注意事項
1.什麼時候需要用類變數
當我們需要讓某個類的所有物件都共享一個變數時,就可以考慮使用類變數(靜態變數):比如:定義學生類,統計所有學生共交多少錢。Student (name, staticfee)
2.類變數與例項變數(普通屬性)區別
類變數是該類的所有物件共享的,而例項變數是每個物件獨享的。
3.加上static稱為類變數或靜態變數,否則稱為例項變數/普通變數/非靜態變數
4.類變數可以透過類名.類變數名或者物件名.類變數名來訪問,但java設計者推薦我們使用類名.類變數名方式訪問。【前提是滿足訪問修飾符的訪問許可權和範圍】
5.例項變數不能透過類名.類變數名方式訪問。
6.類變數是在類載入時就初始化了,也就是說,即使你沒有建立物件,只要類載入了.就可以使用類變數了。
7.類變數的生命週期是隨類的載入開始,隨著類消亡而銷燬。
類方法基本介紹
類方法也叫靜態方法。形式如下:
訪問修飾符 static 資料返回型別 方法名(){}【推薦】
static 訪問修飾符 資料返回型別 方法名(){}
類方法的呼叫
使用方式:
類名.類方法名或者物件名.類方法名
package com.hspedu.static_;
public class StaticMethod {
public static void main(String[] args) {
//建立2個學生物件,叫學費
Stu tom = new Stu("tom");
//tom.payFee(100);
Stu.payFee(100);//對不對?對
Stu mary = new Stu("mary");
//mary.payFee(200);
Stu.payFee(200);//對
//輸出當前收到的總學費
Stu.showFee();//300
//如果我們希望不建立例項,也可以呼叫某個方法(即當做工具來使用)
//這時,把方法做成靜態方法時非常合適
System.out.println("9開平方的結果是=" + Math.sqrt(9));
System.out.println(MyTools.calSum(10, 30));
}
}
//開發自己的工具類時,可以將方法做成靜態的,方便呼叫
class MyTools {
//求出兩個數的和
public static double calSum(double n1, double n2) {
return n1 + n2;
}
//可以寫出很多這樣的工具方法...
}
class Stu {
private String name;//普通成員
//定義一個靜態變數,來累積學生的學費
private static double fee = 0;
public Stu(String name) {
this.name = name;
}
// 說明
// 1. 當方法使用了static修飾後,該方法就是靜態方法
// 2. 靜態方法就可以訪問靜態屬性/變數
public static void payFee(double fee) {
Stu.fee += fee;//累積到
}
public static void showFee() {
System.out.println("總學費有:" + Stu.fee);
}
}
類方法經典的使用場景
當方法中不涉及到任何和物件相關的成員,則可以將方法設計成靜態方法, 提高開發效率。
比如:
工具類中的方法utils。Math類、Arrays類、Collections集合類看下原始碼可以發現都是static方法。
類方法使用注意事項和細節討論
-
類方法和普通方法都是隨著類的載入而載入,將結構資訊儲存在方法區∶類方法中無this的引數。普通方法中隱含著this的引數。
-
類方法可以透過類名呼叫,也可以透過物件名呼叫。普通方法和物件有關,需要透過物件名呼叫,比如物件名.方法名(引數),不能透過類名呼叫。
-
類方法中不允許使用和物件有關的關鍵字,比如this和super。普通方法(成員方法)可以。
-
類方法(靜態方法)中只能訪問靜態變數或靜態方法。普通成員方法,既可以訪問非靜態成員,也可以訪問靜態成員!!
package com.hspedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi();//ok
//非靜態方法,不能透過類名呼叫
//D.say();, 錯誤,需要先建立物件,再呼叫
new D().say();//可以
}
}
class D {
private int n1 = 100;
private static int n2 = 200;
public void say() {//非靜態方法,普通方法
}
public static void hi() {//靜態方法,類方法
//類方法中不允許使用和物件有關的關鍵字,
//比如this和super。普通方法(成員方法)可以。
//System.out.println(this.n1);
}
//類方法(靜態方法)中 只能訪問 靜態變數 或靜態方法
//口訣:靜態方法只能訪問靜態成員.
public static void hello() {
System.out.println(n2);
System.out.println(D.n2);
//System.out.println(this.n2);不能使用
hi();//OK
//say();//錯誤
}
//普通成員方法,既可以訪問 非靜態成員,也可以訪問靜態成員
//小結: 非靜態方法可以訪問 靜態成員和非靜態成員
public void ok() {
//非靜態成員
System.out.println(n1);
say();
//靜態成員
System.out.println(n2);
hello();
}
}
練習:
package com.hspedu.static_;
public class StaticExercise03 {
}
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
// this.total = total;//錯誤,因為在static方法中,不可以使用this 關鍵字
Person.total = total;
}
public Person() {//構造器
total++;
id = total;
}
//編寫一個方法,輸出total的值
public static void m() {
System.out.println("total的值=" + total);
}
}
class TestPerson {
public static void main(String[] args) {
Person.setTotalPerson(3); // 這裡沒有呼叫構造器
new Person(); // new了之後呼叫構造器,count++
Person.m();// 最後 total的值就是4
}
}
注意:
Person.setTotalPerson(3); 呼叫靜態方法 這裡還沒有呼叫構造器
new Person(); new了之後才呼叫構造器,count++
因為構造器是在建立物件時完成對物件的初始化。
理解main 方法語法
深入理解main 方法
解釋main方法的形式: public static void main(String[] args){}
1.main方法時虛擬機器呼叫
2.java虛擬機器需要呼叫類的main()方法,所以該方法的訪問許可權必須是public
3.java虛擬機器在執行main()方法時不必建立物件,所以該方法必須是static
4.該方法接收String型別的陣列引數,該陣列中儲存執行java命令時傳遞給所執行的類的引數,案例演示,接收引數.
5.java執行的程式引數1引數2引數。
說明:在idea如何傳遞引數?
在Program arguments 中傳入引數即可。
特別提示
在main()方法中,我們可以直接呼叫main 方法所在類的靜態方法或靜態屬性。但是,不能直接訪問該類中的非靜態成員,必須建立該類的一個例項物件後,才能透過這個物件去訪問類中的非靜態成員。
程式碼塊
基本介紹
程式碼化塊又稱為初始化塊,屬於類中的成員[即是類的一部分],類似於方法,將邏輯語句封裝在方法體中,透過包圍起來。
但和方法不同,沒有方法名,沒有返回,沒有引數,只有方法體,而且不用透過物件或類顯式呼叫,而是載入類時,或建立物件時隱式呼叫。
基本語法
[修飾符]{
程式碼
};
說明注意;
-
修飾符可選,要寫的話,也只能寫static
-
程式碼塊分為兩類,使用static修飾的叫靜態程式碼塊,沒有static修飾的,叫普通程式碼塊/非靜態程式碼塊。
-
邏輯語句可以為任何邏輯語句(輸入、輸出、方法呼叫、迴圈、判斷等)
-
;號可以寫上,也可以省略。
程式碼塊的好處和案例演示
-
相當於另外一種形式的構造器(對構造器的補充機制),可以做初始化的操作
-
場景:如果多個構造器中都有重複的語句,可以抽取到初始化塊中,提高程式碼的重用性
這樣當我們不管呼叫哪個構造器,建立物件,都會先呼叫程式碼塊的內容,程式碼塊呼叫的順序優先於構造器。
package com.hspedu.codeblock_;
public class CodeBlock01 {
public static void main(String[] args) {
Movie movie = new Movie("你好,李煥英");
System.out.println("===============");
Movie movie2 = new Movie("唐探3", 100, "陳思誠");
}
}
class Movie {
private String name;
private double price;
private String director;
// 3個構造器-》過載
{
System.out.println("電影螢幕開啟...");
System.out.println("廣告開始...");
System.out.println("電影正是開始...");
};
public Movie(String name) {
System.out.println("Movie(String name) 被呼叫...");
this.name = name;
}
public Movie(String name, double price) {
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
System.out.println("Movie(String name, double price, String director) 被呼叫...");
this.name = name;
this.price = price;
this.director = director;
}
}
程式碼塊使用注意事項和細節討論!!!
-
static程式碼塊也叫靜態程式碼塊,作用就是對類進行初始化,而且它隨著類的載入而執行,並且只會執行一次。如果是普通程式碼塊,每建立一個物件, 就執行一次。
-
類什麼時候被載入
- 建立物件例項時(new)
- 建立子類物件例項,父類也會被載入
- 使用類的靜態成員時(靜態屬性,靜態方法)
-
普通的程式碼塊,在建立物件例項時,會被隱式的呼叫。被建立一次,就會呼叫一次。
如果只是使用類的靜態成員時,普通程式碼塊並不會執行。(沒有建立物件例項)
-
建立一個物件時,在一個類呼叫順序是 (重點,難點) ∶
-
呼叫靜態程式碼塊和靜態屬性初始化 (注意:靜態程式碼塊和靜態屬性初始化呼叫的優先順序一樣,如果有多個靜態程式碼塊和多個靜態變數初始化,則按他們定義的先後順序呼叫)
-
呼叫普通程式碼塊和普通屬性的初始化(注意:普通程式碼塊和普通屬性初始化呼叫的優先順序一樣,如果有多個普通程式碼塊和多個普通屬性初始化,則按定義先後順序呼叫)
-
呼叫構造方法
-
-
構造器的最前面其實隱含了super()和呼叫普通程式碼塊, 靜態相關的程式碼塊,屬性初始化,在類載入時,就執行完畢,因此是優先於構造器和普通程式碼塊執行的。
-
我們看一下建立一個子類物件時(繼承關係),他們的呼叫順序如下:
-
父類的靜態程式碼塊和靜態屬性(優先順序一樣,按定義順序執行)(類載入)
-
子類的靜態程式碼塊和靜態屬性(優先順序一樣,按定義順序執行)(類載入)
-
父類的普通程式碼塊和普通屬性初始化(優先順序一樣,按定義順序執行)
-
父類的構造方法
-
子類的普通程式碼塊和普通屬性初始化(優先順序一樣,按定義順序執行)
-
子類的構造方法
-
-
靜態程式碼塊(本質上是靜態方法)只能直接呼叫靜態成員(靜態屬性和靜態方法),普通程式碼塊(本質上是普通方法)可以呼叫任意成員。
package com.hspedu.codeblock_;
public class CodeBlockDetail04 {
public static void main(String[] args) {
//老師說明
//(1) 進行類的載入
//1.1 先載入 父類 A02 1.2 再載入 B02
//(2) 建立物件
//2.1 從子類的構造器開始
//new B02();//物件
new C02();
}
}
class A02 { //父類
private static int n1 = getVal01();
static {
System.out.println("A02的一個靜態程式碼塊..");//(2)
}
{
System.out.println("A02的第一個普通程式碼塊..");//(5)
}
pulic int n3 = getVal02();//普通屬性的初始化
public static int getVal01() {
System.out.println("getVal01");//(1)
return 10;
}
public int getVal02() {
System.out.println("getVal02");//(6)
return 10;
}
public A02() {//構造器
//隱藏
//super()
//普通程式碼和普通屬性的初始化......
System.out.println("A02的構造器");//(7)
}
}
class C02 {
private int n1 = 100;
private static int n2 = 200;
private void m1() {
}
private static void m2() {
}
static {
//靜態程式碼塊,只能呼叫靜態成員
//System.out.println(n1);錯誤
System.out.println(n2);//ok
//m1();//錯誤
m2();
}
{
//普通程式碼塊,可以使用任意成員
System.out.println(n1);
System.out.println(n2);//ok
m1();
m2();
}
}
class B02 extends A02 { //
private static int n3 = getVal03();
static {
System.out.println("B02的一個靜態程式碼塊..");//(4)
}
public int n5 = getVal04();
{
System.out.println("B02的第一個普通程式碼塊..");//(9)
}
public static int getVal03() {
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04() {
System.out.println("getVal04");//(8)
return 10;
}
//一定要慢慢的去品..
public B02() {//構造器
//隱藏了
//super()
//普通程式碼塊和普通屬性的初始化...
System.out.println("B02的構造器");//(10)
// TODO Auto-generated constructor stub
}
}
練習:
package com.hspedu.codeblock_;
public class CodeBlockExercise02 {
}
class Sample
{
Sample(String s)
{
System.out.println(s);
}
Sample()
{
System.out.println("Sample預設建構函式被呼叫");
}
}
class Test{
Sample sam1=new Sample("sam1成員初始化");//
static Sample sam=new Sample("靜態成員sam初始化 ");//
static{
System.out.println("static塊執行");//
if(sam==null)System.out.println("sam is null");
}
Test()//構造器
{
System.out.println("Test預設建構函式被呼叫");//
}
//主方法
public static void main(String str[])
{
Test a=new Test();//無參構造器
}
}
1. 靜態成員sam 初始化
2. static 塊執行
3. sam1 成員初始化
4. Test 預設建構函式被呼叫
單例設計模式
什麼是設計模式
靜態方法和屬性的經典使用
設計模式是在大量的實踐中總結和理論化之後優選的程式碼結構、程式設計風格、以及解決問題的思考方式。設計模式就像是經典的棋譜,不同的棋局,我們用不同的棋譜,免去我們自己再思考和摸索。
什麼是單例模式
-
所謂類的單例設計模式,就是採取一定的方法保證在整個的軟體系統中,對某個類只能存在一個物件例項,並且該類只提供一個取得其物件例項的方法。
-
單例模式有兩種方式:1) 餓漢式 2) 懶漢式
餓漢式
步驟如下:
-
構造器私有化 =》防止直接new
-
類的內部建立物件
-
向外暴露一個靜態的公共方法。getlnstance
餓漢式:有可能還沒有用到這個物件,但是由於類的機制已經將物件建立好了。線上程還沒出現之前就已經例項化了,因此餓漢式執行緒一定是安全的。
package com.hspedu.single_;
public class SingleTon01 {
public static void main(String[] args) {
// GirlFriend xh = new GirlFriend("小紅");
// GirlFriend xb = new GirlFriend("小白");
//透過方法可以獲取物件
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
// 都是同一個物件
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);// T 同一個物件
//System.out.println(GirlFriend.n1);
}
}
// 有一個類, GirlFriend
// 只能有一個女朋友
class GirlFriend {
private String name;
// public static int n1 = 100;
// 為了能夠在靜態方法中,返回 gf物件,需要將其修飾為static
// 物件,通常是重量級的物件, 餓漢式可能造成建立了物件,但是沒有使用.
// 只要類載入了,就一定建立了gf物件
private static GirlFriend gf = new GirlFriend("小紅紅");
// 如何保障我們只能建立一個 GirlFriend 物件
// 步驟[單例模式-餓漢式]
// 1. 將構造器私有化
// 2. 在類的內部直接建立物件(該物件是static)
// 3. 提供一個公共的static方法,返回 gf 物件
private GirlFriend(String name) {
System.out.println("構造器被呼叫.");
this.name = name;
}
// 用static的目的就是在不建立物件的前提下直接呼叫
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
懶漢式
懶漢式,只有當使用者使用getInstance時,才返回cat物件, 後面再次呼叫時,會返回上次建立的cat物件。
懶漢式可能會存線上程安全的問題。
package com.hspedu.single_;
/**
* 演示懶漢式的單例模式
*/
public class SingleTon02 {
public static void main(String[] args) {
//new Cat("大黃");
//System.out.println(Cat.n1);
Cat instance = Cat.getInstance();
System.out.println(instance);
//再次呼叫getInstance
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//T
}
}
//希望在程式執行過程中,只能建立一個Cat物件
//使用單例模式
class Cat {
private String name;
public static int n1 = 999;
private static Cat cat ; //預設是null
//步驟
//1.仍然構造器私有化
//2.定義一個static靜態屬性物件
//3.提供一個public的static方法,可以返回一個Cat物件
//4.懶漢式,只有當使用者使用getInstance時,才返回cat物件, 後面再次呼叫時,會返回上次建立的cat物件
// 從而保證了單例
private Cat(String name) {
System.out.println("構造器呼叫...");
this.name = name;
}
public static Cat getInstance() {
if(cat == null) {//如果還沒有建立cat物件
cat = new Cat("小可愛");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
比較
-
二者最主要的區別在於建立物件的時機不同:餓漢式是在類載入就建立了物件例項,而懶漢式是在使用時才建立。
-
餓漢式不存線上程安全問題,懶漢式存線上程安全問題。(後面學習執行緒後,會完善一把)。
-
餓漢式存在浪費資源的可能。因為如果程式設計師一個物件例項都沒有使用,那麼餓漢式建立的物件就浪費了,懶漢式是使用時才建立,就不存在這個問題。
-
在我們javaSE標準類中,java.lang.Runtime就是經典的單例模式.
final 關鍵字
基本介紹
final中文意思:最後的,最終的.
final可以修飾類、屬性、方法和區域性變數
在某些情況下,程式設計師可能有以下需求,就會使用到final:
-
當不希望類被繼承時,可以用final修飾.
-
當不希望父類的某個方法被子類覆蓋/重寫(override)時,可以用final關鍵字修飾。【案例演示:訪問修飾符final返回型別方法名】
-
當不希望類的的某個屬性的值被修改,可以用final修飾.(例如: public final double TAX RATE=0.08)
-
當不希望某個區域性變數被修改,可以使用final修飾(例如: final double TAX RATE=0.08)
final 使用注意事項和細節討論
-
final修飾的屬性又叫常量,一般用 XX_XX_XX (大寫)來命名
-
final修飾的屬性在定義時,必須賦初值,並且以後不能再修改,賦值可以在如下位置之一:
定義時:如public final double TAX_RATE=0.08;
在構造器中
在程式碼塊中
class AA { /* 1. 定義時:如public final double TAX_RATE=0.08; 2. 在構造器中 3. 在程式碼塊中 */ public final double TAX_RATE = 0.08;//1.定義時賦值 public final double TAX_RATE2 ; public final double TAX_RATE3 ; public AA() {//構造器中賦值 TAX_RATE2 = 1.1; } {//在程式碼塊賦值 TAX_RATE3 = 8.8; } }
-
如果final修飾的屬性是靜態的,則初始化的位置只能是
①定義時
②在靜態程式碼塊(不能在構造器中賦值。因為構造器是在物件建立的時候才會進行賦值)
-
final類不能繼承,但是可以例項化物件。(例項化沒問題)
-
如果類不是final類,但是含有final方法,則該方法雖然不能重寫,但是可以被繼承。(子類用是沒問題的,雖然不能重寫)
-
一般來說,如果一個類已經是final類了,就沒有必要再將方法修飾成final方法。(因為類既然不能被繼承,也就相應無法被重寫)。
-
final不能修飾構造方法(即構造器)。
-
final和static 往往搭配使用,效率更高,因為不會導致類載入,底層編譯器做了最佳化處理。
-
包裝類(Integer,Double,Float,Boolean等都是final),String也是final類。
抽象類
引出
當父類的某些方法,需要宣告,但是又不確定如何實現時,可以將其宣告為抽象方法,那麼這個類就是抽象類。
所謂抽象方法就是沒有實現的方法,所謂沒有實現就是指,沒有方法體。
當一個類中存在抽象方法時,需要將該類宣告為abstract 類,一般來說,抽象類會被繼承,由其子類來實現抽象方法。
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public abstract void eat() ;
}
抽象類的介紹
1)用abstract關鍵字來修飾一個類時,這個類就叫抽象類訪問修飾符
2)用abstract關鍵字來修飾一個方法時,這個方法就是抽象方法
訪問修飾符 abstract 返回型別 方法名(引數列表);//沒有方法體
3)抽象類的價值更多作用是在於設計,是設計者設計好後,讓子類繼承並實現抽象類。
4)抽象類是考官比較愛問的知識點,在框架和設計模式使用較多。
抽象類使用的注意事項和細節討論
1)抽象類不能被例項化
2)抽象類不一定要包含abstract方法。也就是說, 抽象類可以沒有abstract方法。
3)一旦類包含了abstract方法,則這個類必須宣告為abstract。
4)abstract只能修飾類和方法,不能修飾屬性和其它的。
5)抽象類可以有任意成員【抽象類本質還是類】,比如: 非抽象方法、構造器、靜態屬性等等。
6)抽象方法不能有主體,即不能實現。
7)如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法,除非它自己也宣告為abstract類。
8)抽象方法不能使用private、final和 static來修飾,因為這些關鍵字都是和重寫相違背的。
抽象類最佳實踐-模板設計模式
基本介紹
抽象類體現的就是一種模板模式的設計,抽象類作為多個子類的通用模板,子類在抽象類的基礎上進行擴充套件、改造,但子類總體上會保留抽象類的行為方式。
模板設計模式能解決的問題
1)當功能內部一部分實現是確定,一部分實現是不確定的。這時可以把不確定的部分暴露出去,讓子類去實現。
2)編寫一個抽象父類,父類提供了多個子類的通用方法,並把一個或多個方法留給其子類實現,就是一種模板模式.
最佳實踐
需求:
有多個類,完成不同的任務job
要求統計得到各自完成任務的時間
package com.hspedu.abstract_;
abstract public class Template { //抽象類-模板設計模式
public abstract void job();//抽象方法
public void calculateTime() {//實現方法,呼叫job方法
//得到開始的時間
long start = System.currentTimeMillis();
job(); //動態繫結機制
//得的結束的時間
long end = System.currentTimeMillis();
System.out.println("任務執行時間 " + (end - start));
}
}
以上就是把不確定的部分暴露出去,讓子類去實現。
介面
基本介紹
介面就是給出一些沒有實現的方法,封裝到一起,到某個類要使用的時候,在根據具體情況把這些方法寫出來。語法:
interface 介面名{
//屬性
//抽象方法(介面中可以省略abstract關鍵字)(在jdk8後還可以有靜態方法和預設方法)
}
class 類名 implements 介面 {
// 自己屬性;
// 自己方法;
// 必須實現的介面的抽象方法
}
小結:
介面是更加抽象的類。抽象類裡的方法可以有方法體,介面裡的所有方法都沒有方法體(jdk7.0)。介面體現了程式設計的多型和高內聚低偶合的設計思想。
特別說明:Jdk8.0後介面類可以有靜態方法(static),預設方法(default),也就是說介面中可以有方法的具體實現入。
深入討論
說現在有一個專案經理(段玉),管理三個程式設計師,功能開發一個軟體.為了控制和管理軟體,專案經理可以定義一些介面,然後由程式設計師具體實現。
透過介面,不僅可以統一方法名,同時在呼叫時只需要根據介面識別即可。
package com.hspedu.interface_;
public interface DBInterface { //專案經理
public void connect();//連線方法
public void close();//關閉連線
}
package com.hspedu.interface_;
//A程式
public class MysqlDB implements DBInterface {
@Override
public void connect() {
System.out.println("連線mysql");
}
@Override
public void close() {
System.out.println("關閉mysql");
}
}
package com.hspedu.interface_;
//B程式設計師連線Oracle
public class OracleDB implements DBInterface{
@Override
public void connect() {
System.out.println("連線oracle");
}
@Override
public void close() {
System.out.println("關閉oracle");
}
}
package com.hspedu.interface_;
public class Interface03 {
public static void main(String[] args) {
MysqlDB mysqlDB = new MysqlDB();
t(mysqlDB);
OracleDB oracleDB = new OracleDB();
t(oracleDB);
}
public static void t(DBInterface db) {
db.connect();
db.close();
}
}
注意事項和細節
-
介面不能被例項化(new)
-
介面中所有的方法是public方法,介面中抽象方法,可以不用abstract修
飾。void aaa(); 實際上是abstract void aa();(同理,不寫public也是預設public方法,因此實現時該方法不寫public會報錯。) -
一個普通類實現介面,就必須將該介面的所有方法都實現。
-
抽象類實現介面,可以不用實現介面的方法。
-
一個類同時可以實現多個介面。
class Timer implements IA, IB{ }
-
介面中的屬性,只能是final的,而且是 public static final修飾符。比如:int a=1;實際上是public static final int a=1; (必須初始化)
-
介面中屬性的訪問形式:介面名.屬性名
-
介面不能繼承其它的類,但是可以繼承多個別的介面。(介面無法實現介面)
interface A extends B,C{}
-
介面的修飾符只能是public和預設,這點和類的修飾符是一樣的。
實現介面VS繼承類
當子類繼承了父類,就自動的擁有父類的功能,如果子類需要擴充套件功能,可以透過實現介面的方式擴充套件。可以理解 實現介面 是對 java 單繼承機制的一種補充。
-
介面和繼承解決的問題不同
繼承的價值主要在於:解決程式碼的複用性和可維護性。
-
介面的價值主要在於:設計,設計好各種規範(方法),讓其它類去實現這些方法。即更加的靈活
介面比繼承更加靈活:繼承是滿足is - a的關係,而介面只需滿足 like - a的關係。
介面在一定程度上實現程式碼解耦[即:介面規範性+動態繫結機制]
介面的多型特性
-
多型引數
在前面的Usb介面案例,UsbInterface usb,既可以接收手機物件,又可以接收相機物件,就體現了介面多型(介面引用可以指向實現了介面的類的物件)。
package com.hspedu.interface_; public class InterfacePolyParameter { public static void main(String[] args) { //介面的多型體現 //介面型別的變數 if01 可以指向 實現了IF介面類的物件例項 IF if01 = new Monster(); if01 = new Car(); // 繼承體現的多型 // 父類型別的變數 a 可以指向 繼承AAA的子類的物件例項 AAA a = new BBB(); a = new CCC(); } } interface IF {} class Monster implements IF{} class Car implements IF{} class AAA { } class BBB extends AAA {} class CCC extends AAA {}
2. 多型陣列
演示一個案例:給**Usb陣列中,存放 Phone 和相機物件**,Phone類還有一個特有的方法call(),請遍歷Usb陣列,如果是Phone物件,除了呼叫Usb介面定義的方法外,還需要呼叫Phone特有方法call。
```java
package com.hspedu.interface_;
public class InterfacePolyArr {
public static void main(String[] args) {
//多型陣列 -> 介面型別陣列
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
/*
給Usb陣列中,存放 Phone 和 相機物件,Phone類還有一個特有的方法call(),
請遍歷Usb陣列,如果是Phone物件,除了呼叫Usb 介面定義的方法外,
還需要呼叫Phone 特有方法 call
*/
for(int i = 0; i < usbs.length; i++) {
usbs[i].work();//動態繫結..
//和前面一樣,我們仍然需要進行型別的向下轉型
if(usbs[i] instanceof Phone_) {//判斷他的執行型別是 Phone_
((Phone_) usbs[i]).call();
}
}
}
}
interface Usb{
void work();
}
class Phone_ implements Usb {
public void call() {
System.out.println("手機可以打電話...");
}
@Override
public void work() {
System.out.println("手機工作中...");
}
}
class Camera_ implements Usb {
@Override
public void work() {
System.out.println("相機工作中...");
}
}
-
介面存在多型傳遞現象
package com.hspedu.interface_; /** * 演示多型傳遞現象 */ public class InterfacePolyPass { public static void main(String[] args) { //介面型別的變數可以指向,實現了該介面的類的物件例項 IG ig = new Teacher(); //如果IG 繼承了 IH 介面,而Teacher 類實現了 IG介面 //那麼,實際上就相當於 Teacher 類也實現了 IH介面. //這就是所謂的 介面多型傳遞現象. IH ih = new Teacher(); } } interface IH { void hi(); } interface IG extends IH{ } class Teacher implements IG { @Override public void hi() { } }
內部類
如果定義類在區域性位置(方法中/程式碼塊) (1) 區域性內部類 (2) 匿名內部類
定義在成員位置 (1) 成員內部類 (2) 靜態內部類
基本介紹
一個類的內部又完整的巢狀了另一個類結構。被巢狀的類稱為內部類(inner class),巢狀其他類的類稱為外部類(outer class)。
是我們類的第五大成員(類的五大成員:屬性、方法、構造器、程式碼塊、內部類),內部類最大的特點就是可以直接訪問私有屬性,並且可以體現類與類之間的包含關係,注意:內部類是學習的難點,同時也是重點,後面看底層原始碼時,有大量的內部類。
基本語法
class Outer{ // 外部類
class Inner{
// 內部類
}
}
class Other{// 外部其他類
}
內部類的分類
定義在外部類區域性位置上( 比如方法內 ):
-
區域性內部類 ( 有類名 )
-
匿名內部類 ( 沒有類名,重點!!!!!!!! )
定義在外部類的成員位置上:
-
成員內部類 ( 沒用 static 修飾 )
-
靜態內部類 ( 使用 static 修飾 )
區域性內部類的使用
說明:區域性內部類是定義在外部類的區域性位置,比如方法中,並且有類名。
1.可以直接訪問外部類的所有成員,包含私有的。
2.不能新增訪問修飾符,因為它的地位就是一個區域性變數。區域性變數是不能使用修飾符的。但是可以使用final修飾,因為區域性變數也可以使用final。
3.作用域:僅僅在定義它的方法或程式碼塊中。
4.區域性內部類訪問外部類的成員[訪問方式:直接訪問]
5.外部類訪問區域性內部類的成員
訪問方式: 建立物件,再訪問 (注意:必須在作用域內)
小結:
(1)區域性內部類定義在方法中/程式碼塊
(2)作用域在方法體或者程式碼塊中
(3)本質仍然是一個類
6.外部其他類不能訪問區域性內部類(因為區域性內部類地位是一個區域性變數)。
7.如果外部類和區域性內部類的成員重名時,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問。
這裡 外部類名.this
本質上就是外部類的物件,即哪個物件呼叫了n2,那麼 外部類名.this
就指向哪個物件。
System.out.printin(""外部類的n2=”+外部類名.this.n2);
package com.hspedu.innerclass;
/**
* 演示區域性內部類的使用
*/
public class LocalInnerClass {//
public static void main(String[] args) {
//演示一遍
Outer02 outer02 = new Outer02();
outer02.m1();
System.out.println("outer02的hashcode=" + outer02);
}
}
class Outer02 {//外部類
private int n1 = 100;
private void m2() {
System.out.println("Outer02 m2()");
}//私有方法
public void m1() {//方法
//1.區域性內部類是定義在外部類的區域性位置,通常在方法
//3.不能新增訪問修飾符,但是可以使用final 修飾
//4.作用域 : 僅僅在定義它的方法或程式碼塊中
final class Inner02 {//區域性內部類(本質仍然是一個類)
//2.可以直接訪問外部類的所有成員,包含私有的
private int n1 = 800;
public void f1() {
//5. 區域性內部類可以直接訪問外部類的成員,比如下面 外部類n1 和 m2()
//7. 如果外部類和區域性內部類的成員重名時,預設遵循就近原則,如果想訪問外部類的成員,
// 使用 外部類名.this.成員)去訪問
// Outer02.this 本質就是外部類的物件, 即哪個物件呼叫了m1, Outer02.this就是哪個物件
System.out.println("n1=" + n1 + " 外部類的n1=" + Outer02.this.n1);
System.out.println("Outer02.this hashcode=" + Outer02.this);
m2();
}
}
//6. 外部類在方法中,可以建立Inner02物件,然後呼叫方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
匿名內部類的使用!!!!!
(1)本質是類 (2) 內部類 (3) 該類沒有名字 (4) 同時還是一個物件
說明: 匿名內部類是定義在外部類的區域性位置, 比如方法中, 並且沒有類名
1.匿名內部類的基本語法
new 類或介面 (引數列表){
類體
);
package com.hspedu.innerclass;
/**
* 演示匿名內部類的使用
*/
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 { //外部類
private int n1 = 10;//屬性
public void method() {//方法
//基於!!!介面!!!的匿名內部類
//解讀
//1.需求:想使用IA介面,並建立物件
//2.傳統方式,是寫一個類,實現該介面,並建立物件
//3.需求是 Tiger/Dog 類只是使用一次,後面再不使用
//4. 可以使用匿名內部類來簡化開發
//5. tiger的編譯型別 ? IA
//6. tiger的執行型別 ? 就是匿名內部類 Outer04$1
/*
我們看底層 會分配 類名 Outer04$1
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("老虎叫喚...");
}
}
*/
//7. jdk底層在建立匿名內部類 Outer04$1,立即馬上就建立了 Outer04$1例項,並且把地址
// 返回給 tiger
//8. 匿名內部類使用一次,就不能再使用, 但是tiger這個物件就沒有限制了。
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎叫喚...");
}
};
System.out.println("tiger的執行型別=" + tiger.getClass());
tiger.cry();
tiger.cry();
tiger.cry();
// IA tiger = new Tiger();
// tiger.cry();
// 演示基於!!!類!!!的匿名內部類
//分析
//1. father 編譯型別 Father
//2. father 執行型別 Outer04$2
//3. 底層會建立匿名內部類
/*
具體的實現程式碼與註釋中的程式碼近似等價
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名內部類重寫了test方法");
}
}
*/
//4. 同時也直接返回了 匿名內部類 Outer04$2的物件
//5. 注意("jack") 引數列表會傳遞給 Father 構造器
Father father = new Father("jack"){
@Override
public void test() {
System.out.println("匿名內部類重寫了test方法");
}
};
System.out.println("father物件的執行型別=" + father.getClass());//Outer04$2
father.test();
//基於!!!抽象類!!!的匿名內部類
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("小狗吃骨頭...");
}
};
animal.eat();
}
}
interface IA {//介面
public void cry();
}
//class Tiger implements IA {
//
// @Override
// public void cry() {
// System.out.println("老虎叫喚...");
// }
//}
//class Dog implements IA{
// @Override
// public void cry() {
// System.out.println("小狗汪汪...");
// }
//}
class Father { //類
public Father(String name) { //構造器
System.out.println("接收到name=" + name);
}
public void test() { //方法
}
}
abstract class Animal { //抽象類
abstract void eat();
}
2.匿名內部類的語法比較奇特,因為匿名內部類既是一個類的定義.同時它本身也是一個物件,因此從語法上看,它既有定義類的特徵,也有建立物件的特徵,對前面程式碼分析可以看出這個特點,因此可以呼叫匿名內部類方法。
3.可以直接訪問外部類的所有成員,包含私有的。
4、不能新增訪問修飾符,因為它的地位就是一個區域性變數。
5.作用域:僅僅在定義它的方法或程式碼塊中。
6.匿名內部類---訪問---->外部類成員[訪問方式:直接訪問]
7.外部其他類---不能訪問----->匿名內部類(因為匿名內部類地位是一個區域性變數)
8.如果外部類和匿名內部類的成員重名時,匿名內部類訪問的話,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問
package com.hspedu.innerclass;
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
//外部其他類---不能訪問----->匿名內部類
System.out.println("main outer05 hashcode=" + outer05);
}
}
class Outer05 {
private int n1 = 99;
public void f1() {
//建立一個基於類的匿名內部類
//不能新增訪問修飾符,因為它的地位就是一個區域性變數
//作用域 : 僅僅在定義它的方法或程式碼塊中
Person p = new Person(){
private int n1 = 88;
@Override
public void hi() {
// 可以直接訪問外部類的所有成員,包含私有的
// 如果外部類和匿名內部類的成員重名時,匿名內部類訪問的話,
// 預設遵循就近原則,如果想訪問外部類的成員,則可以使用 (外部類名.this.成員)去訪問
System.out.println("匿名內部類重寫了 hi方法 n1=" + n1 +
" 外部內的n1=" + Outer05.this.n1 );
// Outer05.this 就是呼叫 f1的 物件
System.out.println("Outer05.this hashcode=" + Outer05.this);
}
};
p.hi();//動態繫結, 執行型別是 Outer05$1
//也可以直接呼叫, 匿名內部類本身也是返回物件
// class 匿名內部類 extends Person {}
// new Person(){
// @Override
// public void hi() {
// System.out.println("匿名內部類重寫了 hi方法,哈哈...");
// }
// @Override
// public void ok(String str) {
// super.ok(str);
// }
// }.ok("jack");
}
}
class Person {//類
public void hi() {
System.out.println("Person hi()");
}
public void ok(String str) {
System.out.println("Person ok() " + str);
}
}
//抽象類/介面...
匿名內部類的最佳實踐
當做實參直接傳遞,簡潔高效。
package com.hspedu.innerclass;
import com.hspedu.abstract_.AA;
public class InnerClassExercise01 {
public static void main(String[] args) {
//當做實參直接傳遞,簡潔高效
f1(new IL() {
@Override
public void show() {
System.out.println("這是一副名畫~~...");
}
});
//傳統方法
f1(new Picture());
}
//靜態方法,形參是介面型別
public static void f1(IL il) {
il.show();
}
}
//介面
interface IL {
void show();
}
//類->實現IL => 程式設計領域 (硬編碼)
class Picture implements IL {
@Override
public void show() {
System.out.println("這是一副名畫XX...");
}
}
有一個鈴聲介面Bell,裡面有個ring方法。有一個手機類Cellphone,具有鬧鐘功能alarmclock,引數是Bell型別。測試手機類的鬧鐘功能,透過匿名內部類(物件)作為引數,列印:懶豬起床了。再傳入另一個匿名內部類(物件),列印:小夥伴上課了
package com.hspedu.innerclass;
public class InnerClassExercise02 {
public static void main(String[] args) {
/*
1.有一個鈴聲介面Bell,裡面有個ring方法。(右圖)
2.有一個手機類Cellphone,具有鬧鐘功能alarmClock,引數是Bell型別(右圖)
3.測試手機類的鬧鐘功能,透過匿名內部類(物件)作為引數,列印:懶豬起床了
4.再傳入另一個匿名內部類(物件),列印:小夥伴上課了
*/
CellPhone cellPhone = new CellPhone();
//老韓解讀
//1. 傳遞的是實現了 Bell介面的匿名內部類 InnerClassExercise02$1
//2. 重寫了 ring
//3. Bell bell = new Bell() {
// @Override
// public void ring() {
// System.out.println("懶豬起床了");
// }
// }
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("懶豬起床了");
}
});
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("小夥伴上課了");
}
});
}
}
interface Bell{ //介面
void ring();//方法
}
class CellPhone{//類
public void alarmClock(Bell bell){//形參是Bell介面型別
System.out.println(bell.getClass());
bell.ring();//動態繫結
}
}
成員內部類的使用
說明: 成員內部類是定義在外部類的成員位置,並且沒有static修飾。
1.可以直接訪問外部類的所有成員,包含私有的。
2.可以新增任意訪問修飾符(public、protected、預設、private), 因為它的地
位就是一個成員。
3.作用域和外部類的其他成員一樣,為整個類體比如前面案例,在外部類的成員方法中建立成員內部類物件,再呼叫方法。
4.成員內部類---訪問---->外部類成員(比如:屬性) 訪問方式:直接訪問
5.外部類---訪問------>成員內部類(說明) 訪問方式:建立物件,再訪問
6.外部其他類---訪問---->成員內部類
7.如果外部類和內部類的成員重名時,內部類訪問的話,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問
package com.hspedu.innerclass;
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他類,使用成員內部類的三種方式
// 第一種方式
// outer08.new Inner08(); 相當於把 new Inner08()當做是outer08成員
// 這就是一個語法,不要特別的糾結.
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第二方式 在外部類中,編寫一個方法,可以返回 Inner08物件
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 { //外部類
private int n1 = 10;
public String name = "張三";
private void hi() {
System.out.println("hi()方法...");
}
//1.注意: 成員內部類,是定義在外部內的成員位置上
//2.可以新增任意訪問修飾符(public、protected 、預設、private),因為它的地位就是一個成員
public class Inner08 {//成員內部類
private double sal = 99.8;
private int n1 = 66;
public void say() {
//可以直接訪問外部類的所有成員,包含私有的
//如果成員內部類的成員和外部類的成員重名,會遵守就近原則.
//,可以透過 外部類名.this.屬性 來訪問外部類的成員
System.out.println("n1 = " + n1 + " name = " + name + " 外部類的n1=" + Outer08.this.n1);
hi();
}
}
//方法,返回一個Inner08例項
public Inner08 getInner08Instance(){
return new Inner08();
}
//寫方法
public void t1() {
//使用成員內部類
//建立成員內部類的物件,然後使用相關的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
靜態內部類的使用
說明:靜態內部類是定義在外部類的成員位置, 並且有static修飾
1.可以直接訪問外部類的所有靜態成員,包含私有的,但不能直接訪問非靜態成員。
2.可以新增任意訪問修飾符(public. protected、預設、private),因為它的地位就是一個成員。
3.作用域:同其他的成員,為整個類體。
4.靜態內部類---訪問---->外部類(比如:靜態屬性)[訪問方式:直接訪問所有靜態成員]。
5.外部類---訪問------>靜態內部類 訪問方式:建立物件,再訪問。
6.外部其他類---訪問----->靜態內部類。
7.如果外部類和靜態內部類的成員重名時,靜態內部類訪問的時,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.成員)去訪向。
package com.hspedu.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.m1();
//外部其他類 使用靜態內部類
//方式1
//因為靜態內部類,是可以透過類名直接訪問(前提是滿足訪問許可權)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式2
//編寫一個方法,可以返回靜態內部類的物件例項.
Outer10.Inner10 inner101 = outer10.getInner10();
System.out.println("============");
inner101.say();
Outer10.Inner10 inner10_ = Outer10.getInner10_();
System.out.println("************");
inner10_.say();
}
}
class Outer10 { //外部類
private int n1 = 10;
private static String name = "張三";
private static void cry() {}
//Inner10就是靜態內部類
//1. 放在外部類的成員位置
//2. 使用static 修飾
//3. 可以直接訪問外部類的所有靜態成員,包含私有的,但不能直接訪問非靜態成員
//4. 可以新增任意訪問修飾符(public、protected 、預設、private),因為它的地位就是一個成員
//5. 作用域 :同其他的成員,為整個類體
static class Inner10 {
private static String name = "Timerring";
public void say() {
//如果外部類和靜態內部類的成員重名時,靜態內部類訪問的時,
//預設遵循就近原則,如果想訪問外部類的成員,則可以使用 (外部類名.成員)
System.out.println(name + " 外部類name= " + Outer10.name);
cry();
}
}
public void m1() { //外部類---訪問------>靜態內部類 訪問方式:建立物件,再訪問
Inner10 inner10 = new Inner10();
inner10.say();
}
public Inner10 getInner10() {
return new Inner10();
}
public static Inner10 getInner10_() {
return new Inner10();
}
}
課堂測試題
判斷輸出:
package com.hspedu.innerclass;
public class InnerClassExercise {
public static void main(String[] args) {
}
}
class Test {//外部類
public Test() {//構造器
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
System.out.println(s2.a);
}
class Inner { //內部類,成員內部類
public int a = 5;
}
public static void main(String[] args) {
Test t = new Test();
Inner r = t.new Inner();//5
System.out.println(r.a);//5
}
}