Java “referans yoluyla” veya “değerine göre” mi?


6580

Java'nın hep referans olduğunu düşündüm .

Ancak, olmadığını iddia eden birkaç blog yayını (örneğin, bu blog ) gördüm .

Yaptıkları ayrımı anladığımı sanmıyorum.

Açıklama nedir?


706
Bu konudaki karışıklığın çoğunun, farklı insanların "referans" terimi için farklı tanımlara sahip olmasıyla ilgili olduğuna inanıyorum. Bir C ++ arka planından gelen insanlar, "başvuru" nın C ++ ile ne demek istediğini, C arka planından gelen insanlar da "referans" ın kendi dillerindeki "işaretçi" ile aynı olması gerektiğini varsayar. Java'nın referansla geçtiğini söylemek doğru olup olmadığı gerçekten "referans" ile ne kastedildiğine bağlıdır.
Yerçekimi

119
Değerlendirme Stratejisi makalesinde bulunan terminolojiyi tutarlı bir şekilde kullanmaya çalışıyorum . Makale, terimlerin topluluğa göre büyük ölçüde değişmesine rağmen , anlambilimin çok önemli bir şekilde vurgulandığını call-by-valueve call-by-referencefarklılaştığını vurgulamaktadır . (Şahsen ben kullanmayı tercih call-by-object-sharingüzerinde bugünlerde call-by-value[-of-the-reference]bu yüksek seviyede semantiğini açıklar ve bir çakışma yaratmaz gibi call-by-value, bir altta yatan uygulanması.)

59
@Gravity: Yorumunuzu BÜYÜK bir ilan panosuna falan koyabilir misiniz? Özetle tüm mesele bu. Ve tüm bunların semantik olduğunu gösteriyor. Bir referansın temel tanımını kabul
etmezsek

30
Bence karışıklık "referans anlamıyla" ve "referans anlambilimi" dir. Java, referans semantiği ile birlikte-geçiştir.
Mart'ta spraff

11
@Gravity, C ++ 'dan gelen millet içgüdüsel olarak "referans" terimi ile ilgili farklı bir sezgi kümesi olacak kesinlikle doğru olsa da, ben şahsen vücudun "tarafından" daha gömülü olduğuna inanıyorum. "Pass by", Java'da "Passing" den kesinlikle farklı olduğu için kafa karıştırıcıdır. Ancak C ++ 'da sözel olarak değildir. C ++ 'da "bir referansı geçme" diyebilirsiniz ve swap(x,y)testi geçeceği anlaşılmaktadır .

Yanıtlar:


5840

Java her zaman değerlidir . Ne yazık ki, bir nesnenin değerini geçtiğimizde, ona bir referans aktarıyoruz . Bu yeni başlayanlar için kafa karıştırıcı.

Bu böyle devam ediyor:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

Yukarıdaki örnekte aDog.getName()yine de dönecektir "Max". Değeri aDogolan mainişlevinde değiştirilmez fooile Dog "Fifi"nesne referans değeri geçtiği şekilde. Referans olarak geçildiyse, aDog.getName()in mainçağrıldıktan "Fifi"sonra geri döner foo.

Aynı şekilde:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

Yukarıdaki örnekte, nesnenin adı içinde ayarlandığından, çağrıldıktan Fifisonra köpeğin adıdır . O Herhangi operasyonlar gerçekleştirdiği üzerindeki tüm pratik amaçlar için, bunlar üzerinde gerçekleştirilir, şekildedir , ama öyle değil değişkenin değerini değiştirmek mümkün kendisi.foo(aDog)foo(...)foodaDogaDog


263
Sorunu iç ayrıntılarla biraz karıştırmıyor mu? 'İç referansın nesneye değeri' demek istediğinizi varsayarsak, 'bir referansı geçmek' ve 'bir referansın değerini geçmek' arasında kavramsal bir fark yoktur.
izb

383
Ancak ince bir fark var. İlk örneğe bakın. Sadece referans olarak geçilseydi, aDog.name "Fifi" olurdu. Değil - aldığınız referans, işlevden çıkarken üzerine yazılırsa geri yüklenecek bir değer referansıdır.
erlando

300
@Lorenzo: Hayır, Java'da her şey değerden geçer. Temel öğeler değere, nesne başvuruları değere göre geçirilir. Nesnelerin kendileri hiçbir zaman bir yönteme geçirilmez, ancak nesneler her zaman yığın halindedir ve yönteme yalnızca nesneye bir başvuru iletilir.
Esko Luontola

294
Nesnenin geçişini görselleştirmek için iyi bir yol deniyorum: Bir balon düşünün. Bir fxn çağırmak, balona ikinci bir dize bağlamak ve satırı fxn'ye vermek gibidir. parametresi = new Balloon () bu dizeyi keser ve yeni bir balon oluşturur (ancak bunun orijinal balon üzerinde bir etkisi yoktur). Parametresi.pop () yine de aynı orijinal balonun dizesini izlediğinden yine de açılır. Java değere göre geçiştir, ancak geçirilen değer derin değildir, en üst seviyededir, yani ilkel veya bir işaretçi. Bunu, nesnenin tamamen klonlandığı ve geçtiği derin bir değerle karıştırmayın.
dhackner

150
Kafa karıştırıcı olan şey nesne referanslarının aslında işaretçiler olmasıdır. Başlangıçta SUN onları işaretçiler olarak adlandırdı. Sonra pazarlama "işaretçi" kötü bir kelime olduğunu bildirdi. Ama yine de NullPointerException içinde "doğru" adlandırma görüyorsunuz.
Prof. Falken sözleşmesi

3035

Sadece başvurulan fark benim yazı .

Java Spec, Java'daki her şeyin önemsiz olduğunu söyler. Java'da "referans by pass" diye bir şey yoktur.

Bunu anlamanın anahtarı şudur:

Dog myDog;

olduğu olmayan bir köpek; aslında bir köpeğin göstergesidir .

Bunun anlamı,

Dog myDog = new Dog("Rover");
foo(myDog);

esasen oluşturulan nesnenin adresini yönteme geçirirsiniz.Dogfoo

(Esasen Java işaretçileri doğrudan adresler olmadığı için söylüyorum, ancak bu şekilde düşünmek en kolayı)

DogNesnenin bellek adresi 42'de olduğunu varsayalım. Bu, yönteme 42 ilettiğimiz anlamına gelir.

Yöntem şu şekilde tanımlanmışsa

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

neler olduğuna bakalım.

  • parametre someDog42 değerine ayarlanır
  • "AAA" satırında
    • someDogkadar takip edilir Dogiçin bu puan ( Dogadresinde nesne 42)
    • o Dog(adresinde 42 az bir) Max ismini değiştirmek istenir
  • "BBB" satırında
    • yeni Dogoluşturulur. Diyelim ki o adres 74'te
    • parametreyi someDog74'e atarız
  • "CCC" satırında
    • someDog Dogişaret ettiği yere işaret eder ( Dog74 adresindeki nesne)
    • o Dog(adresinde 74 az bir) Rowlf ismini değiştirmek istenir
  • sonra geri dönüyoruz

Şimdi yöntemin dışında neler olduğunu düşünelim:

Değişti myDogmi?

Anahtar var.

Bunun myDoggerçek bir işaretçi değil , bir işaretçi olduğunu akılda tutarak Dogcevap HAYIR. myDoghala 42 değerine sahiptir; hala orijinali gösteriyor Dog(ancak "AAA" satırı nedeniyle adının şimdi "Max" olduğunu unutmayın - hala aynı Köpek; myDogdeğerinin değişmediğini.)

Bir adresi takip etmek ve sonunda ne olduğunu değiştirmek son derece geçerlidir ; ancak bu değişkeni değiştirmez.

Java tam olarak C gibi çalışır. Bir işaretçi atayabilir, işaretçiyi bir yönteme geçirebilir, yöntemdeki işaretçiyi izleyebilir ve işaretlenen verileri değiştirebilirsiniz. Ancak, bu işaretçinin işaret ettiği yeri değiştiremezsiniz.

C ++, Ada, Pascal ve doğrudan referansı destekleyen diğer dillerde, aslında iletilen değişkeni değiştirebilirsiniz.

Java, referans by pass anlambilimine foosahip olsaydı, yukarıda tanımladığımız yöntem , BBB hattına myDogatandığında nereye işaret ettiğini değiştirmiş olurdu someDog.

Referans parametrelerini aktarılan değişken için takma adlar olarak düşünün. Bu takma ad atandığında, aktarılan değişken de olur.


164
Bu nedenle "Java'nın işaretçileri yok" ortak kaçınması çok yanıltıcıdır.
Beska

134
Yanılıyorsun, imho. "MyDog'un gerçek bir Köpek değil, bir işaretçi olduğunu unutmayın, cevap HAYIR. MyDog hala 42 değerine sahiptir; hala orijinal Köpeğe işaret ediyor." myDog'un değeri 42'dir, ancak ad bağımsız değişkeninde artık // AAA satırında "Rover" yerine "Max" bulunmaktadır.
Özgür

180
Bu şekilde düşünün. Birisi "annArborLocation" adlı bir kağıt parçası üzerinde MI Arbor, MI (benim memleketim GO BLUE!) "MyDestination" adlı bir kağıda kopyalarsınız. "MyDestination" a gidebilir ve bir ağaç dikebilirsiniz. Bu konumdaki şehir hakkında bir şey değiştirmiş olabilirsiniz, ancak her iki kağıda da yazılan LAT / LON'u değiştirmez. LAT / LON'u "myDestination" üzerinde değiştirebilirsiniz ancak "annArborLocation" ifadesini değiştirmez. Bu yardımcı olur mu?
Scott Stanchfield

43
@Scott Stanchfield: Makalenizi yaklaşık bir yıl önce okudum ve bu durumun benim için net bir şekilde anlaşılmasına yardımcı oldu. Teşekkürler! Alçakgönüllülükle biraz ekleme önerebilirim: Barbara Liskov tarafından CLU dilinin değerlendirme stratejisini açıklamak için icat ettiği "değerin referans olduğu yerde değere göre çağrı" biçimini tanımlayan özel bir terim olduğunu belirtmelisiniz. 1974, önlemek karışıklık için bu tür one makale adresleri olarak: paylaşarak çağrısı (bazen nesne paylaşımı yoluyla çağrı veya basitçe nesnesi tarafından çağrı ) hemen hemen mükemmel semantiğini açıklar.
Jörg W Mittag

29
@Gevorg - C / C ++ dilleri "işaretçi" kavramına sahip değil. İşaretçiler kullanan ancak C / C ++ 'ın izin verdiği işaretçilerin aynı tür manipülasyonuna izin vermeyen diğer diller vardır. Java'nın işaretçileri vardır; sadece yaramazlıklara karşı korunuyorlar.
Scott Stanchfield

1740

