在 Java 中,理解参数传递机制对于编写高效和可维护的代码至关重要。本文将探讨基本数据类型和引用数据类型的参数传递方式,并介绍 System.identityHashCode
方法及其作用。我们将结合栈帧的概念,通过示例代码来详细解释这些机制。
System.identityHashCode
的作用
System.identityHashCode
方法用于获取对象的哈希码,这个哈希码是基于对象的内存地址的。它可以帮助我们判断两个变量是否指向同一个对象。这个哈希码与 Object
类中的 hashCode
方法不同,后者可能被重写,而 identityHashCode
保证基于对象的实际内存地址。
栈帧的概念与参数传递
⚠️:先普及一下基本知识:一个线程对应一个栈,一个栈包含多个栈帧,一个栈帧对应一个方法。
每当一个方法被调用时,Java 虚拟机(JVM)会为该方法创建一个新的栈帧。栈帧用于存储方法的局部变量、操作数栈和其他执行上下文信息。局部变量表包含了方法中的所有参数和局部变量。每个方法调用都有自己的栈帧,这些栈帧在方法调用结束后会被弹出。
基本数据类型的参数传递
对于基本数据类型(如 int
, float
, boolean
等),Java 使用 值传递 的方式。这意味着方法接收的是参数值的副本,而不是原始变量的引用。修改副本不会影响原始变量。
示例代码
public class Main {
public static void main(String[] args) {
int a = 1;
int hashCode = System.identityHashCode(a);
System.out.println("a: " + hashCode); // a:1456208737
modify(a);
System.out.println("a: " + hashCode); // a:1456208737
}
public static void modify(int b) {
b = 2;
int hashCode = System.identityHashCode(b);
System.out.println("b: " + hashCode); // b:288665596
}
}
解释
-
方法调用过程:
main
方法中的栈帧包含了局部变量a
,其值为1
,哈希码为1456208737
。- 当调用
modify
方法时,a
的值1
被复制到modify
方法的栈帧中的局部变量b
。
-
modify
方法中的操作:- 在
modify
方法的栈帧中,b
的值为1
。将b
的值修改为2
,并将其哈希码输出为288665596
。 - 由于
b
是main
方法栈帧中a
值的副本,修改b
不会影响a
的值。
- 在
-
栈帧作用:
main
方法和modify
方法各自拥有独立的栈帧,局部变量表中的a
和b
是不同的变量,修改b
不会影响a
。
引用数据类型的参数传递
对于引用数据类型(如 Person
对象),Java 使用也是值传递(只不过传递的值是引用数据类型的内存地址)的方式。这意味着方法接收的是对象引用的副本,而不是对象的实际值。修改对象内容会影响所有引用该对象的变量,但修改引用本身不会影响原始引用。
示例代码
public class Main {
public static void main(String[] args) {
Person p1 = new Person("mike");
int hashCode = System.identityHashCode(p1);
modify(p1);
System.out.println("p1: " + hashCode); // p1:1456208737
}
public static void modify(Person p2) {
p2.name = "lucy";
int hashCode = System.identityHashCode(p2);
System.out.println("p2: " + hashCode); // p2:1456208737
}
static class Person {
String name;
public Person(String name) {
this.name = name;
}
}
}
解释
-
方法调用过程:
main
方法中的栈帧包含了局部变量p1
,它是指向Person
对象的引用,其哈希码为1456208737
。- 当调用
modify
方法时,p1
的引用被复制到modify
方法的栈帧中的局部变量p2
。
-
modify
方法中的操作:- 在
modify
方法的栈帧中,此时p2
也是指向同一个Person
对象的引用。修改p2.name
会直接影响到p1
对象,因为它们指向同一个内存地址。 p2
的哈希码与p1
相同,因为它们是同一个对象的引用。
- 在
-
栈帧作用:
main
方法和modify
方法各自拥有独立的栈帧,但p1
和p2
指向同一个内存地址,即同一个Person
对象,修改p2
的内容会影响p1
。
String
类型的特殊情况
String
类型是一个引用数据类型,但它是 不可变的。这意味着每次对 String
对象进行修改时,实际上会创建一个新的 String
对象,而不会改变原始对象。
示例代码
public class Main {
public static void main(String[] args) {
String a = "str1";
int hashCode = System.identityHashCode(a);
modify(a);
System.out.println("a: " + hashCode); // a:1456208737
}
public static void modify(String b) {
b = "str2";
int hashCode = System.identityHashCode(b);
System.out.println("b: " + hashCode); // b:288665596
}
}
解释
-
方法调用过程:
main
方法中的栈帧包含了局部变量a
,它指向String
对象"str1"
,其哈希码为1456208737
。- 当调用
modify
方法时,a
的引用(即"str1"
的地址)被复制到modify
方法的栈帧中的局部变量b
。
-
modify
方法中的操作:- 在
modify
方法中,将b
赋值为"str2"
。按理来说操作的也是同一个内存地址,也会将该内存地址中的值修改为"str2"
。但是Java规定了String
是不可变的,这将会在常量池创建一个新的String
对象"str2"
,并将b
指向这个新的对象。原始的String
对象"str1"
仍然存在,因此a
的哈希码没有改变。
- 在
-
栈帧作用:
main
方法和modify
方法各自拥有独立的栈帧。修改b
的引用不会影响a
,因为String
对象的不可变性确保了原始对象不变。
总结
- 基本数据类型 的参数传递是值传递,修改方法内部的值不会影响外部变量,因为每个方法调用都有自己的栈帧。
- 引用数据类型 的参数传递是引用传递,修改方法内部的对象内容会影响外部变量,因为它们指向同一个对象。栈帧中的引用指向相同的对象内存地址。
String
类型 是不可变的,对String
对象的修改会创建一个新的对象,原始对象保持不变,这与对象的可变性相关。