发现Java StringBuffer使用中的一个隐藏问题,你中招了吗?_weixin_34249678的博客-程序员秘密

import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SynChronizeInfoReadThreadPoolTest {


    public static void main(String[] args) throws InterruptedException {

1          StringBuffer insertservice_tsql = new StringBuffer();      

2          insertservice_tsql.append("abc");

3          new SynChronizeInfoReadThread(insertservice_tsql).run();//可执行线程,并且线程会修改insertservice_tsql内容

          }

}

 

问题描述:

         当在代码1行里面,初始化insertservice_tsql变量后,将这个变量传递给SynChronizeInfoReadThread 线程,作为构造函数的参数,在线程里面对insertservice_tsql变量进行了修改,但是问题出现了,SynChronizeInfoReadThread修改了该变量,居然main函数里面的该变量的值也变化了,这是一种啥情况??

 

这似乎是关于传值与传地址的问题

发现这哥们博客不错。

http://freej.blog.51cto.com/235241/168676

 

         刚才看见一个兄弟在为Java的String传值/传引用问题困惑,翻箱倒柜找到了这篇我很久以前写的文章,发在这里,希望能对迷惑的朋友有些帮助。 

         提要:本文从实现原理的角度上阐述和剖析了:在Java语言中,以String作为类型的变量在作为方法参数时所表现出的“非对象”的特性。

 

一、最开始的示例

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

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类型的变量的重新赋值操作并没有对这个变量的原型产生任何影响。好了,这个示例的逻辑和运行结果都展示清楚了,接下来我们来对这个小程序进行分析。在这之前我们先来回顾下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”;。这是问题所在么?我们来为先前的示例稍微改造下,运行之后看看结果就知道了。改造后的代码如下:

  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参数传递问题的症结所在

其实,要想真正理解一个类或者一个API/框架的最直接的方法就是看源码。

下面我们来看看new出String对象的那小段代码(String类中),也就是String类的构造函数:

       

 public String(String original) { 

                int size = original.count; 

                char[] originalValue = original.value; 

                char[] v; 

                if (originalValue.length > size) { 

                        // The array representing the String is bigger than the new 

                        // String itself.    Perhaps this constructor is being called 

                        // in order to trim the baggage, so make a copy of the array. 

                            int off = original.offset; 

                            v = Arrays.copyOfRange(originalValue, off, off+size); 

                } else { 

                        // The array representing the String is the same 

                        // size as the String, so no point in making a copy. 

                        v = originalValue; 

                } 

                this.offset = 0; 

                this.count = size; 

                this.value = v; 

}

 

也许你注意到了里面的char[],这说明对String的存储实际上通过char[]来实现的。怎么样?其实就是一层窗户纸。不知道大家还记不记得在Java API中定义的那些基本类型的包装类。比如Integer是int包装类、Float是float的包装类等等。对这些包装类的值操作实际上都是通过对其对应的基本类型操作而实现的。是不是有所感悟了?对,String就相当于是char[]的包装类。包装类的特质之一就是在对其值进行操作时会体现出其对应的基本类型的性质。在参数传递时,包装类就是如此体现的。所以,对于String在这种情况下的展现结果的解释就自然而然得出了。同样的,Integer、Float等这些包装类和String在这种情况下的表现是相同的,具体的分析在这里就省略了,有兴趣的朋友可以自己做做试验。

这也就是为什么当对字符串的操作在通过不同方法来实现的时候,推荐大家使用StringBuffer的真正原因了。至于StringBuffer为什么不会表现出String这种现象,大家再看看的StringBuffer的实现就会明白了,在此也不再赘述了。

六、写在最后

由此String类型的参数传递问题的原理也就展现出来了。其实可以看出,只要分析方式正确,思考终究得出正确结论的。

正确分析方法的基础有二:

1、  多实践:手千万不要犯懒,实践必会出真知。

2、  基于原理:搞清楚程序逻辑的最直接最简单的方式就是看源码,这毋庸置疑。

只要基于这两个基础进行分析,在很多情况下会达到事半功倍的效果。这算是经验之谈吧,也算是分析程序的“捷径”方式之一。

转载于:https://my.oschina.net/u/2308739/blog/693350

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_34249678/article/details/92215322

智能推荐

Qt 自绘按钮控件_我不是萧海哇~~~~的博客-程序员秘密_qt自绘控件

Qt5 自绘按钮控件,实现高度个性化的功能。核心思想是重写paintEvent等各种事件处理函数,根据当前状态在QPaintDevice上绘制图像。效果:MPushButton.h#ifndef MPUSHBUTTON_H#define MPUSHBUTTON_H#include <QPushButton>#include <QLineEdit>class MPushButton : public QPushButton{ Q_OBJECTpubli

Linux系统时间偏差的纠正_roy_nutt的博客-程序员秘密_linux时钟偏差

实际上,linux系统有两个时钟:一个是由主板电池驱动的“Real Time Clock”也叫做RTC或者叫CMOS时钟,硬件时钟。当操作系统关机的时候,用这个来记录时间,但是对于运行的系统是不用这个时间的。另一个时间是 “System clock”也叫内核时钟或者软件时钟,是由软件根据时间中断来进行计数的,内核时钟在系统关机的情况下是不存在的,所以,当操作系统启动的时候,内核时钟是要读取RTC时

