java的传值调用什么_Java的传值调用

(本文非引战或diss,只是说出自己的理解,欢迎摆正心态观看或探讨)

引子

之所以写这篇文章是因为前些天写了一篇《Java中真的只有值传递么?》探讨了网上关于Java只有值传递的说法,当时写这篇文章的缘由是因为之前看的文章讲解的Java只有值传递,讲的不是让我很明白,没有拿出比较专业的解释或定义,没有说服我。而我在《Java中真的只有值传递么?》这篇文章中又做了一些解读,发现自己也是没有抓住重点,这才有了今天这篇文章,对之前的这篇文章做一个补充。

从那篇文章后,我了解到Java的参数传递其实牵涉到了Java语言的设计中的参数传递方式,可能在语言设计之时就考虑了这个问题,所以在工作之余自己简单的研究了一下,最终也能根据自己的理解解释一下关于Java是值传递还是引用传递的说法。

Java 是引用传递还是值传递现在有以下这些说法:

1、值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。

2、传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。

3、Java中只有值传递。

关于这个问题应该是分情况讨论的,存在即合理,或许在不同的认识下有不同的说法,也不能简单的就说是值传递还是引用传递。

对或错都是相对的。

回顾

在谈这个问题之前我们先了解下值传递和引用传递的概念及现象。可以简单的通过几个例子来讲解的,大概是这样的。

值传递

例子1:

public static void main(String[] args){

TestJavaParamPass() tjpp = new TestJavaParamPass();

int num = 10;

tjpp.change(num);

System.out.println("num in main():"+i);

}

public void change(int param){

param = 20;

System.out.println("param in change():"+param);

}

控制台输出:

param in change():20

num in main():10

mian()方法中的int变量num传递给change()方法,change()方法接收到后将值改变为20。通过看控制台输出,main()方法中的num变量的值没有改变。

结论:实参没有被形参影响,基本类型是值传递。

引用传递

例子2:

public static void main(String[] args){

TestJavaParamPass() tjpp = new TestJavaParamPass();

User user = new User();

user.setName("Jerry");

tjpp.change(user);

System.out.println("user in mian():"+user);

}

public void change(User param){

param.setName("Tom");

System.out.println("param in change():"+param);

}

控制台输出:

param in change():User(name=Tom}

user in mian():User(name=Tom}

main()方法中的user变量传递给change()方法,change()方法改变了其name属性值。通过看控制台输出,main()方法中的user变量的name属性值发生改变。

结论:形参变了实参也变了,引用类型是引用传递。

特殊的值传递

例子3:

public static void main(String[] args){

TestJavaParamPass() tjpp = new TestJavaParamPass();

String name = "Jerry";

tjpp.change(name);

System.out.println("name in main():"+i);

}

public void change(String param){

param = "Tom";

System.out.println("param in change():"+param);

}

控制台输出:

param in change():Tom

name in mian():Jerry

String也是引用类型的数据类型,为什么值没改变?

因为在change()方法里param = "Tom";相当于param = new String("Tom");就相当于param被重新赋值指向了另外一个对象。所以,其实String类型传的是引用,只不过被重新赋值指向了别的对象了,没有修改原对象。即,String本质上还是引用传递,表像上是值传递。

结论:基本类型是值传递,引用类型是引用传递,String是特殊的值传递。

看到这样的结论,没有去深究过,可能大部分程序员的认知都是这样的。

根据上面的例子我们先初步给值传递和引用传递下个定义,以及解释为什么大多数程序员都将String理解为是特殊的值传递。

概念提取

与其叫概念提取还不如叫结论总结呢。

值传递:基本类型的变量在被传递给方法时,传递的是该变量的值(即复制自己的值传递给方法)。

引用传递:引用类型的变量在被传递给方法时, 传递的是该变量的引用(即自己所指向的内存地址)。

为什么说String是特殊的值传递:是因为String和基本类型从表象来说表现出来的结果是一样,大概是为了便于记忆这个结果才这样说的吧。但是要知道String也是传递的引用,只不过它的引用被重新赋值,指向了别的对象了,所以不会影响原值。所以String不能简单的说是值传递。

而仅仅根据上面的实验就给值传递,引用传递下这样的结论是不是太草率了?

解析

对于文章开始时提到的那些说法,前两种可以这样解释:

大概是因为int没有因为change方法而改变原值,所以就说它传过去的是自身的值,因而叫值传递;User对象经过change方法后,对象的数据变了,就认为是因为实参和形参指向的是同一片内存空间,内存空间的数据变了就都变了,传过去的是引用所以就说对象是引用传递。这样说的侧重点是传递的东西。

所以,如果从传递的东西的角度来看这两种说法也是没问题的呀。

