Java Pass by Value

Java dilinin temel özelliklerinden bir tanesi olan metodlara parametre geçilirken pass-by-value ya da pass-by-copy olması özelliğine biraz daha derinlemesine göz atmak istiyorum, çoğu zaman bir programlama dili öğrenilirken temel anlamdaki özelliklerinden çok o dilin sağladığı bazı kolaylıklar çok daha fazla göz önünde olduğu için onlara odaklanılır ve temel özellikler göz ardı edilir, oysa çoğu zaman karşımıza çıkan durumlar bu temel özelliklerle ilgilidir.

Sözü fazla uzatmadan java’nın pass-by-value olması özelliğini gözden geçirmeye başlayabiliriz, özellikle certification exam’lerde bu konu sıklıkla işlenir, sorulur. Tabi bunu yapmadan önce bir dilin pass-by-value ya da pass-by-reference olması ne demektir bundan kısaca bahsedelim.

Öncelikle bir dilin pass-by-value ya da pass-by-reference olduğundan söz edilirken burada o dilin metodlara/fonksiyonlara parametre aktarırken parametreleri metoda ne şekilde aktardığı kastedilmektedir. Pass-by-reference demek parametrenin gerçek adresini geçmek anlamına gelirken pass-by-value ise sadece değerini bir kopya ile geçmek şeklinde tanımlanabilir. Her metodun bir stack’i vardır ve metoda geçilen parametreler bu stack’e push edilir, java’da kopyalanır. Nesnenin referansının metoda geçilmesi pass-by-reference yapıldığı anlamına gelmez, java’da referans da kopyalanarak geçilir, java pass-by-value’dir.

Java’da metodlara geçilen parametreler kopyalanır (metodun stack frame’ine) ve metod parametrelere bu kopya üzerinden erişir, bu hem reference hem de primitive type’lar için geçerlidir. Şimdi konuyu bir örnekle biraz daha açalım ve bunu bir kod örneği üzerinden yapalım, örneğimizi referans tipte bir parametre ile yapalım, primitive olanlar zaten value ile kopyalanmaktadır, diyelim ki elimizde şöyle bir test kodu var

   class Foo
    {
        private String _someValue = "oldValue";

        public Foo()
        {
        }

        public String getSomeValue()
        {
            return _someValue;
        }

        public void setSomeValue(String someValue)
        {
            _someValue = someValue;
        }
    }

1   class TestFoo
2   {
3      public static void main(String[] args)
4      {
5       Foo f = new Foo();
6       System.out.println("Foo value -> "+f.getSomeValue());
7       System.out.println("In main --> "+f);
8       changeFoo(f);
9       System.out.println("Foo value -> "+f.getSomeValue());
10     }
11
12     private static void changeFoo(Foo foo)
13     {
14      System.out.println("In method [1] --> "+foo);
15      foo.setSomeValue("newValue");
16      foo = new Foo();
17      System.out.println("In method [2]--> "+foo);
18      foo.setSomeValue("dummyValue");
19      System.out.println("Foo value -> "+foo.getSomeValue());
20     }
21   }

Şimdi run edelim ve çıktılar üzerinden değerlendirelim

Foo value -> oldValue
In main --> [email protected]
In method [1] --> [email protected]
In method [2]--> [email protected]
Foo value -> dummyValue
Foo value -> newValue

