在初遇章節(jié)我們就談到過Java是一門面向?qū)ο蟮恼Z言,那么什么是面向?qū)ο竽??既然有面向?qū)ο笳Z言,是否就有其他的語言?面向?qū)ο笥帜芙o我么帶來什么好處呢?接下來,我們將在這個(gè)章節(jié)探討下面向?qū)ο蟆?/p>
面向過程和面向?qū)ο?/h3>
在目前的軟件開發(fā)領(lǐng)域有兩種主流的開發(fā)方法:結(jié)構(gòu)化開發(fā)方法(面向過程)和面向?qū)ο箝_發(fā)方法。早期的編程語言C、Basic、Pascal等都是結(jié)構(gòu)化編程語言,隨著時(shí)代的變遷,軟件的發(fā)展,人們發(fā)現(xiàn)了一種更好的可復(fù)用、可擴(kuò)展和可維護(hù)的的方法,即面向?qū)ο?,代表語言有C++,C#,Ruby,Java等。
- 面向過程
主張按功能來設(shè)計(jì)程序,特點(diǎn)是:自上而下,逐步求精,模塊化等。結(jié)構(gòu)化程序設(shè)計(jì)的最小單元是函數(shù),每個(gè)函數(shù)都負(fù)責(zé)完成一個(gè)功能。局限性有兩點(diǎn):一,設(shè)計(jì)不夠直觀,與人類習(xí)慣思維不一致;二,適應(yīng)性差,可擴(kuò)展性不強(qiáng)。 - 面向?qū)ο?br> 更優(yōu)秀的程序設(shè)計(jì)思想,基本思想是使用類、對象、繼承、封裝、消息等基本概念進(jìn)行程序設(shè)計(jì)。最小單位是類,由類可以生成系統(tǒng)中多個(gè)對象。
面向?qū)ο蟮幕咎卣?/h3>
- 封裝
隱藏細(xì)節(jié),通過公共方法暴露出該對象的功能。比如說一臺電腦,我們在不拆機(jī)的情況下看不到里面的主板,cpu,內(nèi)存條,這些好比是私有方法,我們無法直接訪問,但是我們可以訪問它的鍵盤,開機(jī)鍵,顯示器,這些就是公共方法。
- 繼承
軟件復(fù)用的重要手段,子類繼承父類,可以直接復(fù)用父類的屬性和方法。
- 多態(tài)
子類對象直接賦給父類變量,運(yùn)行的時(shí)候表現(xiàn)為子類的特性。
抽象也是面向?qū)ο蟮闹匾M成之一,但是不是基本特征。抽象是抽取我們當(dāng)前目標(biāo)所需要的東西,排除一些無關(guān)的信息。
Java面向?qū)ο筇卣?/h3>
隱藏細(xì)節(jié),通過公共方法暴露出該對象的功能。比如說一臺電腦,我們在不拆機(jī)的情況下看不到里面的主板,cpu,內(nèi)存條,這些好比是私有方法,我們無法直接訪問,但是我們可以訪問它的鍵盤,開機(jī)鍵,顯示器,這些就是公共方法。
軟件復(fù)用的重要手段,子類繼承父類,可以直接復(fù)用父類的屬性和方法。
子類對象直接賦給父類變量,運(yùn)行的時(shí)候表現(xiàn)為子類的特性。
抽象也是面向?qū)ο蟮闹匾M成之一,但是不是基本特征。抽象是抽取我們當(dāng)前目標(biāo)所需要的東西,排除一些無關(guān)的信息。
在初遇章節(jié)我們就談過Java的面向?qū)ο筇卣?,我們這里再次談?wù)凧ava面向?qū)ο筇卣鳌?/p>
-
一切皆是對象
除了8個(gè)基本數(shù)據(jù)類型,一切皆是對象。對象實(shí)現(xiàn)了數(shù)據(jù)和操作的結(jié)合,是Java的核心,具備唯一性,每個(gè)對象都有一個(gè)標(biāo)識來引用,如果失去這個(gè)引用,那么這個(gè)對象將會(huì)變成垃圾,然后會(huì)被虛擬機(jī)回收掉。Java中不允許直接訪問對象,而是通過一個(gè)引用(也有一種稱呼為句柄)來操作對象。就如同設(shè)計(jì)一臺電視機(jī),電視機(jī)上沒有任何按鈕,只能通過遙控來操作電視機(jī)。而這個(gè)遙控就是引用(句柄),電視機(jī)就是對象。
如 Person p = new Person();
image.png
p就是一個(gè)引用變量,其實(shí)就是C語言中的指針,只是Java友好的將這個(gè)指針封裝起來了,不需要繁瑣的去操作它。p中存儲(chǔ)的是Person的地址,當(dāng)訪問p引用變量的成員變量和方法,其實(shí)就是訪問Person的成員變量和方法。
- 類和對象
對象也稱為實(shí)例instance,對象的抽象化是類,類的具體化是對象。Java語言使用class來定義對象,通過成員變量來描述對象的數(shù)據(jù),通過方法來描述對象的行為特征。類之間的關(guān)系一般有兩種:- 一般->特殊關(guān)系(is a),Java中使用extends來表示這種特殊的關(guān)系,即繼承關(guān)系。
發(fā)生在繼承關(guān)系常見的一個(gè)概念是重寫(Overrride),重寫必須符合規(guī)則式:兩同兩小一大。即,方法名,形參列表相同;返回值類型要比父類的返回值類型更小或者相等;子類拋出的異常必須比父類的異常更小或者相等(不能一代不如一代);子類的訪問權(quán)限必須比父類的相等或者更大。
這里需要注意的是當(dāng)父類的方法是private修飾時(shí),子類是不能訪問的。 - 整體->部分關(guān)系(has a),組合關(guān)系,即Java中一個(gè)類里面保存了另一個(gè)類的引用來實(shí)現(xiàn)這種關(guān)系。
- 一般->特殊關(guān)系(is a),Java中使用extends來表示這種特殊的關(guān)系,即繼承關(guān)系。
修飾符
- private 私有的(類訪問權(quán)限)
- default 默認(rèn)(包訪問權(quán)限)
- protected 子類訪問權(quán)限
- public 公共訪問權(quán)限
this和super
面向?qū)ο箅x不開this和super,這里我們分析下這兩個(gè)關(guān)鍵字
- this
this關(guān)鍵字指向調(diào)用該方法的對象,一般會(huì)出現(xiàn)在構(gòu)造器和方法中。我們知道一種特殊的方法static修飾的,就是靜態(tài)方法,調(diào)用靜態(tài)方法可以使用類對象,所以this無法指向調(diào)用該方法的對象,所以靜態(tài)方法里面不能使用this,同樣,靜態(tài)方法中不能使用非靜態(tài)成員變量。 - super
super是用來子類調(diào)用父類的方法或者構(gòu)造方法的。和this一樣,super也不能應(yīng)用在靜態(tài)方法中。
子類調(diào)用父類構(gòu)造器過程是:- 子類構(gòu)造器執(zhí)行體的第一行使用super顯式調(diào)用父類構(gòu)造器,系統(tǒng)會(huì)根據(jù)super傳入的實(shí)例列表調(diào)用父類對應(yīng)的構(gòu)造器。
- 子類構(gòu)造器執(zhí)行體的第一行使用this顯式的調(diào)用本類的重載構(gòu)造器,執(zhí)行本類的另一個(gè)構(gòu)造器時(shí)即會(huì)調(diào)用父類構(gòu)造器。
- 子類構(gòu)造器既沒有super,也沒有this,系統(tǒng)將會(huì)執(zhí)行子類構(gòu)造器之前,隱式的調(diào)用父類的無參構(gòu)造器
final修飾符
final用來修飾類、變量、方法表示該類、變量、方法不可改變。
- final修飾變量
final修飾變量一旦獲得初始值后是不能改變的。如下圖,我們編譯器在編譯過程中就會(huì)報(bào)錯(cuò)The final local variable a may already have been assigned。
image.png
關(guān)于final修飾成員變量,必須顯式的初始化。
1.普通成員變量,必須在初始化塊(代碼塊)、聲明時(shí)或者構(gòu)造器中初始化。- 靜態(tài)成員變量,必須在靜態(tài)代碼塊、聲明時(shí)初始化。
其實(shí)final的不可改變也不是絕對的,這就是final修飾基本類型變量和引用類型變量的區(qū)別,修飾引用類型時(shí),只要保證引用類型的地址不變,而引用的這個(gè)對象完全可以改變。
- 靜態(tài)成員變量,必須在靜態(tài)代碼塊、聲明時(shí)初始化。
- final方法
final方法不能被重寫,如果父類不想讓子類繼承某個(gè)方法,可以定義為final類型。 - final類
final類不能有子類
聊聊Lambda表達(dá)式
Lambda表達(dá)式是Java8新增的一個(gè)重要功能,是大家期待已久的,它使得代碼更為的簡潔、直觀,接下來讓我們了解下Lambda表達(dá)式的功能。
- 組成部分
- 形參列表。允許省略參數(shù)類型,如果是一個(gè)參數(shù)甚至可以省略圓括號
- 箭頭。(->)必須是英文的劃線號和大于號組成
- 代碼塊。 如果代碼塊只有一條語句,可以省略花括號。如果只有一條返回語句,return關(guān)鍵字也可以省略。
比如說,我們可以創(chuàng)建一個(gè)線程類
Thread thread = new Thread((Runnable) ()->{
System.out.println(Thread.currentThread().getName()+"-run--");
});
這樣寫也是可以的
Thread thread = new Thread(()->System.out.println(Thread.currentThread().getName()+"-run--"));
- 方法引用和構(gòu)造器引用
@FunctionalInterface
interface Converter{
Integer converter(String from);
}
Converter converter = from ->Integer.valueOf(from);
上面代碼其實(shí)就是對接口Converter的一個(gè)實(shí)現(xiàn),然后把實(shí)現(xiàn)的地址賦給了引用變量converter。上面的代碼還可以簡寫成
Converter converter = Integer::valueOf;
調(diào)用converter.converter("5");也就是調(diào)用Integer.valueOf("5");
Lambda還有很多有意思的寫法,這就需要通過實(shí)踐中去探索了。
實(shí)戰(zhàn)
沒有實(shí)戰(zhàn)的概念就是耍流氓。
- 一個(gè)比較坑的問題
public class StaticThreadDemo implements Runnable{
public static Integer i = new Integer(0);
@Override
public void run() {
while(true){
synchronized (i) {
if(i<100){
i++;
System.out.println("i="+i);
}else{
break;
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new StaticThreadDemo());
Thread t2 = new Thread(new StaticThreadDemo());
t1.start();
t2.start();
}
}
問題輸出的結(jié)果是啥?按順序1-100?重復(fù)輸出1-100?無序的1-100?
運(yùn)行的結(jié)果是:無序的,有重復(fù),有確實(shí)的打印1-100。
就是說,這是個(gè)線程不安全的程序。那么為什么會(huì)導(dǎo)致這種情況呢?
分析:
synchronized 鎖對象的問題。我們知道,靜態(tài)變量和類信息(區(qū)分類對象)都是存放在我們的方法區(qū)中(因此靜態(tài)變量屬于類本身而不屬于實(shí)例),我們可以認(rèn)為是線程共享的,唯一的。那,我們應(yīng)該要理解的是引用i對應(yīng)的對象是否被偷換的問題,如果沒有變化,那么,i肯定是線程安全的。我們編譯下這段代碼。
public class StaticThreadDemo implements Runnable {
public static Integer i = new Integer(0);
public StaticThreadDemo() {
}
public void run() {
while(true) {
Integer var1 = i;
synchronized(i) {
if(i.intValue() >= 100) {
return;
}
Integer e = i;
i = Integer.valueOf(i.intValue() + 1);
System.out.println("i=" + i);
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new StaticThreadDemo());
Thread t2 = new Thread(new StaticThreadDemo());
t1.start();
t2.start();
}
}
我們發(fā)現(xiàn):Integer要獲取它的數(shù)據(jù)需要通過intValue() 方法,那么intValue()方法干了件什么事呢?查看Integer對象源碼
private final int value;
public int intValue() {
return value;
}
我們上面說過,對象的數(shù)據(jù)使用成員變量來描述,而這個(gè)成員變量是私有的,我們只能通過它的方法來獲取。
i++分解成了兩句
Integer e = i;
i = Integer.valueOf(i.intValue() + 1);
第一句我們比較好理解,就是用一個(gè)新的對象保存舊的數(shù)據(jù),而第二句才是重點(diǎn),我們先看下Interger的靜態(tài)方法valueOf
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
解釋一下這段代碼,就是當(dāng)i的字段在-128和127之間的話,從IntegerCache緩存里面獲取,如果在區(qū)間之外的話,重新new一個(gè)對象,當(dāng)然,緩存里面其實(shí)也是new Integer(i);所以說i的對象發(fā)生了改變了,因此,synchronized鎖不住對象了。
我們可以這樣理解這個(gè)流程,線程t1獲取鎖對象,進(jìn)入run方法,執(zhí)行i++后,鎖對象發(fā)生了改變,這個(gè)時(shí)候線程t1,t2一起爭取新的鎖對象,由于這一步和打印語句并行,所以存在線程安全問題。

這里提一下Integer內(nèi)部類IntegerCache緩存對象問題,在Java5加入了自動(dòng)裝箱和自動(dòng)拆箱后(實(shí)現(xiàn)原理就是valueOf方法),如果int值在-128和127之間,Java不會(huì)new一個(gè)對象,而是直接從緩存里面獲取了,這就有了面試題Integer a =127;Integer b = 127;Integer c =128;Integer d = 128;
System.out.println(a==b); System.out.println(c==d);
尾聲
通過本章節(jié),我們說到了面向?qū)ο蟮幕咎匦耘c面向過程的優(yōu)勢所在,然后闡述了Java面向?qū)ο蟮奶卣?,引出了引用?shù)據(jù)類型。后面我們說到了一些修飾符,如訪問權(quán)限修飾符,關(guān)鍵字等。還提到了Java8新增的Lambda表達(dá)式的應(yīng)用??傊?,Java面向?qū)ο蟛┐缶?,不是一篇文章就能說得清楚的,如果要深入學(xué)習(xí),我們還需要閱讀相關(guān)的書籍。在最后,我舉了一個(gè)多線程安全問題的案例,詳細(xì)分析了Integer對象在i++過程中的實(shí)際操作以及對象之間的變化,希望能幫到大家進(jìn)一步了解面向?qū)ο笏枷搿?/p>

