Java對象與類

你之因此能優於別人,正是由於你堅持了別人所不能堅持的。
本文相關代碼在個人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

  • 對象的行爲(behavior)- 能夠對對象施加哪些操做,或能夠對對象施加哪些方法?
  • 對象的狀態(state)- 當施加那些方法時,對象如何響應?
  • 對象標識(identity)- 如何辨別具備相同行爲與狀態的不一樣對象?

識別類

在面向對象的學習中,咱們首先要學會設計類,而後在往類中添加所需的方法。微信

識別類的簡單規則是在分析問題的過程當中尋找名詞,而方法對應着動詞。框架

例如:在訂單處理系統中,有這樣一些名詞:ide

  • 商品(Item)
  • 訂單(Order)
  • 送貨地址(Shipping address)
  • 付款(Payment)
  • 帳戶(Account)

這些名詞極可能成爲類 ItemOrder 等。函數

接下來, 查看動詞:商品被添加到訂單中, 訂單被髮送或取消, 訂單貨款被支付。對於每個動詞如:「 添加」、「 發送」、「 取消」 以及「 支付」, 都要標識出主要負責完成相應動做的對象。例如,當一個新的商品添加到訂單中時, 那個訂單對象就是被指定的對象, 由於它知道如何存儲商品以及如何對商品進行排序。也就是說,add 應該是 Order 類的一個方法, 而 Item 對象是一個參數。

面向對象的特色

  • 將複雜的事情簡單化
  • 面向對象將之前的過程當中的執行者,變成了指揮者

    過程和對象在咱們程序中是如何體現的呢?
    過程其實就是函數,對象是將函數等一些內容進行了封裝
  • 面向對象思想符合人們思考習慣的一種思想

面向對象的三大特徵

  • 封裝(下面會介紹)
  • 繼承(下面會介紹)
  • 多態(下面會介紹)

自定義類

要想建立一個完整的程序, 應該將若干類組合在一塊兒, 其中只有一個類有 main 方法。

Employee類

在Java中,最簡答的類的形式以下:

class Employee {
    //成員變量
    field1;
    field2;
    ....
    //構造器
    constructor1;
    constructor2;
    ...
    //成員方法
    method1;
    method2;
    ....
}

類的成員

在類中定義其實都稱之爲成員。成員有兩種:

  • 成員變量:其實對應的就是事物的屬性
  • 成員方法:其實對應的就是事物的行爲

成員變量和局部變量的區別?

  1. 成員變量直接定義在類中

    局部變量定義在方法中,參數上,語句中

  2. 成員變量在這個類中有效

    局部變量只在本身所屬的大括號內有效,大括號結束,局部變量失去做用域

  3. 成員變量存在於堆內存中,隨着對象的產生而存在,消失而消失

    局部變量存在於棧內存中,隨着所屬區域的運行而存在,結束而釋放

構造器

構造器 用於給對象進行初始化,是給與之對應的對象進行初始化。

  • 構造器與類同名,在建立類的對象時,構造器會運行。
  • 構造器總會伴隨 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 = "清華大學";
}

成員變量和靜態變量的區別

  1. 成員變量所屬於對象,因此也稱爲實例變量

    靜態變量所屬於類,因此也稱爲類變量

  2. 成員變量存在於堆內存中

    靜態變量存在於方法中

  3. 成員變量隨着對象建立而存在,隨着對象被回收而消失

    靜態變量隨着類加載而存在,隨着類的消失而消失

  4. 成員變量只能被對象所調用

    靜態變量,也能夠被對象調用,也能夠被類調用。

靜態常量

靜態變量用的比較少,但靜態常量用的相對比較多

例如,例如在Math類中定義一個靜態常量

public Math {
    private static final double PI = 3.1415926;
}

在程序中,可使用Math.PI 的方式來使用靜態常量,若是省去 static ,則必須經過 Math 的對象來訪問 PI

靜態方法

靜態方法是一種不能向對象實施操做的方法。

public static String getSchoolName(){
    return schoolName;
}

靜態方法只能經過類名去訪問。 example: Student.getSchoolName();