Java, bağımsız değişkenleri her zaman değere göre iletir, başvuru olarak DEĞİL.


Bunu bir örnekle açıklayayım :

public class Main {

     public static void main(String[] args) {
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }

     public static void changeReference(Foo a) {
          Foo b = new Foo("b");
          a = b;
     }

     public static void modifyReference(Foo c) {
          c.setAttribute("c");
     }

}

Bunu adım adım açıklayacağım:

  1. fType adlı bir başvuruyu bildirme Foove ona Foobir özniteliğe sahip yeni bir nesne nesnesi atayın "f".

    Foo f = new Foo("f");

    resim açıklamasını buraya girin

  2. Yöntem tarafından, bir Fooad içeren türün bir başvurusu abildirilir ve başlangıçta atanır null.

    public static void changeReference(Foo a)

    resim açıklamasını buraya girin

  3. Yöntemi çağırdığınızda, changeReferencebaşvuruya abağımsız değişken olarak iletilen nesne atanır.

    changeReference(f);

    resim açıklamasını buraya girin

  4. bType adlı bir başvuruyu bildirme Foove ona Foobir özniteliğe sahip yeni bir nesne nesnesi atayın "b".

    Foo b = new Foo("b");

    resim açıklamasını buraya girin

  5. a = breferans için yeni bir atama yapar a, değil f , olan öznitelik nesnenin "b".

    resim açıklamasını buraya girin

  6. Siz modifyReference(Foo c)yöntemi çağırdığınızda , bir başvuru coluşturulur ve özniteliğe sahip nesne atanır "f".

    resim açıklamasını buraya girin

  7. c.setAttribute("c");kendisine işaret eden nesnenin niteliğini değiştirir cve ona fişaret eden nesnenin aynısıdır .

    resim açıklamasını buraya girin

Umarım şimdi nesneleri bağımsız değişken olarak geçirmenin Java'da nasıl çalıştığını anlarsınız :)


82
+1 Güzel şeyler. iyi diyagramlar. Ayrıca burada güzel bir özlü sayfa buldum adp-gmbh.ch/php/pass_by_reference.html Tamam PHP ile yazılmış itiraf ediyorum, ama önemli olduğunu düşünüyorum fark anlamak (ve bu farkı manipüle nasıl ihtiyaçlarınız).
DaveM

5
@ Eng.Fouad Güzel bir açıklama ama aaynı nesneyi işaret ettiğinde f(ve nesnenin kendi kopyasını hiçbir zaman fişaret etmediğinde), kullanılarak yapılan nesnede yapılan adeğişiklikler fde aynı şekilde değiştirilmelidir (çünkü ikisi de aynı nesneyle çalışıyorsa) ), bir noktada nesnenin işaret ettiği nesnenin akendi kopyasını almalıdır f.
0x6C38

14
@MrD, 'a' aynı nesneyi 'f' de gösterdiğinde, o nesnede 'a' ile yapılan herhangi bir değişiklik 'f' ile de gözlemlenebilir, AMA DEĞİŞTİRMEDİ 'f'. 'f' hala aynı nesneyi gösterir. Nesneyi tamamen değiştirebilirsiniz, ancak 'f' nin işaret ettiği şeyi asla değiştiremezsiniz. Bazı insanların bazı nedenlerden anlayamadığı temel mesele budur.
Mike Braun

@MikeBraun ... ne? Şimdi beni karıştırdın: S. Yazdıklarınız 6. ve 7. gösterilerin aksine değil mi?
Evil Çamaşır Makinesi

6
Bulduğum en iyi açıklama bu. Bu arada, temel durum ne olacak? Örneğin, bağımsız değişken bir int türü gerektirir, yine de bir int değişkeninin kopyasını bağımsız değişkene geçirir mi?
allenwang

732

Bu, Java'nın referans ile geçme veya değere göre geçme hakkında bir sonraki tartışmanızda sadece gülümseyeceğiniz noktaya kadar nasıl çalıştığına dair bazı bilgiler verecektir :-)

Birinci adım lütfen özellikle diğer programlama dillerinden geliyorsanız 'p' "_ _ _ _ _ _ _" ile başlayan kelimeyi aklınızdan silin. Java ve 'p' aynı kitapta, forumda ve hatta txt'de yazılamaz.

İkinci adım, bir Nesneyi bir yönteme geçirdiğinizde, Nesnenin kendisini değil, Object başvurusunu ilettiğinizi unutmayın.

  • Öğrenci : Shifu, bu Java'nın referans yoluyla olduğu anlamına mı geliyor?
  • Shifu : Çekirge, Hayır.

Şimdi bir Object'in başvurusunun / değişkeninin ne yaptığını / ne olduğunu düşünün:

  1. Bir değişken, JVM'ye başvurulan nesneye bellekte (Yığın) nasıl ulaşacağını söyleyen bitleri tutar.
  2. Bir yönteme argüman iletirken, referans değişkeni DEĞİLDİR, ancak referans değişkendeki bitlerin bir kopyasıdır . Böyle bir şey: 3bad086a. 3bad086a, iletilen nesneye ulaşmanın bir yolunu temsil eder.
  3. Yani sadece 3bad086a'yı geçiyorsunuz, referansın değeri.
  4. Referansın değerini değil, referansın kendisini geçiyorsunuz (nesneyi değil).
  5. Bu değer aslında COPIED'dir ve yönteme verilir .

Aşağıda (lütfen bunu derlemeye / yürütmeye çalışmayın ...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

Ne oluyor?

  • Değişken kişi 1 numaralı satırda oluşturulur ve başlangıçta null olur.
  • Yeni bir Kişi Nesnesi, 2. satırda bellekte saklanır ve değişken kişiye Kişi nesnesine başvuru verilir. Yani adresi. 3bad086a diyelim.
  • Nesnenin adresini tutan değişken kişi # 3 satırındaki işleve iletilir.
  • 4. satırda sessizliğin sesini dinleyebilirsiniz
  • 5. satırdaki yorumu kontrol edin
  • Bir yöntem yerel değişkeni - anotherReferenceToTheSamePersonObject - oluşturulur ve sonra sihir # 6 satırında gelir:
    • Değişken / başvuru kişisi bitler halinde kopyalanır ve işlevin içindeki anotherReferenceToTheSamePersonObject öğesine iletilir .
    • Yeni Kişi örneği oluşturulmaz.
    • Hem " person " hem de " anotherReferenceToTheSamePersonObject " aynı 3bad086a değerine sahiptir.
    • Bunu denemeyin ama person == anotherReferenceToTheSamePersonObject doğru olur.
    • Her iki değişken de referansın KİMLİK KOPYALARINA sahiptir ve her ikisi de aynı Kişi Nesnesine, Öbekteki SAME Nesnesine ve KOPYA DEĞİLDİR.

Bir resim bin kelime değerinde bir olup:

Değere Göre Geç

AnotherReferenceToTheSamePersonObject oklarının değişken kişiye değil Object'e yönlendirildiğini unutmayın!

Eğer anlamadıysanız, bana güvenin ve Java'nın değere göre geçtiğini söylemenin daha iyi olduğunu unutmayın . O referans değeriyle geçmesi . Ah, daha da iyisi, değişken değerinin kopyalanmasıdır! ;)

Şimdi benden nefret etmekten çekinmeyin, ancak bunun göz önüne alındığında , yöntem argümanlarından bahsederken ilkel veri türlerini ve Nesneleri geçirme arasında bir fark olmadığını unutmayın .

Referans değerinin bitlerinin bir kopyasını her zaman geçirirsiniz!

  • İlkel bir veri türü ise, bu bitler ilkel veri türünün değerini içerir.
  • Bir Nesne ise, bitler JVM'ye Nesneye nasıl ulaşacağını söyleyen adresin değerini içerir.

Java bir pass-by-değeridir, çünkü bir yöntemin içinde başvurulan Nesneyi istediğiniz kadar değiştirebilirsiniz, ancak ne kadar denerseniz deneyin referans vermeyi sürdürecek iletilen değişkeni asla değiştiremezsiniz (p _ _ _ değil) _ _ _ _) ne olursa olsun aynı Nesne!


Yukarıdaki changeName işlevi, iletilen başvurunun gerçek içeriğini (bit değerleri) hiçbir zaman değiştiremez. Başka bir deyişle changeName, Kişinin başka bir Nesneye başvurmasını sağlayamaz.


Tabii ki kısa kesebilir ve sadece Java'nın by-value olduğunu söyleyebilirsiniz !


5
İşaretçiler mi demek istediniz? .. Doğru alırsam, içinde public void foo(Car car){ ... }, caryerel foove nesnenin yığın konumunu içerir? Yani car'nin değerini değiştirirsem car = new Car(), öbek üzerinde farklı bir nesneye işaret eder mi? ve eğer carözellik değerini olarak değiştirirsem car.Color = "Red", öbeğin işaret ettiği nesne cardeğiştirilecektir. Ayrıca, C #? Lütfen cevap verin! Teşekkürler!
dpp

11
@ domanokz Beni öldürüyorsun, lütfen o kelimeyi bir daha söyleme! ;) Bu soruyu 'referans' demeden de cevaplayabileceğimi unutmayın. Bu bir terminoloji sorunu ve 'p'ler daha da kötüleştiriyor. Ben ve Scot ne yazık ki bu konuda farklı görüşlere sahibiz. Sanırım Java'da nasıl çalıştığını anladınız, şimdi buna değer-değeri, nesne-paylaşma, değişken-değeri-kopyasını geçme veya başka bir şey bulmaktan çekinmeyin! Nasıl çalıştığı ve bir değişken türü ne var aldığınız sürece gerçekten umurumda değil: sadece bir PO Box adresi! ;)
Marsellus Wallace

9
Senin gibi Sesler sadece bir referans geçti ? Java'nın hala kopyalanmış bir referans kodu olduğu gerçeğini savunacağım. Kopyalanmış bir referans olması, terminolojiyi değiştirmez. Her iki REFERANSLAR hala aynı nesneyi işaret ediyor. Bu bir safkanın iddiası ...
John Strickler

3
9 numaralı teorik çizgiye girin ve System.out.println(person.getName());ne görünecek? "Tom" mu yoksa "Jerry" mi? Bu karışıklığı engellememe yardımcı olacak son şey bu.
TheBrenny

1
" Referansın değeri " nihayet bana bunu açıkladı.
Alexander Shubert

689

Java her zaman bunun istisnası değeriyle geçmesi olduğunu hiç .

