关于Java的String类型,可能你会碰到这种情况,将String类型的变量传到一个函数,在这个函数中修改变量的值,但是,实参的值并没有发生改变。

Java中String的传值/传地址问题:

例子引入:

 package com.cnblog.GDUTtiantian.String;

 /**
  * @author GDUTtiantian
  */
 public class JavaString {

     public static void change(String name){
         //修改name的值
         name = "ChangedName";
     }

     public static void main(String[] args) {
         String name = "GDUTtiantian";        

         change(name);

         System.out.println(name);

     }

 }

运行结果:

1 GDUTtiantian

为什么结果不是"ChangedName"呢?

String类的底层实现是用一个字符数组去实现的,就像Integer类,底层也是对int进行封装[装箱和拆箱]。

看String类的修饰部分(源码):

 public final class String
     implements java.io.Serializable, Comparable<String>, CharSequence {
     /** The value is used for character storage. */
     private final char value[];

注意,String类加了final关键字,所以不能被继承。

第4行是字符串底层的存储结构:字符数组。

String的内容不能被动态地修改,因为底层是字符数组实现的,数组的大小是在初始化时决定的;

如果可以修改,新的字符串长度比原来数组大,那么就会造成数组越界。

String和StringBuffer的比较:

 package com.cnblog.GDUTtiantian.String;

 /**
  * @author GDUTtiantian
  *
  * String, StringBuffer 在传参过程中的哈希值比较
  */
 public class JavaString4 {

     public static void change(String str) {
         System.out.println("形参的哈希值:" + str.hashCode());

         str = "newString";//修改了形参的值
         System.out.println("修改后:" + str.hashCode());
     }

     public static void change(StringBuffer sb) {
         System.out.println("形参的哈希值:" + sb.hashCode());
         sb.append("newStringBuffer");//修改了形参的值
         System.out.println("修改后:" + sb.hashCode());
     }

     public static void main(String[] args) {
         String str = new String("GDUTtiantian");
         StringBuffer sb = new StringBuffer("tiantian");

         System.out.println("修改前:" + str.hashCode());
         change(str);

         System.out.println("\n----------------------------\n");

         System.out.println("修改前:" + sb.hashCode());
         change(sb);
     }

 }

运行结果:

 修改前:-501451264
 形参的哈希值:-501451264
 修改后:-595706415

 ----------------------------

 修改前:1036782095
 形参的哈希值:1036782095
 修改后:1036782095

实参String变量传给形参,是传一个地址过去,并没有重新创建一个对象,StringBuffer变量也是这么做;

但是,在修改形参的值后,String变量的哈希值发生了改变,StringBuffer变量的哈希没有发生改变,即String变量指向了一个新建的对象。

看看JDK中String类的一段源码(String类的一个构造方法):

     /**
      * Allocates a new {@code String} that contains characters from a subarray
      * of the <a href="Character.html#unicode">Unicode code point</a> array
      * argument.  The {@code offset} argument is the index of the first code
      * point of the subarray and the {@code count} argument specifies the
      * length of the subarray.  The contents of the subarray are converted to
      * {@code char}s; subsequent modification of the {@code int} array does not
      * affect the newly created string.
      *
      * @param  codePoints
      *         Array that is the source of Unicode code points
      *
      * @param  offset
      *         The initial offset
      *
      * @param  count
      *         The length
      *
      * @throws  IllegalArgumentException
      *          If any invalid Unicode code point is found in {@code
      *          codePoints}
      *
      * @throws  IndexOutOfBoundsException
      *          If the {@code offset} and {@code count} arguments index
      *          characters outside the bounds of the {@code codePoints} array
      *
      * @since  1.5
      */
     public String(int[] codePoints, int offset, int count) {
         if (offset < 0) {
             throw new StringIndexOutOfBoundsException(offset);
         }
         if (count < 0) {
             throw new StringIndexOutOfBoundsException(count);
         }
         // Note: offset or count might be near -1>>>1.
         if (offset > codePoints.length - count) {
             throw new StringIndexOutOfBoundsException(offset + count);
         }

         final int end = offset + count;

         // Pass 1: Compute precise size of char[]
         int n = count;
         for (int i = offset; i < end; i++) {
             int c = codePoints[i];
             if (Character.isBmpCodePoint(c))
                 continue;
             else if (Character.isValidCodePoint(c))
                 n++;
             else throw new IllegalArgumentException(Integer.toString(c));
         }

         // Pass 2: Allocate and fill in char[]
         final char[] v = new char[n];

         for (int i = offset, j = 0; i < end; i++, j++) {
             int c = codePoints[i];
             if (Character.isBmpCodePoint(c))
                 v[j] = (char)c;
             else
                 Character.toSurrogates(c, v, j++);
         }

         this.value = v;
     }

代码57行开始,就是对字符数组进行复制。

这里,用C/C++中的字符串/数组/指针的引用做比较:

 #include<stdio.h>

 //形参中数组退化为指针了
 //这里s是指向array数组的指针
 void go(char * s){

     s = "JavaString";//指针指向另一个空间,"JavaString"字符串的首地址
     printf("s:%s#\n", s);
 }

 //形参中数组退化为指针了
 void change(char * s){

     s[] = 'c';
     s[] = 'h';
     s[] = 'a';
     s[] = 'n';
     s[] = 'g';
     s[] = 'e';
     s[] = '\0';
 }

 int main(){
     ] = "GDUTtiantian";

     go(array);
     printf("array:%s#\n", array);

     change(array);
     printf("array:%s#\n", array);

     ;
 }

