Truyền chuỗi bằng tham chiếu trong Java?


161

Tôi đã quen làm như sau trong C:

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

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

Và đầu ra là:

foo

Tuy nhiên, trong Java, điều này dường như không hoạt động. Tôi giả sử vì Stringđối tượng được sao chép thay vì thông qua tham chiếu . Tôi nghĩ rằng String là các đối tượng, luôn được thông qua tham chiếu.

Chuyện gì đang xảy ra ở đây?


2
Ngay cả khi chúng được truyền bằng tham chiếu (trong Java, thứ được truyền là bản sao của giá trị tham chiếu nhưng đó là một luồng khác) Các đối tượng chuỗi là bất biến, vì vậy dù sao nó cũng không hoạt động
OscarRyz

8
Đó không phải là mã C.
Alston

@Phil: Điều này cũng sẽ không hoạt động trong C #.
Mangesh

Câu trả lời:


196

Bạn có ba lựa chọn:

  1. Sử dụng StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
    
  2. Tạo một lớp container và truyền một thể hiện của container vào phương thức của bạn:

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
    
  3. Tạo một mảng:

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

Từ quan điểm hiệu suất, StringBuilder thường là lựa chọn tốt nhất.


11
Chỉ cần nhớ rằng StringBuilder không phải là chủ đề an toàn. Trong môi trường đa luồng, sử dụng lớp StringBuffer hoặc tự chăm sóc đồng bộ hóa.
Boris Pavlović

12
@Boris Pavlovic - có, nhưng trừ khi cùng một StringBuilder được sử dụng bởi các luồng khác nhau, điều mà IMO không thể xảy ra trong bất kỳ kịch bản cụ thể nào, thì đó không phải là vấn đề. Sẽ không có vấn đề gì nếu phương thức được gọi bởi các luồng khác nhau, nhận StringBuilder khác nhau.
Ravi Wallau

Tôi thích tùy chọn thứ ba (mảng) nhất vì nó là lựa chọn chung duy nhất. Nó cũng cho phép vượt qua boolean, int, v.v. Bạn có thể giải thích chi tiết hơn một chút về hiệu suất của giải pháp này - bạn cho rằng giải pháp đầu tiên tốt hơn từ quan điểm hiệu suất.
meolic

@meolic StringBuildernhanh hơn s += "..."vì nó không phân bổ các đối tượng String mới mỗi lần. Trong thực tế khi bạn viết mã Java như thế s = "Hello " + name, thì trình biên dịch sẽ tạo mã byte tạo ra một StringBuildervà gọi append()hai lần.
Aaron Digulla

86

Trong Java không có gì được thông qua tham chiếu . Mọi thứ đều được thông qua bởi giá trị . Tham chiếu đối tượng được thông qua bởi giá trị. Ngoài ra, Chuỗi là bất biến . Vì vậy, khi bạn nối vào Chuỗi đã qua, bạn chỉ cần lấy Chuỗi mới. Bạn có thể sử dụng giá trị trả về hoặc thay vào đó là StringBuffer.


2
Đây không phải là một quan niệm sai lầm, đó là một khái niệm
Patrick Cornelissen

41
Ummm..không, đó là một quan niệm sai lầm rằng bạn đang truyền một đối tượng bằng cách tham chiếu. Bạn đang chuyển một tham chiếu theo giá trị.
Ed S.

30
Vâng, đó là một quan niệm sai lầm. Đó là một quan niệm sai lầm lớn, phổ biến. Nó dẫn đến một câu hỏi phỏng vấn mà tôi ghét: ("Java vượt qua các đối số như thế nào"). Tôi ghét nó bởi vì khoảng một nửa số người phỏng vấn thực sự muốn trả lời sai ("nguyên thủy theo giá trị, đối tượng theo tham chiếu"). Câu trả lời đúng mất nhiều thời gian hơn để đưa ra, và dường như gây nhầm lẫn cho một số trong số họ. Và họ sẽ không bị thuyết phục: Tôi thề rằng tôi đã làm hỏng màn hình công nghệ bởi vì người sàng lọc kiểu CSMajor đã nghe quan niệm sai lầm ở trường đại học và tin rằng đó là tin lành. Feh.
CPerkins

