Java ile Başvuru yoluyla bir dize geçmek?


161

Aşağıdakileri yapmaya alışkınım C:

void main() {
    String zText = "";
    fillString(zText);
    printf(zText);
}

void fillString(String zText) {
    zText += "foo";
}

Ve çıktı:

foo

Ancak, Java'da bu işe yaramıyor gibi görünüyor. StringNesne başvurulan tarafından yerine kopyalandığı için varsayalım . Dizelerin her zaman referans olarak iletilen nesneler olduğunu düşündüm.

Burada neler oluyor?


2
Referansla
geçilse

8
C kodu değil.
Alston

@Phil: Bu C # 'da da çalışmayacak.
Mangesh

Yanıtlar:


196

Üç seçeneğiniz var:

  1. Bir StringBuilder kullanın:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
  2. Bir kapsayıcı sınıfı oluşturun ve kapsayıcı örneğini yönteminize iletin:

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
  3. Dizi oluşturun:

    new String[] zText = new String[1];
    zText[0] = "";
    
    void fillString(String[] zText) { zText[0] += "foo"; }

Performans açısından, StringBuilder genellikle en iyi seçenektir.


11
StringBuilder'ın iş parçacığı için güvenli olmadığını unutmayın. Çok iş parçacıklı ortamlarda StringBuffer sınıfını kullanın veya el ile eşitlemeye dikkat edin.
Boris Pavlović

12
@Boris Pavlovic - evet, ancak aynı StringBuilder IMO'nun herhangi bir senaryoda olası olmadığı farklı iş parçacıkları tarafından kullanılmadığı takdirde, sorun olmamalıdır. Yöntemin farklı StringBuilder alarak farklı iş parçacıkları tarafından çağrılması önemli değildir.
Ravi Wallau

Üçüncü seçenek (dizi) en çok hoşuma gidiyor çünkü tek genel olanı. Ayrıca boolean, int, vb. Geçişlerini de sağlar. Bu çözümün performansını biraz daha ayrıntılı olarak açıklayabilir misiniz? Birincisinin performans açısından daha iyi olduğunu iddia edersiniz.
meolic

@meolic StringBuilder, s += "..."her seferinde yeni String nesneleri ayırmadığından daha hızlıdır . Aslında gibi Java kodu yazdığınızda s = "Hello " + name, derleyici a oluşturur StringBuilderve append()iki kez çağıran bayt kodu oluşturur .
Aaron Digulla

86

Java'da hiçbir şey referansla geçilmez . Her şey değerden geçer . Nesne başvuruları değere göre iletilir. Ek olarak Dizeler değişmez . Böylece, geçirilen String'e eklediğinizde yeni bir String alırsınız. Bir dönüş değeri kullanabilir veya bunun yerine bir StringBuffer iletebilirsiniz.


2
Bu bir kavram değil
Patrick Cornelissen

41
Ummm..no, bir nesneyi referans olarak ilettiğiniz bir yanlış anlama. Bir referansı değere göre geçiriyorsunuz.
Ed S.

30
Evet, bu bir yanlış anlama. Bu büyük, yaygın bir yanlış anlama. Nefret ettiğim bir röportaj sorusuna yol açar: ("Java argümanları nasıl geçer"). Bundan nefret ediyorum, çünkü görüşmecilerin kabaca yarısı yanlış cevabı istiyor gibi görünüyor ("değere göre ilkeller, referans olarak nesneler"). Doğru cevabın verilmesi daha uzun sürüyor ve bazılarını karıştırıyor gibi görünüyor. Ve ikna olmayacaklar: Yemin ederim bir teknoloji ekranını fırlattım çünkü CSMajor tipi elek kolejdeki yanlış anlayışı duymuş ve müjde olduğuna inanmıştı. Feh.
CPerkins

4
@CPerkins Bunun beni ne kadar sinirlendirdiğine dair hiçbir fikrin yok. Kanımı kaynatır.

6
Her şey her dilde değere göre geçer. Bu değerlerden bazıları adresler (referanslar) olur.
gerardw

52

Olan şey, referansın değere göre geçirilmesidir, yani referansın bir kopyası geçirilir. Java'daki hiçbir şey başvuru ile iletilmez ve bir dize değiştirilemez olduğundan, bu atama başvurunun kopyasının şimdi işaret ettiği yeni bir dize nesnesi oluşturur . Orijinal başvuru yine de boş dizeyi gösterir.

