Java参数传递机制:by value Or by reference?

  • 一、前言
  • 二、by value
  • 三、by reference


一、前言

春招在即,相信很多小伙伴像我一样奔波于毕业与就业之间,但是学习不可少噢。今天抽点时间和大家聊聊 Java开发岗中,面试官常问或者笔试中常考的Java传参机制。

很多时候人们会因为[ Java 操控的是object reference(对象引用,虽然网上很多文章说是对象,这里不反驳)] 而臆测[ Java传参数也是以by reference (地址)方式进行传递]。因此,如果不能很准确地理解其中奥妙,很多时候在面试或者笔试的时候,则可能造成不必要的错误。(郑重声明:Java 传参只有 by value 方式)

举个栗子噢!!

import java.awt.Point;
public class PassByValue {
  public static void modifyPoint(Point pt,int i) {
    pt.setLocation(10, 10);
    i = 10;
    System.out.println("During modifPoint "+"pt = "+pt+"and i = "+i);
  }
  
  public static void main(String[] args) {
    Point p = new Point(1,1);
    int i = 1;
    System.out.println("Before modifPoint "+"pt = "+p+"and i = "+i);
    modifyPoint(p, i);
    System.out.println("During modifPoint "+"pt = "+p+"and i = "+i);
  }
}

说明:该段代码先是建立一个 Point 对象并设其初值为(1,1),然后将其赋值给Object reference 变量 p,同时对基本数据类型 int i 赋予数值 1。之后调用 static modifyPoint() 方法,并传入 p 和 i。modifyPoint() 方法中对参数 pt 调用 setLocation() ,将其坐标改为(10,10)。同时将参数i也赋值为10,最后返回并打印 p 和 i 的值。

大家思考一下,看看这段代码的输出为何?

程序输入如下:

java reference占用内存大小 java intbyreference_数据值


很明显,modifyPoint()方法改变了创建的Point对象,那为什么没有改变基本数据类型 int i 的值呢?保持思考,一起来探讨一下吧!!!

二、by value

其实Java传参方式是以 (by value)传值 OR (by reference)传引用 一直都在Java里有较大的争论,那么先来认识一下Java中的传值吧。

来,举个栗子 !!

public class PassByValue {
  public static void swap(int a,int b) {
    int temp = a;
    a = b ;
    b = temp;
  }
  
  public static void main(String[] args) {
    int a = 10;
    int b = 100;
    System.out.println("a 和 b 交换前:a="+a+" b="+b);
    swap(a, b);
    System.out.println("a 和 b 交换后:a="+a+" b="+b);
  }
}

说明:创建两个基本数据类型 int a=10;int b=100,并调用swap()方法交换两个变量的值,然后分别输出调用方法前后a和b的值。程序输入如下:

java reference占用内存大小 java intbyreference_Java_02

为什么这里 a 和 b 没有交换数据值呢?要深入了解这个原因,那我们来看看这个过程中a和b都发生了什么。

java reference占用内存大小 java intbyreference_System_03


(调用swap()方法,创建a,b变量副本)

java reference占用内存大小 java intbyreference_Java_04


(执行完成swap()方法)

因此,无论是 modifyPoint() 方法中的变量 int i ,还是 swap() 方法中的int a;int b 都是通过by value 方式传递,所以它们都是收到了对应变量的一个副本,然后对应方法把变量的副本对应的数据值改变了,但是不会影响变量本身的数据值,所以输出还是变量本身。

三、by reference

拿第一个栗子举例来说,看完by value的数据变化过程以及我们一直强调Java只有by value方式传参,那么岂不是所有改变数据的方法都是无法达到目标? 为什么modifyPoint()为什么把Point对象变量数据值改变了呢?

这里就不得不说,对象的存储了。我们都知道基本数据类型变量或者常量一般都存储在本地方法栈中,能很快的获取到变量,但是由于对象所具有的数据量较多,而本地方法栈较小,无法存储较多的对象。因此,聪明的工程师们想到了堆,使用堆来存储对象本身,而使用方法栈来存储对对象的引用(地址),使得我们可以很快的找到对象的同时又不会造成对栈内存的消耗。

java reference占用内存大小 java intbyreference_数据值_05


因此,我们平日里创建的 new Object(),获得是对象引用,而不是对象本身。举个栗子吧!!!

User user = new User();

user 便是对象 User 的引用,而不是对象本身,很多文章都在说是对象,我们可以“认为”是对象,但是必须知道是对象的引用,而不是对象本身。

扯远了…

事实上在 modifyPoint() 方法中,是在与[ Point 对象的 reference 的副本 ]打交道,而不是与 [ Point 对象的副本 ]打交道。因此(继续拿第一个栗子来说)p 是个 object reference ,并且 Java 以 by value 方式传递参数。更明确地说,此时Java以by value 方法传递 object reference 。

当p从main()被传入modifyPoint()时,传递的是p(也就是一个reference)的副本。所以modifyPoint()是在与同一个对象打交道,只不过通过别名pt罢了。在进入modifyPoint()之后和执行之前,这个对象都如下:

java reference占用内存大小 java intbyreference_Java_06


(执行modifyPoint()方法前)

java reference占用内存大小 java intbyreference_数据值_07


(modifyPoint()通过pt改变Point对象)

因此,我们可以通过使用“by reference”方式进行传值方式,达到修改数据的方式。如果还有人说Java有引用传参方式,那么请把这篇文章甩给它。

那么如果我们希望一个引用类型的数据传参后数据不被改变,我们该怎么办呢?这里以为modifyPoint()为例子进行说明!

  • 对modifyPoint()传递一个Point对象的克隆件(Clone)
  • 令Point对象为immutable(不可改变的)

这里给出两种解决方案,大家可以自己去学习并验证。学习就是体会,自己学到的才是自己的噢。