JAVA的String的传值和传地址问题

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

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

例子引入:

 1 package com.cnblog.GDUTtiantian.String;
 2
 3 /**
 4  * @author GDUTtiantian
 5  */
 6 public class JavaString {
 7
 8     public static void change(String name){
 9         //修改name的值
10         name = "ChangedName";
11     }
12
13
14     public static void main(String[] args) {
15         String name = "GDUTtiantian";
16
17         change(name);
18
19         System.out.println(name);
20
21     }
22
23 }

运行结果:

1 GDUTtiantian

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

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

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

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

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

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

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

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

String和StringBuffer的比较:

 1 package com.cnblog.GDUTtiantian.String;
 2
 3 /**
 4  * @author GDUTtiantian
 5  *
 6  * String, StringBuffer 在传参过程中的哈希值比较
 7  */
 8 public class JavaString4 {
 9
10
11     public static void change(String str) {
12         System.out.println("形参的哈希值:" + str.hashCode());
13
14         str = "newString";//修改了形参的值
15         System.out.println("修改后:" + str.hashCode());
16     }
17
18     public static void change(StringBuffer sb) {
19         System.out.println("形参的哈希值:" + sb.hashCode());
20         sb.append("newStringBuffer");//修改了形参的值
21         System.out.println("修改后:" + sb.hashCode());
22     }
23
24
25     public static void main(String[] args) {
26         String str = new String("GDUTtiantian");
27         StringBuffer sb = new StringBuffer("tiantian");
28
29         System.out.println("修改前:" + str.hashCode());
30         change(str);
31
32         System.out.println("\n----------------------------\n");
33
34         System.out.println("修改前:" + sb.hashCode());
35         change(sb);
36     }
37
38 }

运行结果:

1 修改前:-501451264
2 形参的哈希值:-501451264
3 修改后:-595706415
4
5 ----------------------------
6
7 修改前:1036782095
8 形参的哈希值:1036782095
9 修改后:1036782095

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

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

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

 1     /**
 2      * Allocates a new {@code String} that contains characters from a subarray
 3      * of the <a href="Character.html#unicode">Unicode code point</a> array
 4      * argument.  The {@code offset} argument is the index of the first code
 5      * point of the subarray and the {@code count} argument specifies the
 6      * length of the subarray.  The contents of the subarray are converted to
 7      * {@code char}s; subsequent modification of the {@code int} array does not
 8      * affect the newly created string.
 9      *
10      * @param  codePoints
11      *         Array that is the source of Unicode code points
12      *
13      * @param  offset
14      *         The initial offset
15      *
16      * @param  count
17      *         The length
18      *
19      * @throws  IllegalArgumentException
20      *          If any invalid Unicode code point is found in {@code
21      *          codePoints}
22      *
23      * @throws  IndexOutOfBoundsException
24      *          If the {@code offset} and {@code count} arguments index
25      *          characters outside the bounds of the {@code codePoints} array
26      *
27      * @since  1.5
28      */
29     public String(int[] codePoints, int offset, int count) {
30         if (offset < 0) {
31             throw new StringIndexOutOfBoundsException(offset);
32         }
33         if (count < 0) {
34             throw new StringIndexOutOfBoundsException(count);
35         }
36         // Note: offset or count might be near -1>>>1.
37         if (offset > codePoints.length - count) {
38             throw new StringIndexOutOfBoundsException(offset + count);
39         }
40
41         final int end = offset + count;
42
43         // Pass 1: Compute precise size of char[]
44         int n = count;
45         for (int i = offset; i < end; i++) {
46             int c = codePoints[i];
47             if (Character.isBmpCodePoint(c))
48                 continue;
49             else if (Character.isValidCodePoint(c))
50                 n++;
51             else throw new IllegalArgumentException(Integer.toString(c));
52         }
53
54         // Pass 2: Allocate and fill in char[]
55         final char[] v = new char[n];
56
57         for (int i = offset, j = 0; i < end; i++, j++) {
58             int c = codePoints[i];
59             if (Character.isBmpCodePoint(c))
60                 v[j] = (char)c;
61             else
62                 Character.toSurrogates(c, v, j++);
63         }
64
65         this.value = v;
66     }

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

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

 1 #include<stdio.h>
 2
 3 //形参中数组退化为指针了
 4 //这里s是指向array数组的指针
 5 void go(char * s){
 6
 7     s = "JavaString";//指针指向另一个空间,"JavaString"字符串的首地址
 8     printf("s:%s#\n", s);
 9 }
10
11 //形参中数组退化为指针了
12 void change(char * s){
13
14     s[0] = ‘c‘;
15     s[1] = ‘h‘;
16     s[2] = ‘a‘;
17     s[3] = ‘n‘;
18     s[4] = ‘g‘;
19     s[5] = ‘e‘;
20     s[6] = ‘\0‘;
21 }
22
23 int main(){
24     char array[100] = "GDUTtiantian";
25
26     go(array);
27     printf("array:%s#\n", array);
28
29     change(array);
30     printf("array:%s#\n", array);
31
32     return 0;
33 }

第7行 : s = "JavaString";

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

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

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

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

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

1 s:JavaString#
2 array:GDUTtiantian#
3 array:change#
4
5 Process returned 0 (0x0)   execution time : 0.140 s
6 Press any key to continue.

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

用数组实现

 1 package com.cnblog.GDUTtiantian.String;
 2
 3 /**
 4  * @author GDUTtiantian
 5  */
 6 public class JavaString2 {
 7
 8     public static void change(String[] name){
 9         //修改name的值
10         name[0] = "ChangedName";
11     }
12
13
14     public static void main(String[] args) {
15         String[] name = {"GDUTtiantian"};
16
17         change(name);
18
19         System.out.println(name[0]);
20
21     }
22
23 }

