java的参数传递机制你真的了解吗?

引言

  其实我很早之前就想写关于java参数传递的内容,因为早在大学老师讲编程基础的时候就教我们参数的传递机制:值传递和引用传递,在很多java相关的书籍上也说明了java的传递机制有两种:值传递和引用传递,其实这种说法是不严谨的,严格来讲,java的参数传递机制只有一种——值传递,下面且听我细细道来。

值传递和引用传递

  下面的表格阐述了何为值传递和引用传递:

传递类型 说明
值传递 在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数,如我们常见的基本数据类型作为参数即为值传递
引用传递 在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数

  下面我们来写一段代码来说明问题,有助于加深理解:

public class passByValue {

    public static void main(String[] args) {

        int i = 10;
        changeValue(i);
        System.out.println(i);

    }


    private static void changeValue(int i) {

        i = 100;
    }
}

  代码十分简单,int类型为基本数据类型,所以毫无疑问,这是值传递,在changeValue方法中修改的 i 其实是一个复制品,所以修改复制品,并不会影响原装产品的值,所以运行结果输出为10。
  让我们再看下面一段代码:

public class passByValue {

    public static void main(String[] args) {

        String str = "test";
        changeValue(str);
        System.out.println(str);

    }


    private static void changeValue(String str) {

        str = "change";
    }
}

  上面的代码运行结果是什么呢,相信有少部分人会有所犹豫,但经过仔细考虑给出的答案应该是“test”,没错,答案是正确的,但是其中的原因是什么呢,String并不是基本数据类型,为什么也出现了值传递才有的结果?
  这个时候有些人会说String是final类型的,不允许引用发生变化,所以给它再次赋值对原来的String来说是无效的,听起来似乎有些道理,但这种说法其实并不对,属于偷换概念。final修饰的类不能被继承,但是被final修饰的类声明的变量的值是可以改变的,只要这个变量没有被final修饰即可。
  好,让我们再看一段代码,如下:

public class passByValue {


    public static void main(String[] args) {

        Person person = new Person();
        person.setName("小红");
        changeValue(person);
        System.out.println(person);

    }


    private static void changeValue(Person person) {

        person.setName("小明");

    }
}

class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

  首先我声明了一个Person对象的引用,然后经过changeValue方法setName,那么最终执行的结果输出的person对象的名字是小红还是小明呢?哈哈,相信这个答案百分之99.9的人都可以答对,必然是小明!person是Person对象的引用,在changeValue方法中改变了它的name属性,所以最后的name属性也发生了变化,这也符合引用传递的特点。
  好,让我们继续往下看:

public class passByValue {


    public static void main(String[] args) {

        Person person = new Person();
        person.setName("小红");
        changeValue(person);
        System.out.println(person);

    }


    private static void changeValue(Person person) {

        person = new Person();
        person.setName("小明");

    }
}

class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

  这段代码跟上段代码的区别就在于changeValue方法里加了一行 person = new Person();,结果就大不一样了,没错,最终name还是小红,而不是小明,怎么回事,不是引用传递吗,如果是直接传递引用的内存地址,那么 new 了一个Person之后,地址应该指向 new 的Person才对,最后应该是小明呀,为什么还是小红呢?好了,让我们画图来分析一下吧,相信会让你豁然开朗!

堆栈图解

  让我们假设person是引用传递,那么在堆栈当中应该是如下的状态:
引用传递
  很明显,最后的结果就应该是小明,但是执行结果却是小红,好了,不卖关子,这是因为java并不是引用传递,而是值传递,提一句,上图在C#中是适用的。
  好了,画图解析一下吧!
值传递
  如上图,这就是真相,相信你已经一目了然,没错,正如你所看到的,在进行参数传递的时候,jvm复制了一份传递的引用指向小红,然后当 new 一个Person对象的时候指向新的内存空间,所以再去改变属性不会影响原来的引用,改变的只是副本的属性,导致程序最终的执行结果还是小红。 new 与 不 new 的区别就在于person副本有没有指向新的内存空间。

小结

  好,到这里关于Java的参数传递便讲解完毕,因为我发现身边的很多同事和朋友对这个概念模糊不清,所以一直想写这么一篇博客。至于为什么java采用副本的方式传递参数,我想大概是为了安全吧,举个例子,一个人你好久不见,再见的时候他肯定还是本人,有可能他换了发型,换了名字,但他还是他,不可能是别人,如果再见的时候他变成了另外一个人岂不是太奇怪了!所以说java的开发者也许考虑到了这一点,而且这样做也可以有效的避免空指针异常的发生,因为有人总喜欢把传过来的对象赋值为null,这样副本的好处就体现出来了,原来的引用不会因此也变为null。希望这篇文章对你有所帮助,感谢您的观看,再见!

发布了26 篇原创文章 · 获赞 99 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/m0_37719874/article/details/103504784