【Java基礎】面向物件下
面向物件下
這一章主要涉及其他關鍵字,包括 this、super、static、final、abstract、interface、package、import 等。
static
在 Java 類中,可用 static 修飾屬性、方法、程式碼塊、內部類。
特點:
- 隨著類的載入而載入,由於類只會載入一次,則靜態變數在記憶體中也只會存在一份,存在方法區的靜態域中;
- 優先於物件存在;
- 修飾的成員,被所有物件所共享;
- 訪問許可權允許時,可不建立物件,直接被類呼叫。
注意:
- 在靜態的方法中,不能使用 this 關鍵字和 super 關鍵字;
- 關於靜態屬性和靜態方法的使用,要從生命週期的角度去理解。
單例模式
所謂的單例模式,就是採取一定的方法保證在整個系統中,對某個類只能存在一個物件例項。
單例模式的好處:
- 某些類建立比較頻繁,對於一些大型的物件,這是一筆很大的系統開銷;
- 省去了 new 操作符,降低了系統記憶體的使用頻率,減輕了 GC 壓力;
- 保證獨立性等。
實現:
對比:
- 餓漢式的壞處是物件載入時間過長,好處是執行緒安全的;
- 懶漢式的好處是延遲物件的載入,壞處是執行緒不安全的;
應用:
- 網站的計數器,一般也是單例模式實現,否則難以同步;
- 應用程式的日誌應用,一般都使用單例模式實現,這一般是由於共享的日誌
檔案一直處於開啟狀態,因為只能有一個例項去操作,否則內容不好追加; - 資料庫連線池的設計一般也是採用單例模式,因為資料庫連線是一種資料庫
資源; - 專案中,讀取配置檔案的類,一般也只有一個物件。沒有必要每次使用配置
檔案資料,都生成一個物件去讀取; - Application 也是單例的典型應用;
- Windows 的 Task Manager (工作管理員) 就是很典型的單例模式;
- Windows 的 Recycle Bin (回收站) 也是典型的單例應用。在整個系統執行過程中,回收站一直維護著僅有的一個例項。
理解 main 方法的語法
由於 JVM 需要呼叫類的 main() 方法,所以該方法的訪問許可權必須是
public,又因為 JVM 在執行 main() 方法時不必建立物件,所以該方法必須是 static
又因為 main() 方法是靜態的,我們不能直接訪問該類中的非靜態成員,必須創
建該類的一個例項物件後,才能通過這個物件去訪問類中的非靜態成員。
程式碼塊
程式碼塊(或初始化塊)的作用:對 Java 類或物件進行初始化。
一個類中程式碼塊若有修飾符,則只能被 static 修飾,稱為靜態程式碼塊
(static block),沒有使用 static 修飾的,稱為非靜態程式碼塊。
靜態程式碼塊:用 static 修飾的程式碼塊
- 可以有輸出語句;
- 可以對類的屬性、類的宣告進行初始化操作;
- 不可以呼叫非靜態的屬性和方法;
- 若有多個靜態程式碼塊,那麼按照從上到下的順序依次執行;
- 靜態程式碼塊隨著類的載入而載入,且只執行一次。靜態程式碼塊的執行要先於非靜態程式碼塊。
非靜態程式碼塊:沒有 static 修飾的程式碼塊
- 可以有輸出語句;
- 可以對類的屬性、類的宣告進行初始化操作;
- 可以呼叫非靜態和靜態的屬性和方法;
- 若有多個非靜態程式碼塊,那麼按照從上到下的順序依次執行;
- 每次建立物件的時候,都會執行一次。非靜態程式碼塊的執行要先於構造器。
package com.parzulpan.java.ch04;
/**
* @Author : parzulpan
* @Time : 2020-11-22
* @Desc : 程式碼塊練習
*/
class Root{
static{
System.out.println("Root的靜態初始化塊"); // 1
}
{
System.out.println("Root的普通初始化塊"); // 4
}
public Root(){
System.out.println("Root的無引數的構造器"); // 5
}
}
class Mid extends Root{
static{
System.out.println("Mid的靜態初始化塊"); // 2
}
{
System.out.println("Mid的普通初始化塊"); // 6
}
public Mid(){
System.out.println("Mid的無引數的構造器"); // 7
}
public Mid(String msg){
//通過this呼叫同一類中過載的構造器
this();
System.out.println("Mid的帶引數構造器,其引數值:"
+ msg); // 8
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的靜態初始化塊"); // 3
}
{
System.out.println("Leaf的普通初始化塊"); // 9
}
public Leaf(){
//通過super呼叫父類中有一個字串引數的構造器
super("尚矽谷");
System.out.println("Leaf的構造器"); // 10
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf(); // 輸出結果順序看註釋
}
}
package com.parzulpan.java.ch04;
/**
* @Author : parzulpan
* @Time : 2020-11-22
* @Desc : 程式碼塊練習
*/
class Father {
static {
System.out.println("11111111111"); // 1
}
{
System.out.println("22222222222"); // 2
}
public Father() {
System.out.println("33333333333"); // 3
}
}
public class Son extends Father {
static {
System.out.println("44444444444"); // 4
}
{
System.out.println("55555555555"); // 5
}
public Son() {
System.out.println("66666666666"); // 6
}
public static void main(String[] args) { // 由父及子 靜態先行
System.out.println("77777777777"); // 7
System.out.println("************************"); // 8
new Son(); // 輸出結果:1 -> 4 -> 7 -> 8 -> 2 -> 3 -> 5 -> 6
System.out.println("************************");
new Son(); // 輸出結果:2 -> 3 -> 5 -> 6
System.out.println("************************");
new Father(); // 輸出結果:2 -> 3
}
}
總結:由父及子,靜態先行。
程式中成員變數賦值的執行順序:
- 宣告成員變數的預設初始化;
- 顯式初始化、多個初始化塊一次被執行(同級別下按先後順序執行);
- 構造器再對成員進行初始化操作;
- 通過“物件.屬性”或“物件.方法”的方式,可多次給屬性賦值。
final
在 Java 中宣告類、方法和變數時,可使用關鍵字 final 來修飾,表示“最終的”。
- final 標記的類不能被繼承。比如 String類、System類、StringBuffer類;
- final 標記的方法不能被子類重寫。比如 Object 類中的
getClass()
; - final 標記的變數(成員變數或區域性變數)稱為常量(名稱大寫,且只能被賦值一次)。比如
final double MY_PI = 3.14;
。
注意:final 標記的成員變數必須 在宣告時 或 在每個構造器中 或 程式碼塊中 顯式賦值,然後才能使用。
static final 用來修飾屬性,則稱為全域性常量。
抽象類與抽象方法
- 用 abstract 關鍵字修飾一個類,這個類叫做抽象類;
- 抽象類不能被例項化;
- 抽象類是用來被繼承的,抽象類的子類必須重寫父類的抽象方法,並提供方法體,此時才可例項化。若沒有重寫全部的抽象方法,仍為抽象類,需要使用 abstract 修飾;
- 抽象類中一定有構造器,便於子類例項化呼叫(涉及子類物件例項化的全過程)。
- 用 abstract 關鍵字修飾一個方法,這個方法叫做抽象方法;
- 抽象方法:只有方法的宣告,沒有方法的實現,以分號結束。比如:
public abstract void talk();
- 含有抽象方法的類必須被宣告為抽象類。
- 抽象方法:只有方法的宣告,沒有方法的實現,以分號結束。比如:
注意:
- 不能用 abstract 修飾變數、程式碼塊、構造器;
- 不能用 abstract 修飾私有方法、靜態方法、final 的方法、final 的類。
package com.parzulpan.java.ch04;
/**
* @Author : parzulpan
* @Time : 2020-11-22
* @Desc : 抽象類舉例
*/
public class AbstractTest {
public static void main(String[] args) {
AA aa = new BB();
aa.m1();
aa.m2();
}
}
abstract class AA {
abstract void m1();
public void m2() {
System.out.println("A類中定義的m2方法");
}
}
class BB extends AA {
void m1() {
System.out.println("B類中定義的m1方法");
}
}
模版方法模式
抽象類體現的就是一種模板模式的設計,抽象類作為多個子類的通用模板,子類在抽象類的基礎上進行擴充套件、改造,但子類總體上會保留抽象類的行為方式。
解決的問題:
- 當功能內部一部分實現是確定的,一部分實現是不確定的。這時可以把不確定的部分暴露出去,讓子類去實現。
- *換句話說,在軟體開發中實現一個演算法時,整體步驟很固定、通用,這些步驟已經在父類中寫好了。但是某些部分易變,易變部分可以抽象出來,供不同子類實現。這就是一種模板模式。
package com.parzulpan.java.ch04;
/**
* @Author : parzulpan
* @Time : 2020-11-22
* @Desc : 抽象類的應用:模版方法模式
*/
public class TemplateMethodTest2 {
public static void main(String[] args) {
BankTemplateMethod bankTemplateMethod = new DrewMoney();
bankTemplateMethod.process();
BankTemplateMethod bankTemplateMethod1 = new ManageMoney();
bankTemplateMethod1.process();
}
}
abstract class BankTemplateMethod {
private int id;
private static int init = 0;
BankTemplateMethod() {
super();
id = init++;
}
// 具體方法
public void takeNumber() {
System.out.println("取號排隊 " + this.id);
}
public void evaluate() {
System.out.println("反饋評分\n");
}
// 鉤子方法,辦理具體的業務
public abstract void transact();
// 模版方法,把基本操作組合到一起,子類一般不能重寫
public final void process() {
this.takeNumber();
this.transact();
this.evaluate();
}
}
class DrewMoney extends BankTemplateMethod {
@Override
public void transact() {
System.out.println("我要取款");
}
}
class ManageMoney extends BankTemplateMethod {
@Override
public void transact() {
System.out.println("我要理財");
}
}
模板方法設計模式是程式設計中經常用得到的模式。各個框架、類庫中都有他的影子,比如常見的有:
- 資料庫訪問的封裝;
- Junit 單元測試;
- JavaWeb 的 Servlet 中關於 doGet/doPost 方法呼叫;
- Hibernate 中模板程式;
- Spring 中 JDBCTemlate、HibernateTemplate 等。
interface
為什麼要有介面:
- 一方面,有時必須從幾個類中派生出一個子類,繼承它們所有的屬性和方法。但是,Java 不支援多重繼承。有了介面,就可以得到多重繼承的效果。
- 另一方面,有時必須從幾個類中抽取出一些共同的行為特徵,而它們之間又沒有 is-a 的關係,僅僅是具有相同的行為特徵而已。例如:滑鼠、鍵盤、印表機、掃描器、攝像頭、充電器、MP3機、手機、數碼相機、行動硬碟等都
支援USB連線。
介面就是規範,定義的是一組規則,體現了現實世界中“如果你是/要...則必須能...”的思想。繼承是一個 "是不是" 的關係,而介面實現則是 "能不能"
的關係。
介面(interface)是抽象方法和常量值定義的集合。
介面的特點:
- 用 interface 來定義,和 class 是並列關係;
- 介面中的所有成員變數都預設是由 public static final 修飾的;
- 介面中的所有抽象方法都預設是由 public abstract 修飾的;
- 介面中沒有構造器,意味著介面不能例項化;
- 介面採用多繼承機制,介面讓類去實現(implements)的方式來使用:
- 如果實現覆蓋了介面的所有抽象方法,則此實現類就可以例項化;
- 如果實現類沒有覆蓋介面中的所有抽象方法,則此實現類仍未一個抽象類。
- 一個類可以實現多個介面。語法:
class SubClass extends SuperClass implements InterfaceA, InterfaceB {}
; - 介面也可以繼承其它介面,並且可以多繼承。語法:
interface AA extends BB, CC {}
; - 與繼承關係類似,介面與實現類之間存在多型性。
抽象類和介面的異同:
區別點 | 抽象類 | 介面 |
---|---|---|
定義 | 包含抽象方法的類 | 主要是抽象方法和全域性常量的集合 |
組成 | 構造器、抽象方法、普通方法、常量、變數 | 抽象方法、常量 |
使用 | 子類繼承抽象類(extends) | 子類實現介面(implements) |
關係 | 抽象類可以實現多個介面 | 介面不能繼承抽象類,但允許繼承多個介面 |
常用設計模式 | 模版方法 | 簡單工廠、工廠方法、代理 |
物件 | 都不能直接例項化,都必須通過物件的多型性產生例項化物件 | |
侷限 | 抽象類有單繼承的侷限 | 介面沒有此侷限 |
實際 | 作為一個模板 | 是作為一個標準或是表示一種能力 |
選擇 | 如果抽象類和介面都可以使用的話,優先使用介面,因為避免單繼承的侷限 |
// 如何定義介面:JDK7及以前,只能定義全域性常量和抽象方法。
// 全域性常量:public static final,書寫時可省略;
// 抽象方法:public abstract,書寫時可省略;
public interface Flyable {
public static final int MAX_SPEED = 7900;
init MIN_SPEED = 1;
public abstract void fly();
void stop();
}
class Plane implements Flyable {
public void fly() {
System.out.println("通過引擎起飛");
}
punlic void stop() {
System.out.println("通過減速停止");
}
}
// 如何定義介面:JDK8,除了能定義全域性常量和抽象方法外,還能定義靜態方法、預設方法。
Java8 介面新特性
JDK8,除了能定義全域性常量和抽象方法外,還能定義靜態方法、預設方法。
靜態方法:使用 static 關鍵字修飾。只能通過介面直接呼叫靜態方法,並執行其方法體。我們經常在相互一起使用的類中使用靜態方法。你可以在標準庫中找到像 Collection/Collections 或者 Path/Paths 這樣成對的介面和類。
預設方法:預設方法使用 default 關鍵字修飾。可以通過實現類物件來呼叫。我們在已有的介面中提供新方法的同時,還保持了與舊版本程式碼的相容性。比如:java 8 API 中對 Collection、List、Comparator 等介面提供了豐富的預設方法。
public interface AA {
double PI = 3.14;
public default void method() {
System.out.println("北京");
}
default String method1() {
return "上海";
}
public static void method2() {
System.out.println(“hello lambda!");
}
}
若一個介面中定義了一個預設方法,而另外一個介面中也定義了一個同名同
引數的方法(不管此方法是否是預設方法),在實現類同時實現了這兩個接
口時,會出現介面衝突。解決辦法:實現類必須覆蓋介面中同名同參數的方法,來解決衝突。
若一個介面中定義了一個預設方法,而父類中也定義了一個同名同參數的非
抽象方法,則不會出現衝突問題。因為此時遵守類優先原則。介面中具有相同名稱和引數的預設方法會被忽略。
代理模式
代理模式是 Java 開發中使用較多的一種設計模式。代理設計就是為其他物件提供一種代理以控制對這個物件的訪問。
信用卡是銀行賬戶的代理, 銀行賬戶則是一大捆現金的代理。 它們都實現了同樣的介面, 均可用於進行支付。 消費者會非常滿意, 因為不必隨身攜帶大量現金; 商店老闆同樣會十分高興, 因為交易收入能以電子化的方式進入商店的銀行賬戶中, 無需擔心存款時出現現金丟失或被搶劫的情況。
package com.parzulpan.java.ch04;
/**
* @Author : parzulpan
* @Time : 2020-11-22
* @Desc : 介面的應用:代理模式
*/
public class ProxyTest {
public static void main(String[] args) {
Star s = new Proxy(new RealStar());
s.confer();
s.signContract();
s.bookTicket();
s.sing();
s.collectMoney();
}
}
interface Star {
void confer();// 面談
void signContract();// 籤合同
void bookTicket();// 訂票
void sing();// 唱歌
void collectMoney();// 收錢
}
// 代理類
class RealStar implements Star {
public void confer() {
}
public void signContract() {
}
public void bookTicket() {
}
public void sing() {
System.out.println("明星:歌唱~~~");
}
public void collectMoney() {
}
}
// 被代理類
class Proxy implements Star {
private Star real;
public Proxy(Star real) {
this.real = real;
}
public void confer() {
System.out.println("經紀人面談");
}
public void signContract() {
System.out.println("經紀人籤合同");
}
public void bookTicket() {
System.out.println("經紀人訂票");
}
public void sing() {
real.sing();
}
public void collectMoney() {
System.out.println("經紀人收錢");
}
}
應用場景:
- 安全代理:遮蔽對真實角色的直接訪問;
- 遠端代理:通過代理類處理遠端方法呼叫(RMI);
- 延遲載入:先載入輕量級的代理物件,真正需要再載入真實物件。
代理分類:
- 靜態代理(靜態定義代理類);
- 動態代理(動態生成代理類)。
內部類
當一個事物的內部,還有一個部分需要一個完整的結構進行描述。而這個內部的完整的結構又只為外部事物提供服務,那麼整個內部的完整結構最好使用內部類。
在 Java 中,允許一個類的定義位於另一個類的內部,前者稱為內部類,後者稱為外部類。
內部類的分類:
- 成員內部類(static 成員內部類 和 非 static 成員內部類);
- 區域性內部類(不談修飾符,方法內、程式碼塊內、構造器內)、匿名內部類。
成員內部類作為類的成員:
- 可以呼叫外部類的結構;
- 可以被 static 修飾;
- 可以被 4 中許可權修飾符修飾。
成員內部類作為類:
- 可以在內部定義屬性、方法、構造器等結構;
- 可以宣告為 abstract 的 ,因此可以被其它的內部類繼承;
- 可以宣告為 final 的,即不能被其它類繼承;
- 編譯以後生成 OuterClass$InnerClass.class 位元組碼檔案。
class Person {
// 靜態成員內部類
static class A {
}
// 非靜態成員內部類
class B {
}
Person() {
class E {
}
}
public void method() {
class C {
}
}
{
class D {
}
}
}
注意:
- 如何例項化成員內部類的物件?InnerClassTest1
- 如何在成員內部類中區分呼叫外部類的結構?InnerClassTest1
- 開發中區域性內部類的使用?InnerClassTest2
練習和總結
以下程式碼的執行情況?
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
// System.out.println(x);
System.out.println(super.x); // 更改 1
System.out.println(A.x); // 更改 0
}
public static void main(String[] args) {
new C().pX();
}
}
編譯出錯,x 屬性不明確。
以下程式碼的執行情況?
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball = new Ball("PingPang");
}
class Ball implements Rollable {
private String name;
public String getName() {
return name;
}
public Ball(String name) {
this.name = name;
}
public void play() {
ball = new Ball("Football");
System.out.println(ball.getName());
}
interface Rollable 裡的 ball 是 全域性常量 public static final,Ball 中的 play() 重新給 ball 賦值了。