欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-20 23:58:34
...

引言

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

小结

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

相关标签: java