Bu herhangi bir nesne için aynıdır, yani bir yöntemde yeni bir değere ayarlamak. Aşağıdaki örnek, olup biteni daha açık hale getiriyor, ancak bir dizeyi birleştirmek gerçekten aynı şey.

void foo( object o )
{
    o = new Object( );  // original reference still points to old value on the heap
}

6
Mükemmel örnek!
Nickolas


8

nesneler referansla, ilkeller değere göre aktarılır.

Dize bir ilkel değil, bir nesnedir ve özel bir nesne örneğidir.

Bu bellek tasarrufu amaçlıdır. JVM'de, bir dize havuzu vardır. Oluşturulan her dize için JVM, dize havuzunda aynı dizenin var olup olmadığını görmeye çalışır ve zaten varsa bir dizenin üzerine gelir.

public class TestString
{
    private static String a = "hello world";
    private static String b = "hello world";
    private static String c = "hello " + "world";
    private static String d = new String("hello world");

    private static Object o1 = new Object();
    private static Object o2 = new Object();

    public static void main(String[] args)
    {
        System.out.println("a==b:"+(a == b));
        System.out.println("a==c:"+(a == c));
        System.out.println("a==d:"+(a == d));
        System.out.println("a.equals(d):"+(a.equals(d)));
        System.out.println("o1==o2:"+(o1 == o2));

        passString(a);
        passString(d);
    }

    public static void passString(String s)
    {
        System.out.println("passString:"+(a == s));
    }
}

/* ÇIKTI */

a==b:true
a==c:true
a==d:false
a.equals(d):true
o1==o2:false
passString:true
passString:false

== bellek adresini (başvuru) ve .equals içeriği (değeri) denetliyor


3
Tamamen doğru değil. Java her zaman VALUE ile geçer; işin püf noktası, nesnelerin değere göre geçen başvuru ile iletilmesidir . (zor, biliyorum). Bunu kontrol et: javadude.com/articles/passbyvalue.htm
adhg

1
@adhg yazdı "Nesneler değere göre geçirilen başvuru ile iletilir." <--- bu anlamsız. Yani nesnelerin değere göre referansları var
barlop

2
-1 "Nesneler referans ile iletilir" yazdınız. <- YANLIŞ. Bu doğru olsaydı, bir yöntemi çağırırken ve bir değişkeni geçirirken, yöntem değişken noktasını / farklı bir nesneye başvuruda bulunabilir, ancak yapamaz. Java'da her şey değere göre geçer. Ve yine de, bir nesnenin niteliklerini, yöntemin dışında değiştirmenin etkisini görüyorsunuz.
barlop

cevabı buradan görebilirsiniz stackoverflow.com/questions/40480/…
barlop

7

Java'daki tüm argümanlar değere göre iletilir. Bir Stringişleve bir ilettiğinizde , iletilen değer bir String nesnesine başvuru olur, ancak bu başvuruyu değiştiremezsiniz ve temeldeki String nesnesi değiştirilemez.

Görev

zText += foo;

şuna eşittir:

zText = new String(zText + "foo");

Yani, (yerel olarak) parametreyi zTextyeni bir referans olarak yeniden atar , bu yeni bir bellek konumuna işaret eder, burada ekli ile Stringorijinal içeriğini içeren yeni bir bellektir .zText"foo"

Orijinal nesne değiştirilmez ve main()yöntemin yerel değişkeni zTextyine de orijinal (boş) dizeyi gösterir.

class StringFiller {
  static void fillString(String zText) {
    zText += "foo";
    System.out.println("Local value: " + zText);
  }

  public static void main(String[] args) {
    String zText = "";
    System.out.println("Original value: " + zText);
    fillString(zText);
    System.out.println("Final value: " + zText);
  }
}

baskılar:

Original value:
Local value: foo
Final value:

StringBuilderDizeyi değiştirmek istiyorsanız, belirtildiği gibi veya AtomicReferenceişaretçi dolaylaması ek düzeyi veren bir kap (dizi veya bir veya özel kap sınıfı) kullanabilirsiniz. Alternatif olarak, yeni değeri döndürüp atamanız yeterlidir:

class StringFiller2 {
  static String fillString(String zText) {
    zText += "foo";
    System.out.println("Local value: " + zText);
    return zText;
  }

  public static void main(String[] args) {
    String zText = "";
    System.out.println("Original value: " + zText);
    zText = fillString(zText);
    System.out.println("Final value: " + zText);
  }
}

baskılar:

Original value:
Local value: foo
Final value: foo

Bu muhtemelen genel durumdaki en Java benzeri çözümdür - Etkili Java öğesi "Değişmezliği destekle" konusuna bakın .

