你之因此能優於別人,正是由於你堅持了別人所不能堅持的。
本文相關代碼在個人Github,歡迎Star~
https://github.com/zhangzhibo1014/DaBoJava
在此以前,咱們已經把Java基礎的基礎語法總結了一下,今天咱們來學習一下面向對象的相關知識,今天的內容理論性偏多,但願你們能耐心的看完,相信會收穫不少。都說 Java
是面向對象程序設計的語言,那麼究竟什麼是面向對象呢?若是你沒有面向對象程序設計的應用背景,那麼和我一塊兒來認真的閱讀本文吧!java
面向對象程序設計(簡稱 OOP
),是當今主流的設計範型。面向對象程序是由對象組成的,每一個對象包含對用戶公開的特定功能部分和隱藏的實現部分。在 OOP
中,沒必要關心對象的具體實現,只要能知足用戶的需求便可。git
類( class)是構造對象的模板或藍圖。由類構造(construct) 對象的過程稱爲建立類的實例 (instance )。github
封裝( encapsulation , 有時稱爲數據隱藏) 是與對象有關的一個重要概念。從形式上看,封裝不過是將數據和行爲組合在一個包中, 並對對象的使用者隱藏了數據的實現方式。對象中的數據稱爲實例域( instance field ), 操縱數據的過程稱爲方法( method ) 。對於每一個特定的類實例(對象)都有一組特定的實例域值。這些值的集合就是這個對象的當前狀態( state )。不管什麼時候,只要向對象發送一個消息,它的狀態就有可能發生改變。sql
OOP
的另外一個原則會讓用戶自定義 Java
類變得垂手可得,這就是:能夠經過擴展一個類來創建另一個新的類。在擴展一個已有的類時, 這個擴展後的新類具備所擴展的類的所有屬性和方法。在新類中,只需提供適用於這個新類的新方法和數據域就能夠了。編程
要想使用 OOP
,必定要清楚對象的三個主要特性segmentfault
在面向對象的學習中,咱們首先要學會設計類,而後在往類中添加所需的方法。微信
識別類的簡單規則是在分析問題的過程當中尋找名詞,而方法對應着動詞。框架
例如:在訂單處理系統中,有這樣一些名詞:ide
這些名詞極可能成爲類 Item、 Order 等。函數
接下來, 查看動詞:商品被添加到訂單中, 訂單被髮送或取消, 訂單貨款被支付。對於每個動詞如:「 添加」、「 發送」、「 取消」 以及「 支付」, 都要標識出主要負責完成相應動做的對象。例如,當一個新的商品添加到訂單中時, 那個訂單對象就是被指定的對象, 由於它知道如何存儲商品以及如何對商品進行排序。也就是說,add 應該是 Order 類的一個方法, 而 Item 對象是一個參數。
面向對象將之前的過程當中的執行者,變成了指揮者
過程和對象在咱們程序中是如何體現的呢? 過程其實就是函數,對象是將函數等一些內容進行了封裝
要想建立一個完整的程序, 應該將若干類組合在一塊兒, 其中只有一個類有 main
方法。
在Java中,最簡答的類的形式以下:
class Employee { //成員變量 field1; field2; .... //構造器 constructor1; constructor2; ... //成員方法 method1; method2; .... }
在類中定義其實都稱之爲成員。成員有兩種:
成員變量和局部變量的區別?
局部變量定義在方法中,參數上,語句中
局部變量只在本身所屬的大括號內有效,大括號結束,局部變量失去做用域
局部變量存在於棧內存中,隨着所屬區域的運行而存在,結束而釋放
構造器 用於給對象進行初始化,是給與之對應的對象進行初始化。
new
操做符的執行被調用。0
個、 1
個或多個參數public Student { private String name; //聲明變量name,存儲學生的姓名 private int age; //聲明變量age,存儲學生的年齡 //無參構造器 public Student() { } //帶有一個參數的構造器 public Student(String aName) { name = aName; } //帶有兩個參數的構造器 public Student(String aName, int aAge){ name = aName; age = aAge; } } new Student();// 使用此方法new一個對象實例會調用Student()構造器,name被初始化爲null,age被初始化爲0 new Student("Tom");//使用此方法new一個對象實例會調用Student(String aName)構造器,age被初始化爲0
定義 指隱藏對象的屬性和實現細節,僅提供對外公共訪問方式
// 自定義Employee類 class Employee { // 成員變量 private String name; private double salary; private LocalDate hireDay; // 構造器 或 構造函數 public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; hireDay = LocalDate.of(year, month, day); } // 成員方法 // 獲取姓名 public String getName() { return name; } //獲取薪資 public double getSalary() { return salary; } //獲取僱用日期 public LocalDate getHireDay() { return hireDay; } /** * 按百分比漲工資 * @param byPercent 百分比 */ public void raiseSalary(double byPercent) { double raise = salary * (byPercent / 100); salary += raise; } } 使用private來修飾的成員變量爲私有的,只能被當前類使用,體現了良好的封裝性。 getName() getSalary() getHireDay()使用public來修飾,供外界來訪問類的私有屬性
若是將變量定義爲static
,每一個類中只有一個這樣的變量,每個對象都共享這樣一個static
變量,這個static
變量不屬於任何對象,只屬於這個類
public Student { // 該靜態變量只屬於Student類,無論聲明多少個學生對象,每一個學生都共有這一個學校名。都是清華大學 private static String schoolName = "清華大學"; }
成員變量和靜態變量的區別
靜態變量所屬於類,因此也稱爲類變量
靜態變量存在於方法中
靜態變量隨着類加載而存在,隨着類的消失而消失
靜態變量,也能夠被對象調用,也能夠被類調用。
靜態變量用的比較少,但靜態常量用的相對比較多
例如,例如在Math類中定義一個靜態常量
public Math { private static final double PI = 3.1415926; }
在程序中,可使用Math.PI
的方式來使用靜態常量,若是省去 static
,則必須經過 Math
的對象來訪問 PI
靜態方法是一種不能向對象實施操做的方法。
public static String getSchoolName(){ return schoolName; } 靜態方法只能經過類名去訪問。 example: Student.getSchoolName();
該成員變量的數據是不是全部對象都同樣
若是是,那麼該變量須要被靜態修飾,由於是共享數據
若是不是,那麼就說這是對象的特有數據,要存儲到對象中
如何判斷成員函數是否被靜態修飾呢?
只要參考,該函數內是否訪問了對象中特有的數據
若是有訪問特有數據,那麼方法不能被靜態修飾
若是沒有訪問特有數據,那麼這個方法須要被靜態修飾
有些類可能有不少個構造器。例如
public Student { private String name; //聲明變量name,存儲學生的姓名 private int age; //聲明變量age,存儲學生的年齡 //無參構造器 public Student() { } //帶有一個參數的構造器 public Student(String aName) { name = aName; } //帶有兩個參數的構造器 public Student(String aName, int aAge){ name = aName; age = aAge; } }
這種特徵叫作重載(overload)。
若是多個方法有相同的名字、不一樣的參數,便產生了重載。
Java容許重載任何方法,不只僅是構造器。
不能有兩個名字相同、 參數類型也相同卻返回不一樣類型值的方法,這不是方法的重載。
在一個類的聲明中,能夠包含多個代碼塊。只要構造類的對象,這些塊就會被執行。例如:
public Student{ private String name; private int age; //初始化塊 { age = 18; } } 不管使用哪一個構造器構造對象,age變量都在對象初始化塊中被初始化。 首先運行初始化塊,而後才運行構造器的主體部分。
對於靜態成員初始化,可使用靜態初始化塊
public Student{ private static String schoolName; static{ schoolName = "清華大學"; } }
沒有顯式初始化的成員變量會默認進行初始化
0
false
null
靜態初始化塊、初始化塊、構造函數同時存在時的執行順序:靜態初始化塊 -> 初始化塊 -> 構造函數
public class Demo3 { public static void main(String[] args) { People people = new People(); System.out.println(people.toString()); } } class People { private String name; private int age; { System.out.println("構造塊"); } static { System.out.println("靜態構造塊"); } public People() { System.out.println("Person 構造器"); } public String toString() { return getClass().getName() + "[name=" + name + ",age=" + age + "]"; } } 執行結果: 靜態構造塊 構造塊 Person 構造器 People[name=null,age=0] //默認初始化值
this
表明當前對象。就是所在方法所屬對象的引用
this(實際參數)
this
對象後面跟上 .
調用的是成員變量和成員方法this
對象後面跟上()
調用的是本類中的對應參數的構造函數final
final
修飾的類是一個最終類,不能夠被繼承。final
修飾的方法是一個最終方法,不能夠被覆蓋。final
修飾的變量是一個常量,只能賦值一次。Java 容許使用包( package ) 將類組織起來。藉助於包能夠方便地組織本身的代碼,並將本身的代碼與別人提供的代碼庫分開管理。
一個類可使用所屬包中的全部類, 以及其餘包中的公有類( public class。)
咱們能夠採用兩種方式訪問另外一個包中的公有類
在每一個類名以前添加完整的包名。
java.tiie.LocalDate today = java.tine.LocalDate.now();
使用 import
語句
import java.util .*; LocalDate today = LocalDate.now();
在發生命名衝突的時候,就不能不注意包的名字了。例如,java.util
和 java.sql
包都有日期( Date) 類。在每一個類名的前面加上完整的包名。
java.util.Date deadline = new java.util.Date(); java.sql.Date today = new java.sql.Date();
import
語句不只能夠導入類,還增長了導入靜態方法和靜態變量的功能
import java.lang.System.*; out.println();
要想將一個類放入包中, 就必須將包的名字放在源文件的開頭,包中定義類的代碼以前。
package com.robin.java;
若是沒有在源文件中放置 package
語句, 這個源文件中的類就被放置在一個默認包( defaulf package ) 中。
get
和 set
方法關鍵字 extends
public Animal{ private int age; public void eat(){ System.out.println("aminal eat food"); } } // 狗繼承動物 public Dog extends Animal{ private String sex; } Animal稱之爲:超類,基類,父類 Dog稱之爲:子類,派生類 Dog不只從超類中繼承了age屬性,並且還定義了屬性sex,此時Dog類中有age,sex兩個屬性 eat()方法,Dog也能夠同時使用
超類中的方法不必定徹底適用於子類,因此須要提供一個新的方法來覆蓋超類中的方法。
Animal中的eat()方法是用來吃食物,而Dog中也須要eat()方法,可是須要吃骨頭,所以咱們能夠提供一個新的方法來覆蓋超類中的方法 public void eat() { System.out.println("Dog eat bone"); }
注意
子類在重寫超類的方式時,子類方法不能低於父類方法的可見性。如超類的方法是 public
,子類必定爲 public
Java
中只支持單繼承子父類出現後,類中的成員都有了哪些特色
當子父類出現同樣的屬性時,子類類型的對象,調用該屬性,值是子類的屬性值。
若是想要調用父類的屬性值,須要使用一個關鍵字: super
this
表明是本類類型的對象引用
super
表明是子類所屬父類中內存空間的引用
當子父類中出現瞭如出一轍的方法時,創建子類對象會運行子類中的方法
因此這種狀況,是函數的另外一個特性:覆寫(重寫,複寫)
發現子類構造函數運行時,先運行了父類的構造函數。爲何呢?
緣由:子類的全部構造函數的第一行,其實都有一條隱身的語句 super()
super()
和 this()
是否能夠同時出如今構造器中。
兩個語句只能有一個定義在第一行,因此只能出現其中一個。
若是自下而上在類的繼承層次結構中上移,位於上層的類更具備通用性,甚至可能更加抽象。從某種角度看, 祖先類更加通用, 人們只將它做爲派生其餘類的基類,而不做爲想使用的特定的實例類。
在不斷抽取過程當中,將共性內容中的方法聲明抽取,可是方法不同,沒有抽取,這時抽取到的方法,並不具體,須要被指定關鍵字 abstract
所標示,聲明爲抽象方法。
抽象類的特色
abstract
關鍵字修飾(能夠描述類和方法,不能夠描述變量)抽象類細節
有,用於給子類對象進行初始化
能夠,其實,抽象類和通常類沒有太大區別,都是在描述事物,只不過抽象類在描述事物時,有些功能不具體。因此抽象類和通常類在定義上,都是須要定義屬性和行爲的。只不過,比通常類多了一個抽象函數,並且比通常類少了一個建立對象的部分
abstract
和哪些不能夠共存?final
private
static
能夠。抽象方法目的僅僅爲了避免讓該類建立對象
Java
中提供了4種訪問控制符
private
- 僅對本類可見public
- 對全部類課件protected
- 對本包和全部子類可見Object
類是 Java
中全部類的始祖, 在 Java
中每一個類都是由它擴展而來的。可是並不須要這樣寫:
public class Student extends Object
public boolean equals(Object obj) { return (this == obj); }
Object
類中的 equals
方法用於檢測一個對象是否等於另一個對象。在 Object
類中,這個方法將判斷兩個對象是否具備相同的引用。若是兩個對象具備相同的引用, 它們必定是相等的。
Java語言規範要求 equals
方法具備如下特性 :
x
,x.equals(x)
應該返回 true
x
和 y
,當且僅當 y.equals(x)
返回 true
,x.equals(y)
也應該返回 true
x
, y
和 z
,若是 x.equals(y)
返回 true
, y.equals(z)
返回 true
,x.equals(z)
也應該返回 true
x
和 y
引用的對象沒有發生變化,反覆調用 x.equals(y)
應該返回一樣的結果x
, x.equals(null)
應該返回 false
public boolean equals(Object otherObject){ if (this == otherObject) return true;//檢測this與otherObject是否引用同一個對象 if (otherObject == null) return false;//檢測otherObject是否爲null,若是是返回false if (getClass() != otherObject.getClass()) return false;//比較this與otherObject是否屬於同一個類 ClassName other = (ClassName)otherObject;//將otherObject轉爲相應類類型變量 return field1 == other.field1 && Object.equals(field2, other.field2) && .... ; //對每項成員變量進行比較 }
散列碼( hash code ) 是由對象導出的一個整型值。散列碼是沒有規律的。
因爲 hashCode
方法定義在 Object
類中, 所以每一個對象都有一個默認的散列碼,其值爲對象的存儲地址。
hashcode
必定相同hashcode
相同,對象不必定相同用於返回表示對象值的字符串
Object中的toString() public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } 類名@哈希值 = getClass().getName()+'@'+Integer.toHexString(hasCode())//默認格式
在自定義類中建議重寫 toString
方法,用來返回類中的各個屬性值
public Student{ private String name; private int age; public String toString() { return getClass().getName() + "[name=" + name + ",age=" + age + "]"; } }
Class getClass()
: 獲取任意對象運行時所屬字節碼文件對象
String getName()
: 返回這個類的名字
函數自己就具有多態性,某一種事物有不一樣的具體的體現
體現 :父類引用或者接口的引用指向了本身的子類對象。Animal a = new Cat()
多態的好處 :提升了程序的擴展性
多態的弊端 :當父類引用指向子類對象時,雖然提升了擴展性,可是隻能訪問父類中具有的方法,不能夠訪問子類中特有的方法。
若是想用子類特有的方法,如何判斷對象是哪一個具體的子類類型呢?
能夠經過一個關鍵字 instanceof
判斷對象是否實現了指定的接口或繼承了指定的類
格式: <對象 instanceof 類型>
判斷一個對象是否所屬於指定類型
Student instanceof Person == true; //Student繼承了Person
在編譯期:參考引用型變量所屬的類中是否有調用的成員(編譯時不產生對象只檢查語法錯誤)
在運行期:參考引用型變量所屬的類中是否有調用的成員
成員變量 - 編譯運行都看 - 左邊
在編譯期:參考引用型變量所屬的類中是否有調用方法
在運行期:參考的是對象所屬的類中是否有調用方法
成員函數 - 編譯看左邊 - 運行看右邊
在編譯期:參考引用型變量所屬的類中是否有調用的成員
在運行期:參考引用型變量所屬的類中是否有調用的成員
靜態函數 - 編譯運行都看 - 左邊
有時, 須要將 int 這樣的基本類型轉換爲對象。 全部的基本類型都冇一個與之對應的類。
Integer 類對應基本類型 int。一般, 這些類稱爲包裝器。
這些對象包裝器類擁有很明顯的名字:Integer、Long、Float、Double、Short、Byte、Character 和 Boolean (前 6 個類派生於公共的超類 Number)。對象包裝器類是不可變的,即一旦構造了包裝器,就不容許更改包裝在其中的值。同時, 對象包裝器類仍是 final , 所以不能定義它們的子類。
關於包裝類的具體使用,後續在經常使用類的文字中詳細介紹
當int值賦給Integer對象時,將會自動裝箱 Integer i = 3; Integer i = Integer.valueOf(3); 這種變換稱之爲自動裝箱 當將一個Integer對象賦給一個int值時,將會自動地拆箱 Integer i = new Integer(3); int n = i; int n = i.intValue(); 這種變化稱之爲自動拆箱
讓你的設計應當對擴展開放 ,對修改關閉 。抽象化 是開閉原則的關鍵。
用抽象構建框架,用實現擴展細節。
全部引用基類(父類)的地方必須能透明地使用其子類的對象
通俗的說:軟件中若是可以使用基類對象,那麼必定可以使用其子類對象。
在程序中儘可能使用基類類型來對對象進行定義,在運行過程當中使用子類對象。
子類能夠擴展父類的功能,但不能改變父類原有的功能。
依賴倒置原則
要針對接口編程,不用針對實現編程。
層模塊不該該依賴底層模塊,他們都應該依賴抽象。抽象不該該依賴細節,細節應該依賴於抽象。 依賴三種寫法: 1.構造函數注入 2.Setter依賴注入 3.接口注入 依賴原則本質:經過抽象(接口或抽象類)使各個類或模塊的實現彼此獨立,不互相影響,實現模塊間的鬆耦合。 原則使用: 每一個類儘可能有接口或抽象類,或者抽象類和接口二者都具有 變量的類型儘可能使接口或者抽象類 任何類都不該該從具體類派生 儘可能不要覆寫基類的方法 結合里氏替換原則
在軟件系統中,一個類只負責一個功能領域中的相應職責。
應該僅有一個引發它變化的緣由。
該原則的核心就是解耦和加強內聚性
將一個接口拆分多個接口,知足不一樣的實現類。
面向對象的思想博大精深,所以咱們不只要學會編寫代碼, 更更更 重要的是學會面向對象的思想。
相關代碼記錄於GitHub中,歡迎各位夥伴 Star !
有任何疑問 微信搜一搜 [程序猿大博] 與我聯繫~
若是以爲對您有所幫助,請 點贊 ,收藏 ,若有不足,請評論或私信指正,謝謝~