1. 程式人生 > >關於java中的==,equals()

關於java中的==,equals()

1. 先從一道面試題說起

請問下面的

public class Demo {
    public static void main(String args[]){
        String a = "a" + "b" + 1;
        String b = "ab1";
        System.out.println(a == b);
    }
}

要了解這個問題,需要回答下面的幾個問題:

  1. 關於“ ==”是做什麼的?
  2. equals 呢?
  3. a和b在記憶體中是什麼樣的?
  4. 編譯時優化方案。

2. 關於==

在Java語言中,“==”就是對比兩個記憶體單元的內容是否一樣。
如果是原始型別byte,boolean,short,char,int,long,float,double,就是直接比較它們的值。
如果是引用,比較的就是引用的值,“引用的值”可以被認為物件的邏輯地址。如果兩個引用發生“==”操作,就是比較相應的兩個物件的地址值是否一樣。換句話說,如果兩個引用所儲存的物件是同一個物件,則返回true,否則返回false(如果引用指向的是null,其實這也是一個jvm賦予給它的某個指定的值)。
看下面的程式碼:

public class Demo {
    public static void main(String args[]){
        List<String> a = null;
        List<String> b = null;
        System.out.println(a == b);
    }
}
  // 輸出結果
  true
  

3. 關於“equals()”方法

“equals()”方法,首先是在Object類中被定義的,它的定義中就是使用“==”方式來匹配的。

//equals在Object類中的原始碼
public boolean equals(Object obj) {
    return (this == obj);
}

也就是說,如果不去重寫equals()方法,並且對應的類其父類中沒有重寫過equals()方法,那麼預設的equals()操作就是對比物件的地址。

equals()方法之所以存在,是希望子類去重寫這個方法,實現對比值的功能。

3. a和b在記憶體中是什麼樣的?

a和b在記憶體中是指向同一塊記憶體空間的。這就得益於Java的編譯時優化方案。

我們用反編譯軟體jd-gui看看編譯後的程式碼是怎麼樣的?

import java.io.PrintStream;

public class Demo
{
  public static void main(String[] args)
  {
    String a = "ab1";
    String b = "ab1";
    System.out.println(a == b);
  }
}

看到這裡結果應該就一目瞭然了。JVM會把常量疊加在編譯時進行優化,因為常量疊加得到的是固定的值,無須執行時再進行計算,所以會這樣優化。

看到這裡彆著急,JVM只會優化它可以幫你優化的部分,它並不是對所有的內容都可以優化。例如,就拿上面疊加字串來說,如果幾個字串疊加出現了變數,即在編譯時還不確定具體的值是多少,那麼JVM是不會去做這樣的編譯時合併的。

如果上面的這段話你理解了,我們再來看一個例子:

public class Demo {
    public static void main(String args[]){
        String a = "a";
        final String c ="a";

        String b = a + "b";
        String d = c + "b";
        String e = getA() + "b";
        String compare = "ab";

        System.out.println( b == compare);
        System.out.println( d == compare);
        System.out.println( e == compare);
    }
    
    private static String getA(){
        return "a";
    }
}
//輸出結果:
false
true
false

根據我們上面的解釋,判斷b==compare和e==compare輸出結果為false,這個比較容易理解,因為a和getA()並不是一個常量,編譯時並不會對此進行優化,我們用jd-gui可靠編譯後的程式碼:

import java.io.PrintStream;

public class Demo
{
  public static void main(String[] args)
  {
    String a = "a";
    String c = "a";
    
    String b = a + "b";
    String d = "ab";
    String e = getA() + "b";
    String compare = "ab";
    
    System.out.println(b == compare);
    System.out.println(d == compare);
    System.out.println(e == compare);
  }
  
  private static String getA()
  {
    return "a";
  }
}

從編譯後的程式碼,我們可以驗證我們的結論,b和e並沒有被JVM優化。

比較奇怪的是變數d,被JVM優化了。區別在於對疊加的變數c有一個final修飾符。從定義上強制約束了c是不允許被改變的,由於final不可變,所以編譯器自然認為結果是不可變的。

4. 記憶體中的字串(詳細解釋)

字串物件內部是用字元陣列儲存的,那麼看下面的例子:

String m = "hello,world";
String n = "hello,world";
String u = new String(m);
String v = new String("hello,world");

