关于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. js实现判断浏览器版本

    //判断浏览器版本是否过低 var ua = navigator.userAgent.toLowerCase(); if (window.ActiveXObject) var IEversion = ...

  2. Swift中的Optional类型 (可选类型)与强制解包 ? !

    我们在swift的开发中会经常遇见?和! ,理解这两个符号深层次的内容对我们的开发是相当有利的: 目前网上对swift3.0的教程还相当的少,如果去搜索会发现早期的说法,在定义变量的时候,swift是 ...

  3. Android IOS WebRTC 音视频开发总结(七二)-- 看到Google Duo,你想到了什么?

    本文主要介绍在线教育这个行业,文章最早发表在我们的微信公众号上,支持原创,详见这里, 欢迎关注微信公众号blackerteam,更多详见www.rtc.help 在昨天的Google I/O大会上Go ...

  4. ThinkPHP 学习记录

    index.php //入口文件 define('APP_DEBUG',True); //开启调试模式 define('APP_PATH','./Application/'); //定义应用目录 re ...

  5. SharePoint report site.

    Create site. Upload rdl files into Dashboards lib. Manage Data source. Select the rdl file and click ...

  6. bootstrap分页插件--Bootstrap Paginator的使用&AJAX版备份(可单独使用)

    html部分: <ul class="pagination"></ul> <!--bootstrap3版本用ul包裹--> <div cl ...

  7. 使用timer控件控制进度条

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  8. asp.net TreeView控件绑定数据库显示信息

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  9. SVM1 线性SVM

    一.Linear Support Vector Machine 接下来的讨论假设数据都是线性可分的. 1.1 SVM的引入:增大对测量误差的容忍度 假设有训练数据和分类曲线如下图所示: 很明显,三个分 ...

  10. computer repair services in Hangzhou

    We provide support for all kinds of Windows based Desktops and Laptops all over Hangzhou,I will be i ...