Belirtildiği gibi, StringBuildergenellikle size daha iyi performans verecektir - özellikle bir döngü içinde yapacak çok fazla ekiniz varsa kullanın StringBuilder.

Ancak, mümkünse değişmez Stringsyerine geçmeye çalışın StringBuilders- kodunuzun okunması daha kolay ve daha sürdürülebilir. Parametrelerinizi oluşturmayı finalve IDE'nizi, bir yöntem parametresini yeni bir değere yeniden atadığınızda sizi uyaracak şekilde yapılandırmayı düşünün .


6
"Kesinlikle konuşan" diyorsunuz, neredeyse ilgisizmiş gibi - buna karşın sorunun merkezinde yer alıyor. Java gerçekten referans olarak geçerse, sorun olmazdı. Lütfen "Java referans ile nesneleri geçer" mitini yaymaktan kaçınmaya çalışın - (doğru) "referanslar değerin geçtiğini açıklamak" modeli çok daha yararlı, IMO.
Jon Skeet

Hey, önceki yorumum nereye gitti? :-) Daha önce söylediğim gibi, Jon'un da eklediği gibi, burada asıl mesele referansın değere göre geçmesidir. Bu çok önemli ve birçok insan anlambilimsel farkı anlamıyor.
Ed S.

1
Yeterince adil. Java programcısı olarak ben bir şey görmedim aslında benim dilim özensiz almış böylece, on yıldan daha iyi referans olarak gönderilir. Ama haklısın, özellikle C-programlama izleyicileri için daha fazla özen göstermeliydim.
David Moles

5

String, Java'da değiştirilemez bir nesnedir. StringBuilder sınıfını, gerçekleştirmeye çalıştığınız işi aşağıdaki gibi yapmak için kullanabilirsiniz:

public static void main(String[] args)
{
    StringBuilder sb = new StringBuilder("hello, world!");
    System.out.println(sb);
    foo(sb);
    System.out.println(sb);

}

public static void foo(StringBuilder str)
{
    str.delete(0, str.length());
    str.append("String has been modified");
}

Başka bir seçenek de, aşağıdaki gibi, kapsam değişkeni olarak String (son derece cesaret kırılmış) olarak bir sınıf oluşturmaktır:

class MyString
{
    public String value;
}

public static void main(String[] args)
{
    MyString ms = new MyString();
    ms.value = "Hello, World!";

}

public static void foo(MyString str)
{
    str.value = "String has been modified";
}

1
Dize, Java'da ilkel bir tür değildir.
VWeber

Doğru, ama dikkatimi tam olarak neye sürüklemeye çalışıyorsun?
Fadi Hanna AL-Kass

Ne zamandan beri Java String ilkel ?! YANİ! Dizge referans ile geçti.
thedp

2
"değişmez nesne" demek istediğim şeydi. Nedense, @VWeber bir yorum yaptıktan sonra cevabımı tekrar okumadım, ama şimdi ne demeye çalıştığını görüyorum. benim hatam. cevap değiştirildi
Fadi Hanna AL-Kass

2

