天天看点

Java中==与equals()方法的区别

== :   

比较的是两个变量的值是否相等.

基本数据类型 : 变量直接存储的是值,数值相等,则返回true;

引用数据类型 : 变量存储的是其指向的对象在内存中存储的地址,并非指向的对象所存储的内容.

Ps:对于引用数据类型的比较时,String中存在字符串常量池,“使用常量池”对应的字节码是一个 ​

​ldc​

​ 指令,在给 String 类型的引用赋值的时候会先执行这个指令,看常量池中是否存在这个字符串对象的引用,若有就直接返回这个引用,若没有,就在堆里创建这个字符串对象并在字符串常量池中记录下这个引用.JVM 中除了字符串常量池,8种基本数据类型中除了两种浮点类型剩余的6种基本数据类型的包装类,都使用了缓冲池技术,但是 Byte、Short、Integer、Long、Character 这5种整型的包装类也只是在对应值在 [-128,127] 时才会使用缓冲池,超出此范围仍然会去创建新的对象。

所以,String变量会先从常量池中找,若没有则存储在常量池中,若有,则指向常量池中已存在的值,故:String str1="aa";String str2="aa";System.out.println(str1==str2);会输出true.

然而String str1=new String("111");String str2=new String("111");System.out.println(str1==str2);会输出false,原因是该出是new的两个不同的对象,开辟了两块空间,指向的内存地址值不同,故输出false.

Java中==与equals()方法的区别
来自黑马视频

equals :

equals()方法存在于Object类中,所有类继承自Object类,Object类中equals()比较的是两个变量指向的对象在内存中存储的地址是否相等.所以对于未重写equals方法的类,比较的都是对象在内存中存储的地址.

//Object类中equals方法源码

public boolean equals(Object obj) {

return (this == obj);

}

而对于String,Integer,Double,Long..等包装类,List,Set,Map类等等中的equals方法,都重写了equals方法,因为这些类都是Object的子类,根据多态的特征,这些类调用equals方法时是调用子类(即本身)的equals方法.这些类中的equals方法比较的是两个变量所指向的对象中存储的值是否相等.

//String类中的equals方法源码

public boolean equals(Object anObject) {

if (this == anObject) {

return true;

}

if (anObject instanceof String) {

String anotherString = (String)anObject;

int n = value.length;

if (n == anotherString.value.length) {

char v1[] = value;

char v2[] = anotherString.value;

int i = 0;

while (n-- != 0) {

if (v1[i] != v2[i])

return false;

i++;

}

return true;

}

}

return false;

}

在HashTable或HashMap类中,重写equals()方法时,一定要重写hashCode方法!

       如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相 同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如 果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希 表的操作。

  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。 

重写equals方法的建议

  1. 显示参数命名为otherObject, 稍后需要将他转换成另一个叫做other的变量.
  2. 检测this与otherObject是否引用同一个对象 : if (this == otherObject) return true;
  3. 检测otherObject是否为null, 如果为null, 返回false : if (otherObject == null) return false;
  4. 比较this与otherObject是否属于同一个类. 如果equals的语义在每个子类中有所改变,就使用getClass检测 : if (getClass() != otherObject.getClass()) return false; 如果所有的子类都拥有统一的语义,就使用instanceof检测 : if (!(otherObject instanceof ClassName)) return false;
  5. 将otherObject转换为相应的类类型变量 : ClassName other = (ClassName) otherObject;
  6. 现在开始对所有需要比较的与进行比较了. 使用 == 比较基本类型域, 使用 equals 比较对象与.如果所有的域都匹配, 就返回true;否则返回false.
@Override
    public boolean equals(Object otherObject) {
        if (this == otherObject) {
            return true;
        }

        if (otherObject == null) {
            return false;
        }

        if (getClass() != otherObject.getClass()) {
            return false;
        }

        Employee other = (Employee) otherObject;

        return Objects.equals(name, other.name) && salary == other.salary
                && Objects.equals(hireDay, other.hireDay);
    }      

 如果在子类中重新定义equals,就要在其中包含调用super.equals(other).

