Вопрос

Это может быть самый глупый вопрос, который когда-либо задавали, но я думаю, что он довольно сбивает с толку новичка в Java.

  1. Может кто-нибудь уточнить, что имеется в виду неизменный?
  2. Почему String неизменный?
  3. Каковы преимущества/недостатки неизменяемых объектов?
  4. Почему изменяемый объект, такой как StringBuilder быть предпочтительнее String и наоборот?

Хороший пример (на Java) будет очень признателен.

Это было полезно?

Решение

Неизменяемость означает, что после завершения выполнения конструктора объекта этот экземпляр не может быть изменен.

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

например

class Foo
{
     private final String myvar;

     public Foo(final String initialValue)
     {
         this.myvar = initialValue;
     }

     public String getValue()
     {
         return this.myvar;
     }
}

Foo не нужно беспокоиться, что вызывающий абонент getValue() может изменить текст в строке.

Если вы представите себе аналогичный класс Foo, но с StringBuilder а не String как участник, вы можете видеть, что звонящий на getValue() смог бы изменить StringBuilder атрибут Foo пример.

Также остерегайтесь различных видов неизменяемости, которые вы можете встретить:Эрик Липперт написал статья в блоге об этом.По сути, у вас могут быть объекты, интерфейс которых является неизменяемым, но за кулисами фактическое изменяемое частное состояние (и, следовательно, не может быть безопасно совместно использовано между потоками).

Другие советы

Неизменяемый объект - это объект, для которого нельзя изменить внутренние поля (или, по крайней мере, все внутренние поля, которые влияют на его внешнее поведение).

Есть много преимуществ для неизменяемых строк:

Производительность: выполните следующую операцию:

String substring = fullstring.substring(x,y);

Основной C для метода substring (), вероятно, выглядит примерно так:

// Assume string is stored like this:
struct String { char* characters; unsigned int length; };

// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
    struct String* out = malloc(sizeof(struct String));
    out->characters = in->characters + begin;
    out->length = end - begin;
    return out;
}

Обратите внимание, что ни один из символов не должен быть скопирован! Если бы объект String был изменяемым (символы могли измениться позже), вам пришлось бы скопировать все символы, в противном случае изменения в символах в подстрока будет отражена в другой строке позже.

Параллельность: Если внутренняя структура неизменяемого объекта является действительной, она всегда будет действительной. Нет никаких шансов, что разные потоки могут создать недопустимое состояние в этом объекте. Следовательно, неизменяемые объекты являются Thread Safe .

Сборка мусора: сборщику мусора гораздо проще принимать логические решения относительно неизменных объектов.

Однако есть и недостатки неизменности:

Производительность: Подождите, я думал, вы сказали, что производительность - это непревзойденная производительность! Ну, иногда, но не всегда. Возьмите следующий код:

foo = foo.substring(0,4) + "a" + foo.substring(5);  // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder

Обе строки заменяют четвертый символ буквой «а». Второй фрагмент кода не только более читабелен, но и быстрее. Посмотрите, как вы должны сделать основной код для foo. Подстроки просты, но теперь, когда в пятой позиции уже есть символ и что-то еще может ссылаться на foo, вы не можете просто изменить его; Вы должны скопировать всю строку (конечно, некоторые из этих функций абстрагированы в функции в реальном базовом C, но суть здесь в том, чтобы показать код, который выполняется все в одном месте).

struct String* concatenate(struct String* first, struct String* second)
{
    struct String* new = malloc(sizeof(struct String));
    new->length = first->length + second->length;

    new->characters = malloc(new->length);

    int i;

    for(i = 0; i < first->length; i++)
        new->characters[i] = first->characters[i];

    for(; i - first->length < second->length; i++)
        new->characters[i] = second->characters[i - first->length];

    return new;
}

// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));

Обратите внимание, что concatenate вызывается дважды , что означает, что вся строка должна быть зациклена! Сравните это с кодом C для операции bar :

bar->characters[4] = 'a';

Операция с изменяемой строкой, очевидно, намного быстрее.

В заключение. В большинстве случаев вам нужна неизменная строка. Но если вам нужно много добавлять и вставлять в строку, вам нужна изменчивость для скорости. Если вы хотите использовать преимущества безопасности параллелизма и сборки мусора, ключом является сохранение ваших изменяемых объектов локальными для метода:

// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
    StringBuilder mutable;
    boolean first = true;

    for(int i = 0; i < strings.length; i++)
    {
        if(!first) first = false;
        else mutable.append(separator);

        mutable.append(strings[i]);
    }

    return mutable.toString();
}

Поскольку объект mutable является локальной ссылкой, вам не нужно беспокоиться о безопасности параллелизма (его когда-либо касается только один поток). А поскольку на него больше нигде нет ссылок, он размещается только в стеке, поэтому он освобождается, как только завершается вызов функции (вам не нужно беспокоиться о сборке мусора). И вы получаете все преимущества производительности как изменчивости, так и неизменности.

На самом деле String не является неизменным, если вы используете определение Википедии, предложенное выше.

Состояние строки действительно меняет пост-конструкцию. Взгляните на метод hashcode (). String кэширует значение хеш-кода в локальном поле, но не вычисляет его до первого вызова hashcode (). Эта ленивая оценка хеш-кода ставит String в интересное положение как неизменного объекта, состояние которого изменяется, но нельзя наблюдать, как он изменился без использования отражения.

Так что, возможно, определение неизменяемого должно быть объектом, который не может быть изменен.

Если состояние изменяется в неизменяемом объекте после того, как он был создан, но никто не может видеть его (без отражения), остается ли объект неизменным?

Неизменяемые объекты - это объекты, которые нельзя изменить программно. Они особенно хороши для многопоточных сред или других сред, в которых более одного процесса могут изменять (изменять) значения в объекте.

Просто для пояснения, однако, StringBuilder на самом деле является изменяемым объектом, а не неизменным. Обычная строка Java является неизменной (это означает, что после ее создания вы не можете изменить базовую строку без изменения объекта).

Например, допустим, у меня есть класс ColoredString, который имеет значение String и цвет String:

public class ColoredString {

    private String color;
    private String string;

    public ColoredString(String color, String string) {
        this.color  = color;
        this.string = string;
    }

    public String getColor()  { return this.color;  }
    public String getString() { return this.string; }

    public void setColor(String newColor) {
        this.color = newColor;
    }

}

В этом примере ColoredString называется изменяемой, поскольку вы можете изменить (изменить) одно из его ключевых свойств, не создавая новый класс ColoredString. Причина, по которой это может быть плохо, например, допустим, у вас есть приложение с графическим интерфейсом, которое имеет несколько потоков, и вы используете ColoredStrings для печати данных в окне. Если у вас есть экземпляр ColoredString, который был создан как

new ColoredString("Blue", "This is a blue string!");

Тогда вы ожидаете, что строка всегда будет "синим". Однако, если другой поток получил этот экземпляр и вызвал

blueString.setColor("Red");

Теперь у вас внезапно и, вероятно, неожиданно появился «красный». Строка, когда вы хотели "синий" один. Из-за этого неизменные объекты почти всегда предпочтительнее при передаче экземпляров объектов вокруг. Если у вас есть случай, когда изменяемые объекты действительно необходимы, тогда вы, как правило, охраняете объект, только передавая копии из вашей конкретной области управления.

Напомним, что в Java java.lang.String является неизменным объектом (его нельзя изменить после его создания), а java.lang.StringBuilder является изменяемым объектом, поскольку его можно изменять без создание нового экземпляра.

  1. В больших приложениях строковые литералы обычно занимают большие биты памяти.Поэтому для эффективного управления памятью JVM выделяет область под названием «Пул строковых констант».(Обратите внимание, что в памяти даже строка, на которую нет ссылки, содержит char[], int для ее длины и еще один для ее хеш-кода.Для числа, напротив, требуется максимум восемь непосредственных байтов.)
  2. Когда компилятор встречает строковый литерал, он проверяет пул на наличие уже существующего идентичного литерала.И если он найден, ссылка на новый литерал направляется на существующую строку, и новый «объект строкового литерала» не создается (существующая строка просто получает дополнительную ссылку).
  3. Следовательно : Изменяемость строк экономит память...
  4. Но когда какая-либо из переменных меняет значение, на самом деле меняется только их ссылка, а не значение в памяти (следовательно, это не повлияет на другие переменные, ссылающиеся на нее), как показано ниже....

