【Java基礎概念】object常用方法

本文源碼都基于JDK1.8

概述

Java是一門面向對象的編程語言理郑,在Java的世界里蹄溉,萬物皆對象。而Object是一切對象的祖先您炉。所以理解Object的常用方法就非常必要了柒爵,下面是Object的成員方法圖解:

8032730f.png

問題

1、hashCode()方法的作用是什么赚爵?

2棉胀、equals()方法和hashCode()方法的關聯(lián)是什么?

3冀膝、equals()與“==”的區(qū)別是什么唁奢?

4、native關鍵字的作用是什么窝剖?

5麻掸、clone()的深拷貝和淺拷貝的區(qū)別是什么?

6赐纱、線程相關方法的用法

Native關鍵字

在初次見到Native關鍵字的時候脊奋,我還懵比,這個Native是干啥的疙描,我怎么從來沒用過诚隙。為什么我找不到被Native修飾的方法的實現呢?

其實Native關鍵字是JNI的一部分起胰,JNI全稱是Java Native Interface久又。JNI是JDK的一部分,它允許Java代碼與其他語言代碼進行交互效五。Java本身是運行在虛擬機上的地消,Java本身是不允許直接訪問硬件的,這就引出了Native關鍵字畏妖。

用Native修飾的方法犯建,在虛擬機里都有一個與之同名的函數,去做Java想要做的事情瓜客。 使用native關鍵字說明這個方法是原生函數,也就是這個方法是用C/C++語言實現的,并且被編譯成了DLL谱仪,由java去調用玻熙。這些函數的實現體在DLL中,JDK的源代碼中并不包含疯攒,對于不同的平臺它們也是不同的嗦随。這也是java的底層機制,實際上java就是在不同的平臺上調用不同的native方法實現對操作系統(tǒng)的訪問的

hashCode()

hashCode()是一個native本地方法敬尺,其實默認的hashCode()方法返回的就是對象對應的內存地址枚尼。當我們在一些場景下復寫了hashCode()方法后,例如需要使用map來存放對象的時候砂吞,覆寫后hashCode返回的就不是對象的內存地址了署恍。

hash算法簡介

hash 算法弓熏,又被稱為散列算法挟冠,基本上激才,哈希算法就是將對象本身的鍵值离斩,通過特定的數學函數運算或者使用其他方法其掂,轉化成相應的數據存儲地址的迄沫。而哈希法所使用的數學函數就被稱為 『哈希函數』又可以稱之為散列函數膘怕。在常見的 hash 函數中有一種最簡單的方法交「除留余數法」捞烟,操作方法就是將要存入數據除以某個常數后赎瑰,使用余數作為索引值王悍。 下面看個例子:
將 323 ,458 餐曼,25 压储,340 ,28 晋辆,969渠脉, 77 使用「除留余數法」存儲在長度為11的數組中。我們假設上邊說的某個常數即為數組長度11瓶佳。 每個數除以11以后存放的位置如下圖所示:

3fbbfa06.png

試想一下我們現在想要拿到 77 在數組中的位置芋膘,是不是只需要 arr[77%11] = 77 就可以了。但是上述簡單的 hash 算法霸饲,缺點也是很明顯的为朋,比如 77 和 88 對 11 取余數得到的值都是 0,但是角標為 0 位置已經存放了 77 這個數據厚脉,那88就不知道該去哪里了习寸。上述現象在哈希法中有個名詞叫碰撞:

碰撞:若兩個不同的數據經過相同哈希函數運算后,得到相同的結果傻工,那么這種現象就做碰撞霞溪。

于是在設計 hash 函數的時候我們就要盡可能做到:

降低碰撞的可能性孵滞。盡量將要存入的元素經過 hash 函數運算后的結果,盡量能夠均勻的分布在指定的容器(我們在稱之為桶)鸯匹。