這些語句會發生什麼事情?大概是這樣的:

  1. 會分配一個11長度的char陣列,並在常量池分配一個由這個char陣列組成的字串,然後由m去引用這個字串。
  2. 用n去引用常量池裡邊的字串,所以和m引用的是同一個物件
  3. 生成一個新的字串,單內部的字元陣列引用著m內部的字元陣列。
  4. 同樣會生成一個新的字串,但內部的字元陣列引用常量池裡邊的字串內部的字元陣列,意思是和u是同樣的字元陣列。

我們使用圖來表示的話,情況就大概是這樣的:

image

結論就是,m和n是同一個物件,但m,u,v都是不同的物件,但都使用了同樣的字元陣列,並且用equal判斷的話也會返回true。

我們可以使用反射修改字元陣列來驗證一下效果:

public class Demo {
    public static void main(String args[]) throws NoSuchFieldException, IllegalAccessException {
        String m = "hello,world";
        String n = "hello,world";
        String u = new String(m);
        String v = new String("hello,world");
        Field f = m.getClass().getDeclaredField("value");
        f.setAccessible(true);
        char[] cs = (char[]) f.get(m);
        cs[0] = 'H';
        String p = "Hello,world";
        System.out.println(m.equals(p));
        System.out.println(n.equals(p));
        System.out.println(u.equals(p));
        System.out.println(v.equals(p));
    }
}
//輸出結果:
true
true
true
true

從上面的例子可以看到,經常說的字串是不可變的,其實和其他final類沒有什麼區別,還是引用不可變的意思。雖然String類不開放value,但同樣是可以通過反射進行修改。

5. 關於String中的intern方法

public class Demo {
    public static void main(String args[]){
        String a = "a";
        String b = a + "b";
        String c = "ab";
        String d = new String(b);
        System.out.println(b == c);
        System.out.println(c == d);
        System.out.println(c == d.intern());
        System.out.println(b.intern() == d.intern());
    }
}
//輸出結果
false
false
true
true

String引用所指向的物件,它們儲存在常量池中,同一個值的字串保證全域性唯一。

如何保證全域性唯一呢? 當呼叫intern()方法時,JVM會在這個常量池中通過equals()方法查詢是否存在等值的String,如果存在,則直接返回常量池中這個String物件的地址;若沒有找到,則會建立等值的字串,然後再返回這個新建立空間的地址。只要是同樣的字串,當呼叫intern()方法時,都會得到常量池中對應String的引用,所以兩個字串通過intern()操作後用等號是可以匹配的。

相關推薦

Java==和equals()equalsIgnoreCase()

關於==和equals,我們需要知道java中的資料型別,可分為兩類: 1.基本資料型別,也稱原始資料型別。byte,short,char,int,long,float,double,boolean  他們之間的比較,應用雙等號(==),比較的是他們的值。  2.複合資料

Java==和equals的區別equals和hashCode的區別

在java中: ==是運算子,用於比較兩個變數是否相等。 equals,是Objec類的方法,用於比較兩個物件是否相等,預設Object類的equals方法是比較兩個物件的地址,跟==的結果一樣。Ob

JAVA學習】java==、equals()、hashCode()都和物件的比較有關java這三者各有什麼用處呢java為什麼需要設計這三種物件的比較方法呢?

關於hashCode() 為什麼會設計hashCode()方法?    hashCode()方法返回的就是一個數值,我們稱之為hashCode吧。從方法的名稱上就可以看出,其目的是生成一個hash碼。hash碼的主要用途就是在對物件進行雜湊的時候作為key輸入,據此很容易推斷出,我們需要每個物件的ha

Java == 與 equals方法以及常見的 == 比較

1、”==”: 是算數運算子,比較的是兩個引用指向的是否是同一個記憶體地址,也就是指向的是否是同一物件。 2、equals方法: 是屬於Object的方法,是開發者自己根據具體的業務邏輯來定義該方法,用於檢查兩個物件的相等性,注意是讓開發者自己去重寫的方法

JAVA重寫equals()方法的同時要重寫hashcode()方法

內存地址 his mov bool args 變量 維護 log obj object對象中的 public boolean equals(Object obj),對於任何非空引用值 x 和 y,當且僅當 x 和 y 引用同一個對象時,此方法才返回 true;註意:當此方法