Öyleyse, herkes bununla nasıl karıştırılabilir ve Java'nın referans olarak geçtiğine inanır veya Java'nın referans olarak geçerken bir örneği olduğunu düşünür? Anahtar nokta Java asla değerlerine doğrudan erişim sağlar kendileri nesneler içinde, herhangi bir koşul. Nesnelere tek erişim , o nesneye yapılan bir başvurudır . Java nesnelerine her zaman doğrudan yerine bir başvuru yoluyla erişildiğinden , bilgiçliksel olarak yalnızca nesnelere başvurulduğunda alanlar ve değişkenler ve yöntem bağımsız değişkenleri nesne olarak konuşmak yaygındır .Karışıklık, isimlendirmedeki bu (açık konuşmak gerekirse, yanlış) değişiklikten kaynaklanmaktadır.

Yani, bir yöntemi çağırırken

  • İlkel bağımsız değişkenler ( int, longvb.) İçin by pass value, ilkel olanın gerçek değeridir (örneğin, 3).
  • Nesneler için, pass by değeri, nesneye yapılan başvurunun değeridir .

Eğer varsa doSomething(foo)ve public void doSomething(Foo foo) { .. }iki Foos aynı nesnelere işaret eden referansları kopyaladıysanız .

Doğal olarak, bir nesneye bir referansın değerinden geçirilmesi, bir nesneyi referans olarak geçirmeye çok benzemektedir (ve pratikte ayırt edilemez).


7
İlkellerin değerleri değişmez olduğundan (String gibi), iki durum arasındaki fark gerçekten alakalı değildir.
Paŭlo Ebermann

4
Kesinlikle. Gözlenebilir JVM davranışı ile anlatabileceğiniz her şey için, ilkeller referans olarak geçirilebilir ve yığın üzerinde yaşayabilir. Onlar değil, ama aslında hiçbir şekilde gözlemlenebilir değil.
Yerçekimi

6
ilkel değişmez mi? Java 7'de yeni olan bu mu?
user85421

4
İşaretçiler değişmez, genel olarak ilkel değişebilir. Dize de ilkel değil, bir nesnedir. Dahası, String'in temel yapısı değiştirilebilir bir dizidir. Bununla ilgili tek değişmeyen şey, dizilerin doğal doğası olan uzunluktur.
kingfrito_5005

3
Bu, tartışmanın büyük ölçüde anlamsal doğasını gösteren başka bir cevaptır. Bu cevapta verilen referansın tanımı, Java'yı "doğrudan referans" yapacaktır. Yazar, esasen son paragrafta, "uygulamada ayırt edilemez" ifadesini "referans yoluyla" ifadesinden itiraf ederek itiraf etmektedir. OP'nin Java uygulamasını anlama arzusundan ziyade Java'nın doğru şekilde nasıl kullanılacağını anlaması nedeniyle sorduğundan şüpheliyim. Eğer pratikte ayırt edilemez olsaydı, o zaman umursamamanın ve hatta düşünmenin bir zaman kaybı olacağının bir anlamı olmazdı.
Loduwijk

331

Java, referansları değere göre geçirir.

Böylece, iletilen referansı değiştiremezsiniz.


27
Ancak tekrarlanan "argümanlarda aktarılan nesnelerin değerini değiştiremezsiniz" ifadesi yanlıştır. Farklı bir nesneye başvurmalarını sağlayamayabilirsiniz, ancak yöntemlerini çağırarak içeriklerini değiştirebilirsiniz. IMO, referansların tüm avantajlarını kaybedeceğiniz ve ek garanti almamanız anlamına gelir.
Timmmm

34
Asla "argümanlarda aktarılan nesnelerin değerini değiştiremezsiniz" demedim. Java dili hakkında gerçek bir deyim olan "Yöntem bağımsız değişkeni olarak iletilen nesne başvurusunun değerini değiştiremezsiniz" diyorum. Açıkçası, nesnenin durumunu değiştirebilirsiniz (değişmez olmadığı sürece).
ScArcher2

20
Java'da nesneleri gerçekten aktaramayacağınızı unutmayın; nesneler öbek üzerinde kalır. Nesnelere işaretçiler geçirilebilir (çağrılan yöntem için yığın çerçevesine kopyalanır). Böylece, aktarılan değeri (işaretçi) asla değiştirmezsiniz, ancak onu takip etmekte ve öbek üzerinde işaret ettiği şeyi değiştirmekte özgürsünüz. Bu, her şeyden önce değer.
Scott Stanchfield

10
Senin gibi Sesler sadece bir referans geçti ? Java'nın hala kopyalanmış bir referans kodu olduğu gerçeğini savunacağım. Kopyalanmış bir referans olması, terminolojiyi değiştirmez. Her iki REFERANSLAR hala aynı nesneyi işaret ediyor. Bu bir safkanın iddiası ...
John Strickler

3
Java bir nesneyi iletmez, işaretçinin değerini nesneye iletir. Bu, yeni bir değişkende orijinal nesnenin o bellek konumuna yeni bir işaretçi oluşturur. Bir yöntemde bu işaretçi değişkeninin değerini (işaret ettiği bellek adresi) değiştirirseniz, yöntem arayanında kullanılan orijinal işaretçi değiştirilmeden kalır. Parametreye bir referans çağırıyorsanız, o zaman orijinal referansın bir kopyası olduğu gerçeği , nesneye şimdi iki referans olacak şekilde kendisinin değil, değerinin pass-by olduğu anlamına gelir
theferrit32

238

Ben "referans by pass-by-value" hakkında tartışmak gibi süper hissediyorum değil gibi hissediyorum.

"Java her ne ise olursa olsun (referans / değer)" derseniz, her iki durumda da tam bir cevap sağlamazsınız. Hafızada neler olup bittiğini anlamada yardımcı olacak bazı ek bilgiler.

Java uygulamasına geçmeden önce yığın / yığın üzerinde çökme kursu: Değerler, bir kafeteryadaki tabak yığını gibi, düzenli ve güzel bir şekilde yığını açar ve kapatır. Yığındaki bellek (dinamik bellek olarak da bilinir) gelişigüzel ve düzensizdir. JVM mümkün olan her yerde yer bulur ve onu kullanan değişkenlere artık ihtiyaç duyulmadığından alanı boşaltır.

Tamam. İlk önce, yerel ilkel yığın yığınına gider. Yani bu kod:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

bunun sonuçları:

yığındaki ilkeller

Bir nesneyi beyan edip somutlaştırdığınızda. Gerçek nesne öbek üzerinde gider. Yığına ne oluyor? Öbek üzerindeki nesnenin adresi. C ++ programcıları buna bir işaretçi derler, ancak bazı Java geliştiricileri "işaretçi" kelimesine karşıdır. Her neyse. Sadece nesnenin adresinin yığının üzerinde olduğunu bilin.

Şöyle ki:

int problems = 99;
String name = "Jay-Z";

ab * 7 kanal değil!

Bir dizi bir nesnedir, bu yüzden öbek üzerinde de gider. Dizideki nesneler ne olacak? Kendi yığın alanlarını alırlar ve her nesnenin adresi dizinin içine girer.

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

Marx kardeşler

Peki, bir yöntemi çağırdığınızda neler geçiyor? Bir nesneyi iletirseniz, aslında geçtiğiniz şey nesnenin adresidir. Bazıları adresin "değeri" diyebilir ve bazıları bunun sadece nesneye referans olduğunu söyleyebilir. Bu, "referans" ve "değer" taraftarları arasındaki kutsal savaşın doğuşudur. Dediğiniz şey, aktarılan şeyin nesnenin adresi olduğunu anladığınız kadar önemli değildir.

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

Bir Dize oluşturulur ve bunun için yığın öbeğe ayrılır ve dizeye adres yığını üzerinde depolanır ve tanımlayıcı verilir hisName, çünkü ikinci Dize'nin adresi birinciyle aynıdır, yeni Dize oluşturulmaz ve yeni yığın alanı ayrılmaz, ancak yığın üzerinde yeni bir tanımlayıcı oluşturulur. Sonra çağırırız shout(): yeni bir yığın çerçevesi oluşturulur ve yeni bir tanımlayıcı nameoluşturulur ve zaten var olan Dize'nin adresi atanır.

la da di da da da da

Değer, referans mı? "Patates" diyorsun.


7
Ancak, bir işlevi adresinin başvurusu olan bir değişkeni değiştirdiği göründüğü daha karmaşık bir örnek izlemiş olmalısınız.
Brian Peterson

34
O çünkü insanlar yığın vs yığının "gerçek soruna dans" olmayan değil asıl mesele. En iyi ihtimalle bir uygulama detayıdır ve en kötü ihtimalle düpedüz yanlıştır. ( "Kaçış analizi" google Ve nesnelerin çok sayıda muhtemelen ilkeller ihtiva nesneler yığını yaşamak için oldukça mümkündür. Yok asıl mesele olan yığın canlı.) Tam olarak referans türleri ve değer türleri arasındaki fark - özellikle, bir referans tipi değişkenin değerinin referans aldığı nesne değil referans olduğu.
cHao

8
Java'nın bir nesnenin bellekte nerede yaşadığını göstermesi için hiçbir zaman gerekli olmadığı ve aslında bu bilgilerin sızmasını önlemek için kararlı olduğu bir "uygulama detayı" dır . Nesneyi yığına koyabilir ve asla bilemezsiniz. Eğer umursuyorsanız, yanlış şeye odaklanıyorsunuz - ve bu durumda bu, gerçek meseleyi görmezden gelmek demektir.
cHao

10
Ve her iki durumda da, "ilkeler yığının üstüne çıkıyor" yanlış. İlkel yerel değişkenler yığına gider. (Tabii ki optimize edilmemişlerse, elbette.) Ama sonra, yerel referans değişkenleri de öyle . Ve bir nesnenin içinde tanımlanan ilkel üyeler, nesnenin yaşadığı her yerde yaşarlar.
cHao

9
Buradaki yorumlara katılıyorum. Yığın / yığın bir yan konudur ve ilgili değildir. Bazı değişkenler yığın üzerinde, bazıları statik bellekte (statik değişkenler) ve yığın üzerinde bol miktarda yaşar (tüm nesne üyesi değişkenleri). Bu değişkenlerin HİÇBİRİ başvuru ile iletilemez: çağrılan bir yöntemden bağımsız değişken olarak iletilen bir değişkenin değerini değiştirmek ASLA mümkün değildir. Bu nedenle, Java'da doğrudan başvuru yoktur.
balıkçılarMar

195

Sadece kontrastı göstermek için aşağıdaki C ++ ve Java snippet'lerini karşılaştırın:

C ++ ile: Not: Bozuk kod - bellek sızdırıyor! Ama konuyu gösteriyor.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

Java'da,

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java yalnızca iki tür geçişe sahiptir: yerleşik türler için değere ve nesne türleri için işaretçinin değerine göre.


7
+1 Ayrıca Dog **objPtrPtrC ++ örneğine eklerdim, bu şekilde işaretçinin "işaret ettiği" şeyi değiştirebiliriz.
Amro