前文說了 hashCode 方法與 java 中使用散列表的集合類息息相關坊饶,我們拿 Set 來舉例,我們都知道 Set 中是不允許存放重復的元素的殴蓬。那么我們憑借什么來判斷已有的 Set 集合中是否有何要存入的元素重復的元素呢匿级?有人可能會說我們可以通過 equals 來判斷兩個元素是否相同。那么問題又來染厅,如果 Set 中已經有 10000個元素了痘绎,那么之后在存入一個元素豈不是要調用 10000 次 equals 方法。顯然這不合理肖粮,性能低到令人發(fā)指孤页。那要怎么辦才能保證即高效又不重復呢?答案就在于 hashCode 這個函數尿赚。經過之前的分析我們知道 hash 算法是使用特定的運算來得到數據的存儲位置的散庶,那么 hashCode 方法就充當了這個特定的函數運算。這里我們可以簡單認為調用 hashCode 方法后得到數值就是元素的存儲位置(其實集合內部還做了進一步的運算凌净,以保證盡可能的均勻分布在桶內)悲龟。當 Set 需要存放一個元素的時候,首先會調用 hashCode 方法去查看對應的地址上有沒有存放元素冰寻,如果沒有則表示 Set 中肯定沒有相同的元素须教,直接存放在對應位置就好,但是如果 hashCode 的結果相同斩芭,即發(fā)生了碰撞轻腺,那么我們在進一步調用該位置元素的 equals 方法與要存放的元素進行比較,如果相同就不存了划乖,如果不相同就需要進一步散列其它的地址贬养。這樣我們就可以盡可能高效的保證了無重復元素的方法。

equals()

equals 方法屬于Object基類的方法琴庵,所有的對象都擁有這個方法误算,并有權重寫該方法。該方法返回了一個boolean類型的結果迷殿,代表比較的兩個對象是否相同儿礼。事實上很多java定義好的一些引用數據類型,都重寫了equals 方法庆寺。當我們自定義引用數據類型的時候蚊夫,如果判定兩個對象相等,需要根據具體的業(yè)務規(guī)則而定懦尝,但是必須遵循以下規(guī)則知纷;

自反性(reflexive):對于任意不為null 的引用值x壤圃,x.equals(x)一定為true;

對稱性(symmetric): 對于任意不為null的引用值x和y,當且僅當x.equals(y)為true時屈扎,y.equals(x) 為true埃唯;

傳遞性(transitive): 對于任意不為null的引用值x、y和z鹰晨,如果x.equals(y)為true同時y.equal(z)為true,那么x.equals(z)也為true止毕;

一致性(consistent):對于任意不為null的引用值x和y模蜡,如果用于equals比較的對象信息沒有被修改的話,多次調用時 x.equals(y) 要么一致地返回 true 要么一致地返回 false扁凛。

null值要求:對于任意不為 null 的引用值 x忍疾,x.equals(null) 返回 false。

equals 與 == 的區(qū)別

java數據類型可以分為基礎數據類型和引用數據類型谨朝÷倍剩基礎數據類型包括short,int,byte,long,dubble,float,boolean,char八種。對于基礎數據類型字币,==判斷的是左右兩邊的值则披。

int a = 10;
int b = 10;
float c = 10.0f;
//以下輸出結果均為 true
System.out.println("(a == b) = " + (a == b));
System.out.println("(b == c) = " + (b == c));

而對于引用數據類型,==操作符判斷的就是左右兩邊對象的內存地址是否相同洗出。也就是說通過==判斷的兩個引用數據類型士复,如果相等,那么他們指向的肯定是同一個對象翩活。

可以總結出兩者比較的結果如下:

1阱洪、如果==兩邊都是基礎數據類型,那么比較的是兩個的值是否相等菠镇;

2冗荸、如果==兩邊都是引用數據類型,那么比較的是兩者的內存地址是否相同利耍。若相同蚌本,則左右兩邊的是同一個對象。

