Melewati sebuah String dengan Referensi di Jawa?

161

Saya terbiasa melakukan hal berikut dalam C:

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

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

Dan hasilnya adalah:

foo

Namun, di Jawa, ini sepertinya tidak berhasil. Saya berasumsi karena Stringobjek disalin alih-alih dilewatkan oleh referensi . Saya pikir String adalah objek, yang selalu dilewati dengan referensi.

Apa yang terjadi disini?

Soren Johnson
sumber
2
Bahkan jika mereka lewat referensi (di Jawa apa yang akan berlalu adalah salinan dari nilai referensi tapi itu thread lain) String objek ADALAH berubah, sehingga tidak akan bekerja pula
OscarRyz
8
Itu bukan kode C.
Alston
@ Phil: Ini tidak akan berfungsi di C # juga.
Mangesh

Jawaban:

196

Anda memiliki tiga opsi:

  1. Gunakan StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
  2. Buat kelas wadah dan berikan instance wadah ke metode Anda:

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
  3. Buat sebuah array:

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

Dari sudut pandang kinerja, StringBuilder biasanya merupakan pilihan terbaik.

Aaron Digulla
sumber
11
Perlu diingat bahwa StringBuilder tidak aman. Dalam lingkungan multithreaded gunakan kelas StringBuffer atau secara manual menjaga sinkronisasi.
Boris Pavlovic
12
@ Boris Pavlovic - ya, tapi kecuali StringBuilder yang sama digunakan oleh utas berbeda, yang IMO tidak mungkin dalam skenario apa pun, itu seharusnya tidak menjadi masalah. Tidak masalah jika metode ini dipanggil oleh utas yang berbeda, menerima StringBuilder yang berbeda.
Ravi Wallau
Saya suka opsi ketiga (array) paling karena itu adalah satu-satunya yang umum. Hal ini memungkinkan juga lewat boolean, int, dll. Bisakah Anda jelaskan sedikit lebih rinci kinerja solusi ini - Anda mengklaim bahwa yang pertama lebih baik dari sudut pandang kinerja.
meolic
@ folic StringBuilderlebih cepat s += "..."karena tidak mengalokasikan objek String baru setiap saat. Bahkan ketika Anda menulis kode Java seperti s = "Hello " + name, maka kompiler akan menghasilkan kode byte yang membuat StringBuilderdan memanggil append()dua kali.
Aaron Digulla
86

Di Jawa tidak ada yang dilewatkan dengan referensi . Semuanya dilewatkan dengan nilai . Referensi objek dilewatkan oleh nilai. Selain itu, String tidak dapat diubah . Jadi ketika Anda menambahkan ke String yang diteruskan Anda hanya mendapatkan String baru. Anda bisa menggunakan nilai balik, atau meneruskan StringBuffer sebagai gantinya.

zedoo
sumber
2
Ini bukan kesalahpahaman itu konsep
Patrick Cornelissen
41
Ummm..tidak, itu adalah kesalahpahaman bahwa Anda melewati objek dengan referensi. Anda melewati referensi berdasarkan nilai.
Ed S.
30
Ya, itu kesalahpahaman. Ini adalah kesalahpahaman besar yang tersebar luas. Ini mengarah ke pertanyaan wawancara yang saya benci: ("bagaimana Java menyampaikan argumen"). Aku benci itu karena sekitar setengah dari pewawancara sebenarnya ingin jawaban yang salah ("primitif berdasarkan nilai, objek dengan referensi"). Jawaban yang tepat membutuhkan waktu lebih lama, dan tampaknya membingungkan sebagian dari mereka. Dan mereka tidak akan diyakinkan: Aku bersumpah aku gagal layar teknologi karena screener tipe CSMajor telah mendengar kesalahpahaman di perguruan tinggi dan percaya itu sebagai Injil. Feh.
CPerkins
4
@CPerkins Anda tidak tahu seberapa marah itu membuat saya. Itu membuat darah saya mendidih.
6
Semuanya dilewatkan dengan nilai dalam semua bahasa. Beberapa dari nilai itu adalah alamat (referensi).
gerardw
52

Apa yang terjadi adalah bahwa referensi dilewatkan oleh nilai, yaitu, salinan referensi dilewatkan. Tidak ada dalam java yang dilewatkan oleh referensi, dan karena sebuah string tidak dapat diubah, tugas itu menciptakan objek string baru yang ditunjuk oleh salinan referensi tersebut . Referensi asli masih menunjuk ke string kosong.

Ini akan sama untuk objek apa pun, yaitu, menetapkannya ke nilai baru dalam suatu metode. Contoh di bawah ini hanya membuat apa yang sedang terjadi menjadi lebih jelas, tetapi menggabungkan string adalah hal yang sama.

void foo( object o )
{
    o = new Object( );  // original reference still points to old value on the heap
}
Ed S.
sumber
6
Contoh bagus!
Nickolas
18

java.lang.String tidak dapat diubah.

Saya benci menempel URL tetapi https://docs.oracle.com/javase/10/docs/api/java/lang/String.html sangat penting bagi Anda untuk membaca dan memahami jika Anda berada di java-land.