1
Ancak bu, Java'da verilen soruya cevap vermez. Aslında, Java'nın yöntemindeki her şey Pass By Value, başka bir şey değil.
tauitdnmd

2
Bu örnek sadece Java C işaretçi çok eşdeğer kullandığını kanıtlamak? C cinsinden değere göre geçiş temel olarak salt okunurdur? Sonunda, tüm bu konu anlaşılmaz
amdev


166

Temel olarak, Object parametrelerini yeniden atamak bağımsız değişkeni etkilemez, ör.

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

"Hah!"yerine yazdırılacaktır null. Bunun çalışmasının nedeni bar, değerinin bir kopyasıdır baz, bu sadece referanstır "Hah!". Gerçek referans kendisi olsaydı, o zaman fooyeniden olurdu bazetmek null.


8
Barın başlangıçta aynı nesneye işaret eden referans baz (veya baz diğer adı) 'nın bir kopyası olduğunu söyleyebilirim.
MaxZoom

String sınıfı ve diğer tüm sınıflar arasında küçük bir fark yok mu?
Mehdi Karamosly

152

Henüz kimsenin Barbara Liskov'dan bahsetmediğine inanamıyorum. O 1974 yılında CLU'yu tasarlanmış, o bu aynı terminoloji sorun koştu ve o dönem icat paylaşımı çağrıyı (olarak da bilinen nesne paylaşımı yoluyla çağrı ve obje tarafından çağrı değeridir değerle çağrısı" bu özel durum için) referans".


3
İsimlendirmedeki bu ayrımı seviyorum. Java'nın çağrıyı nesneler için paylaşarak desteklemesi talihsizdir, ancak değere göre çağrı yapmaz (C ++ gibi). Java, bileşik veri türleri için değil, yalnızca ilkel veri türleri için değere göre aramayı destekler.
Derek Mahar

2
Gerçekten ekstra bir terime ihtiyacımız olduğunu düşünmüyorum - belirli bir değer türü için sadece by-pass değeri. "İlkel ile çağrı" eklenmesi herhangi bir açıklama getirir mi?
Scott Stanchfield


Bazı global bağlam nesnelerini paylaşarak, hatta başka referanslar içeren bir bağlam nesnesini ileterek referans yoluyla geçebilir miyim? Ben hala değer geçiyor, ama en azından değiştirebileceğim ve başka bir şeye işaret edebileceğim referanslara erişimim var .
YoYo

1
@Sanjeev: nesne arama çağrısı, özel bir değere göre geçiş durumudur. Bununla birlikte, birçok insan Java'nın (ve Python, Ruby, ECMAScript, Smalltalk gibi benzer dillerin) doğrudan referans olduğunu iddia ediyor. Ben pass-by-değeri de hitap etmeyi tercih ediyorum, ancak arama-by-nesne paylaşımı o olduğunu iddia insanlar bile dair makul terim gibi görünüyor değil pass-by-değerine anlaşabiliriz.
Jörg W Mittag

119

Meselenin özü bu kelime referans kelime olağan anlamından araçlar şey "referans olarak geçmektedir" ifadesinde bambaşka referans Java.

Genellikle Java başvurusunda bir nesneye aa başvurusu anlamına gelir . Ancak programlama dili teorisinden referans / değerle geçen teknik terimler , değişkeni tutan bellek hücresine yapılan bir referanstan bahsediyor , ki bu tamamen farklı bir şey.


7
@Gevorg - O zaman "NullPointerException" nedir?
Hot Licks

5
@Hot: Ne yazık ki, Java açık bir terminolojiye yerleşmeden önceki adı verilen bir istisna. C # içindeki anlamsal olarak eşdeğer özel duruma NullReferenceException adı verilir.
JacquesB

4
Bana her zaman Java terminolojisinde "referans" kullanımının anlayışı engelleyen bir sevgi olduğu görülüyordu.
Hot Licks

Bunları "nesnelere işaret eden" olarak adlandırmayı "nesneye tanıtıcı" olarak adlandırmayı öğrendim. Bu belirsizliği azalttı.
moronkreacionz

86

Point pnt1 = new Point(0,0);Java'da her şey referanstır, bu nedenle şöyle bir şey olduğunda: Java aşağıdakileri yapar:

  1. Yeni Point nesnesi oluşturur
  2. Yeni Nokta başvurusu oluşturur ve bu başvuruyu önceden oluşturulan Point nesnesindeki noktaya (başvuruya) başlatır .
  3. Buradan, Point nesnesi ömrü boyunca, o nesneye pnt1 referansı ile erişeceksiniz. Java'da nesneyi referansı ile değiştirdiğinizi söyleyebiliriz.

resim açıklamasını buraya girin

Java yöntem bağımsız değişkenlerini başvuru ile iletmez; onları değere göre geçer. Bu siteden örnek kullanacağım :

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

Program akışı:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

İlişkili iki farklı başvuru ile iki farklı Point nesnesi oluşturma. resim açıklamasını buraya girin

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

Beklenen çıktı olacağı gibi:

X1: 0     Y1: 0
X2: 0     Y2: 0

Bu satırda 'by-value' oyuna giriyor ...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

Kaynaklar pnt1ve pnt2edilmektedir değer geçirilir artık senin referanslar bu zor yöntem ile hangi araçlara pnt1ve pnt2onların sahip copiesadlandırılmış arg1ve arg2.bu pnt1ve arg1 puan aynı nesneye. (Aynı için pnt2vearg2 ) resim açıklamasını buraya girin

İn trickyyöntemi:

 arg1.x = 100;
 arg1.y = 100;

resim açıklamasını buraya girin

Bir Sonraki trickyyöntem

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

Burada, ilk yeni oluştur tempedecek Nokta referansı işaret gibi aynı yerde arg1referans. Sonra başvuruyu arg1, başvuru gibi aynı yeri işaret edecek şekilde arg2taşırsınız. Nihayet arg2edecek işaret gibi aynı yere temp.

resim açıklamasını buraya girin

Buradan kapsamı trickyyöntemine gitti ve referanslar artık erişiminiz yok: arg1, arg2, temp. Ancak önemli not, bu referanslarla 'hayatta' olduklarında yaptığınız her şeyin, üzerinde bulundukları nesneyi kalıcı olarak etkileyeceğidir. işaret .

Yani yöntemi uyguladıktan sonra tricky , , geri döndüğünüzde main, bu duruma sahipsiniz: resim açıklamasını buraya girin

Şimdi, tamamen programın yürütülmesi olacak:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0

7
Java'da "Java'da her şey referanstır" derken, tüm nesnelerin referansla geçtiği anlamına gelir . İlkel veri türleri referans olarak iletilmez.
Eric

Değiştirilen değeri ana yöntemle yazdırabilirim. trickey yönteminde, şu ifadeyi ekleyin, arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;böylece arg1 şimdi pnt2 referansını tutuyor ve arg2 şimdi pnt1 referansını tutuyor, bu nedenle, yazdırılıyorX1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor

85

Java her zaman değere göre geçer, referansla geçilmez

Her şeyden önce, değere göre geçişin ve referansla geçişin ne olduğunu anlamamız gerekir.

Değere göre geç, aktarılan gerçek parametrenin değerinin belleğinde bir kopya yaptığınız anlamına gelir. Bu, gerçek parametrenin içeriğinin bir kopyasıdır .

Referansla geçiş (adrese göre geçiş de denir), gerçek parametrenin adresinin bir kopyasının saklandığı anlamına gelir .

Bazen Java referans olarak geçiş yanılsaması verebilir. Aşağıdaki örneği kullanarak nasıl çalıştığını görelim:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

Bu programın çıktısı:

changevalue

Adım adım anlayalım:

Test t = new Test();

Hepimizin bildiği gibi, yığın içinde bir nesne oluşturacak ve referans değerini tekrar t'ye döndürecektir. Örneğin, t değerinin 0x100234(gerçek JVM iç değerini bilmediğimizi varsayalım , bu sadece bir örnek).

ilk örnek

new PassByValue().changeValue(t);

Fonksiyona t referansını iletirken, nesne testinin gerçek referans değerini doğrudan geçmeyecek, ancak t'nin bir kopyasını oluşturacak ve sonra fonksiyona iletecektir. Bu olduğundan değeri geçirerek , bu değişken değil, bunun yerine gerçek bir referans kopyasını geçer. T'nin değeri olduğunu söylediğimizden 0x100234, hem t hem de f aynı değere sahip olacak ve dolayısıyla aynı nesneyi gösterecektir.

ikinci resim

Referans f'yi kullanarak işlevdeki herhangi bir şeyi değiştirirseniz, nesnenin var olan içeriği değiştirilir. Bu yüzden changevaluefonksiyonda güncellenen çıktıyı aldık .

Bunu daha net anlamak için aşağıdaki örneği göz önünde bulundurun:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

Bu atar NullPointerExceptionmı? Hayır, çünkü referansın sadece bir kopyasını geçer. Referans olarak geçilmesi durumunda, NullPointerExceptionaşağıda görüldüğü gibi a atılmış olabilir :

üçüncü örnek

Umarım bu yardımcı olacaktır.


71

Referans, hangi dili kullanırsanız kullanın, temsil edildiğinde her zaman bir değerdir.

Kutu görünümünün dışına çıkarak, Meclis'e veya bazı düşük seviye bellek yönetimine bakalım. CPU düzeyinde , herhangi bir şeye yapılan bir başvuru , belleğe veya CPU kayıtlarından birine yazılırsa hemen bir değer olur. (Bu yüzden işaretçi iyi bir tanımdır. Aynı zamanda bir amacı olan bir değerdir).

Bellekteki verilerin bir Konumu vardır ve bu konumda bir değer vardır (bayt, sözcük, her neyse). Kurul biz bir vermek için uygun bir çözüme sahip Adı belli etmek Konum (aka değişken), ancak kod derleme yaparken, montajcı basitçe yerini Adı sadece IP adresleri ile tarayıcı cümledeki alan adları gibi belirlenen konuma sahip.

Çekirdeğe kadar, herhangi bir dilde herhangi bir şeye temsil etmeden referans vermek (hemen bir değer olduğunda) teknik olarak imkansızdır.

Diyelim ki bir Foo değişkenimiz var, Konumu bellekte 47. baytta ve Değeri 5. Başka bir değişken Ref2Foo var. , program tarafından açıkça oluşturulmamıştır. Eğer herhangi bir bilgi olmadan 5 ve 47'ye bakarsanız, sadece iki Değer göreceksiniz . Bunları referans olarak kullanırsanız, oraya ulaşmak 5için seyahat etmeliyiz:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

Atlama tabloları bu şekilde çalışır.

Foo değerine sahip bir yöntemi / işlevi / yordamı çağırmak istiyorsak, dile ve çeşitli yöntem çağırma modlarına bağlı olarak değişkeni yönteme geçirmenin birkaç olası yolu vardır :

  1. 5 CPU kayıtlarından birine (yani EAX) kopyalanır.
  2. 5 PUSHd'yi yığına alır.
  3. 47 CPU kaydından birine kopyalanır
  4. 47 PUSHd yığına.
  5. 223 CPU kayıtlarından birine kopyalanır.
  6. 223 PUSHd'yi yığına alır.

Bir değerin üzerindeki her durumda - mevcut bir değerin bir kopyası - yaratıldı, şimdi onu ele almak için alıcı yöntemine bağlı. Yöntemin içine "Foo" yazdığınızda, EAX'tan okunur veya otomatik olarak kayıttan çıkarılır veya çift kayıttan çıkarılır, işlem dilin nasıl çalıştığına ve / veya Foo türünün ne gerektirdiğine bağlıdır. Bu, kayıt silme işlemini atlayana kadar geliştiriciden gizlenir. Bu nedenle başvuru , temsil edildiğinde bir değerdir , çünkü başvuru, işlenmesi gereken bir değerdir (dil düzeyinde).

Şimdi Foo'yu yönteme geçtik:

  • Durum 1 ve 2'de Foo (Foo = 9 ) ' bir kopyasına sahip olduğunuz için yalnızca yerel kapsamı etkiler. Yöntemin içinden orijinal Foo'nun bellekte nerede olduğunu bile belirleyemiyoruz.
  • 3. ve 4. durumda, varsayılan dil yapılarını kullanır ve Foo'yu ( Foo = 11) değiştirirseniz, Foo'yu global olarak değiştirebilir (dile bağlıdır, yani Java veya Pascal'ın var procedure findMin(x, y, z: integer;m'si gibi : integer);). Ancak, dil dereference sürecini atlatmanıza izin veriyorsa 47, diyelim 49. Bu noktada, eğer okursanız Foo değiştirilmiş gibi görünüyor, çünkü yerel işaretçiyi değiştirdiniz . Ve eğer bu Foo'yu yöntem ( Foo = 12) içinde değiştirirseniz , muhtemelen programın yürütülmesini FUBAR (aka. Segfault) çünkü beklenenden farklı bir belleğe yazacaksınız, hatta çalıştırılabilir tutmaya yönelik bir alanı bile değiştirebilirsiniz. program ve ona yazmak çalışan kodu değiştirir (Foo artık değil47 ). AMA Foo'nun değeri47global olarak değişmedi, sadece yöntemin içindeki olan, çünkü yöntemin 47bir kopyasıydı.
  • 5. ve 6. durumda 223, yöntemin içinde değişiklik yaparsanız , 3. veya 4. ile aynı kargaşayı oluşturur (bir işaretçi, şimdi kötü bir değere işaret eder, bu yine işaretçi olarak kullanılır), ancak bu yine de yereldir 223 kopyalandığı için sorun . Ancak, referansı kaldırabiliyorsanız Ref2Foo(yani 223), sivri değere ulaşıp değiştirebiliyorsanız 47, örneğin, 49Foo'yu küresel olarak etkileyecektir , çünkü bu durumda yöntemlerin bir kopyası var, 223 ancak referans verilen 47sadece bir kez var ve bunu değiştiriyor için 49her yol açacaktır Ref2Fooyanlış değere çift referanslanması.

