Передача строки по ссылке в Java?

161

Я привык делать следующее в C:

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

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

И вывод:

foo

Однако в Java это, похоже, не работает. Я предполагаю, потому что Stringобъект копируется, а не передается по ссылке . Я думал, что строки - это объекты, которые всегда передаются по ссылке.

Что здесь происходит?

Сорен Джонсон
источник
2
Даже если они были переданы по ссылке (в Java то, что передается, является копией ссылочного значения, но это другой поток) Строковые объекты являются неизменяемыми, так что это не сработает в любом случае
OscarRyz
8
Это не код Си.
Олстон
@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.
Рави Валлау
Мне больше нравится третий вариант (массив), потому что он единственный общий. Он также позволяет передавать boolean, int и т. Д. Не могли бы вы немного подробнее объяснить производительность этого решения - вы утверждаете, что первое лучше с точки зрения производительности.
meolic
@meolic StringBuilderбыстрее, s += "..."поскольку он не выделяет новые объекты String каждый раз. Фактически, когда вы пишете код Java, как s = "Hello " + name, тогда компилятор будет генерировать байтовый код, который создает StringBuilderи вызывает append()два раза.
Аарон Дигулла
86

В Java ничего не передается по ссылке . Все передается по значению . Ссылки на объекты передаются по значению. Кроме того, строки неизменяемы . Поэтому, когда вы добавляете к переданной строке, вы просто получаете новую строку. Вы можете использовать возвращаемое значение или передать вместо него StringBuffer.

zedoo
источник
2
Это не заблуждение, это концепция
Патрик Корнелиссен
41
Ммм ... нет, ошибочно считать, что вы передаете объект по ссылке. Вы передаете ссылку по значению.
Эд С.
30
Да, это ошибочное мнение. Это огромное распространенное заблуждение. Это приводит к вопросу об интервью, который я ненавижу: («как Java передает аргументы»). Я ненавижу это, потому что примерно половина интервьюеров действительно, кажется, хотят неправильный ответ («примитивы по значению, объекты по ссылке»). Правильный ответ длится дольше и, кажется, смущает некоторых из них. И они не будут убеждены: я клянусь, что завалил технический экран, потому что проверщик типа CSMajor услышал неправильное представление в колледже и полагал, что это Евангелие. Фэ.
CPerkins
4
@Perkins Ты не представляешь, как это меня злит. Это заставляет мою кровь кипеть.
6
Все передается по значению на всех языках. Некоторые из этих значений оказываются адресами (ссылками).
Gerardw
52

Происходит то, что ссылка передается по значению, т. Е. Передается копия ссылки. Ничто в java не передается по ссылке, и поскольку строка является неизменной, это присваивание создает новый строковый объект, на который теперь указывает копия ссылки . Исходная ссылка все еще указывает на пустую строку.

Это будет то же самое для любого объекта, т. Е. Установка для него нового значения в методе. Приведенный ниже пример просто делает то, что происходит, более очевидным, но объединение строк - это одно и то же.

void foo( object o )
{
    o = new Object( );  // original reference still points to old value on the heap
}
Эд С.
источник
6
Отличный пример!
Николай
18

java.lang.String является неизменной.

Я ненавижу вставлять URL-адреса, но https://docs.oracle.com/javase/10/docs/api/java/lang/String.html важно для вас, чтобы читать и понимать, если вы находитесь в java-land.

Райан Фернандес
источник
Я не думаю, что Сорен Джонсон не знает, что такое String, это был не вопрос и не то, что я искал здесь после 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

== проверяет адрес памяти (ссылка), а .equals проверяет содержимое (значение)

janetsmith
источник
3
Не совсем верно. Java всегда проходит мимо VALUE; Хитрость заключается в том, что объекты передаются по ссылке, которые передаются по значению. (хитрый, я знаю). Проверьте это: javadude.com/articles/passbyvalue.htm
adhg
1
@adhg вы написали "Объекты передаются по ссылке, которые передаются по значению." <--- это бред. Вы имеете в виду, что у объектов есть ссылки, которые передаются по значению
barlop
2
-1 Вы написали «объекты передаются по ссылке» <- ЛОЖЬ. Если это так, то при вызове метода и передаче переменной метод может заставить переменную указывать / ссылаться на другой объект, но не может. Все передается по значению в Java. И вы все еще видите эффект изменения атрибутов объекта вне метода.
barlop
смотрите ответ здесь stackoverflow.com/questions/40480/…
barlop
7

