文档章节

关于JAVA中String类以形参传递到函数里面,修改后外面引用不能获取到更改后的值

文文1
 文文1
发布于 2017/09/01 11:26
字数 2391
阅读 82
收藏 0
点赞 0
评论 0

一、 最开始的示例
写代码最重要的就是实践,不经过反复试验而得出的说辞只能说是凭空遐想罢了。所以,在本文中首先以一个简单示例来抛出核心话题:

public class StringAsParamOfMethodDemo {

public static void main(String[] args) {
StringAsParamOfMethodDemo StringAsParamOfMethodDemo = 
new StringAsParamOfMethodDemo();
StringAsParamOfMethodDemo.testA();
}

private void testA() {
String originalStr = "original";
System.out.println("Test A Begin:");
System.out.println("The outer String: " + originalStr);
simpleChangeString(originalStr);
System.out.println("The outer String after inner change: " + originalStr);
System.out.println("Test A End.");
System.out.println();
}

public void simpleChangeString(String original) {
original = original + " is changed!";
System.out.println("The changed inner String: " + original);
}

}

这段代码的逻辑是这样的:先赋值一个String类型的局部变量,然后把这个变量作为参数送进一个方法中,在这个方法中改变该变量的值。编译运行之后,发现输出结果是这样的:

Test A Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test A End.

这个结果表明在方法内部对String类型的变量的重新赋值操作并没有对这个String变量的原型产生任何影响。好了,这个示例的逻辑和运行结果都展示清楚了,接下来我们来对这个小程序进行分析。在这之前我们先来回顾下Java中所谓的“传值”和“传引用”问题。

二、 Java中的“传值”和“传引用”问题
许多初学Java的程序员都在这个问题上有所思索,那是因为这是所谓的“C语言的传值和传指针问题”在Java语言上同类表现。
最后得出的结论是:
在Java中,当基本类型作为参数传入方法时,无论该参数在方法内怎样被改变,外部的变量原型总是不变的,因为方法内部有外部变量的一份拷贝,对这个拷贝的更改不会改变外部变量的值。代码类似上面的示例:

int number = 0;
changeNumber(number) {number++}; //改变送进的int变量
System.out.println(number); //这时number依然为0

这就叫做“值传递”,即方法操作的是参数变量(也就是原型变量的一个值的拷贝)改变的也只是原型变量的一个拷贝而已,而非变量本身。所以变量原型并不会随之改变。

但当方法传入的参数为非基本类型时(也就是说是一个对象类型的变量), 方法里面改变参数变量的同时变量原型也会随之改变,代码同样类似上面的示例:

StringBuffer strBuf = new StringBuffer(“original”);
changeStringBuffer(strBuf) {strbuf.apend(“ is changed!”)} //改变送进的StringBuffer变量
System.out.println(strBuf); //这时strBuf的值就变为了original is changed!

这 种特性就叫做“引用传递”,也叫做传址,即方法操作参数变量时是拷贝了变量的引用,注意下传递给方法的参数为变量的引用,其实也就是指针,而后通过这个引用找到变量(在这里是对象)的真正地址,并对其进行操作。当 该方法结束后,方法内部的那个参数变量随之消失。但是要知道这个变量只是对象的一个引用而已,它只是指向了对象所在的真实地址,而非对象本身,所以它的消 失并不会带来什么负面影响。回头来看原型变量,原型变量本质上也是那个对象的一个引用(和参数变量是一样一样的),当初对参数变量所指对象的改变就根本就 是对原型变量所指对象的改变。所以原型变量所代表的对象就这样被改变了,而且这种改变被保存了下来。

了解了这个经典问题,很多细心的读者肯定会立刻提出新的疑问:“可是String类型在Java语言中属于非基本类型啊!它在方法中的改变为什么没有被保 存下来呢!”的确,这是个问题,而且这个新疑问几乎推翻了那个经典问题的全部结论。真是这样么?好,现在我们就来继续分析。

三、 关于String参数传递问题的曲解之一——直接赋值与对象赋值
String类型的变量作为参数时怎么会像基本类型变量那样以传值方式传递呢?关于这个问题,有些朋友给出过解释。
一 种解释就是,对String类型的变量赋值时并没有new出对象,而是直接用字符串赋值,所以Java就把这个String类型的变量当作基本类型看待 了。即,应该String str = new String(“original”);,而不是String str = “original”;。这种因为给String类型变量赋值方式不同是造成问题所在么?我们来为先前的示例稍微改造下,运行之后看看结果就知道了。改造后的代码如下:

private void testB() {
String originalStr = new String("original");
System.out.println("Test B Begin:");
System.out.println("The outer String: " + originalStr);
changeNewString(originalStr);
System.out.println("The outer String after inner change: " + originalStr);
System.out.println("Test B End:");
System.out.println();
}

public void changeNewString(String original) {
original = new String(original + " is changed!");
System.out.println("The changed inner String: " + original);
}

我们来看看这次运行结果是怎么样的:

Test B Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test B End.

实践证明,这种说法是错的。
实际上,字符串直接赋值和用new出的对象赋值的区别仅仅在于存储方式不同。
简单说明下:
字 符串直接赋值时,String类型的变量所引用的值是存储在类的常量池中的。因为”original”本身是个字符串常量,另一方面String是个不可 变类型,所以这个String类型的变量相当于是滴对一个常量的引用。这种情况下,变量的内存空间大小是在编译期就已经确定的。
new对象的方式是将”original”存储到String对象的内存堆空间中,而这个存储动作是在运行期进行的。在这种情况下,Java并不是把”original”这个字符串当作常量对待的,因为这时它是作为创建String对象的参数出现的。
所以对String的赋值方式和其参数传值问题并没有直接联系。总之,这种解释并不是正解。
四、 关于String参数传递问题的曲解之二——“=”变值与方法赋值的区别上面
又有些朋友认为,变值不同步的问题是处在改变值的方式上。
这种说法认为:“在Java 中,改变参数的值有两种情况,第一种,使用赋值号“=”直接进行赋值使其改变;第二种,对于某些对象的引用,通过一定途径对其成员数据进行改变,如通过对象本身的成员方法。认为对于第一种情况,其改变不会影响到被传入该参数变量的方法以外的数据,或者直接说不会改变原来的数据。而第二种方法,则相反,会影响到源数据——因为引用指向的对象没有变,对其成员数据进行改变那么实质上改变的是该对象。”这种观点说必须用类的成员变量改变成员数据才可以成功改变成员数据。
这种方式听起来似乎有些…,我们还是用老办法,编写demo,做个小试验,代码如下:

private void testC() {
String originalStr = new String("original");
System.out.println("Test C Begin:");
System.out.println("The outer String: " + originalStr);
changeStrWithMethod(originalStr);
System.out.println("The outer String after inner change: " + originalStr);
System.out.println("Test C End.");
System.out.println();
}

private static void changeStrWithMethod(String original) {
original = original.concat(" is changed!");
System.out.println("The changed inner String: " + original);
}

结果如下:

Test C Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test C End.

怎么样,这证明了问题并不是出在这,
那到底是什么原因导致了这种状况呢?
好了,下面说下我的解释。

真正答案

这个问题真正原因是因为String类的存储是通过final修饰的char[]数组来存放结果的。不可更改。所以每次当外部一个String类型的引用传递到方法内部时候,只是把外部String类型变量的引用传递给了方法参数变量。对的。外部String变量和方法参数变量都是实际char[]数组的引用而已。所以当我们在方法内部改变这个参数的引用时候,因为char[]数组不可改变,所以每次新建变量都是新建一个新的String实例。很显然外部String类型变量没有指向新的String实例。所以也就不会获取到新的更改。
下面程序例程假定tString指向A内存空间,A内存空间存放了”hello”这个字符串,然后调用modst函数将tString引用赋值给了text引用,注意是引用。确实是传址,我们知道String是不可变的,任何进行更改的操作都会产生新的String实例。所以在方法里面text指向了B空间,B空间存放了”sdf” 字符串,但是这个时候tString还是指向A空间,并没有指向B空间。

String tString = "hello";
        System.out.println(tString);
        modst(tString);
        System.out.println(tString);
        //改变text指向
public static String modst(String text) {
        return text = "sdf";
    }

两次输出结果都是

hello
hello

© 著作权归作者所有

共有 人打赏支持
文文1
粉丝 19
博文 328
码字总数 113821
作品 0
长沙
程序员
Android JNI学习(四)——JNI的常用方法的中文API

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头 ⋅ 05/09 ⋅ 0

Kotlin泛型-你可能需要知道这些

本博文主要讲解一些Kotlin泛型的问题,中间会对比穿插Java泛型。 1. 泛型类型参数 1.1 形式 我们使用泛型的形式无非是类、借口、方法几种,我们先看两个例子。 1.2 声明泛型类 和Java一样,我...

24K男 ⋅ 04/21 ⋅ 0

Android JNI学习(三)——Java与Native相互调用

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头 ⋅ 05/09 ⋅ 0

