01. Source code analysis of String and Long

String

1. Invariance

Invariance means that once the value is initialized, it can no longer be changed. If the value is modified, a new class will be generated and the physical address of memory will be changed.

The reason why String is invariant:

  • String class is modified by final. All string classes cannot be inherited, that is, any operation method on string will not be inherited or overwritten;
  • The data stored in String is an array of char type, which is also modified by final, that is, once the String variable is assigned a value, the memory address can never be modified. Moreover, the array is modified by private, and the array variable cannot be accessed externally. The value can only be obtained through the method provided by String class

If our own class also wants to be immutable, we can imitate String, modify the class with final, and then modify the variables in the class with private and final. The external can only access the class variables through the methods we provide

Because String is invariant, most methods of String will return a new String

2. String garbled

The reason for String garbled code is that different operating systems have different default character codes, resulting in normal local operation and garbled code when deployed to the server. The solution is to uniformly specify UTF-8 where character codes are needed. For example, getBytes and new String of String can be used to specify character codes

3. Equality judgment

You can use the equals method and equalsInnoreCase method of String to judge whether two strings are equal. The equalsIgnoreCase method ignores case

The equals method compares whether two strings are equal:

  • Use = = to compare whether the memory addresses of two strings are the same. If they are the same, return true
  • Use instanceof to judge whether the parameter is of String type. If not, return false
  • Forcibly convert the parameter to String type to judge whether the length of the two strings is the same. If not, return false
  • Convert the string into a character array, and use the while loop to traverse the array to judge whether the characters with the same subscript of the two arrays are the same. If one is different, return false
  • Return true

The source code is as follows:

public boolean equals(Object anObject) {
  // Determine whether it is the same object
  if (this == anObject) {
    return true;
  }
  // Check whether the parameter is of String type
  if (anObject instanceof String) {
    String anotherString = (String)anObject;
    int n = value.length;
    // Check whether the length of two strings is the same
    if (n == anotherString.value.length) {
      char v1[] = value;
      char v2[] = anotherString.value;
      int i = 0;
      // Compare characters one by one
      while (n-- != 0) {
        if (v1[i] != v2[i])
          return false;
        i++;
      }
      return true;
    }
  }
  return false;
}

From the source code of equals of String, we can see that the logic is very clear and is written completely according to the underlying structure of String. We can also refer to it when implementing the equals method of custom class.

The equalsInnoreCase method ignores whether the case comparison characters are the same. The principle is similar to that of equals, except that the toUpperCase method is called when comparing each character in a loop, and the characters are converted to uppercase for comparison

4. The difference between equals and = =

Both equals and = = judge whether two objects are equal. The difference is that equals uses user-defined logic to judge whether two objects are equal, while = = simply judges whether the memory addresses of two objects are equal. If the equals method is not rewritten, the equals method also judges whether the memory addresses of two objects are the same. At this time, it is the same as = =, String overrides the equals method

public static void main(String[] args) {
  // Case 1
  String s1 = "123";
  String s2 = "123";
  System.out.println(s1 == s2); // true
  // Print the memory address of the variable
  System.out.println("s1: " + System.identityHashCode(s1) + "  s2: " + System.identityHashCode(s2)); // s1: 1347137144  s2: 1347137144

  // Case 2
  s1 = new String("123");
  s2 = new String("123");
  System.out.println(s1 == s2); // false
  // Print the memory address of the variable
  System.out.println("s1: " + System.identityHashCode(s1) + "  s2: " + System.identityHashCode(s2)); // s1: 997608398  s2: 1973336893
}

Case 1 is true because the memory addresses of s1 and s2 are the same. The reason is that the String object is put into the constant pool, so s1 and s2 both point to the same object in the constant pool

Case 2 is false because every new object will be re created

5. Relationship between equals and hashCode

The hashCode method is used to reduce the number of calls to the equals method and improve efficiency.
The hashCode method generates the hash code of the class. If two objects are the same, the hash codes of the two objects must be the same. If the hash codes of the two objects are the same, the two objects are not necessarily the same. According to this principle, when comparing whether the two objects are the same, first call the hashCode method to check whether the hash codes of the two objects are the same, If not, the two objects must be different. If the hash code is the same, call the equals method to determine whether the two objects are really the same (the overhead and efficiency of the hashCode method are much less than those of the equals method).

However, not all classes need to implement the equals method and hashCode method at the same time. The hashCode method is related to the equals method only when it is necessary to create a "hash table of classes". In other cases, the hashCode method is not related to the equals method. In short, the hashCode method is used only when our customized objects are placed in HashSet, HashTable, HashMap and other structures that are essentially hash tables. When adding objects, the hashCode method will be called first to check whether the objects exist. In this case, if only the equals method is rewritten, equals will not take effect, Because hashcodes are different (hashcodes are memory addresses by default), they will be directly considered as two objects and will not continue to call the equals method. Therefore, it is recommended to rewrite the equals method and the hashCode method at the same time

6. The difference between replace and replaceAll methods

Both the replace method and the replaceAll method can replace a specified character with a string. The difference is that the replace method parameter is a string, while the replaceAll parameter can be a regular expression

public static void main(String[] args) {
  String s1 = "123";
  String s2 = "123";

  System.out.println("replace: " + s1.replace("1", "0")); // 023
  System.out.println("replaceAll: " + s1.replaceAll("\\d", "0")); // 000
}

You can use the replace and replaceAll methods to delete the specified characters in the string.

Long

1. Long cache mechanism

Long caches all long values from - 128 to 127. If it is a long value in this range, it will not be initialized, but will be taken from the cache.

public static void main(String[] args) {
  Long l1 = 12L;
  Long l2 = 12L;
  System.out.println("l1 == l2 : " + (l1 == l2) + "   l1.equals(l2): " + l1.equals(l2));
  l1 = new Long(12);
  l2 = new Long(12);
  System.out.println("l1 == l2 : " + (l1 == l2) + "   l1.equals(l2): " + l1.equals(l2));
  l1 = 128L;
  l2 = 128L;
  System.out.println("l1 == l2 : " + (l1 == l2) + "   l1.equals(l2): " + l1.equals(l2));
}

/**
* Operation results:
* l1 == l2 : true   l1.equals(l2): true
* l1 == l2 : false   l1.equals(l2): true
* l1 == l2 : false   l1.equals(l2): true
*/

Relevant source code:

private static class LongCache {
  private LongCache(){}

  static final Long cache[] = new Long[-(-128) + 127 + 1];

  static {
    for(int i = 0; i < cache.length; i++)
      cache[i] = new Long(i - 128);
  }
}

2. The difference between valueOf and parseLong

The valueOf method can convert a numeric string or number into a Long type, while the parseLong method can only convert a string into a Long type. If the parameter of the valueOf method is a number and the range is - 128 to 127, it will get the value from the Long cache, so as to reduce the resource overhead; If the parameter is a string, the underlying valueOf also calls the parseLong method.

// The valueOf method parameter is long
public static Long valueOf(long l) {
  final int offset = 128;
  if (l >= -128 && l <= 127) { // will cache
    return LongCache.cache[(int)l + offset];
  }
  return new Long(l);
}

// The valueOf method parameter is string
public static Long valueOf(String s) throws NumberFormatException {
  return Long.valueOf(parseLong(s, 10));
}

Keywords: Java

Added by Kold on Mon, 07 Feb 2022 21:29:24 +0200