>  기사  >  Java  >  Java에서 ==, equals 및 hashCode 간의 차이점과 연결을 자세히 설명하는 샘플 코드

Java에서 ==, equals 및 hashCode 간의 차이점과 연결을 자세히 설명하는 샘플 코드

黄舟
黄舟원래의
2017-03-14 11:25:011352검색


1. 개요

1. 개념

2. 관계 연산자==

1. 피연산자 값

기본
    데이터 유형
  • 변수Java에는 8가지 기본 데이터 유형이 있습니다.

     

    부동 소수점 유형

    :

    float(4 byte), double(8 byte) 정수형: byte(1 byte), short(2 byte), int(4 byte), long(8 byte)

    문자형: char (2바이트)

    부울 유형: 부울(JVM 사양에서는 차지하는 공간을 명확하게 규정하지 않고 리터럴 값 "true" 및 "false"만 취할 수 있다고 규정함)

     

    이러한 8가지 기본 데이터 유형의 변수의 경우 변수는 "값"을 직접 저장합니다. 따라서 관계 연산자 ==를 사용하여 비교할 때 비교되는 것은 "값" 자체입니다.

    부동 소수점과 정수는 모두 부호 있는 유형입니다(가장 높은 비트는 양수와 음수를 나타내는 데만 사용되며 계산에 참여하지 않습니다. [바이트를 예로 들겠습니다.] , 범위는 -2^7 ~ 2^7 - 1, -0은 -128]) char은 unsigned 유형입니다(모든 비트가 계산에 포함되므로 char 유형의 값 범위는 0~2입니다). ^16-1) .


    참조
  • 유형 변수

    Java에서는 참조 유형의 변수가 저장되고 그렇지 않습니다. "값" 자체, 그러나 그와 연관된 객체의 메모리에 있는 주소
    . 예를 들어, 다음 코드 줄은

        String str1;

    입니다. 이 문장은 현재 어떤 개체와도 연결되지 않은 참조 유형 변수를 선언합니다.
  • 그리고
new

를 사용하여 객체를 생성하고 이 객체를 str1에 바인딩합니다.

str1= new String("hello");
그런 다음 str1은 이 객체를 가리키고 이때 는 변수 str1을 참조합니다. 저장된 위치는 "값" 자체가 아니라 메모리에서 가리키는 개체의 저장 주소입니다. 즉, 직접 저장된

문자열

"hello" 이 아닙니다. 여기의 참조는 C/C++의 포인터와 매우 유사합니다. 2. 요약


따라서 관계 연산자의 경우 ==:

피연산자의 유형이
  • 기본 데이터 유형

    인 경우 관계 연산자는 왼쪽 및 오른쪽 피연산자의 값 ​​이 동일한지 피연산자의 유형이

  • 참조 데이터 유형
  • 인 경우 관계 연산자는 왼쪽의 메모리 주소 를 결정합니다. 및 오른쪽 피연산자는 동일합니까? 즉, 이때 true가 반환된다면 연산자는 동일한 객체에 대해 작업 중이어야 합니다.

    3. 동등 메소드
1. 소스
동등 메소드는 기본 클래스 Object의 인스턴스 메소드입니다. 따라서 Object에서

을 상속받는 모든 클래스에는 이 메서드가 있습니다.

 
 객체 내 선언:

    public boolean equals(Object obj) {}

2. equals 메소드의 역할
 

원래 의도:

두 객체의 차이를 판단
내용은 동일한가요? Equals 메소드의 역할을 보다 직관적으로 이해하기 위해 먼저 Object 클래스 .

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

물론

Object 클래스에서는 두 객체의 참조가 동일한지, 즉 동일한 객체를 가리키는지 비교하기 위해 equals 메소드를 사용합니다.

  但我们都知道,下面代码输出为 true:

public class Main {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");

        System.out.println(str1.equals(str2));
    }
}

原来是 String 类重写了 equals 方法:

public boolean equals(Object anObject) {   // 方法签名与 Object类 中的一致
    if (this == anObject) {     // 先判断引用是否相同(是否为同一对象),
        return true;
    }    if (anObject instanceof String) {   // 再判断类型是否一致,
        // 最后判断内容是否一致.
        String anotherString = (String)anObject;        
        int n = count;        
        if (n == anotherString.count) {        
        char v1[] = value;        
        char v2[] = anotherString.value;        
        int i = offset;        
        int j = anotherString.offset;       
         while (n-- != 0) {            
         if (v1[i++] != v2[j++])            
         return false;
        }        return true;
        }
    }    return false;
}

即对于诸如“字符串比较时用的什么方法,内部实现如何?”之类问题的回答即为:

使用equals方法,内部实现分为三个步骤:

  • 比较引用是否相同(是否为同一对象),

  • 判断类型是否一致(是否为同一类型),

  • 最后 比较内容是否一致

Java 中所有内置的类的 equals 方法的实现步骤均是如此,特别是诸如 Integer,Double 等包装器类。


3、equals 重写原则

对象内容的比较才是设计equals()的真正目的,Java语言对equals()的要求如下,这些要求是重写该方法时必须遵循的:

  • 对称性: 如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true” ;

  • 自反性: x.equals(x)必须返回是“true” ;

  • 类推性: 如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true” ;

  • 一致性: 如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true” ;

  • 对称性: 如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

  • 任何情况下,x.equals(null)【应使用关系比较符 ==】,永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”


4、小结
 因此,对于 equals 方法:

  • 本意比较两个对象的 content 是否相同

  • 必要的时候,我们需要重写该方法,避免违背本意,且要遵循上述原则


四、hashCode 方法

1、hashCode 的来源
  hashCode 方法是基类Object中的 实例native方法,因此对所有继承于Object的类都会有该方法。
  
  在 Object类 中的声明(native方法暗示这些方法是有实现体的,但并不提供实现体,因为其实现体是由非java语言在外面实现的):

     public native int hashCode();

2、哈希相关概念
 我们首先来了解一下哈希表:

  • 概念 : Hash 就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出(int),该输出就是散列值。这种转换是一种  压缩映射,也就是说,散列值的空间通常远小于输入的空间。不同的输入可能会散列成相同的输出,从而不可能从散列值来唯一的确定输入值。简单的说,就是一种将任意长度的消息压缩到某一固定长度的消息摘要函数

  • 应用–数据结构 : 数组的特点是:寻址容易,插入和删除困难; 而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入和删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法——拉链法,我们可以理解为 “链表的数组”,如图:

             Java에서 ==, equals 및 hashCode 간의 차이점과 연결을 자세히 설명하는 샘플 코드
                               图1 哈希表示例

     왼쪽은 당연히 배열이고, 배열의 각 구성원은 연결 리스트입니다. 이 데이터 구조에 포함된 모든 요소에는 요소 간 연결을 위한 포인터가 포함되어 있습니다. 우리는 각각의 특성에 따라 요소를 서로 다른 연결 목록에 할당합니다. 또한 이러한 특성을 기반으로 올바른 연결 목록을 찾은 다음 연결 목록에서 요소를 찾습니다. 그 중 요소 특성을 기반으로 요소 배열 첨자를 계산하는 방식이 해싱(Hashing) 방식이다.

  • 지퍼 방식의 적용 범위: 빠른 검색 및 삭제를 위한 기본 데이터 구조로, 일반적으로 전체 데이터 양을 메모리에 담을 수 있습니다.

  • 핵심 사항:
    해시 함수 선택, 문자열, 정수, 순열, 특정 해당 해시 방법
    충돌 처리, 하나는 오픈 해싱입니다. 지퍼 방식이라고도 하며, 다른 하나는 폐쇄형 해싱(open addressing 방식)이라고도 합니다.


3. hashCode에 대한 간략한 설명
Java에서 Object 클래스에 의해 정의된 hashCode 메소드는 객체마다 다른 정수를 반환합니다. (이는 객체의 내부 주소를 정수로 변환하여 수행되지만 JavaTM 프로그래밍 언어 에서는 이러한 구현 트릭이 필요하지 않습니다.)

