Java 方法中的值传递与引用传递

Java 方法中的值传递与引用传递


总结

  1. 基本数据类型:方法形参改变不产生影响,原因在于方法传递过程中传递的是值的拷贝
  2. 包装数据类型:方法形参改变不产生影响,原因在于final修饰value
  3. String 字符串:方法形参改变不产生影响,原因在于final修饰value
  4. 引用类型未初始化:方法形参改变不产生影响,原因在于栈中句柄指向null,在堆空间中无地址,在方法中重新初始化,生成新的地址,但此地址未回传给调用方
  5. 引用类型已实例化:方法形参改变会产生影响,原因在于方法传递过程中传递的是地址,方法中与调用方法处均指向堆中同一个对象

一、值传递与引用传递

其实按值还是按引用的区别在于“是否在传递的时候进行对象的内存拷贝”,java中基本类型是由于在JVM中存储区域不同于普通对象所以传递前会拷贝,传递的是拷贝后的值,但是对象传递的时候不拷贝,直接传“引用值”,指向同一片对象堆内存区域


二、基本数据类型
 

    public class IntegerTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		int a = 10 ;
		Integer b = new Integer(10);
		IntegerTest test = new IntegerTest();
		test.change(a);
		test.change(b);
		System.out.println(a);
                // 10
		System.out.println(b);
                // 10
	}

	public int change(Integer a){
		a = a + 10 ;
		return a ;
	}
    }

main() 方法向 change() 中传递的是 栈中a 所存储的内容 10

change() 方法,形参 a ,虽然与 main() 中,名称相同,但是两个不同的变量

此时传递的是数值的拷贝
 


三、包装数据类型

// final 修饰:类不可被子类继承
public final class Integer extends Number implements Comparable<Integer> {
    // final 修饰:存储堆空间的地址值不可改变
    private final int value;
}

其底层实现中value是final修饰,是不可改变的;与String 类似,其值是不可变更的

若欲通过change() 修改数值:通过change()的返回值,重新赋值给 main()中的a

更改 a 句柄在栈中的指向,即指向新的堆空间中的地址,而原有的对象在堆空间中的地址并没有改变,只是此时没有指向,等待回收

结论:

基本数据类型及对应的包装类在传递过程中,不会改变其所指向的内容
 



四、String 类型

    public class StringChangeTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		String str = "abc" ;
		new StringChangeTest().change(str);
		System.out.println(str);
	}

	public void change(String str){
		str = "bcd";
	}
    }

字符串只要赋值就相当于新new一个对象,字符串变量指向这个新new的对象,之前的对象就成了没有引用指向的对象了

1. String 的两种声明方式
 

String str = "abc"
栈中建立 str
判断堆中是否已有"abc",无则创建新的匿名空间,存放 "abc"
匿名空间首地址存放在 str 中,即 str 指向堆中的此空间

备注:
提高程序运行效率,优选第一种方式

 

String str = new String("abc")
栈中建立 str
判断堆中是否已有"abc",无则创建新的匿名空间,存放 "abc"
堆中开辟新的空间,存放 "abc"
新的空间首地址存放在 str 中,匿名空间因无引用,过后被GC回收


2.
String str = "abc" ;
未显示调用 new 操作,在编译时 相当于 new String("abc");

3.
change(String str);
此处的str 不同于 main() 中的 str ,相当于重新开辟了一个空间,虽然名称相同
调用方法时,main() 方法将 str = "abc" 的堆空间地址传递给了 change() 中的str
此时 main() change() 中的两个不同的 str 均指向同一个堆内存块

但 str = "bcd" ; 相当于 在堆中新开辟了一块空间,存放 "bcd",并将地址存放在了str 中
即 change() 中的 str 指向了新的堆中的地址 -- "bcd" 的地址,与 main() 中的 str 指向的 "abc" 无关了,在 change() 中对 str 的修改,也与 main() 中无关,所以值没有改变



五、引用类型

    public class ArrayIntegerTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		int [] a1 = {1,2,3,4,5};
		new ArrayIntegerTest().change(a1);
		for(int i = 0 ; i < a1.length ; i++){
		    System.out.println(a1[i]);
		}

		int [] a12 = new int[]{1,2,3,4,5};
		new ArrayIntegerTest().change(a12);
		for(int i = 0 ; i < a12.length ; i++){
		    System.out.println(a12[i]);
		}

		Integer [] a2 = {1,2,3,4,5};
		new ArrayIntegerTest().change(a2);
		for(int i = 0 ; i < a2.length ; i++){
		    System.out.println(a2[i]);
		}

		char [] s1 = {'1','2','3'};
		new ArrayIntegerTest().change(s1);
		System.out.println(new String(s1));
	}

	public void change(int[] a){
		a[0] = 10 ;
	}

	public void change(Integer[] a){
		a[0] = 10 ;
	}

	public void change(char[] a){
		a[0] = 'a';
	}
    }



main() 中,建立引用对象,在堆中开辟新的空间
change() 中,形参声明新的对象,与main的不同
main() 调用 change() 时,将 栈中存放的堆中空间的首地址传递给 change() 中的参数
两者指向同一个数据地址
所以,change() 中的操作影响 main() 中的数据内容

 

    public class PersonTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Person p = new Person();
		p.setAge(10);

		new PersonTest().change(p);
		System.out.println(p.getAge());
	}

	public void change(Person p){
		p.setAge(20);

		p = new Person();
		p.setAge(30);
	}
    }

    class Person{
	private int age ;

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}


    }

