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