preface

Recently, I am learning Go language. In Go language, there are pointer objects, a pointer variable points to the memory address of a value. Ape friends who have learned C language should know the concept of Pointers. Go language syntax is similar to C, can be said to be a C-like programming language, so Go language Pointers are also very normal. We can get the memory address of a variable by using the fetch address character & in front of it.

 1package main

2

3import "fmt"

4

5func main(a) {

6   var a int20   /* Declare the actual variable */

7   var ip *int        /* Declare a pointer variable */

8

9   ip = &a  /* The storage address of the pointer variable */

10   fmt.Printf("The address of variable A is: %x\n", &a  )

11

12   /* The storage address of the pointer variable */

13   fmt.Printf("IP variable stored pointer address: %x\n", ip )

14   /* Use Pointers to access values */

15   fmt.Printf("* IP variable value: %d\n", *ip )

16}

Copy the code

Because my main development language is Java, so I think there is no pointer in Java, so how to obtain the memory address of a variable in Java?

If you can get the memory address of the variable, you can clearly know whether the two objects are the same object. If the memory address of the two objects is equal, it is definitely the same object, and vice versa.

Many people say that the HashCode method of an object returns the memory address of the object, including I found in Chapter 5 of Java Core Programming, Volume I that the value of HashCode is the memory address of the object.

But is the HashCode method really a memory address? Before we answer that question, let’s review some basics.

= = and equals

The main way to compare two objects for equality in Java is by the == sign, which compares their memory location. Object is a Java superclass that all classes inherit by default. If a class does not override Object’s equals method, it can determine whether two objects are the same via equals because it is internally implemented via ==.

1//Indicates whether some other object is "equal to" this one.

2public boolean equals(Object obj) {

3    return (this == obj);

4}

Copy the code

Tips: Here’s an additional puzzle to explain

When we learn Java, we know that Java inheritance is single inheritance, if all classes inherit the Object class, then why can create a class extend other classes?

This involves direct inheritance and indirect inheritance. When A class is created that does not extend the specified class through the keyword extend, the class directly inherits Object by default, A -> Object. When A class is created that extends the specified class through the keyword extend, it indirectly inherits the Object class, A –> B –> Object.

By “same”, we mean whether the two objects are the same, that is, whether their addresses in memory are equal. Sometimes we need to compare two objects to see if they have the same content, that is, classes have their own unique concept of “logical equality,” rather than whether they refer to the same object.

For example, if a = “Hello” and b = new String(“Hello”) are the same, then a and B are the same. Or are they equal? How do you distinguish this?

Using == compares whether they are the same Object in memory. However, the default parent of String objects is also Object, so the default equals method also compares the memory address, so we need to override equals as written in the String source code.

 1public boolean equals(Object anObject) {

2    if (this == anObject) {

3        return true;

4    }

5    if (anObject instanceof String) {

6        String anotherString = (String)anObject;

7        int n = value.length;

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

9            char v1[] = value;

10            char v2[] = anotherString.value;

11            int i = 0;

12            while(n-- ! =0) {

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

14                    return false;

15                i++;

16            }

17            return true;

18        }

19    }

20    return false;

21}

Copy the code

In this way, when a == B, we judge whether a and B are the same object, and a.equals(b) compares whether the contents of a and B are the same, which should be easy to understand.

Not only does the String class override equals in the JDK, but the Integer, Long, Double, Float, and other data types also override equals. So when we use Long or Integer as business parameters in our code, if we want to compare them for equality, remember to use equals instead of ==.

Because of the unexpected pitfalls of using the == sign, many of these data types encapsulate a constant pool internally, such as IntegerCache, LongCache, etc. Data values within a range are fetched directly from the constant pool without creating new objects.

If you want to use ==, you can convert these data wrapper types to base types and use == to compare values. However, you need to pay attention to the occurrence of NullPointException (NPE) during the conversion process.

The Object of HashCode

Equals method to compare the content of the two objects are equal, so whether can be used to locate an object in a collection container, usually is roughly one by one, to get each object of a set of element and need query object equals comparison, find an element equals method compared with the objects to find the result of the equal, Stop the search and return positive; otherwise, return negative.

However, this comparison method is inefficient and time complexity is high. Then, can we use some coding method to group each object with a specific code value and divide the objects into different areas according to the code value, so that when we need to query an object in the set, we can first determine which area the object is stored according to the code value of the object. Then go to the field and compare the contents to equals to see if the object is in the collection.

In this way, we reduce the number of query comparisons, optimize the efficiency of the query and reduce the query time.

This encoding is known in Java as the hashCode method, which is defined by default in the Object class. It is a native modified local method that returns an int.

 1/ * *

2 * Returns a hash code value for the object. This method is

3 * supported for the benefit of hash tables such as those provided by

4 * {@link java.util.HashMap}.

5*...

6 * As much as is reasonably practical, the hashCode method defined by

7 * class {@code Object} does return distinct integers for distinct

