整理的关于Java参数传递的研究

本文深入探讨Java中的参数传递机制,明确指出Java采用值传递而非引用传递,并通过具体示例对比基本类型与对象引用的参数传递差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    Javaeye论坛看到这么一个帖子,是关于老生常谈的java参数传递到底是传引用还是传值的,讨论的比较透彻原帖地址在
http://www.javaeye.com/topic/4189?page=1。
    各种观点,众说纷纭,又对有错(个人观点),这里整理一下。
    先说我的观点:
    其实传值和传引用,都是个说法问题,《Practical Java》一书将的第一个实践便是这个,书中说的很清晰透彻,作者认为java中所有的参数传递都是值传递,方法所修改的都是参数的副本,当为基本类型( primitive types)是,就是该值的副本,当为对象(object)时,则为该对象引用(object reference)的副本。
    《core java》第一卷Chapter 4有专门的一章《Method Parameters》更翔实的解释了这个问题,这里直接引用原文了:

Method Parameters

Let us review the computer science terms that describe how parameters can be passed to a method (or a function) in a programming language. The term call by value means that the method gets just the value that the caller provides. In contrast, call by reference means that the method gets the location of the variable that the caller provides. Thus, a method can modify the value stored in a variable that is passed by reference but not in one that is passed by value. These "call by . . . " terms are standard computer science terminology that describe the behavior of method parameters in various programming languages, not just Java. (In fact, there is also a call by name that is mainly of historical interest, being employed in the Algol programming language, one of the oldest high-level languages.)

The Java programming language always uses call by value. That means that the method gets a copy of all parameter values. In particular, the method cannot modify the contents of any parameter variables that are passed to it.

For example, consider the following call:

double percent = 10;
harry.raiseSalary(percent);

No matter how the method is implemented, we know that after the method call, the value of percent is still 10.

Let us look a little more closely at this situation. Suppose a method tried to triple the value of a method parameter:

public static void tripleValue(double x) // doesn't work
{
x = 3 * x;
}

Let's call this method:

double percent = 10;
tripleValue(percent);

However, this does not work. After the method call, the value of percent is still 10. Here is what happens:

  1. x is initialized with a copy of the value of percent (that is, 10).

  2. x is tripled—it is now 30. But percent is still 10 (see Figure 4-6).

    Figure 4-6. Modifying a numeric parameter has no lasting effect


  3. The method ends, and the parameter variable x is no longer in use.

There are, however, two kinds of method parameters:

  • Primitive types (numbers, Boolean values)

  • Object references

You have seen that it is impossible for a method to change a primitive type parameter. The situation is different for object parameters. You can easily implement a method that triples the salary of an employee:

public static void tripleSalary(Employee x) // works
{
x.raiseSalary(200);
}

When you call

harry = new Employee(. . .);
tripleSalary(harry);

then the following happens:

  1. x is initialized with a copy of the value of harry, that is, an object reference.

  2. The raiseSalary method is applied to that object reference. The Employee object to which both x and harry refer gets its salary raised by 200 percent.

  3. The method ends, and the parameter variable x is no longer in use. Of course, the object variable harry continues to refer to the object whose salary was tripled (see Figure 4-7).

    Figure 4-7. Modifying an object parameter has a lasting effect


As you have seen, it is easily possible—and in fact very common—to implement methods that change the state of an object parameter. The reason is simple. The method gets a copy of the object reference, and both the original and the copy refer to the same object.

Many programming languages (in particular, C++ and Pascal) have two methods for parameter passing: call by value and call by reference. Some programmers (and unfortunately even some book authors) claim that the Java programming language uses call by reference for objects. However, that is false. Because this is such a common misunderstanding, it is worth examining a counterexample in detail.

Let's try to write a method that swaps two employee objects:

public static void swap(Employee x, Employee y) // doesn't work
{
Employee temp = x;
x = y;
y = temp;
}

If the Java programming language used call by reference for objects, this method would work:

Employee a = new Employee("Alice", . . .);
Employee b = new Employee("Bob", . . .);
swap(a, b);
// does a now refer to Bob, b to Alice?

However, the method does not actually change the object references that are stored in the variables a and b. The x and y parameters of the swap method are initialized with copies of these references. The method then proceeds to swap these copies.

// x refers to Alice, y to Bob
Employee temp = x;
x = y;
y = temp;
// now x refers to Bob, y to Alice

But ultimately, this is a wasted effort. When the method ends, the parameter variables x and y are abandoned. The original variables a and b still refer to the same objects as they did before the method call (see Figure 4-8).

Figure 4-8. Swapping object parameters has no lasting effect


This discussion demonstrates that the Java programming language does not use call by reference for objects. Instead, object references are passed by value.

Here is a summary of what you can and cannot do with method parameters in the Java programming language:

  • A method cannot modify a parameter of primitive type (that is, numbers or Boolean values).

  • A method can change the state of an object parameter.

  • A method cannot make an object parameter refer to a new object.

The program in Example 4-4 demonstrates these facts. The program first tries to triple the value of a number parameter and does not succeed:

Testing tripleValue:
Before: percent=10.0
End of method: x=30.0
After: percent=10.0

It then successfully triples the salary of an employee:

Testing tripleSalary:
Before: salary=50000.0
End of method: salary=150000.0
After: salary=150000.0

After the method, the state of the object to which harry refers has changed. This is possible because the method modified the state through a copy of the object reference.

Finally, the program demonstrates the failure of the swap method:

Testing swap:
Before: a=Alice
Before: b=Bob
End of method: x=Bob
End of method: y=Alice
After: a=Alice
After: b=Bob

As you can see, the parameter variables x and y are swapped, but the variables a and b are not affected.

C++ NOTE

