## 面向對象的概念
面向對象是一種符合人類思維習慣的編程思想。現實生活中存在各種形態不同的事物,這些事物之間存在著各種各樣的聯系。在程序中使用對象來映射現實中的事物,使用對象的關系來描述事物之間的聯系,這種思想就是面向對象。
#### 封裝性
封裝是面向對象的核心思想,將對象的屬性和行為封裝起來,不需要讓外界知道具體實現細節,這就是封裝思想。
例如,用戶使用電腦,只需要使用手指敲鍵盤就可以了,無須知道電腦內部是如何工作的,即使用戶可能碰巧知道電腦的工作原理,但在使用時,并不完全依賴電腦工作原理這些細節。
#### 繼承性
繼承性主要描述的是類與類之間的關系,通過繼承,可以在無須重新編寫原有類的情況下,對原有類的功能進行擴展。
例如,有一個汽車的類,該類中描述了汽車的普通特性和功能,而轎車的類中不僅應該包含汽車的特性和功能,還應該增加轎車特有的功能,這時,可以讓轎車類繼承汽車類,在轎車類中單獨添加轎車特性的方法就可以了。繼承不僅增強了代碼復用性,提高了開發效率,而且為程序的修改補充提供了便利。
#### 多態性
多態性指的是在程序中允許出現重名現象,它指在一個類中定義的屬性和方法被其他類繼承后,它們可以具有不同的數據類型或表現出不同的行為,這使得同一個屬性和方法在不同的類中具有不同的語義。
例如,當聽到“Cut”這個單詞時,理發師的行為是剪發,演員的行為是停止表演,不同的對象,所表現的行為是不一樣的。
面向對象的思想光靠上面的介紹是無法真正理解的,只有通過大量的實踐去學習和理解,才能將面向對象真正領悟。
## 類與對象
面向對象的編程思想力圖在程序中對事物的描述與該事物在現實中的形態保持一致。為了做到這一點,面向對象的思想中提出兩個概念,即類和對象。其中,類是對某一類事物的抽象描述,而對象用于表示現實中該類事物的個體。接下來通過一個圖例來描述類與對象的關系。

在圖中,可以將玩具模型看作一個類,將一個個玩具看作對象,從玩具模型和玩具之間的關系便可以看出類與對象之間的關系。類用于描述多個對象的共同特征,它是對象的模板。對象用于描述現實中的個體,它是類的實例。從圖可以明顯看出對象是根據類創建的,并且通過一個類可以創建多個對象。
#### 類的定義
在面向對象的思想中最核心的就是對象,為了在程序中創建對象,首先需要定義一個類。類是對象的抽象,它用于描述一組對象的共同特征和行為。類中可以定義成員變量和成員方法,其中成員變量用于描述對象的特征,也被稱作屬性,成員方法用于描述對象的行為,可簡稱為方法。接下來通過一個案例來學習如何定義一個類。
```java
public class Person {
int age; // 定義int 類型的變量age
// 定義speak() 方法
void speak() {
System.out.println("大家好,我今年" + age + "歲!");
}
}
```
例中定義了一個類。其中,Person是類名,age是成員變量,speak()是成員方法。在成員方法speak()中可以直接訪問成員變量age。
#### 對象的創建與使用
應用程序想要完成具體的功能,僅有類是遠遠不夠的,還需要根據類創建實例對象。在Java程序中可以使用new關鍵字來創建對象,具體格式如下:
```
類名 對象名稱 = new 類名();
```
例如,創建Person類的實例對象代碼如下:
```
Person p = new Person();
```
上面的代碼中,“newPerson()”用于創建Person類的一個實例對象,“Personp”則是聲明了一個Person類型的變量p。中間的等號用于將Person對象在內存中的地址賦值給變量p,這樣變量p便持有了對象的引用。接下來的章節為了便于描述,通常會將變量p引用的對象簡稱為p對象。在內存中變量p和對象之間的引用關系如圖