Cevap basit. Java dizelerinde değişmez. Bu nedenle 'son' değiştirici (veya C / C ++ 'da' const ') kullanmak gibidir. Böylece, bir kez atandığında, bunu yaptığınız gibi değiştiremezsiniz.

Bir dizenin işaret ettiği değeri değiştirebilirsiniz , ancak bu dizenin şu anda işaret ettiği gerçek değeri DEĞİŞTİRMEYİNİZ.

Yani. String s1 = "hey". Yapabilirsiniz s1 = "woah"ve bu tamamen tamam, ancak aslında plusEquals, vb. (Ör. s1 += " whatup != "hey whatup") Kullanılarak atandığında başka bir şey olarak dizenin temel değerini (bu durumda: "hey") değiştiremezsiniz .

Bunu yapmak için, StringBuilder veya StringBuffer sınıflarını veya diğer değiştirilebilir kapları kullanın, ardından nesneyi bir dizeye dönüştürmek için .toString () öğesini çağırmanız yeterlidir.

Not: Dizeler genellikle karma anahtarları olarak kullanılır, bu nedenle değişmez olmasının nedeninin bir parçasıdır.


Değişebilir ya da değişmez olup olmadığı önemli değildir. =bir referansta ASLA sınıftan bağımsız olarak işaret ettiği nesneyi etkilemez.
newacct

2

String, Java'da özel bir sınıftır. "Bir String örneği oluşturulduktan sonra, String örneğinin içeriği hiçbir zaman değişmeyecektir" anlamına gelen Thread Safe'tir.

İşte ne için

 zText += "foo";

İlk olarak, Java derleyicisi zText String örneğinin değerini alır, ardından değeri "foo" ekleyerek zText olan yeni bir String örneği oluşturur. Yani zText'in işaret ettiği örneğin neden değişmediğini biliyorsunuz. Tamamen yeni bir örnek. Aslında, String "foo" bile yeni bir String örneğidir. Yani, bu ifade için Java iki String örneği yaratacaktır, biri "foo", diğeri zText eki "foo" değeridir. Kural basittir: String örneğinin değeri hiçbir zaman değiştirilmez.

FillString yöntemi için, bir StringBuffer parametresi olarak kullanabilir veya bunu şu şekilde değiştirebilirsiniz:

String fillString(String zText) {
    return zText += "foo";
}

... eğer bu koda göre 'fillString' tanımlayacak olsaydınız, gerçekten daha uygun bir isim vermelisiniz!
Stephen C

Evet. Bu yöntem "appendString" olarak adlandırılmalıdır.
DeepNightTwo


1

Bu StringBuffer kullanın çalışır

public class test {
 public static void main(String[] args) {
 StringBuffer zText = new StringBuffer("");
    fillString(zText);
    System.out.println(zText.toString());
 }
  static void fillString(StringBuffer zText) {
    zText .append("foo");
}
}

StringBuilder'ı daha da iyi kullanın

public class test {
 public static void main(String[] args) {
 StringBuilder zText = new StringBuilder("");
    fillString(zText);
    System.out.println(zText.toString());
 }
  static void fillString(StringBuilder zText) {
    zText .append("foo");
}
}

1

Dize java'da değişmez. varolan bir dizgi değişmezi / nesnesini değiştiremez / değiştiremezsiniz.

Dize s = "Merhaba"; ş = s + "Merhaba";

Burada önceki referanslar, "HelloHi" değerini işaret eden yeni referansla değiştirilir.

Ancak, değişkenliği getirmek için StringBuilder ve StringBuffer'a sahibiz.

StringBuilder s = new StringBuilder (); s.append ( "Merhaba");

bu, aynı referansa yeni "Hi" değerini ekler. //


0

Aaron Digulla şu ana kadarki en iyi cevaba sahip. İkinci seçeneğinin bir varyasyonu, commons lang kütüphanesi sürüm 3+'nin sarıcı veya kap sınıfı MutableObject'i kullanmaktır:

void fillString(MutableObject<String> c) { c.setValue(c.getValue() + "foo"); }

container sınıfının bildirimini kaydedersiniz. Dezavantajı commons lang lib bir bağımlılıktır. Ancak lib oldukça kullanışlı bir işleve sahiptir ve üzerinde çalıştığım hemen hemen her büyük proje onu kullanmıştır.


0

Java'da referans olarak bir nesneyi (String dahil) geçirmek için, onu çevreleyen bir bağdaştırıcının üyesi olarak iletebilirsiniz. Genel olan bir çözüm burada:

import java.io.Serializable;

public class ByRef<T extends Object> implements Serializable
{
    private static final long serialVersionUID = 6310102145974374589L;

    T v;

    public ByRef(T v)
    {
        this.v = v;
    }

    public ByRef()
    {
        v = null;
    }

    public void set(T nv)
    {
        v = nv;
    }

    public T get()
    {
        return v;
    }

// ------------------------------------------------------------------

    static void fillString(ByRef<String> zText)
    {
        zText.set(zText.get() + "foo");
    }

    public static void main(String args[])
    {
        final ByRef<String> zText = new ByRef<String>(new String(""));
        fillString(zText);
        System.out.println(zText.get());
    }
}

0

Daha meraklı biri için

class Testt {
    static void Display(String s , String varname){
        System.out.println(varname + " variable data = "+ s + " :: address hashmap =  " + s.hashCode());
    }

    static void changeto(String s , String t){
        System.out.println("entered function");
        Display(s , "s");
        s = t ;
        Display(s,"s");
        System.out.println("exiting function");
    }

    public static void main(String args[]){
        String s =  "hi" ;
        Display(s,"s");
        changeto(s,"bye");
        Display(s,"s");
    }
}

Şimdi yukarıdaki kodu çalıştırarak hashcodesString değişkeni s ile adres değişikliğini görebilirsiniz . changetos değiştiğinde işlev s değişkenine yeni bir nesne tahsis edilir

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.