4
@CPerkins Bạn không biết làm thế nào mà tức giận làm cho tôi. Nó làm sôi máu của tôi.

6
Tất cả mọi thứ được thông qua bởi giá trị trong tất cả các ngôn ngữ. Một số trong những giá trị đó là địa chỉ (tài liệu tham khảo).
gerardw

52

Điều đang xảy ra là tham chiếu được truyền theo giá trị, nghĩa là một bản sao của tham chiếu được truyền. Không có gì trong java được truyền qua tham chiếu và vì một chuỗi là bất biến, phép gán đó tạo ra một đối tượng chuỗi mới mà bản sao của tham chiếu bây giờ trỏ đến. Tham chiếu ban đầu vẫn trỏ đến chuỗi trống.

Điều này sẽ giống nhau cho bất kỳ đối tượng nào, tức là đặt nó thành một giá trị mới trong một phương thức. Ví dụ dưới đây chỉ làm cho những gì đang diễn ra rõ ràng hơn, nhưng nối một chuỗi thực sự là điều tương tự.

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

6
Ví dụ tuyệt vời!
Nickolas


8

đối tượng được truyền bằng tham chiếu, nguyên thủy được truyền bằng giá trị.

Chuỗi không phải là một nguyên thủy, nó là một đối tượng và nó là một trường hợp đặc biệt của đối tượng.

Đây là cho mục đích tiết kiệm bộ nhớ. Trong JVM, có một chuỗi chuỗi. Đối với mỗi chuỗi được tạo, JVM sẽ cố gắng xem liệu cùng một chuỗi có tồn tại trong nhóm chuỗi hay không và trỏ đến chuỗi đó nếu đã có một chuỗi.

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

/ * ĐẦU RA * /

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

== đang kiểm tra địa chỉ bộ nhớ (tham chiếu) và .equals đang kiểm tra nội dung (giá trị)


3
Không hoàn toàn đúng. Java luôn luôn vượt qua GIÁ TRỊ; mẹo là các đối tượng được truyền bằng tham chiếu được truyền theo giá trị. (khó khăn, tôi biết). Kiểm tra cái này: javadude.com/articles/passbyvalue.htmlm
adhg

1
@adhg bạn đã viết "Các đối tượng được truyền bằng tham chiếu được truyền theo giá trị." <--- đó là vô nghĩa. Ý bạn là các đối tượng có các tham chiếu được truyền theo giá trị
barlop

2
-1 Bạn đã viết "các đối tượng được truyền bằng tham chiếu", <- FALSE. Nếu điều đó là đúng thì khi gọi một phương thức và truyền một biến, phương thức đó có thể làm cho điểm biến / tham chiếu đến một đối tượng khác, nhưng không thể. Tất cả mọi thứ được thông qua bởi giá trị trong java. Và bạn vẫn thấy hiệu ứng thay đổi các thuộc tính của một đối tượng, bên ngoài phương thức.
barlop

xem câu trả lời tại đây stackoverflow.com/questions/40480/ Lời
barlop

7

Tất cả các đối số trong Java được truyền theo giá trị. Khi bạn truyền một hàm Stringcho một giá trị, giá trị được truyền tham chiếu đến đối tượng Chuỗi, nhưng bạn không thể sửa đổi tham chiếu đó và đối tượng Chuỗi bên dưới là bất biến.

Nhiệm vụ

zText += foo;

tương đương với:

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

Đó là, nó (cục bộ) gán lại tham số zTextlà tham chiếu mới, trỏ đến một vị trí bộ nhớ mới, trong đó là một vị trí mới Stringchứa nội dung gốc zText"foo"gắn thêm.

Đối tượng ban đầu không được sửa đổi và main()biến cục bộ của phương thức zTextvẫn trỏ đến chuỗi gốc (trống).

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

in:

Original value:
Local value: foo
Final value:

Nếu bạn muốn sửa đổi chuỗi, bạn có thể sử dụng ghi chú StringBuilderhoặc một số vùng chứa khác (một mảng hoặc một AtomicReferencelớp chứa tùy chỉnh) cung cấp cho bạn một mức bổ sung con trỏ bổ sung. Ngoài ra, chỉ cần trả về giá trị mới và gán nó:

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

in:

Original value:
Local value: foo
Final value: foo

Đây có lẽ là giải pháp giống Java nhất trong trường hợp chung - xem mục Java hiệu quả "Tính bất biến ưu tiên".

Tuy nhiên, như đã lưu ý, StringBuilderthường sẽ cung cấp cho bạn hiệu suất tốt hơn - nếu bạn có nhiều việc phải làm, đặc biệt là trong vòng lặp, hãy sử dụng StringBuilder.

Nhưng hãy cố gắng vượt qua bất biến thay Stringsvì có thể thay đổi StringBuildersnếu bạn có thể - mã của bạn sẽ dễ đọc hơn và dễ bảo trì hơn. Xem xét việc tạo các tham số của bạn finalvà định cấu hình IDE của bạn để cảnh báo bạn khi bạn gán lại một tham số phương thức cho một giá trị mới.


6
Bạn nói "nói đúng" như thể nó gần như không liên quan - trong khi nó nằm ở trung tâm của vấn đề. Nếu Java thực sự có thông qua tham chiếu, nó sẽ không thành vấn đề. Hãy cố gắng tránh truyền bá huyền thoại về "Java truyền các đối tượng bằng tham chiếu" - giải thích mô hình "chính xác" được truyền theo giá trị "hữu ích hơn rất nhiều, IMO.
Jon Skeet

Này, bình luận trước đây của tôi đã đi đâu? :-) Như tôi đã nói trước đây và như Jon đã thêm, vấn đề thực sự ở đây là tham chiếu được truyền theo giá trị. Điều đó rất quan trọng và nhiều người không hiểu sự khác biệt về ngữ nghĩa.
Ed S.

1
Đủ công bằng. Là một lập trình viên Java, tôi chưa thấy bất cứ điều gì thực sự được thông qua tham chiếu trong hơn một thập kỷ, vì vậy ngôn ngữ của tôi đã trở nên cẩu thả. Nhưng bạn nói đúng, tôi nên quan tâm nhiều hơn, đặc biệt là đối với khán giả lập trình C.
David Moles

5

Chuỗi là một đối tượng bất biến trong Java. Bạn có thể sử dụng lớp StringBuilder để thực hiện công việc bạn đang cố gắng thực hiện, như sau:

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

Một tùy chọn khác là tạo một lớp với Chuỗi là biến phạm vi (không được khuyến khích) như sau:

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
Chuỗi không phải là một kiểu nguyên thủy trong Java.
VWeber

Đúng, nhưng chính xác thì bạn đang cố lôi kéo sự chú ý của tôi vào điều gì?
Fadi Hanna AL-Kass

Kể từ khi Chuỗi Java là nguyên thủy?! VÌ THẾ! Chuỗi thông qua tham chiếu.
thedp

2
"Đối tượng bất biến" là những gì tôi dự định nói. Tôi, vì một số lý do, đã không đọc lại câu trả lời của tôi sau khi @VWeber đưa ra nhận xét, nhưng bây giờ tôi thấy những gì anh ta đang cố nói. lỗi của tôi. câu trả lời được sửa đổi
Fadi Hanna AL-Kass

2

Đáp án đơn giản. Trong chuỗi java là bất biến. Do đó, nó giống như sử dụng công cụ sửa đổi 'cuối cùng' (hoặc 'const' trong C / C ++). Vì vậy, một khi được chỉ định, bạn không thể thay đổi nó như cách bạn đã làm.

Bạn có thể thay đổi giá trị mà một điểm chuỗi, nhưng bạn không thể thay đổi giá trị thực tế mà chuỗi này hiện đang trỏ đến.

