面向?qū)ο笈c面向過(guò)程編程的區(qū)別
一、面向?qū)ο笈c面向過(guò)程編程的區(qū)別
我們以一個(gè)實(shí)際例子來(lái)說(shuō)明這兩者的區(qū)別 , 例如:寫(xiě)一個(gè)計(jì)算器的軟件。
面向過(guò)程程序員思考方式:
[1]定義變量保存用戶的輸入的數(shù)據(jù)
[2]實(shí)現(xiàn)一個(gè)加法函數(shù),完成數(shù)據(jù)的加法
[3]實(shí)現(xiàn)一個(gè)減法函數(shù),完成數(shù)據(jù)的減法
[4]實(shí)現(xiàn)一個(gè)乘法函數(shù),完成數(shù)據(jù)的乘法
.....
面向?qū)ο蟪绦騿T思考方式:
[1]計(jì)算器是一個(gè)對(duì)象
[2]這個(gè)對(duì)象應(yīng)該有保存數(shù)據(jù)的變量
[2]這個(gè)對(duì)象應(yīng)該有完成數(shù)據(jù)計(jì)算的方法(函數(shù))
....
可以看的出來(lái),好像這兩個(gè)哥們思考方式幾乎沒(méi)啥區(qū)別,只不過(guò)面向?qū)ο蟮某绦騿T從整體出發(fā),把實(shí)現(xiàn)計(jì)算器的保存數(shù)據(jù)的變量和計(jì)算數(shù)據(jù)的方法封裝在一個(gè)對(duì)象里面。
我們都是搞C語(yǔ)言的,C語(yǔ)言是一種典型的面向過(guò)程語(yǔ)言。在這里想問(wèn)一個(gè)問(wèn)題:如何用C語(yǔ)言描述面向?qū)ο蟪绦騿T的思考的計(jì)算器呢?
嗯,你一定會(huì)想到用C語(yǔ)言中的結(jié)構(gòu)體來(lái)實(shí)現(xiàn),封裝的結(jié)構(gòu)體如下:
typedef struct
{
int data1;
int data2;
int (*calc_add)(int,int);
int (*calc_sub)(int,int);
int (*calc_mul)(int,int);
...
}calc_t;
定義一個(gè)計(jì)算器類(lèi)型的變量
calc_t calc;
可以看的出來(lái),編程語(yǔ)言本身并沒(méi)有面向?qū)ο蠛兔嫦蜻^(guò)程之分,只是程序員的思考方式不一樣罷了。
好了,我們接著思考:如果我要開(kāi)發(fā)一個(gè)手機(jī)軟件,這個(gè)手機(jī)軟件軟件中要包含打電話功能、計(jì)算器功能、播放音樂(lè)功能,這些又該如何實(shí)現(xiàn)呢?
面向過(guò)程序員,思考方式:
[1]定義變量保存用戶的輸入的數(shù)據(jù)
[2]實(shí)現(xiàn)一個(gè)加法函數(shù),完成數(shù)據(jù)的加法
[3]實(shí)現(xiàn)一個(gè)減法函數(shù),完成數(shù)據(jù)的減法
[4]實(shí)現(xiàn)一個(gè)乘法函數(shù),完成數(shù)據(jù)的乘法
[5]實(shí)現(xiàn)一個(gè)打電話功能函數(shù)
[6]實(shí)現(xiàn)一個(gè)播放音樂(lè)功能函數(shù)
.....
面向?qū)ο蟪绦騿T,思考方式:
[1]計(jì)算器是一個(gè)對(duì)象,包含一些數(shù)據(jù)和方法
[2]打電話是一個(gè)對(duì)象,包含一些數(shù)據(jù)和方法
[3]播放音樂(lè)是一個(gè)對(duì)象,包含一些數(shù)據(jù)和方法
...
面向?qū)ο蟪绦騿T這時(shí)想到,自己以前寫(xiě)過(guò)一個(gè)計(jì)算器對(duì)象,寫(xiě)過(guò)一個(gè)打電話對(duì)象,寫(xiě)過(guò)一個(gè)播放器對(duì)象,他們之間是獨(dú)立的,于是高富帥的干活方式出現(xiàn)了。啥也不用干,"ctrl +c" 和 "ctrl + v"把活干完了,然后去喝茶了。
面向過(guò)程程序員也不傻,看到面向?qū)ο蟪绦騿T的干活方式,立馬自己也"ctrl + c" 和 "ctrl + v"把自己以前編寫(xiě)的代碼從n個(gè)文件的n行代碼中尋找,找到之后發(fā)現(xiàn)自己的視力從+2.5下降到-2.5。不管咋地,咱就是這么任性,已經(jīng)把代碼拷貝過(guò)來(lái),接下來(lái)就編譯完,交給老大就可以去喝茶了。編譯器瘋了,變量名沒(méi)有定義、變量名沖突,函數(shù)名沖突....,最后的結(jié)果是n行代碼編譯器無(wú)情的報(bào)了"n+1"行錯(cuò)誤。
故事看到這,我們可以看出,面向?qū)ο缶幊痰奶攸c(diǎn):
[1]萬(wàn)事萬(wàn)物都看成對(duì)象,對(duì)象包含數(shù)據(jù)和操作數(shù)據(jù)的方式,是一個(gè)獨(dú)立的個(gè)體
[2]編寫(xiě)程序之前,先通過(guò)封裝的方法設(shè)計(jì)出對(duì)象應(yīng)該包含的內(nèi)容
[3]整個(gè)軟件系統(tǒng)由對(duì)象構(gòu)成,就像這個(gè)人類(lèi)世界一樣,有n個(gè)人構(gòu)成,每個(gè)人扮演者不同的角色
[4]代碼的復(fù)用性好,更便于維護(hù)
好了,就說(shuō)這么多了,想要真正理解,必須自己在實(shí)際的項(xiàng)目中慢慢體會(huì),才能真正理解面向?qū)ο蠛兔嫦蜻^(guò)程的不同。
二 Java面向?qū)ο笾庋b
我們知道,在面向?qū)ο蟮木幊趟枷胫?,一個(gè)軟件系統(tǒng)由n個(gè)對(duì)象構(gòu)成。而對(duì)象需要先設(shè)計(jì),就像前面我們用C語(yǔ)言的結(jié)構(gòu)體來(lái)描述計(jì)算器一樣。
typedef struct
{
int data1;
int data2;
int (*calc_add)(int,int);
int (*calc_sub)(int,int);
int (*calc_mul)(int,int);
...
}calc_t;
在Java 中,用可以用類(lèi)來(lái)描述一個(gè)對(duì)象的特點(diǎn):
class Calc{
int data1;
int data2;
int calc_add(){
return data1 + data2;
}
int calc_sub(){
return data1 - data2;
}
....
};
在C語(yǔ)言中的結(jié)構(gòu)體內(nèi)部是沒(méi)法直接編寫(xiě)函數(shù)的,在Java的中是可以直接編寫(xiě)函數(shù)的,可以看出Java的類(lèi)封裝性更強(qiáng)。
問(wèn)題1 : Java的類(lèi)和C語(yǔ)言的結(jié)構(gòu)體是一樣的嗎?
回答 : 相似,都是程序員設(shè)計(jì)出來(lái)的類(lèi)型。
問(wèn)題2: Java的類(lèi)和對(duì)象有什么聯(lián)系呢?
回答: 相當(dāng)于C語(yǔ)言的結(jié)構(gòu)體類(lèi)型和結(jié)構(gòu)體變量。
好了,接下來(lái)我們給出Java中標(biāo)準(zhǔn)的類(lèi)定義方法:
接下來(lái)我們就來(lái)實(shí)戰(zhàn)一下吧,設(shè)計(jì)一個(gè)描述人的類(lèi):
編譯出現(xiàn)的錯(cuò)誤如下:
修改完后,接著編譯,出現(xiàn)的錯(cuò)誤如下:
問(wèn)題:在Java中如何給引用類(lèi)型變量初始化呢?
回答: 讓引用類(lèi)型變量保存一個(gè)可用內(nèi)存空間首地址就可以了。
類(lèi)名 引用類(lèi)型變量名;
引用類(lèi)型變量名 = new 類(lèi)名() 或 引用類(lèi)型變量名 = new 類(lèi)名(參數(shù)列表);
例如:
people = new Person();
好了,知道錯(cuò)誤后,我們接著修改代碼如下:
編譯沒(méi)有錯(cuò)誤,輸出如下結(jié)果:
嗯,還是有哥們寫(xiě)錯(cuò),它的寫(xiě)法如下:
嗯,我們應(yīng)該定義一個(gè)構(gòu)造器,這樣我們?cè)趧?chuàng)建對(duì)象后就可以自動(dòng)給對(duì)象的成員變量進(jìn)行初始化了,修改代碼如下:
問(wèn)題:如果創(chuàng)建一個(gè)對(duì)象時(shí),我不想給構(gòu)造器傳遞參數(shù),我該怎么做呢?
回答:在類(lèi)中在定義一個(gè)無(wú)參數(shù)的構(gòu)造器。
修改代碼如下:
三 Java中的訪問(wèn)修飾符public和private
還是通過(guò)列子說(shuō)明吧!
嗯,明白了,private 修飾的成員變量和成員方法只能在類(lèi)中訪問(wèn),在別的類(lèi)中是不能通過(guò)對(duì)象來(lái)訪問(wèn)的。public 修飾的成員變量和成員方法除了在類(lèi)中可以直接訪問(wèn),在其他類(lèi)中也可以通過(guò)對(duì)象來(lái)訪問(wèn)。
現(xiàn)在問(wèn)題來(lái)了,具體什么時(shí)候用private修飾,什么時(shí)候用public修飾成員變量和成員方法呢?
大牛們這樣回答你,類(lèi)的成員變量都應(yīng)該設(shè)為private,類(lèi)的成員方法如果只是類(lèi)內(nèi)部使用則設(shè)為private,如果給外部使用則設(shè)為public。
試著思考這樣一個(gè)問(wèn)題:如果我們把成員變量設(shè)為public,這樣在任何一個(gè)類(lèi)中都可以隨意訪問(wèn)。有一天編寫(xiě)類(lèi)的人將成員變量名更改了,此時(shí)在別的類(lèi)中使用過(guò)此類(lèi)的成員變量的代碼都需要修改。如果成員變量設(shè)為private,此時(shí)在別的類(lèi)中是無(wú)法直接訪問(wèn)的,所以你做了修改是不會(huì)影響到別人的。
注意:我們封裝的目的就是想隱藏一些細(xì)節(jié),向外界提供統(tǒng)一的接口。
現(xiàn)在來(lái)了一個(gè)新的問(wèn)題,把成員變量設(shè)為私有的,別的類(lèi)中如何訪問(wèn)呢?嗯,聰明的你應(yīng)該可以想到,通過(guò)類(lèi)的公有方法,在公有方法中訪問(wèn)類(lèi)的私有成員。于是乎代碼修改成如下:
三 Java 中的this
1、表示對(duì)當(dāng)前對(duì)象的引用!
2、表示用類(lèi)的成員變量,而非函數(shù)參數(shù),注意在函數(shù)參數(shù)和成員變量同名是進(jìn)行區(qū)分!其實(shí)這是第一種用法的特例,比較常用,所以那出來(lái)強(qiáng)調(diào)一下!
3、用于在構(gòu)造方法中引用滿足指定參數(shù)類(lèi)型的構(gòu)造器(其實(shí)也就是構(gòu)造方法)。但是這里必須非常注意:只能引用一個(gè)構(gòu)造方法且必須位于開(kāi)始。
注意:
this不能用在static方法中!所以甚至有人給static方法的定義就是:沒(méi)有this的方法!雖然夸張,但是卻充分說(shuō)明this不能在static方法中使用!
四 Java 中的static
關(guān)于"static"這個(gè)關(guān)鍵字大家并不陌生,在C語(yǔ)言中static的用途如下:
(1)static 修飾一個(gè)局部變量,表示這個(gè)局部變量的值具有繼承性,在函數(shù)調(diào)用結(jié)束的時(shí)候,static修飾的局部變量空間
(2)static 修飾一個(gè)全局變量或函數(shù)時(shí),表示限制全局變量和函數(shù)的作用范圍,此時(shí)全局變量或函數(shù)只能在本文件中使用。
在Java中,"static"關(guān)鍵字和C語(yǔ)言的用法差別很大,下面我們就一起來(lái)看看在Java中,什么時(shí)候應(yīng)該使用"static"關(guān)鍵字呢?
1.用static 修飾類(lèi)的成員變量
大牛名言:如果你想讓同一個(gè)類(lèi)的所有對(duì)象共享同一個(gè)變量,那么這個(gè)類(lèi)的成員變量應(yīng)該使用"static"關(guān)鍵字修飾。
解讀大牛名言如下
(1)static修飾的成員變量屬于類(lèi)的變量,所有對(duì)象共享。也就是說(shuō)當(dāng)一個(gè)類(lèi)加載到內(nèi)存中之后,static修飾的成員變量空間就已經(jīng)分配好了。接下來(lái)通過(guò)這個(gè)類(lèi)創(chuàng)建的所有對(duì)象都共享static修飾的成員變量。
(2)static修飾的成員變量,不需要?jiǎng)?chuàng)建對(duì)象來(lái)訪問(wèn),由于它屬于類(lèi),所以可以通過(guò) "類(lèi)名.成員變量名"來(lái)訪問(wèn)。
2.用static修飾類(lèi)的成員方法(靜態(tài)成員方法)
大牛名言:如果你不想通過(guò)創(chuàng)建對(duì)象來(lái)訪問(wèn)成員函數(shù),那么這個(gè)類(lèi)的成員函數(shù)應(yīng)該用"static"關(guān)鍵字來(lái)修飾。
解讀大牛名言如下
類(lèi)中非靜態(tài)成員方法在訪問(wèn)的時(shí)候,必須先創(chuàng)建對(duì)象,然后通過(guò)對(duì)象來(lái)訪問(wèn)成員方法。創(chuàng)建對(duì)象必定會(huì)有內(nèi)存開(kāi)銷(xiāo),有些時(shí)候我們?cè)贘ava中編寫(xiě)的一些普通的算法函數(shù),它不需要訪問(wèn)類(lèi)的非靜態(tài)的成員變量和函數(shù),只是自己內(nèi)部完成一些計(jì)算,這樣的函數(shù)很明顯和對(duì)象沒(méi)有聯(lián)系,此時(shí)應(yīng)該將這個(gè)函數(shù)用"staic"關(guān)鍵字修飾,讓它變成靜態(tài)成員方法。這樣訪問(wèn)它的時(shí)候,就不需要?jiǎng)?chuàng)建對(duì)象了,只需要通過(guò)"類(lèi)名.成員函數(shù)名"來(lái)訪問(wèn)就可以了。
其實(shí)這樣的例子有很多,例如:main函數(shù) , System.arraycopy,Arrays.copyOf等。
思考:如何在靜態(tài)成員方法中,訪問(wèn)非靜態(tài)成員變量和函數(shù)?
四 Java 中的static靜態(tài)塊和非靜態(tài)塊
(1)static{}(即static塊),會(huì)在類(lèi)被加載的時(shí)候執(zhí)行且僅會(huì)被執(zhí)行一次,一般用來(lái)初始化靜態(tài)變量和調(diào)用靜態(tài)方法。
(2){}(非靜態(tài)塊)每個(gè)對(duì)象生成時(shí)都會(huì)被執(zhí)行一次,它可以初始化類(lèi)的成員變量。非靜態(tài)初始化塊代碼會(huì)在構(gòu)造函數(shù)調(diào)用前先執(zhí)行。
五 Java 中的包機(jī)制
所謂包,就是把不同特征的類(lèi)隔離起來(lái),即使這些彼此隔離的包中包含同名的類(lèi)也無(wú)所謂。在一個(gè)大的軟件系統(tǒng)中,有很多種類(lèi)的對(duì)象,要想描述這些不同種類(lèi)的對(duì)象就必須設(shè)計(jì)不同的類(lèi)來(lái)完成。一般都是將這些類(lèi)進(jìn)行分類(lèi),由不同的人去完成不同的類(lèi)。這樣可能會(huì)產(chǎn)生A定義的類(lèi)名和B定義的類(lèi)重名,此時(shí)如果將A和B定義的類(lèi)拷貝到同一個(gè)目錄下,必定會(huì)產(chǎn)生覆蓋,于是乎Java中就提出了包的機(jī)制來(lái)解決這種命名沖突的問(wèn)題。
簡(jiǎn)單總結(jié)包的用途:
1) 將功能相近的類(lèi)放在同一個(gè)包中,可以方便查找與使用。
2) 由于在不同包中可以存在同名類(lèi),所以使用包在一定程度上可以避免命名沖突。
3) 在Java中, 包也限定了訪問(wèn)權(quán)限,擁有包訪問(wèn)權(quán)限的類(lèi)才能訪問(wèn)某個(gè)包中的類(lèi)
1. Java中的包的創(chuàng)建
package 包名;
注意:包名的命名方式 (全部小寫(xiě),以公司或項(xiàng)目組織的順序倒寫(xiě),中間以.分隔,如: com.farsight.www.cyg)
我估計(jì)大家看到這里還是糊涂,我們還是以例子來(lái)說(shuō)明:
以包的方式,編譯我們的java代碼:
javac -d . TestPackage.java
最終生成的效果如下:
2. Java中的使用指定包下面的類(lèi)
每次都這樣寫(xiě),遲早都要 崩潰的,有沒(méi)有更簡(jiǎn)單的方法呢?
使用import語(yǔ)句引入包中的類(lèi)
由于采用使用長(zhǎng)名引用包中的類(lèi)的方法比較繁瑣,所以Java提供了import語(yǔ)句來(lái)引入包中的類(lèi)。
import語(yǔ)句的基本語(yǔ)法格式如下: import 包名1 [ .包名2 ...].類(lèi)名 | *;
當(dāng)存在多個(gè)包名時(shí),各個(gè)包名之間使用“.”分隔,同時(shí)包名與類(lèi)名之間也使用“.”分隔。
*:表示包中所有的類(lèi)。
例如,引入com.wgh包中的Circ類(lèi)的代碼如下:
import com.wgh.Circ;
如果 com.wgh包中包含多個(gè)類(lèi),也可以使用以下語(yǔ)句引入該包下的全部類(lèi)。
import com.wgh.*;
嗯,還有另一種寫(xiě)法,如下所示:
六 Java 中常用的環(huán)境變量
CLASSPATH
1. 在java的編譯環(huán)境中使用
它的作用與import、package關(guān)鍵字有關(guān)。當(dāng)你寫(xiě)下import java.util.*時(shí),編譯器面對(duì)import關(guān)鍵字時(shí),就知道你要引入java.util這個(gè)package中的類(lèi);但是編譯器如何知道你把這個(gè)package放在哪里了呢?所以你首先得告訴編譯器這個(gè)package的所在位置;如何告訴它呢?就是設(shè)置CLASSPATH啦 !
當(dāng)你自己開(kāi)發(fā)一個(gè)package時(shí),然后想要用這個(gè)package中的類(lèi);自然,你也得把這個(gè)package所在的目錄設(shè)置到CLASSPATH中去!CLASSPATH的設(shè)定,對(duì)JAVA的初學(xué)者而言是一件棘手的事。所以Sun讓JAVA2的JDK更聰明一些。你會(huì)發(fā)現(xiàn),在你安裝之后,即使完全沒(méi)有設(shè)定CLASSPATH,你仍然能夠編譯基本的JAVA程序,并且加以執(zhí)行。
例如:我把CLASSPATH值設(shè)為D:,此時(shí)我編譯我的java程序,效果如下:
有人肯定在想,以前我們使用"System、Arrays,String"等類(lèi)時(shí),既沒(méi)有設(shè)置CLASSPATH,也沒(méi)有用"import"進(jìn)行導(dǎo)包,為什么可以編譯通過(guò)呢?
從上面的編譯過(guò)程可以看出,javac在編譯java源文件的時(shí)候,有兩個(gè)搜索路徑:
(1)CLASSPATH指定的路徑
(2)默認(rèn)的搜索路徑 "安裝目錄Javajdkjrelib"下的xxx.jar包。例如:jrelibrt.jar文件中就有常用的java類(lèi),如:System,String等。
問(wèn)題:jar包是什么呢?
回答:jar包是將我們的包進(jìn)行壓縮后的文件
2. 在java的運(yùn)行環(huán)境中使用
當(dāng)我們通過(guò)java 運(yùn)行一個(gè)類(lèi)時(shí),默認(rèn)是從當(dāng)前目錄下尋找這個(gè)類(lèi),然后將這個(gè)類(lèi)加載到內(nèi)存中,如果這個(gè)類(lèi)在運(yùn)行的時(shí)候,需要"import"導(dǎo)入的類(lèi),在從當(dāng)前目錄下開(kāi)始尋找"import"指定的路徑下類(lèi),然后將他們加載到內(nèi)存。
注意:如果設(shè)置了CLASSPATH環(huán)境變量,則java虛擬機(jī)從CLASSPATH指定的路徑下搜索需要的類(lèi)加入到內(nèi)存。
好了,我們已經(jīng)知道了CLASSPATH環(huán)境變量用途,到目前為止,我們沒(méi)有配置CLASSPATH環(huán)境變量,我們?nèi)匀豢梢跃幾g和運(yùn)行java程序。有人可能會(huì)認(rèn)為,學(xué)這個(gè)沒(méi)啥用,不配置也可以用呀。其實(shí)不然,如果我們不使用java環(huán)境自帶的類(lèi)進(jìn)行開(kāi)發(fā),而是用別人提供的類(lèi)進(jìn)行開(kāi)發(fā),這個(gè)時(shí)候CLASSPATH就派上用場(chǎng)了,你必須將你需要類(lèi)的路徑設(shè)置在CLASSPATH環(huán)境變量中。