Строка s1 = "Старая строка";

//s1 variable, refers to string in memory
        reference                 |     MEMORY       |
        variables                 |                  |

           [s1]   --------------->|   "Old String"   |

Строка s2 = s1;

//s2 refers to same string as s1
                                  |                  |
           [s1]   --------------->|   "Old String"   |
           [s2]   ------------------------^

s1 = «Новая строка»;

//s1 deletes reference to old string and points to the newly created one
           [s1]   -----|--------->|   "New String"   |
                       |          |                  |
                       |~~~~~~~~~X|   "Old String"   |
           [s2]   ------------------------^

Исходная строка «в памяти» не изменилась, но эталонная переменная была изменена, чтобы она относилась к новой строке.И если бы у нас не было S2, «старая строка» все равно была бы в памяти, но мы не сможем получить к нему доступ ...

«неизменяемый» означает, что вы не можете изменить значение.Если у вас есть экземпляр класса String, любой метод, который вы вызываете, который изменяет значение, на самом деле создаст другую строку.

String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"

Чтобы сохранить изменения, вы должны сделать что -то вроде этого foo = foo.sustring (3);

Неизменяемые и изменяемые могут быть забавными, когда вы работаете с коллекциями.Подумайте, что произойдет, если вы используете изменяемый объект в качестве ключа для карты, а затем измените значение (подсказка:думать о equals и hashCode).

java.time

Возможно, уже немного поздно, но чтобы понять, что такое неизменяемый объект, рассмотрим следующий пример из нового API даты и времени Java 8 (java.time).Как вы, вероятно, знаете, все объекты даты в Java 8 неизменный поэтому в следующем примере

LocalDate date = LocalDate.of(2014, 3, 18); 
date.plusYears(2);
System.out.println(date);

Выход:

2014-03-18

При этом печатается тот же год, что и исходная дата, поскольку plusYears(2) возвращает новый объект, поэтому старая дата остается неизменной, поскольку это неизменяемый объект.После создания вы не сможете в дальнейшем изменять его, и переменная даты по-прежнему указывает на него.

Итак, этот пример кода должен захватить и использовать новый объект, созданный и возвращенный этим вызовом plusYears.

LocalDate date = LocalDate.of(2014, 3, 18); 
LocalDate dateAfterTwoYears = date.plusYears(2);

date.toString()… 18 марта 2014 г.

dateAfterTwoYears.toString()… 18 марта 2016 г.

Мне очень нравится объяснение из SCJP Sun Certified Programmer для Java 5 Учебное пособие .

  

Чтобы повысить эффективность использования памяти Java, JVM выделяет специальную область памяти, называемую «пул констант строки». Когда компилятор встречает литерал String, он проверяет пул, чтобы увидеть, существует ли уже идентичная строка. Если совпадение найдено, ссылка на новый литерал направляется на существующую строку, и новый объект литерала строки не создается.

Состояние неизменяемых объектов не может быть изменено после их создания.

Есть три основные причины использовать неизменяемые объекты всякий раз, когда это возможно, и каждая из них поможет уменьшить количество ошибок, которые вы вносите в свой код:

  • Гораздо легче рассуждать о том, как работает ваша программа, когда вы знаете, что состояние объекта нельзя изменить другим методом.
  • Неизменяемые объекты автоматически становятся потокобезопасными (при условии, что они опубликованы безопасно), поэтому никогда не будут причиной трудно выявляемых ошибок многопоточности.
  • Неизменяемые объекты всегда будут иметь один и тот же хэш-код, поэтому их можно использовать в качестве ключей в HashMap (или аналогичном).Если бы хеш-код элемента в хеш-таблице изменился, запись таблицы фактически была бы потеряна, поскольку попытки найти ее в таблице в конечном итоге привели бы к поиску в неправильном месте.Это основная причина того, что объекты String неизменяемы — они часто используются в качестве ключей HashMap.

Есть также некоторые другие оптимизации, которые вы можете выполнить в коде, когда знаете, что состояние объекта неизменно - например, кэширование вычисленного хеша - но это оптимизации и поэтому не так интересны.