在創建Person對象后,可以通過對象的引用來訪問對象所有的成員,具體格式如下:
```
對象引用.對象成員
```
接下來通過一個案例來學習如何訪問對象的成員
```java
public class Example01 {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.age = 18;
p1.speak();
p2.speak();
}
}
```
運行結果:
```
大家好,我今年18歲!
大家好,我今年0歲!
```
p1、p2分別引用了Person類的兩個實例對象。從圖3-3所示的運行結果可以看出,p1和p2對象在調用speak()方法時,打印的age值不相同。這是因為p1對象和p2對象是兩個完全獨立的個體,它們分別擁有各自的age屬性,對p1對象的age屬性進行賦值并不會影響到p2對象age屬性的值。程序運行期間p1、p2引用的對象在內存中的狀態如圖所示。

在例中,通過“p1.age=18”將p1對象的age屬性賦值為18,但并沒有對p2對象的age屬性進行賦值,按理說p2對象的age屬性應該是沒有值的。但所顯示的運行結果可以看出p2對象的age屬性也是有值的,其值為0。這是因為在實例化對象時,Java虛擬機會自動為成員變量進行初始化,針對不同類型的成員變量,Java虛擬機會賦予不同的初始值。

當對象被實例化后,在程序中可以通過對象的引用變量來訪問該對象的成員。需要注意的是,當沒有任何變量引用這個對象時,它將成為垃圾對象,不能再被使用。接下來通過兩段程序代碼來分析對象是如何成為垃圾的。
第一段程序代碼:
```
{
Person p1=new Person();
……
}
```
上面的代碼中使用變量p1引用了一個Person類型的對象,當這段代碼運行完畢時,變量p1就會超出其作用域而被銷毀,這時Person類型的對象就沒有被任何變量引用,變成垃圾。
第二段程序代碼:
```java
class Person {
void say() {
System.out.println("你好Java");
}
}
public class Example01 {
public static void main(String[] args) {
Person p = new Person();
p.say();
p = null;
p.say();
}
}
```
執行結果:
```
你好Java
Exception in thread "main" java.lang.NullPointerException
at Example01.main(Example01.java:12)
```
在例中,創建了一個Person類的實例對象,并兩次調用了該對象的say()方法。第一次調用say()方法時可以正常打印,但在第10行代碼中將變量p2的值置為null,當再次調用say()方法時拋出了空指針異常。在Java中,null是一種特殊的常量,當一個變量的值為null時,則表示該變量不指向任何一個對象。當把變量p2置為null時,被p2所引用的Person對象就會失去引用,成為垃圾對象,其過程如圖所示。

