转载自 http://www.iteye.com/topic/412531

提要:本文从实现原理的角度上阐述和剖析了:在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、 基于原理:搞清楚程序逻辑的最直接最简单的方式就是看源码,这毋庸置疑。 
只要基于这两个基础进行分析,在很多情况下会达到事半功倍的效果。这算是经验之谈吧,也算是分析程序的“捷径”方式之一。

String值传递剖析的更多相关文章

  1. 牛客_Java_值传递(拷贝)不该表原来变量+传引用的话会一起改变

    总结: 许多编程语言都有2种方法将参数传递给方法------按值传递和按引用传递.  与其他语言不同,Java不允许程序员选择按值传递还是按引用传递各个参数,基本类型(byte--short--int ...

  2. c# 传递Null的string值导致的调用C++的dll报错 Attempted to read or write protected memory.

    c# 调用C++的dll报错 Attempted to read or write protected memory:   原因是:c# 传递Null的string值导致的,将Null改为string ...

  3. String是值传递还是引用传递

    String是值传递还是引用传递 今天上班时,同事发现了一个比较有意思的问题.他把一个String类型的参数传入方法,并在方法内改变了引用的值. 然后他在方法外使用这个值,发现这个String还是之前 ...

  4. (摘录)String是值传递还是引用传递

    String应该是一个封装类型,它应该是引用传递,是可以改变值的, 运行的结果应该是”cd”.我们实际运行一下看看, str=ab,这如何解释呢?难道String是基本类型?也说不通呀. 这就要从ja ...

  5. 请问C#中string是值传递还是引用传递?

    https://www.cnblogs.com/xiangniu/archive/2011/08/17/2143486.html 学了这么久,终于弄明白了... 是引用传递 但是string又有值传递 ...

  6. String 不变性以及 Java 值传递和引用传递

    String 不变性以及 Java 值传递和引用传递 public class Example { String str = new String("good"); char[] ...

  7. java中 引用传递、值传递的理解(数组,自定义类,基本数据类型,String类)

    代码部分: public static void main(String[] args) { testInt(); testString(); testArray(); testX(); } publ ...

  8. 由String作为方法参数,引起的值传递,引用传递,及StringBuffer

    原文引用: http://www.cnblogs.com/zuoxiaolong/p/lang1.html http://www.cnblogs.com/clara/archive/2011/09/1 ...

  9. java基础之 数据类型 & 值传递 引用传递 & String & 四种引用类型

    一.Java数据类型 分为基本数据类型与引用数据类型 基本数据类型: byte:Java中最小的数据类型,在内存中占1个字节(8 bit),取值范围-128~127,默认值0 short:短整型,2个 ...

随机推荐

  1. ZBrush模型的细分

    在ZBrush®中对模型进行雕刻时,随着细节越来越丰富,原有的面数已经不能满足我们对细节的要求,为了得到更多的细节,我们就必须增加模型的面数,让更多的面来支持我们进行雕刻,如下图(左)和下图(右)所示 ...

  2. Python安装遇到的问题

    1.import MySQLdb  错误 导入MySQLdb模块时报错: >>> import MySQLdb Traceback (most recent call last): ...

  3. CF949B A Leapfrog in the Array 思维题,推理

    题意: Dima是一名初级程序员. 在他的工作中,他经常不断地重复以下操作:从数组中删除每个第二个元素. 有一天,他对这个问题的解决方案感到厌倦,他提出了以下华丽的算法. 假设有一长度为2n的数组,最 ...

  4. [NOIP补坑计划]NOIP2016 题解&做题心得

    感觉16年好难啊QAQ,两天的T2T3是不是都放反了啊…… 场上预计得分:100+80+100+100+65+100=545(省一分数线280) ps:loj没有部分分,部分分见洛咕 题解: D1T1 ...

  5. 一些css布局

    # css布局 #---bootstrap   详情请看官方文档---首先要按照相应的官方规范引入相应的css js fonts等 container相当于一个容器 一般设置一个 接下来设置行 用ro ...

  6. [luogu] P3333 [ZJOI2013]丽洁体(贪心)

    P3333 [ZJOI2013]丽洁体 题目描述 平时的练习和考试中,我们经常会碰上这样的题:命题人给出一个例句,要我们类比着写句子.这种往往被称为仿写的题,不单单出现在小学生的考试中,也有时会出现在 ...

  7. Vue生命周期函数的应用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. 调整mysql数据库最大连接数

    1.查看mariadb数据库最大连接数,默认为151 MariaDB [(none)]> show variables like 'max_connections'; +------------ ...

  9. Mysql学习总结(30)——MySQL 索引详解大全

    什么是索引? 1.索引 索引是表的目录,在查找内容之前可以先在目录中查找索引位置,以此快速定位查询数据.对于索引,会保存在额外的文件中. 索引,是数据库中专门用于帮助用户快速查询数据的一种数据结构.类 ...

  10. MyBatis学习总结(6)——调用存储过程

    一.提出需求 查询得到男性或女性的数量, 如果传入的是0就女性否则是男性 二.准备数据库表和存储过程 create table p_user( id int primary key auto_incr ...