第7行 : s = "JavaString";

这一行比较重要,s是指向main()函数中array数组的首地址的指针,然后在第7行,s指向另外一个字符串的首地址;

这里和String变量在形参中的改变有相似之处。

第14行: s[0] = 'c';

这里的s也是指向main()函数中array数组的首地址的指针,然后把array数组的第一个字符修改为'c'.

运行结果[在CodeBlock编译运行]:

 s:JavaString#
 array:GDUTtiantian#
 array:change#

 Process returned  (0x0)   execution time : 0.140 s
 Press any key to continue.

Java实现字符串的值修改可以有两种方式:

用数组实现

 package com.cnblog.GDUTtiantian.String;

 /**
  * @author GDUTtiantian
  */
 public class JavaString2 {

     public static void change(String[] name){
         //修改name的值
         name[0] = "ChangedName";
     }

     public static void main(String[] args) {
         String[] name = {"GDUTtiantian"};        

         change(name);

         System.out.println(name[0]);

     }

 }

运行结果:

1 ChangedName

将String设置为新建类型的一个成员变量

 package com.cnblog.GDUTtiantian.String;

 /**
  * @author GDUTtiantian
  */

 class NewString {
     private String value;

     public NewString(String str){
         value = str;
     }

     public String getValue() {
         return value;
     }

     public void setValue(String value) {
         this.value = value;
     }

     @Override
     public String toString() {
         return getValue();
     }
 }

 public class JavaString3 {
     private static NewString newName = new NewString("ChangedName");

     public static void change(NewString name){
         //修改name的值
         name.setValue(newName.getValue());
     }

     public static void main(String[] args) {
         NewString name = new NewString("GDUTtiantian");        

         change(name);

         System.out.println(name);

     }

 }

运行结果:

1 ChangedName

这两种方式中String变量的值都发生了改变,但是,其底层还是创建了一个新的String对象[忽略涉及字符串在缓冲池创建对象的部分],然后返回引用给当前句柄。

为什么通过这两种方式可以去改变String变量的值呢?

 暂时还想不太清楚,欢迎大家补充;

可以对比python的列表和元组,元组是不可变的,即元组的对象不可以删除,不可以增加;

如果该元组的一个元素为列表类型,那么,可以修改这个列表的内容,当然,列表对象还是当前这个列表对象。

欢迎讨论交流, 我的主页:http://www.cnblogs.com/GDUT/

我的邮箱:zone.technology.exchange@gmail.com