Önemsiz ayrıntılar, hatta referans yoluyla geçen diller bile nitelendirmek, değerlere işlevlere geçecektir, ancak bu işlevler, bunu kayıttan çıkarma amaçları için kullanmaları gerektiğini bilir. Bu değer olarak referans değeri, programcıdan gizlenir, çünkü pratik olarak işe yaramaz ve terminoloji sadece referans yoluyla geçer .

Sıkı geçiş değeri de işe yaramaz, bu, diziyle bir yöntemi bağımsız değişken olarak her çağırdığımızda 100 Mbaytlık bir dizinin kopyalanması gerektiği anlamına gelir, bu nedenle Java kesin olarak değere sahip olamaz. Her dil bu büyük diziye (değer olarak) bir başvuru iletir ve bu dizi yöntemin içinde yerel olarak değiştirilebiliyorsa veya yöntemin (Java'nın yaptığı gibi) diziyi genel olarak ( arayanın görünümü) ve birkaç dil referansın Değerinin değiştirilmesine izin verir.

Yani kısacası ve Java'nın kendi terminolojisinde, Java olduğunu pass-by-değeri nerede değeri bir ya: olabilmektedir gerçek değeri veya değer bir bir temsilidir referans .


1
Referans yoluyla geçen bir dilde, geçilen şey (referans) geçici; alıcının onu kopyalaması "gerekiyor" değildir. Java'da, bir dizi iletilmesi bir "nesne tanımlayıcısı" dır - oluşturulan 24601st nesnesi bir dizi olduğunda "Object # 24601" yazan kağıda eşdeğerdir. Alıcı "Object # 24601" i istediği herhangi bir yere kopyalayabilir ve "Object # 24601" diyen bir kağıt parçası olan herkes dizi öğelerinden herhangi biriyle istediği her şeyi yapabilir.
Geçilen

... kilit nokta, diziyi tanımlayan nesne tanıtıcısının alıcısının bu nesne tanıtıcısını istediği yerde depolayabilmesi ve istediği kişiye verebilmesi ve herhangi bir alıcının diziye istediği zaman erişebilmesi veya değiştirebilmesidir. istiyor. Aksine, bir dizi bu tür şeyleri destekleyen Pascal gibi bir dilde referansla iletildiyse, çağrılan yöntem dizi ile istediği her şeyi yapabilir, ancak referansı kodun diziyi değiştirmesine izin verecek şekilde depolayamazdı geri döndükten sonra.
supercat

Nitekim, Pascal size olabilir , her değişkenin adresini almak kopyalanmış lokal yan referans-geçti veya, addro türsüz yapar @türüyle yapar ve (yerel kopyalanmış olanları hariç) sonradan başvurulan değişkeni değiştirebilir. Ama bunu neden yapacağınızı anlamıyorum. Örneğinizdeki (Object # 24601) bu kağıt kayması bir referanstır, amacı dizideki belleği bulmaya yardımcı olmaktır, kendi içinde herhangi bir dizi verisi içermez. Programınızı yeniden başlatırsanız, içeriği önceki çalıştırmada olduğu gibi olsa bile aynı dizi farklı bir nesne kimliği alabilir.
karatedog

"@" Operatörünün standart Pascal'ın bir parçası olmadığını, ancak ortak bir uzantı olarak uygulandığını düşünmüştüm. Standardın bir parçası mı? Demek istediğim, gerçek pass-by-ref'ye sahip bir dilde ve geçici bir nesneye geçici olmayan bir işaretçi oluşturma yeteneği yok, diziyi geçmeden önce evrenin herhangi bir yerinde, bir diziye tek referansı tutan kod referans alıcı "hile" sürece daha sonra hala tek referans tutacağını bilir. Java'da bunu yapmanın tek güvenli yolu geçici bir nesne inşa etmek olacaktır ...
supercat

... AtomicReferencereferansı ya da hedefini kapsamayan ve ne ortaya koyan, bunun yerine hedefe bir şeyler yapma yöntemleri içerir; nesnenin iletildiği kod geri döndüğünde, AtomicReference[yaratıcısının doğrudan referans tuttuğu] geçersiz kılınmalı ve terk edilmelidir. Bu uygun anlambilimi sağlar, ancak yavaş ve buz gibi olur.
supercat

70

Java değere göre bir çağrıdır

Nasıl çalışır

  • Referans değerinin bitlerinin bir kopyasını her zaman geçirirsiniz!

  • İlkel bir veri türü ise bu bitler ilkel veri türünün değerini içerir, Bu nedenle yöntemin içindeki üstbilginin değerini değiştirirsek, o zaman dışarıdaki değişiklikleri yansıtmaz.

  • Bunun gibi bir nesne veri türü ise ) Foo foo = new Foo ( dosya kısayol gibi, varsayalım geçer nesnenin adresi bu durumda kopyasında o zaman bir metin dosyası var abc.txt de \ masaüstü: C ve biz kısa yolunu yapmak varsayalım Aynı dosyayı C: \ desktop \ abc-shortcut'ın içine koyun, böylece C: \ desktop \ abc.txt dosyasından dosyaya erişip 'Stack Overflow' yazıp dosyayı kapatıp dosyayı yeniden kısayoldan açtığınızda yazma 'programcılar için öğrenilecek en büyük çevrimiçi topluluktur' o zaman toplam dosya değişikliği olacaktır, 'Stack Overflow programcılar için öğrenilecek en büyük çevrimiçi topluluktur'bu da dosyayı açtığınız yerden önemli olmadığı anlamına gelir, aynı dosyaya her eriştiğimizde, buradaFoo bir dosya ve saklanan varsayalım foo olarak 123hd7h (orijinal adresi gibi : \ masaüstü \ abc.txt C adres ve) 234jdid (gibi kopyalanan adrese C: \ masaüstü \ abc-kısayol aslında dosya iç özgün adresini içerir). Daha iyi anlamak için kısayol dosyası ve hissettirin ..


66

Bunu kapsayan harika cevaplar zaten var. Ben c ++ referans Pass-by-value Java ile davranışları zıt çok basit bir örnek (bu derleme) paylaşarak küçük bir katkı yapmak istedim .

Birkaç nokta:

  1. "Referans" terimi, iki ayrı anlama sahip aşırı yüklenmedir. Java'da basitçe bir işaretçi anlamına gelir, ancak "Referansla" bağlamında, içeri aktarılan orijinal değişken için bir tanıtıcı anlamına gelir.
  2. Java, değere göre . Java (diğer diller arasında) C'nin soyundan gelir. C'den önce, FORTRAN ve COBOL gibi birkaç (ancak hepsi değil) önceki dil PBR'yi destekledi, ancak C desteklemedi. PBR bu diğer dillerin alt rutinler içinde geçirilen değişkenlerde değişiklik yapmasına izin verdi. Aynı şeyi başarmak için (yani fonksiyonların içindeki değişkenlerin değerlerini değiştirmek için), C programcıları değişkenlere fonksiyonlara yöneltti. Java gibi C'den esinlenen diller bu fikri ödünç aldı ve Java'nın işaretçileri Referanslar olarak adlandırması dışında C'nin yaptığı yöntemlere işaretçi aktarmaya devam ediyor. Yine, bu "Referans" kelimesinin "Referansla Geçiş" ten farklı bir kullanımıdır.
  3. C ++ başvuruya izin verir , "&" karakterini (hem C hem de C ++ 'da "bir değişkenin adresini" belirtmek için kullanılan karakterle aynı olan) bir referans parametresi bildirerek . Örneğin, bir işaretçiyi başvuru ile iletirsek, parametre ve bağımsız değişken yalnızca aynı nesneyi işaret etmez. Aksine, bunlar aynı değişkendir. Biri farklı bir adrese veya null değerine ayarlanırsa diğeri de ayarlanır.
  4. Aşağıdaki C ++ örneğinde , başvuru ile boş bir sonlandırılmış dizeye bir işaretçi geçiriyorum . Ve aşağıdaki Java örneğinde, bir String'e bir Java başvurusu geçiriyorum (yine, bir String'e gösterici ile aynı) değere göre. Yorumlarda çıktıya dikkat edin.

Referans örneğiyle C ++ geçişi:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java, örnek örneği ile "bir Java başvurusu" iletir

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

DÜZENLE

Birkaç kişi ya benim örneklerime bakmıyor ya da c ++ örneğini alamadıklarını gösteren yorumlar yazdı. Bağlantıyı kesmenin nerede olduğundan emin değilim, ancak c ++ örneğini tahmin etmek net değil. Aynı örneği pascal'da gönderiyorum çünkü pass-by-referansın pascal'da daha temiz göründüğünü düşünüyorum, ama yanlış olabilirim. İnsanları daha çok karıştırıyorum; Umarım değildir.

Pascal'de, referansla iletilen parametrelere "var parametreleri" denir. Aşağıdaki setToNil prosedüründe, lütfen 'ptr' parametresinden önce gelen 'var' anahtar kelimesine dikkat edin. Bu yordama bir işaretçi iletildiğinde , başvuru ile iletilir . Davranışı not edin: Bu yordam ptr değerini nil olarak ayarladığında (bu NULL için parola konuşur), argümanı nil olarak ayarlar - Java'da bunu yapamazsınız.

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

DÜZENLEME 2

Ken Arnold, James Gosling (Java'yı icat eden kişi) ve David Holmes, bölüm 2, bölüm 2.6.5 tarafından yazılan "THE Java Programlama Dili" nden bazı alıntılar

Yöntemlere ilişkin tüm parametreler "değere göre" geçirilir . Başka bir deyişle, bir yöntemdeki parametre değişkenlerinin değerleri, invoker'ın argüman olarak belirtilen kopyalarıdır.

Nesneler için de aynı noktayı vurgulamaya devam ediyor. . .

Parametre bir nesne başvurusu olduğunda, nesnenin kendisi değil, nesnenin başvurusu olduğunu ve "değere göre" iletildiğini unutmayın .

Ve aynı bölümün sonuna doğru, java'nın sadece değere göre geçtiği ve asla referansla geçmediği konusunda daha geniş bir açıklama yapar.

Java programlama dili nesneleri referans olarak iletmez; bu değer nesne referansları geçer . Aynı başvurunun iki kopyası aynı gerçek nesneyi ifade ettiğinden, bir referans değişkeni üzerinden yapılan değişiklikler diğeri tarafından görülebilir. Tam olarak bir parametre geçirme modu vardır - değer -ve ile işleri basit tutmaya yardımcı olur.

Kitabın bu bölümünde Java'da parametre geçişi ve referans by-pass ve by-value arasındaki farkın büyük bir açıklaması vardır ve Java'nın yaratıcısıdır. Özellikle ikna olmadıysanız, kimseyi okumaya teşvik ederim.

Bence iki model arasındaki fark çok incedir ve gerçekten referans olarak kullandığınız yerde programlama yapmadıysanız, iki modelin farklı olduğu yerleri kaçırmak kolaydır.

Umarım bu tartışmayı çözer, ama muhtemelen çözmez.

DÜZENLE 3

Bu yazıya biraz takıntılı olabilirim. Muhtemelen Java üreticilerinin yanlışlıkla yanlış bilgi yaydığını hissediyorum. İşaretçiler için "referans" kelimesini kullanmak yerine, başka bir şey kullandılarsa, dingleberry diyelim, sorun olmazdı. "Java, pislikleri referans ile değil değere geçirir" diyebilirsiniz ve kimse karışmaz.

Bu nedenle sadece Java geliştiricilerinin sorunu var. "Referans" kelimesine bakarlar ve bunun tam olarak ne anlama geldiğini bildiklerini düşünürler, bu yüzden karşıt argümanı düşünmek bile zahmetine girmezler.

Her neyse, eski bir gönderide, gerçekten sevdiğim bir balon benzetmesi yapan bir yorum fark ettim. Öyle ki, noktayı göstermek için bir dizi karikatür yapmak için bazı küçük resimleri yapıştırmaya karar verdim.

Bir referansın değere göre iletilmesi - Referanstaki değişiklikler, arayanın kapsamına yansıtılmaz, ancak nesnedeki değişiklikler yapılır. Bunun nedeni, başvurunun kopyalanmasıdır, ancak hem orijinal hem de kopya aynı nesneye başvurur. Nesne başvurularını Değere Göre Geçme

Referansla geç - Referansın bir kopyası yok. Tek referans hem arayan hem de çağrılan işlev tarafından paylaşılır. Referans veya Nesne verilerindeki herhangi bir değişiklik, arayanın kapsamına yansıtılır. Referans ile geç

DÜZENLEME 4

Bu konuda, Java'da parametre geçişinin düşük düzeyli uygulamasını açıklayan yayınlar gördüm, bu da büyük ve çok yararlı olduğunu düşünüyorum çünkü soyut bir fikri somutlaştırıyor. Ancak, bana göre soru daha çok davranışın teknik uygulamasından çok dil spesifikasyonunda tarif edilen davranış hakkındadır. Bu, Java Dil Spesifikasyonu, bölüm 8.4.1'den alınmıştır :

Yöntem veya kurucu çağrıldığında (§15.12), gerçek argüman ifadelerinin değerleri , yöntemin veya kurucunun gövdesinin yürütülmesinden önce, her biri bildirilen türden yeni oluşturulan parametre değişkenlerini başlatır. DeclaratorId'de görünen Tanımlayıcı, resmi parametreye başvurmak için yöntem veya yapıcı gövdesinde basit bir ad olarak kullanılabilir.

Yani java, bir yöntemi uygulamadan önce iletilen parametrelerin bir kopyasını oluşturur. Üniversitede derleyici okuyan çoğu insan gibi ben de "Ejderha Kitabı" nı kullandım olduğunu derleyiciler kitabı. Bölüm 1'de "Değer değerine göre çağrı" ve "Referansa göre çağrı" ile ilgili iyi bir açıklamaya sahiptir. Değere göre çağrı açıklaması, Java Spesifikasyonları ile tam olarak eşleşir.

90'lı yıllarda derleyiciler okuduğumda, 1986'dan itibaren Java'yı yaklaşık 9 veya 10 yıl öncesine dayanan kitabın ilk baskısını kullandım. Ancak, 2007'den 2. Baskı'nın bir kopyasına rastladım aslında Java'dan bahseden ! "Parametre Geçiş Mekanizmaları" etiketli Bölüm 1.6.6, parametre geçişini oldukça iyi açıklamaktadır. Java'dan bahseden "Değere göre çağrı" başlığı altında bir alıntı:

Call-by-value'da, gerçek parametre değerlendirilir (eğer bir ifade ise) veya kopyalanır (bir değişkense). Değer, çağrılan prosedürün karşılık gelen resmi parametresine ait konuma yerleştirilir. Bu yöntem C ve Java'da kullanılır ve C ++ 'da ve diğer birçok dilde yaygın bir seçenektir.


4
Dürüst olmak gerekirse, Java'nın sadece ilkel tipler için değere göre geçtiğini söyleyerek bu cevabı basitleştirebilirsiniz. Object'den devralınan her şey, referansın geçtiğiniz işaretçidir olduğu etkili bir şekilde referansla geçirilir.
Scuba Steve

1
@JuanMendes, ben sadece bir örnek olarak C ++ kullandım; Size başka dillerde örnekler verebilirim. "Başvuru ile geç" terimi, C ++ bulunmadan çok önce vardı. Çok özel bir tanımı olan bir ders kitabı terimidir. Ve tanım gereği, Java referans olarak geçmez. İsterseniz terimi bu şekilde kullanmaya devam edebilirsiniz, ancak kullanımınız ders kitabı tanımıyla tutarlı olmayacaktır. Bu sadece bir işaretçiye işaretçi değildir. "Referansla Geçiş" e izin vermek için dil tarafından sağlanan bir yapıdır. Lütfen örneğime yakından bakın, ama lütfen benim sözüme güvenmeyin ve kendiniz arayın.
Sanjeev

2
@AutomatedMike Java'yı "referansla" olarak tanımlamanın da yanıltıcı olduğunu, referanslarının işaretçilerden başka bir şey olmadığını düşünüyorum. Sanjeev'in değer yazdığı gibi, referans ve işaretçi, Java yaratıcılarının ne kullandığına bakılmaksızın kendi anlamları olan ders kitabı terimleridir.
Jędrzej Dudkiewicz

1
@ArtanisZeratul nokta ince, ama karmaşık değil. Lütfen gönderdiğim karikatüre bir göz atın. Takip etmenin oldukça basit olduğunu hissettim. Java'nın değer bazında olması sadece bir fikir değildir. Bu, değere göre ders kitabı tanımı ile doğrudur. Ayrıca, "her zaman değerden geçtiğini söyleyen insanlar", Java'nın yaratıcısı James Gosling gibi bilgisayar bilimcilerini de içerir. Lütfen yazımda "Düzenle 2" altındaki kitabından alıntılara bakın.
Sanjeev

1
Ne büyük bir cevap, "referans ile geçmek" java (ve JS gibi diğer dillerde) mevcut değildir.
newfolder

56

Bildiğim kadarıyla, Java sadece çağrıya göre değer biliyor. Bu, ilkel veri türleri için bir kopyayla ve nesneler için nesnelere yapılan başvurunun bir kopyasıyla çalışacağınız anlamına gelir. Ancak bazı tuzaklar olduğunu düşünüyorum; örneğin, bu çalışmaz:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

Bu, Hello World'ü değil Hello World'ü dolduracaktır, çünkü takas işlevinde ana referanslar üzerinde hiçbir etkisi olmayan kopyaları kullanırsınız. Ancak nesneleriniz değişmezse, örneğin değiştirebilirsiniz:

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

Bu, Hello World'ü komut satırında dolduracaktır. StringBuffer'ı String olarak değiştirirseniz, String değişmez olduğu için yalnızca Hello üretir. Örneğin:

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

Ancak, String için şu şekilde bir sarmalayıcı oluşturabilirsiniz;

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

edit: bu da String gibi değişmez nesneleri ile yapamaz orijinal nesne değiştirebilirsiniz çünkü iki dizeleri "ekleme" söz konusu olduğunda StringBuffer kullanmanın nedeni olduğuna inanıyorum.


Ayırt muhtemelen en basit ve ilişkilendirilebilir şekilde - değiştirilebilir testi için + 1 referans geçirerek ve değeri bir referans geçen . Kolayca bir fonksiyon yazabiliriz IFF swap(a, b)(1) takasları olduğunu ave barayanın POV, (2) tip-agnostik statik yazarak izin verdiği ölçüde olduğunu (başka türüyle kullanarak anlam daha beyan türlerini değiştirerek daha hiçbir şey gerektirir ave b) ve (3) arayan kişinin açıkça bir işaretçi veya isim geçirmesini gerektirmez, ardından dil referans ile geçmeyi destekler.
cHao

".. ilkel veri türleri için bir kopya ile çalışacaksınız ve nesneler için nesnelere referans bir kopya ile çalışacak" - mükemmel yazılmış!
Raúl

55

Hayır, referans ile geçilmez.

Java, Java Dil Belirtimi'ne göre değere göre geçer:

Yöntem veya kurucu çağrıldığında (§15.12), gerçek argüman ifadelerinin değerleri , yöntemin veya kurucunun gövdesinin yürütülmesinden önce, her biri bildirilen türden yeni oluşturulan parametre değişkenlerini başlatır . DeclaratorId'de görünen Tanımlayıcı, resmi parametreye başvurmak için yöntem veya yapıcı gövdesinde basit bir ad olarak kullanılabilir .


52

Dört örnek yardımıyla anlayışımı açıklamaya çalışayım. Java, değere göre değerlidir ve başvuruya göre değil

/ **

Değere Göre Geçme

Java'da tüm parametreler değere göre iletilir; başka bir deyişle, bir yöntem bağımsız değişkeni atamak arayan tarafından görülemez.

* /

Örnek 1:

public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Sonuç

output : output
value : Nikhil
valueflag : false

Örnek 2:

/ ** * * Değere Göre Geç * * /

public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Sonuç

output : output
value : Nikhil
valueflag : false

Örnek 3:

/ ** Bu 'Pass By Value' Referansla Pass 'hissine sahiptir

Bazı insanlar ilkel türlerin ve 'String'in' değere göre geç 'olduğunu ve nesnelerin' referansla geç 'olduğunu söyler.

Ancak bu örnekten, referansı değer olarak geçirdiğimizi akılda tutarak, bunun sadece değere göre kesin olarak geçtiğini anlayabiliriz. yani: referans değere göre iletilir. Bu yüzden değişebilir ve yerel kapsamdan sonra hala geçerlidir. Ancak asıl referansı orijinal kapsamın dışında değiştiremeyiz. bunun anlamı bir sonraki PassByValueObjectCase2 örneği ile gösterilmiştir.

* /

public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}

Sonuç

output : output
student : Student [id=10, name=Anand]

Örnek 4:

/ **

Örnek3'te (PassByValueObjectCase1.java) belirtilenlere ek olarak, gerçek başvuruyu orijinal kapsamın dışında değiştiremeyiz. "

Not: Kodunu yapıştırmıyorum private class Student. İçin sınıf tanımı StudentÖrnek3 ile aynıdır.

* /

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

Sonuç

output : output
student : Student [id=10, name=Nikhil]

49

Java'da hiçbir zaman başvuru ile geçemezsiniz ve aşikar olan yollardan biri, bir yöntem çağrısından birden fazla değer döndürmek istemenizdir. Aşağıdaki kod bitini C ++ ile düşünün:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

Bazen Java'da aynı kalıbı kullanmak istersiniz, ancak kullanamazsınız; en azından doğrudan değil. Bunun yerine böyle bir şey yapabilirsiniz:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

Önceki yanıtlarda açıklandığı gibi, Java'da diziye bir değer olarak bir işaretçi geçiriyorsunuz getValues. Bu yeterlidir, çünkü yöntem daha sonra dizi öğesini değiştirir ve kural olarak, 0 öğesinin dönüş değerini içermesini beklersiniz. Açıkçası bunu, kodunuzu gerekli olmayacak şekilde yapılandırmak veya dönüş değerini içerebilecek veya ayarlanmasına izin verecek bir sınıf oluşturmak gibi başka şekillerde de yapabilirsiniz. Ancak yukarıdaki C ++ ile kullanabileceğiniz basit desen Java'da mevcut değildir.


48

Spesifikasyonlardan daha fazla ayrıntı eklemek için bu cevaba katkıda bulunacağımı düşündüm.

İlk olarak, referansla geçiş ile değere göre geçiş arasındaki fark nedir?

Referans yoluyla geçme, çağrılan fonksiyonların parametresi, arayanların geçirilen argümanı ile aynı olacaktır (değer değil, kimlik - değişkenin kendisi).

Değere göre geçirme, çağrılan işlevlerin parametresi, arayanların geçirilen bağımsız değişkeninin bir kopyası olacağı anlamına gelir.

Veya wikipedia'dan, referansla pass konusunda

Referansa göre arama değerlendirmesinde (referansla by pass olarak da adlandırılır), bir işlev, değerinin bir kopyası yerine bağımsız değişken olarak kullanılan bir değişkene örtülü bir başvuru alır. Bu genellikle işlevin argüman olarak kullanılan değişkeni değiştirebileceği (yani atayabileceği) anlamına gelir - arayan tarafından görülecek bir şey.

Ve değer bazında

Call-by-value'da, argüman ifadesi değerlendirilir ve ortaya çıkan değer, [...] fonksiyonundaki ilgili değişkene bağlanır. İşlev veya prosedür parametrelerine değer atayabilirse, yalnızca yerel kopyası atanır [...].

İkincisi, Java'nın yöntem çağrılarında ne kullandığını bilmemiz gerekir. Java Dil Şartname devletler

Yöntem veya kurucu çağrıldığında (§15.12), gerçek argüman ifadelerinin değerleri , yöntemin veya kurucunun gövdesinin yürütülmesinden önce, her biri bildirilen türden yeni oluşturulan parametre değişkenlerini başlatır .

Böylece, bağımsız değişkenin değerini ilgili parametre değişkenine atar (veya bağlar).

Argümanın değeri nedir?

Let referans tipleri, düşünün Java Virtual Machine Şartname devletleri

Üç tür referans türü vardır : sınıf türleri, dizi türleri ve arabirim türleri. Değerleri, dinamik olarak oluşturulan sınıf örneklerine, dizilere veya arabirimleri uygulayan sınıf örneklerine veya dizilere referanstır.

Java Dil Şartname ayrıca devletler

Referans değerleri (genellikle sadece referanslar) bu nesnelere işaret eder ve hiçbir nesneye atıfta bulunmayan özel bir boş referanstır.

Bir argümanın değeri (bazı referans türlerinden) bir nesneye işaret eder. Bir değişkenin, başvuru türü döndürme türüne sahip bir yöntemin çağrılması ve bir örnek oluşturma ifadesinin ( new ...) hepsinin referans türü değerine çözümlendiğini unutmayın.

Yani

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

tümü, bir Stringörneğe yapılan başvurunun değerini yöntemin yeni oluşturulan parametresine bağlar param. Bu, tam-değer-tanımının tanımladığı şeydir. Bu nedenle, Java by-pass-by değeridir .

Bir yöntemi çağırmak veya başvurulan nesnenin bir alanına erişmek için referansı takip edebilmeniz, konuşma ile tamamen ilgisizdir. Referansla pass tanımı

Bu genellikle işlevin argüman olarak kullanılan değişkeni değiştirebileceği (yani atayabileceği) anlamına gelir - arayan tarafından görülecek bir şey.

Java'da değişkeni değiştirmek, yeniden atamak anlamına gelir. Java'da, değişkeni yöntem içinde yeniden atarsanız, arayana fark edilmez. Değişkenin referans aldığı nesneyi değiştirmek tamamen farklı bir kavramdır.


İlkel değerler ayrıca burada Java Sanal Makine Spesifikasyonu'nda tanımlanmıştır . Türün değeri, uygun şekilde kodlanmış karşılık gelen integral veya kayan nokta değeridir (8, 16, 32, 64 vb. Bitler).


42

Java'da yalnızca başvurular iletilir ve değere göre iletilir:

Java bağımsız değişkenlerinin tümü değere göre iletilir (başvuru yöntem tarafından kullanıldığında kopyalanır):

İlkel türlerde Java davranışı basittir: Değer, ilkel türün başka bir örneğinde kopyalanır.

Nesneler söz konusu olduğunda aynıdır: Nesne değişkenleri, yalnızca "new" anahtar sözcüğü kullanılarak oluşturulan ve ilkel türler gibi kopyalanan Object'in adresini tutan işaretçilerdir (kovalar) .

Davranış ilkel türlerden farklı görünebilir: Kopyalanan nesne değişkeni aynı adresi içerdiğinden (aynı Nesneye). Nesnenin içeriği / üyeleri yine de bir yöntem içinde değiştirilebilir ve daha sonra dışarıya erişerek Nesnenin kendisinin referans olarak geçirildiği yanılsamasını verir.

"String" Nesneleri , "Nesnelerin referans olarak iletildiğini" söyleyen kentsel efsaneye iyi bir karşı örnek gibi görünüyor :

Aslında, bir yöntem kullanarak, argüman olarak iletilen bir Dizenin değerini asla güncelleyemezsiniz:

Bir String Nesnesi, karakterleri değiştirilemeyen final olarak belirtilen bir diziye göre tutar . Yalnızca nesnenin adresi "yeni" kullanılarak başka bir adresle değiştirilebilir. Değişkeni güncellemek için "yeni" kullanıldığında, değişken başlangıçta değere göre geçirildiğinden ve kopyalandığından Nesneye dışarıdan erişilmesine izin vermez.


Yani nesnelere göre byRef ve ilkellere göre byVal?
Mox

@mox lütfen okuyun: Nesneler başvuru ile iletilmiyor, bu bir ledgend: String a = new String ("unchanged");
user1767316

1
@Aaron "Başvuru ile geç", "Java'da" başvuru "olarak adlandırılan türün bir üyesi olan bir değeri iletmek anlamına gelmez". "Referans" ın iki kullanımı farklı anlamlara gelir.
philipxy

2
Oldukça kafa karıştırıcı bir cevap. Bir dize nesnesini başvuru yoluyla iletilen bir yöntemde değiştirememenizin nedeni, Dize nesnelerinin tasarımla değiştirilememesi ve benzeri şeyler yapamamanızdır strParam.setChar ( i, newValue ). Bununla birlikte, dizeler, başka bir şey olarak, değere göre geçirilir ve String ilkel olmayan bir tür olduğundan, bu değer yeni ile oluşturulan şeye bir referanstır ve String.intern () kullanarak bunu kontrol edebilirsiniz. .
zakmck

1
Bunun yerine, param = "another string" (yeni String ("another string") ile eşdeğer) bir dizeyi değiştiremezsiniz çünkü param'ın referans değeri (şimdi "another string" i gösterir), yöntemin vücut. Ancak bu, diğer herhangi bir nesne için doğrudur, fark, sınıf arabirimi buna izin verdiğinde, param.changeMe () yapabilir ve yönlendirilen üst düzey nesne değişecektir, çünkü param'ın referans değerinin kendisine rağmen param ona işaret ediyor (C cinsinden adreslenen konum) yöntemden geri çıkamaz.
zakmck

39

Ayrım, ya da belki de orijinal posterle aynı izlenim altında olduğum gibi hatırladığım yol şudur: Java her zaman değere göre geçer. Java'daki tüm nesneler (Java'da, ilkel öğeler hariç her şey) referanstır. Bu referanslar değere göre iletilir.


2
İkinci-son cümlenizi çok yanıltıcı buluyorum. "Java'daki tüm nesnelerin referans olduğu" doğru değildir. Sadece referans olan nesnelerin referanslarıdır.
Dawood ibn Kareem

37

Daha önce birçok kişinin belirttiği gibi, Java her zaman değerlidir

Farkı anlamanıza yardımcı olacak başka bir örnek ( klasik takas örneği ):

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

Baskılar:

Önce: a = 2, b = 3
Sonra: a = 2, b = 3

Bunun nedeni, iA ve iB'nin iletilen referanslarla aynı değere sahip yeni yerel referans değişkenleridir (sırasıyla a ve b'yi gösterir). Bu nedenle, iA veya iB referanslarını değiştirmeye çalışmak, bu yöntemin dışında değil, yalnızca yerel kapsamda değişecektir.