hashCode의 일반 계약은 다음과 같습니다.

  • Java 애플리케이션 실행 중 동일한 객체에 대한 다중 호출 hashCode 메소드는 객체를 같음과 비교하는 데 사용되는 정보가 수정되지 않은 경우 일관되게 동일한 정수를 반환해야 합니다. 이 정수는 애플리케이션의 한 실행에서 동일한 애플리케이션의 다른 실행까지 일관성을 가질 필요는 없습니다.

  • equals(Object) 메서드에 따라 두 개체가 동일한 경우 두 개체 각각에 대해 hashCode 메서드를 호출하면 동일한 정수 결과가 생성되어야 합니다.

  • equals(java.lang.Object) 메소드에 따라 두 객체가 동일하지 않으면 두 객체 중 하나에서 hashCode를 호출합니다. 🎜> 이 다른 정수 결과를 생성하도록 요구하지 않습니다. 그러나 프로그래머는 동일하지 않은 개체에 대해 서로 다른 정수 결과를 생성하면 해시 테이블 성능이 향상될 수 있다는 점을 알고 있어야 합니다.  
    hashCode의 역할을 더 잘 이해하려면 먼저 Java의 컨테이너를 이해해야 합니다.
    HashCode는 HashSet, HashMap과 같은 해싱 알고리즘이 필요한 데이터 구조에서만 유용하기 때문입니다 및 해시테이블.

    Java에는 세 가지 유형의 컬렉션이 있는데 하나는 List, 다른 하나는 Queue, 세 번째는 Set입니다. 처음 두 세트의 요소는 순서가 지정되어 있으며 요소는 반복될 수 있습니다. 마지막 세트의 요소는 순서가 없지만 요소는 반복될 수 없습니다.

    그렇다면 여기에 더 심각한 문제가 있습니다. 요소가 반복되지 않도록 하려면 두 요소의 반복 여부를 판단하는 기준은 무엇이어야 합니까? 이것이

    Object.equals 메서드입니다. 하지만 요소가 추가될 때마다 한 번씩 확인하게 되면 요소가 많아지면 컬렉션에 추가된 요소들의 비교 횟수가 매우 많아지게 됩니다. 즉, 컬렉션에 1000개의 요소가 있는 경우 첫 번째 001 요소가 컬렉션에 추가되면 equals 메서드가 1000번 호출됩니다. 이는 분명히 효율성을 크게 떨어뜨릴 것입니다. 그래서 자바는 해시 테이블의 원리를 채택했습니다. 이러한 방식으로 해시 알고리즘을 사용하여 컬렉션에 저장될 각 요소의 값을 계산한 다음 이 값을 기반으로 배열에서 요소의 위치를 ​​계산합니다. 따라서 컬렉션에 새 요소가 추가되면 두 단계로 나눌 수 있습니다.

  • 먼저 이 요소의 hashCode 메서드를 호출한 다음 결과 값은 배열에서 요소가 있어야 하는 위치를 계산합니다. 이 위치에 요소가 없으면

  • 이 위치에 이미 요소가 있는 경우; , 그런 다음 해당 요소의 equals 메소드를 호출하여 새 요소와 비교합니다. 동일하면 저장되지 않습니다. 그렇지 않으면 이 위치에 해당하는 연결 목록에 저장됩니다(Java의 HashSet, HashMap 및 Hashtable 구현). 항상 연결리스트의 선두에 요소를 배치합니다).

4. 동등 및 해시 코드

전제 조건:

해시 코드에 관해서는 이야기해야 합니다. equals 메소드에 대해서는 둘 다 Object 클래스의 메소드입니다. Object 클래스는 모든 클래스의 기본 클래스이므로 이 두 메서드는 모든 클래스에서 재정의될 수 있습니다.

  • 원칙 1:

    x.equals(y)가 "true"를 반환하는 경우 x와 y의 hashCode()는 동일해야 합니다.

  • 원칙 2:

    x.equals(y)가 "false"를 반환하는 경우 x와 y의 hashCode()는 같거나 다를 수 있습니다.

  • 原则 3 : 如果 x 和 y 的 hashCode() 不相等,那么 x.equals(y) 一定返回 “false” ;

  • 原则 4 : 一般来讲,equals 这个方法是给用户调用的,而 hashcode 方法一般用户不会去调用 ;

  • 原则 5 : 当一个对象类型作为集合对象的元素时,那么这个对象应该拥有自己的equals()和hashCode()设计,而且要遵守前面所说的几个原则。


5、实现例证

 hashCode()在object类中定义如下:

public native int hashCode();

 说明是一个本地方法,它的实现是根据本地机器相关的。

 String 类是这样重写它的:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence{
    /** The value is used for character storage. */
    private final char value[];     //成员变量1

    /** The offset is the first index of the storage that is used. */
    private final int offset;      //成员变量2

    /** The count is the number of characters in the String. */
    private final int count;       //成员变量3

   /** Cache the hash code for the string */
    private int hash; // Default to 0    //非成员变量

    public int hashCode() {    int h = hash;        int len = count;         //用到成员变量3
    if (h == 0 && len > 0) {        int off = offset;         //用到成员变量2
        char val[] = value;       //用到成员变量1
            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];       //递推公式
            }
            hash = h;
        }        return h;
    }
}

对程序的解释:h =  s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],由此可以看出,对象的hash地址不一定是实际的内存地址。


五、小结

  • hashcode是系统用来快速检索对象而使用

  • equals方法本意是用来判断引用的对象是否一致

  • 重写equals方法和hashcode方法时,equals方法中用到的成员变量也必定会在hashcode方法中用到,只不过前者作为比较项,后者作为生成摘要的信息项,本质上所用到的数据是一样的,从而保证二者的一致性


위 내용은 Java에서 ==, equals 및 hashCode 간의 차이점과 연결을 자세히 설명하는 샘플 코드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.