深入理解Java中方法的参数传递机制

简介: Java中到底是值传递or引用传递?

形参和实参

我们知道,在Java中定义方法时,是可以定义参数的,比如:

public static void main(String[] args){
    
}

这里的args就是一个字符串数组类型的参数。

在程序设计语言中,参数有形式参数和实际参数之分,先来看下它们的定义:

形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数,简称“形参”。

实际参数:在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”,简称“实参”。

举个栗子:

public class ParamTest {
     public static void main(String[] args) {
        ParamTest pt = new ParamTest();
        // 实际参数为“张三”
        pt.sout("张三");
    }

    public void sout(String name) {
        // 形式参数为 name
        System.out.print(name);
    }   
}

上面例子中,ParamTest类中定义了一个sout方法,该方法有个String类型的参数name,该参数即为形参。在main方法中,调用了sout方法,传入了一个参数“张三”,该参数即为实参。

那么,实参值是如何传入方法的呢?这是由方法的参数传递机制来控制的。

值传递和引用传递

参数传递机制有两种:值传递和引用传递。我们先来看下程序语言中是如何定义和区分值传递和引用传递的:

值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

那么,在我们大Java中,到底是值传递还是引用传递呢?

Java中是值传递还是引用传递?

有了上面的概念,我们就可以一起来探究一下,Java中方法参数到底是值传递还是引用传递了。

先看如下代码:

public class ParamPass1 {
    public static void main(String[] args) {
        ParamPass1 p = new ParamPass1();
        int i = 10;
        System.out.println("pass方法调用前,i的值为=" + i);
        p.pass(i);
        System.out.println("pass方法调用后,i的值为=" + i);
    }

    public void pass(int i) {
        i *= 3;
        System.out.println("pass方法中,i的值为=" + i);
    }
}

上面代码中,我们在类中定义了一个pass方法,方法内部将传入的参数i的值增加至3倍,然后分别在pass方法和main方法中打印参数的值,输出结果如下:

pass方法执行前,i的值为=10
pass方法中,i的值为=30
pass方法执行后,i的值为=10

从上面运行结果来看,pass方法中,i的值是30,pass方法执行结束后,变量i的值依然是10。

可以看出,main方法里的变量i,并不是pass方法里的i,pass方法内部对i的值的修改并没有改变实际参数i的值,改变的只是pass方法中i的值(pass方法中,i=30),因为pass方法中的i只是main方法中变量i的复制品

因此同学们很容易得出结论:Java中,一个方法不可能修改一个基本数据类型的参数 ,所以是值传递

然而,结论下的还太早,因为方法参数共有两种类型:

  1. 基本数据类型
  2. 引用数据类型

前面看到的只是基本数据类型的参数,那对于引用类型的参数,又是怎么样的呢?看如下代码:

public class ParamPass2 {
    public static void main(String[] args) {
        ParamPass2 p = new ParamPass2();

        User user = new User();
        user.setName("张三");
        user.setAge(18);

        System.out.println("pass方法调用前,user=" + user.toString());
        p.pass(user);
        System.out.println("pass方法调用后,user=" + user.toString());
    }

    public void pass(User user) {
        user.setName("李四");
        System.out.println("pass方法中,user = " + user.toString());
    }
}

class User {
    /**
     * 姓名
     */
    private String name;
    
    /**
     * 年龄
     */
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

上面代码中,定义了一个User类,在main方法中,new了一个新的User对象user,然后给user对象的成员变量赋值,pass方法中,修改了传入的user对象的属性。

运行main方法,结果如下:

pass方法调用前,user= User{name='张三', age=18}
pass方法中,user = User{name='李四', age=18}
pass方法调用后,user= User{name='李四', age=18}

经过pass方法执行后,实参的值竟然被改变了!!!那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么?

有同学可能会说:难道在Java的方法中,在传递基本数据类型的时候是值传递,在传递引用数据类型的时候是引用传递?

其实不然,Java中传递引用数据类型的时候也是值传递

为什么呢?

先给大家说一下概念中的重点:

值传递,是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

引用传递,是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

总结下两者的区别:

值传递 引用传递
根本区别 会创建副本 不会创建副本
所以 函数中无法改变原始对象 函数中可以改变原始对象

敲黑板:复制的是参数的引用(地址值),并不是引用指向的存在于堆内存中的实际对象。

main方法中的user是一个引用(也就是一个指针),它保存了User对象的地址值,当把user的值赋给pass方法的user形参后,即让pass方法的user形参也保存了这个地址值,即也会引用到堆内存中的User对象。

上面代码中,之所以产生引用传递的错觉,是因为参数保存的是实际对象的地址值,你改变的只是地址值指向的堆内存中的实际对象,并没有真正改变参数,参数的地址值没有变。

下面结合生活中的场景,再来深入理解一下值传递和引用传递。

你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。

你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。

但是,不管上面哪种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?

我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。你改变的不是那把钥匙(地址值),而是钥匙打开的房子(地址值对应的实际对象)。

那我们如何真正的改变参数呢,看如下代码:

public class ParamPass3 {
    public static void main(String[] args) {
        ParamPass3 p = new ParamPass3();

        User user = new User();
        user.setName("张三");
        user.setAge(18);

        System.out.println("pass方法调用前,user= " + user.toString());
        p.pass(user);
        System.out.println("pass方法调用后,user= " + user.toString());
    }

    public void pass(User user) {
        user = new User();
        user.setName("李四");
        user.setAge(20);
        System.out.println("pass方法中,user = " + user.toString());
    }
}

class User {
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

在这段代码中,pass方法中,我们真正的改变了user参数,因为它指向了一个新的地址(user = new User()),即参数的地址值改变了。运行结果如下:

pass方法调用前,user= User{name='张三', age=18}
pass方法中,user = User{name='李四', age=20}
pass方法调用后,user= User{name='张三', age=18}

从结果看出,对参数进行了修改,没有影响到实际参数。

所以说,Java中其实还是值传递的,只不过对于引用类型参数,值的内容是对象的引用。

目录
相关文章
|
20天前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
|
21天前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
44 2
|
21天前
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
42 2
|
24天前
|
Java 程序员 UED
深入理解Java中的异常处理机制
本文旨在揭示Java异常处理的奥秘,从基础概念到高级应用,逐步引导读者掌握如何优雅地管理程序中的错误。我们将探讨异常类型、捕获流程,以及如何在代码中有效利用try-catch语句。通过实例分析,我们将展示异常处理在提升代码质量方面的关键作用。
31 3
|
24天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
25天前
|
运维 Java 编译器
Java 异常处理:机制、策略与最佳实践
Java异常处理是确保程序稳定运行的关键。本文介绍Java异常处理的机制,包括异常类层次结构、try-catch-finally语句的使用,并探讨常见策略及最佳实践,帮助开发者有效管理错误和异常情况。
72 4
|
24天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
37 2
|
28天前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
40 4
|
28天前
|
Java
深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
28 4
|
26天前
|
Java 数据处理 数据安全/隐私保护
Java处理数据接口方法
Java处理数据接口方法
26 1