JAVA方法参数到底是值传递还是引用传递,求大师来解答!

gm100861 发布于 11/22 15:23
阅读 498
收藏 2

想请教一下各位大师,java到底是引用传递还是值传递

网上已经搜过了各种文章. 

观点A是: 基本类型(8大基本类型)是值拷贝,对象类型是引用传递.

观点B是: java中只有值传递,没有引用传递.

看了各自的观点,好像都不大能说服我.所以想请教以下各路神仙,来帮我解答一下.

 

加载中
0
tcxu
tcxu
  1. 仔细阅读了各位的发言,特别是楼主的提问,特设计以下案例,说明java语言,调用方法的时候,如果参数是人(Person)这个对象的引用 p, 总是将主调方法里 p 的值,赋予被调方法。就这个意义讲,仍然是传值。
  2. 由于调用方法 changeName_Age(Person p, String name, int age), 传递了 p 的值(李明对象的地址),故被调方法 可以借助 p 的值("地址"),合法访问 p 的属性,甚至为 p 的属性从新赋值。
  3. 调用 方法 Person becomeIndian(Person p), 也是将主调方法中的 引用 p 的值赋予 被调方法。
  4. 方法 Person becomeIndian(Person p),用引用 p 的属性(name, age),创建了一个印度人新实体,并且将 印度人新实体的地址,赋予 被调方法里的引用 p。这样,被调方法里的引用 p 就有了新数值,指向了印度人。
  5. 为了让主调方法获得这个新的印度人的地址,方法 Person becomeIndian(Person p) 就必须返回方法内的 持有新数值的引用 p.
  6. 主调方法,用指向李明的引用 p , 接收了这个新址(印度人的地址).
  7. 注意,这时实体李明的堆内存已经没有任何引用指向它.
  8. 任何类都是 Object 类的间接子类,所以都 继承方法 public int hashCode()。 该方法返回的值一般是通过将该对象的内部地址转换成一个整数来实现的。 这样能保证每个对象的哈希码值不一样, 即哈希码值反应的是对象的地址。

  9. 这里,特将对象引用的数值(哈希码值),用16进制打印出,以显示引用 p 所指向的实体的"内存地址"。

  10. 检查主调方法分别调用方法 changeName_Age 和 becomeIndian 的时候,p,作为引用参数,传递(复制)的是,它 (p) 的数值:p 的哈希码值。所以说是,也是传值。 

  11. 鉴于 哈希码值 反映的是对象的地址,楼主,我们是否可以这样讲:"java 的引用传递,就相当于 C 语言中的 '传址' 或 '指针传递'"? 

//因为有一个抽象方法:吃饭, 所以是抽象类
abstract class Person { 
	String name;
	int age;
	public Person(String name, int age){
	this.name=name;
	this.age=age;
 	}
/* 这里,不同人吃饭方法不一样,没有统一定义,
 * 这时,只能在方法签名之前冠以关键字 abstract
 */
	abstract String havingDinner(); //抽象方法
	public String toString(){
		String s = age + "岁的";
		s += havingDinner();
		return s;
	}
}

class Chinese extends Person{ // 具体定义中国人
	public Chinese(String name, int age){
		super(name, age);
	}
	public String havingDinner(){ //中国人吃饭具体化
		return "中国人  " + name + "用筷子吃饭。";
	}
}

class Indian extends Person{ //具体定义印度人
	public Indian(String name, int age){
		super(name, age);
	}
	public String havingDinner(){ //印度人吃饭具体化
	return "印度人  " + name + "用手抓着吃饭。";
	}
}

