String型別函數傳遞問題
問題
- 以前沒有注意過的一個問題, 最近在使用String型別作為函數入參的時候, 發現函數內對於String型別的改變並不會影響到外層呼叫物件本身;
結論 (先說結論)
- 這個問題根本不存在 (屬於是自己把自己繞進去了);
- String型別與普通的java物件一樣, 只不過是用final修飾的不可變物件 (具體看String型別的原始碼與相關介紹);
測試資料(為什麼會有這個問題, 來源於以下操作)
- 發現String (其實Integer, Long... 等等這些型別也會這樣)函數傳遞修改後, 物件的值並沒有被改變;
- 主要是因為String型別與Integer...等等這些型別的賦值方式迷惑了我們, 不需要通過"new"關鍵字和反射也可以構建物件;
- 比如: Integer a = 123; 這種操作, 讓我們誤當作基本型別賦值(實際上這個問題比較基礎, 但有時候也會迷糊);
- 以下是對上述的操作案例;
package timer;
/**
* @author liwangcai E-mail:[email protected]
*/
public class StringDemo {
public static void changeString(String tmp) {
//此處操作具有一定的迷惑行為, 通常情況下只有基本資料型別才會這麼操作;
//但是對與String型別, 相當於新建了一個物件或者是拿到了常數池中的物件;
tmp = "new";
//列印通過函數傳遞進來的tmp引數的地址
System.out.println(System.identityHashCode(tmp));
}
public static void changeInteger(Integer tmp) {
//此處操作和String型別一樣
tmp = 199;
//列印通過函數傳遞進來的tmp引數的地址
System.out.println(System.identityHashCode(tmp));
}
public static void changeOther(StringDemo stringDemo) {
System.out.println(System.identityHashCode(stringDemo));
}
public static void main(String[] args) {
String tmp = "old";
//列印原始的tmp
System.out.println(tmp);
//列印原始的tmp物件地址
System.out.println(System.identityHashCode(tmp));
changeString(tmp);
//列印函數呼叫後的tmp值
System.out.println(tmp);
//出於好奇也測試了一下Integer型別
Integer iTmp = 1;
//列印原始的iTmp的值
System.out.println(iTmp);
//列印原始的iTmp物件地址
System.out.println(System.identityHashCode(iTmp));
changeInteger(iTmp);
//列印函數呼叫後iTmp的值
System.out.println(iTmp);
//對於普通的java物件
StringDemo stringDemo = new StringDemo();
//列印當前物件的地址
System.out.println(System.identityHashCode(stringDemo));
changeOther(stringDemo);
}
}
//執行結果
old
685325104
685325104
460141958
old
1
1163157884
1163157884
1956725890
1
356573597
356573597
以上測試結果得到的結果分析
- 上面的這個操作實際上就是迷惑所在,在這裡單獨把它列出來看一下
Integer a;
//此處實際上是java的自動裝箱, 相當於呼叫了valueOf函數
// 實際上是new了一個Integer物件出來,或者將另一個Integer物件直接賦值(可以去看一下Integer的原始碼)
a = 123;
//基礎型別的直接賦值
int b;
b = 123;
//與Integer類似,String型別也有常數池, 相當於快取, 此處不是重點;
// 相當於new了一個物件出來, 或將另一個String物件直接賦值;
String c;
c = "abc"
普通物件操作對比
/**
* @author liwangcai E-mail:[email protected]
*/
public class StringTest {
public static void changeUser(User user) {
user = new User("zhang san", 24);
}
public static void main(String[] args) {
User user = new User("li si", 25);
System.out.println(user);
changeUser(user);
System.out.println(user);
}
@Data
@AllArgsConstructor
@ToString
static class User {
private String name;
private Integer age;
}
}
//執行結果, 可以發現物件的值並沒有改變
StringTest.User(name=li si, age=25)
StringTest.User(name=li si, age=25)
Process finished with exit code 0
為什麼new一個物件並不會改物件值?
- 主要是因為在Java中函數傳遞只有值傳遞 (不是本部落格重點, 不展開描述)
如果想要改變String的值正確的操作姿勢?
- String物件是普通的Java物件, 不過是被final修飾了;
- 實際上String物件的值存在其內部的char value[]陣列中,如果想要改變String的值應該區修改這個陣列的資料;
- 不過上述陣列是使用final修飾的, 所以如果使用jdk中的String類, 那麼String的值是無法被修改的;
如果要改變String的值應該怎麼做?
- 實現自己的String型別, 內部儲存char[]陣列不設定為final型別就可以了;
/**
* @author liwangcai E-mail:[email protected]
*/
public class MyStringTest {
public static void main(String[] args) {
MyString myString = new MyString(new char[]{'a', 'b'});
System.out.println(myString);
changeMyString(myString);
System.out.println(myString);
}
private static void changeMyString(MyString myString) {
myString.setValue(new char[]{'c', 'd'});
}
@ToString
@AllArgsConstructor
@Data
static class MyString {
char[] value;
}
}
//測試結果
//可以發現值改變了
MyStringTest.MyString(value=[a, b])
MyStringTest.MyString(value=[c, d])
Process finished with exit code 0
總結
- 對於String,Integer型別 "=" 操作符號迷惑了我們, 實際上一開始提出的問題並不存在;
- 如果要修改同一個物件, 需要修改其內的成員;