該系列博文會告訴你如何從入門到進階,一步步地學習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、引用構造函數:
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。對一個類或對象實現良好的封裝,可以實現以下目的。
- 隱藏類的實現細節。
- 讓使用者只能通過事先預定的方法來訪問數據,從而可以在該方法里加入控制邏輯,限制對成員變量的不合理訪問。
- 可進行數據檢查,從而有利於保證對象信息的完整性。
- 便於修改,提高代碼的可維護性。
為了實現良好的封裝,需要從兩個方面考慮。
- 將對象的成員變量和實現細節隱藏起來,不允許外部直接訪問。
- 把方法暴露出來,讓方法來控制對這些成員變量進行安全的訪問和操作。
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中,子類可繼承父類中的方法,而不需要重新編寫相同的方法。
重載:方法重載是讓類以統一的方式處理不同類型數據的一種手段。多個同名函數同時存在,具有不同的參數個數/類型。重載是一個類中多態性的一種表現。
重載發生在一個類當中,重寫發生在兩個類當中,但是這兩個類是父子類的關系。