@Override
    public boolean equals(Object otherObject) {
        if (!super.equals(otherObject)) {
            return false;
        }

        Manager other = (Manager) otherObject;

        return bonus == other.bonus;
    }      

--摘自<Java核心技术卷一>

举例:

import java.util.ArrayList;
import java.util.List;

public class Main{
  public static void main(String[] args) {
    int a1 = 2;
    int a2 = 2;
    String str1 = "111";
    String str2 = "111";
    String str3 = str1;
    /*
     * ==的比较
     * 
     */
    System.out.println(a1 == a2);// 基本数据类型的比较 结果为:true
    System.out.println(str1 == str2);// 引用数据类型的比较 结果为:true 
                             // 原因:用到了常量池,创建的第一个对象存储在常量池中,
                                       // 第二个对象引用时会先从常量池中找看是否存在,存在则指向常量池中对象的内存地址
    System.out.println(str3 == str1);// 结果为:true

    String s1 = new String("111");
    String s2 = new String("111");
    System.out.println(s1 == s2);// 结果为:false 原因:两个变量引用的对象的地址值不同
    /*
     * Object中equal方法
     */
    Object obj1 = "abc";
    Object obj2 = "abc";
    System.out.println(obj1.equals(obj2));// 结果为:true 原因:与常量池道理类似
    Object ob1 = new Object();
    Object ob2 = new Object();
    System.out.println(ob1.equals(ob2));// 结果为:false 原因:两个变量引用的对象的地址值不同
    ob1 = "12";
    ob2 = "12";
    System.out.println(ob1.equals(ob2));// 结果为:true 原因:尽管开辟了两块地址空间,但又都指向了一个"12"的内存地址.

    Integer in1 = 1;
    Integer in2 = 1;
    System.out.println(in1 == in2);// 结果为:true 原因:存在缓冲区,缓冲大小为[-128,127]
    Integer inte1 = -129;
    Integer inte2 = -129;
    System.out.println(inte1 == inte2);//结果为:false 原因:超出了缓冲区的范围
    /*
     * 重写的equals方法
     */
    // 输出都为true.因为这些类都重写了equals方法,比较的是引用对象所存储的内容
    System.out.println(str1.equals(str2));
    Integer i = new Integer(2);
    Integer i1 = new Integer(2);
    System.out.println(i.equals(i1));
    List list1 = new ArrayList(1);
    List list2 = new ArrayList(1);
    System.out.println(list1.equals(list2));
  }
}      

String在内存中如何存放?

String str = new String( "abc" );
String str = "abc" ;      

第一种是用new()来新建对象的,它会在存放于堆中,每调用一次就会创建一个新的对象。 

第二种是先在栈中创建一个对String类的对象引用变量str,然后查找运行时常量池中有没有存放”abc”,如果没有,则将”abc”存放进常量池,并令str指 向”abc”,如果已经有”abc” 则直接令str指向”abc”。

常量池在方法区内,用来存放基本类型包装类(包装类不管理浮点型,整形只会管理-128到127)和String(通过String.intern()方法可以强制将String放入常量池)。

intern

public String intern()

返回字符串对象的规范表示。

最初为空的字符串池由​

​String​

​​类​

​String​

​ 。

当调用intern方法时,如果池已经包含与​​equals(Object)​​方法确定的相当于此​

​String​

​​对象的字符串,则返回来自池的字符串。 否则,此​

​String​

​​对象将添加到池中,并返回对此​

​String​

​对象的引用。

由此可见,对于任何两个字符串​

​s​

​​和​

​t​

​​ , ​

​s.intern() == t.intern()​

​​是​

​true​

​​当且仅当​

​s.equals(t)​

​​是​

​true​

​ 。

所有文字字符串和字符串值常量表达式都被实体化。 字符串文字在The Java™ Language Specification的 3.10.5节中定义。

结果

一个字符串与该字符串具有相同的内容,但保证来自一个唯一的字符串池。

[参考文章]

Kingram              : Java内存中的常量池

千古壹号             : Java语法----Java中equals和==的区别

Matrix海子          : 浅谈Java中的equals和==

​​安东尼_Anthony​​​ : ​​Java 重写Object类的常见方法-equals和hashCode​​