`

Java中String类型的参数传递问题的解析

    博客分类:
  • JESE
阅读更多
publicclass StringAsParamOfMethodDemo {
     publicstaticvoidmain(String[] args) {

       StringAsParamOfMethodDemo StringAsParamOfMethodDemo = 

           newStringAsParamOfMethodDemo();

       StringAsParamOfMethodDemo.testA();

    }

    privatevoidtestA() {

       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();

    }

    publicvoidsimpleChangeString(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”;。这是问题所在么?我们来为先前的示例稍微改造下,运行之后看看结果就知道了。改造后的代码如下:

   

 privatevoidtestB() {

       String originalStr = newString("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();

       }

    publicvoidchangeNewString(String original) {

       original = newString(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,做个小试验,代码如下:

   

 privatevoidtestC() {

       String originalStr = newString("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();

}

    privatestaticvoidchangeStrWithMethod(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类的构造函数:

    

publicString(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的实现就会明白了,在此也不再赘述了

分享到:
评论

相关推荐

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

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

    javaScript如何处理从java后台返回的list

    事情: 从java后台返回List<String>类型数据,用于界面显示。但js中想获取它并操作它。直接使用EL表达式,js把它识别成字符串了。不是我想要的啊。。网上搜了搜大家的解决方案…最好的当然是把List集合转成json格式...

    java泛型解析

    泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值...

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

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

    java的传值与传引用详解

    那说明,参数类型是简单类型的时候,是按值传递的。以参数形式传递简单类型的变量时,实际上是将参数的值作了一个拷贝传进方法函数的,那么在方法函数里再怎么改变其值,其结果都是只改变了拷贝的值,而不是源值。 ...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    3.4 小结:基本数据类型—— Java中一切数据和运算的基础 63 3.5 习题 65 第4章 Java中的程序执行流程 67 教学视频:1小时57分钟 4.1 顺序执行 67 4.2 使用if-else让程序懂得判断 68 4.2.1 if语句 68 4.2.2 ...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    3.4 小结:基本数据类型—— Java中一切数据和运算的基础 63 3.5 习题 65 第4章 Java中的程序执行流程 67 教学视频:1小时57分钟 4.1 顺序执行 67 4.2 使用if-else让程序懂得判断 68 4.2.1 if语句 68 4.2.2 ...

    java6string源码-CloudMusicApi:网易云音乐API接口获取和分析

    果然是大厂,在安全方面肯定做的比较多,原因是在传递参数的时候对参数进行加密,所以在我们直接访问网址的时候,如果传递的参数没有经过加密,前端页面就获取不到数据。看看知乎的大神们的解法: 下面是我的分析 ...

    java 面试题 总结

    如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。 15、error和exception有什么区别? error 表示恢复不是...

    Java大厂面试题汇总及答案解析.docx

    Java中的继承存在着传递性 D. 当实例化子类时会递归调用父类中的构造方法 答案:A 分析:Java是单继承的,一个类只能继承一个父类。 2.下面程序的运行结果() public static void main(String[] args) { Thread t...

    Thinking in Java简体中文(全)

    2.2.3 Java中的数组 2.3 绝对不要清除对象 2.3.1 作用域 2.3.2 对象的作用域 2.4 新建数据类型:类 2.4.1 字段和方法 2.5 方法、自变量和返回值 2.5.1 自变量列表 2.6 构建Java程序 2.6.1 名字的可见性 2.6.2 使用...

    java联想(中文)

    2.2.3 Java中的数组 2.3 绝对不要清除对象 2.3.1 作用域 2.3.2 对象的作用域 2.4 新建数据类型:类 2.4.1 字段和方法 2.5 方法、自变量和返回值 2.5.1 自变量列表 2.6 构建Java程序 2.6.1 名字的可见性 2.6.2 使用...

    java jdk实列宝典 光盘源代码

    java的参数传递;自定义形状类;类的加载顺序;方法和变量在继承时的覆盖和隐藏;排序类; 3数字 数字类;格式化数字;转换数字进制;生成随机数; 4数组和集合 使用Arrays类;动态调整数组长度;java为数据结构中的...

    张孝祥Java就业培训教程.pdf

    本书不仅全面的介绍了Java语言本身,最重要还交会读者去掌握编程思想,找到编程感觉,而不是死记硬背语言本身,书中涉及到的应用问题分析,远远超了一个Java程序员在学习和应用Java过程中所有可能碰到的问题。...

    java面试宝典

    68、java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类? 17 69、文件读写的基本类 17 70、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 17 71、启动一个...

    java基础案例与开发详解案例源码全

    5.6.8 方法参数及其传递问题144 5.6.9 理解main方法语法及命令行参数147 5.6.1 0递归算法147 5.7 this关键字148 5.8 JavaBean149 5.9 包150 5.9.1 为什么需要包?150 5.9.2 如何创建包151 5.9.3 编译并生成包:151 ...

    Thinking in Java 中文第四版+习题答案

    2.2.3 Java中的数组 2.3 绝对不要清除对象 2.3.1 作用域 2.3.2 对象的作用域 2.4 新建数据类型:类 2.4.1 字段和方法 2.5 方法、自变量和返回值 2.5.1 自变量列表 2.6 构建Java程序 2.6.1 名字的可见性 2.6.2 使用...

    [java]读书笔记整理:一切都是对象

    许多C++编程问题在java中就完全消失了,在C++中,最难的问题似乎在于:程序员并不能从语言本身获得任何帮助,以确保在需要调用对象时,该对象仍然可用。更重要的是:在C++中,一旦使用完对象后,必须确保要销毁对象...

    基于JAVA的搜索引擎 lucene-2.2.0

    * a :一个传递进来分析器 * create :是否要重新写入索引文件,如果为true,则重写索引文件;如果为false,则追加写入索引文件 * closeDir :一个boolean型变量,表示是否关闭索引目录Directory d,与IndexWriter的...

    Java开发详解.zip

    010301_【第3章:Java基础程序设计】_Java数据类型笔记.pdf 010302_【第3章:Java基础程序设计】_运算符、表达式与语句笔记.pdf 010303_【第3章:Java基础程序设计】_判断与循环语句笔记.pdf 010401_【第4章:数组与...

Global site tag (gtag.js) - Google Analytics