Ryan Fernandes
sumber
Saya tidak berpikir Soren Johnson tidak tahu apa itu String, itu bukan pertanyaan dan bukan apa yang saya cari di sini setelah 15 tahun di Jawa.
AHH
8

objek dilewatkan dengan referensi, primitif dilewatkan oleh nilai.

String bukan primitif, itu adalah objek, dan itu adalah kasus khusus objek.

Ini untuk tujuan penghematan memori. Di JVM, ada kumpulan string. Untuk setiap string yang dibuat, JVM akan mencoba untuk melihat apakah string yang sama ada di kumpulan string, dan arahkan ke sana jika sudah ada.

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));
    }
}

/ * OUTPUT * /

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

== memeriksa alamat memori (referensi), dan .equals memeriksa isi (nilai)

petugas kebersihan
sumber
3
Tidak sepenuhnya benar. Java selalu dilewati oleh VALUE; Triknya adalah bahwa Objek dilewatkan oleh referensi yang dilewatkan oleh nilai. (rumit, saya tahu). Lihat ini: javadude.com/articles/passbyvalue.htm
adhg
1
@ adhg Anda menulis "Objek dilewatkan dengan referensi yang dilewatkan oleh nilai." <--- itu omong kosong. Maksud Anda objek memiliki referensi yang diteruskan oleh nilai
barlop
2
-1 Anda menulis "objek dilewatkan dengan referensi," <- FALSE. Jika itu benar maka ketika memanggil metode dan melewati variabel, metode tersebut dapat membuat titik variabel / merujuk ke objek yang berbeda, tetapi tidak bisa. Semuanya dilewatkan oleh nilai dalam java. Dan Anda masih melihat efek mengubah atribut suatu objek, di luar metode.
barlop
lihat jawabannya di sini stackoverflow.com/questions/40480/…
barlop
7

Semua argumen di Jawa diteruskan oleh nilai. Saat Anda melewatkan Stringfungsi, nilai yang diteruskan adalah referensi ke objek String, tetapi Anda tidak bisa mengubah referensi itu, dan objek String yang mendasarinya tidak berubah.

Tugas

zText += foo;

setara dengan:

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

Artinya, itu (secara lokal) menetapkan kembali parameter zTextsebagai referensi baru, yang menunjuk ke lokasi memori baru, di mana ini adalah yang baru Stringyang berisi konten asli zTextdengan "foo"ditambahkan.

Objek asli tidak dimodifikasi, dan main()variabel lokal metode zTextmasih menunjuk ke string asli (kosong).

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);
  }
}

cetakan:

Original value:
Local value: foo
Final value:

Jika Anda ingin memodifikasi string, Anda dapat menggunakan StringBuilderatau mencatat beberapa wadah (array atau AtomicReferencekelas kontainer kustom) yang memberi Anda tingkat tipuan pointer tambahan. Atau, kembalikan nilai baru dan berikan:

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);
  }
}

cetakan:

Original value:
Local value: foo
Final value: foo

Ini mungkin solusi yang paling mirip-Java dalam kasus umum - lihat item Java yang Efektif "Fungsikan ketidakmampuan."

Seperti yang disebutkan, StringBuilderakan sering memberi Anda kinerja yang lebih baik - jika Anda memiliki banyak penambahan yang harus dilakukan, khususnya di dalam satu loop, gunakan StringBuilder.

Tetapi cobalah untuk melewati kekekalan Stringsalih-alih bisa berubah StringBuildersjika Anda bisa - kode Anda akan lebih mudah dibaca dan lebih bisa dipelihara. Pertimbangkan membuat parameter Anda final, dan mengkonfigurasi IDE Anda untuk memperingatkan Anda ketika Anda menetapkan kembali parameter metode ke nilai baru.

David Moles
sumber
6
Anda mengatakan "secara tegas" seolah-olah itu hampir tidak relevan - padahal itu merupakan inti masalahnya. Jika Java benar - benar telah melewati referensi, itu tidak akan menjadi masalah. Silakan coba hindari menyebarkan mitos "Java melewati objek dengan referensi" - menjelaskan (benar) "referensi diteruskan oleh nilai" model jauh lebih bermanfaat, IMO.
Jon Skeet
Hei, kemana perginya komentar saya sebelumnya? :-) Seperti yang saya katakan sebelumnya, dan seperti yang telah ditambahkan oleh Jon, masalah sebenarnya di sini adalah bahwa referensi dilewatkan oleh nilai. Itu sangat penting dan banyak orang tidak mengerti perbedaan semantik.
Ed S.
1
Cukup adil. Sebagai seorang programmer Java saya belum melihat sesuatu yang benar - benar lulus dengan referensi lebih dari satu dekade, jadi bahasa saya menjadi ceroboh. Tapi Anda benar, saya seharusnya lebih berhati-hati, terutama untuk audiens pemrograman C.
David Moles
5

String adalah objek abadi di Jawa. Anda bisa menggunakan kelas StringBuilder untuk melakukan pekerjaan yang ingin Anda capai, sebagai berikut:

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");
}