3堂竟、Object基類的equals默認比較的是兩者的內存地址是否相等魂毁。在構建的對象沒有重寫equals對象時,equals與==作用相同出嘹。

4席楚、equals用于比較引用數據類型是否相等。在滿足equals判斷規(guī)則的前提下税稼,兩個對象只要規(guī)定的屬性相同烦秩,那么就認為兩個對象是相同的垮斯。

equals 與 hashCode的關系

1、如果兩個對象調用equals方法返回的true只祠,那么他們的hashCode一定相同兜蠕;

2、如果兩個對象的hashCode相同抛寝,他們卻不一定是同一個對象熊杨,調用equals方法,不一定為true盗舰;但是如果兩個對象的hashCode不相同晶府,那么他們一定不是同一個對象,調用equals方法一定返回false钻趋;

clone 深淺拷貝

在某些情況下川陆,我們需要獲取一個對象的拷貝來處理某些事情。這個時候就需要用到object.clone方法蛮位,要是用clone方法的類较沪,必須實現cloneable接口,才能夠使用clone方法失仁,否則在使用時會拋出CloneNotSupportedException尸曼。而我們在實際應用中可能會發(fā)現,當對象中包含可變的引用數據類型時陶因,在拷貝得到的新對象中對該引用數據類型的屬性進行修改骡苞,原始對象相應的屬性也會發(fā)生變化,這種現象就是“淺拷貝”楷扬。object默認的clone方法就是淺拷貝解幽。

在了解淺拷貝和深拷貝之前,我們需要先了解一點鋪墊知識:Java中的數據類型分為基礎數據類型和引用數據類型烘苹。這兩種類型在進行賦值操作和作為方法參數或返回值時躲株,會有值傳遞和引用地址傳遞的差別。

淺拷貝

我們來寫一個例子看一下clone()方法的淺拷貝現象:

/**
 * @author xiongchenyang
 * @Date 2019/6/21
 **/
public class CloneTest {


    public static void main(String[] args) throws CloneNotSupportedException {
        Student student = new Student("張三","男",28 ,new Address("深圳","南海大道"));
        Student cloneStudent = (Student) student.clone();
        System.out.println("student的地址:"+student);
        System.out.println("cloneStudent的地址:"+cloneStudent);
        cloneStudent.setAge(44);
        Address address = cloneStudent.getAddress();
        address.setProvince("北京");
        System.out.println("修改cloneStudent后結果為:");
        System.out.println("cloneStudent:" + cloneStudent.display());
        System.out.println("student:"+student.display());
    }

    static class Student implements Cloneable{
        private  String name;
        private String sex;
        private Integer age;
        private Address address;

        private Student(String name, String sex, Integer age, Address address) {
            this.name = name;
            this.sex = sex;
            this.age = age;
            this.address = address;
        }

        public String getName() {
            return name;
        }

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

        public String getSex() {
            return sex;
        }

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

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }

        public String display() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", sex='" + sex + '\'' +
                    ", age=" + age +
                    ", address=" + address +
                    '}';
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }


    static class Address {
        private String province;
        private String street;

        public Address(String province, String street) {
            this.province = province;
            this.street = street;
        }

        public String getProvince() {
            return province;
        }

        public void setProvince(String province) {
            this.province = province;
        }

        public String getStreet() {
            return street;
        }

        public void setStreet(String street) {
            this.street = street;
        }

        @Override
        public String toString() {
            return "Address [province=" + province + ", street=" + street + "]";
        }

    }
}

上述代碼運行后的結果為:

student的地址:com.xcy.test.CloneTest$Student@47fd17e3
cloneStudent的地址:com.xcy.test.CloneTest$Student@7cdbc5d3
修改cloneStudent后結果為:
cloneStudent:Student{name='張三', sex='男', age=44, address=Address [province=北京, street=南海大道]}
student:Student{name='張三', sex='男', age=28, address=Address [province=北京, street=南海大道]}

