類中使用shared_ptr()的問題 當我們先定義一個指針,然後再用這個指針構造兩個智能指針 int main() { int* pt = new int(); std::shared_ptr<int> p1(pt); std::shared_ptr<int> p2(pt); std::cout ...
準備整理一個系列,這是系列的第一篇。
這是一個經典的問題,也是JAVA程式員所必須掌握的。
一、小結論和例子
1.1結論
內容沒有多少,可以先說結論:
變數的表示和參數傳遞
- 變數是如何表示,尤其是參數是如何表示的
- 存儲則具體看變數是什麼類型:類靜態、實例變數、方法
- 變數表示-基本類型直接存儲,類類型則存儲地址
- 值是如何傳遞的
- 如果是基本類型-則是值的副本
- 如果是類類型-則是指向具體數據的地址的副本
變數通過方法加工後,對原來變數的影響
- 方法對基礎類型參數做任何處理,都不會影響到參數關聯的變數的值
- 如果方法中對對象類型參數不做重新賦值,那麼方法會影響參數關聯的變數的值
- 如果方法中對對象類型參數重新賦值,那麼方法不會影響參數關聯的變數的值
1.2示例代碼
本例子是基於JDK17編寫:
package study.base.param; import java.lang.reflect.InvocationTargetException; import java.util.Random; import com.alibaba.fastjson2.JSON; /** * 本類主要演示以下內容: * <pre> * 1.在方法中傳遞對象 * 2.如何在方法中交換兩個對象的值 * 3.通過對象地址驗證方法參數被重新賦值後,會指向另外一個對象的地址 * </pre> */ public class TestPassObjectParam { public void testPassint(int x) { x=x+10; System.out.println("x=x+10="+x); } /** * 測試-傳遞字元串,但是對參數整體調整,不會影響外部的變數, * 因為這會給參數重新賦值,即重新指向另外一個對象的地址,已經不指向原來的對象 * @param s1 * @param s2 */ public void testPassString(String s1,String s2) { System.out.println("參數s1,s2在方法中被重新賦值,但不會影響到相關的變數"); s1=s1+"**"; s2=s2.substring(2); } /** * 改變參數的局部值,會改變數本身 * @param dog */ public void testPassObject(Dog dog) { dog.www(); dog.eat(); System.out.printf("參數dog的邏輯地址=%s \n",System.identityHashCode(dog)); } /** * 為對象類型參數重新賦值,不會改變變數 * @param dog */ public void testPassObjectAndchange(Dog dog) { dog=new Dog("等級很高", "白", 24); System.out.printf("參數dog被重新賦值後的邏輯地址=%s\n",System.identityHashCode(dog)); } public Dog createDog(String name,String color,Integer weight){ Dog dog=new Dog(name,color,weight); return dog; } /** * 經典的字元串交換例子--這是不可能的,這是因為字元類型是不可變的。 * @param a * @param b */ public void swap(String a,String b) { String tmp=a; a=b; b=tmp; } public void swapDog(Dog a,Dog b) { Dog c=a; a=b; b=c; } public void swapDog2(Dog a,Dog b) { //Dog c=a; String tsa=JSON.toJSONString(a); String tsb=JSON.toJSONString(b); a=JSON.to(Dog.class, tsb); b=JSON.to(Dog.class, tsa); } public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { TestPassObjectParam test=new TestPassObjectParam(); // 演示基本類型和對象類型傳遞 // a.1 傳遞基本類型。基本類型 的值不會被改變 int x=10; test.testPassint(x); System.out.println(x); // 基本類型不會被改變,因為傳遞的是x的副本 //a.2 傳遞對象。可能改變變數的值,也可能不會。 這裡需要格外小心,尤其是傳遞String類型的時候。 //在方法中對對象類型進行處理,是否會修改對象,需要格外小心,有時候會修改變數,有時候不會 //大體上可以有3個基本對的結論: // 1.如果只是對參數的局部屬性進行修改,那麼變數也會被改變 // 2.如果對參數整體進行處理,或者重新賦值,那麼變數不會被改變 // 3.以上2點用String不好理解,最好使用更加複雜的一些類進行測試 String name="lzf"; String address="宇宙銀河系太陽系地球中國"; System.out.println("變數name,address被傳入方法前的值"); System.out.printf("name=%s,address=%s",name,address); test.testPassString(name, address); System.out.println("變數name,address被傳入方法後,查看它們的值是否改變"); System.out.printf("name=%s,address=%s \n\r",name,address); System.out.println("...現在交換字元串變數name,address"); test.swap(name, address); System.out.println("...name,address交換後的值(事實證明挖法在簡單傳參的情況下交換兩個對象,包括字元串)"); System.out.printf("name=%s,address=%s \n\r",name,address); System.out.println("---------------------------------------------------------"); // 通過對象的哈希編碼驗證在方法中對參數的賦值影響=》會被賦予另外一個對象的地址 //a.3 只是修改參數的屬性,會影響變數。 演示一個Dog類型的屬性變換會影響到變數,因為沒有為參數重新賦值 Dog dog=test.createDog(name, "黃",15); System.out.printf("變數dog傳入方法前的邏輯地址=%s \n",System.identityHashCode(dog)); test.testPassObject(dog); dog.www(); //a.4 參數被重新賦值,不會改變變數 test.testPassObjectAndchange(dog); dog.www(); //b:簡單傳參交換兩個對象也是不行的 System.out.println("------------------------------------------------------------"); Dog a=test.createDog("ss", "red", 10); Dog b=test.createDog("ww", "black", 20); System.out.printf("Dog a,b在交換前的顏色:%s,%s \n",a.getColor(),b.getColor()); String cb=a.getColor(); test.swapDog(a, b); String ca=a.getColor(); System.out.printf("Dog a,b在交換後的顏色:%s,%s \n",a.getColor(),b.getColor()); if (cb.equals(ca)) { System.out.println("Dog a,b交換失敗(無法通過簡單傳遞來交換兩個對象)"); } System.out.println("Dog a,b通過嘗試通過json序列和反序列進行交換"); test.swapDog2(a, b); System.out.printf("Dog a,b在交換後的顏色:%s,%s \n",a.getColor(),b.getColor()); cb=a.getColor(); if (cb.equals(ca)) { System.out.println("Dog a,b交換失敗(無法通過簡單傳遞來交換兩個對象)"); } System.out.println("通過簡單的論證,可以得出結論:兩個對象通過一個函數來進行簡單的交換屬性,是不可行"); System.out.println("在沒有特殊的情況下,java不可能再調整為參數複製/變數賦值的方法:先創建值,然後把值的地址賦予類變數/參數"); } class Dog{ private String name; private String color; private Integer weight; Dog(String name,String color,Integer weight){ this.name=name; this.color=color; this.weight=weight; } public void eat() { Random rand=new Random(); int randValue=rand.nextInt(1,10); int rd=rand.nextInt(10,100); if (rd>50) { this.weight+=randValue; } else { this.weight-=randValue; } } public void www() { System.out.println("有一隻"+color+"色,重"+weight.toString()+"斤,它正在吠叫:"+name); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } } }
註:所謂的簡單交換,即經典的交換方法,通過一個臨時變數過度。
二、註意事項和其它一些問題
大部分情況下,參數的傳遞並不是一個問題,這裡的註意事項,其實主要就是和字元(String)類型有關。
我們都知道,由於某些原因String本身是final存儲的:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc { /** * The value is used for character storage. * * @implNote This field is trusted by the VM, and is a subject to * constant folding if String instance is constant. Overwriting this * field after construction will cause problems. * * Additionally, it is marked with {@link Stable} to trust the contents * of the array. No other facility in JDK provides this functionality (yet). * {@link Stable} is safe here, because value is never null. */ @Stable private final byte[] value;
這就意味著字元對一個字元串變數重新賦值,則必須重新創建一個字元串對象。而一個新的對象必然指向一個新的地址。
當變數/參數被指向新的地址的時候,對原來的對象自然無法產生影響。
對字元串做變更的操作都會導致為創建一個新的對象,併為變數重新賦予新對象的地址。
例如常見的substring,concat,replace都是這樣的,如果僅僅是訪問字元變數的屬性,是不會改變字元的。
所以,如果希望通過一個函數修改一個字元串,那麼必須只有兩種途徑可以影響原來的字元變數:
1.函數返回新的字元串,並把這個新的字元串賦值給原來的變數
2.把字元串包裝在某個對象內部,然後在方法中為對象的字元屬性重新賦值