# Java面向對象
[TOC]
## 導學
通過前面的學習,我們對Java程序的運行流程有了一定的認識,掌握了分支結構,循環結構等常用邏輯,了解了Java的基本數據類型和引用數據類型,還認識了Java的方法構建。
我們也能夠通過這些知識來解決一些簡單的問題,但是當遇到一些復雜問題的時候,這些技能是遠遠不夠的。這就像蓋房子,想蓋一間小房子,會砌磚抹泥就已經足夠了。但是想要蓋一幢摩天大樓,就一定要懂得建筑工程方面的知識了。
在Java開發中,面向對象程序思想就是這樣一種技能。相較于早年面向過程的程序開發,面向對象開發在程序的穩定性,可擴展性和可重用性方面都有著無可比擬的優勢。
在本單元的學習中,我們將要學習面向對象編程的三大特征,封裝,繼承和多態,以及編寫具有面向對象思想的Java程序。
## 初識面向對象
### 類和對象
* 什么是對象
對象不僅在編程領域,而且在現實生活中都是一個非常重要的概念。我們需要理解一個概念,“萬物皆對象”。顯示存在的客觀事物都是對象。比如長城,電腦,一件衣服,一只狗,一只貓都是對象。只要是現實生活中存在的都是對象。
* 什么是面向對象
從字面上理解,面向對象就是與對象面對面,關注對象。從計算機的角度,就是關注現實存在的事物的各方面信息,從對象的角度出發根據事物的特征進行程序的設計。
雖然這些概念比較玄幻,但是只要根據現實生活的中事物的分析方式,就可以輕松的搞定對象了。
* 什么是類
這里我們使用一個例子來描述:
比我去逛寵物商店,想要買一只貓。我會告訴店員我想要一只短毛的,小一點的可愛的貓。于是店員給我推薦了兩只貓


所以這樣的一個虛擬的描述,就是我們的類,而花花與凡凡就是根據類的特征而選出來的具體的實物(對象)。
所以,針對于類,我們可以給出這樣的描述:
>[info]類就是模子,用于確定對象將會擁有的特征(屬性)和行為(方法)
**對象是類的實例化表現。**
由于在計算機世界中所有的信息都是數據,所以我們可以認為:
1. 類是對象的類型
2. 對象是特定類型的數據
~~~
//類作為數據類型 對象因類成就數據
Cat huahua
~~~
* 類和對象的關系
所以,針對于類與對象的關系,我們結合上述課程可以總結出如下觀點:
* 對象是類的實例化表現
* 類是對象的類型
* 對象是特定類型的數據
之前我們也提到對象擁有屬性和方法兩個特征,那么什么是屬性和方法呢。屬性代表著對象具有的各種靜態特征,即對象有什么;方法代表著對象具有的各種動態行為,即對象能做什么。
在上節內容中,我們舉了兩只貓的例子,我們可以發現這兩只貓都有著名字,性別,年齡和毛色等這些特征。只不過每只貓的具體指示是不同的,那么這些名字,性別,年齡和毛色等共有特征,就是貓的屬性,而類似貓的跑,跳,睡,吃等動作,就是貓能干什么,意味著貓這個對象的方法。
最后總結一下:

### 創建類
本節內容,我們試著通過Java程序來描述一下,我們舉過的兩只貓的例子。
#### 包的管理
首先,針對于本節課的開始,我們需要理解一下包的概念。比如我們在操作系統中會通過文件夾來實現文件的管理,在Java中,我們也可以通過包實現類的管理。
為了便于類文件的管理,Java 中引入了包的概念 package,類的唯一性是要帶包名的。
包名也存在其對應的命名規范
1. 英文字母小寫
2. 域名的倒序
比如:
~~~
第一層是企業的域名的反寫
例如:com.dodoke
第二層是企業項目的名稱
例如:com.dodoke.j96、com.dodoke.crm
第三層是企業項目模塊的名稱
例如:com.dodoke.j96.oop、com.dodoke.crm.base
~~~
我們也可以在類中自主的定義包
~~~
// 在類文件的第一行
package com.duduke.j96.oop;
~~~
>[danger]一個Java源文件中,只能有一個package語句,而且必須放置在Java源文件的第一行。
>[info]在不同的包中,是可以定義相同類名的類的。因為一個類的唯一性是包含包名的,比如`com.ntduduke.j96.oop.Demo1`叫做類的全名。
在實際的企業開發中,是不允許出現沒有包的類。
#### 包的使用
使用 import 關鍵字將本類要使用的其他包中的類進行引入。
>[info] 但是,import 不是必須的,我們可以使用類全名的方式進行類的使用。一般不建議,太麻煩。
使用`ALT+/`可以單個引入,也可以使用`CTRL+SHIFT+O`全部引入。
如果要引入某個包下面的所有類,可以使用通配符`*`,例如,引入`com.ntduduke.j96.oop.*`,但是要注意通配符只能出現在最后。
對于 java.lang 包中的類,系統會自動的默認引入,不需要使用 import。
#### 創建對象的類型
~~~
public class Cat{
//成員屬性(屬性指有什么)、昵稱、年齡、體重;
String name;//昵稱
int month;//年齡
//方法(方法指能做什么):跑步、吃東西
//成員方法
//跑步的方法
public void run(){
System.out.println("我會跑步");
}
//吃東西的方法
public void eat(){
System.out.println("我會吃魚");
}
}
~~~
### 實例化對象
對象是由類創建的,我們通過一堆對象總結出了類,那么類就有了對象的共同屬性與方法。由此,我們同樣可以得到,由類這個模板來創建對象,這種方式我們稱之為實例化。
~~~
public class CatTest{
public static void main(String[] args) {
Cat one= new Cat();
one.eat();
one.run();
System.out.println(one.month);//有默認值
}
}
~~~
我們發現對于對象的`month`屬性,它是存在默認值的。
那么再來對比一下,我們之前在方法中給大家介紹的局部變量。
局部變量通常是指我們在方法,流程控制結構等內容中定義的變量。
>[warning] 變量的生命周期在 {} 定義的范圍內
~~~
public void demo(int a) {
int b = 5;
if(a > 5) {
int c = a - b;//可以調用a和b
System.out.println(c);
} else {
//System.out.println(c);不能調用,c的生命周期只在上一個大括號內
int n;
//System.out.println(n);顯示錯誤,未初始化
}
}
~~~
而針對于類中的成員屬性,如果是引用數據類型的,沒有定義默認值,那么成員變量的值為`null`,如果是基本數據類型,沒有定義默認值,那么成員變量的值是有意義的,比如說int類型的值就是0,boolean類型的值就是false。
最后,針對于成員變量,我們也可以**通過賦值等于號,在程序中為屬性賦值**。
### 單一職責原則
在上述的程序中,我們創建了兩個類,在`CatTest`中利用`main`方法進行程序的測試。這和我們之前學習過的內容不同,之前都是在一個類中進行代碼的構建。而這個時候,我們使用的是兩個類進行程序的構建。
本章節,我們就來簡單的介紹一下單一職責原則(單一功能原則)。
該原則是面向對象程序設計中的一項重要原則,該原則要求我們一個類有且只有一個引起功能變化的原因。簡單的說就是一個類最好只有一個功能,只干一件事。如果在一個類中承載的功能越多,那么它的**交融和耦合性**就越高,從而**被復用**的可能性就越低。
同時,因為一個類中存在多個職責,當其中一個職責發生改變就有可能會引起同類中其他職責的變化,進而影響整個程序的運行。在程序設計中,盡量把不同的職責,放在不同的類中(也就是說把不同的可能引發變化的原因封裝到不同的類里面,這樣當一方發生變化時,對其他參與者的影響會少很多,并提升復用性)
### new關鍵字
在之前的學習中,我們通過new關鍵字完成了對象的實例化過程,實際上也就是對象的創建過程。
~~~
對象實例化語法:
類名 對象名 = new 類名();
~~~
實例化對象的過程可以分為兩部分:
- 聲明對象 Cat one
- 實例化對象 new Cat();
**聲明對象**:是在內存的"棧"空間里開辟了一塊空間,取名叫one,此時里面為空(null),并且對它的屬性和方法的調用是不允許的。所以因為這樣我們并不能像真正的對象那樣使用它
**實例化對象**:是在內存的堆空間里開辟了一塊空間,完成了對象相關信息的初始化操作
聲明對象和實例化對象是在內存的兩個不同的空間去完成的,接著通過賦值符號"="把兩個空間關聯起來關聯,將堆空間中的內存地址存放到了棧空間中,在棧當中存儲的實際上是堆當中地址的引用。