VMware vCenter 各版本号对照表_白昼ron的博客-程序员秘密_vcenter 版本

VMware vCenter 各版本号对照表本博文转自以下链接:https://www.virten.net/vmware/vcenter-release-and-build-number-history/

Unix NetWork Programming——环境搭建(解决unp.h等源码编译问题)_天天向上zb的博客-程序员秘密

搭建《unix网络编程》那本书所需的环境我用的是ubuntu11.10系统:1,安装编译器,为了齐全还是安装一下build-essential[python] view plaincopysudo apt-get install build-essential  2,下载本书的头文件及示例源码点此下载u

MySQL中的AND 和 OR_明天会更好吗的博客-程序员秘密_mysql的or

MySQL中AND 和 OR 关键字在 WHERE 中可以多次出现 AND 优先级 高于 OR  建议使用括号来明确你的逻辑,避免出错也避免别人理解错   例一:从用户表中查找出id等于12并且年龄为12的用户或者年龄为13的用户    SELECT * FROM user WHERE id=12 AND age=12 OR age=13 例二:从用户...

vs2010 x64 malloc大内存失败的解决方法_psbins的博客-程序员秘密

vs2010 x64环境下无法malloc大内存的解决方法:malloc(整型的一个数);注意整型的数的类型不只有int,特别是开辟大内存的时候。vs2010不智能,不能将malloc()中的参数转化为长整型long long.给个例子:在vs2010下,int aaa=16384;int bbb=22000;int sss=8;char星matric=(char星)malloc(aaa×bbb×sss);会失败,而在vs2013会成功,就是因为vs2010不智能.改为==char星

随便推点

Linux编程send函数,网络异常导致程序退出解决办法_黄大刀的博客-程序员秘密

发现调用send函数时,如果远端的socket异常中断的时候,会导致send异常,并退出,这是不允许的。原因是网络异常的时候,系统会触发一个底层信号SIGPIPE,如果没有函数接口处理这个信号的话,就会导致程序异常退出。解决办法,最简单的办法:禁用这个信号,也就是调用send的时候,使用MSG_NOSIGNAL标志,例如:if (send(comm->comm_fd, Dat...

scikitlearn下载地址_木头生辉的博客-程序员秘密_scikitlearn下载

https://pypi.org/project/scikit-learn/#files适用版本号主要是3.5——3.8适用mac,linux和win下载时要对应安装环境和python版本,不匹配容易出错。

一个完整的研发体系应该包括的内容_波特王子的博客-程序员秘密_研发体系

转自:http://www.360doc.com/content/17/0215/19/33578855_629254120.shtml多年前笔者所在公司作为甲方和融智咨询进行全面合作,按照融智咨询提供的“四四四”模型系统性的对公司的研发体系进行了变革,并且取得不错了效果。 “四四四”模型如下图:       四四四代表了四大团队、四个流程、四个支撑体系。根据“四四四”模型,笔者公司分别完成了以下...

互联网企业的职位&职位之间的相互关系_雪心玉竹的博客-程序员秘密_互联网企业

了解企业的运行逻辑,就要考虑一下企业所在行业的价值链、产业链这些相对宏观的方面,然后再考虑微观的层面。从微观方面,要了解一家企业的运作逻辑,可以去梳理典型企业的核心业务流,岗位都是基于业务流而产生的。这篇文章,将从宏观和微观两个方面,通过价值链和业务流的角度,来剖析企业的职位以及职位之间的相互关系。一、互联网企业的组织结构与部门设置要了解一个互联网企业的所有岗位及其之间的相互关系,我们有必要首先介绍一下互联网企业的典型组织结构图,这样便于你从整体上形成对一个企业的各个部门及其职位的总体印象。 而要了解

手把手教你将小程序项目跑起来【真自律小程序】_喵宁一的博客-程序员秘密_小程序项目怎么运行

1、下载项目文件在【猫宁一】公众号中回复【源码】,得到下载链接,将项目下载下来并解压2、打开项目文件,编辑server/config.js文件将红框圈出来的地方改成自己的信息(1)qcloudAppId、qcloudSecretId、qcloudSecretKey去哪里找?首先登录自己的腾讯云,点击下面链接:https://console.cloud.tencent.com/capi...

Prometheus 多租写入 Thanos Receiver 配置_shida_csdn的博客-程序员秘密_prometheus多租户

背景描述当采集指标过多时,超过单个 prometheus 的处理能力,我们通常会采用多个 prometheus 实例分别采集一定的指标分片,然后通过 thanos 做聚合,对外提供统一查询服务。本文通过简单的实验,演示 thanos 的分片数据采集与聚合查询能力,架构如下:实验假设存在两个 prometheus 实例,分别采集不同的监控指标分片,prometheus 实例均通过 remote write 方式将数据写入 thanos receiver。receiver 采用多副本方式运行,并通过 h

推荐文章

热门文章

相关标签