JAVA的String的传值和传地址问题的更多相关文章

  1. 一段代码让你秒懂java方法究竟是传值还是传地址

    先看看代码以及执行结果: 凝视写得非常清楚了.我就不多说了. 我说说我的结论.事实上在java中没有传值还是传址的概念,java仅仅有引用的概念.引用类似传址.只是是一个变量名中保存着对象的地址,地址 ...

  2. python中传值和传地址问题

    在python中,还没有对这个知识点有一个详细的定义,很模糊的说明了,通过下面代码,可以观察出来,什么时候传的是值,什么时候传的是地址 有时候会发现自己的数据发生变化,可能就是这个原因,python的 ...

  3. C/C++中传值和传地址(引用)

    C/C++中参数传递有两种方式,传值或传地址(传引用),通常我们要在被调用函数中改变一个变量的值就需要传地址调用方式,例如: void swap_by_value(int a, int b) { in ...

  4. c语言 参数传值和传地址

    static void TestCharP(char **p) { char *q = "ssssss"; *p=q; } static void TestCharP1(char ...

  5. java中函数传值和传地址的问题

    记录一下这个难过的双休,2019.3.16-2019.3.17,16号上午字节跳动笔试,四道题只做出1道半,输入输出搞的半死,第三题类似于leetcode上的分糖问题,数组初始化的时候全部赋为0了,要 ...

  6. Java经典问题:传值与传引用?

    转自:http://developer.51cto.com/art/201104/254715.htm Java到底是传值还是传引用?相信很少有人能完全回答正确.通常的说法是:对于基本数据类型(整型. ...

  7. Java方法传递参数传值还是传址的问题

    这几天重构项目代码遇到一个疑问:可不可以在方法A中定义一个boolean变量b为false,然后A调用方法C把b传递到C方法中经过一些列业务判断后修改为true,C执行结束后A方法中b的值还是原来的f ...

  8. java集合中的传值和传引用

    在学习java集合过程中发现了传值和传引用的区别: 我们来看下面两句话 ●java集合就像一种容器,我们可以把多个对象(实际上是对象的引用),丢进该容器.(来自疯狂java讲义) ●当使用Iterat ...

  9. C语言:传值,传地址

    形参:形式参数实参:实际参数 传值: 把实参的值复制给形参, 修改函数内的形参,不会影响实参. 传地址: 指针传值,形参为指向实参地址的指针 当对形参的指向操作时,相当于对实参本身进行的操作 #inc ...

随机推荐

  1. UBUNTU 13.04 install Grive

    sudo apt-get install software-properties-common sudo apt-get install python-software-properties sudo ...

  2. 第三章 数组与字符串 UVa1588 Kickdown

    题目要求简述:给定长度分别为n1,n2(n1,n2<=100)且每列的高度只为1或者2的长条.需要将他们放入一个高度为3的容器,问能够容纳它们的最短容器长度. 分析: 对于这样的题目显而易见有两 ...

  3. HIVE 启动出错总结

    1: [centos@centos4- bin]$ hive /home/centos/app/apache-hive--bin/bin/hive: line : /tmp/centos/stderr ...

  4. 通过IL分析C#中的委托、事件、Func、Action、Predicate之间的区别与联系

    先说一下个人理解的结论吧: delegate是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类. delegate声明的变量与delegate声明的事件,并没有本质的区别,事件是在dele ...

  5. avalon2学习教程11数据联动

    在许多表单应用,我们经常遇到点击一个复选框(或下拉框)会引发旁边的复选框(或下拉框)发生改变,这种联动效果用avalon来做是非常简单的.因为avalon拥有经典MVVM框架的一大利器,双向绑定!绝大 ...

  6. IIS中使用LocalDB遇到错误:error 50,Local Database Runtime error occurred.的解决办法

    参见: [1] http://www.cnblogs.com/yjmyzz/archive/2009/10/26/1590033.html [2] http://blogs.msdn.com/b/sq ...

  7. VC++ 在控件上写字时 字体的设置技巧

    //人物照片下方的文字 CFont* nFont = &afxGlobalData.fontRegular; CFont* oFont = pDc->SelectObject(nFont ...

  8. web应用中对配置文件的包装

    <bean id="placeholderConfig" class="com.shz.utils.AdvancedPlaceholderConfigurer&qu ...

  9. DIOCP之EchoServer分析

    constructor TfrmMain.Create(AOwner: TComponent);begin inherited Create(AOwner); sfLogger.setAppender ...

  10. gradle教程 [原创](eclipse/ADT下 非插件 非Android Studio/AS)纯手打 第一篇:安装配置gradle

    一个bug 一个脚印的叫你们用gradle. 1介于网络上的很多资料都是老的 不适用与现在的新版本gradle 尤其是有些gradle方法改名了老的用不了 2介于网上都是粘贴复制并且零碎我很蛋疼啊,走 ...