输出:20

调用 change() ,与 main() 中 p 是两个不同的句柄,但此时指向同一个堆中的地址
setAge(20) 有效
重新new 一个p ,此时 change() 中的 p 与 main() 的 p 指向的堆中的地址已经不一样了
new 新开辟空间,一定与之前的不同
所以 setAge(30) 对 main() 中的无影响

public class NumberAndAddressTest {

    private Long id ;

    private List<Integer> integerList ;

    public static void main(String[] args) {
        int a = 1 ;
        changeInt(a);
        System.out.println(a);
        // 1

        Integer b = 1 ;
        changeInt(b);
        System.out.println(b);
        // 1

        String str = "a" ;
        changeStr(str);
        System.out.println(str);
        // a

        NumberAndAddressTest test = null ;
        changePO(test);
        System.out.println(test);
        // null

        test = new NumberAndAddressTest();
        test.setId(1L);
        changeId(test);
        System.out.println(test);
        // id:2

        changeIntList(test);
        System.out.println(test.getIntegerList());
    }

    private static void changeInt(int a){
        a = 2 ;
    }

    private static void changeStr(String str){
        str = "abc" ;
    }

    private static void changePO(NumberAndAddressTest test){
        test = new NumberAndAddressTest();
        test.setId(2L);
    }

    private static void changeId(NumberAndAddressTest test){
        test.setId(2L);
    }

    private static void changeIntList(NumberAndAddressTest test){
        // 此处对 test 中的属性进行初始化,但 test 中属性原来指向 null
        List<Integer> integerList = test.getIntegerList();
        // 此处虽然进行了初始化,但两者并没有指向同一个地址
        if(null == integerList){
            integerList = new ArrayList<>();
        }
        integerList.add(1);
        // 只有通过重新绑定两者之间的关系
//        test.setIntegerList(integerList);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "id:"+this.id;
    }

    public void setIntegerList(List<Integer> integerList) {
        this.integerList = integerList;
    }

    public List<Integer> getIntegerList() {
        return integerList;
    }
}

 

    public static void main(String[] args) {

    	// 此处传递空引用,并没有传递对象的地址指向
        List<Integer> list = null;
        // 方法中重新生成对象,但是地址并没有回传给调用方
        remove(list);
        // 所以方法传值过程中没有产生影响
        System.out.println(list);

        list = new ArrayList<>();
        remove(list);
        System.out.println(list);

        list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7,8,9));
        removeAll(list);
        System.out.println(list);
    }

    private static void remove(List<Integer> list){
        list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7,8,9));
        list.remove(1);
    }

    private static void removeAll(List<Integer> list){
        list.remove(1);
    }

六、生产问题

  • 数据传递过程中集合变为null
  • 逐步在测试环境添加日志打印结果
  • 定位在main方法中调用结果为null,在 add() 方法中有结果
  • 但add()方法中对于引用数据类型的修改没有影响到main方法中的对象
package test;

import java.util.ArrayList;
import java.util.List;

/**
 * DEMO 模拟
 * 异常描述:
 * 1.主方法调用add方法:
 * 2.add方法内部对对象的属性进行处理
 * 3.主方法使用对象属性,结果为空
 */
public class Test {

    private List<Integer> integerList;

    public List<Integer> getIntegerList() {
        return integerList;
    }

    public void setIntegerList(List<Integer> integerList) {
        this.integerList = integerList;
    }

    /**
     * 场景模拟
     * 1.integerList1 模拟问题异常场景
     * 2.integerList2 模拟问题修复场景
     * 异常疑惑
     * 1.为何仅仅是移动了取值的顺序就可以解决取值为空的场景?
     * 2.为何开发时没有发现会出现此类问题?
     * 异常分析
     * 1.主方法与从方法进行的是值传递,又因为是引用数据类型,从方法内部的修改会影响到主方法的属性,因为指向同一个堆空间地址
     * 2.特殊场景:
     * a.主方法中对象的属性为null,主方法中对象的属性 integerList 在堆空间中无指向
     * b.又把 integerList 赋值给了 integerList1 , integerList1 为 null
     * c.add 方法中对 integerList 重新初始化,生成堆空间,并添加内容
     * d.主方法将 test 对象的地址给了 add() 方法中的 test 形参,形参的修改影响主方法中的 test 对象
     * e.test.getIntegerList() 取到的是 add() 方法中的初始化后的对象信息
     * 问题反思
     * 1.因为方案设计时只考虑到了主方法中的属性一定非空的场景,那么add方法与主方法中的test对象均指向同一个堆空间
     * 2.墨菲定律:如果可能出错就一定会出错;考虑为题要全面,如果空了该如何处理?
     * 3.需要重点注意的场景:形参传递/数据拷贝/数值校验
     * @param args
     */
    public static void main(String[] args) {

        Test test = new Test();
//        List<Integer> integerList = test.getIntegerList();
//        if(null == integerList){
//            integerList = new ArrayList<>();
//            test.setIntegerList(integerList);
//        }
        List<Integer> integerList1 = test.getIntegerList();
        add(test);
        List<Integer> integerList2 = test.getIntegerList();
        System.out.println(integerList1);
        System.out.println(integerList2);
//        for (Integer integer : integerList1) {
//            System.out.println(integer);
//        }
    }

    private static void add(Test test){
        List<Integer> integerList = test.getIntegerList();
        if(null == integerList){
            integerList = new ArrayList<>();
            test.setIntegerList(integerList);
        }
        integerList.add(2);
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值