至于Java只有值传递的说法,我查阅了一些资料结合网上的文章了解到了求值策略这个名词,这大概牵涉到了语言本身的设计。所以就从这些名词来探究Java的方法调用时参数传递的奥秘。

我们先来看看这些编程语言里关于参数传递函数调用有关的术语。

(以下术语来自Wiki )

求值策略(Evaluation strategy)

在计算机科学中,求值策略(英语:Evaluation strategy)是确定编程语言中表达式的求值的一组(通常确定性的)规则。 重点典型的位于函数或算子上——求值策略定义何时和以何种次序求值给函数的实际参数,什么时候把它们代换入函数,和代换以何种形式发生。

求值策略:是一组求值规则,用来定义如何为函数的实际参数求值。它是用来规定程序语言在方法、函数或过程调用时的传参策略,是在程序语言设计时就应该考虑的问题。而下面的这几个调用方式都属于求值策略。

传值调用(Call by value)

“传值调用”求值是最常见的求值策略,C和Scheme这样差异巨大的语言都在使用。在传值调用中实际参数被求值,其值被绑定到函数中对应的变量上(通常是把值复制到新内存区域)。如果函数或过程能把值赋给它的形式参数,则被赋值的只是局部拷贝——就是说,在函数返回后调用者作用域里的曾传给函数的任何东西都不会变。

传值调用不是一个单一的求值策略,而是指一类函数的实参在被传给函数之前就被求值的求值策略。 尽管很多使用传值调用的编程语言(如Common Lisp、Eiffel、Java)从左至右的求值函数的实际参数,某些语言(比如OCaml)从右至左的求值函数和它们的实际参数,而另一些语言(比如Scheme和C)未指定这种次序(尽管它们保证顺序一致性)。

传值调用:在传值调用中,实际参数被求值后传递给被调函数。也就是说传值调用是实参在被传给函数之前就被求值的一种求值策略。

在Java中的体现

那什么叫实参在被传给函数之前就被求值呢?求的是谁的值呢?这个值又是什么呢?是怎么求得呢?

带着这些疑问,我们来看下面的例子。

如下,在调用change()方法时实参为i,当程序执行到change(i)这一行时,i是实参,这时i就要被求值了,会求出i的值即4传给change()方法;change()的形参a拿到的是实参i的值,是一个拷贝副本。

伪代码:

void change(int a){//拿到求得的实参的值

a = a/2;

}

int i = 4;

change(i);

System.out.print(i);

因为是值的副本,所以在函数内对形参操作不会影响实参,所以输出是4。

这里我们举的例子是基本类型int类型的。那对于引用类型呢?

同样需要对实参求值,这时得到的值是实参的地址值,形参拿到的是实参的地址值,这个地址值指向的是u1等号后面使用new关键字开辟出来的那片内存空间,所以此时u2也指向这片内存空间,所以打印出来u2将会和u1输出同样的内容。

伪代码:

void change(User u2){//拿到求得的实参的地址值

System.out.print(u2);

u2 = getNewUser();

u2.setName("$%#@*")

System.out.print(u2);

}

public static void main(){

User u1 = new User();

u1.setName("1234");

change(u1);

System.out.print(u1);

}

然后,我们模仿上面的change(int a)的方法里,对形参接收到的值进行改变。注意,是形参的值,对change(User u2)来说,形参u2接收到的值是地址值,我们咋改变它呢?我们可以让u2指向另一个内存空间,即通过getNewUser()方法获取一个新的User对象,用这种方式给u2一个新的地址值,这不就改变了吗。

此时我们看输出,发现经过change()方法实参u1打印信息没变,为什么?因为u1的地址值没变,且u2是获得新地址后(指向另一片内存),在新的这片内存里操作的,故而不会影响到之前的那片内存空间的数据。

这样基本类型和引用类型的实验方法是一样的,看到的效果也是一样的,即实参没有随形参的改变而改变。

总结

最后得出的结论:从语言设计的角度,Java的方法调用时参数的求值策略是传值调用(Call by value)的。

如果我们想表达引用类型传递的是引用,仅仅是想说传的是引用不是别的东西的话,我们可以说的明确点:引用类型传的是引用,和程序语言中的求值策略不沾边 。那你说的引用传递就和求值策略中的传引用调用没关系,只是想表达传的是引用的话也没人会说你错。由此来看文章开头提到的前2种说法是不是也有解释的余地?

存在即合理,不同的说法有不同的前提条件不同的解释方式。如果是从程序语言设计的求值策略角度来问Java是哪种求值策略的话,那可以肯定的说是传值调用(Call by value)。

(以下术语摘抄自Wiki。能力有限,对这样些专业名词还无法完美解读,仅供参考)

附录

传引用调用和传共享对象调用都是求值策略的一种。

传引用调用(Call by reference)

