Java 面向?qū)ο笠挥[
本文轉(zhuǎn)載自微信公眾號(hào)「蝸?;ヂ?lián)網(wǎng)」,作者白色蝸牛。轉(zhuǎn)載本文請(qǐng)聯(lián)系蝸牛互聯(lián)網(wǎng)公眾號(hào)。
本文大綱:
前言
學(xué) Java 的朋友都知道,Java 是一門典型的面向?qū)ο蟮母呒?jí)程序設(shè)計(jì)語(yǔ)言,但有些朋友可能不清楚面向?qū)ο笤?Java 中是怎么體現(xiàn)的。這篇文章就向大家分享下 Java 在面向?qū)ο蠓矫娴囊恍┲R(shí)。
Java 語(yǔ)言簡(jiǎn)介
Java 語(yǔ)言特點(diǎn)
首先我們看下 Java 的語(yǔ)言特點(diǎn),如圖所示。
Java 是純粹的面向?qū)ο笳Z(yǔ)言,它因統(tǒng)一的字節(jié)碼文件和差異化的 JDK 而具有平臺(tái)無(wú)關(guān)的特性。
Java 內(nèi)置豐富的類庫(kù),使開(kāi)發(fā)者效率大為提升。它支持 web,廣泛應(yīng)用于各大互聯(lián)網(wǎng)企業(yè)的網(wǎng)站后臺(tái),像阿里美團(tuán)都在使用。
Java 的安全性也很出眾,通過(guò)沙箱安全模型保證其安全性,能夠有效防止代碼攻擊。
Java 也具備很強(qiáng)的健壯性,比如它是強(qiáng)類型的,支持自動(dòng)化的垃圾回收器,有完善的異常處理機(jī)制和安全檢查機(jī)制。
與 C++ 比較
比較點(diǎn) | C++ | Java |
---|---|---|
語(yǔ)言類型 | 編譯型語(yǔ)言 | 解釋編譯混合型語(yǔ)言 |
執(zhí)行速度 | 快 | 慢 |
是否跨平臺(tái) | 否 | 是 |
面向?qū)ο?/td> | 面向?qū)ο蠛兔嫦蜻^(guò)程混合 | 純面向?qū)ο?/td> |
指針 | 有 | 無(wú) |
多繼承 | 支持 | 不支持 |
內(nèi)存管理 | 手動(dòng) | 自動(dòng) |
從語(yǔ)言類型上看,C++ 的代碼編譯好,就能被計(jì)算機(jī)直接執(zhí)行,它是編譯型語(yǔ)言,而 Java 經(jīng)過(guò) javac 把 java 文件編譯成 class 文件后,還需要 JVM 從 class 文件讀一行解釋執(zhí)行一行,它是解釋編譯混合型語(yǔ)言。也就是中間多了 JVM 這一道,Java 也具備了跨平臺(tái)特性,而 C++ 就沒(méi)有這個(gè)優(yōu)勢(shì)。
從面向?qū)ο蟮慕嵌壬峡?,C++ 是在 C 的基礎(chǔ)上的新的探索和延伸,因此它是面向?qū)ο蠛兔嫦蜻^(guò)程混合的,而 Java 就是純粹的面向?qū)ο蟆?/p>
此外,C++ 有指針的概念,Java 沒(méi)有。C++ 支持多繼承,Java 不支持。C++ 需要手動(dòng)進(jìn)行內(nèi)存管理,Java 通過(guò)垃圾回收機(jī)制實(shí)現(xiàn)了內(nèi)存的自動(dòng)管理。
面向?qū)ο笏枷?/h2>
我們總在提面向?qū)ο?,那面向?qū)ο缶烤故莻€(gè)什么東西呢?在面向?qū)ο蟪霈F(xiàn)之前的面向過(guò)程又是怎么回事呢?
其實(shí)無(wú)論是面向?qū)ο筮€是面向過(guò)程,都是我們?cè)诰幊虝r(shí)解決問(wèn)題的一種思維方式。
只是在最初,人們分析解決問(wèn)題的時(shí)候,會(huì)把所需要的步驟都列出來(lái),然后通過(guò)計(jì)算機(jī)中的函數(shù)把這些步驟挨個(gè)實(shí)現(xiàn),這種過(guò)程化的敘事思維,就是面向過(guò)程思想。
你比如,把一頭大象放進(jìn)冰箱,通常會(huì)怎么做呢?
我們的習(xí)慣性思維是會(huì)分為三步,第一步,把冰箱門打開(kāi),第二步,把大象推進(jìn)去,第三步,把冰箱門關(guān)閉(假設(shè)大象很乖,冰箱很大,門能關(guān)住)。
這種方式固然可行,但當(dāng)場(chǎng)景發(fā)生變化時(shí),比如大象變成豬,冰箱變成衣柜,類似的步驟用面向過(guò)程編碼的話就要再寫一遍。這樣就導(dǎo)致代碼開(kāi)發(fā)變成了記流水賬,久而久之就成為面條代碼。
我們仔細(xì)分析面向過(guò)程的這些步驟,會(huì)發(fā)現(xiàn)都是命令式的動(dòng)賓結(jié)構(gòu):開(kāi)冰箱門,推大象,場(chǎng)景切換下就是開(kāi)衣柜門,推豬。你會(huì)發(fā)現(xiàn)從這兩種場(chǎng)景下是可以找到共性的,就是冰箱門和衣柜門都有打開(kāi)和關(guān)閉的特點(diǎn),大象和豬都能走路,所以能被人推進(jìn)去。
當(dāng)我們的視角不再是流程,而是操作對(duì)象的時(shí)候,冰箱門和衣柜門都可以抽象成門,有打開(kāi)和關(guān)閉的特點(diǎn),大象和豬都可以抽象成動(dòng)物,有走路的特點(diǎn)。按這個(gè)思路,我們可以把這件事簡(jiǎn)化成主謂結(jié)構(gòu):門打開(kāi),動(dòng)物走進(jìn)去,門關(guān)閉。
這種把事情分解成各個(gè)對(duì)象,描述對(duì)象在整個(gè)事情中的行為,就是面向?qū)ο笏枷搿?/p>
你會(huì)發(fā)現(xiàn),面向過(guò)程更講事情的步驟,面向?qū)ο蟾v對(duì)象的行為。
面向?qū)ο罂梢曰趯?duì)象的共性做抽象,為軟件工程的復(fù)用和擴(kuò)展打好了堅(jiān)實(shí)的基礎(chǔ)。這也是為什么在很多大型軟件開(kāi)發(fā)選型上,大多會(huì)使用面向?qū)ο笳Z(yǔ)言編程。
面向?qū)ο蠡A(chǔ)
Java 作為純面向?qū)ο笳Z(yǔ)言,我們有必要了解下面向?qū)ο蟮幕A(chǔ)知識(shí)。
面向?qū)ο笥兴拇筇卣?,是抽象,封裝,繼承和多態(tài)。也有很多人認(rèn)為是三大特征,不包括抽象,但我覺(jué)得抽象才是面向?qū)ο笏枷胱顬楹诵牡奶卣鳎渌齻€(gè)特征無(wú)非是抽象這個(gè)特征的實(shí)現(xiàn)或擴(kuò)展。
我總結(jié)了下這四大特征在面向?qū)ο箢I(lǐng)域分別解決了什么問(wèn)題,再逐一介紹:
- 抽象:解決了模型的定義問(wèn)題。
- 封裝:解決了數(shù)據(jù)的安全問(wèn)題。
- 繼承:解決了代碼的重用問(wèn)題。
- 多態(tài):解決了程序的擴(kuò)展問(wèn)題。
抽象
抽象是面向?qū)ο蟮暮诵奶卣?,良好的業(yè)務(wù)抽象和建模分析能力是后續(xù)封裝、繼承和多態(tài)的基礎(chǔ)。
面向?qū)ο笏季S中的抽象分為歸納和演繹兩種。
歸納是從具體到本質(zhì),從個(gè)性到共性,將一類對(duì)象的共同特征進(jìn)行歸一化的邏輯思維過(guò)程。比如我們把見(jiàn)到的像大象,老虎,豬這些能動(dòng)的有生命的對(duì)象,歸納成動(dòng)物。
演繹是從本質(zhì)到具體,從共性到個(gè)性,將對(duì)象逐步形象化的過(guò)程。比如從生物到動(dòng)物,從動(dòng)物到鳥(niǎo)類。演繹的結(jié)果不一定是具體的對(duì)象,也可以是像鳥(niǎo)類這種抽象結(jié)果,因此演繹仍然是抽象思維,而非具象思維。
Java 中的 Object 類是任何類的默認(rèn)父類,是對(duì)萬(wàn)物的抽象。這就是我們常說(shuō)的:萬(wàn)物皆對(duì)象。
看一看 java.lang.Object 類的源碼,我們基本能看到 Java 世界里對(duì)象的共同特征。
getClass() 說(shuō)明了對(duì)象是誰(shuí),toString() 是對(duì)象的名片,clone() 是繁殖對(duì)象的方式, finalize() 是銷毀對(duì)象的方式,hashCode() 和 equals() 是判斷當(dāng)前對(duì)象與其他對(duì)象是否相等的方式,wait() 和 notify() 是對(duì)象間通信與協(xié)作的方式。
類的定義
除了 JDK 中提供的類之外,我們也可以基于自己業(yè)務(wù)場(chǎng)景的抽象定義類。
我們看下 Java 語(yǔ)法中的 class(類)是怎么構(gòu)成的。
以下是概覽圖,我們按圖介紹。
我們先關(guān)注圖中的黃色區(qū)塊,在 Java 里就叫 class(類)。
好比一個(gè)事物有屬性和能力一樣,比如人有名字,人能吃飯。對(duì)應(yīng)到 Java class 里就是變量和方法,即紅色區(qū)塊和紫色區(qū)塊。
變量分為成員變量,靜態(tài)變量和局部變量三種,方法分為構(gòu)造方法、實(shí)例方法和靜態(tài)方法三種。
我們舉個(gè)例子來(lái)說(shuō)明下,假設(shè)全世界的面包數(shù)量就 100 個(gè),并且生產(chǎn)已經(jīng)停滯,而且只有蝸牛和小白兩個(gè)人能吃到,我們就可以按以下的代碼來(lái)描述這兩個(gè)人吃面包的過(guò)程以及面包的情況。
- package cn.java4u.oo;
- /**
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public class Person {
- /**
- * [成員變量]需要被實(shí)例化后使用,每個(gè)實(shí)例都有獨(dú)立空間,通過(guò) 對(duì)象.成員變量名 訪問(wèn)
- * 名字
- */
- String name;
- /**
- * [靜態(tài)變量]用 static 修飾,無(wú)需實(shí)例化即可使用,每個(gè)實(shí)例共享同一個(gè)空間,通過(guò) 類名.靜態(tài)變量名 訪問(wèn)
- * 面包數(shù)量
- */
- static int breadNum;
- /**
- * [方法]
- * 吃一個(gè)面包
- *
- * @param num 方法入?yún)?,要吃面包的個(gè)數(shù)
- */
- void eatBread(int num) {
- // num 是[局部變量]
- breadNum = breadNum - num;
- System.out.println(name + "吃了 " + num + " 個(gè)面包,全世界的面包還剩 " + breadNum + " 個(gè)!");
- }
- /**
- * [構(gòu)造方法]
- * 參數(shù)為空
- */
- public Person() {
- }
- /**
- * [構(gòu)造方法]
- *
- * @param name 此為構(gòu)造方法的輸入?yún)?shù),和成員變量有關(guān)
- */
- public Person(String name) {
- this.name = name;
- }
- /**
- * [靜態(tài)方法]
- */
- static void testStaticMethod() {
- // 通過(guò)構(gòu)造方法,初始化名字叫蝸牛的人
- Person woniu = new Person("蝸牛");
- // 通過(guò)構(gòu)造方法,初始化名字叫小白的人
- Person xiaobai = new Person("小白");
- // 假設(shè)全世界的面包數(shù)量就 100 個(gè),并且生產(chǎn)已經(jīng)停滯
- Person.breadNum = 100;
- // 蝸牛吃五個(gè)面包
- woniu.eatBread(5);
- // 小白吃六個(gè)面包
- xiaobai.eatBread(6);
- // 打印成員變量和靜態(tài)變量的值
- System.out.println(woniu.name + "和" + xiaobai.name + "吃飽后,世界只剩 " + Person.breadNum + " 個(gè)面包了!");
- }
- }
變量
首先定義了一個(gè)名字叫 Person 的類,表示人,然后定義了一個(gè)成員變量 name ,表示人的名字。成員變量也叫實(shí)例變量,實(shí)例變量的特點(diǎn)就是,每個(gè)實(shí)例都有獨(dú)立的變量,各個(gè)實(shí)例之間的同名變量互不影響。
其次定義了一個(gè)靜態(tài)變量 breadNum ,表示面包的數(shù)量,靜態(tài)變量用 static 修飾。靜態(tài)變量相對(duì)于成員變量就不一樣了,它是共享的,所有實(shí)例會(huì)共享這個(gè)變量。
方法
再接著定義了一個(gè)返回值為空,只有一個(gè)入?yún)⒌姆椒?eatBread(int num) ,方法入?yún)?num 作為局部變量參與了內(nèi)部的運(yùn)算,通過(guò)和它的運(yùn)算,靜態(tài)變量breadNum 的值得到了更新,并打印了一行操作信息。方法的語(yǔ)法結(jié)構(gòu)如下:
- 修飾符 返回類型 方法名(方法參數(shù)列表) {
- 方法語(yǔ)句;
- return 方法返回值;
- }
另外定義了 Person 的構(gòu)造方法,你會(huì)發(fā)現(xiàn)構(gòu)造方法和實(shí)例方法的區(qū)別就在于它是沒(méi)有返回值的,因?yàn)樗哪康暮芗兇?,就是用?lái)初始化對(duì)象實(shí)例的,和 new 搭配使用,所以它的方法名就是類名,它的入?yún)⒁捕己统蓡T變量有關(guān)。
到這里,你會(huì)發(fā)現(xiàn) Java 方法的返回值并不是那么重要,甚至沒(méi)有都可以!是的,Java 方法簽名只包括名稱和參數(shù)列表,它們是 JVM 標(biāo)識(shí)方法的唯一索引,是不包含返回值的,更不包括各種修飾符或者異常類型。
請(qǐng)注意,任何 class 都是有構(gòu)造方法的,即便你代碼里不寫,Java 也會(huì)在編譯 class 文件的時(shí)候,默認(rèn)生成一個(gè)無(wú)參構(gòu)造方法。但是只要你手動(dòng)定義了構(gòu)造方法,編譯器就不會(huì)再生成。也就是說(shuō)如果你僅定義了一個(gè)有參的構(gòu)造方法,那么編譯后的 class 是不會(huì)有無(wú)參構(gòu)造方法的。
最后就是靜態(tài)方法了,名字叫testStaticMethod ,方法內(nèi)部我們先用 new 的語(yǔ)法調(diào)用構(gòu)造方法,初始化了蝸牛和小白的Person 對(duì)象。這兩個(gè)對(duì)象就是 Person 這個(gè)類的實(shí)例,這兩個(gè)實(shí)例都有獨(dú)立空間,name 這個(gè)成員變量也只能在被實(shí)例化后使用,可以通過(guò) 對(duì)象.成員變量名 訪問(wèn)。
接著我們通過(guò) Person.breadNum 也就是 類名.靜態(tài)變量名 的方式,更新了面包數(shù)量這個(gè)值。你會(huì)發(fā)現(xiàn) breadNum 這個(gè)靜態(tài)變量無(wú)需實(shí)例化就能使用,因?yàn)榫瓦@個(gè)變量而言,Person 的每個(gè)實(shí)例都會(huì)共享同一個(gè)空間。這意味著,每個(gè)實(shí)例的修改,都會(huì)影響到這個(gè)變量值的變化。
然后我們通過(guò)調(diào)用方法 eatBread 并傳參的方式,影響到了面包數(shù)的值。
- package cn.java4u.oo;
- /**
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public class MainTest {
- public static void main(String[] args) {
- // 靜態(tài)方法,通過(guò) 類名.靜態(tài)方法名 訪問(wèn)
- Person.testStaticMethod();
- }
- }
最后我們新定義一個(gè)觸發(fā)調(diào)用的入口函數(shù),通過(guò) Person.testStaticMethod() 這樣 類名.靜態(tài)方法名 的方式就能訪問(wèn)到靜態(tài)方法了。
抽象類與接口
抽象類顧名思義,就是會(huì)對(duì)同類事物做抽象,通常包括抽象方法、實(shí)例方法和成員變量。被抽象類和抽象類之間是 is-a 關(guān)系,這種關(guān)系要符合里氏替換原則,即抽象類的所有行為都適用于被抽象類,比如大象是一種動(dòng)物,動(dòng)物能做的事,大象都能做。代碼定義也很簡(jiǎn)單,就是在 class 和抽象方法上加 abstract 修飾符。
- package cn.java4u.oo;
- /**
- * 抽象類
- *
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public abstract class AbstractClass {
- String name;
- /**
- * 實(shí)例方法
- *
- * @return name
- */
- public String getName() {
- return name;
- }
- /**
- * 抽象方法-操作
- *
- * @return 結(jié)果
- */
- public abstract String operate();
- }
如果一個(gè)抽象類只有一個(gè)抽象方法,那它就等于一個(gè)接口。接口是要求被普通類實(shí)現(xiàn)的,接口在被實(shí)現(xiàn)時(shí)體現(xiàn)的是 can-do 關(guān)系,它表達(dá)了對(duì)象具備的能力。鳥(niǎo)有飛的能力,宇宙飛船也有飛的能力,那么可以把飛的能力抽出來(lái),有單獨(dú)的一個(gè)抽象方法。代碼定義也比較簡(jiǎn)單,class 的關(guān)鍵字用 interface 來(lái)替換。
- package cn.java4u.oo;
- /**
- * 可飛翔
- *
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public interface Flyable {
- /**
- * 飛
- */
- void fly();
- }
內(nèi)部類
在 Java 源代碼文件中,只能定義一個(gè)類目與文件名完全一致的公開(kāi)類。如果想在一個(gè)文件里定義另外一個(gè)類,在面向?qū)ο罄镆彩侵С值?,那就是?nèi)部類。
內(nèi)部類分為以下四種:
- 靜態(tài)內(nèi)部類:static class StaticInnerClass {}
- 成員內(nèi)部類:private class InstanceInnerClass {}
- 局部?jī)?nèi)部類:class MethodClass {} ,定義在方法或者表達(dá)式內(nèi)部
- 匿名內(nèi)部類:(new Thread() {}).start();
示例代碼如下:
- package cn.java4u.oo.innerclass;
- /**
- * 內(nèi)部類演示
- *
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public class InnerClassDemo {
- /**
- * 成員內(nèi)部類
- */
- private class InstanceInnerClass {}
- /**
- * 靜態(tài)內(nèi)部類
- */
- static class StaticInnerClass {}
- public static void main(String[] args) {
- // 兩個(gè)匿名內(nèi)部類
- (new Thread() {}).start();
- (new Thread() {}).start();
- // 方法內(nèi)部類
- class MethodClass {}
- }
- }
編譯后得到的 class 文件如下:
我們會(huì)發(fā)現(xiàn),無(wú)論什么類型的內(nèi)部類,都會(huì)編譯生成一個(gè)獨(dú)立的 .class 文件,只是內(nèi)部類文件的命名會(huì)通過(guò) $ 連接在外部類后面,如果是匿名內(nèi)部類,會(huì)使用編號(hào)來(lái)標(biāo)識(shí)。
類關(guān)系
關(guān)系是指事物之間有沒(méi)有單向或者相互作用或者影響的狀態(tài)。
類和類之間的關(guān)系分為 6 種:
- 繼承:extends(is-a)
- 實(shí)現(xiàn):implements(can-do)
- 組合:類是成員變量(contains-a)
- 聚合:類是成員變量(has-a)
- 依賴:?jiǎn)蜗蛉蹶P(guān)系(使用類屬性,類方法、作為方法入?yún)?、作為方法出?
- 關(guān)聯(lián):互相平等的依賴關(guān)系(links-a)
序列化
內(nèi)存中的數(shù)據(jù)對(duì)象只有轉(zhuǎn)換為二進(jìn)制流才可以進(jìn)行數(shù)據(jù)持久化和網(wǎng)絡(luò)傳輸。
將數(shù)據(jù)對(duì)象轉(zhuǎn)換成二進(jìn)制流的過(guò)程稱為對(duì)象的序列化(Serialization)。
將二進(jìn)制流恢復(fù)為數(shù)據(jù)對(duì)象的過(guò)程稱為反序列化(Deserialization)。
常見(jiàn)的序列化使用場(chǎng)景是 RPC 框架的數(shù)據(jù)傳輸。
常見(jiàn)的序列化方式有三種:
- Java 原生序列化。特點(diǎn)是兼容性好,不支持跨語(yǔ)言,性能一般。
- Hessian 序列化。特點(diǎn)是支持跨語(yǔ)言,性能高效。
- JSON 序列化。特點(diǎn)是可讀性好,但有安全風(fēng)險(xiǎn)。
封裝
封裝是在抽象基礎(chǔ)上決定信息是否公開(kāi),以及公開(kāi)等級(jí),核心問(wèn)題是以什么樣的方式暴露哪些信息。
抽象是要找到成員和行為的共性,成員是行為的基本生產(chǎn)資料,具有一定的敏感性,不能直接對(duì)外暴露。封裝的主要任務(wù)是對(duì)成員、數(shù)據(jù)、部分內(nèi)部敏感行為實(shí)現(xiàn)隱藏。
對(duì)成員的訪問(wèn)與修改必須通過(guò)定義公共的接口來(lái)進(jìn)行,另外某些敏感方法或者外部不需要感知的復(fù)雜邏輯處理,一般也會(huì)進(jìn)行封裝。
像智能音箱,與用戶交互的唯一接口就是語(yǔ)音輸入,封裝了內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)和相關(guān)數(shù)據(jù)。
設(shè)計(jì)模式七大原則之一的迪米特法則也說(shuō)明了封裝的要求,A 接口使用 B 接口,對(duì) B 知道的要盡可能少。
包
包(package)這個(gè)名稱就很明顯體現(xiàn)了封裝的含義,它能起到把一個(gè)模塊封裝到一起,并由幾個(gè)接口開(kāi)放給使用方。使用方只能看到接口信息,而看不到接口實(shí)現(xiàn)。另外包解決重名問(wèn)題,相同類名在相同路徑下是不允許的,切換包路徑就可以起相同的類名。
訪問(wèn)權(quán)限控制
我們編寫的程序要想讓使用方,能看到一些信息,又不能看到另外一些信息,這就涉及到信息隱藏了。
信息隱藏是面向?qū)ο蟪绦蛟O(shè)計(jì)的重要特點(diǎn)之一,它可以防止類的使用者意外損壞數(shù)據(jù),對(duì)任何實(shí)現(xiàn)細(xì)節(jié)所作的修改不會(huì)影響到使用該類的其它代碼,也使類更易于使用。
那在 Java 里,實(shí)現(xiàn)信息隱藏的就是訪問(wèn)權(quán)限控制機(jī)制了。Java 的訪問(wèn)權(quán)限控制有 4 個(gè)訪問(wèn)修飾符:public 、protected 、private 和缺省??梢允褂眠@四個(gè)訪問(wèn)修飾符修飾類的成員,它們?cè)诓煌恢玫目稍L問(wèn)性如下表所示。
位置\訪問(wèn)修飾符 | public | protected | 缺省 | private |
---|---|---|---|---|
本類 | 可以 | 可以 | 可以 | 可以 |
本包 | 可以 | 可以 | 可以 | 不可以 |
子類 | 可以 | 可以 | 不可以 | 不可以 |
所有 | 可以 | 不可以 | 不可以 | 不可以 |
你會(huì)發(fā)現(xiàn) public 不受任何限制,本類和非本類都可以隨意訪問(wèn)(全局友好)。protected 本類及其子類可以訪問(wèn)(父子友好),同一個(gè)包中的其它類也可以訪問(wèn)(包內(nèi)友好)。而缺省的時(shí)候,只有相同包中的類可以訪問(wèn)(包內(nèi)友好)。private 只有本類可以訪問(wèn),其余都不可以(類內(nèi)友好)。
除了為類成員添加訪問(wèn)權(quán)限控制外,也可以在定義類的時(shí)候,為類添加訪問(wèn)修飾符,對(duì)類進(jìn)行訪問(wèn)權(quán)限控制。不過(guò)對(duì)類使用的訪問(wèn)修飾符只有 public 和缺省兩種,訪問(wèn)范圍也分別是全局友好和包內(nèi)友好。
getter 與 setter
為了讓類成員不對(duì)外直接暴露,我們經(jīng)常把成員變量的訪問(wèn)權(quán)限設(shè)置成 private,而成員值的訪問(wèn)與修改使用相應(yīng)的 getter/setter 方法。而不是對(duì) public 的成員進(jìn)行讀取和修改。
- package cn.java4u.oo.packagedemo;
- /**
- * getter 和 setter 演示
- * @author 蝸牛
- * @from 公眾號(hào):蝸牛互聯(lián)網(wǎng)
- */
- public class GetterSetterDemo {
- /**
- * 成員變量私有化
- */
- private String name;
- /**
- * 公開(kāi)方法獲取成員變量值
- *
- * @return 名稱
- */
- public String getName() {
- return name;
- }
- /**
- * 公開(kāi)方法設(shè)置成員變量值
- *
- * @param name 名稱
- */
- public void setName(String name) {
- this.name = name;
- }
- }
繼承
類繼承
class 了解之后,我們考慮一個(gè)問(wèn)題。如果兩個(gè) class,它們的變量和方法基本相同,僅僅是其中一個(gè) class 會(huì)有一些自己特有的變量和方法,那么相同的那些變量和方法真的需要在兩個(gè) class 里都寫一遍么?
比如一個(gè)表示學(xué)生的 class Student ,它相對(duì)于 class Person 只是多了一個(gè)分?jǐn)?shù) score 的成員變量,那還需要像下面這樣,把 name 字段也定義一下么?
- /**
- * 學(xué)生
- *
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public class Student {
- /**
- * 名字
- */
- String name;
- /**
- * 分?jǐn)?shù)
- */
- int score;
- }
這很明顯帶來(lái)了代碼重復(fù)使用的問(wèn)題!那能不能在 Student 中不寫重復(fù)代碼?
Java 里的繼承這時(shí)候就派上用場(chǎng)了,繼承是面向?qū)ο缶幊痰囊环N強(qiáng)大機(jī)制,能夠讓子類繼承父類的特征和行為,使得子類對(duì)象能夠具有父類的實(shí)例變量和方法。
子類繼承父類,父類派生子類。父類也叫基類,子類也叫派生類。
通常來(lái)講,類的層次劃分總是下一層比上一層更具體,并且包含上一層的特征,這樣下層的類就能自動(dòng)享有上層類的特點(diǎn)和性質(zhì)。繼承就是派生類自動(dòng)地共享基類中成員變量和成員方法的機(jī)制。
在 Java 中,通過(guò) extends 關(guān)鍵字實(shí)現(xiàn)繼承,并且所有的類都是繼承于 java.lang.Object ,所以這就是萬(wàn)物皆對(duì)象在 Java 里的真實(shí)寫照。你可能會(huì)疑惑,自定義的類并沒(méi)有 extends 關(guān)鍵字為什么還能繼承 Object 呢?這是因?yàn)檫@個(gè)類在 java.lang 包里,Java 已經(jīng)默認(rèn)支持了。
- package cn.java4u.oo;
- /**
- * 學(xué)生
- *
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public class Student extends Person {
- /**
- * 分?jǐn)?shù)
- */
- int score;
- }
知道了繼承的基礎(chǔ)概念后,我們看下繼承有啥作用?
首先,繼承是能夠自動(dòng)傳播代碼和重用代碼的有力工具。它能在已有類上擴(kuò)充新類,減少代碼的重復(fù)冗余,也因?yàn)槿哂喽冉档?,一致性就得到了增?qiáng),從而提升了程序的可維護(hù)性。
其次,繼承可以清晰體現(xiàn)出類與類之間的層次結(jié)構(gòu)關(guān)系,提升了代碼的可讀性。
另外,繼承是單方向的,即派生類可以繼承和訪問(wèn)基類成員,但反過(guò)來(lái)就不行。而且 Java 只允許單一繼承,也就是一個(gè)派生類不能同時(shí)繼承多個(gè)基類,這和 C++ 是不同的。
在使用繼承的時(shí)候,還要考慮到基類成員的訪問(wèn)控制權(quán)限??梢詤⒖挤庋b那塊內(nèi)容的訪問(wèn)權(quán)限控制介紹。
子類實(shí)例化過(guò)程
特別要說(shuō)明的是,父類的構(gòu)造方法是不能被子類繼承的,即便它是 public 的。父類的構(gòu)造方法負(fù)責(zé)初始化屬于它的成員變量,而子類的構(gòu)造方法只需考慮自己特有的成員變量即可,不必關(guān)注父類狀況。
- package cn.java4u.oo.inherit;
- /**
- * 定義父類
- *
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public class Parent {
- /**
- * 構(gòu)造方法
- */
- public Parent() {
- System.out.println("這是父類 Parent 的構(gòu)造方法");
- }
- }
- package cn.java4u.oo.inherit;
- /**
- * 定義子類
- *
- * @author 蝸牛
- * @from 公眾號(hào):蝸牛互聯(lián)網(wǎng)
- */
- public class Child extends Parent {
- /**
- * 構(gòu)造方法
- */
- public Child() {
- System.out.println("這是子類 Child 的構(gòu)造方法");
- }
- }
- package cn.java4u.test;
- import cn.java4u.oo.inherit.Child;
- /**
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public class InheritTest {
- public static void main(String[] args) {
- Child child = new Child();
- }
- }
因此,在實(shí)例化子類的對(duì)象時(shí),Java 先是執(zhí)行父類的構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法。如果父類還有更上級(jí)的父類,就會(huì)先調(diào)用更高父類的構(gòu)造方法,再逐個(gè)依次地將所有繼承關(guān)系的父類構(gòu)造方法全部執(zhí)行。如果父類的構(gòu)造方法執(zhí)行失敗,則子類的對(duì)象也將無(wú)法實(shí)例化。
上邊的代碼運(yùn)行后,會(huì)輸出:
- 這是父類 Parent 的構(gòu)造方法
- 這是子類 Child 的構(gòu)造方法
this 與 super
如果調(diào)用父類構(gòu)造方法涉及到有參構(gòu)造方法,可以使用 super 關(guān)鍵字來(lái)調(diào)用父類構(gòu)造方法并傳遞參數(shù)。
說(shuō)的 super,它還有一個(gè)能力,就是父類和子類的成員如果同名了,子類中默認(rèn)只能訪問(wèn)自己的那個(gè)成員,想要訪問(wèn)父類成員,就可以通過(guò) super.成員名 的語(yǔ)法實(shí)現(xiàn)。但這有個(gè)前提,就是父類的這個(gè)成員不能是 private 的。
與 super 相對(duì)的關(guān)鍵字是 this ,super 是指向當(dāng)前對(duì)象的父類,而 this 是指向當(dāng)前對(duì)象自己。this 常用來(lái)區(qū)別成員變量和局部變量,比如下面這段代碼,我加了個(gè)有參構(gòu)造方法。
- public class Parent {
- int a;
- /**
- * 構(gòu)造方法
- */
- public Parent() {
- System.out.println("這是父類 Parent 的構(gòu)造方法");
- }
- public Parent(int a) {
- this.a = a;
- }
- }
多態(tài)
說(shuō)完繼承,我們?cè)賮?lái)聊聊多態(tài)!
多態(tài)字面上解釋,就是程序可以有多個(gè)運(yùn)行狀態(tài)。
既然是運(yùn)行狀態(tài),那其實(shí)更多的是強(qiáng)調(diào)方法的使用。
重載與覆寫
方法在兩種情況下使用會(huì)比較特別,一種是 overload(重載),overload 方法是本類內(nèi)的新方法,方法名一樣,但是參數(shù)的類型或數(shù)量不同。這種方法沒(méi)有特殊的標(biāo)識(shí),通過(guò)類內(nèi)方法是否重名判定。
另外一種就是 override(覆寫),override 方法是繼承關(guān)系下子類的新方法,方法簽名和父類完全相同。這種方法都會(huì)有 @Override 注解的標(biāo)識(shí)。
- package cn.java4u.oo.polymorphism;
- /**
- * 動(dòng)物
- *
- * @author 蝸牛
- * @from 公眾號(hào):蝸牛互聯(lián)網(wǎng)
- */
- public class Animal {
- /**
- * 與 eat(String food) 重載
- */
- public void eat() {
- System.out.println("Animal.eat");
- }
- /**
- * 與 eat() 重載
- *
- * @param food 食物
- */
- public void eat(String food) {
- System.out.println("Animal.eat: " + food);
- }
- /**
- * 覆寫
- *
- * @return 字符串
- * @see java.lang.Object#toString
- */
- @Override
- public String toString() {
- return "Animal " + super.toString();
- }
- }
舉個(gè)例子,Animal 類里兩個(gè) eat 方法就互為重載方法,toString 方法就是相對(duì)于父類方法 java.lang.Object#toString 的覆寫方法。
多態(tài)就發(fā)生在覆寫這種場(chǎng)景下。針對(duì)某個(gè)類型的方法調(diào)用,它真正執(zhí)行的方法取決于運(yùn)行時(shí)期實(shí)際類型的方法。比如下面這段代碼,當(dāng)聲明類型為 Object ,初始化類型為 Animal 時(shí),你覺(jué)得輸出的是 Animal 的 toString 方法,還是 Object 的 toString 方法?
- package cn.java4u.oo.polymorphism;
- /**
- * @author 蝸牛
- * @from 公眾號(hào):蝸?;ヂ?lián)網(wǎng)
- */
- public class PolymorphismTest {
- /**
- * 打印對(duì)象
- *
- * @param scene 打印場(chǎng)景
- * @param obj obj
- */
- public static void printObjectString(String scene, Object obj) {
- System.out.println(scene + ": " + obj.toString());
- }
- public static void main(String[] args) {
- // 父類引用初始化父類對(duì)象并打印
- Object rootObj = new Object();
- printObjectString("父類引用初始化父類對(duì)象", rootObj);
- // 子類引用初始化子類對(duì)象并打印
- Animal animal = new Animal();
- printObjectString("子類引用初始化子類對(duì)象", animal);
- // 父類引用初始化子類對(duì)象并打印
- Object animalWhenParentRef = new Animal();
- printObjectString("父類引用初始化子類對(duì)象", animal);
- }
- }
答案是子類 Animal 的 toString 方法!
- 父類引用初始化父類對(duì)象: java.lang.Object@60e53b93
- 子類引用初始化子類對(duì)象: Animal cn.java4u.oo.polymorphism.Animal@5e2de80c
- 父類引用初始化子類對(duì)象: Animal cn.java4u.oo.polymorphism.Animal@5e2de80c
實(shí)際類型為 Animal 引用類型為 Object ,調(diào)用 toString 方法時(shí),實(shí)際上是子類的。因此我們可以得出結(jié)論:Java 的實(shí)例方法調(diào)用是基于運(yùn)行時(shí)的實(shí)際類型的動(dòng)態(tài)調(diào)用,而非變量的聲明類型。這種特性就是多態(tài)!
你會(huì)發(fā)現(xiàn) printObjectString 方法的第二個(gè)參數(shù),即便聲明的是 Object ,實(shí)際運(yùn)行的時(shí)候,卻可以是它的子類覆寫方法。
至此,我們也理出了 Java 實(shí)現(xiàn)多態(tài)三要素,那就是 繼承、覆寫和向上轉(zhuǎn)型。即兩個(gè)類之間有繼承關(guān)系,某個(gè)類覆寫了父類的某個(gè)方法,方法的引用會(huì)指向子類的實(shí)現(xiàn)處。
總結(jié)
本文從 Java 的視角出發(fā),分析了 Java 的語(yǔ)言特點(diǎn),并和 C++ 進(jìn)行了比較。針對(duì)這門典型的面向?qū)ο笳Z(yǔ)言,我們又分析了面向?qū)ο蟮母拍詈退枷搿=又诿嫦驅(qū)ο蟮奶卣鳎撼橄?、封裝、繼承和多態(tài),我們又詳細(xì)的分析了在 Java 中的體現(xiàn)方式,并伴有很多樣例代碼輔助學(xué)習(xí)。看完這篇文章,想必你對(duì)面向?qū)ο筮@個(gè)東西會(huì)有更全面的了解。
好啦,本期的分享就到這里,如果各位喜歡我的分享,請(qǐng)務(wù)必三連,點(diǎn)贊,在看,收藏,關(guān)注我,這會(huì)對(duì)我有非常大的幫助。
我們下期再見(jiàn)。