Java - Pass by value or Pass by reference?

古古

2018/09/10


  • 在探討 Java 傳遞參數是 pass by value 還是 pass by reference 之前,需要先了解 Java 中 = 的具體細節

  • 賦值 = 的用法

    • = 的意義是賦值,但是這個賦值用在 基本類型 和 對象類型 上會有非常大的差別

      • 如果 = 用在基本類型上,因為基本類型儲存了實際的數值,所以在為其賦值時,是直接將值複製一份新的過去
        • 因此假設 a、b 都是基本類型,如果執行了 a=b,那麼就是將 b 的內容直接複製一份新的給 a,之後如果改變了 a 的值,也不會影響到 b
        • 此處的基本類型,泛指 int、long、boolean….和其包裝型態 Integer、Long、Boolean….,只要是這些類型的變量,都適用基本類型的 = 規則
      • 但如果 = 用在對象類型上,因為在使用對象操作時,實際儲存的其實是對象的引用,所以在為其賦值時,實際上只是把 “引用” 從一個地方複製到另一個地方
        • 因此假設 c、d 都是對象類型,如果執行了 c=d,那麼 c 和 d 都會指向原本只有 d 指向的那個對象,而原本 c 的那個對象因為沒人引用了,所以會被垃圾回收清理掉
      • 另外,只要是數組,不管你是基本類型 int[] 還是對象類型 Tank[],一律存的都是引用,所以只要賦值了,也會互相影響
    • 具體實例

      • t1、t2是基本類型的 = 效果(不會互相影響)

      • t3、t4是對象類型的 = 效果(因為存的是引用,所以會互相影響)

      • i5、i6是基本類型的數組的 = 效果(因為存的也是引用,所以也會互相影響)

        class Tank {
            int level;
        }
        
        public class Main {
            public static void main(String[] args) {
                Tank t1 = new Tank();
                Tank t2 = new Tank();
        
                t1.level = 1;
                t2.level = 2;
                System.out.println("t1: " + t1.level + ", t2: " + t2.level);
        
                //此處只是基本類型的賦值,所以t1、t2仍舊指到兩個不同對象
                t1.level = t2.level;
                System.out.println("t1: " + t1.level + ", t2: " + t2.level);
        
                t1.level = 100;
                System.out.println("t1: " + t1.level + ", t2: " + t2.level);
        
                System.out.println("----");
        
                Tank t3 = new Tank();
                Tank t4 = new Tank();
        
                t3.level = 3;
                t4.level = 4;
                System.out.println("t3: " + t3.level + ", t4: " + t4.level);
        
                //此處是對象類型的賦值,所以是t3和t4都指到了同一個對象上
                //而原本t3那個對象因為沒人引用了,所以會被垃圾回收清理掉
                t3 = t4;
                System.out.println("t3: " + t3.level + ", t4: " + t4.level);
        
                t3.level = 100;
                System.out.println("t3: " + t3.level + ", t4: " + t4.level);
        
                System.out.println("----");
        
                int[] i5 = {5};
                int[] i6 = {6};
                System.out.println("i5[0]: " + i5[0] + ", i6[0]: " + i6[0]);
        
                //因為數组存的是引用,所以i5和i6會指到同一個地方上
                i5 = i6;
                System.out.println("i5[0]: " + i5[0] + ", i6[0]: " + i6[0]);
        
                i5[0] = 200;
                System.out.println("i5[0]: " + i5[0] + ", i6[0]: " + i6[0]);
            }
        }
        
        t1: 1, t2: 2
        t1: 2, t2: 2
        t1: 100, t2: 2
        ----
        t3: 3, t4: 4
        t3: 4, t4: 4
        t3: 100, t4: 100
        ----
        i5[0]: 5, i6[0]: 6
        i5[0]: 6, i6[0]: 6
        i5[0]: 200, i6[0]: 200
        
  • Pass by value or Pass by reference

    • = 一樣,只要掌握好基本類型實際儲存的是 “值”、對象類型儲存的是 “引用”、數組不論什類型存的都是 “引用”,就能了解 Java 到底什麼時候是 pass by value,什麼時候是 pass by reference

    • 基本類型 pass by value,對象類型 pass by reference,而數組因為存的都是引用,所以也是 pass by reference

      class Tank {
          int level;
      }
      public class Main {
          public static void main(String[] args) {
              Tank t1 = new Tank();
              Tank t2 = new Tank();
              t1.level = 1;
              System.out.println("t1.level: " + t1.level);
              fooInt(t1.level); //基本類型pass by value
              System.out.println("t1.level: " + t1.level);
              t2.level = 2;
              System.out.println("t2.level: " + t2.level);
              fooTank(t2); //對象類型pass by reference
              System.out.println("t2.level: " + t2.level);
          }
          public static void fooTank(Tank tank){
              tank.level = 1000;
          }
          public static void fooInt(int level){
              level = 5;
          }
      }
      
      t1.level: 1
      t1.level: 1
      t2.level: 2
      t2.level: 1000
      
    • 基本類型的List、Set、Map 也是 pass by value,對象類型的 List、Set、Map 是 pass by reference

      class Tank {
          int level;
      }
      public class Main {
          public static void main(String[] args) {
              List<Tank> tankList = new ArrayList<>();
              List<Integer> intList = new ArrayList<>();
              for (int i = 1; i <= 2; i++) {
                  Tank tank = new Tank();
                  tank.level = i;
                  tankList.add(tank);
                  intList.add(i * 100);
              }
              System.out.println("intList: " + intList.get(0) + ", " + intList.get(1));
              fooIntList(intList);
              System.out.println("intList: " + intList.get(0) + ", " + intList.get(1));
              System.out.println("tankList: " + tankList.get(0).level + ", " + tankList.get(1).level);
              fooTankList(tankList);
              System.out.println("tankList: " + tankList.get(0).level + ", " + tankList.get(1).level);
          }
          public static void fooTankList(List<Tank> tankList) {
              for (Tank tank : tankList) {
                  tank.level = 500;
              }
          }
          public static void fooIntList(List<Integer> intList) {
              for (Integer i : intList) {
                  i = 2000;
              }
          }
      }
      
      intList: 100, 200
      intList: 100, 200
      tankList: 1, 2
      tankList: 500, 500