Java:关于值传递你需要了解的事情

原文链接:https://dzone.com/articles/java-pass-by-reference-or-pass-by-value

我们都知道,在Java中,方法的参数传递永远都是指值传递。让我们来看一看基本类型和集合的参数传递在内存中是如何体现的。

在讨论Java中参数是如何传递之前,我们有必要先弄清楚Java的变量(主要指的是基本类型和对象)是怎么存储在内存中的。
基本类型一般都存储在堆栈中;对于Java对象,实际的对象数据存储在堆中,而对象的指针(指向推中的对象)存储在堆栈中。

1.png

1.传值 vs 传引用

“传值”和“传引用”分别是什么意思:

  • 传值:当方法参数是值传递时,意味着原参数的一个拷贝被传到了参数内部而不是原始参数,所以任何对于该参数的改变都只会影响这个拷贝值。
  • 传引用:当方法参数是引用传递时,意味着原始参数的引用或者说指针被传递到了方法内部,而不是这个原始参数的内容。

2.在Java中参数是怎么传递的

在Java中,不管原始参数的类型是什么,参数都是按值传递的。每次当一个方法被执行的时候,在堆栈中就会为每个参数创建一个拷贝,这个拷贝会被传递到方法内部。

  • 如果原始参数是基本类型,那么在堆栈中创建的便是这个参数的简单拷贝
  • 如果原始参数不是基本类型,那么在堆栈中创建的便是指向真正对象数据的新的引用或指针。这个新的引用被传递到方法内部(在这种情况下,有2个引用指向了同一个对象数据)

3.解决疑惑

在接下来的示例中,我们通过往方法中传递不同类型的参数(基本类型,包装类,集合类,自定义类),在方法执行完成后去检查他们是否被修改了来尝试证明“在Java中参数传递永远是值传递”。

基本类型参数

public static void main(String[] args) {
    int x = 1;
    int y = 2;
    System.out.print("Values of x & y before primitive modification: ");
    System.out.println(" x = " + x + " ; y = " + y );
    modifyPrimitiveTypes(x,y);
    System.out.print("Values of x & y after primitive modification: ");
    System.out.println(" x = " + x + " ; y = " + y );
}
private static void modifyPrimitiveTypes(int x, int y)
{
    x = 5;
    y = 10;
}

输出:

Values of x & y before primitive modification:  x = 1 ; y = 2
Values of x & y after primitive modification:  x = 1 ; y = 2

说明:
x,y这2个参数是基本类型,所以存储在堆栈中。当调用modifyPrimitiveTypes()方法时,在堆栈中创建了这2个参数的拷贝(我们就叫它们w,z),实际上是w,z被传递到了方法中。所以原始的参数并没有被传递到方法中,在方法中的任何修改都只作用于参数的拷贝w,z

2.png

包装类

public static void main(String[] args) {
    Integer obj1 = new Integer(1);
    Integer obj2 = new Integer(2);
    System.out.print("Values of obj1 & obj2 before wrapper modification: ");
    System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue());
    modifyWrappers(obj1, obj2);
    System.out.print("Values of obj1 & obj2 after wrapper modification: ");
    System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue());
}
private static void modifyWrappers(Integer x, Integer y)
{
    x = new Integer(5);
    y = new Integer(10);
}

输出:

Values of obj1 & obj2 before wrapper modification: obj1 = 1 ; obj2 = 2
Values of obj1 & obj2 after wrapper modification: obj1 = 1 ; obj2 = 2

说明:
包装类存储在堆中,在堆栈中有一个指向它的引用
当调用modifyWrappers()方法时,在堆栈中为每个引用创建了一个拷贝,这些拷贝被传递到了方法里。任何在方法里面的修改都只是改变了引用的拷贝,而不是原始的引用

3.png

P.S: 如果方法中的表达式为x += 2,x值得改变也不会影响到方法外部,因为包装类是immutable类型的。当他们的state变化时,他们就会创建一个新的实例。如果你想了解更多关于immutable类,可以阅读How to create an immutable class in Java。字符串类型和包装类相似,所以以上的规则对于字符串也有效。

集合类型

public static void main(String[] args) {
    List<Integer> lstNums = new ArrayList<Integer>();
    lstNums.add(1);
    System.out.println("Size of list before List modification = " + lstNums.size());
    modifyList(lstNums);
    System.out.println("Size of list after List modification = " + lstNums.size());
}
private static void modifyList(List<Integer> lstParam)
{
    lstParam.add(2);
}

输出:

Size of list before List modification = 1
Size of list after List modification = 2

说明:
当我们创建一个ArrayList或任意集合,在堆栈中便会创建一个指向堆中多个对象的引用。当modifyList()被调用时,一个引用的拷贝被创建中传递到了方法中。现在有2个引用指向了真正的对象数据,其中任何一个引用的数据改变会影响到另一个。
在方法中,当我们调用lstParam.add(2)时,一个Integer对象在堆中被创建,添加到了现有的list对象。所以原始的list引用可以看见这次修改,因为2个引用都指向了内存中的同一个对象

4.png

自定义对象

public static void main(String[] args) {
    Student student = new Student();
    System.out.println("Value of name before Student modification = " + student.getName());
    modifyStudent(student);
    System.out.println("Value of name after Student modification = " + student.getName());
}
private static void modifyStudent(Student student)
{
    student.setName("Alex");
}

输出:

Value of name before Student modification = null
Value of name after Student modification = Alex

说明:
student对象在堆中被创建,在堆栈中存储着指向它的引用。当调用calling modifyStudent(),在堆栈中创建了这个引用的拷贝,传递到了方法中。所以任何对这个对象属性的修改会影响原始的对象引用

结论

在Java中,参数都是按值传递的。被传递到方法中的拷贝值,要不就是一个引用或一个变量,取决于原始参数的类型。从现在开始,下面的几条规则将帮助你理解方法中对于参数的修改怎么影响原始参数变量。

  1. 在方法中,修改一个基础类型的参数永远不会影响原始参数值。
  2. 在方法中,改变一个对象参数的引用永远不会影响到原始引用。然而,它会在堆中创建了一个全新的对象。(译者注:指的是包装类和immutable对象)
  3. 在方法中,修改一个对象的属性会影响原始对象参数。
  4. 在方法中,修改集合和Maps会影响原始集合参数。

转载自我的博客:刘文哲的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355