关于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. PDF 生成插件 flying saucer 和 iText

    最近的项目中遇到了需求,用户在页面点击下载,将页面以PDF格式下载完成供用户浏览,所以上网找了下实现方案. 在Java世界,要想生成PDF,方案不少,所以简单做一个小结吧. 在此之前,先来勾画一下我心 ...

  2. string.Format 格式化输出日期

    string.Format("{0:d}",System.DateTime.Now) 结果为:2009-3-20 (月份位置不是03) string.Format("{0 ...

  3. Attribute "resource" must be declared for element type "mapper".

    今天在玩mybatis的时候,遇到这个奇葩问题. 最后发现,原因是 dtd文件配置错误了.错把Mapper的直接copy过来 把DOCTYPE mapper改成configuration,Mapper ...

  4. WdatePicker 设置日期第一个比第二个的日期小

    WdatePicker 设置日期第一个比第二个的日期小 可以设置,日期只显示某一天的,比如只显示周一,和周日 <input id="Text1" class="Wd ...

  5. 三星framebuffer驱动代码分析

    一.驱动总体概述 本次的驱动代码是Samsung公司为s5pv210这款SoC编写的framebuffer驱动,对应于s5pv210中的内部外设Display Controller (FIMD)模块. ...

  6. Linux课程实践一:Linux基础实践(基础操作)

    一.软件源维护 1. 基本操作 (1)查看源列表 sudo vim /etc/apt/sources.list deb:二进制软件安装包 deb-src:源码包 (2)备份软件源列表 sudo cp ...

  7. screen 命令

    # screen [-AmRvx -ls -wipe][-d <作业名称>][-h <行数>][-r <作业名称>][-s ][-S <作业名称>] 参 ...

  8. Web页面多对象多文档事件冲突的解决方案

    这段时间写了很多基于js和jquery的前端控件,每一个的功能都很复杂,事件也很多. 因为都是单独封装的,单独使用没有问题,但把他们放到一个页面使用,就经常发生事件冲突的问题. 这几天一直在考虑用一个 ...

  9. 济南学习D2T1__折纸带

    他[问题描述]一张长度为n的纸带,我们可以从左至右编号为0 −n(纸带最左端标号为0) .现在有m次操作,每次将纸带沿着某个位置进行折叠,问所有操作之后纸带的长度是多少.[输入格式]第一行两个数字n, ...

  10. css加阴影

    box-shadow: 1px 1px 3px 1px rgba(0,0,0,0.1); -webkit-box-shadow: 1px 1px 3px 1px rgba(0,0,0,0.1); -m ...