Java中的比较、自动装箱拆箱与缓存机制

37 阅读4分钟

1. Java中的比较

1.1 比较操作符 ==

  • 对于基本类型(如int, double, boolean等),==比较的是值。
  • 对于引用类型(如String, Integer等),==比较的是内存地址(即是否指向同一个对象)。

1.2 equals方法

  • equals方法是Object类的方法,默认行为与==相同,比较内存地址。
  • 很多类重写了equals方法,使其比较对象的内容。例如String、包装类等。
  • 重写equals方法时,通常也要重写hashCode方法。

1.3 字符串比较

  • 字符串常量池:字符串字面量会被放入常量池,相同的字面量会指向同一个对象。
  • 使用new String("hello")会创建新的对象,与常量池中的对象不是同一个。

1.4 包装类的比较

  • 包装类重写了equals方法,比较的是值。
  • 但由于缓存机制,在缓存范围内的值使用==可能会返回true,但超出范围则会返回false。因此,包装类的比较推荐使用equals

1.5 Objects.equals方法

  • 这是Java 7引入的工具方法,用于安全地比较两个对象。
  • 它先检查两个对象是否同一个引用(包括都为null),然后检查非null并调用第一个对象的equals方法。
  • 优点:可以避免空指针异常。

2. 自动装箱与自动拆箱

2.1 基本概念

  • 自动装箱:将基本类型自动转换为对应的包装类对象。例如:Integer i = 10; 等价于 Integer i = Integer.valueOf(10);
  • 自动拆箱:将包装类对象自动转换为对应的基本类型。例如:int n = i; 等价于 int n = i.intValue();

2.2 发生时机

  • 赋值:Integer i = 10;(装箱) 和 int n = i;(拆箱)
  • 方法调用:将基本类型传递给包装类参数(装箱),反之亦然(拆箱)。
  • 集合操作:集合中存储包装类,但可以添加基本类型(装箱),取出时也可以直接赋值给基本类型(拆箱)。
  • 算术运算:包装类对象参与算术运算时会被拆箱。

2.3 注意事项

  • 自动拆箱时,如果包装类对象为null,会抛出NullPointerException。
  • 频繁的装箱拆箱可能会影响性能。

3. 包装类的缓存机制

3.1 缓存范围

  • Java为部分包装类提供了缓存机制,常用于数值范围在-128到127之间。

  • 具体缓存范围:

    • Byte:全部缓存(-128到127)
    • Short:-128到127
    • Integer:-128到127(可通过JVM参数-XX:AutoBoxCacheMax扩展上限)
    • Long:-128到127
    • Character:0到127
    • Boolean:缓存了TRUE和FALSE

3.2 缓存的作用

  • 提高性能:避免频繁创建和销毁对象。
  • 节省内存:重复使用缓存的对象。

3.3 缓存实现原理

  • 使用valueOf方法时,会先检查值是否在缓存范围内,如果在则返回缓存对象,否则创建新对象。
  • 例如Integer.valueOf(int i)方法。

3.4 注意事项

  • 使用new关键字会创建新对象,绕过缓存。
  • 在比较包装类时,使用==比较可能会因为缓存而出现预期之外的结果,因此推荐使用equals方法。

4. 示例代码

4.1 比较示例

java

// 基本类型比较
int a = 100;
int b = 100;
System.out.println(a == b); // true

// 引用类型比较
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true

// 包装类比较
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2); // true,在缓存范围内
System.out.println(i3 == i4); // false,超出缓存范围
System.out.println(i3.equals(i4)); // true

// 使用Objects.equals
System.out.println(Objects.equals(i3, i4)); // true
System.out.println(Objects.equals(null, i4)); // false
System.out.println(Objects.equals(null, null)); // true

4.2 自动装箱拆箱示例

java

// 自动装箱
Integer i = 10; // 等价于 Integer.valueOf(10)
// 自动拆箱
int n = i; // 等价于 i.intValue()

// 方法调用中的装箱拆箱
public void method(Integer i) { ... }
method(10); // 装箱,将10转换为Integer

public int method2() {
    Integer i = 10;
    return i; // 拆箱
}

// 集合中的装箱拆箱
List<Integer> list = new ArrayList<>();
list.add(1); // 装箱
int num = list.get(0); // 拆箱

// 算术运算中的拆箱
Integer a = 10;
Integer b = 20;
Integer c = a + b; // 先拆箱再装箱

4.3 缓存机制示例

java

Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // true

Integer c = Integer.valueOf(128);
Integer d = Integer.valueOf(128);
System.out.println(c == d); // false

// 使用new绕过缓存
Integer e = new Integer(127);
Integer f = new Integer(127);
System.out.println(e == f); // false

5. 最佳实践

  1. 基本类型比较使用==
  2. 引用类型比较使用equals,并注意重写equalshashCode方法。
  3. 包装类比较一律使用equals,避免使用==
  4. 使用Objects.equals进行null安全的比较。
  5. 注意自动装箱拆箱的时机,避免不必要的性能损耗和空指针异常。
  6. 了解包装类的缓存机制,但不要依赖缓存范围进行编程(除非有明确规范)。

希望这份笔记能帮助你更好地理解Java中的比较、自动装箱拆箱和缓存机制。