Java基礎|this和super;靜態和動態綁定;equals與hashCode和toString有你所不知道的原理!(小白掃盲)

##⒈this和super關鍵字的3種含義 ##

this關鍵字

this關鍵字有3個用途:一是引用隱式參數(即引用自己的成員變量);二是調用自己其它的構造器;三是代表本身類的一個對象。

###⑴ super關鍵字
super關鍵字也有3個用途:一是調用超類的方法;二是調用超類的構造器;三是代表超類的一個對象。
###⑵ 二者相似之處
在調用構造器時,兩個關鍵字的使用方式相似。調用構造器的語句只能作爲另一個構造器的第一條語句出現。
###⑶ 示例代碼

/**
 * @author gao tianci
 * @version $Id: Father.java, v 0.1 2017年6月1日 下午8:21:22 gao tianci Exp $
 */
public class Father {
    private String name;
    private int    age;

    /**
     * 默認構造器
     */
    public Father() {
    }

    public Father(String name) {
        //調用本類中的其他構造器
        this(name, 20);
    }

    public Father(String name, int age) {
        //引用隱式參數
        this.name = name;
        this.age = age;
    }

    //父類中的方法
    public void printInfo(String name, int age) {
        System.out.println("name = " + name);
        System.out.println("age = " + age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
/**
 * 
 * @author gao tianci
 * @version $Id: Child.java, v 0.1 2017年6月1日 下午8:30:03 gao tianci Exp $
 */
public class Child extends Father {
    private String sex;

    public Child() {
    }

    public Child(String sex) {
        //[super]調用超類構造器
        super("tianci", 20);
        //[super]調用超類方法;[super]代表超類對象
        super.printInfo(super.getName(), super.getAge());

        //[this]引用隱式參數
        this.sex = sex;
        //[this]代表本類的對象
        this.printInfo(this.getSex());
    }

    //子類中的方法
    public void printInfo(String sex) {
        System.out.println("sex = " + sex);
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

##⒉方法調用[靜態綁定]和[動態綁定] ##

⑴靜態綁定

程序執行前方法已經被綁定(也就是說在編譯過程中就已經知道這個方法到底是哪個類中的方法)。針對Java簡單的可以理解爲程序編譯期的綁定.

⑵動態綁定

程序在運行期間判斷對象的類型,調用適當的方法(也就是說在編譯過程中並不知道這個方法是哪個類中的方法,但在程序運行期間通過一定的機制可以確定個方法是哪個類的方法,並正確的調用)。動態綁定的過程:❶虛擬機提取對象的實際類型的方法表;❷虛擬機搜索方法簽名;❸調用方法。
###⑶ Java中的靜態綁定和動態綁定體現

動態綁定針對的範疇只是對象的方法
②java當中的方法只有finalstaticprivate構造方法是靜態綁定,其它方法都是動態綁定。
③java中對屬性採取靜態綁定。

###⑷java的編譯與運行
①java的編譯過程是將java源文件編譯成字節碼(jvm可執行代碼,即.class文件)的過程,在這個過程中java是不與內存打交道的,在這個過程中編譯器會進行語法的分析,如果語法不正確就會報錯。


②Java的運行過程是指jvm(java虛擬機)裝載字節碼文件並解釋執行。在這個過程纔是真正的創立內存佈局,執行java程序。java字節碼的執行有兩種方式: ❶即時編譯方式:解釋器先將字節編譯成機器碼,然後再執行該機器碼;❷解釋執行方式:解釋器通過每次解釋並執行一小段代碼來完成java字節碼程序的所有操作。

⑸Java靜態綁定和動態綁定特點

①靜態綁定:可以讓我們在編譯期就發現程序中的錯誤,而不是在運行期。靜態綁定對系統開銷較小,提高了程序運行效率。

②動態綁定:而對方法採取動態綁定是爲了實現多態,以效率爲代價來實現多態。動態綁定可以讓我們對現存的代碼無需修改,就可以對其功能進行擴展。


3.equals與hashCode和toString方法

###⑴ equals

①’=='雙等號兩種應用

 ❶比較基本數據類型,如果兩個值相同,則結果爲true,否則false.
 ❷比較引用類型時,如果引用指向內存中的同一對象(即兩個變量引用同一個對象時),結果爲true,否則false。

②equals

Object類中equals方法
作用是判斷兩個變量是否具有相同的引用,如果是結果爲true,否則false,從這一點來說equals本質實現方式使用了==。


覆寫equals方法
一般我們有這樣的需求:只比較兩個對象的內容是否相同,如果兩個對象的內容都相同,就認爲這兩個對象相等。想達到這一目的,我們就需要自己覆寫equals方法。java常用的類基本都默認覆寫了equals方法來實現比較內容相同。


覆寫equals方法建議
在自己定義的類中編寫一個完美的equals方法的建議:
❶顯式參數命名爲otherObject,稍後將它轉換成另一個叫做other的的變量。
❷檢測this與otherObject是否引用同一個對象:

	if(this==otherObject) return true;

❸檢測otherObject是否爲null,如果爲null,返回false:

	if(null==otherObject) return false;

❹比較this與otherObject是否屬於同一類。如果equals的語義在每個子類中有所改變,就是用getClass檢測:

	if(this.getClass()!=otherObject.getClass()) return false;

如果所有的子類都擁有統一的語義,就是用instanceof 檢測:

	if(!(otherObject instanceof ClassName)) return false;

❺將otherObject 轉換爲相應的類類型變量:

	ClassName other = (ClassName)otherObject;

❻接下來對所有需要比較的域進行比較。使用==比較基本類型域,使用equals比較對象域。如果所有的域都匹配,返回true,否則返回false。

return field1==other.field1&&Objects.equals(field2,other.field2)&&...;
如果子類中重新定義equals,就要在其中包含調用:
super.equals(other);

⑵hashCode

####① Object類中hashCode方法。
由於hashCode方法定義在Object類中,因此每個對象都有一個默認的散列碼,其值是一個整型數值,爲對象的存儲地址。

②覆寫hashCode方法。

覆寫hashCode方法原則。當我們在自定義的類覆寫equals方法的時候,就必須覆寫hashCode方法。
爲什麼要遵循覆寫hashCode原則
1 HashCode存在的作用:當對象存儲在哈希表中(如Hashtable,HashMap)時,哈希表中查找對象時,先依據HashCode確定對象所在的鏈表,再依據equals方法在鏈表中確定對象是否存在。
2 覆寫equals()時,要覆寫hashCode()原因:

  • <1>JavaSE規範約束:可查看Object的hashCode()方法,註釋文檔。歸納起來就是以下3條:

    兩個對象相同,hashCode一定相同;

    hashCode不同,兩個對象一定不相同;

    hashCode相同,兩個對象不一定相同。
  • <2>覆寫的本質意義:


    若對象不會保存在哈希表結構中,那麼是不需要覆寫hashCode();


    若在HashMap和Hashtable裏面如果是作爲value而不是作爲key的話,也是不必覆寫 hashCode()。至於HashSet,實際上它只是忽略value的HashMap,每次HashSet.add(obj)其實就是HashMap.put(obj, dummyObject)。


    爲了保證對象做爲key時,每次get的時候HashMap(包括HashTable,HashSet)既要看equals是不是true,也要看hash code是不是true;put的時候也是要判斷equals()和hash code()是不是都是true,來進行添加和獲取操作。重寫了equals()改變了判等依據,所以要對應重寫hashCode()改變對應的判等依據,否則無法保持一致性造成錯誤。

⑶toString

####① Object類中toString方法。
Object類定義了toString方法,因此每個對象都有一個默認的toString方法,打印輸出的默認內容爲:對象所屬的類名和散列碼。
####② 覆寫toString方法。
一般對自定義的類,覆寫toString方法,方便打印我們需要看到的內容。


⑷equals與hashCode和toString用例

父類代碼:

import java.io.Serializable;
import java.time.LocalDate;
import java.util.Objects;
/**
 * 
 * @author gao tianci
 * @version $Id: Employee.java, v 0.1 2017年5月18日 下午6:32:19 gao tianci Exp $
 */
public class Employee implements Serializable {
    /**  */
    private static final long serialVersionUID = -1213732035543208949L;

    private String            name;
    private double            salary;
    private LocalDate         hireDay;

    public Employee(String name, double salary, int year, int month, int day) {
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    public void raiseSalary(double byPrecent) {
        double raise = salary * byPrecent / 100;
        salary += raise;
    }

    //equals方法
    /*編寫完整equals方法步驟:
     * 檢測this 和 otherObject是否引用同一個對象
     * 檢測otherObject是否爲null
     * 檢測this 和 otherObject是否爲同一個類
     * 將otherObject轉換爲相應的類類型變量
     * 使用Objects.equals()比較域對象
     * 
    */
    public boolean equals(Object otherObject) {
        // 檢測this 和 otherObject是否引用同一個對象
        if (this == otherObject)
            return true;
        // 檢測otherObject是否爲null
        if (null == otherObject)
            return false;
        //檢測this 和 otherObject是否爲同一個類
        if (this.getClass() != otherObject.getClass())
            return false;
        //將otherObject轉換爲相應的類類型變量
        Employee other = (Employee) otherObject;
        // 使用Objects.equals()比較域對象
        return Objects.equals(name, other.name) && Objects.equals(salary, other.salary) && Objects.equals(hireDay, other.hireDay);
    }

    //hashCode方法
    public int hashCode() {
        //由所有對象的散列碼組合組合一個散列碼
        return Objects.hash(name, salary, hireDay);
    }

    /** 
     * toString 方法
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Employee [name=" + name + ", salary=" + salary + ", hireDay=" + hireDay + "]";
    }
}   

子類代碼:

import java.util.Objects;

/**
 * @author gao tianci
 * @version $Id: Manager.java, v 0.1 2017年5月18日 下午6:58:53 gao tianci Exp $
 */
public class Manager extends Employee {
    /**  */
    private static final long serialVersionUID = 5401329903146485794L;
    /**
     * @param name
     * @param salary
     * @param year
     * @param month
     * @param day
     * 繼承中,當基類中沒有無參數的構造器時(換句話說,基類中只提供了有參數的構造器),
     * 那麼子類也要提供對應的含參數構造器,否則編譯出錯。在子類 的構造器中我們使用super
     * 關鍵字,實現對基類的構造器的調用。
     */
    public Manager(String name, double salary, int year, int month, int day) {
        //使用super關鍵字調用基類的構造器
        super(name, salary, year, month, day);
        bonus = 0;
    }

    private double bonus;

    public double getSalary() {
        //使用super關鍵字調用基類的方法
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }

    public void setBonus(double bonus) {
        //this 關鍵字引用隱式參數
        this.bonus = bonus;
    }

    //equals方法
    public boolean equals(Object otherObject) {
        //如果子類中重新定義equals,就要在其中包含調用: super.equals(otherObject);
        if (!super.equals(otherObject))
            return false;
        //調用父類的super.equals(otherObject)檢測通過,只需檢測子類中獨有的域值
        Manager other = (Manager) otherObject;
        return Objects.equals(bonus, other.bonus);
    }

    //hashCode方法
    public int hashCode() {
        return super.hashCode() + 17 * new Double(bonus).hashCode();
    }

    /** 
    * @see java.lang.Object#toString()
    */
    @Override
    public String toString() {
        return super.toString() + "[bonus=" + bonus + "]";
    }
}

測試代碼:

/**
 * @author gao tianci
 * @version $Id: Test.java, v 0.1 2017年5月18日 下午7:10:04 gao tianci Exp $
 */
public class Test {
    public static void main(String[] args) {
        Employee alice1 = new Employee("Alice Adams", 7500, 1987, 12, 5);
        Employee alice2 = alice1;
        Employee alice3 = new Employee("Alice Adams", 7500, 1987, 12, 5);
        Employee bob = new Employee("Bob Brandson", 5000, 1989, 10, 1);

        System.out.println("--------==-------");
        System.out.println(alice1 == alice2);
        System.out.println(alice1 == alice3);
        System.out.println("-----父類equals----");
        System.out.println(alice1.equals(alice2));
        System.out.println(alice1.equals(alice3));
        System.out.println(alice1.equals(bob));
        System.out.println("-----子類equals-----");
        Manager carl = new Manager("Carl", 5000, 1987, 12, 10);
        Manager boss = new Manager("Carl", 5000, 1987, 12, 10);
        Manager boss2 = new Manager("Carl", 5000, 1987, 12, 10);
        boss2.raiseSalary(5000);
        System.out.println(carl.equals(boss));
        System.out.println(boss.equals(boss2));
        System.out.println("----hashCode值----");
        //不同對象的hashCode值可能相同
        System.out.println(alice1.hashCode());
        System.out.println(alice2.hashCode());
        System.out.println(alice3.hashCode());
        System.out.println("----hashCode值----");
        System.out.println(bob.hashCode());
        System.out.println(carl.hashCode());
        System.out.println(boss.hashCode());
        System.out.println(boss2.hashCode());
    }
}

測試結果:

--------==-------
true
false
-----父類equals----
true
true
false
-----子類equals-----
true
false
----hashCode值----
-916556984
-916556984
-916556984
------------------
-731457450
1306331297
1306331297
1492420577

###⑸自定義類覆寫equals與hashCode和toString方法時使用輔助工具類建議
1.java.util.Objects (jdk7)
static int hash(Object… objects)
返回一個散列碼,由提供的所有對象的散列碼組合得到。
static int hashCode(Object a)
如果a爲null,返回0;否則返回a.hashCode();
static boolean equals(Object a,Object b)
如果a和b都爲null,返回true;如果其中之一爲null,返回false;否則返回a.equals(b)

2.針對數組的一些工具類方法在java.util.Arrays類中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章