Java基本数据类型与引用类型的区别

Java中基本类型存值,引用类型存地址;基本类型在栈上,引用类型变量在栈、对象在堆;参数均为值传递,引用类型传地址副本。

基本数据类型直接存值,引用类型存的是对象地址

Java里 intbooleanchar 这些基本类型,变量里放的就是真实数据本身;而 StringArrayList、自定义类的实例这些引用类型,变量里存的只是一个内存地址——指向堆里真正对象的指针。

这意味着:两个 int 变量赋值是彻底复制数值,互不影响;但两个 String 变量赋值,只是复制了地址,如果对象可变(比如 StringBuilder),改一个可能影响另一个(除非重新赋值或用 new 创建新对象)。

  • int a = 10; int b = a; → 改 b 不影响 a
  • StringBuilder s1 = new StringBuilder("a"); StringBuilder s2 = s1;s2.append("b") 后,s1.toString() 也会变成 "ab"
  • 包装类如 Integer 是引用类型,但因为不可变,行为上常被误当成“像基本类型”,实际仍是对象,有装箱/拆箱开销

基本类型没有方法,引用类型才能调用方法

你不能对 intmyInt.toString(),会编译报错;但 Integer 可以,因为它是类。基本类型只有运算符(+&& 等)和有限的隐式转换规则。

注意:自动装箱(Integer i = 100;)会让代码看起来能“调用方法”,但这其实是编译器帮你把 int 包成了 Integer 对象,背后有性能成本,尤其在循环中频繁发生时。

  • 常见陷阱:Integer a = 127; Integer b = 127; System.out.println(a == b); 输出 true(缓存机制);但 a =

    128; b = 128;
    就输出 false
  • 比较值是否相等,始终用 .equals()(注意空指针),不要用 == 判断包装类
  • intInteger 混用时,拆箱可能抛 NullPointerException

内存分配位置不同:栈 vs 堆

基本类型变量(包括方法参数、局部变量)默认分配在栈上,生命周期随方法调用结束而释放;引用类型的变量本身在栈上,但它指向的对象一定在堆上(除了某些逃逸分析优化后的例外)。

这直接影响 GC 行为:栈上数据不用 GC 管理;堆上对象由 GC 负责回收。所以大量创建 new String("...") 或临时集合,会增加堆压力;而一堆 double 局部变量几乎不触发 GC。

  • 数组是特例:基本类型数组(如 int[])本身是引用类型,整个数组对象在堆上;数组元素才是真正的 int
  • 成员变量如果是基本类型,随所在对象一起存在堆上;静态变量也存在方法区(JDK 8+ 是元空间),不是栈
  • 栈空间小且固定,递归过深或超大局部数组(哪怕基本类型)仍可能导致 StackOverflowError

传参时:基本类型是值传递,引用类型也是值传递(传的是地址的副本)

Java 所有参数都是值传递。所谓“引用类型传引用”是误解。实际是:传的是引用变量所存地址的一个副本。这个副本和原变量指向同一对象,所以能修改对象状态;但如果在方法内让参数指向新对象(如 list = new ArrayList();),不会影响外部变量。

public static void changeValue(int x) { x = 100; }
public static void changeList(ArrayList list) {
    list.add("new");        // ✅ 外部 list 会看到这个元素
    list = new ArrayList<>(); // ❌ 外部 list 引用不变
}
  • 函数内重新赋值引用参数,只改变副本,不影响调用方
  • 要彻底隔离对象修改,需手动克隆(new ArrayList(original))或使用不可变容器(如 ImmutableList
  • 数组作为参数同理:arr[0] = 99 会影响外部,但 arr = new int[5] 不会
容易被忽略的一点:基本类型没有 null 值,引用类型才有。这意味着所有基本类型字段都有默认值(0false 等),而引用类型字段默认是 null——这是空指针异常最常发生的源头之一。