Pilihan lain adalah membuat kelas dengan String sebagai variabel lingkup (sangat tidak disarankan) sebagai berikut:

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";
}
Fadi Hanna AL-Kass
sumber
1
String bukan tipe primitif di Jawa.
VWeber
Benar, tetapi sebenarnya apa yang Anda coba tarik perhatian saya?
Fadi Hanna AL-Kass
Sejak kapan Java String adalah primitif ?! BEGITU! String dilewatkan dengan referensi.
thedp
2
'objek abadi' adalah apa yang ingin saya katakan. Saya, untuk beberapa alasan, tidak membaca kembali jawaban saya setelah @Weber membuat komentar, tetapi sekarang saya melihat apa yang dia coba katakan. salahku. jawaban diubah
Fadi Hanna AL-Kass
2

Jawabannya sederhana. Dalam string java tidak berubah. Maka itu seperti menggunakan 'final' modifier (atau 'const' di C / C ++). Jadi, setelah ditugaskan, Anda tidak dapat mengubahnya seperti yang Anda lakukan.

Anda dapat mengubah yang nilai yang string poin, tetapi Anda tidak dapat mengubah nilai aktual yang string ini saat ini menunjuk.

Yaitu. String s1 = "hey". Anda dapat membuat s1 = "woah", dan itu sama sekali tidak apa-apa, tetapi Anda tidak dapat benar-benar mengubah nilai yang mendasari string (dalam hal ini: "hei") menjadi sesuatu yang lain setelah ditugaskan menggunakan plusEquals, dll. (Mis. s1 += " whatup != "hey whatup").

Untuk melakukan itu, gunakan kelas StringBuilder atau StringBuffer atau wadah yang bisa berubah, lalu panggil saja .toString () untuk mengubah objek kembali menjadi string.

Catatan: String sering digunakan sebagai kunci hash karena itu bagian dari alasan mengapa mereka tidak dapat diubah.

ak_2050
sumber
Apakah itu bisa berubah atau tidak berubah tidak relevan. =pada referensi PERNAH memengaruhi objek yang digunakan untuk menunjuk, terlepas dari kelas.
newacct
2

String adalah kelas khusus di Jawa. Ini adalah Thread Safe yang berarti "Setelah instance String dibuat, konten instance String tidak akan pernah berubah".

Inilah yang sedang terjadi

 zText += "foo";

Pertama, kompiler Java akan mendapatkan nilai instance String zText, kemudian membuat instance String baru yang nilainya zText menambahkan "foo". Jadi, Anda tahu mengapa instance yang zText tunjuk tidak berubah. Ini benar-benar contoh baru. Bahkan, bahkan String "foo" adalah turunan String baru. Jadi, untuk pernyataan ini, Java akan membuat dua instance String, satu adalah "foo", yang lain adalah nilai zText menambahkan "foo". Aturannya sederhana: Nilai instance String tidak akan pernah berubah.

Untuk metode fillString, Anda dapat menggunakan StringBuffer sebagai parameter, atau Anda dapat mengubahnya seperti ini:

String fillString(String zText) {
    return zText += "foo";
}
DeepNightTwo
sumber
... meskipun jika Anda adalah untuk mendefinisikan 'fillString' sebagai per kode ini, Anda benar-benar harus memberikan nama yang lebih tepat!
Stephen C
Iya. Metode ini harus dinamai "appendString" atau lebih.
DeepNightTwo
1

String tidak dapat diubah di Jawa.

Grzegorz Oledzki
sumber
1

Ini berfungsi menggunakan StringBuffer

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");
}
}

Bahkan lebih baik gunakan StringBuilder

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");
}
}
Mohammed Rafeeq
sumber
1

String tidak dapat diubah dalam java. Anda tidak dapat memodifikasi / mengubah, string literal / objek yang ada.

String s = "Hello"; s = s + "hai";

Di sini referensi sebelumnya diganti oleh referensi baru yang menunjuk ke nilai "HelloHi".

Namun, untuk menghadirkan mutabilitas, kami memiliki StringBuilder dan StringBuffer.

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

ini menambahkan nilai baru "Hai" ke referensi yang sama. //

gauravJ
sumber
0

Aaron Digulla memiliki jawaban terbaik sejauh ini. Variasi dari opsi keduanya adalah menggunakan kelas pembungkus atau wadah MutableObject dari commons lang library versi 3+:

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

Anda menyimpan deklarasi kelas kontainer. Kekurangannya adalah ketergantungan pada comm lun lib. Tetapi lib memiliki cukup banyak fungsi yang berguna dan hampir semua proyek besar yang saya kerjakan menggunakannya.

pengguna2606740
sumber
0

Untuk melewati objek (termasuk String) dengan referensi di java, Anda mungkin meneruskannya sebagai anggota adaptor di sekitarnya. Solusi dengan obat generik ada di sini:

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());
    }
}
Sam Ginrich
sumber
0

Untuk seseorang yang lebih penasaran

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");
    }
}

Sekarang dengan menjalankan kode di atas Anda dapat melihat bagaimana alamat hashcodesberubah dengan variabel String s. objek baru dialokasikan ke variabel s dalam fungsi changetoketika s diubah

Rajesh kumar
sumber