#### 類的設計
在Java中,對象是通過類創建出來的。因此,在程序設計時,最重要的就是類的設計。接下來通過一個具體的案例來學習如何設計一個類。
假設要在程序中描述一個學校所有學生的信息,可以先設計一個學生類(Student),在這個類中定義兩個屬性name、age分別表示學生的姓名和年齡,定義一個方法introduce()表示學生做自我介紹。根據上面的描述設計出來的Student類如例所示。
```java
public class Student {
String name;
int age;
public void introduce() {
// 方法中打印屬性name 和age 的值
System.out.println("大家好,我叫" + name + ",我今年" + age + "歲!");
}
}
```
在例中的Student類中,定義了兩個屬性name和age。其中的name屬性為String類型,在Java中使用String類的實例對象表示一個字符串,例如:
```java
String name = "李芳";
```
關于字符串的相關知識在本書的第6章將會進行詳細地講解,在此處可簡單地將字符串理解為一連串的字符。
#### 類的封裝
接下來針對上一個例中設計的Student類創建對象,并訪問該對象的成員,如例所示
```java
public class Example {
public static void main(String[] args) {
Student student = new Student();
student.name = "easilyj";
student.age = -20;
student.introduce();
}
}
```
運行結果:
```
大家好,我叫easilyj,我今年-20歲!
```
在第6行代碼中,將年齡賦值為一個負數-20,這在程序中不會有任何問題,但在現實生活中明顯是不合理的。為了解決年齡不能為負數的問題,在設計一個類時,應該對成員變量的訪問做出一些限定,不允許外界隨意訪問。這就需要實現類的封裝。
所謂類的封裝是指在定義一個類時,將類中的屬性私有化,即使用private關鍵字來修飾,私有屬性只能在它所在類中被訪問。為了能讓外界訪問私有屬性,需要提供一些使用public修飾的公有方法,其中包括用于獲取屬性值的getXxx()方法和設置屬性值的setXxx()方法。接下來通過一個案例來實現類的封裝。
```java
class Student {
int age;
String name;
public String getName() {
return name;
}
public void setName(String name) {
name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 下面是對傳入的參數進行檢查
if (age <= 0) {
System.out.println("年齡不合法……");
} else {
age = age; // 對屬性賦值
}
}
public void introduce() {
// 方法中打印屬性name 和age 的值
System.out.println("大家好,我叫" + name + ",我今年" + age + "歲!");
}
}
public class Example {
public static void main(String[] args) {
Student student = new Student();
student.setName("easilyj");
student.setAge(-20);
student.introduce();
}
}
```
運行結果:
```
年齡不合法……
大家好,我叫easilyj,我今年0歲!
```
在例中的Student類中,使用private關鍵字將屬性name和age聲明為私有,對外界提供了幾個公有的方法,其中getName()方法用于獲取name屬性的值,setName()方法用于設置name屬性的值,同理,getAge()和setAge()方法用于獲取和設置age屬性的值。在main()方法中創建Student對象,并調用setAge()方法傳入一個負數-30,在setAge()方法中對參數stuAge的值進行檢查,由于當前傳入的值小于0,因此會打印“年齡不合法”的信息,age屬性沒有被賦值,仍為默認初始值0。
## 構造方法
#### 構造方法的定義
在一個類中定義的方法如果同時滿足以下三個條件,該方法稱為構造方法,具體如下:
- 方法名與類名相同。
- 在方法名的前面沒有返回值類型的聲明。
- 在方法中不能使用return語句返回一個值。
接下來通過一個案例來演示如何在類中定義構造方法。
```java
class Person {
public Person() {
System.out.println("無參構造方法 ...");
}
}
public class Example {
public static void main(String[] args) {
Person person = new Person();
}
}
```
運行結果:
```
無參構造方法 ...
```
Person類中定義了一個無參的構造方法Person()。從運行結果可以看出,Person類中無參的構造方法被調用了。這是因為在實例化Person對象時會自動調用類的構造方法,“new Person()”語句的作用除了會實例化Person對象,還會調用構造方法Person()。
在一個類中除了定義無參的構造方法,還可以定義有參的構造方法,通過有參的構造方法就可以實現對屬性的賦值。
```java
class Person {
int age;
public Person(int a) {
age = a;
}
public void speak() {
System.out.println("easilyj的歲數是:" + age);
}
}
public class Example {
public static void main(String[] args) {
Person person = new Person(20);
person.speak();
}
}
```
運行結果:
```
easilyj的歲數是:20
```
Person類中定義了有參的構造方法Person(inta)。代碼中的“new Person(20)”會在實例化對象的同時調用有參的構造方法,并傳入了參數20。在構造方法Person(int a)中將20賦值給對象的age屬性。通過運行結果可以看出,Person對象在調用speak()方法時,其age屬性已經被賦值為20。
#### 構造方法的重載
與普通方法一樣,構造方法也可以重載,在一個類中可以定義多個構造方法,只要每個構造方法的參數類型或參數個數不同即可。在創建對象時,可以通過調用不同的構造方法為不同的屬性賦值。接下來通過一個案例來學習構造方法的重載。
```java
class Person {
String name;
int age;
public Person(int a) {
age = a;
}
public Person(String name, int age) {
name = name;
age = age;
}
public Person(String name) {
name = name;
}
public void speak() {
System.out.println("我的名字是:" + name + ", 我的年齡是:" + age);
}
}
public class Example {
public static void main(String[] args) {
Person person = new Person("小海綿", 20);
Person person1 = new Person("easilyj");
person.speak();
person1.speak();
}
}
```
運行結果:
```
我的名字是:小海綿, 我的年齡是:20
我的名字是:easilyj, 我的年齡是:0
```
Person類中定義了兩個構造方法,它們構成了重載。在創建p1對象和p2對象時,根據傳入參數的不同,分別調用不同的構造方法。從程序的運行結果可以看出,兩個構造方法對屬性賦值的情況是不一樣的,其中一個參數的構造方法只針對name屬性進行賦值,這時age屬性的值為默認值0。
**細節**
在Java中的每個類都至少有一個構造方法,如果在一個類中沒有定義構造方法,系統會自動為這個類創建一個默認的構造方法,這個默認的構造方法沒有參數,在其方法體中沒有任何代碼,即什么也不做。
## this關鍵字
在上例中使用變量表示年齡時,構造方法中使用的是name,成員變量使用的是name,這樣的程序可讀性很差。這時需要將一個類中表示年齡的變量進行統一的命名,例如都聲明為name。但是這樣做又會導致成員變量和局部變量的名稱沖突,在方法中將無法訪問成員變量name。為了解決這個問題,Java中提供了一個關鍵字this,用于在方法中訪問對象的其他成員。接下來將為大家詳細地講解this關鍵字在程序中的三種常見用法,具體如下:
- 通過this關鍵字可以明確地去訪問一個類的成員變量,解決與局部變量名稱沖突問題。具體示例代碼如下:
```java
class Person {
int age;
public Person(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
}
```
在上面的代碼中,構造方法的參數被定義為age,它是一個局部變量,在類中還定義了一個成員變量,名稱也是age。在構造方法中如果使用“age”,則是訪問局部變量,但如果使用“this.age”則是訪問成員變量。
- 通過this關鍵字調用成員方法,具體示例代碼如下:
```java
class Person {
public void openMouth() {
?
}
public void speak() {
this.openMouth();
}
}
```
在上面的speak()方法中,使用this關鍵字調用openMouth()方法。注意,此處的this關鍵字可以省略不寫,也就是說上面的代碼寫成“this.openMouth()”和“openMouth()”,效果是完全一樣的。
- 構造方法是在實例化對象時被Java虛擬機自動調用的,在程序中不能像調用其他方法一樣去調用構造方法,但可以在一個構造方法中使用“this([參數1,參數2…])”的形式來調用其他的構造方法。
```java
class Person {
public Person() {
System.out.println("無參的構造方法被調用了……");
}
public Person(String name) {
this(); // 調用無參的構造方法
System.out.println("有參的構造方法被調用了……");
}
}
public class Test {
public static void main(String[] args) {
Person p = new Person("itcast"); // 實例化Person 對象
}
}
```
運行結果:
```
無參的構造方法被調用了……
有參的構造方法被調用了……
```
代碼在實例化Person對象時,調用了有參的構造方法,在該方法中通過this()調用了無參的構造方法,因此運行結果中顯示兩個構造方法都被調用了。在使用this調用類的構造方法時,應注意以下幾點。
1. 只能在構造方法中使用this調用其他的構造方法,不能在成員方法中使用。
2. 在構造方法中,使用this調用構造方法的語句必須位于第一行,且只能出現一次。
3. 不能在一個類的兩個構造方法中使用this互相調用,下面的寫法編譯會報錯。
## 垃圾回收
在Java中,當一個對象成為垃圾后仍會占用內存空間,時間一長,就會導致內存空間的不足。針對這種情況,Java中引入了垃圾回收機制。程序員不需要過多關心垃圾對象回收的問題,Java虛擬機會自動回收垃圾對象所占用的內存空間。
一個對象在成為垃圾后會暫時地保留在內存中,當這樣的垃圾堆積到一定程度時,Java虛擬機就會啟動垃圾回收器將這些垃圾對象從內存中釋放,從而使程序獲得更多可用的內存空間。除了等待Java虛擬機進行自動垃圾回收,也可以通過調用System.gc()方法來通知Java虛擬機立即進行垃圾回收。當一個對象在內存中被釋放時,它的finalize()方法會被自動調用,因此可以在類中通過定義finalize()方法來觀察對象何時被釋放。接下來通過一個案例來演示Java虛擬機進行垃圾回收的過程。
```java
class Person {
public void finalize() {
System.out.println("對象被作為垃圾回收 ...");
}
}
public class Test {
public static void main(String[] args) {
Person p = new Person();
Person p2 = new Person();
// 下面將變量置為null,讓對象成為垃圾
p = null;
p2 = null;
// 調用方法進行垃圾回收
System.gc();
for (int i = 0; i < 1000000; i++) {
// 為了延長程
}
}
}
```
運行結果:
```
對象被作為垃圾回收 ...
對象被作為垃圾回收 ...
```
Person類中定義了一個finalize()方法,該方法的返回值必須為void,并且要使用public來修飾。在main()方法中創建了兩個對象p1和p2,然后將兩個變量置為null,這意味著新創建的兩個對象成為垃圾了,緊接著通過“System.gc()”語句通知虛擬機進行垃圾回收。從運行結果可以看出,虛擬機針對兩個垃圾對象進行了回收,并在回收之前分別調用兩個對象的finalize()方法。
需要注意的是,Java虛擬機的垃圾回收操作是在后臺完成的,程序結束后,垃圾回收的操作也將終止。因此,在程序的最后使用了一個for循環,延長程序運行的時間,從而能夠更好地看到垃圾對象被回收的過程。
## static關鍵字
**特點:**
- static是一個修飾符,用于修飾成員。(成員變量,成員方法)static修飾的成員變量稱之為靜態變量或類變量。
- static修飾的成員被所有的對象共享。
- static優先于對象存在,因為static的成員隨著類的加載就已經存在。
- static修飾的成員多了一種調用方式,可以直接被類名所調用,(類名.靜態成員)。
- static修飾的數據是共享數據,對象中的存儲的是特有的數據。
#### 靜態變量
可以直接通過**類名.靜態變量名**調用。每次創建對象時,靜態變量都是相同的,且一個地方修改了靜態變量值,所有的對象的該變量值都會被修改,**不提倡這種寫法**。
```java
public class Book {
String name = "Tom";
static String price = "100";
public static void main(String[] args) {
System.out.println(Book.price);
}
}
```
**成員變量和靜態變量的區別:**
1.生命周期的不同:
成員變量隨著對象的創建而存在隨著對象的回收而釋放。
靜態變量隨著類的加載而存在隨著類的消失而消失。
2.調用方式不同:
成員變量只能被對象調用。
靜態變量可以被對象調用,也可以用類名調用。(推薦用類名調用)
3.別名不同:
成員變量也稱為實例變量。
靜態變量稱為類變量。
4.數據存儲位置不同:
成員變量數據存儲在堆內存的對象中,所以也叫對象的特有數據。
靜態變量數據存儲在方法區(共享數據區)的靜態區,所以也叫對象的共享數據
#### 靜態方法
不需要創建對象,直接使用**類名.方法名**就可調用。
```java
public class Book {
String name = "Tom";
static String price = "100";
public static String hello() {
return "hello";
}
public static void main(String[] args) {
System.out.println(Book.hello());
}
}
```
**什么時候使用static來修飾**
1.靜態變量:
當分析對象中所具備的成員變量的值都是相同的。這時這個成員就可以被靜態修飾。
只要是數據在對象中都是不同的,就是對象的特有數據,必須存儲在對象中,是非靜態的。
如果是相同的數據,對象不需要做修改,只需要使用即可,不需要存儲在對象中,是靜態的。
2.靜態函數:
函數是否用靜態修飾,就參考一點,就是該函數功能是否有訪問到對象中特有的數據。
簡單來說,從源代碼看,該功能是否需要訪問非靜態的成員變量,如果需要,該功能就是非靜態的。如果不需要,就可以將該功能定義成靜態的。當然,也可以定義成非靜態,但是非靜態需要被對象調用,而僅創建對象是沒有意義的。
#### 靜態代碼塊
隨著類的調用或創建實例而執行,而且只執行一次。用于給類進行初始化。
```java
public class Book {
private static final String name;
private static final String age;
static {
name = "Tom";
age = "20";
}
public static void main(String[] args) {
System.out.println(name + " : " + age);
}
}
```
執行結果:
```java
Tom : 20
```
**靜態使用時需要注意的事項:**
- 靜態方法只能訪問靜態成員。(非靜態既可以訪問靜態,又可以訪問非靜態)
- 靜態方法中不可以使用this或者super關鍵字。
- 主函數是靜態的。