可以看到镣衡,clone之后我們得到的是兩個對象霜定,我們改變clone得到的cloneStudent的基礎類型(值類型)屬性后,原始student的值不會隨之改變廊鸥;但是我們改變了cloneStudent的引用類型屬性后望浩,原始student的引用類型屬性也隨之改變了。

總結:淺拷貝創(chuàng)建了一個新的對象惰说,然后將當前對象的非靜態(tài)字段復制到該對象磨德,如果字段類型為基礎類型(值類型),那么復制該字段的值;如果字段類型為引用類型,那么復制該字段的引用到新的對象典挑,而不是復制引用指向的值到新的對象酥宴。
此時新對象中的引用類型字段相當于原始字段中引用類型字段你的一個副本,原始對象和新對象的引用字段指向的是同一個對象您觉。

深拷貝

淺拷貝是對值類型進行拷貝拙寡,對引用數據類型進行引用的拷貝。那么深拷貝就是要講引用類型的屬性內容也都拷貝一份新的琳水。

我目前了解到的深拷貝實現方式肆糕,總共兩種:1、引用類型也實現cloneable接口在孝,并重寫clone()方法擎宝;2、通過序列化和反序列化浑玛,實現。下面我們用兩種方式實現下深拷貝

1噩咪、實現cloneable接口

修改Address類:

static class Address implements  Cloneable {
        
        
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

        ……
    }

修改Student類:

static class Student implements Cloneable{

        @Override
        protected Object clone() throws CloneNotSupportedException {
            Student student = (Student) super.clone();
            student.address = (Address) address.clone();
            return student;
        }
    }
    
    ……

執(zhí)行原測試代碼之后得到結果如下:

student的地址:com.xcy.test.CloneTest$Student@47fd17e3
cloneStudent的地址:com.xcy.test.CloneTest$Student@7cdbc5d3
修改cloneStudent后結果為:
cloneStudent:Student{name='張三', sex='男', age=44, address=Address [province=北京, street=南海大道]}
student:Student{name='張三', sex='男', age=28, address=Address [province=深圳, street=南海大道]}

可以看到重寫Clone方法后顾彰,執(zhí)行原測試代碼,修改cloneStudent的Address的province屬性后胃碾,原student對應的值沒有發(fā)生改變涨享。我們也不難想到,當一個實體類中有多個引用數據類型時仆百,我們需要手動引用多個引用數據類型的clone方法厕隧,不是很方便。而對于這種情況俄周,我們可以考慮用序列化來進行深拷貝吁讨。

編寫序列化和反序列化的深拷貝方法:

import java.io.*;

/**
 * @author xiongchenyang
 * @Date 2019/6/24
 **/
public class DeepClone implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 利用序列化和反序列化進行對象的深拷貝
     * @return
     * @throws Exception
     */
    protected Object deepClone() throws Exception{
        //序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(this);

        //反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return ois.readObject();
    }
}

Student和Address方法都繼承DeepClone。然后執(zhí)行下面的測試代碼:

public static void main(String[] args) throws Exception {
        Student student = new Student("張三","男",28 ,new Address("深圳","南海大道"));
        Student cloneStudent = (Student) student.deepClone();
        System.out.println("student的地址:"+student);
        System.out.println("cloneStudent的地址:"+cloneStudent);
        cloneStudent.setAge(44);
        Address address = cloneStudent.getAddress();
        address.setProvince("北京");
        System.out.println("修改cloneStudent后結果為:");
        System.out.println("cloneStudent:" + cloneStudent.display());
        System.out.println("student:"+student.display());
    }

得到結果如下:

student的地址:com.xcy.test.CloneTest$Student@1b0375b3
cloneStudent的地址:com.xcy.test.CloneTest$Student@32d992b2
修改cloneStudent后結果為:
cloneStudent:Student{name='張三', sex='男', age=44, address=Address [province=北京, street=南海大道]}
student:Student{name='張三', sex='男', age=28, address=Address [province=深圳, street=南海大道]}

