Передавання рядка за посиланням на Java?


161

Я звик робити наступне C:

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

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

А вихід:

foo

Однак у Java це, здається, не працює. Я припускаю, тому що Stringоб'єкт копіюється замість переданого посилання . Я думав, що Струни - це об'єкти, які завжди передаються шляхом посилання.

Що тут відбувається?


2
Навіть якщо вони були передані за посиланням (на Java те, що передається - це копія еталонного значення, але це вже інша нитка). Струнні об'єкти є непорушними, так що це не спрацювало б
OscarRyz

8
Це не код C.
Елстон

@Phil: Це також не буде працювати в C #.
Мангеш

Відповіді:


196

У вас є три варіанти:

  1. Використовуйте StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
  2. Створіть клас контейнера та передайте примірник контейнера своєму методу:

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
  3. Створіть масив:

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

З точки зору продуктивності, StringBuilder зазвичай є найкращим варіантом.


11
Просто майте на увазі, що StringBuilder не є безпечним для потоків. У багатопотокових середовищах використовуйте клас StringBuffer або вручну подбайте про синхронізацію.
Борис Павлович

12
@Boris Pavlovic - так, але якщо один і той же StringBuilder не використовується різними потоками, що IMO навряд чи в будь-якому конкретному сценарії, це не повинно бути проблемою. Не має значення, чи метод викликається різними потоками, отримуючи різні StringBuilder.
Раві Валлау

Третій варіант (масив) мені найбільше подобається, тому що він єдиний загальний. Це дозволяє також передавати булеві, int тощо. Чи можете ви, будь ласка, пояснити трохи детальніше ефективність цього рішення - ви стверджуєте, що перший з точки зору продуктивності кращий.
меолічний

@meolic StringBuilderшвидше, s += "..."оскільки не виділяє нові об'єкти String кожного разу. Насправді, коли ви пишете Java-код, як s = "Hello " + name, тоді компілятор генерує байтовий код, який створює StringBuilderі викликає append()два рази.
Аарон Дігулла

86

На Java нічого не передається посиланням . Все передається за значенням . Посилання на об'єкти передаються за значенням. Крім того, струни незмінні . Отже, додаючи до переданої рядка, ви просто отримуєте нову рядок. Ви можете використовувати повернене значення або передавати StringBuffer замість цього.


2
Це не оману, це концепція
Патрік Корнеліссен

41
Уммм..но, це неправильне уявлення про те, що ви передаєте об'єкт за посиланням. Ви передаєте посилання за значенням.
Ред С.

30
Так, це неправильне уявлення. Це величезна, поширена помилка. Це призводить до питання інтерв'ю, яке я ненавиджу: ("як Java передає аргументи"). Я ненавиджу це тому, що приблизно половина інтерв'юерів насправді хочуть неправильної відповіді ("примітиви за значенням, об'єкти за посиланням"). Правильна відповідь потребує більше часу, і, здається, бентежить деякі з них. І вони не будуть переконані: я клянусь, що я заглянув на екрани технологій, оскільки провідниця типу CSMajor почула помилкове уявлення в коледжі і вважала це євангелією. Feh.
CPerkins

4
@CPerkins Ви поняття не маєте, наскільки я злий. Це змушує мою кров закипіти.

6
Все передається за значенням на всіх мовах. Деякі з цих значень трапляються як адреси (посилання).
gerardw

52

Що відбувається, це те, що посилання передається за значенням, тобто передається копія посилання. Ніщо в Java не передається посиланням, і оскільки рядок є незмінним, це призначення створює новий об'єкт рядка, на який зараз вказує копія посилання . Початкова посилання все ще вказує на порожній рядок.

Це було б однаково для будь-якого об'єкта, тобто встановлення нового значення в методі. Наведений приклад просто робить те, що відбувається, більш очевидним, але об'єднання рядків - це дійсно те саме.

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

6
Відмінний приклад!
Nickolas

18

java.lang.String незмінна.

Я ненавиджу вставляти URL-адреси, але https://docs.oracle.com/javase/10/docs/api/java/lang/String.html має важливе значення для того, щоб ви могли прочитати та зрозуміти, чи перебуваєте ви в Java-Land.


