1. 程式人生 > >Java基礎系列1:Java面向物件

Java基礎系列1:Java面向物件

該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接著瞭解每個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,形成自己的知識框架。

 

概述:

Java是面向物件的程式設計語言,Java語言提供了定義類、成員變數、方法等最基本的功能。類可被認為是一種自定義的資料型別,可以使用類來定義變數,所有使用類定義的變數都是引用變數,它們將會引用到類的物件。類用於描述客觀世界裡某一類物件的共同特徵,而物件則是類的具體存在,Java程式使用類的構造器來建立該類的物件。

 

物件和類:

Java是面向物件的程式設計語言,類是面向物件的重要內容,可以把類當成一種自定義型別,可以使用類來定義變數,這種型別的變數統稱為引用變數。也就是說,所有類是引用型別。物件是由類創建出來的,可以說類時物件的抽象,物件是類的例項。

 

物件的概念:

Java 是面向物件的程式語言,物件就是面向物件程式設計的核心。所謂物件就是真實世界中的實體,物件與實體是一一對應的,也就是說現實世界中每一個實體都是一個物件,它是一種具體的概念。物件有以下特點:

  • 物件具有屬性和行為。
  • 物件具有變化的狀態。
  • 物件具有唯一性。
  • 物件都是某個類別的例項。
  • 一切皆為物件,真實世界中的所有事物都可以視為物件。

 

面向物件與面向過程:

1、面向過程:

面向過程是一種以事件為中心的程式設計思想,程式設計的時候把解決問題的步驟分析出來,然後用函式把這些步驟實現,在一步一步的具體步驟中再按順序呼叫函式。

 

 

 

我們以五子棋為例來解釋一下面向過程是如何解決問題的:

下過五子棋的同學都知道,首先要找兩個人,然後把棋譜擺放好,其中一方手持黑棋,另一方手持白旗,一般約定白棋先動,然後黑棋在動,這樣每人一步,直到某一方先湊成五子一條線便為贏。這是我們平常下五子棋的過程,那麼用面向過程該如何表示呢?

我們可以將下五子棋的過程抽象成如下步驟:

(1)開始遊戲(2)黑子先走(3)繪製畫面(4)判斷輸贏(5)輪到白子(6)繪製畫面(7)判斷輸贏(8)返回步驟(2) (9)輸出最後結果。

接著我們用面向過程來實現五子棋這一程式:

下五子棋{

    開始遊戲();

    黑子先走();

    繪製畫面();

    判斷輸贏();

    輪到白子();

    繪製畫面();

    判斷輸贏();

    返回到 黑子先走();

    輸出最後結果;

}

  

可見,面向過程始終關注的是怎麼一步一步地判斷棋局輸贏的,通過控制程式碼,從而實現函式的順序執行。

 

2、面向物件:

​ 一種基於面向過程的新程式設計思想,顧名思義就是該思想是站在物件的角度思考問題,我們把多個功能合理放到不同物件裡,強調的是具備某些功能的物件。

具備某種功能的實體,稱為物件。面向物件最小的程式單元是:類。面向物件更加符合常規的思維方式,穩定性好,可重用性強,易於開發大型軟體產品,有良好的可維護性。

Java程式設計思想一書中有一段對面向物件的總結非常清晰到位,可謂是面向物件的精華所在:

1、萬物皆物件

2、程式時物件的集合,它們通過傳送訊息來告知彼此所需要做的

3、每個物件都有自己的由其他物件所構成的儲存

4、每個物件都擁有其型別

5、某一特定型別的所有物件都可以接收同樣的訊息

 

3、兩者優缺點比較:

(1)、面向過程:

優點:

  流程化使得程式設計任務明確,在開發之前基本考慮了實現方式和最終結果,具體步驟清楚,便於節點分析。

  效率高,面向過程強調程式碼的短小精悍,善於結合資料結構來開發高效率的程式。

缺點:

  需要深入的思考,耗費精力,程式碼重用性低,擴充套件能力差,後期維護難度比較大。

 

(2)、面向物件:

優點:

  結構清晰,程式是模組化和結構化,更加符合人類的思維方式;

  易擴充套件,程式碼重用率高,可繼承,可覆蓋,可以設計出低耦合的系統;

  易維護,系統低耦合的特點有利於減少程式的後期維護工作量。