Android JNI(一)——NDK与JNI基础

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头 ⋅ 05/09 ⋅ 0

Java基础之反射(非常重要)

反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)) 一、反射的概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道...

Java-老刘 ⋅ 05/15 ⋅ 0

ThreadLocal源码分析

阅读原文请访问我的博客 BrightLoong's Blog 一. 简介 提醒篇幅较大需耐心。 简介来自ThreadLocal类注释 ThreadLocal类提供了线程局部 (thread-local) 变量。这些变量与普通变量不同,每个线...

BrightLoong ⋅ 05/28 ⋅ 0

java编程学习常见面试题及答案

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/22 ⋅ 0

Java编程学习常见的几大代码,Java开发者常犯的一些错误

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰 ⋅ 05/17 ⋅ 0

总结:深入浅出探索Java体系知识

前言 (1)编程:也就是人和计算机之间交流的过程,让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。 (2)写代码的要诀:明确需求->分析思路->确定步骤-...

iwen-J ⋅ 04/07 ⋅ 0

面向对象—this关键字的使用

一.类和对象的概述 类:描述生活中的一类事物,是一类事物所具有的共性内容。比如人类,动物类,车类。 对象:基于这个类的实体(实例),比如人类中的某一个具体的人,张三就是一个具体的对象...

权晓魏 ⋅ 04/26 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

BS与CS的联系与区别【简】

C/S是Client/Server的缩写。服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle、Sybase、InFORMix或 SQL Server。客户端需要安装专用的客户端软件。 B/S是Brower/...

anlve ⋅ 49分钟前 ⋅ 0

发生了什么?Linus 又发怒了?

在一个 Linux 内核 4.18-rc1 的 Pull Request 中,开发者 Andy Shevchenko 表示其在对设备属性框架进行更新时,移除了 union 别名,这引发了 Linus 的暴怒。 这一次 Linus Torvalds 发怒的原...

问题终结者 ⋅ 今天 ⋅ 0

在树莓派上搭建一个maven仓库

在树莓派上搭建一个maven仓库 20180618 lambo init 项目说明 家里有台树莓派性能太慢。想搭建一个maven私服, 使用nexus或者 jfrog-artifactory 运行的够呛。怎么办呢,手写一个吧.所在这个...

林小宝 ⋅ 今天 ⋅ 0

Spring发展历程总结

转自与 https://www.cnblogs.com/RunForLove/p/4641672.html 目前很多公司的架构,从Struts2迁移到了SpringMVC。你有想过为什么不使用Servlet+JSP来构建Java web项目,而是采用SpringMVC呢?...

onedotdot ⋅ 今天 ⋅ 0

Python模块/包/库安装(6种方法)

Python模块/包/库安装(6种方法) 冰颖机器人 2016-11-29 21:33:26 一、方法1: 单文件模块 直接把文件拷贝到 $python_dir/Lib 二、方法2: 多文件模块,带setup.py 下载模块包(压缩文件zip...

cswangyx ⋅ 今天 ⋅ 0

零基础学习大数据人工智能,学习路线篇!系统规划大数据之路?

大数据处理技术怎么学习呢?首先我们要学习Python语言和Linux操作系统,这两个是学习大数据的基础,学习的顺序不分前后。 Python:Python 的排名从去年开始就借助人工智能持续上升,现在它已经...

董黎明 ⋅ 今天 ⋅ 0

openJdk和sun jdk的区别

使用过LINUX的人都应该知道,在大多数LINUX发行版本里,内置或者通过软件源安装JDK的话,都是安装的OpenJDK, 那么到底什么是OpenJDK,它与SUN JDK有什么关系和区别呢? 历史上的原因是,Ope...

jason_kiss ⋅ 今天 ⋅ 0

梳理

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。 它是JS的状态容器,是一种解决问题的方式,所以即可以用于 react 也可以用于 vue。 需要理解其思想及实现方式。 应用中所有的 stat...

分秒 ⋅ 今天 ⋅ 0

Java 后台判断是否为ajax请求

/** * 是否是Ajax请求 * @param request * @return */public static boolean isAjax(ServletRequest request){return "XMLHttpRequest".equalsIgnoreCase(((HttpServletReques......

JavaSon712 ⋅ 今天 ⋅ 0

Redis 单线程 为何却需要事务处理并发问题

Redis是单线程处理,也就是命令会顺序执行。那么为什么会存在并发问题呢? 个人理解是,虽然redis是单线程,但是可以同时有多个客户端访问,每个客户端会有 一个线程。客户端访问之间存在竞争...

码代码的小司机 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部