C++ has both call by value and call by reference. You tag reference parameters with &. For example, you can easily implement methods void tripleValue(double& x) or void swap(Employee& x, Employee& y) that modify their reference parameters.


Example 4-4. ParamTest.java
 1. public class ParamTest
2. {
3. public static void main(String[] args)
4. {
5. /*
6. Test 1: Methods can't modify numeric parameters
7. */
8. System.out.println("Testing tripleValue:");
9. double percent = 10;
10. System.out.println("Before: percent=" + percent);
11. tripleValue(percent);
12. System.out.println("After: percent=" + percent);
13.
14. /*
15. Test 2: Methods can change the state of object
16. parameters
17. */
18. System.out.println("/nTesting tripleSalary:");
19. Employee harry = new Employee("Harry", 50000);
20. System.out.println("Before: salary=" + harry.getSalary());
21. tripleSalary(harry);
22. System.out.println("After: salary=" + harry.getSalary());
23.
24. /*
25. Test 3: Methods can't attach new objects to
26. object parameters
27. */
28. System.out.println("/nTesting swap:");
29. Employee a = new Employee("Alice", 70000);
30. Employee b = new Employee("Bob", 60000);
31. System.out.println("Before: a=" + a.getName());
32. System.out.println("Before: b=" + b.getName());
33. swap(a, b);
34. System.out.println("After: a=" + a.getName());
35. System.out.println("After: b=" + b.getName());
36. }
37.
38. public static void tripleValue(double x) // doesn't work
39. {
40. x = 3 * x;
41. System.out.println("End of method: x=" + x);
42. }
43.
44. public static void tripleSalary(Employee x) // works
45. {
46. x.raiseSalary(200);
47. System.out.println("End of method: salary="
48. + x.getSalary());
49. }
50.
51. public static void swap(Employee x, Employee y)
52. {
53. Employee temp = x;
54. x = y;
55. y = temp;
56. System.out.println("End of method: x=" + x.getName());
57. System.out.println("End of method: y=" + y.getName());
58. }
59. }
60.
61. class Employee // simplified Employee class
62. {
63. public Employee(String n, double s)
64. {
65. name = n;
66. salary = s;
67. }
68.
69. public String getName()
70. {
71. return name;
72. }
73.
74. public double getSalary()
75. {
76. return salary;
77. }
78.
79. public void raiseSalary(double byPercent)
80. {
81. double raise = salary * byPercent / 100;
82. salary += raise;
83. }
84.
85. private String name;
86. private double salary;
87. }

”  
    《thinking in java》(3rd edition)中也对传值和传引用的问题进行了阐述,作者的看法更宽松一些,传达的意思却是一样的,这里引用一些内容,(由于ebook本身的原因,不能复制文字,这里直接用图片了):



”。

      
<think>好的,我现在需要回答用户关于Java中如何传递函数作为参数的问题。首先,我要回忆一下自己了解的Java相关知识。记得在Java 8之前,函数不能直接作为参数传递,但可以通过接口和匿名内部类来实现类似的效果。Java 8引入了Lambda表达式和函数式接口,这大大简化了这个过程。 接下来,我需要查阅用户提供的引用资料,确保答案准确。引用[1]提到,Java中不能直接传递函数,但可以通过接口或抽象类的实例,使用回调机制。引用[2]、[3]、[4]、[5]都提到了使用Lambda表达式和函数式接口的方法,特别是java.util.function包中的接口如Function、Consumer等。 然后,我需要整理这些信息,分步骤说明。首先介绍传统方法(接口和匿名类),然后讲Java 8之后的Lambda和内置函数式接口。可能需要给出示例代码,比如使用自定义接口和Lambda的例子,以及使用Function接口的例子。 还要注意用户提到的格式要求:行内数学公式用$...$,独立公式用$$...$$,不过这个问题可能不涉及数学公式,所以可能不需要。另外,生成相关问题,并正确添加引用标识。 需要确保回答结构清晰,逐步引导用户理解。可能需要分点说明,每个方法配一个示例代码块。同时,引用资料中的示例代码需要正确引用,比如引用[1]、[2]、[3]中的例子。 最后,生成相关问题,考虑用户可能感兴趣的后续问题,比如性能比较、应用场景、其他函数式接口等。</think>在Java中,函数不能直接作为参数传递,但可以通过以下两种主要方法实现类似效果: ### 一、传统方法:接口与匿名类 通过定义包含单个抽象方法的接口(函数式接口),将接口实例作为参数传递: ```java // 自定义函数式接口 interface MathOperation { int execute(int a, int b); } public class Calculator { public static void main(String[] args) { calculate(5, 3, new MathOperation() { @Override public int execute(int a, int b) { return a * b; } }); } public static void calculate(int x, int y, MathOperation op) { System.out.println(op.execute(x, y)); // 输出15 } } ``` [^1][^3] ### 二、Java 8+方法:Lambda与内置函数式接口 利用Java 8引入的Lambda表达式和预定义的函数式接口: ```java import java.util.function.Function; public class Main { public static void main(String[] args) { // Lambda表达式定义函数 Function<Integer, Integer> square = x -> x * x; // 传递函数参数 processNumber(5, square); // 输出25 } public static void processNumber(int num, Function<Integer, Integer> func) { System.out.println(func.apply(num)); } } ``` [^2][^4][^5] ### 主要实现方式对比 | 方法 | 优点 | 局限性 | |---------------|-------------------------|-----------------------| | 自定义接口 | 灵活,可读性好 | 需要预先定义接口 | | 内置函数式接口 | 代码简洁,类型安全 | 适用标准场景 | | Lambda表达式 | 语法简洁,开发效率高 | 需要Java 8+环境支持 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值