public class PassByValue {
	
/* 调用此方将法的时间,主调方法的实体的引用 p 的值(即,与 p 相关的对
 * 象的"地址"),作为参数,赋予被调方法。
 */	
  static Person becomeIndian(Person p){
  System.out.printf("方法 becomeIndian 收到 引用 p 的\"地址\": %x\n", p.hashCode() );
  
/* 在被调方法中创建一个印度人实体,并将其"地址"赋予 p
 *注意,从今后,p 不再指向原来的"中国人李明33岁"实体。
 */
p = new Indian(p.name,p.age);


/* 必须 返回 p, 才能将 p 的这个新值(地址)传给主调方法 
 */
	return p;
	}

// 传递引用 p 的值(即,与 p 相关的对象的"地址")	
  static void changeName_Age(Person p, String name, int age){
  	System.out.printf("方法 changeName_Age 收到 引用 p 的\"地址\": %x\n", p.hashCode() );
		p.name = name;
		p.age = age;
	}
	
	public static void main(String args[]){
	Person p = new Chinese("李明", 33); //创建 33岁的中国人李明
	System.out.println(p);
	System.out.printf("在主调方法里,引用 p 的\"地址\": %x\n", p.hashCode() );
/* 被调方法为实体 李明 改名换姓,并更改年龄。
 * 而实体的引用值,即实体的"地址",始终没有变化。
 */
	changeName_Age(p, "阿布舍克 巴强 ", 55);
	System.out.printf("在主调方法里,引用 p 的\"地址\": %x\n", p.hashCode() );
/*
 *  可见,被调方法得以将主调方法的实体的属性赋予新数值。
 */
	System.out.println(p); 
	System.out.printf("在主调方法里,引用 p 的\"地址\": %x\n", p.hashCode() );
	
/* 由于被调方法将引用参数 p 赋予"新址",主调方法就用 原来 
 * 中国人李明的引用 p,接受这个新"地址", 此后,p 将"指向"
 * 55 岁的印度人 阿布舍克?巴强。  
 */	
	p=becomeIndian(p);
	System.out.printf("在主调方法里,引用 p 的\"地址\": %x\n", p.hashCode() );

	System.out.println(p);
	}
}

33岁的中国人  李明用筷子吃饭。
在主调方法里,引用 p 的"地址": 6d06d69c
方法 changeName_Age 收到 引用 p 的"地址": 6d06d69c
在主调方法里,引用 p 的"地址": 6d06d69c
55岁的中国人  阿布舍克 巴强 用筷子吃饭。
在主调方法里,引用 p 的"地址": 6d06d69c
方法 becomeIndian 收到 引用 p 的"地址": 6d06d69c
在主调方法里,引用 p 的"地址": 28d93b30
55岁的印度人  阿布舍克 巴强 用手抓着吃饭。

 

g
gm100861
非常感谢您的多次耐心详细回答,学习受教了.非常感谢!
1
tcxu
tcxu

    观点 A 与 观点 B 的说法都没有错。
    观点 A 所说的 "对象类型是引用传递", 其操作是:主调方法,将对象引用的值,赋予(传递给)被调用的方法。这种操作,就是传递对象引用的值,也就是 观点 B 所说的 "值传递",也就是 "对象引用值的传递"。
就基本数据类型而言,被调用的方法只是复制(拷贝)基本数据类型参数的值,而非参数的"地址",所以被调方法内的代码,(由于没有"地址"),无法访问主调方法里的相关变量,无法改变主调方法里相关变量的值。
    就对象数据类型而言,被调用的方法复制(拷贝)了对象参数的值,即复制(拷贝)对象内存的"地址",所以被调方法内的代码,与主调方法中的代码一样,可以通过这个引用值,访问主调方法里的相关对象,进而,改变对象属性的值。

 

    java.lang.Integer 是 包装类,楼主的疑问是:若用包装类作参数,那结果会怎样?下面照抄 案例:Passing Wrapper Arguments (java中只有值传递是咋回事?Java: What to Know About Passing by Value) 来解答楼主的提问:"Integer是对象类型吧,为啥传入了之后做修改,也不能生效呢?求指教",

import java.lang.*;
import java.io.*;

