Java中值传递与引用传递的理解

本文详细探讨了Java中参数传递的两种方式:值传递和引用传递。通过具体代码示例,解释了基本类型和引用类型参数传递的区别,以及如何影响方法内外部变量的值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java中参数的传递方式有两种,一种是值传递,另一种是引用传递

值传递: 在值传递中,会对实参求值(如果他是表达式)或拷贝(如果他是变量),这些值被放在属于被调函数的相应形参的内存位置上。值传递的效果是,被调函数所做的所有有关形参的计算都局限于这个函数,相应的实参本身不会被改变。

引用传递: 在引用传递中,实参的地址作为相应形参的值传递给被调函数,在被调函数中使用形参时,实现方式是沿着这个指针找到调用者指明的内存位置。因此,改变形参看起来就像是改变了实参一样。但是如果实参是一个表达式,那么在调用之前首先会对表达式求值,然后他的值被存放在一个该值自己的位置上。改变性参会改变这个位置上的值,但是对调用函数的数据没有影响。

曾经我一度认为我自己是把这个知识点彻底掌握的,但今天学习一个开源框架时,作者的一顿操作把我整蒙圈了,于是乎又重新测试总结了一下。

  • 方法参数是基本类型时,传递的是值。
    Java中总共有八种基本类型:byte,short,int,long,char,float,double,Boolean
  • 方法参数是一个引用类型时,传递的是对象的引用,也就是对象在堆内存的地址的引用,而非一个对象的地址。
    引用类型有:数组变量,类类型变量,接口类型变量。

如果你对JVM内存划分掌握的还可以的话,就很好理解这些名词和例子,如果不了解也没关系,想了解可以看这里https://blog.csdn.net/qq_42570601/article/details/99751338

举个例子:

		int[] array = { 0 };
		int m = 0;
		Token token = new Token("t0");
  • array是一个数组变量,他是引用类型,他里面存的值是数组的第一个元素的地址值。这里强调一下,array是个引用类型,但是array[0]他不是引用类型而是基本类型,前者指向数组的第一个元素的地址(因为数组是一块连续的内存,所以只要找到第一个元素,就能找到数组的所有元素),后者存放着数组的第一元素的值。
  • m是一个基本类型的变量,他里面存的是0本身。
  • token是一个类类型变量,他是引用类型,里面存的是对象new Token("t0")在堆中的地址值。
  • 注意:地址值和地址不是一回事,前者是你们家的门牌号,后者是你们家的们,通过门牌号可以找到你们家的门,你就好比是token,会记住你们家的门牌号,然后回家。如果说有一天,你忘了你们家的门牌号(token被赋值其他的地址值,new Token("t456")什么的)你就会找不到你们家的门,但是你们家的门牌号不会发生变化,门也不会发生变化,只是你门家丢了个傻儿子而已(皮一下很开心)。

看下面:

在这里插入图片描述