运行结果:

1 ChangedName

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

 1 package com.cnblog.GDUTtiantian.String;
 2
 3 /**
 4  * @author GDUTtiantian
 5  */
 6
 7 class NewString {
 8     private String value;
 9
10     public NewString(String str){
11         value = str;
12     }
13
14     public String getValue() {
15         return value;
16     }
17
18     public void setValue(String value) {
19         this.value = value;
20     }
21
22
23     @Override
24     public String toString() {
25         return getValue();
26     }
27 }
28
29 public class JavaString3 {
30     private static NewString newName = new NewString("ChangedName");
31
32     public static void change(NewString name){
33         //修改name的值
34         name.setValue(newName.getValue());
35     }
36
37
38     public static void main(String[] args) {
39         NewString name = new NewString("GDUTtiantian");
40
41         change(name);
42
43         System.out.println(name);
44
45     }
46
47 }

运行结果:

1 ChangedName

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

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

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

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

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

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

我的邮箱:zone.technology.ex[email protected]

JAVA的String的传值和传地址问题

时间: 2024-08-27 03:01:10

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

C语言结构体,C语言结构体指针,java对象引用,传值,传地址,传引用

C语言结构体,C语言结构体指针,java对象引用,传值,传地址,传引用 传值 把实参的值赋值给行参 那么对行参的修改,不会影响实参的值 传地址 传值的一种特殊方式,只是他传递的是地址,不是普通的如int 那么传地址以后,实参和行参都指向同一个对象 传引用 真正的以地址的方式传递参数 传递以后,行参和实参都是同一个对象,只是他们名字不同而已 对行参的修改将影响实参的值 所谓变量是内存地址的一个抽象名字,在静态编译的程序中,所有变量名都会在编译时转换成内存地址,机器不知道变量名,只知道地址. C 语

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

C/C++中参数传递有两种方式,传值或传地址(传引用),通常我们要在被调用函数中改变一个变量的值就需要传地址调用方式,例如: void swap_by_value(int a, int b) { int temp; temp = a; a = b; b = temp; } void swap_by_ptr(int* pa, int* pb) { int temp; temp = *pa; *pa = *pb; *pb = temp; } int main(int argc, char* argv

C语言-传值,传地址(指针),传引用区别和联系

很多编程老手对传值,传地址,传引用的区别搞的也不会很清楚,今天我就花一点时间再次介绍一下这些概念的本质. 其实,不用分为三类,只有两类即可.传值和传引用.为什么会出现传地址(即传指针)呢?本质就是大家一致对传值和传地址概念的理解错误导致,也是对指针的概念的理解错误导致. 指针:(简单补充一下)其实很简单,指针就是一个变量,如果非要说是一个特殊的变量也不为过,因为指针的初始化和解引用等不同的操作方式而已.就内存的分布来说,指针和一个变量在内存中存放是没有任何区别的,无非指针存放的是变量的地址,就是

java传递参数是传值还是传引用

这个问题,网上一查越看越迷糊,纠结完了先总结下 先看个栗子: 1 public class Example { 2  3     public void change(String str1, StringBuffer sbr1, char[] ch1, int a) { 4         str1 = "world"; 5         sbr1.append("world"); 6         ch1[0] = 'g'; 7     } 8  9    

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

转自:http://developer.51cto.com/art/201104/254715.htm Java到底是传值还是传引用?相信很少有人能完全回答正确.通常的说法是:对于基本数据类型(整型.浮点型.字符型.布尔型等),传值;对于引用类型(对象.数组),传引用.基本类型传值,所有人都不会对此有疑义;问题出在引用类型上. 为引入正题,不妨先看看下面的例子,你能正确给出程序的运行结果嘛? /** * @(#)Swap.java * * *  @author *  @version  1.00

C语言:传值,传地址

形参:形式参数实参:实际参数 传值: 把实参的值复制给形参, 修改函数内的形参,不会影响实参. 传地址: 指针传值,形参为指向实参地址的指针 当对形参的指向操作时,相当于对实参本身进行的操作 #include <iostream> // 交换位置 // “值调用” void swap_value(int x, int y) { int temp = x; x = y; y = temp; } // “引用调用1”,形参:传的是地址,取得是值(*) void swap_p(int *x, int

传值和传地址调用

1.原理 传值调用:对形参进行函数变化 传地址调用:对实参进行函数变化 2.实例 #include<iostream>using namespace std;void exchange(int x, int y){             //传入形参    int temp = x;                       //在main函数中x=3,y=4    x = y;    y = temp;    printf("x=%d,y=%d\n", x, y);}

golang中method的传值与传地址

golang中,struct的method的形式如下: func (r ReceiverType) funcName(parameters) (results) 如果想要修改struct的成员的值,method被定义时候其ReceiverType必须是struct*形式.如果ReceiverType是struct,则无法改变struct成员的值. 废话少说,代码验证: package main import ( "fmt" ) type tag struct { value int32

Java 中的传值与传引用

Java 函数中的传值和传引用问题一直是个比较"邪门"的问题,其实 Java 函数中的参数都是传递值的,所不同的是对于基本数据类型传递的是参数的一份拷贝,对于类类型传递的是该类参数的引用的拷贝,当在函数体中修改参数值时,无论是基本类型的参数还是引用类型的参数,修改的只是该参数的拷贝,不影响函数实参的值,如果修改的是引用类型的成员值,则该实参引用的成员值是可以改变的,例子如下 class Model { public int i = 0; public String s = "