何時定義靜態成員呢??

  1. 成員變量。(數據共享時靜態化)

    該成員變量的數據是不是全部對象都同樣

    若是是,那麼該變量須要被靜態修飾,由於是共享數據

    若是不是,那麼就說這是對象的特有數據,要存儲到對象中

  2. 成員函數。(方法中沒有調用特有數據時就定義靜態)

    如何判斷成員函數是否被靜態修飾呢?

    只要參考,該函數內是否訪問了對象中特有的數據

    若是有訪問特有數據,那麼方法不能被靜態修飾

    若是沒有訪問特有數據,那麼這個方法須要被靜態修飾

重載

有些類可能有不少個構造器。例如

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 與 final

this表明當前對象。就是所在方法所屬對象的引用

  • 調用格式: this(實際參數)
  • this 對象後面跟上 . 調用的是成員變量和成員方法
  • this 對象後面跟上()調用的是本類中的對應參數的構造函數

final

  1. 這個關鍵字是一個修飾符,能夠修飾類,方法,變量。
  2. final 修飾的類是一個最終類,不能夠被繼承。
  3. final 修飾的方法是一個最終方法,不能夠被覆蓋。
  4. final 修飾的變量是一個常量,只能賦值一次。

Java 容許使用包( package ) 將類組織起來。藉助於包能夠方便地組織本身的代碼,並將本身的代碼與別人提供的代碼庫分開管理。

類的導入

一個類可使用所屬包中的全部類, 以及其餘包中的公有類( public class。)

咱們能夠採用兩種方式訪問另外一個包中的公有類

  • 在每一個類名以前添加完整的包名。

    java.tiie.LocalDate today = java.tine.LocalDate.now();
  • 使用 import 語句

    import java.util .*;
    LocalDate today = LocalDate.now();

在發生命名衝突的時候,就不能不注意包的名字了。例如,java.utiljava.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 ) 中。

類的設計技巧

  • 必定要保證數據私有(這是最重要的,要保證類的封裝性)
  • 必定要對數據初始化
  • 不要在類中使用過多的成員變量(也就是說能夠把某些變量拆爲一個類,下降耦合性)
  • 不是全部的成員變量都須要 getset 方法
  • 類名和方法名要能體現具體的職責

繼承

定義子類

關鍵字 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 中只支持單繼承

子父類出現後,類中的成員都有了哪些特色

  1. 成員變量

    當子父類出現同樣的屬性時,子類類型的對象,調用該屬性,值是子類的屬性值。

    若是想要調用父類的屬性值,須要使用一個關鍵字: super

    this 表明是本類類型的對象引用

    super 表明是子類所屬父類中內存空間的引用

  2. 成員函數

    當子父類中出現瞭如出一轍的方法時,創建子類對象會運行子類中的方法

    因此這種狀況,是函數的另外一個特性:覆寫(重寫,複寫)

  3. 構造函數

    發現子類構造函數運行時,先運行了父類的構造函數。爲何呢?

    緣由:子類的全部構造函數的第一行,其實都有一條隱身的語句 super()

super()this() 是否能夠同時出如今構造器中。
兩個語句只能有一個定義在第一行,因此只能出現其中一個。

抽象類

若是自下而上在類的繼承層次結構中上移,位於上層的類更具備通用性,甚至可能更加抽象。從某種角度看, 祖先類更加通用, 人們只將它做爲派生其餘類的基類,而不做爲想使用的特定的實例類。

在不斷抽取過程當中,將共性內容中的方法聲明抽取,可是方法不同,沒有抽取,這時抽取到的方法,並不具體,須要被指定關鍵字 abstract 所標示,聲明爲抽象方法。

抽象類的特色

  1. 抽象方法只能定義在抽象類中,抽象類和抽象方法必須由 abstract 關鍵字修飾(能夠描述類和方法,不能夠描述變量)
  2. 抽象方法只定義方法聲明,並不定義方法實現
  3. 抽象類不能夠被建立對象(實例化)
  4. 只有經過子類繼承抽象類並覆蓋了抽象類中的全部抽象方法後,該子類才能夠實例化。不然,該子類仍是一個抽象類

