Java 基础之方法中的参数


本篇文章部分转载自以下博客: https://blog.csdn.net/cauchyweierstrass/article/details/49047217

一、概述

对于 C++ 来说,由于有指针的存在,使得一个函数的参数可以有多种参数传递方式:值传递、引用传递以及指针传递。而 Java 是没有指针的概念的,这也就意味着 Java 只存在值传递,不过这似乎不符合我们直观的感受,看下面例子:

public class Book {

    private String bookName;

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public static void main(String[] args) {
        Book book = new Book();
        book.setBookName("围城");
        System.out.println("before invoke change, book name is " + book.getBookName());
        change(book);
        System.out.println("after invoke change, book name is " + book.getBookName());
    }

    public static void change(Book book) {
        book.setBookName("活着");
    }
}

运行结果为:

before invoke change, book name is 围城
after invoke change, book name is 活着

你可能会觉得这和我们刚才说的 Java 只存在值传递的说法相矛盾,但事实上这是因为它是将对象的引用地址作为参数传递给方法,在方法内部就可以通过该引用地址访问对象,改变对象的内容,所以它的本质还是值传递,只不过传递的值是对象的引用地址。

下面我们对参数进行更加详细的讨论。

二、Java 的数据类型

Java 的数据类型可分为两大类,分别是 基本数据类型 引用数据类型

基本数据类型为以下 8 种:

  • int
  • boolean
  • double
  • byte
  • long
  • char
  • float
  • short

引用数据类型为以下 4 种:

  • String
  • 接口
  • 数组

在引用数据类型中 String 是比较特殊的一个,它会在后面单独列出来说。接下来我们就先针对基本数据类型作为参数类型进行分析。

1. 基本数据类型作为参数

这 8 个基本数据类型的值都有一个共同的特点,那就是它们都是不可变的。在作为参数传进方法时,传进去的只是值的一个拷贝,即使在方法内部改变了值,对于该参数的原值也没有丝毫的影响,示例代码如下所示:

public static void main(String[] args) {
    int num = 1;
    System.out.println("before invoke change, num = " + num);
    change(num);
    System.out.println("after invoke change, num = " + num);
}

public static void change(int num){
    num = 2;
}

输出结果如下所示:

before invoke change, num = 1
after invoke change, num = 1

它的示意图如下所示:

在这里插入图片描述
其它类型的数据也是与上面结果一致的,这里就不一一展示了。接下来看看引用数据类型作为参数的例子。

2. 引用数据类型作为参数

首先我们先来看看数组作为参数的情况。

2.1 数组作为参数

我们直接看例子:

public static void main(String[] args) {
    int[] num = {1,2};
    System.out.println("before invoke change, num = " + num[0] + " " + num[1]);
    exchange(0,1 , num);
    System.out.println("after invoke change, num = " + num[0] + " " + num[1]);

}

public static void exchange(int a, int b, int[] num){
    int temp = num[a];
    num[a] = num[b];
    num[b] = temp;
}

输出结果如下:

before invoke change, num = 1 2
after invoke change, num = 2 1

可以看到将数组作为参数传递进方法 exchange 之后,在方法内对于数组的交换也会影响到外部数组 num,这是因为值传递传的实际上是数组 num 的引用地址,所以在方法内部实际上就是对该地址上的 num 数组进行操作的。它的示意图如下所示:

在这里插入图片描述

2.2 类或接口作为参数

同样直接看例子,我们还是以最开头概述中的 Book 类为例,再做一些增加:

public class Book {

    private String bookName;

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public static void main(String[] args) {
        Book book = new Book();
        book.setBookName("围城");
        System.out.println("before invoke changeValue, book name is " + book.getBookName());
        changeValue(book);
        System.out.println("after invoke changeValue, book name is " + book.getBookName());

        book = new Book();
        book.setBookName("围城");
        System.out.println("before invoke changeRef, book name is " + book.getBookName());
        changeRef(book);
        System.out.println("after invoke changeRef, book name is " + book.getBookName());
    }

    public static void changeValue(Book book) {
        book.setBookName("活着");
    }

    public static void changeRef(Book book){
        Book newBook = new Book();
        newBook.setBookName("活着");
        book = newBook;
    }
}

我们这里有 2 个操纵 Book 的方法,一个是 changeValue,它改变的是参数值的成员;另一个是 changeRef,它改变的是参数的引用,我们在 main 中分别调用它们,输出结果如下:

before invoke changeValue, book name is 围城
after invoke changeValue, book name is 活着
before invoke changeRef, book name is 围城
after invoke changeRef, book name is 围城

可以看出,changeValue 这个方法对外部的 Book 实例有影响,而 changRef 则对外部 Book 实例毫无影响,它们之间的区别在哪里呢?

我们分别看到这两个方法:

public static void changeValue(Book book) {
    book.setBookName("活着");
}

changeValue 中,我们直接调用 Book#setBookName 这个方法,我们在前面也有提到过,对于引用数据类型来说,值传递传的是对象的引用地址,所以在 changeValue 中我们对参数的操作实际上会对引用地址上的对象进行操作,所以方法外部的 Book 对象也会发生改变。