Я не думаю, що Сорен Джонсон не знає, що таке струна, це не питання, а не те, що я прийшов шукати сюди після 15 років Java.
AHH

8

об'єкти передаються за посиланням, примітиви передаються за значенням.

Рядок не є примітивом, це об'єкт, і це особливий випадок об'єкта.

Це для збереження пам'яті. У JVM є пуловий рядок. Для кожного створеного рядка JVM спробує перевірити, чи існує одна і та ж рядок у пулі рядків, і вкаже на неї, якщо вона вже була.

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

/ * ВИХІД * /

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

the == перевіряє адресу пам'яті (посилання), а .equals перевіряє вміст (значення)


3
Не зовсім правда. Java завжди проходить повний VALUE; хитрість полягає в тому, що Об'єкти передаються за посиланням, які передаються за значенням. (хитро, я знаю). Перевірте це: javadude.com/articles/passbyvalue.htm
adhg

1
@adhg Ви писали "Об'єкти передаються за посиланням, які є пропусками за значенням". <--- це гріш. Ви маєте на увазі, що об’єкти мають посилання, які передаються за значенням
barlop

2
-1 Ви написали: "Об'єкти передаються за посиланням" <- ЛЖ. Якщо це було правдою, тоді при виклику методу та передачі змінної, метод міг би зробити змінну точку / посилатися на інший об'єкт, але він не може. У яві все передається за значенням. І ви все ще бачите ефект зміни атрибутів об’єкта поза методом.
барлоп

см тут відповідь stackoverflow.com/questions/40480 / ...
barlop

7

Усі аргументи на Java передаються за значенням. Коли ви Stringпередаєте функцію a, передане значення є посиланням на об'єкт String, але ви не можете змінити це посилання, і основний об'єкт String є незмінним.

Завдання

zText += foo;

еквівалентно:

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

Тобто, він (локально) переназначає параметр zTextяк нове посилання, яке вказує на нове місце пам'яті, в якому є нове, Stringщо містить оригінальний вміст zTextз "foo"доданим.

Оригінальний об'єкт не змінюється, а main()локальна змінна методу zTextвсе ще вказує на початковий (порожній) рядок.

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

відбитки:

Original value:
Local value: foo
Final value:

Якщо ви хочете змінити рядок, ви можете, як зазначалося, використовувати StringBuilderабо інший контейнер (масив, AtomicReferenceабо спеціальний клас контейнера), який дає додатковий рівень непрямості вказівника. Крім того, просто поверніть нове значення та призначте його:

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

відбитки:

Original value:
Local value: foo
Final value: foo

Це, мабуть, найбільш схоже на Java рішення у загальному випадку - див. Ефективний пункт Java "Корисність незмінності".

Як зазначалося, однак, StringBuilderчасто ви будете мати кращі показники - якщо у вас є багато додатків, особливо це стосується циклу, використовуйте StringBuilder.

Але намагайтеся обходити незмінний, Stringsа не змінний, StringBuildersякщо зможете - ваш код буде простішим для читання та більш ретельним. Поміркуйте над створенням параметрів finalта налаштуванням IDE, щоб попереджати, коли параметр методу присвоює нове значення.


6
Ви говорите "строго кажучи" так, ніби це майже не має значення - тоді як це лежить в основі проблеми. Якби Java справді пройшла посилання, це не було б проблемою. Будь ласка, намагайтеся уникати поширення міфу "Java передає об'єкти посиланням" - пояснення (правильне) "посилання передаються за значенням" модель набагато корисніше, IMO.
Джон Скіт

Гей, куди пішов мій попередній коментар? :-) Як я вже говорив раніше, і, як додав Джон, справжнє питання полягає в тому, що посилання передається за значенням. Це дуже важливо, і багато людей не розуміють смислової різниці.
Ред С.

1
Досить справедливо. Як Java-програміст, я не бачив нічого, що насправді пройшло за посиланням краще за десять років, тому моя мова стала неохайною. Але ти маєш рацію, я мав би більше подбати, особливо для аудиторії програмування С.
Девід Молес

5

Рядок - це незмінний об'єкт на Java. Ви можете використовувати клас StringBuilder, щоб виконувати завдання, які ви намагаєтеся виконати, таким чином:

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

