值传递和引用传递

此篇博客只是copy下列博客或文章整理出来的,谢绝转载,侵权删除:

https://www.cnblogs.com/dolphin0520/p/3592498.html

https://zwmf.iteye.com/blog/1738574

https://www.cnblogs.com/aspirant/p/10320652.html

https://www.zhihu.com/question/31203609


概念

  • 按值调用:一个方法接收的是调用着提供的值(值传递)
  • 按引用调用:一个方法接收的是调用者提供的变量地址(如果是C语言的话来说就是指针啦,当然java并没有指针的概念)。

如果想搞明白值传递和引用传递需要搞明白对象对象引用

一、对象对象引用

对象

首先我们应该明白对象对象引用不是一回事。

《Java编程思想》原话说:“按照通俗的说法,每个对象都是某个类(class)的一个实例(instance),这里,‘类’就是‘类型’的同义词。”

从这句话可以得知:对象类的实例。比如,可以把人类看做类的实例,那么具体到某个人的张三他就是对象.


对象对象引用

再读《Java编程思想》的一段话:“每种编程语言都有自己的数据处理方式。有些时候,程序员必须注意将要处理的数据是什么类型。你是直接操纵元素,还是用某种基于特殊语法的间接表示(例如C/C++里的指针)来操作对象。所有这些在 Java 里都得到了简化,一切都被视为对象。因此,我们可采用一种统一的语法。尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“引用”(reference)。”

"操纵的标识符实际是指向一个对象的“引用”(reference)"这句话明确的指出引用对象不是一回事。

为了方便说明,定义一个简单的类:

class Person {
    
    String name;
    int age;
    
}

有了这个类,就可以用它来创建对象:

Person person1 = new Person();

通常把这条语句的动作称之为创建一个对象,其实,它包含了四个动作。

  • 1)右边的“new Person”,是以Person类为模板,在堆空间里创建一个Person类对象(也简称为Person对象)。
  • 2)末尾的()意味着,在对象创建后,立即调用Person类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。
  • 3)左边的“Person person1”创建了一个Person类的引用变量。所谓Person类引用,就是以后可以用来指向Person这一类对象对象引用
  • 4)“=”操作符使对象引用指向刚创建的那个Person类的对象

为了更加清楚了解原理,把Person person1 = new Person() 这条语句拆成两部分:

Person person1;//第一句

person1 = new Person(); //第二句

为了形象地说明对象、引用及它们之间的关系,可以做一个或许不很妥当的比喻。对象好比是一只很大的气球,大到我们抓不住它。引用变量是一根绳, 可以用来系汽球。

如果只执行了第一条语句,还没执行第二条,此时创建的引用变量person1还没指向任何一个对象,它的值是null.(引用变量可以指向某个对象,或者为null。)

此时的person1好比一根还没有系上任何一个汽球的绳1。

执行了第二句后,一只汽球1做出来了(new一个Person类对象),并被系在person1这根绳上。我们抓住这根绳,就等于抓住了那只汽球1

在原来的基础上增加多几句代码:

Person person1;//第一句

person1 = new Person(); //第二句

Person person2; //第三句

person2 = person1; //第四句

person2 = new Person(); //第五句

person1 = person2; //第六句

执行到第三句代码就又做了一根绳2,还没系上汽球。

执行第四句代码,这里发生了复制行为,名为person1的对象引用复制给了person2(要说明的是,对象本身并没有被复制),person2也是对象引用,相当于绳2也系在汽球1上。

执行第五句代码,则引用变量person2改指向第二个对象,绳2系向新的汽球。

  • 从以上叙述再推演下去,我们可以获得以下结论:

    • 1.一个对象引用可以指向0个或1个对象(一根绳子可以不系汽球,也可以系一个汽球);
    • 2.一个对象可以有N个引用指向它(可以有N条绳子系住一个汽球)。

按上面的推断,person1也指向了第二个对象。这个没问题。问题是第一个对象呢?没有一条绳子系住它,它飞了。多数书里说,它被Java的垃圾回收机制回收了。这不确切。正确地说,它已成为垃圾回收机制的处理对象。至于什么时候真正被回收,那要看垃圾回收机制的心情了。

由此看来,下面的语句应该不合法吧?至少是没用的吧?