I E. String s1 = "hey". Bạn có thể thực hiện s1 = "woah"và điều đó hoàn toàn ổn, nhưng thực tế bạn không thể thay đổi giá trị cơ bản của chuỗi (trong trường hợp này: "hey") thành một thứ khác sau khi được gán bằng plusEquals, v.v. (vd. s1 += " whatup != "hey whatup").

Để làm điều đó, hãy sử dụng các lớp StringBuilder hoặc StringBuffer hoặc các thùng chứa có thể thay đổi khác, sau đó chỉ cần gọi .toString () để chuyển đổi đối tượng trở lại thành một chuỗi.

lưu ý: Chuỗi thường được sử dụng làm khóa băm do đó là một phần lý do tại sao chúng không thay đổi.


Cho dù đó là đột biến hay bất biến không liên quan. =trên một tham chiếu KHÔNG BAO GIỜ ảnh hưởng đến đối tượng mà nó sử dụng để trỏ tới, bất kể lớp nào.
newacct

2

Chuỗi là một lớp đặc biệt trong Java. Đó là Thread Safe có nghĩa là "Một khi một thể hiện Chuỗi được tạo, nội dung của thể hiện Chuỗi sẽ không bao giờ thay đổi".

Đây là những gì đang xảy ra cho

 zText += "foo";

Đầu tiên, trình biên dịch Java sẽ lấy giá trị của cá thể Chuỗi zText, sau đó tạo một cá thể Chuỗi mới có giá trị là zText nối thêm "foo". Vì vậy, bạn biết tại sao ví dụ mà zText trỏ đến không thay đổi. Nó hoàn toàn là một ví dụ mới. Trong thực tế, ngay cả Chuỗi "foo" là một thể hiện Chuỗi mới. Vì vậy, đối với câu lệnh này, Java sẽ tạo hai thể hiện String, một là "foo", một cái khác là giá trị của zText nối thêm "foo". Quy tắc rất đơn giản: Giá trị của thể hiện String sẽ không bao giờ thay đổi.

Đối với phương thức fillString, bạn có thể sử dụng StringBuffer làm tham số hoặc bạn có thể thay đổi nó như sau:

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

... mặc dù nếu bạn định nghĩa 'fillString' theo mã này, bạn thực sự nên đặt cho nó một cái tên phù hợp hơn!
Stephen C

Đúng. Phương pháp này nên được đặt tên là "appendString" hoặc hơn thế.
DeepNightTwo


1

Công việc này sử dụng 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");
}
}

Thậm chí sử dụng StringBuilder tốt hơ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

Chuỗi là bất biến trong java. bạn không thể sửa đổi / thay đổi, một chuỗi ký tự / đối tượng hiện có.

Chuỗi s = "Xin chào"; s = s + "hi";

Ở đây, các tham chiếu trước được thay thế bằng các tham chiếu mới chỉ đến giá trị "HelloHi".

Tuy nhiên, để mang lại khả năng biến đổi, chúng ta có StringBuilder và StringBuffer.

StringBuilder s = new StringBuilder (); s.append ("Chào");

cái này nối thêm giá trị mới "Hi" vào cùng một giới thiệu s. //


0

Aaron Digulla có câu trả lời tốt nhất cho đến nay. Một biến thể của tùy chọn thứ hai của anh ta là sử dụng lớp bao bọc hoặc lớp chứa MutableObject của thư viện commons lang phiên bản 3+:

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

bạn lưu khai báo của lớp container. Hạn chế là một phụ thuộc vào commons lang lib. Nhưng lib có khá nhiều chức năng hữu ích và hầu như bất kỳ dự án lớn nào tôi đã làm việc đều sử dụng nó.


0

Để truyền một đối tượng (bao gồm Chuỗi) bằng tham chiếu trong java, bạn có thể chuyển nó làm thành viên của bộ điều hợp xung quanh. Một giải pháp với chung chung là ở đây:

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

Đối với một người tò mò hơn

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

Bây giờ bằng cách chạy mã ở trên, bạn có thể thấy cách hashcodesthay đổi địa chỉ với biến String s. một đối tượng mới được phân bổ cho biến s trong hàm changetokhi s bị thay đổi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.