32

Java sadece değere göre geçer. Bunu doğrulamak için çok basit bir örnek.

public void test() {
    MyClass obj = null;
    init(obj);
    //After calling init method, obj still points to null
    //this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
    objVar = new MyClass();
}

4
Bu, Java'nın değere göre geçtiğini görmenin en açık ve basit yoludur. obj( null) Değeri initbir referans değil, olarak geçirildi obj.
David Schwartz

31

Ben her zaman "kopya ile geçmek" olarak düşünüyorum. İlkel veya referans olsun, değerin bir kopyasıdır. Bir ilkel ise, değer olan bitlerin bir kopyasıdır ve bir Nesne ise, referansın bir kopyasıdır.

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

Java PassByCopy çıktısı:

name = Maxx
name = Fido

İlkel sargı sınıfları ve Dizeler değiştirilemez, bu nedenle bu türleri kullanan herhangi bir örnek diğer türlerle / nesnelerle aynı şekilde çalışmaz.


5
"by by pass", "by-pass-value" anlamına gelir .
TJ Crowder

@TJCrowder. "Kopyala geç", Java için aynı kavramı ifade etmenin daha uygun bir yolu olabilir: çünkü anlamsal olarak, Java'da olmasını istediğiniz referansla geçmeye benzer bir fikirde ayakkabı çekmeyi çok zorlaştırır.
mike kemirgen

