`
javahacker2
  • 浏览: 41387 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java-String类型的参数传递问题

    博客分类:
  • Java
 
阅读更多
刚才看见一个兄弟在为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/框架的最直接的方法就是看源码。
下面我们来看看newString对象的那小段代码(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中定义的那些基本类型的包装类。比如Integerint包装类、Floatfloat的包装类等等。对这些包装类的值操作实际上都是通过对其对应的基本类型操作而实现的。是不是有所感悟了?对,String就相当于是char[]的包装类。包装类的特质之一就是在对其值进行操作时会体现出其对应的基本类型的性质。在参数传递时,包装类就是如此体现的。所以,对于String在这种情况下的展现结果的解释就自然而然得出了。同样的,IntegerFloat等这些包装类和String在这种情况下的表现是相同的,具体的分析在这里就省略了,有兴趣的朋友可以自己做做试验。
这也就是为什么当对字符串的操作在通过不同方法来实现的时候,推荐大家使用StringBuffer的真正原因了。至于StringBuffer为什么不会表现出String这种现象,大家再看看的StringBuffer的实现就会明白了,在此也不再赘述了。
六、         写在最后
由此String类型的参数传递问题的原理也就展现出来了。其实可以看出,只要分析方式正确,思考终究得出正确结论的。
正确分析方法的基础有二:
1、  多实践:手千万不要犯懒,实践必会出真知。
2、  基于原理:搞清楚程序逻辑的最直接最简单的方式就是看源码,这毋庸置疑。
只要基于这两个基础进行分析,在很多情况下会达到事半功倍的效果。这算是经验之谈吧,也算是分析程序的“捷径”方式之一。
分享到:
评论
1 楼 尔今尔后 2014-10-20  
写的很好,简明扼要的概述。谢谢

相关推荐

    JNI开发Java调用C传递int、String、Array类型参数

    JNI开发Java调用C传递int、String、Array类型参数; 详情参考: int类型: (https://blog.csdn.net/niuba123456/article/details/80959892) String类型(https://blog.csdn.net/niuba123456/article/details/80977247);...

    简单谈谈Java中String类型的参数传递问题

    主要介绍了简单谈谈Java中String类型的参数传递问题的相关资料,需要的朋友可以参考下

    Java参数传递的经典示例

    Java参数传递的经典教学PPT,引用类型,基础类型传递的区别,String和StringBuffer类型传递的区别。

    Java当中的String数据类型

     对于字符串对象来说,虽然在参数传递的时候也是引用传递,但是java虚拟机在函数内部对字符串对象进行了特殊处理–视String对象为常量(final) 所以对传进来的引用地址所引用的string对象比能直接进行修改,而是...

    在axis1.4中传递复杂类型数组参数(ArrayMapping)

    NULL 博文链接:https://conkeyn.iteye.com/blog/1010959

    第4章-Java面向对象程序设计-Java语言GUI程序设计-赵满来-清华大学出版社.pptx

    4.1.2 方法的调用及参数传递 对象名.方法名(参数表); Java程序中方法的调用是按值调用,即调用者将实参的值传递给被调方法对应的形参。 实参与形参的类型、次序和个数一一对应。 基本数据类型参数,形参复制了实参...

    url传递的参数值中包含&时,url自动截断问题的解决方法

    在做一个公告浏览功能时,只要通过url传递的某参数值中包含 & 或 ,就会出现问题–该变量的值无法显示。 问题定位结果: 遇到&时,该参数的值会自动截断,导致参数值传递有误。 二、问题的解决 java代码中做如下...

    java参数传递

    试根据html文件中提供的参数,编写一个计算矩形面积和周长的Java Applet程序,并把计算结果显示在APPLET窗口中。

    Java中的值传递和引用传递

    当一个变量为一个参数传入方法...  2、引用类型(除String外)都是引用传递  3、Strng比较特殊,它虽然是引用类型,但是却是值传递  通过一下例子来理解:  Student.java package com.tianjf; public cla

    java-servlet-api.doc

    当客户端发出请求时,Servlet引擎传递给Servlet一个ServletRequest对象和一个ServletResponse对象,这两个对象作为参数传递到service()方法中。 Servlet也可以执行ServletRequest接口和ServletResponse接口。...

    javabase64-1.3.1

    例如,在Java Persistence系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中的参数。在其他应用程序中,也常常需要把二进制数据编码...

    C#调用JavaWebService

    然后自动产生代理类,但是在调用JAVA的WebService时并没有这么简单,特别是对于SoapHeader的处理,通过C#添加Web引用方式访问JavaWebService的方法,除了string类型能正常传递参数外,q其他类型的参数不是默认值就是...

    jquery ajax 向后台传递数组参数示例

    需求: 在JS中向后台传递数组参数 分析: JS中的数组是弱类型的可以放任何类型(对象、基本类型),但是如果数组中放的是对象类型,传递到后台是显示的只能是对象字符串–[object Object],原因如下: 在后台接收的...

    NDKC调用Java函数传参或获取变量

    NDK开发时,C/C++调用Java的函数...传递String类型参数: https://blog.csdn.net/niuba123456/article/details/80978916 传递多个参数: https://blog.csdn.net/niuba123456/article/details/80979082 调用静态函数: ...

    为什么Java只有值传递

    值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 引用传递:是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的...

    JNI与C++数据类型传递示例(包括ArrayList对象、ArrayList嵌套返回)

    一个C++(Ubuntu16.04+QT5.9.1)通过JNI,调用JAVA类及方法的示例。通过JNI传递和返回多种类型的参数,boolean ,int,String,ArrayList<string>,ArrayList嵌套ArrayList<ArrayList<String>>等。

    java发送邮件封装,链式传递参数

    .subject("使用JavaMail发送混合组合类型的邮件测试主题") // .attachFiles(attachFiles) .content(contentBuf.toString()) .toRecipients(toRecipients); ServiceResponse<String> serviceResponse = ...

    Java开发技术大全(500个源代码).

    localVSmember.java 局部变量与成员变量同名问题示例 onlyTest.java 对象传值示例 otherClass.java 从类的外部访问对象的成员 showInstVar.java 演示不同的对象拥有不同的成员变量 showMain.java 演示main方法...

    java 返回多个值的问题

    由于某些项目需要知道一个函数处理中需要返回函数的处理状态,同时 ...原因就是java 使用的是对象传递. 那么怎么解决返回多个参数值呢? 解决方式就是用HashMap来保存返回的结果.这样就可以解决多个返回值的问题

    Android intent之间复杂参数传递方法详解

    本文详细讲述了Android intent之间复杂参数传递方法。分享给大家供大家参考,具体如下: Intent是Activity与Activity之间,Activity与Service之间传递参数的介质,而这两种通常实现的是Java基本对象类型和String的...

Global site tag (gtag.js) - Google Analytics