在“传引用调用”求值中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。通常函数能够修改这些参数(比如赋值),而且改变对于调用者是可见的。因此传引用调用提供了一种调用者和函数交换数据的方法。传引用调用的语言中追踪函数调用的副作用比较难,易产生不易察觉的bug。

很多语言支持某种形式的传引用调用,但是很少有语言默认使用它。FORTRAN II 是一种早期的传引用调用语言。一些语言如C++、PHP、Visual Basic .NET、C#和REALbasic默认使用传值调用,但是提供一种传引用的特别语法。

在那些使用传值调用又不支持传引用调用的语言里,可以用引用(引用其他对象的对象),比如指针(表示其他对象的内存地址的对象)来模拟。C和ML就用了这种方法。这不是一种不同的求值策略(语言本身还是传值调用)。它有时被叫做“传地址调用”(call by address)。这可能让人不易理解。在C之类不安全的语言里会引发解引用空指针之类的错误。但ML的引用是类型安全和内存安全的。

类似的效果可由传共享对象调用(传递一个可变对象)实现。比如Python、Ruby。

例:C用指针模拟的传引用调用

void modify(int p, int* q, int* r){

p = 27; // passed by value: only the local parameter is modified

*q = 27; // passed by value or reference, check call site to determine which

*r = 27; // passed by value or reference, check call site to determine which

}

int main(){

int a = 1;

int b = 1;

int x = 1;

int* c = &x;

modify(a, &b, c); // a是传值调用, b通过创建指针实现引用传递,c是按值传递的指针

//b and x are changed

return 0;

}

传共享对象调用(Call by sharing)

此方式由Barbara Liskov命名[1],并被Python、Java(对象类型)、JavaScript、Scheme、OCaml等语言使用。

与传引用调用不同,对于调用者而言在被调用函数里修改参数是没有影响的。如果要达成传引用调用的效果就需要传一个共享对象,一旦被调用者修改了对象,调用者就可以看到变化(因为对象是共享的,没有拷贝)。比如这段Python代码:

def f(l):

l.append(1)

l = [2]

m = []

f(m)

print(m)

会输出[1]而不是[2]。因为列表是可变的,append方法改变了m。而赋值局部变量l的行为对外面作用域没有影响(在这类语言中赋值是给变量绑定一个新对象,而不是改变对象)。

使用C/C++语言的程序员可能因不能用指针等使函数返回多个值而感到不便,但是像Python这样的语言提供了替代方案:函数能方便的返回多个值,比C++11的std::tie更加简单。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/333597.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 命令之 apt -- Debian Linux 新一代的软件包管理工具

文章目录 一、命令介绍二、常用命令三、常用选项四、命令示例(一)安装、更新和删除软件包安装指定的软件包安装软件包的同时修复依赖问题安装指定版本的软件包安装本地的 deb 包文件删除软件包,保留配置文件删除软件包,不保留配置文件将已经安装的但是不再使用的软件包删除…

liskov替换原则_坚实原则:Liskov替代原则

liskov替换原则以前,我们深入研究了坚实的原则,包括单一责任和开放/封闭原则。 Liskov替代原则(LSP)是子类型关系的一种特殊定义,称为(强)行为子类型, 假设对象S是对象T的子类型&a…

java 聚合_Java聚合

聚合如果一个类有一个类的实体引用(类中的类),则它称为聚合。 聚合表示HAS-A关系。考虑有一种情况,Employee对象包含许多信息,例如:id,name,emailId等。它包含另一个类对象:address,…

Linux 命令之 dpkg --Debian 的一个底层包管理工具

文章目录 一、命令介绍二、deb 软件包命名规则三、软件包管理相关文件/目录四、dpkg 数据库五、子命令六、常用选项(一)安装、升级和删除软件包(二)查询和检验软件包(三)其它七、命令示例(一)安装、升级和卸载软件包安装指定的软件包解开软件包到系统目录但不配置配置软…

java jep_Java 10 – JEP 286:局部变量类型推断

java jepJava 10即将发布,RC Build可在此处获得 。 可在此处找到此发行版的目标功能。 在针对Java 10的所有JEP中,开发人员社区中最有趣且最受关注的是286:Local-Variable Type Inference 。 什么是局部变量类型推断? 我们在Java…

java ffmpeg 视频水印_java 实现视频转换通用工具类:视频加水印-Ffmpeg(五-1)