接下来看 changRef 方法的代码:

public static void changeRef(Book book){
    Book newBook = new Book();
    newBook.setBookName("活着");
    book = newBook;
}

可以看到这个方法内部其实是又创建了一个 Book 实例,然后将这个实例赋值给参数 book。这实际上所做的操作是:外部的 Book 对象将它的对象引用地址传给参数 book 时,此时参数 book 指向的就是外部 Book 对象所在的地址,但是接下来的代码:book = newBook 却又将 book 指向的地址又改成了 newBook 实例所在的地址,所以参数 book 此时也就相当于失去了对外部 Book 对象的地址引用,所以也就失去了对外部 Book 对象的操纵能力了。

这两个方法的示意图分别如下所示。

changeValue 方法示意图:
在这里插入图片描述
可以看到就是将参数 book 指向外部对象 book 所在的引用地址,然后对其进行操作。

changeRef 方法示意图:
在这里插入图片描述
可以看到,一开始参数 book 确实是指向外部对象 book 所在的引用地址的,但是在执行了代码:
book = newBook 之后参数 book 就改变了其指向的地址,此时它指向的是 newBook 这个实例所在的地址。

所以说,对于引用数据类型参数来说,值传递传递的就是该对象的地址,通过该地址我们可以修改它所指向的内存处的值。而改变地址的值是毫无意义的,它只会让我们失去对真实对象的掌控。

而将接口作为参数的方法和将类作为参数的方法所达成的效果是一样的,所以这里就不再赘述了。接下来我们再了解下 String 作为参数类型的时候方法是否会对外部的 String 对象造成影响。

2.3 String 作为参数

在 Java 中,我们都知道 String 就是一个类,实际上它和上面的类作为参数类型的时候的分析是一样的,但是它又具有一定的迷惑性,我们首先来看一个例子:

public static void main(String[] args) {
	String s = "Marck";
	System.out.println("before invoke change, s = " + s);
	change(s);
	System.out.println("after invoke change, s = " + s);
}

private static void change(String s){
	s = "Beryl";
}

这个例子的输出如下:

before invoke change, s = Marck
after invoke change, s = Marck

是否感觉结果和预期不太相符呢?如果你怀疑是初始化的方式不一样,那么就换一种方式,采用 new 的方式来创建一个 String 对象:

public static void main(String[] args) {
	String s = new String("Marck");   // 采用 new 方式创建
	System.out.println("before invoke change, s = " + s);
	change(s);
	System.out.println("after invoke change, s = " + s);
}

private static void change(String s){
	s = "Beryl";
}

输出结果仍然和上面是一致的,对象 s 的内容并没有发生任何改变。那么这究竟是什么原因造成的呢?接下来我们来分析下原因:

我们接下来重新回过头看一下前面将类作为参数的时候会对外部对象有影响的方法 changeValue

public static void changeValue(Book book) {
    book.setBookName("活着");
}

可以看到,它操纵的是这个地址所在对象的内容,没有进行更改地址引用的操作。如果还是不太理解的话,我们在看下面的一个方法:

public static void changeNull(Book book) {
    book = null;
}

在执行完这个方法之后,外部的对象是否就会为空呢?答案是不会,因为参数 book 实际上是失去了对外部 book 的地址引用,然后重新指向了空指针的地址,所以参数 book 相当于是失去了对外部真实对象的掌控。这点在我们前面已经说的很清楚了,那么再来看到以 String 类型作为参数的 change 方法:

private static void change(String s){
	s = "Beryl";
}

同理可得,这个方法中对于参数 s,我们没有对 s 所在地址上的内容进行任何的修改(String 对象也不允许做任何修改),而是将它直接指向到了堆上的另一个 String 对象的地址上去了。既然地址都修改了,那么参数 s 自然也就失去了对外部真实对象的掌控了,所以也就不会发生任何改变了。它的示意图如下:
在这里插入图片描述
所以事实上以 String 作为参数的时候和将类作为对象的时候本质都是一样的,这里对其讲解还是比较简单,详细的可以看下这篇博客,对于 String 作为参数的情况做了很详细的介绍:
https://blog.csdn.net/cauchyweierstrass/article/details/49047217

至此,我们对于参数的分析就到此结束了,我们接下来简单做个总结:

三、总结

  • Java 中参数的传值形式只存在值传递!
  • Java 的数据类型可分为基本数据类型(byte/int/boolean/short/long/double/float/char)和引用数据类型(类/接口/数组/String)。
  • 基本数据类型作为参数时直接拷贝它的值传入到方法中,无论方法对该值如何改变,都不会影响外部的变量。
  • 引用数据类型则是将其所在地址拷贝传递给参数,只要参数所指向的地址不发生改变,那么它可以影响外部对象的内容,即只要参数指向的地址不变,它就掌控着外部的真实对象

猜你喜欢

转载自blog.csdn.net/qq_38182125/article/details/88094475
今日推荐