缺點:

  開銷大,當要修改物件內部時,物件的屬性不允許外部直接存取,所以要增加許多沒有其他意義、只負責讀或寫的行為。這會為程式設計工作增加負擔,增加執行開銷,並且使程式顯得臃腫。

  效能低,由於面向更高的邏輯抽象層,使得面向物件在實現的時候,不得不做出效能上面的犧牲,計算時間和空間儲存大小都開銷很大。

 

面向物件的三大特性:

概述:

1、繼承:

  繼承是面向物件的三大特徵之一,也是實現軟體複用的重要手段。Java的繼承具有單繼承的特點,每個子類只有一個直接父類。

2、封裝:

  封裝(Encapsulation)是面向物件的三大特徵之一,它指的是將物件的狀態資訊隱藏在物件內部,不允許外部程式直接訪問物件內部資訊,而是通過該類所提供的方法來實現對內部資訊的操作和訪問。

  封裝是面向物件程式語言對客觀世界的模擬,在客觀世界裡,物件的狀態資訊都被隱藏在物件內部,外界無法直接操作和修改。比如說一個人的年齡,年齡只會隨著時間的流逝而逐漸增長,不能隨意修改人的年齡。對一個類或物件實現良好的封裝,可以實現以下目的。

3、多型:

  Java引用變數有兩個型別:一個是編譯時型別,一個是執行時型別。編譯時型別由宣告該變數時使用的型別決定,執行時型別由實際賦給該變數的物件決定。如果編譯時型別和執行時型別不一致,就可能出現所謂的多型(Polymorphism)。

  多型的作用是消除型別之間的耦合關係。

 

詳解:

一、繼承:

1、繼承的概念:

程式來源於生活,也為生活所用。我們先從生活中的例子來看一下什麼是繼承:

現在有一個農場主,家有良田萬頃,每年收入很多,他有一個兒子,就是我們口中的富二代。有一天農場主不幸去世了,那麼他手下的農田和財產都是誰的了,毫無疑問,當然是他兒子的了(如果你好好努力,將來你兒子有很大機會是富二代哦)。那麼他兒子本來一無所有,現在頃刻間多了需要Money,農田,房子等等,也就是擁有了他父親的所有物資財富,這個我們就稱之為繼承。

Java的繼承通過extends關鍵字來實現,實現繼承的類被稱為子類,被繼承的類被稱為父類,有的也稱其為基類、超類。父類和子類的關係,是一種一般和特殊的關係。例如水果和蘋果的關係,蘋果繼承了水果,蘋果是水果的子類,則蘋果是一種特殊的水果。

class Fruit{
	public double weight;
	public void info() {
		System.out.println("我是一個水果,重"+weight+"g");
	}
}

public class Apple extends Fruit{
	public static void main(String[] args) {
		//建立Apple物件
		Apple apple=new Apple();
		//Apple物件本身並沒有weight成員變數
		//但是Apple的父類用於weight變數,所以Apple也可以方位
		apple.weight=88;
		apple.info();
	}

}

結果:我是一個水果,重88.0g

  

2、重寫父類的方法:

子類擴充套件了父類,子類是一個特殊的父類。大部分時候,子類總是以父類為基礎,額外增加新的成員變數和方法。但有一種情況例外:子類需要重寫父類的方法。例如鳥類都包含了飛翔方法,其中鴕鳥是一種特殊的鳥類,因此鴕鳥應該是鳥的子類,因此它也將從鳥類獲得飛翔方法,但這個飛翔方法明顯不適合鴕鳥,為此,鴕鳥需要重寫鳥類的方法。

//父類
class Bird{
	public void fly() {
		System.out.println("我在天空自由的飛翔");
	}
}

public class Ostrich extends Bird {
	//重寫Bird的fly方法
	public void fly() {
		System.out.println("我只能在地上奔跑");
	}
	
	public static void main(String[] args) {
		//建立Ostrich物件
		Ostrich ostrich=new Ostrich();
		ostrich.fly();
	}
}

結果:我只能在地上奔跑

  

重寫時需要注意:

1、返回值型別

2、方法名

3、引數型別及個數

都要與父類繼承的方法相同,才叫方法的重寫。

 

重寫與過載的區別:

重寫:相對繼承而言,子類中對父類已經存在的方法進行區別化的修改。

過載:在同一個類中處理不同資料的多個相同方法名的多型手段。過載方法名相同,引數列表不同。

 

 

3、繼承的初始化順序:

先思考一下下面程式碼的輸出結果:

class Animal{
	public Animal() {
		System.out.println("我是父類動物");
	}
}

class Humanity extends Animal{
	public Humanity() {
		System.out.println("我是父類人類");
	}
}