咋还是用代码说话:

  • 注意每个方法中对参数的处理方式

    package czfsTest;
    
    public class Token {
    
    	private String name;
    
    	public Token() {
    	}
    
    	public Token(String name) {
    		this.name = name;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
    
    
    public class Main {
    
    	public void arrayM(int[] array1) {	//重点是里面对参数的处理方式
    		array1 = new int[] { 1 };
    //		array[0] = 1;
    	}
    
    	public void intM(int m1) {
    		m1 = 1;
    	}
    
    	public void tokenM(Token token1) {	//重点是里面对参数的处理方式
    		token1 = new Token("t1");
    //		token.setName("t1");
    
    	}
    
    	public static void main(String[] args) {
    		Main ma = new Main();
    		int[] array = { 0 };
    		int m = 0;
    		Token token = new Token("t0");
    
    		System.out.println("传值前:");
    		System.out.println("array[0]:  " + array[0]);
    		System.out.println("m:  " + m);
    		System.out.println("token:  " + token.getName());
    
    		ma.arrayM(array); // 传递的是地址值
    		ma.intM(m); // 传的是值
    		ma.tokenM(token);
    
    		System.out.println("传值后:");
    		System.out.println("array[0]:  " + array[0]);
    		System.out.println("m:  " + m);
    		System.out.println("token:  " + token.getName());
    
    	}
    
    }
    

    输出结果:
    在这里插入图片描述
    这是为什么呢,为什么引用传递和值传值,前后都没有发生变化?
    在这里插入图片描述
    (边看代码边看图)以token为例:token先把new Token("t0")的地址值传给token1,两个变量共同指向同一内存,但是在tokenM方法的内部,又给token1付了新的内存地址(上图中的红线变成了黄线,红线消失),这时候token所指向的对象依旧是new Token("t0");这个对象,没有发生变化,所以token是不会发生变化的。array也是一样的道理!

    Token token = new Token("t0");
    //这里调用
    ma.tokenM(token);
    
    public void tokenM(Token token1) {
    		token1 = new Token("t1");
    	}
    
  • 继续看下面这种处理方式

    public class Main {
    
    	public void arrayM(int[] array1) {
    //		array1 = new int[] { 1 };
    		array1[0] = 1;
    	}
    
    	public void intM(int m1) {
    		m1 = 1;
    	}
    
    	public void tokenM(Token token1) {
    //		token1 = new Token("t1");
    		token1.setName("t1");
    
    	}
    
    	public static void main(String[] args) {
    		Main ma = new Main();
    		int[] array = { 0 };
    		int m = 0;
    		Token token = new Token("t0");
    
    		System.out.println("传值前:");
    		System.out.println("array[0]:  " + array[0]);
    		System.out.println("m:  " + m);
    		System.out.println("token:  " + token.getName());
    
    		ma.arrayM(array); // 传递的是地址值
    		ma.intM(m); // 传的是值
    		ma.tokenM(token);
    
    		System.out.println("传值后:");
    		System.out.println("array[0]:  " + array[0]);
    		System.out.println("m:  " + m);
    		System.out.println("token:  " + token.getName());
    
    	}
    
    }
    

    输出结果:
    在这里插入图片描述
    有没有和上面的结果不一样???

    在这里插入图片描述

(边看代码边看图)继续以token为例,注意看每个方法内部对引用变量处理的方式:token把对象new Token("t0")的地址值传给token1,这时候,token1也指向了new Token("t0"),从方法内部通过token1对对象的处理,处理的都是new Token("t0")对象(里面的 t0 变成 t1 ),因为token也指向new Token("t0"),两次输出的内容才会不一样,这也就是所谓的引用传值。

Token token = new Token("t0");
//这里调用
ma.tokenM(token);

public void tokenM(Token token1) {
		token1.setName("t1");
}

都看到这里了,那就再看一个例子噻:

有两个类,就是为了举例方便,随便起的名字。如果上面的东西看懂了,下面的很容易理解,这里主要想强调的就是,值传递与引用传递,不仅仅只是在方法参数里才会遇到,其他地方也会遇到,比如方法的返回值上面。仔细推敲理解吧!

public class Banana {
	String name;
	int age;

	public Banana(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "[name = " + name + ",age = " + age + "]";
	}
}
public class Student {
	Map<String, Banana> map = new HashMap<String, Banana>();

	/*
	 * 在当前map中查找,是否有name这个key对应的value 
	 * 如果有,返回这个value,如果没有,新建一个value放进map,并返回value
	 */
	Banana enterLocal(String name) {
		if (map.containsKey(name)) {
			return map.get(name);
		} else {
			Banana banana = new Banana(name);
			map.put(name, banana);
			return banana;
		}
	}

	public static void main(String[] args) {
		Student stu = new Student();
		String name = "007";
		/*
		 * 其实我写这个例子想要表达的点也在这个地方:
		 * 方法enterLocal创建了一个name属性的Banana类的对象,别忘了Banana类有两个属性哈
		 * 这个时候age属性是0,int类型的默认值
		 */
		Banana ba = stu.enterLocal(name);
		System.out.println(ba.toString());// [name = 007,age = 0]
		/*
		 * 通过ba引用变量 (编程思想里面叫句柄) 对age属性赋值
		 * 这时候因为ba指向的还是map里面的那个对象的地址,所以这里赋值,实际上是对map里面的对象操作
		 */
		ba.age = 18;
		System.out.println(stu.enterLocal(name).toString());// [name = 007,age = 18]
	}
}

封装类有一个自动装箱和自动拆箱,所以这个的理解和普通的引用类型的理解又不太一样
我想专门再写一篇自动装箱和自动拆箱的博客,只要明白自动装箱拆箱,这个点就很好理解!等写好后会把博文地址放在这里。

public class Student {
	public static void main(String[] args) {

		Integer x0 = new Integer(0);
		Integer x1 = new Integer(1);
		// 调用swap(Integer,Integer)
		swap(x0, x1);
		// x0=0, x1=1
		System.out.println("swap(Integer,Integer):x0=" + x0 + ",   x1=" + x1);

		StringBuffer sA = new StringBuffer("A");
		StringBuffer sB = new StringBuffer("B");
		// 输出为:sA=A, sB=B
		System.out.println("Original:sA=" + sA + ",   sB=" + sB);
		append(sA, sB);
		// 输出为:sA=AB, sB=B
		System.out.println("Afterappend:sA=" + sA + ", sB=" + sB);
	}

	// Integer 是按引用传递的,但是Integer 类没有用于可以修改引用所指向值的方法,不像StringBuffer
	public static void swap(Integer n1, Integer n2) { // 传递的是a 的引用,但引用本身是按值传递的
		Integer tmp = n1;
		n1 = n2;
		n2 = tmp;
	}

	// StringBuffer和Integer一样是类,同样在方法中是引用传递,
	// 但是StringBuffer类有用于可以修改引用所指向值的方法,如.append
	public static void append(StringBuffer n1, StringBuffer n2) {
		n1.append(n2);
		n2 = n1;
	}
}
		// intValue和valueof的区别和联系
		// intvalue返回的是int值,而 valueof 返回的是Integer的对象,它们的调用方式也不同
		int x = x0.intValue();
		Integer s = Integer.valueOf(x0);
		/*
		 * x == s输 出为true 
		 * 这里面涉及到一个自动打包解包的过程,如果jdk版本过低的话没有这个功能的,所以输出的是false
		 * 现在新版本的jdk都有自动打包解包功能了,所以回是true
		 */
		System.out.println("compare:int=" + x + ",   Integer=" + s + "  " + (x == s)); // true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值