Одно из значений связано с тем, как значение хранится на компьютере. Например, для строки .Net это означает, что строку в памяти нельзя изменить. Когда вы думаете, что изменяете ее, вы на самом деле создание новой строки в памяти и указание существующей переменной (которая является просто указателем на фактическую коллекцию символов где-то еще) на новую строку.

String s1="Hi";
String s2=s1;
s1="Bye";

System.out.println(s2); //Hi  (if String was mutable output would be: Bye)
System.out.println(s1); //Bye

s1 = " Hi " : объект s1 создан с " Hi " значение в нем.

s2 = s1 : объект s2 создается со ссылкой на объект s1.

s1 = " Пока " : значение предыдущего объекта s1 не изменяется, поскольку s1 имеет тип String, а тип String является неизменяемый тип, вместо этого компилятор создает новый объект String с " Bye " значение и s1 ссылаются на него. здесь, когда мы печатаем значение s2 , результатом будет " Привет " не "пока" потому что s2 ссылается на предыдущий объект s1 , который имел " Привет " значение.

Неизменяемый означает, что после создания объекта ни один из его членов не изменится. String является неизменным, поскольку вы не можете изменить его содержимое. Например:

String s1 = "  abc  ";
String s2 = s1.trim();

В приведенном выше коде строка s1 не изменилась, другой объект ( s2 ) был создан с использованием s1 .

Неизменяемые просто означают неизменяемые или неизменяемые. После создания строкового объекта его данные или состояние не могут быть изменены

Рассмотрим приведенный ниже пример.

class Testimmutablestring{  
  public static void main(String args[]){  
    String s="Future";  
    s.concat(" World");//concat() method appends the string at the end  
    System.out.println(s);//will print Future because strings are immutable objects  
  }  
 }  

Давайте разберемся с приведенной ниже схемой,

 введите описание изображения здесь

На этой диаграмме вы видите новый объект, созданный как "Мир будущего". Но не изменяйте "Future". Поскольку String является неизменным . s , все еще ссылаются на «Будущее». Если вам нужно позвонить в «Мир будущего»,

String s="Future";  
s=s.concat(" World");  
System.out.println(s);//print Future World

Почему строковые объекты неизменяемы в Java?

  

Потому что в Java используется концепция строкового литерала. Предположим, что существует 5 ссылочных переменных, все ссылаются на один объект «Будущее». Если одна ссылочная переменная изменит значение объекта, это повлияет на все ссылочные переменные. Вот почему строковые объекты неизменны в Java.

Однажды созданный, не может быть изменен. Рассмотрим класс, экземпляр которого может быть использован в качестве ключа для хеш-таблицы или аналогичного. Ознакомьтесь с рекомендациями по Java.

Неизменяемые объекты

Объект считается неизменным, если его состояние не может измениться после его создания. Максимальная опора на неизменяемые объекты широко признана в качестве разумной стратегии для создания простого и надежного кода.

Неизменяемые объекты особенно полезны в параллельных приложениях. Так как они не могут изменить состояние, они не могут быть повреждены из-за вмешательства потока или обнаружены в несовместимом состоянии.

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

Следующие подразделы берут класс, экземпляры которого являются изменяемыми, и получают класс с неизменяемыми экземплярами из него. При этом они дают общие правила для этого вида преобразования и демонстрируют некоторые преимущества неизменяемых объектов.

источник

документы оракула говорят

  

Объект считается неизменным , если его состояние не может измениться после него   построен Максимальная опора на неизменные объекты широко   принята в качестве разумной стратегии для создания простого и надежного кода.

     

Неизменяемые объекты особенно полезны в параллельных приложениях.   Так как они не могут изменить состояние, они не могут быть повреждены потоком   вмешательство или наблюдается в несовместимом состоянии.

Мне нравится эта фраза из поста

  

Неизменяемые объекты упрощают параллельное программирование

Неизменяемый объект - это объект, который нельзя изменить после его создания. Типичным примером являются строковые литералы.

Язык программирования D, который становится все более популярным, имеет понятие «неизменность». через "инвариант" ключевое слово. Ознакомьтесь с этой статьей Dr.Dobb об этом - http: // dobbscodetalk .com / index.php? option = com_myblog & amp; show = Invariant-Strings.html & amp; Itemid = 29 . Это прекрасно объясняет проблему.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top