public class Student extends Humanity{
	
	public Student() {
		System.out.println("我是子類學生");
	}

	public static void main(String[] args) {
		Student student=new Student();
	}
}

  

不要看結果,自己先思考一下

 

輸出結果:

結果

 

 

是不是和你思考的結果一樣,不一樣的同學接著往下看:

Java中繼承初始化順序如下:

1、初始化父類再初始化子類

2、先執行初始化物件中屬性,再執行構造方法中的初始化。

基於上面兩點,我們就知道例項化一個子類,java程式的執行順序是:

父類物件屬性初始化---->父類物件構造方法---->子類物件屬性初始化—>子類物件構造方法

下面放上一張形象的圖:

 

 

 

4、final關鍵字:

final 關鍵字可用於修飾類、變數和方法,final關鍵字有點類似C#裡的sealed關鍵字,用於表示它修飾的類、方法和變數不可改變。

final修飾變數時,表示該變數一旦獲得了初始值就不可被改變,final既可以修飾成員變數(包括類變數和例項變數),也可以修飾區域性變數、形參。有的書上介紹說final修飾的變數不能被賦值,這種說法是錯誤的!嚴格的說法是,final修飾的變數不可被改變,一旦獲得了初始值,該final變數的值就不能被重新賦值。由於final變數獲得初始值之後不能被重新賦值,因此final修飾成員變數和修飾區域性變數時有一定的不同。

1、final修飾變數:

  ☆:final修飾成員變數:一旦有了初始值,就不能被重新賦值。final修飾的成員變數必須由程式顯示的指定初始值。final修飾類變數必須在靜態初始化塊中指定初始值或宣告該類變數時指定初始值,而且只能在兩個地方其中之一指定;final修飾例項變數必須在非靜態初始化塊、宣告該例項變數或構造器中指定初始值,而且只能在三個地方的其中之一指定。

  ☆:final修飾區域性變數:系統不會對區域性變數進行初始化,區域性變數必須由程式設計師顯式初始化。因此使用final修飾區域性變數時,既可以在定義時指定預設值,也可以不指定預設值。

  ☆:final修飾基本型別變數與引用型別變數區別:當使用final修飾基本型別變數時,不能對基本型別變數重新賦值,因此基本型別變數不能被改變。但對於引用型別變數而言,它儲存的僅僅是一個引用,final只保證這個引用型別變數所引用的地址不會改變,即一直引用同一個物件,但這個物件完全可以發生改變。

import java.util.Arrays;

public class FinalTest {
	
	public static void main(String[] args) {
		//final修飾陣列變數,arr只是一個引用變數
		final int[] arr= {3,90,33,12};
		System.out.println(Arrays.toString(arr));
		
		//對陣列進行排序,合法
		Arrays.sort(arr);
		System.out.println(Arrays.toString(arr));
		//對陣列元素進行賦值,合法
		arr[2]=109;
		System.out.println(Arrays.toString(arr));
		
		//下面語句對arr重新賦值,非法
		//arr=null;
		
	}

}

  

2、final修飾方法:

  final修飾的方法不可被重寫

 

3、final修飾類:

  final修飾的類不可以有之類

 

5、this關鍵字:

this是自身的一個物件,代表物件本身,可以理解為:指向物件本身的一個指標。

this的用法:

1、普通的直接引用

2、形參與成員名字重名,用this來區分

public class Student{
	
	String username;
	String password;
	
	public Student(String username,String password) {
		//this 代表當前物件 也就是下面的student
		this.username=username;
		this.password=password;
	}

	public static void main(String[] args) {
		Student student=new Student("jack","123");
	}
}

  

3、引用建構函式,這個放在super關鍵字中說

 

6、super關鍵字:

super可以理解為是指向自己超(父)類物件的一個指標,而這個超類指的是離自己最近的一個父類。

super的用法:

1、普通的直接引用:與this類似,super相當於是指向當前物件的父類

super.name:引用父類的變數

super.add():引用父類的方法

2、子類中的成員變數或方法與父類中的成員變數或方法同名:

class Humanity{
	public void eat() {
		System.out.println("動物吃肉");
	}
}

public class Student extends Humanity{
	
	public void eat() {
		//super呼叫父類中的同名方法
		super.eat();
		System.out.println("人吃飯");
	}

	public static void main(String[] args) {
		Student student=new Student();
		student.eat();
	}
}

  

結果:

動物吃肉
人吃飯

  

3、引用建構函式:

super(引數):呼叫父類中的某一個建構函式(應該為建構函式中的第一條語句)。 this(引數):呼叫本類中另一種形式的建構函式(應該為建構函式中的第一條語句)。
class Humanity{
	
	public Humanity() {
		System.out.println("父類無參構造");
	}
	
	public Humanity(String s) {
		System.out.println("父類有參構造======"+s);
	}
	
}

public class Student extends Humanity{
	
	public Student() {
		super();//呼叫父類的無參構造方法
		System.out.println("子類無參構造");
	}
	
	
	public Student(String s) {
		super(s);//呼叫父類的有參構造
		System.out.println("子類的有參構造======"+s);
	}
	
	public Student(String username,String password) {
		this(username);//呼叫本類的有參構造
		System.out.println("子類帶兩個引數的建構函式======"+username+"======"+password);
	}
	
	public static void main(String[] args) {
		Student student=new Student();
		Student student2=new Student("小明");
		Student student3=new Student("小明","123");
	}
	
}

  

輸出結果:

父類無參構造
子類無參構造
父類有參構造======小明
子類的有參構造======小明
父類有參構造======小明
子類的有參構造======小明
子類帶兩個引數的建構函式======小明======123

  

 

二、封裝:

1、封裝的概念與優點:

封裝(Encapsulation)是面向物件的三大特徵之一,它指的是將物件的狀態資訊隱藏在物件內部,不允許外部程式直接訪問物件內部資訊,而是通過該類所提供的方法來實現對內部資訊的操作和訪問。


封裝是面向物件程式語言對客觀世界的模擬,在客觀世界裡,物件的狀態資訊都被隱藏在物件內部,外界無法直接操作和修改。就如剛剛說的Person物件的age變數,只能隨著歲月的流逝,age才會增加,通常不能隨意修改Person物件的age。對一個類或物件實現良好的封裝,可以實現以下目的。

  1. 隱藏類的實現細節。
  2. 讓使用者只能通過事先預定的方法來訪問資料,從而可以在該方法里加入控制邏輯,限制對成員變數的不合理訪問。
  3. 可進行資料檢查,從而有利於保證物件資訊的完整性。
  4. 便於修改,提高程式碼的可維護性。

 

為了實現良好的封裝,需要從兩個方面考慮。

  1. 將物件的成員變數和實現細節隱藏起來,不允許外部直接訪問。
  2. 把方法暴露出來,讓方法來控制對這些成員變數進行安全的訪問和操作。

 

2、訪問修飾符:

Java提供了3個訪問修飾符:public、protected和private,另外還有一個預設的修飾符default,Java的訪問控制級別如下圖所示:

 

 

下面來詳細介紹一下四個訪問修飾符:

  • private(當前類訪問許可權):如果類裡的一個成員(包括成員變數、方法和構造器等)使用private訪問控制符來修飾,則這個成員只能在當前類的內部被訪問。很顯然,這個訪問控制符用於修飾成員變數最合適,使用它來修飾成員變數就可以把成員變數隱藏在該類的內部。
  • default(包訪問許可權):如果類裡的一個成員(包括成員變數、方法和構造器等)或者一個外部類不使用任何訪問控制符修飾,就稱它是包訪問許可權的,default 訪問控制的成員或外部類可以被相同包下的其他類訪問。關於包的介紹請看5.4.3節。
  • protected(子類訪問許可權):如果一個成員(包括成員變數、方法和構造器等)使用protected訪問控制符修飾,那麼這個成員既可以被同一個包中的其他類訪問,也可以被不同包中的子類訪問。在通常情況下,如果使用protected來修飾一個方法,通常是希望其子類來重寫這個方法。
  • public(公共訪問許可權):這是一個最寬鬆的訪問控制級別,如果一個成員(包括成員變數、方法和構造器等)或者一個外部類使用public訪問控制符修飾,那麼這個成員或外部類就可以被所有類訪問,不管訪問類和被訪問類是否處於同一個包中,是否具有父子繼承關係。

掌握了訪問修飾符後,我們就可以來使用封裝了。

public class Person {
	
	private String username;
	private Integer age;
	
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	
}

  

上述程式碼Person類中有兩個成員變數username和age,它們都是私有變數,外部不能訪問,但是提供了get和set方法,通過這兩個方法便可以修改和獲取Person類中的相關資料,這就是封裝

 

3、java中的包是什麼?

記得我上初中的時候,班級有兩個同名的同學,都叫王健,老師每次叫王健的時候他倆不知道叫的是誰,後來加上性別區分,一個叫男王健,一個叫女王健,這次區分開。