8 * objects. (This is typically implemented by converting the internal

9 * address of the object into an integer, but this implementation

10 * technique is not required by the

11 * Java™ programming language.)

12 *

13 * @return  a hash code value for this object.

14 * @see     java.lang.Object#equals(java.lang.Object)

15 * @see     java.lang.System#identityHashCode

16* /


17public native int hashCode(a);

Copy the code

As you can see from the description in the annotation, the hashCode method returns the hash value of the object. It can be beneficial for hash tables like HashMap. The hashCode methods defined in the Object class return different integer values for different objects. The task is to implement by converting the internal address of the object into an integer. This is typically done by converting the internal address of an object to an integer value.

If you don’t dig a little deeper you might think it’s just the memory address of the object, and we can go ahead and look at the implementation, but since this is a native method we can’t directly see how it’s implemented internally here. Native method itself is not Java implementation, if you want to see the source code, only download the complete JDK source code, Oracle JDK is not seen, OpenJDK or other open source JRE can find the corresponding C/C++ code. We find the object.c file in the OpenJDK and see that the hashCode method points to the JVM_IHashCode method for processing.

1static JNINativeMethod methods[] = {

2    {"hashCode"."()I",                    (void *)&JVM_IHashCode},

3    {"wait"."(J)V",                   (void *)&JVM_MonitorWait},

4    {"notify"."()V",                    (void *)&JVM_MonitorNotify},

5    {"notifyAll"."()V",                    (void *)&JVM_MonitorNotifyAll},

6    {"clone"."()Ljava/lang/Object;",   (void *)&JVM_Clone},

7};

Copy the code

The JVM_IHashCode method implementation in jVM.cpp is defined as:

1JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))  

2  JVMWrapper("JVM_IHashCode");  

3  // as implemented in the classic virtual machine; return 0 if object is NULL

4  return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;  

5JVM_END 

Copy the code

Here is a ternary expression, really get hashCode values is calculated by ObjectSynchronizer: : FastHashCode, its specific implementation in synchronizer. The CPP, capture some key code snippet.

 1intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {

2  if (UseBiasedLocking) {

3

4.

5

6  // Inflate the monitor to set hash code

7  monitor = ObjectSynchronizer::inflate(Self, obj);

8  // Load displaced header and check it has hash code

9  mark = monitor->header();

10  assert (mark->is_neutral(), "invariant");

11  hash = mark->hash();

12  if (hash == 0) {

13    hash = get_next_hash(Self, obj);

14    temp = mark->copy_set_hash(hash); // merge hash code into header

15    assert (temp->is_neutral(), "invariant");

16    test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);

17    if(test ! = mark) {

18      // The only update to the header in the monitor (outside GC)

19      // is install the hash code. If someone add new usage of

20      // displaced header, please update this code

21      hash = test->hash();

22      assert (test->is_neutral(), "invariant");

23assert (hash ! =0."Trivial unexpected object/monitor header usage.");

24    }

25  }

26  // We finally get the hash

27  return hash;

28}

Copy the code

As you can see from the code snippet above, it is get_next_hash that actually computes hashCode, and in this file we search get_next_hash to get its key code.

 1static inline intptr_t get_next_hash(Thread * Self, oop obj) {

2  intptr_t value = 0 ;

3  if (hashCode == 0) {

4     // This form uses an unguarded global Park-Miller RNG,

5     // so it's possible for two threads to race and generate the same RNG.

6     // On MP system we'll have lots of RW access to a global, so the

7     // mechanism induces lots of coherency traffic.

8     value = os::random() ;

9  } else

10  if (hashCode == 1) {

11     // This variation has the property of being stable (idempotent)

12     // between STW operations. This can be useful in some of the 1-0

13     // synchronization schemes.

14     intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;

15     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;

16  } else

17  if (hashCode == 2) {

18     value = 1 ;            // for sensitivity testing

19  } else

20  if (hashCode == 3) {

21     value = ++GVars.hcSequence ;

22  } else

23  if (hashCode == 4) {

24     value = cast_from_oop<intptr_t>(obj) ;

25  } else {

26     // Marsaglia's xor-shift scheme with thread-specific state

27     // This is probably the best overall implementation -- we'll

28     // likely make this the default in future releases.

29     unsigned t = Self->_hashStateX ;

30     t ^= (t << 11);

31     Self->_hashStateX = Self->_hashStateY ;

32     Self->_hashStateY = Self->_hashStateZ ;

33     Self->_hashStateZ = Self->_hashStateW ;

34     unsigned v = Self->_hashStateW ;

35     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));

36     Self->_hashStateW = v ;

37     value = v ;

38  }

39

40  value &= markOopDesc::hash_mask;

41  if (value == 0) value = 0xBAD ;

42assert (value ! = markOopDesc::no_hash,"invariant");

43  TEVENT (hashCode: GENERATE) ;

44  return value;

45}

Copy the code