@mikerodent - Üzgünüm, bunu takip etmedim. :-) "Değere göre aktar" ve "referansla aktar" bu kavramlar için doğru teknik terimlerdir. Bunları yenileri oluşturmak yerine (insanların ihtiyaç duyduklarında tanımlarını sağlayarak) kullanmalıyız. Yukarıda "kopya ile geç" dedim, "by by value" ne demek, ama aslında "kopya ile geç" in ne anlama geldiği belli değil - neyin kopyası? Değer? (Örn. Nesne başvurusu.) Nesnenin kendisi? Bu yüzden sanat şartlarına bağlı kalıyorum. :-)
TJ Crowder

28

Diğer bazı dillerden farklı olarak, Java, değere göre ve referansla geçiş arasında seçim yapmanıza izin vermez; tüm bağımsız değişkenler değere göre geçirilir. Bir yöntem çağrısı, bir yönteme iki tür değer iletebilir: ilkel değerlerin kopyaları (örn. İnt ve double değerleri) ve nesnelere başvuruların kopyaları.

Bir yöntem, ilkel tipte bir parametreyi değiştirdiğinde, parametrede yapılan değişikliklerin, çağıran yöntemdeki orijinal bağımsız değişken değeri üzerinde hiçbir etkisi yoktur.

Nesneler söz konusu olduğunda, nesnelerin kendileri yöntemlere aktarılamaz. Böylece nesnenin referansını (adresini) iletiyoruz. Bu referansı kullanarak orijinal nesneyi değiştirebiliriz.