Satır satır durumu incelemeye çalışırsak, şimdi en başta şunu söylemiştik reference da olsa primitive de olsa metoda kopya geçiliyordu.

 5. satırda bellekteki temsili durum şöyledir

 [main metodu] [f referansı] = 3fbefab0 adresini tutmakta

 bellekte(heap) 3fbefab0 adresinde
 [3fbefab0] --> Foo'nun instance'ı _someValue field'ı "oldValue" set
 edilmiş bulunmakta

     f reference                  Foo instance @3fbefab0
  ____________________          ___________________________
  |  3fbefab0        |          |  _someValue="oldValue"  |
  |__________________|          |_________________________|

 6. satırda [main metodu] içerisinden print edersek
        someValue field'ını "oldValue" görüyoruz
 7. satırda [main metodu] içerisinden print edersek
        f nin tuttuğu adresi görürüz yani 3fbefab0
 8. satırda changeValue() metodu call edildi, yani metoda
        f referansının bir kopyası oluşturulup geçilecek, bunu şöyle düşünelim,
        metodun içerisindeki işlemlerde kopya olarak geçilen referans
        kullanılırken, metod dışında dışarıdaki referans kullanılacak,
        ancak her iki referans da aynı nesneye işaret ediyor şu anda,
        bunu da çizelim daha net anlaşılma açısından
                                                    ________________________
        main metodundaki referans            ----> |Foo instance @3fbefab0  |
        method içerisindeki (kopya) referans ----> |________________________|

        yani instance'a şu anda 2 referans mevcut, biri main içerisinden,
        bir de bunun bir kopyası olarak parametre geçilen metod içerisinden.

 14. satırda print edersek referans değerini daha önce
     main içerisinde print ettiğimiz değerin aynısını alıyoruz,
     bu son derece normal, çünkü kopya olan referans da
     aynı nesneyi işaret etmekte.
 15. satırda nesnenin _someValue alanına "newValue" değerini
     set ettik, metod içerisinde kopyalanmış referans çalışır demiştik,
     kopyalanmış referans da nesnemize işaret ettiğine göre
     ilgili alanı "newValue" olarak set etti,
     yani nesnemizin ilgili alanı değişti, "newValue" oldu
 16. satırda yeni bir nesne oluşturuldu, referans ismi de
     metoda geçilen ile aynı, bu ne demek oluyor, öncelikle
     yeni bir nesne oluştuğuna göre bu heap üzerinde yer alacak
     ve yeni bir adresi olacak, bu yeni adres de kendisini
     tutan referansa set edilecek, burada "foo" isimli referans
     aynı zamanda metod içerisinde kullanılan kopya referanstı,
     artık bu referans bellekte başka bir nesneye (yine Foo instance)
     işaret ediyor, şu andaki bellek durumuna bakarsak hemen
                                                __________________________
     main metodundaki referans ---------------> |Foo instance @3fbefab0  |
                                                |________________________|
                                                __________________________
                                                | Foo instance @133c5982 |
     method içerisindeki (kopya) referans ----> |________________________|

     şimdi artık method içerisindeki referans main'den geçilen referans tan
     ayrıldı ve farklı bir nesneyi işaret etmeye başladı, benzer şekilde
     foo = null demiş olsaydık sadece metod içerisindeki referansımızı null
     etmiş olurduk yani kopya olan metod içerisindeki referans herhangi bir
     nesneye işaret etmezdi, main metodundaki referans kalmaya devam ederdi.

     17. satırda foo referansını ekrana yazdırdığımızda "[email protected]"
         çıktısını veriyor, artık farklı bir nesneye işaret ettiği ortada.
     18. satırda foo referansının işaret ettiği nesnenin _someValue alanına
         "dummyValue" yazdık, bu @133c5982 adresindeki nesnenin "_someValue"
         alanına "dummyValue" yazdı.
     19. satırda _someValue alanını bastırdık ve foo nun refere ettiği
         nesnenin ilgili alanında "dummyValue" yazdığını gördük.

    Metod burada bitti ve main metodundaki
    System.out.println("Foo value -> "+f.getSomeValue()); satırına döndük
    artık burada f referansı kimi gösteriyorsa ki o da @3fbefab0 adresindeki
    Foo nesnesidir, onun _someValue alanı ekrana yazdırılır, onu da metoda
    ilk girdiğimiz satırlarda "newValue" olarak
    set etmiştik, dolayısıyla "newValue" değerini görürüz.

Bu arada metod ierisinde oluşturulan nesne metod bittiğinde kendisine herhangi bir referans kalmayacağı için garbage collector için collect edilebilir nesnelerden biri haline gelmiştir.

C# hem pass-by-value hem de pass-by-reference destekler, ancak default olarak pass-by-reference değildir, eğer özellikle pass-by-reference yapılmak isteniyorsa metoda ilgili parametre “ref” keyword’ü ile aktarılır.

2 Comments
  • birmuhgunlugu
    Posted at 23:34, 23/03/2017

    Teşekkürler açıklayıcı olmuş.

  • Samet Parlak
    Posted at 16:25, 22/06/2018

    Çok başarılı bir anlatım olmuş.

Post a Comment

Comment
Name
Email
Website