The get_next_hash method provides six hash methods for calculating the hash value starting from 0, including auto-increment sequence, random number, and memory address association. The official default is the last one, which is random number generation. As you can see, hashCode may be associated with memory addresses, but it does not directly represent memory addresses, depending on the vm version and Settings.

The equals and hashCode

Equals and hashCode are methods owned by the Object class, including the toString method in the Object class that prints hashCode’s unsigned hexadecimal value.

1public String toString(a) {

2    return getClass().getName() + "@" + Integer.toHexString(hashCode());

3}

Copy the code

We usually override equals because we need to compare object content, but overwriting equals also requires overwriting hashCode. Ever wonder why?

Because failing to do so would violate the general convention of hashCode, making it impossible for the class to work properly with all hash-based collections, such as HashMap and HashSet.

The general convention here, as can be seen from the annotation of the hashCode method of the Object class, mainly includes the following aspects:

  • The hashCode method must always return the same value for multiple calls to the same object during the execution of the application, as long as the information used for the equals method comparison of the object has not been modified.

  • If two objects are equal according to the equals method, then calling the hashCode method on both objects must produce the same integer result.

  • If two objects are not equal according to the equals method, then calling the hashCode method in the two objects does not necessarily require that the hashCode method produce different results. But it is possible to improve the performance of a hash table by producing different integer hashes for objects that are not equal.

In theory, overwriting equals without overwriting hashCode violates the second rule of the convention, which states that equal objects must have equal hash values.

But rules are tacit conventions. What happens if we override hashCode after overriding equals instead of going the other way?

So if we call hashCode of Student, by default we call hashCode of the superclass Object. An integer value returned based on a random number.

 1public class Student {

2

3    private String name;

4

5    private String gender;

6

7    public Student(String name, String gender) {

8        this.name = name;

9        this.gender = gender;

10    }

11

12    // omit Setter, Gettter

13

14    @Override

15    public boolean equals(Object anObject) {

16        if (this == anObject) {

17            return true;

18        }

19        if (anObject instanceof Student) {

20            Student anotherStudent = (Student) anObject;

21

22            if (this.getName() == anotherStudent.getName()

23                    || this.getGender() == anotherStudent.getGender())

24                return true;

25        }

26        return false;

27    }

28}

Copy the code

Create two objects and set their properties to the same value. Test the result:

1public static void main(String[] args) {

2

3    Student student1 = new Student("Xiao Ming"."male");

4    Student student2 = new Student("Xiao Ming"."male");

5

6    System.out.println(Equals result: + student1.equals(student2));

7    System.out.println("Hash value of object 1:" + student1.hashCode() + ", hash value of object 2: + student2.hashCode());

8}

Copy the code

The results obtained

1Equals the result:true

2object1Hash value of:1058025095Object,2Hash value of:665576141

Copy the code

We overrode equals to determine whether the contents of objects are equal based on the name and gender attributes, but hashCode, which calls the hashCode method of the Object class, prints two different integer values.

If we store this object as a HashMap, and use the object as a key, and those of you who know how HashMap works know that HashMap is made up of an array plus a linked list, the result is that because they have different Hashcodes, they are placed at different indices of the array. When we query by Key, the result is null.

 1public static void main(String[] args) {

2

3    Student student1 = new Student("Xiao Ming"."male");

4    Student student2 = new Student("Xiao Ming"."male");

5

6    HashMap<Student, String> hashMap = new HashMap<>();

7    hashMap.put(student1, "Xiao Ming");

8

9    String value = hashMap.get(student2);

10    System.out.println(value); 

11}

Copy the code

The output

1null

Copy the code

Student1 and student2 have different memory addresses, but they have the same logical content, so we think they should be the same.

If this is a bit of a puzzle, you can replace Student with String, which is the Key we use for HashMap. Imagine if String overrode equals instead of HashCode. A new String(“s”) is used as a Key and a value is put, but a new String(“s”) is used to Get a null value, which is unacceptable.

So keep in mind that we always overwrite hashCode when overwriting equals, both theoretically and programmatically.

The hashCode method has been overwritten, but if we want to get the hashCode from the original Object class, we can get it through system.identityhashcode (Object a), This method returns the default value of the Object’s hashCode method, even if the Object’s hashCode method has been overridden.

1public static native int identityHashCode(Object x);

Copy the code

conclusion

If HashCode is not a memory address, how do you get a memory address in Java? I looked around and found no direct way to do it.

In retrospect, perhaps the Java language writers didn’t think it was necessary to get the memory address directly, because Java is a high-level language that is more abstract and hides complexity than machine language assembly or C, since it is, after all, further encapsulated in C and C++. Also, because of automatic garbage collection and object age issues, the addresses of objects in Java change, so getting the actual memory address doesn’t make much sense.

Of course, the above is the blogger’s own point of view, if ape friends have other different opinions or opinions can also leave a message, we discuss together.


Individual public account: small dish also cow

Welcome to long press the picture below to pay attention to the public number: small dishes also cattle!

We regularly provide you with the explanation and analysis of distributed, micro-services and other first-line Internet companies.