`new`關鍵字的作用,實際上就是去開辟新的內存空間。
~~~
Cat one= new Cat();
Cat two= new Cat();
~~~
我們針對于`two`對象,采取同樣的賦值。如果修改`two`對象的信息則不會對`one`對象造成任何的影響。

另一種實例化方式:
~~~
Cat one= new Cat();
Cat two= one;//將one所代表的內存地址復制給了two
~~~
此時,如果對one`對象`進行修改,則會影響到`two`對象

## 構造方法介紹
構造方法也稱之為構造函數,構造器,是面向對象編程中的一個重要概念。
我們經常會使用構造方法來完成對象初始化的相關設置。構造方法在調用的時候必須配合new關鍵字,是不能被單獨調用的。
構造方法語法:

注意:**構造方法與類同名且沒有返回值。構造方法只能在對象實例化的時候被調用**
構造器本身是一個比較特殊的方法,方法名就是類名,沒有返回值(和void是有區別的),構造器是類創建對象的唯一途徑。
>[danger]構造器的最大用處就是創建對象
### 無參構造方法
之前我們也學習過方法,方法必須先定義好才能使用。但是,在我們的類中并沒有創建構造方法,但是我們依然可以使用構造方法去創建對象。
這是因為,**當沒有指定構造方法時,系統會自動添加無參構造方法**。**也就是說在一個類中至少會存在一個構造方法**。便于我們的程序能夠正常的執行,對象能夠正常的進行實例化操作。
~~~
public Cat() {
System.out.println("我是無參構造方法");
}
Cat one = new Cat();//其實也就是調用了無參構造方法
~~~
**一個類中可以有多個構造方法,當有指定構造方法、無論是有參、無參的構造方法,都不會自動添加無參的構造方法。**
### 有參構造方法
通常我們會通過構造方法來完成對象的實例化操作。**通過構造器為成員變量定義初始化值,這也是構造器的最最最重要的用途之一**
~~~
public Cat(String name, int month, double weight, String species) {
name = name;
month = month;
weight = weight;
species = species;
}
~~~
實際上,上述構造方法中的四條語句不會起作用。其實此處的代碼邏輯發生了錯誤,遵循了一種**就近原則** ——賦值過程中先優先的去找同一個作用范圍內的成員進行賦值操作。只有找不到的情況下才會擴大作用范圍,去類里面找。
針對于這樣的問題,我們可以有兩種解決方案。
1. 修改參數名稱
2. 使用this.屬性名=參數名
### 構造器小結
結合之前學習的內容,我們再來分析一下一個對象的創建過程:
1. 在棧內存中,會存儲對象名, 在沒有執行構造器創建對象并賦值是,此時對象名對應的值應為null
2. 通過new關鍵字調用類的構造器在堆內存中分配了一塊對象區域;
3. 通過賦值運算符= ,將堆內存中的對象地址賦給棧內存中的變量名;
4. 例如再次給對象的屬性賦值: 通過棧內存中的地址定位到對象在堆內存中的地址,找到相應的成員變量,進行一個賦值操作
>[info]引用: 引用還可以稱之為【地址】,【指針】 。特指的是引用數據類型。因為只用類類型才會在堆內存中分配對象空間,并將地址(指針)在棧內存中用于對象變量名的引用。
### this關鍵字
~~~
public Cat(String name, int month, double weight, String species) {
this.name = name;
this.month = month;
this.weight = weight;
this.species = species;
}
~~~
Java中使用this關鍵字,指向調用該方法的對象。根據this所在的位置,大致分為兩種情況
* 出現在構造器中:引用該構造器正在初始化的對象
* 出現在普通方法中: 正在調用該方法的對象
this用在類定義中,獲取當前對象的屬性,或者調用當前對象的方法
>[warning] 在類定義中,可以省略this關鍵字去調用屬性或者方法,但是在類被編譯的時候,編譯器還是會加上this關鍵字。所以我們強烈建議在類定義的時候如果要調用該類中的普通成員變量或者方法,還是要把this給加上去
>[danger] 用static修飾的方法是不能使用this關鍵字的
在之前的方法學習中,我們也給大家提到了在普通方法中調用另一個方法是不需要通過對象的,但是我們也建議調用的使用加上this關鍵字。
### 構造方法調用
首先,如果我們創建一個與構造器同名的方法
~~~
public Cat(String name, int month, double weight, String species) {
this.name = name;
this.month = month;
this.weight = weight;
this.species = species;
}
public Cat() {
System.out.println("我是無參構造器");
}
public void Cat() {
System.out.println("我不是構造方法,只是一個與構造方法同名的普通方法");
}
~~~
這樣的方法不會出現語法上的錯誤,但是強烈不推薦大家這樣使用
第二點,通過this()調用重載的構造器:
構造方法在類外進行調用時,只能配合new關鍵字,不能直接通過對象名.來調用。
類中的普通方法之間可以互相調用。如
~~~
public void run(){
Cat(); //出錯
eat();
System.out.println("aaa");
}
~~~
構造方法在類內,普通方法不能調用構造方法。
構造方法的調用,只能在構造方法之間來完成。如
~~~
public Cat(String name ,...){
this();
}
~~~
構造方法內可以使用this()來調用構造方法(有參、無參均可,只要是申明過的),且必須放在方法體內第一行。**而且不允許出現兩條this()語句。**
## 課程總結