public class TestInteger{

public static void main(String[] args) {
    Integer obj1 = new Integer(1);
    Integer obj2 = new Integer(2);
    System.out.println("调用 modifyWrappers(Integer x, Integer y) 方法前,两个引用所指的对象的值:");
    System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue());
    modifyWrappers(obj1, obj2);
    System.out.println("调用 modifyWrappers(Integer x, Integer y) 方法后,两个引用所指的对象的值:");
    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);
     
   System.out.println("仅在被调用的方法内,引用参数的值指向另外两个堆中的对象:");
   System.out.println("obj1 = " + x.intValue() + " ; obj2 = " + y.intValue());
   }
}

输出:

调用 modifyWrappers(Integer x, Integer y) 方法前,两个引用所指的对象的值:
bj1 = 1 ; obj2 = 2
仅在被调用的方法内,引用参数的值指向另外两个堆中的对象:
bj1 = 5 ; obj2 = 10
调用 modifyWrappers(Integer x, Integer y) 方法后,两个引用所指的对象的值:
bj1 = 1 ; obj2 = 2

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

补充: 

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

tcxu
tcxu
回复 @gm100861 : 因为,Integer是不可变类。
g
gm100861
Integer是对象类型吧,为啥传入了之后做修改,也不能生效呢?求指教
0
DeMoNHaDeS
DeMoNHaDeS

不要纠结于这个问题,关键是搞明白方法中操作的是什么。

两种观点都有一定的道理。java中方法传递的都是值,引用也是值的一种,我觉得这么想是最简单的。

DeMoNHaDeS
DeMoNHaDeS
回复 @gm100861 : 因为方法中只能传这个引用指向的对象,所以无法修改引用指向的对象
g
gm100861
那为啥在方法里修改了引用指向的对象,不生效呢?
0
有一个分号是中文
有一个分号是中文

两种说法都是对的,java中只有值传递,意思是引用传的也是值。好比C语言的指针。

有一个分号是中文
有一个分号是中文
回复 @gm100861 : 引用对象传到方法里以后,等于是两个引用指向同一个对象,换句话说就是两个指针,指向同一块内存。
g
gm100861
好像不对吧,c语言如果传指针,我是可以修改这个指针的指向的,比如原来指向A, 可以改成指向B 但是JAVA不能修改指向啊,如果传入一个对象的引用地址,指向的是A,在方法里新建一个对象,然后指向这个新建的对象,你会发现,没有效果.根本就不能修改指向.
0
银杏果果
银杏果果

java的内置数据类型除了String都是值传递,简单类型的包装类型是作为对象处理的,所以都是引用传递的。方法中区分是否为值传递和引用传递也很简单,引用传递能在方法体中改变内容而且方法外部能获得这个被改变的内容,而值传递是不行的。

0
tcxu
tcxu

看下Integer类的源码,因为Integer类中的value值是final的:
/**
 * The value of the {@code Integer}.
 *
 * @serial
 */
private final int value;
因此,不论是主调方法,还是被调方法,要更改一个Integer对象的值,就必须新创建一个Integer对象的。换言之,Integer是不可变类,其对象进入一个方法后,若要改变它的值,就要创建一个新的对象,因此,无论在被调方法内,或被调方法外,都不会改变这个引用所引荐的对象的值。

参考:
Integer类源码阅读(一)

0
shawnZeng
shawnZeng

http://www.importnew.com/29023.html,原文链接:https://dzone.com/articles/java-pass-by-reference-or-pass-by-value,可以解你疑惑

0
MeiJM
MeiJM
值传递 对象传递的是对象的指针 可以写个例子试试看一个方法接收一个对象为参数 方法内将对象重新初始化 看看结果
0
rz
rz

值传递没错,基本数据类型传递过程中会复制一份值到方法内部,引用数据类型会复制引用值(对象地址)到方法内部

0
乌龟壳
乌龟壳

java所有变量都可以理解为引用类型,赋值传参通通是引用。

所谓的基本类型值传递,只不过因为基本类型太小了,分引用和值两部分不值得。编译器做的优化而已。

用这样的模型去理解,

tcxu
tcxu
有道理。java语言 调用方法,参数传递的都是数值。基本类型参数的场合,直接传递它们的数值。引用类型参数的场合,传递的也是它们自身的值,而不是其所指对象的属性值。
返回顶部
顶部