Javaequals()和hashCode()

sea 接口 後來 ide itl 一個數 毫無 exceptio title 概述 在我們使用類集框架(比方使用hashMap、hashSet)的時候,常常會涉及到重寫equals()和hashCode()這兩個方法。 這兩個方法的聯系是:

java==與equals

字符串 基本類型 true 變量 由於 als 不同 引用 str2 ==   ==可用於比較基本類型與引用類型,對於基本類型變量比較的是其存儲的值是否相等,對於引用類型則比較的是其是否指向同一個對象。 如: int a = 10; int b = 20; d

java什麽是構造函數?什麽是構造函數重載?什麽是復制構造函數?

默認 調用 構造函數 多個 必須 自己 ava nbsp 每一個 當新對象被創建的時候,會調用構造函數。每一個類都有構造函數。在程序員沒有給類提供構造函數的情況下,java編譯器會為這個類創建一個默認的構造函數。   java中構造函數的重載和方法重載很相似。

Java“==”和“equals()”的區別

spa logs bsp 指向 monday class code equals out “==”比較的是變量所指向的對象,當S1在內存中定義以後,再定義s2的時候s2所指向的值是定義s1時候所創建的,而不是又在內存創建了一個“Monda

java “==” 和 equals 的區別

通過 引用 而在 program 值範圍 兩個 比較 copy mon   在初學Java時,可能會經常碰到下面的代碼: 1 String str1 = new String("hello"); 2 String str2 = new String("hello");

使用java面向對象封裝+繼承的方法算題

去掉空格 方法 amp get urn 余數 oid pan 新的 1.第一種:給定一行字符,逆序輸出此字符串(空格.數字不輸出),如“ab 23,(4 cd”輸出“dc(,ba”。(要求:使用面向對象封裝+繼承) class Bu { private Strin

Java==和equals和區別詳解+案例

兩個 布爾型 整數 返回 boolean 和equal clas 定義 true 一開始遇見==和equals我也是分不清,後來看了很多博客,收益匪淺, 擔心以後給忘了,所以寫下這個,以後復習可以用。 (有哪裏寫得不對的,希望可以留言幫忙改進,大家一起共同進步) 一、Jav

javaequals與==

bsp 源碼 內部 brush ++ class object類 功能 length equals()與==都是java中用於進行比較的,返回boolean值,不同的是equals()是Object類中定義的一個方法,==是一個比較運算符。下面是equals()在O

Java類及其組成所使用的常見修飾符

成員 name fin 常用 tro string prot 默認 abstract Java中,類及其組成所使用的常見修飾符   (1)修飾符的分類:     權限修飾符:private、默認、protected、public     狀態修飾符:static、fin

Java內部類的概述和內部類的訪問特點和內部類的分類(內部類的位置)

back 外部 mage 對象 post info bsp 一個 strong 內部類的概述:   把類定義在另一個類的內部,該類就被稱為內部類。   舉例:把類B定義在類A中,類B就被稱為內部類。 內部類的訪問特點:   A:內部類可以直接訪問外部類的成員,包括

Java局部內部類

外部類 col 局部變量 外部 成員 變量名 class post 常量值 局部內部類   A:局部內部類可以直接訪問外部類的成員。   B:局部內部類在局部位置可以創建內部類對象,通過內部類對象調用內部類方法,來使用局部內部類功能。   C:局部內部類訪問局部變量

Java匿名內部類

java () ack http 代碼 一個 back 抽象 子類 匿名內部類   就是局部內部類的簡化寫法。 前提:存在一個類或者接口。   這裏的類可以是具體類也可以是抽象類。 格式:   new 類名或者接口名() {     重寫方法;   }

Java權限修飾符的權限測試

註意 div pre package img string 需要 fat prot ============================================================================= 1、 1 /* 2

javaequals方法

equal zjoi csb wow kvc gfw 因此 sas hid 一、equals方法介紹 1.1.通過下面的例子掌握equals的用法 1 package cn.galc.test; 2 3 public class TestEquals { 4

java輸入兩個數輸出較大的數

ring bsp println OS [] tint sys span 適用於 第一種 1 import java.util.*; 2 public class A{ 3 public static void main(String[] args){ 4