new Person();

它是合法的,而且可用的。譬如,如果我们仅仅为了打印而生成一个对象,就不需要用引用变量来系住它。最常见的就是打印字符串:

System.out.println(“Hello World”);

字符串对象“Hello World”在打印后即被丢弃。有人把这种对象称之为临时对象。

对象与引用的关系将持续到对象回收。


值传递

  • Java只有一种参数传递方式:那就是按值传递,即Java中传递任何东西都是传值。如果传入方法的是基本类型的东西,你就得到此基本类型的一份拷贝。如果是传递引用,就得到引用的拷贝。

基本类型

public class TestBasicType {

    //基本类型的参数传递
    public static void testBasicType(int m) {
        System.out.println("m=" + m);//m=50
        m = 100;
        System.out.println("m=" + m);//m=100

    }

    public static void main(String[] args) {
        int i = 50;
        testBasicType(i);
        System.out.println("i=+"+i);//i=50
        
        //只是将值复制给参数m,m和i是两回事
    }

}


引用类型

public class Person {



    public int age;//Person的属性,全局变量,初始age为0


    public void addAge(){
        this.age = this.age+1;
    }


  
    public static void changeAge(Person p){
        Person p1 = p;    //这里将对象引用p复制给p1,p和p1指向同一对象,因此无论p还是p1操纵对象,对象的内容都会改变
        p1.addAge();      //p1操纵 对象发生改变,p1指向的对象的age=1
        System.out.println("p1.age="+p1.age); //因此p1.age=1
    }

    public static void main(String[] args) {

        Person p = new Person();//创建一个Person对象,Person p 创建一个Person类的对象引用
        changeAge(p);//将引用p复制给了这个方法的形参
        System.out.println("p.age="+p.age);//p和p1指向同一对象,因此p.age=1
    }

}

可以将上面的p1比喻为汽车的刹车,p比作汽车的油门,汽车的速度同时受到油门和刹车的控制,无论踩下油门还是刹车,
车速必有变化。当然控制车速的还会有离合,可以假设离合为新的并指向车速的引用p2,那么车速就由p,p1,p2控制,也就是说可以多个引用指向一个对象。


下面我们通过一个反例来证明值传递

public class CallByValue {

    private static User user=null;
    private static User stu=null;

    /**
     * 交换两个对象
     * @param x
     * @param y
     */
    public static void swap(User x,User y){
        User temp =x;
        x=y;
        y=temp;
        //只是复制了引用给了参数x,y
        //x,y变化影响不了外面的引用user,stu
    }


    public static void main(String[] args) {
        user = new User("user",26);
        stu = new User("stu",18);
        System.out.println("调用前user的值:"+user.toString());
        System.out.println("调用前stu的值:"+stu.toString());
        swap(user,stu);
        System.out.println("调用后user的值:"+user.toString());
        System.out.println("调用后stu的值:"+stu.toString());
    }
}
调用前user的值:User [name=user, age=26]

调用前stu的值:User [name=stu, age=18]

调用后user的值:User [name=user, age=26]

调用后stu的值:User [name=stu, age=18]

即使java函数在传递引用数据类型时,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用。


  • 总结:
    • 一个方法不能修改一个基本数据类型的参数(数值型和布尔型)。
    • 一个方法可以修改一个引用所指向的对象状态,但这仍然是按值调用而非引用调用。
    • Java只存在值传递。上面两种传递都进行了值拷贝的过程。

"="号

Java中对 = 的理解很重要啊!!

  • = 赋值操作 :在java中,= 是一个动作,一个可以改变内存状态的操作,一个可以改变变量的符号,而+ - * /却不会。

  • 这里的赋值操作其实是包含了两个意思:

    • 1.放弃了原有的值或引用.
    • 2.得到了=右侧变量的值或引用。
  • 对于基本数据类型变量,=操作是完整地复制了变量的值。换句话说,“=之后,你我已无关联”.

  • 对于非基本数据类型变量,= 操作是复制了变量的引用。

  • 参数本身是变量,所有我们对变量的操作、变量能有的行为,参数都有,参数传递本质就是一种 = 操作。

posted @ 2019-05-13 15:00  奇异果与奇迹果  阅读(150)  评论(0编辑  收藏  举报