由以上結果可以發(fā)現峦朗,修改克隆得到的cloneStudent值建丧,對原student沒有任何影響

線程相關方法

Object中線程相關的方法有wait(),wait(long),wait(long,int)波势,notify(),notifyAll()這五個方法翎朱,它們都屬于final方法,其中wait(long),notify(),notifyAll()又屬于native 方法尺铣,所以他們無法被子類重寫拴曲。這些方法有一個共同特點:他們都必須在同步方法或者同步塊中執(zhí)行,因為在調用他們的時候都必須持有對象鎖凛忿,如果方法沒有持有對象鎖澈灼,那么會拋出InterruptedException異常

wait(long)

public final native void wait(long timeout) throws InterruptedException;

當執(zhí)行wait(long)方法時,會釋放當前鎖侄非,讓出CPU資源蕉汪,線程由Running狀態(tài)變?yōu)閃aiting狀態(tài)流译,并將當前線程放入到對象的等待隊列中。如果超出入參的等待時間者疤,那么該線程將被喚醒福澡,進入同步隊列,由waiting狀態(tài)轉換為Blocked狀態(tài)驹马。

wait(),wait(long,int)

這兩個方法的本質是調用wait(long)方法革砸,具體可以看下面的代碼:

public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }
    
    public final void wait() throws InterruptedException {
            wait(0);
        }

notify(),notifyAll()

notify()當在同步方法或同步塊中,執(zhí)行該方法并退出當前同步塊或同步方法后糯累,會釋放鎖算利,并隨機喚醒當前等待隊列中的某一線程,將該線程從等待隊列加入到同步隊列中泳姐。notify()默認喚醒策略是:先進入wait的線程先被喚醒 (可以自己設置策略)

notifyAll()則會將等待隊列中的所有線程都喚醒效拭,加入到同步隊列中,然后這些線程會競爭對象鎖胖秒,競爭到的線程會執(zhí)行缎患。notifyAll()默認喚醒策略是:采用LIFO策略 (可以自己設置策略)

這里先簡要了解一下wait(),notify(),notifyAll()方法的作用,在后面學習到多線程相關知識的時候阎肝,會對object中的線程通信方法做一個詳細的分析挤渔。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市风题,隨后出現的幾起案子判导,更是在濱河造成了極大的恐慌,老刑警劉巖沛硅,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眼刃,死亡現場離奇詭異,居然都是意外死亡稽鞭,警方通過查閱死者的電腦和手機鸟整,發(fā)現死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朦蕴,“玉大人篮条,你說我怎么就攤上這事》宰ィ” “怎么了涉茧?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疹娶。 經常有香客問我伴栓,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任钳垮,我火速辦了婚禮惑淳,結果婚禮上,老公的妹妹穿的比我還像新娘饺窿。我一直安慰自己歧焦,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布肚医。 她就那樣靜靜地躺著绢馍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肠套。 梳的紋絲不亂的頭發(fā)上舰涌,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音你稚,去河邊找鬼瓷耙。 笑死,一個胖子當著我的面吹牛刁赖,可吹牛的內容都是我干的哺徊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼乾闰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盈滴?” 一聲冷哼從身側響起涯肩,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎巢钓,沒想到半個月后病苗,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡症汹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年硫朦,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片背镇。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡咬展,死狀恐怖,靈堂內的尸體忽然破棺而出瞒斩,到底是詐尸還是另有隱情破婆,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布胸囱,位于F島的核電站祷舀,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜裳扯,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一抛丽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饰豺,春花似錦亿鲜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锅很,卻和暖如春其馏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爆安。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工叛复, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扔仓。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓褐奥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親翘簇。 傳聞我的和親對象是個殘疾皇子撬码,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345