I was fired the next day because I changed int to Integer

Keywords: Java Design Pattern Back-end Programmer

This article is excerpted from the Flyweight Pattern of "design patterns should be learned in this way"

1 story background

Because a programmer changed a method parameter in the production environment and changed the int type to Integer type, because money was involved, the company suffered heavy losses and the programmer was dismissed. Believe it or not, keep looking down. Let's start with a code:

public static void main(String[] args) {

        Integer a = Integer.valueOf(100);
        Integer b = 100;

        Integer c = Integer.valueOf(129);
        Integer d = 129;

        System.out.println("a==b:" + (a==b));
        System.out.println("c==d:" + (c==d));
}

Guess what the result is? After running the program, we found something wrong and got an unexpected running result, as shown in the figure below.

Seeing this running result, someone will ask, why? This result is obtained because of the meta mode used by Integer. Look at the source code of Integer,

public final class Integer extends Number implements Comparable<Integer> {

        ...

        public static Integer valueOf(int i) {

                        if (i >= IntegerCache.low && i <= IntegerCache.high)
                                return IntegerCache.cache[i + (-IntegerCache.low)];
                        return new Integer(i);

        }

        ...

}

Continue to the source code of IntegerCache to see the values of low and high:

private static class IntegerCache {
  // minimum value
  static final int low = -128;
  // Maximum value, support customization
  static final int high;
  // Cache array
  static final Integer cache[];

  static {
    // The maximum value can be changed by property configuration
    int h = 127;
    String integerCacheHighPropValue =
      sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    // This value is used if the corresponding property is set
    if (integerCacheHighPropValue != null) {
      try {
        int i = parseInt(integerCacheHighPropValue);
        i = Math.max(i, 127);
        // The maximum array size is Integer.MAX_VALUE
        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
      } catch( NumberFormatException nfe) {
        // If the property cannot be parsed into an int, ignore it.
      }
    }
    high = h;

    cache = new Integer[(high - low) + 1];
    int j = low;
    // Instantiate all the values in the low high range and store them in the array for caching
    for(int k = 0; k < cache.length; k++)
      cache[k] = new Integer(j++);

    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
  }

  private IntegerCache() {}
}

It can be seen from the above that the valueOf() method in the Integer source code makes a condition judgment. If the target value is - 128 - 127, it will be taken directly from the cache, otherwise an object will be created. In fact, when Integer is used for the first time, it initializes the cache. The minimum value of the range is - 128 and the maximum value is 127 by default. Then, all the data from low to high will be initialized and stored in the data. By default, 256 cycles of - 128 - 127 will be instantiated and stored in the cache array. To be exact, the addresses of these 256 objects in memory should be stored in the array. Here someone will ask why the default is - 128 - 127, why not - 200 - 200 or other values? Then why did JDK do this?

This is explained in the Java API:

Returns an Integer instance representing the specified int value. If a new Integer instance is not required, this method should generally be used in preference to the constructor Integer(int), as this method is likely to yield significantly better space and time performance by caching frequently requested values. This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range

It roughly means:

128 ~ 127 data are most frequently used in the int range. In order to reduce the memory consumption caused by frequent object creation, the meta sharing mode is actually used here to improve space and time performance.

JDK adds this default range, which is not immutable. We can modify the cache range by setting - Djava.lang.Integer.IntegerCache.high=xxx or - XX:AutoBoxCacheMax=xxx before use, as shown in the following figure:

Later, I found a more reliable explanation:

In fact, when this feature was first introduced in Java 5, the range was fixed to - 127 to + 127. Later, in Java 6, the maximum value of the range is mapped to java.lang.Integer.IntegerCache.high, and the VM parameter allows us to set the high-order number. According to our application use cases, it can flexibly adjust the performance. What is the reason why this number range should be selected from - 127 to 127. This is considered to be a widely used Integer range. Using Integer for the first time in a program must take extra time to cache instances.

The original text of the Java Language Specification is explained as follows:

Ideally, boxing a given primitive value p, would always yield an identical reference. In practice, this may not be feasible using existing implementation techniques. The rules above are a pragmatic compromise. The final clause above requires that certain common values always be boxed into indistinguishable objects. The implementation may cache these, lazily or eagerly. For other values, this formulation disallows any assumptions about the identity of the boxed values on the programmer's part. This would allow (but not require) sharing of some or all of these references. This ensures that in most common cases, the behavior will be the desired one, without imposing an undue performance penalty, especially on small devices. Less memory-limited implementations might, for example, cache all char and short values, as well as int and long values in the range of -32K to +32K.

2 Comparison between Integer and int

\1) Because the Integer variable is actually a reference to an Integer object, the two Integer variables generated by new are always unequal (because new generates two objects with different memory addresses).

Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false

\2) When comparing Integer variables with int variables, as long as the values of the two variables are equal, the result is true (because when comparing wrapper class Integer with basic data type int, java will automatically unpack it as int and then compare it, which will actually become the comparison of two int variables)

Integer i = new Integer(100);
int j = 100;
System.out.print(i == j); //true

\3) When comparing the non new generated Integer variable with the variable generated by new Integer(), the result is false. (because ① when the variable value is between - 128 - 127, the Integer variable generated by non new points to the object in the java constant pool, while the variable generated by new Integer () points to the object newly created in the heap, and their addresses in memory are different; ② when the variable value is between - 128 - 127, when the Integer variable generated by non new, the java API will eventually process it according to new Integer(i) (refer to Article 4 below). The addresses of the two intergers are also different)

Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false

\4) For two Integer objects not generated by new, if the values of the two variables are between - 128 and 127, the comparison result is true. If the values of the two variables are not in this interval, the comparison result is false

3 expand knowledge

In JDK, this application is not only int, but also the following packaging types apply the meta mode to cache values, but the cache range is different, as shown in the following table:

Basic typesizeminimum valueMaximumWrapper typeCache rangeSupport customization
boolean---Bloolean--
char6bitUnicode 0Unic ode 2(16)-1Character0~127no
byte8bit-128+127Byte-128~127no
short16bit-2(15)2(15)-1Short-128~127no
int32bit-2(31)2(31)-1Integer-128~127support
long64bit-2(63)2(63)-1Long-128~127no
float32bitIEEE754IEEE754Float-
double64bitIEEE754IEEE754Double-
void---Void--

Do you think this pot is worth it?

4. Implement database connection pool using shared meta mode

For another example, the database Connection pool that we often use is mainly performance consuming when using Connection objects. In order to improve the performance of Connection objects when establishing and closing connections, the Connection objects are created and cached before calling. When in use, they are directly taken from the cache and put back after use, so as to achieve resource duplication For the purpose of using, the code is as follows.

public class ConnectionPool {

    private Vector<Connection> pool;

    private String url = "jdbc:mysql://localhost:3306/test";
    private String username = "root";
    private String password = "root";
    private String driverClassName = "com.mysql.jdbc.Driver";
    private int poolSize = 100;

public ConnectionPool() {

        pool = new Vector<Connection>(poolSize);

        try{
            Class.forName(driverClassName);
            for (int i = 0; i < poolSize; i++) {
                Connection conn = DriverManager.getConnection(url,username,password);
                pool.add(conn);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public synchronized Connection getConnection(){
        if(pool.size() > 0){
            Connection conn = pool.get(0);
            pool.remove(conn);
            return conn;
        }
        return null;
    }

    public synchronized void release(Connection conn){
        pool.add(conn);
}

}

Such connection pools are widely used in open source frameworks and can effectively improve the underlying performance.

Posted by AStrangerWCandy on Mon, 01 Nov 2021 22:36:23 -0700