java 实现视频转换通用工具类:视频相互转换-Ffmpeg(三)说明:视频加水印支持右上角、左上角、左下角、右下角、底部居中几个方位1.根据水印文字---添加视频水印/*** 根据水印文字---添加视频水印* param srcPath 原视频路径* param tarVideoPath 生成后的…

手动 clone 安装 Homebrew

文章目录 手动 clone 安装 HomebrewHomebrew 换源替换 homebrew 源替换 homebrew-core 源替换 homebrew-bottles 源bash用户zsh 用户更新 Homebrew手动 clone 安装 Homebrew 首先进入 homebrew 目录:cd "$(brew --repo)/Library/Taps/homebrew/"创建目录 homebrew-c…

lambda 匿名内部类_Lambda运行时内部:窥视无服务器巢穴

lambda 匿名内部类有没有想过lambda里面是什么样的? 不要怀疑了 让我们找出答案。 自从2014年问世以来,AWS的lambda函数就成为一个热门话题,在无服务器计算领域开辟了全新的历史。 无状态,零维护,按次执行的好东西实际…

java VM argument_java之program arguments与VM arguments

program arguments 中的值作为 args[] 的参数传入的,而 VM Arguments 是设置的虚拟机的属性。program arguments 是要传给你的应用程序的,它通过主函数中的 args 来传值。 VM arguments是系统的属性,要传给 java 虚拟机的。如图:是…

java iterator 嵌套_Java中的集合嵌套

import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Set;public classDemo06 {public static voidmain(String[] args) {//大 MapHashMap> oraclenew HashMap>();//小MapHashMap java1018 new HashMap();HashMap java1227 new H…

Linux/Unix 如何查看 man 搜索到的手册页(manual page)的位置及复制手册页的内容

文章目录命令 man 是如何搜索手册页的?如何查看手册页所在的路径通过管道输出给 vim命令 man 是如何搜索手册页的? man uses a sophisticated method of finding manual page files, based on the invocation options and environment variables, the …

xml不利于调试_流利的接口不利于维护

xml不利于调试流畅的界面 (最初由Martin Fowler 创造)是一种与OOP中的对象进行通信的非常便捷的方式。 它使他们的外墙更易于使用和理解。 但是,它破坏了它们的内部设计,使它们更难以维护。 Marco Pivetta在他的博客文章Fluent In…

java输入月份求天数_java输入年份,月份,输出当月天数

package 第二次作业; public class 第二题 { //java文件名 public static void main(String[] args) { //相当于创建主函数 String 年份 = javax.swing.JOptionPane.showInputDialog("输入一个年份:"); //求出输…

java 换行 运算符格式_Java代码样式运算符换行格式

java 换行 运算符格式上周,我发现了什么决定了我在较长时间里一直在努力解决的问题的决定:是否放置&& , ||之类的运算符 或在同一行或下一行。 在构造布尔表达式或隐含字符串时,我们可能需要中断长行。 根据《 Google Ja…

pipeline java_架构模式:pipeline

知名的 Pipeline 模式unix 的 pipelinecat helloworld.txt | grep "hello world" | rev | > output.txt读取文本内容,并过滤 “hello world”,然后反转字符,将最终结果输出到 output.txtWeb 框架中间件Laravel 中的管道Laravel …

MacOS 系统使用命令安装软件包

文章目录 使用 Homebrew 安装软件包先安装 Homebrew使用命令 brew 安装软件包使用命令 installer 安装 pkg 软件包使用命令安装 dmg 格式的软件包包含普通的应用程序文件的 dmg 文件如何使用命令完成安装呢?包含 pkg 安装程序的 dmg 文件又该如何通过命令完成安装呢?Linux 操…

身份证验证 校验码_用户身份验证最佳做法清单

身份证验证 校验码用户身份验证是每个Web应用程序共享的功能。 我们已经实施了很多次了,所以早就应该完善它了。 然而,错误无时无刻不在。 造成这种情况的部分原因是,可能出错的事情列表很长。 您可能会错误地存储密码,可能会具有…

mysql查询不确定的信息_mysql 07.18

1.索引搜索引导,索引是一个特殊的数据结构,期存储的是数据的关键信息与详细详细的位置对应关系。目的:加速查询。索引的影响:不是说拥有索引就能加速,得看你的查询语句有没有正确使用索引,索引也需要占用额…

MacOS 常用命令汇总

文章目录设置环境变量列出所有可以更新的软件包直接更新所有可以更新的软件包打包压缩文件忽略 Mac OS 文件系统中的扩展属性忽略 Mac OS 专有的隐藏文件创建 DMG 格式的文件修改 DMG 文件的大小修改 DMG 格式中的加密口令挂载 DMG 格式的文件推出 DMG 文件将 ISO 格式的文件转…

java多功能钟_Java 11将包含更多功能

java多功能钟Java 11即将发布的功能是什么?它与Java 9和10有何不同? Java 10可能是新手,但现在该谈论Java 11了。Oracle迈向更快的发布周期意味着更多的特性和功能以比以往更快的速度出现。 尽管距离Java 11发行还有六个月的时间&#xff0…