抽象類細節

  1. 抽象類中是否有構造函數?

    有,用於給子類對象進行初始化

  2. 抽象類是否能夠定義非抽象方法?

    能夠,其實,抽象類和通常類沒有太大區別,都是在描述事物,只不過抽象類在描述事物時,有些功能不具體。因此抽象類和通常類在定義上,都是須要定義屬性和行爲的。只不過,比通常類多了一個抽象函數,並且比通常類少了一個建立對象的部分

  3. 抽象關鍵字abstract和哪些不能夠共存?

    final private static

  4. 抽象類可不能夠不定義抽象方法?

    能夠。抽象方法目的僅僅爲了避免讓該類建立對象

訪問控制符

Java 中提供了4種訪問控制符

  • private - 僅對本類可見
  • public - 對全部類課件
  • protected - 對本包和全部子類可見
  • 默認的 - 對本包可見

Obejct - 全部類的超類

Object 類是 Java 中全部類的始祖, 在 Java 中每一個類都是由它擴展而來的。可是並不須要這樣寫:

public class Student extends Object

equals方法

public boolean equals(Object obj) {
    return (this == obj);
}

Object 類中的 equals 方法用於檢測一個對象是否等於另一個對象。在 Object 類中,這個方法將判斷兩個對象是否具備相同的引用。若是兩個對象具備相同的引用, 它們必定是相等的。

Java語言規範要求 equals 方法具備如下特性

  • 自反性:對於任何非空引用 xx.equals(x) 應該返回 true
  • 對稱性: 對於任何引用 xy,當且僅當 y.equals(x) 返回 truex.equals(y) 也應該返回 true
  • 傳遞性: 對於任何引用 xyz ,若是 x.equals(y) 返回 truey.equals(z) 返回 truex.equals(z) 也應該返回 true
  • 一致性: 若是 xy 引用的對象沒有發生變化,反覆調用 x.equals(y) 應該返回一樣的結果
  • 對於任意非空引用 xx.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) &&
            .... ; //對每項成員變量進行比較
}

hashCode方法

散列碼( hash code ) 是由對象導出的一個整型值。散列碼是沒有規律的。

因爲 hashCode 方法定義在 Object 類中, 所以每一個對象都有一個默認的散列碼,其值爲對象的存儲地址。

  • 理論上對象相同,hashcode 必定相同
  • hashcode 相同,對象不必定相同

toString方法

用於返回表示對象值的字符串

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()

多態的好處 :提升了程序的擴展性

多態的弊端 :當父類引用指向子類對象時,雖然提升了擴展性,可是隻能訪問父類中具有的方法,不能夠訪問子類中特有的方法。

多態的前提

  1. 必需要有關係,好比繼承或者實現
  2. 一般會有覆蓋操做

若是想用子類特有的方法,如何判斷對象是哪一個具體的子類類型呢?

能夠經過一個關鍵字 instanceof 判斷對象是否實現了指定的接口或繼承了指定的類

格式: <對象 instanceof 類型> 判斷一個對象是否所屬於指定類型

Student instanceof Person == true; //Student繼承了Person

多態在子父類中的成員上的體現特色

  1. 成員變量:在多態中,子父類成員變量同名

    在編譯期:參考引用型變量所屬的類中是否有調用的成員(編譯時不產生對象只檢查語法錯誤)

    在運行期:參考引用型變量所屬的類中是否有調用的成員

    成員變量 - 編譯運行都看 - 左邊

  2. 成員函數

    在編譯期:參考引用型變量所屬的類中是否有調用方法

    在運行期:參考的是對象所屬的類中是否有調用方法

    成員函數 - 編譯看左邊 - 運行看右邊

  3. 靜態函數

    在編譯期:參考引用型變量所屬的類中是否有調用的成員

    在運行期:參考引用型變量所屬的類中是否有調用的成員

    靜態函數 - 編譯運行都看 - 左邊

包裝類與自動裝箱和拆箱

包裝類

有時, 須要將 int 這樣的基本類型轉換爲對象。 全部的基本類型都冇一個與之對應的類。

Integer 類對應基本類型 int。一般, 這些類稱爲包裝器。

這些對象包裝器類擁有很明顯的名字:IntegerLongFloatDoubleShortByteCharacterBoolean (前 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

有任何疑問 微信搜一搜 [程序猿大博] 與我聯繫~

若是以爲對您有所幫助,請 點贊收藏 ,若有不足,請評論或私信指正,謝謝~

相關文章
相關標籤/搜索