現階段,我們對于類的創建有著如上的了解。同時,需要補充的是成員屬性的默認值問題

## 練習
一、單選
1. 有關Java中的類和對象,以下說法錯誤的是
~~~
A. 同一個類的所有對象都擁有相同的特征和行為
B. 類和對象一樣,只是說法不同
C. 對象是具有屬性和行為的實體
D. 類規定了對象擁有的特征和行為
~~~
2. 在java中,以下程序的運行結果是

~~~
A. 輸出:null
B. 正常運行,但不會輸出任何內容
C. 編譯出錯,不能運行
D. 能運行,但運行時會出現異常
~~~
3. 下面代碼運行的正確結果是

~~~
A. 編譯錯誤,無法正常運行
B. 編譯正確,但運行時產生錯誤
C. hello
D. world
~~~
4. 哪個空間用于存儲使用new關鍵字所創建的對象
~~~
A. 堆
B. 棧
C. 方法區
D. 實例區
~~~
5. 分析下面的Java代碼,編譯運行結果是

~~~
A. 運行結果為:學號:1? 姓名:張三
B. 運行結果為:學號:null 姓名:張三
C. 程序出現編譯錯誤
D. 程序出現運行時異常
~~~
6. 下面的哪幾項是合法的構造方法重載(多選)

~~~
A. public Display(String s){}
B. public int display(int n1,int n2){}
C. Display (int …a){}
D. public display(Strnig s,int a){}
~~~
7. 運行結果為()

~~~
A. mainboard:華碩,cpu:Intel
B. mainboard:s1,cpu:s2
C. mainboard:Intel,cpu:華碩
D. 華碩,Intel
~~~
8. 在Java中,以下程序編譯運行后的輸出結果為( )

~~~
A. 6
B. 3 4
C. 8
D. 7
~~~
9. 在Java中,下列關于this的說法錯誤的選項是(多選)
~~~
A. 在構造方法中如果使用this調用其他構造方法,只能是第一條語句
B. 不能在構造方法中調用同一個類的其他構造方法
C. 在構造方法在中如果使用this調用其他構造方法,語句可以放在任意位置
D. 可以使用“this.方法名()”或“this.屬性名”來引用當前對象的成員
~~~
二、編程
1. 創建Person類和測試類
屬性:名字(name),年齡(age),年級( grade)
方法:
* 無參無返回值的student方法,描述為:我是一名學生!
* 帶參數(性別sex)的方法,描述為:我是一個\*\*孩!(其中,\*\*為傳入參數)
* 無參無返回值的mySelf方法,介紹自己的姓名、年齡、年級(參數參考效果圖)
~~~
public class Test {
public static void main(String[] args) {
//使用new關鍵字實例化對象
//傳入name、age、grade的參數值
//分別調用student、sex、mySelf方法
}
}
~~~
2. 編寫自定義猴子類

效果圖:

~~~
public class Monkey {
//屬性:姓名(name)、特征(feature)
//無參的構造方法(默認初始化name和feature的屬性值,屬性值參考效果圖)
//帶參的構造方法(接收外部傳入的參數,分別向 name 和 feature 賦值)
}
~~~