Іншим варіантом є створення класу з String як змінної області (сильно обережно) наступним чином:

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
Рядок - це не примітивний тип у Java.
VWeber

Щоправда, але на що саме ти намагаєшся привернути мою увагу?
Fadi Hanna AL-Kass

З тих пір, коли Java String - примітив ?! ТАК! Рядок передається за посиланням.
thedp

2
«незмінний предмет» - це те, що я мав намір сказати. Я чомусь не перечитав свою відповідь після того, як @VWeber зробив коментар, але тепер я бачу, що він намагався сказати. моє ліжко. відповідь змінена
Fadi Hanna AL-Kass

2

Відповідь проста. У струні Java незмінні. Звідси схоже на використання "остаточного" модифікатора (або "const" в C / C ++). Отже, після призначення, ви не можете змінити його так, як ви це зробили.

Ви можете змінити який значення , до якого струнні точки, але ви не можете змінити фактичне значення, яке цей рядок в даний момент вказує.

Тобто String s1 = "hey". Ви можете зробити це s1 = "woah", і це абсолютно нормально, але ви не можете насправді змінити базове значення рядка (у цьому випадку: "hey") на щось інше, як тільки його призначити за допомогою plusEquals тощо (тобто s1 += " whatup != "hey whatup").

Для цього використовуйте класи StringBuilder або StringBuffer або інші змінні контейнери, а потім просто зателефонуйте .toString () для перетворення об'єкта назад у рядок.

Примітка: рядки часто використовуються як хеш-ключі, отже, це частина причини, чому вони незмінні.


Незалежно від того, змінюється він чи непорушний, це не має значення. =за посиланням НІКОЛИ не впливає на об'єкт, на який він вказував, незалежно від класу.
newacct

2

String - це особливий клас на Java. Це безпечно для теми, що означає "Після створення String-екземпляра вміст String-екземпляра ніколи не зміниться".

Ось, що відбувається

 zText += "foo";

Спочатку компілятор Java отримає значення екземпляра zText String, потім створить новий екземпляр String, значенням якого є zText, додаючи "foo". Тож ви знаєте, чому екземпляр, на який вказують zText, не змінюється. Це абсолютно новий екземпляр. Насправді навіть String "foo" є новим екземпляром String. Отже, для цього оператора Java створить два екземпляри String, один - "foo", інший - значення zText, додає "foo". Правило просте: значення екземпляра String ніколи не буде змінено.

Для методу fillString ви можете використовувати StringBuffer як параметр, або можете змінити його так:

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

... хоча якщо ви повинні визначити "fillString" відповідно до цього коду, ви дійсно повинні дати йому більш відповідне ім'я!
Стівен C

Так. Цей метод повинен бути названий "appendString" або так.
DeepNightTwo


1

Для цього використовується 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");
}
}

Ще краще використовувати 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");
}
}

1

Рядок непорушний в java. ви не можете змінити / змінити існуючий рядковий літерал / об'єкт.

Рядок s = "Привіт"; s = s + "привіт";

Тут попереднє посилання s замінюється новим s референсом, що вказує на значення "HelloHi".

Однак для приведення змінності у нас є StringBuilder і StringBuffer.

StringBuilder s = новий StringBuilder (); s.append ("Привіт");

це додає нове значення "Привіт" до тієї ж референції s. //


0

Аарон Дігулла має найкращу відповідь досі. Варіантом його другого варіанту є використання обгорткового або контейнерного класу MutableObject бібліотеки Commons lang версії 3+:

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

ви зберігаєте декларацію класу контейнерів. Недолік - залежність від загальнодоступної мови. Але ліб має досить багато корисної функції, і майже будь-який великий проект, над яким я працював, використовував його.


0

Для передачі об'єкта (включаючи String) за посиланням у java, ви можете передати його як член оточуючого адаптера. Рішення з загальним є тут:

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

Для того, хто цікавіший

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

Тепер, запустивши цей вище код, ви можете побачити, як hashcodesзмінюється адреса за допомогою змінної String s. новий об'єкт виділяється змінній s у функції changetoпри зміні s

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.