Java nesneleri nasıl oluşturur ve depolar: Bir nesne oluşturduğumuzda, nesnenin adresini bir referans değişkeninde saklarız. Aşağıdaki ifadeyi analiz edelim.

Account account1 = new Account();

“Account account1” referans değişkeninin türü ve adı, “=” atama operatörüdür, “new” sistemden gerekli alan miktarını ister. Nesneyi oluşturan new anahtar sözcüğünün sağındaki yapıcı, new anahtar sözcüğü tarafından örtük olarak çağrılır. Oluşturulan nesnenin adresi ("sınıf örneği oluşturma ifadesi" olarak adlandırılan bir ifade olan sağ değerin sonucu), atama işleci kullanılarak sol değere (adı ve türü belirtilmiş bir başvuru değişkeni olan) atanır.

Bir nesnenin başvurusu değere göre iletilse de, bir yöntem yine de başvurulan nesneyle ortak başvuru yöntemini nesnenin başvurusunun kopyasını kullanarak çağırarak etkileşime girebilir. Parametrede saklanan başvuru, bağımsız değişken olarak iletilen başvurunun bir kopyası olduğundan, çağrılan yöntemdeki parametre ve çağıran yöntemdeki bağımsız değişken, bellekteki aynı nesneye başvurur.

Dizi nesnelerinin kendileri yerine dizilere başvurular iletmek, performans nedenleriyle mantıklıdır. Java'daki her şey değere göre geçtiğinden, dizi nesneleri geçirilirse, her öğenin bir kopyası geçirilir. Büyük diziler için bu, zaman kaybına neden olur ve öğelerin kopyaları için önemli miktarda depolama alanı kullanır.

Aşağıdaki görüntüde iki referans değişkenimiz var (bunlar C / C ++ 'da işaretçiler olarak adlandırılır ve bu terimin bu özelliği anlamayı kolaylaştırdığını düşünüyorum). İlkel ve referans değişkenleri yığın bellekte tutulur (aşağıdaki görüntülerde sol taraf). dizi1 ve dizi2 referans değişkenleri "nokta" (C / C ++ programcılarının dediği gibi) veya yığın hafızasındaki (aşağıdaki resimlerde sağ taraf) nesneler (bu referans değişkenlerinin tuttuğu değerler) sırasıyla a ve b dizilerine başvuru .

Değer örneği 1 ile geçiş

Array1 başvuru değişkeninin değerini reverseArray yöntemine bağımsız değişken olarak iletirsek, yöntemde bir başvuru değişkeni oluşturulur ve bu başvuru değişkeni aynı diziyi (a) işaret etmeye başlar.

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

Değer örneği 2'ye göre geçiş

Yani, eğer

array1[0] = 5;

reverseArray yönteminde, a dizisinde bir değişiklik yapar.

Bir dizi c'yi gösteren reverseArray yönteminde (dizi2) başka bir başvuru değişkenimiz var. Eğer söyleseydik

array1 = array2;

reverseArray yönteminde, reverseArray yöntemindeki dizi değişkeni başvuru dizisi a dizisine işaret etmeyi durdurur ve c dizisine (ikinci görüntüde noktalı çizgi) işaret etmeye başlar.

Dizi değişkeni değerini reverseArray yönteminin dönüş değeri olarak döndürürsek ve bu değeri ana yöntemde dizi1 başvuru değişkenine atarsak, dizi1 ana diziyi c dizisini göstermeye başlar.

Şimdi yaptığımız her şeyi hemen yazalım.

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

resim açıklamasını buraya girin

Ve şimdi reverseArray yöntemi bitti, referans değişkenleri (dizi1 ve dizi2) gitti. Bu, şimdi sadece ana yöntem dizi1 ve dizi2'de sırasıyla c ve b dizilerini gösteren iki referans değişkenine sahip olduğumuz anlamına gelir. Referans değişken hiçbir nesneyi (dizi) göstermiyor a. Bu nedenle çöp toplama için uygundur.

Ayrıca dizi1'e ana dizi2 dizisinin değerini atayabilirsiniz. dizi1 b'yi işaret etmeye başlar.


27

Burada herhangi bir programlama dili için bu tür sorulara ayrılmış bir iş parçacığı oluşturdum .

Java'dan da bahsedilmektedir . İşte kısa özet:

  • Java, parametreleri değere geçirir
  • java'da bir parametreye bir metoda geçmenin tek yolu "by by value"
  • parametre olarak verilen nesneden yöntemler kullanılması, referanslar orijinal nesnelere işaret ettiği için nesneyi değiştirir. (bu yöntemin kendisi bazı değerleri değiştirirse)

27

Uzun bir hikaye kısaca anlatmak gerekirse, Java nesneleri çok tuhaf özelliklere sahiptir.

Genel olarak, bir Java (ilkel türleri vardır int, bool, char, doubledeğeri doğrudan geçirilir, vb). Sonra Java nesneleri var (her şeyi türetir java.lang.Object). Nesneler aslında her zaman bir referansla (başvuru dokunamayacağınız bir işaretçi) ele alınır. Bu, referanslar normalde ilginç olmadığından, nesnelerin referans olarak geçirildiği anlamına gelir. Ancak bu, başvurunun kendisi değere göre iletilirken hangi nesnenin işaret edileceğini değiştiremeyeceğiniz anlamına gelir.

Kulağa garip ve kafa karıştırıcı geliyor mu? C uygulamalarının referans ile nasıl geçtiğini ve değere göre nasıl geçtiğini ele alalım. C de, varsayılan kural değerdir. void foo(int x)int değerini değere geçirir. void foo(int *x), int abir int istemeyen ancak int: işaretçisi olan bir işlevdir foo(&a). &Değişken bir adres iletmek için bunu operatörle birlikte kullanabilirsiniz .

Bunu C ++ 'a götürün ve referanslarımız var. Referanslar temel olarak (bu bağlamda) denklemin işaretçi kısmını gizleyen sözdizimsel şekerdir: void foo(int &x)denir foo(a), burada derleyici kendisi bir referans olduğunu bilir ve referans olmayanın adresi ailetilmelidir. Java'da, nesnelere atıfta bulunan tüm değişkenler aslında referans türündedir, aslında C ++ tarafından sağlanan ince taneli kontrol (ve karmaşıklık) olmaksızın çoğu amaç ve amaç için çağrıyı referans olarak zorlar.


Java nesnesine ve referansına çok yakın olduğunu düşünüyorum. Java'daki nesne bir referans kopyası (veya takma adı) tarafından bir yönteme iletilir.
MaxZoom
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.