那麼我們在java中會不會遇到這種情況呢?當然會,比如就Person這個類而言,在一個大型專案中,多人協作開發,你寫了一個類叫Person,我也寫個類叫Person,那麼該如何區分這兩個類呢?總不能一個叫男Person,一個叫女Person吧,哈哈。這時候java就引入了包的機制,允許在類名前面加上一個字首來限制這個類,提供了類的多層名稱空間

 

注意:

1、package語句必須作為原始檔的第一條非註釋性語句,一個原始檔只能指定一個包,即只能包含一條package語句,該原始檔中可以定義多個類,則這些類將全部位於該包下。


2、如果沒有顯式指定package語句,則處於預設包下。在實際企業開發中,通常不會把類定義在預設包下,但本書中的大量示例程式為了簡單起見,都沒有顯式指定package語句。


3、同一個包下的類可以自由訪問

 

 

三、多型:

1、多型的概念:

多型是指允許不同類的物件對同一訊息做出響應。即同一訊息可以根據傳送物件的不同而採用多種不同的行為方式。(傳送訊息就是函式呼叫)

 

2、多型的作用:

消除型別之間的耦合關係。

 

3、多型產生的條件:

  1、要有繼承;

  2、要有重寫;

  3、父類引用指向子類物件。

 

4、多型的優點:

1、可替換性(substitutability)。多型對已存在程式碼具有可替換性。例如,多型對圓Circle類工作,對其他任何圓形幾何體,如圓環,也同樣工作。

2、可擴充性(extensibility)。多型對程式碼具有可擴充性。增加新的子類不影響已存在類的多型性、繼承性,以及其他特性的執行和操作。實際上新加子類更容易獲得多型功能。例如,在實現了圓錐、半圓錐以及半球體的多型基礎上,很容易增添球體類的多型性。

3、介面性(interface-ability)。多型是超類通過方法簽名,向子類提供了一個共同介面,由子類來完善或者覆蓋它而實現的。如圖8.3 所示。圖中超類Shape規定了兩個實現多型的介面方法,computeArea()以及computeVolume()。子類,如Circle和Sphere為了實現多型,完善或者覆蓋這兩個介面方法。

4、靈活性(flexibility)。它在應用中體現了靈活多樣的操作,提高了使用效率。

5、簡化性(simplicity)。多型簡化對應用軟體的程式碼編寫和修改過程,尤其在處理大量物件的運算和操作時,這個特點尤為突出和重要。

 Java中多型的實現方式:介面實現,繼承父類進行方法重寫,同一個類中進行方法過載。

 

5、經典的多型案例:

class Animal{
	public void eat() {
		
	}
}


class Cat extends Animal
{
	public void eat()
	{
		System.out.println("吃魚");
	}
	public void catchMouse()
	{
		System.out.println("抓老鼠");
	}
}
 
class Dog extends Animal
{
	public void eat()
	{
		System.out.println("吃骨頭");
	}
	public void kanJia()
	{
		System.out.println("看家");
	}
}

public class AnimalTest {

	 public static void main(String[] args) {
		Animal cat=new Cat();//向上轉型
		cat.eat();
		Animal dog=new Dog();
		dog.eat();
          //Cat c=(Cat)cat;//向上轉型 } }

  

Animal是父類,它有兩個之類分別是Dog和Cat,之類分別重寫了父類的eat方法。

輸出結果:

吃魚
吃骨頭

  

從輸出結果可以看出,同樣都是Animal,但是卻有不同的行為表現,這就是多型

 

6、向上轉型和向下轉型:

1、向上轉型:就以上述的父類Animal和一個子類Dog來說明,當父類的引用可以指向子類的物件時,就是向上型別轉換:Animal cat=new Cat();

2、向下轉型:向下型別轉換(強制型別轉換),是大型別轉換到小型別(有風險,可能出現數據溢位)。例如:Cat c=(Cat)cat

 

7、重寫和過載:

重寫:父類與子類之間的多型性,對父類的函式進行重新定義。如果在子類中定義某方法與其父類有相同的名稱和引數,我們說該方法被重寫 (Overriding)。在Java中,子類可繼承父類中的方法,而不需要重新編寫相同的方法。

過載:方法過載是讓類以統一的方式處理不同型別資料的一種手段。多個同名函式同時存在,具有不同的引數個數/型別。過載是一個類中多型性的一種表現。

 

過載發生在一個類當中,重寫發生在兩個類當中,但是這兩個類是父子類的關係。