1. Introduction
The judgment statement “==” is probably one of the most common mistakes that beginners to Java can make due to a lack of understanding. I made this mistake in my code not so long ago. I have seen the comparison between == and equals() method before, but I forgot it when I was writing code. In the debug and research process of this problem, I found that there were many other problems associated with it. Even a small == judgment is worth digging deeper.
2. Problems occur
The situation where the problem occurs is very common. The author tries to use JSON object as the content of HTTP transmission, uses the JSONObject class of Alibaba, aspoints part of the attributes of a custom object to the JSONObject, and then expands different operations by determining the attribute values of the transmitted JSON object. So I wrote the following code.
JSONObject jsonObject = new JSONObject();
jsonObject.put("command", messageReceive.getCommmand());
if(jsonObject.get("command") == "TERM"){
doSomething();
}
JsonObject.get (“command”)==”TERM” line is always false, and I print the value of jsonObject.get(“command”) is actually a “TERM” string. So what’s the problem?
3. Problem troubleshooting
3.1 hashCode and identityHashCode
Since I had completely forgotten the difference between == and equals(), my first check was to create a new test class in the test folder and simply repeat the process as follows:
JSONObject jsonObject = new JSONObject(); jsonObject.put("command", "TERM"); console.log(josnObject.put("command") == "TERM"); // Prints to true! if(jsonObject.get("command") == "TERM"){ doSomething(); }
However, the test result turned out to be true, indicating that the result of jsonObject.put(“command”) in the above code was indeed TERM, which made me think that I had found the root cause of the problem, that there must be something wrong with my custom class. But why is it that when I use println, the output is actually the target string? Just as I was worrying about it, I was somehow reminded of the hashCode property, and of the “==” sign, which, for non-basic types, actually seems to be the storage address of the two. Right? And the address seems to be based on the hashCode property, right? I quickly hashCode all of the stuff in my Test and Source folders, and the results are amazing. All of the hashcodes are the same! Something must be wrong! Sure enough, with the help of Baidu, I learned a more advanced method — System. IdentityHashCode (String S). For the String class, it overrides the String hashCode () method to return a value determined only by the content of the String. The System.IdentityHashCode () method returns the value of the HashCode based on the address of the object stored before the override! I also learned that the new keyword declaration would produce different reference addresses, so I decided to take a closer look at my new knowledge in the test folder, and I got the following result.
TERM ============================================ s: String s = new String("TERM"); s2: String s2 = "TERM"; s3: String s3 = "TERM"; s4: String s4 = new String("TERM"); s5: String s5 = jsonObject.get("command"); = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = s identity - hashcode: identity of s2-1878246837 hashcode: 929338653 S3 identity-hashCode: 929338653 S4 identity-hashCode: 1259475182 S5 identity-hashCode: 929338653, 929338653: "the TERM" identity - hashcode = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = s hashcode: 2571372 S2 hashCode: 2571372 S3 hashCode: 2571372 S4 hashCode: 2571372 S5 hashCode: 2571372 Term hashCode: 2571372 ============================================= s==s4: false s2==s3: true s==s5: false s2==s5: true s==s2: false =============================================
As shown in the above code results, S ~ S5 was defined by different definitions, and then I listed their identiter-hashCode and hashCode, and compared them with different strings. The results were very consistent with the explanation I looked up!
- The hashCode value of the “TERM” string object obtained by all means is the same!
- S and S4 declared by the new keyword have completely different identity-hashCode even though they have the same content, which means that each new keyword declaration generates a new reference address.
- Directly defined strings s2 and s3 share the same identity-hashCode,
- JsonObject.get (“command”) is the same identity-hashCode as the s2 and s3 defined directly.
This last finding is not difficult to explain, because the JSONObject command attribute is defined by the put method using the String TERM, which is the same as the String declaration, even if it is added to the object as a property. Nor has the object’s identity-hashCode value been changed. A JSONObject is a reference to a String that is placed in a JSONObject.
So, my Source code error should be…
//MessageRecive.java
...
this.command = new String(subBytes(startPos, endPos));
Sure enough, in my custom class, the value of the command property is declared using new String(), because it needs to be converted in byte[]. The new String() declaration yields a different identity-hashCode value, which represents the location of the object, and the == comparison is made by determining identity-hashCode, not by comparing the contents of the String.
This doesn’t end there, of course, because we still have a key question: what is the difference between String STR = “value” and String STR = new String(“value”)? ?
3.2 Java memory shallow understanding
String STR = “value” and String STR = new String(“value”). String STR = “value” and String STR = new String(“value”). String STR = “value”. For a Java beginner, too advanced low-level knowledge is currently unable to understand, directly from the source code analysis is also temporarily beyond my ability, even the official document related instructions also do not understand, can only learn from the online interpretation of God and their own understanding of the method slightly simplified.
3.2.1 Heap, Stack, and Method Areas
Java’s memory is divided into main heap, stack, and method areas. The contents and functions of each part are as follows:
- Stack: The stack is used to store basic types of data and variables. Threads are private, so variables from different threads cannot be shared. The stack consists of a stack frame. A call to a method produces a stack frame. The contents of the stack frame include a local variable area (which holds local variables and parameters) and an operand stack (the operand platform for the method, in which the relevant variables are pushed in and out of the operand stack while the method is running). Note: Member variables of a primitive type are stored in the corresponding object in the heap, not on the stack.
- Heap: Used to hold instances of objects and arrays, shared globally.
- Method area: Static storage area, containing information about classes, constants, static variables. An important part of the method area is the constant pool. Constant pools can be divided into global string constant pools, class static constant pools, and run-time constant pools.
-
Constant pool:
- Class static constant pool. Each class has a private constant pool that is generated at compile time. This constant pool contains compile-time literals and symbolic references. Literals are mainly strings defined by double quotes (and also constants declared final). At compile time, the JVM has a compilation optimization process for String literals, that is, for String STR = “stu” + “de” + “nt”; The JVM will automatically concatenate it into a complete “Student” object and store it in the constant pool at compile time, instead of storing three string fragments. For concatenation that contains variables, however, the JVM cannot make such optimizations.
- Global string constant pool. Since string is the most used object in the program, the creation of object instance is a very time-consuming and space-consuming process. In order to improve program efficiency and take advantage of the immutability of strings, Java specialises in the global string constant pool. String objects that are identifiable at class load time (usually from the class static constant pool) are added to the global string constant pool. Since JDK 1.8 and later, the global string constant pool exists as a hash table. Instead of storing a concrete string instance, the constant pool stores a reference to the corresponding string object, which is stored in the heap. In general, the global String constant pool is updated only when the class is loaded. In particular, you can manually add a String to the String constant pool after the class is loaded via the String’s intern() method (if the String already exists, it is not added but the reference address in the current constant pool is returned). The string constant pool is obviously globally shared.It is said that the global string is not in the method area, which is not well understood, but its location does not affect the overall flow of the function.
- Runtime constant pool. When a program is run, the class static constant pool also imports most of its contents into the runtime constant pool during class loading, because the runtime constant pool is also class-private. For string constants, the constant pool in the imported runtime will query string constants global pool first, if the string constants, is already available in the pool is the constant pool directly references in replacement for runtime constant pool, if not, then create the string object in the heap, and add it to the global string constant pool, at the same time return the reference to itself. It is important to note that class loading is lazy, that is, whether each string will enter the runtime or whether the global string constant value is checked and added to the corresponding constant value (by executing the LDC command) only when the corresponding statement needs to be executed. Subsequent programs that generate new strings during execution (such as by concatenation or breaking) will only be stored in the run-time constant pool and will not be stored in the global string constant pool.
Given the above description, you can summarize the way Java’s stack and string definitions work as shown in the figure below.
The above process can be briefly described as follows:
- The literals generated by the program at compile time include “ABC “,” abcd”(compile-time optimization),” ef”,”gh”; (S6 concatenation contains new object, cannot compile optimization);
- When the program runs, load the class where this program is located, String s1 = “ABC”, create a variable s1 in the stack, at this time there is no content in the global String constant pool, create an instance of “ABC” in the heap, and add this instance address to the global String constant pool “ABC”, “ABC” in the run-time constant pool points directly to the address corresponding to “ABC” in the global string constant pool;
- String s2 = “ABC”, create a variable s2 in the stack, this is defined in the global String constant pool, directly reuse, pass the “ABC” address in the String constant pool to the variable s2;
- String s3 = new String(” ABC “); String s3 = new String(” ABC “); String s3 = new String(” ABC “); String s3 = new String(” ABC “); The literal “ABC” already exists in the global variable string. Copy it directly to the storage space pointed to by s3 in the heap. ;
- String s4 = new String(” ABC “), String s4 = new String(” ABC “), String s4 = new String(” ABC “), String s4 = new String(” ABC “), String s4 = new String(” ABC “), String s4 = new String(” ABC “).
- String s5 = “ab” + “CD”; String s5 = “abcd”; String s5 = “ab” + “CD”;
- String s6 = “ef” + new String(“gh”), String s6 = “ef” + new String(“gh”), String s6 = “ef” + new String(“gh”), String s6 = “ef” + new String(“gh”), String s6 = “ef” + new String(“gh”), String s6 = “ef” + “gh” The resulting s6 result is “efgh”, creating a new space in the heap to store the string. Note that “efgh” is not added to the run-time and global string constant pools.
- If the literal “efgh” of s6 is not in the global string constant pool, create the constant in the global string constant pool and point to the address of the current object.
3.2.2 String literal definitions and object definitions
There are two ways to define a string, the literal definition using the double quotation mark “value”, and the object definition using the new keyword. The two ways to define a string are clear from the analysis in the previous section, but I’ll highlight the process here.
- Literal definition, String s = “value”, “value” already exists in the class constant pool at compile time; If the String “value” is not in the current global String constant pool, create an instance of” value” in the heap and add the instance address to the global String constant pool and runtime constant pool to generate a String instance.
- Object definition, String s = new String(“value”), “value” exists in class constant pool at compile time; When you run this sentence, you first create an object in the heap using the new keyword to hold the string object S, then check the “value” literal, and find that the string” value” is not in the current global string constant pool, then create the “value” literal instance in the heap, then create the” value” literal instance in the heap. Add the instance address to the global String constant pool and the run-time constant pool, and then copy the contents of the corresponding “value” object from the constant pool into the String object S created with the new keyword, thus creating two instances of String. The question here is whether the contents of the String instance s are copied directly from the constant pool or are also referenced to the String instance corresponding to the literal “value”.
3.3 == and equals() methods
From our analysis in Section 3.2, we can see that String objects instantiated by new String() all point to different addresses in the heap. The result of comparison is whether 2 points to the same object. The equals() method is the same as the == sign for the Object superclass, but overwrites the equals() method in several basic types of wrapper and String classes, as follows:
Public Boolean equls(Object obj){return (this == obj); 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; }
The String class overrides methods that compare two strings with exactly the same value.
The problem seemed to be solved at this point, until it suddenly occurred to me that I had tried to receive the result returned by the jsonObject.get() method during my test with the following code, but the code gave an error:
String command = jsonObject.get("command"); //Reqired is String but Provided is Object //jsonObject.get(); public Object get(Object key){ Object val = this.map.get(ket); if(val == null && key instanceof Number){ val = this.map.get(key.toString()); }} // The equals method executed by this statement should not be the equals method of Object. JsonObject. Get (" command ".) the equals (" TERM "); String equals = String equals = String equals = String equals = String equals = String equals = String equals = String equals = String equals = String equals = String equals = String equals = String equals = String equals
Indeed, the jsonObject.get() method returns an Object type, because by its very nature it allows you to put any type you want! When I use euqals() on the result of get(), shouldn’t I call Object’s equals method? After all, the result is an Object.
3.4 Java inheritance and polymorphism
Moving on, it turns out that the problem is simple, and involves inheritance and polymorphism, which are at the heart of object-oriented programming. Since the value of the “command” attribute we put in JsonObject is actually a String Object, the value returned to JsonObject is actually an implicitly converted Object Object.
String value = new String("TERM"); JSONObject jsonObject = new JSONObject(); jsonObject.put("command", value); Object this.mand = vaue = new String("TERM"); */ Object command = jsonObject.get("command");
Object command = new String(“TERM”), and String overrides Object’s equals() method. So when Object command calls equals(), it must call String’s equals() method because of the polymorphic nature of the method. More in-depth online reviews of polymorphic method calls have a priority that can be delved into later.
Therefore, the principle of polymorphic mechanism is summarized as follows: When the superclass object reference variable subclass object reference and cited the type of object rather than a reference variable type determines the call who is a member of the method, but this is the method called must be defined in a superclass, that is covered by a subclass method, but it still according to the priority of a method call in the inheritance chain to confirm the method, the priority is: This.show (O), super.show(O), this.show((super)O), super.show((super)O).
4. To summarize
A brief summary of several issues involved in this study:
- HashCode() and System.IdentityHashCode (), the former of which is overridden in a partial class, ignore the value of the computed HashCode that is returned by the override method.
- Java heap, stack and method area, 3 constant pools. Object instances are stored in the heap, literal strings are stored in the CLASS constant pool at compile time, and the CLASS constant pool is imported into the runtime constant pool and the global string constant pool during class loading during the program run.
- == and equals(). The String class overrides equals() to compare values, not addresses.
- Inheritance and polymorphism in Java. When a superclass refers to a variable that references a subclass object, if the subclass overrides the method of the superclass, the method that refers to the variable will be called when the subclass overrides the method.
5. Afterword.
So far, many problems related to the sentence “if(XXX == “TERM”)” finally come to an end, and there are still some problems left for further clarification in the following learning process. A summary down can not help but let the people feeling, such a simple question once in-depth exploration can also be involved in so many content, as expected everything is nothing more than study two words!