참고: 이 문서는 Clojure 1.12.0-alpha1openjdk-jdk11을 기준으로 합니다.

hasheq

Clojure는 Java로 만들어졌기 때문에 [[/clojure/study/hashmap]] 등을 구성할 때 [[/java/object-hashcode]]를 사용해 해시값을 계산할 것으로 생각할 수 있다. 그러나 실제로는 그렇지 않다.

Clojure는 해시값을 계산할 때 이 글의 주제인 hasheq Java 메소드를 사용한다.

clojure.lang.Util:hasheq

public static int hasheq(Object o) {
    if(o == null)
        return 0;
    if(o instanceof IHashEq)
        return dohasheq((IHashEq) o);
    if(o instanceof Number)
        return Numbers.hasheq((Number)o);
    if(o instanceof String)
        return Murmur3.hashInt(o.hashCode());
    return o.hashCode();
}

null

public static int hasheq(Object o) {
    if(o == null)
        return 0;

nullhasheq 값은 0 이다.

IHashEq

public static int hasheq(Object o) {
    // 생략
    if(o instanceof IHashEq)
        return dohasheq((IHashEq) o);

clojure.lang.IHashEq

public interface IHashEq {
    int hasheq();
}

clojure.lang.Util:dohasheq

private static int dohasheq(IHashEq o) {
    return o.hasheq();
}

IHashEq 인터페이스의 구현체는 hasheq 메소드를 호출해서 해시값을 구한다.

Number

Long

clojure.lang.Numbers:hasheq

static int hasheq(Number x){
    Class xc = x.getClass();

    if(xc == Long.class) {
        long lpart = x.longValue();
        return Murmur3.hashLong(lpart);
        //return (int) (lpart ^ (lpart >>> 32));
    }
    if(xc == Double.class) {
        if(x.equals(-0.0))
            return 0;  // match 0.0
        return x.hashCode();
    }
    return hasheqFrom(x, xc);
}

java.lang.Long 타입의 해시값은 Murmur3.hashLong 메소드를 사용해서 구한다.

clojure.lang.Murmur3:hashLong

public static int hashLong(long input){
    if(input == 0) return 0;
    int low = (int) input;
    int high = (int) (input >>> 32);

    int k1 = mixK1(low);
    int h1 = mixH1(seed, k1);

    k1 = mixK1(high);
    h1 = mixH1(h1, k1);

    return fmix(h1, 8);
}

Double, Float

java.lang.Double 타입의 해시값은 java.lang.NumberhashCode 메소드를 사용해서 구한다.

java.lang.NumberhashCode는 따로 오버라이드 하지 않았으므로 java.lang.ObjecthashCode를 호출하는 것과 같다.

java.lang.Object::hashCode

public native int hashCode();

한편, java.lang.Float 타입도 java.lang.NumberhashCode를 사용한다.

Integer, Short, Byte, BigInteger

이 타입들의 수가 long 타입의 범위 내에 있다면 long으로 변환된 다음, Murmer3.hashLong을 통해 해시값을 구하게 된다.

clojure.lang.Numbers::hasheqFrom

static int hasheqFrom(Number x, Class xc){
    if(xc == Integer.class
            || xc == Short.class
            || xc == Byte.class
            || (xc == BigInteger.class && lte(x, Long.MAX_VALUE) && gte(x,Long.MIN_VALUE))) {
        long lpart = x.longValue();
        return Murmur3.hashLong(lpart);
        //return (int) (lpart ^ (lpart >>> 32));
    }

long 타입의 범위를 넘어선다면 java.lang.ObjecthashCode를 사용해서 해시값을 구한다.

BigDecimal

마지막에 붙은 소수점 이하의 무의미한 0을 제거하고, java.math.BigDecimalhashCode를 사용해서 해시값을 구한다.

clojure.lang.Numbers::hasheqFrom

if(xc == BigDecimal.class) {
    // stripTrailingZeros() to make all numerically equal
    // BigDecimal values come out the same before calling
    // hashCode.  Special check for 0 because
    // stripTrailingZeros() does not do anything to values
    // equal to 0 with different scales.
    if (isZero(x))
        return BigDecimal.ZERO.hashCode();
    else {
        BigDecimal tmp = ((BigDecimal) x).stripTrailingZeros();
        return tmp.hashCode();
    }
}

java.math.BigDecimal::hashCode

public int hashCode() {
    if (intCompact != INFLATED) {
        long val2 = (intCompact < 0)? -intCompact : intCompact;
        int temp = (int)( ((int)(val2 >>> 32)) * 31  +
                          (val2 & LONG_MASK));
        return 31*((intCompact < 0) ?-temp:temp) + scale;
    } else
        return 31*intVal.hashCode() + scale;
}

String

HashMap

HashSet

List

함께 읽기

  • [[/clojure/study/hashmap]]
  • [[/java/object-hashcode]]