Все аргументы в Java передаются по значению. Когда вы передаете a Stringв функцию, переданное значение является ссылкой на объект 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, я не видел ничего, что фактически передавалось по ссылке лучше, чем за десятилетие, поэтому мой язык стал небрежным. Но вы правы, мне следовало больше заботиться, особенно для аудитории C-программирования.
Дэвид Моулз
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
Правда, но на что именно вы пытаетесь привлечь мое внимание?
Фади Ханна АЛ-Касс
С каких пор Java String является примитивом ?! ТАК! Строка передается по ссылке.
Thedp
2
«неизменный объект» - это то, что я намеревался сказать. Я почему-то не перечитал мой ответ после того, как @VWeber сделал комментарий, но теперь я вижу, что он пытался сказать. моя вина. ответ изменен
Фади Ханна АЛ-Касс
2

Ответ прост. В Java строки неизменны. Следовательно, все равно, что использовать модификатор 'final' (или 'const' в C / C ++). Таким образом, после назначения вы не можете изменить его так, как вы это сделали.

Вы можете изменить который значение , к которому струнные точки, но вы не можете изменить фактическое значение, которое эта строка в данный момент указывает.

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

Для этого используйте классы StringBuilder или StringBuffer или другие изменяемые контейнеры, а затем просто вызовите .toString (), чтобы преобразовать объект обратно в строку.

примечание: строки часто используются в качестве ключей хеширования, поэтому это одна из причин их неизменности.

ak_2050
источник
Является ли оно изменчивым или неизменным, не имеет значения. =на ссылку НИКОГДА не влияет на объект, на который он указывал, независимо от класса.
newacct
2

String - это специальный класс в Java. Это Потокобезопасный, что означает «После создания экземпляра String содержимое экземпляра String никогда не изменится».

Вот что происходит

 zText += "foo";

Сначала компилятор Java получит значение экземпляра zText String, затем создаст новый экземпляр String, значением которого является zText, добавив «foo». Итак, вы знаете, почему экземпляр, на который указывает zText, не изменился. Это совершенно новый экземпляр. Фактически, даже String "foo" является новым экземпляром String. Таким образом, для этого оператора Java создаст два экземпляра String, один - «foo», другой - значение zText append «foo». Правило простое: значение экземпляра String никогда не изменится.

Для метода fillString вы можете использовать StringBuffer в качестве параметра или изменить его следующим образом:

String fillString(String zText) {
    return zText += "foo";
}
DeepNightTwo
источник
... хотя, если бы вы определили 'fillString' в соответствии с этим кодом, вы действительно должны дать ему более подходящее имя!
Стивен С
Да. Этот метод должен быть назван "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. Вы не можете изменить / изменить существующий строковый литерал / объект.

String s = "Hello"; S = S + "Привет";

Здесь предыдущая ссылка s заменяется новой ссылкой s, указывающей на значение «HelloHi».

Однако для обеспечения изменчивости у нас есть StringBuilder и StringBuffer.

StringBuilder s = new StringBuilder (); s.append ( "Привет");

это добавляет новое значение «Привет» к той же ссылке s. //

gauravJ
источник
0

У Аарона Дигуллы пока лучший ответ. Вариантом его второго варианта является использование класса-обертки или контейнера MutableObject из библиотеки commons lang версии 3+:

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

Вы сохраняете объявление класса контейнера. Недостатком является зависимость от общего языка lang lib. Но в lib есть много полезных функций, и почти любой большой проект, над которым я работал, использовал его.

user2606740
источник
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меняется адрес с помощью строковой переменной s. новый объект назначается переменной s в функции changetoпри изменении s

Раджеш Кумар
источник