Що розуміється під незмінним?


400

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

  1. Чи може хтось уточнити, що мається на увазі під незмінним ?
  2. Чому Stringнепорушний?
  3. Які переваги / недоліки незмінних предметів?
  4. Чому слід StringBuilderзмінювати такі об'єкти, які можна змінити, як " String" та "vice-verse"?

Гарний приклад (на Java) буде дуже вдячний.


73
Бачите, це було не таке німе питання. Рада, що ви запитали!
DOK

2
До речі, я не думаю, що це найглуміше питання коли-небудь :) Я думаю, що це досить важлива концепція для розуміння
Джейсон Коко

1
Коли ви сказали StringBuilder, ви не мали на увазі змінного класу StringBuffer? String і StringBuffer за функцією більш схожі, ніж String і StringBuilder. StringBuffer - це фактично змінна рядок.
Дерек Махар

3
Чи можу я запропонувати нам додати тег "початківець" до цього питання, щоб програмісти, які не знайшли Java, могли знайти його в пошуку інших вступних питань?
Дерек Махар

Відповіді:


268

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

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

напр

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примірника.

Також остерігайтеся різних видів незмінності, які ви можете знайти: Ерік Ліпперт написав про це статтю в блозі . В основному ви можете мати об'єкти, інтерфейс яких незмінний, але поза кадром фактичні мутації приватного стану (і тому їх не можна безпечно ділити між потоками).


3
Я думаю, вам слід додати один конструктор аргументів, щоб принаймні раз присвоїти значення. Точка поточного коду незрозуміла, оскільки немає значення, щоб насправді змінити :).
Георгій Болюба

4
Ви повинні зробити поле читати тільки. Це робить абсолютно явним, що поле незмінне. Наразі це незмінне за
умовами

7
Член myVar повинен бути остаточним, щоб це було справді непорушним.
лаз

13
Ви вірні, що myVar недоступний за межами Foo. Однак наявність фіналу вказує комусь, що може змінити клас у майбутньому, що його значення не має наміру змінюватися. Я прагну бути максимально явним в таких умовах.
лаз

2
Як щодо "Типи довідок не можна зробити незмінними лише за допомогою остаточного ключового слова. Остаточне запобігає лише переназначенню." від en.wikipedia.org/wiki/Immutable_object
Yousha Aleayoub

81

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

Є багато переваг непорушних рядків:

Продуктивність: виконайте таку операцію:

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 був змінним (символи могли змінитись пізніше), то вам доведеться скопіювати всі символи, інакше зміни в символах підрядки будуть відображені в іншому рядку пізніше.

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

Збір сміття: сміттєзбірнику набагато простіше приймати логічні рішення щодо незмінних об'єктів.

Однак є й недоліки незмінності:

Продуктивність: Зачекайте, я думав, ви сказали, що продуктивність перевершує незмінність! Ну, буває, але не завжди. Візьміть наступний код:

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 викликається двічі, що означає, що весь рядок повинен бути пропущений через цикл! Порівняйте це з кодом С для 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об'єкт є локальним посиланням, вам не потрібно турбуватися про безпеку одночасності (лише одна нитка коли-небудь торкається його). А оскільки на нього ніде не посилається, він виділяється лише у стеку, тому він розміщується, як тільки виклик функції закінчується (вам не доведеться турбуватися про збирання сміття). І ви отримуєте всі переваги від продуктивності як незмінність, так і незмінність.


4
Чудово читати! тільки одна річ, я думаю, це має бути, якщо (перший), а не, якщо (! перший)
Siddhartha

Що потрібно, це не те, щоб поля були непорушними, а в тому, щоб визначений стан спостереження об'єкта був незмінним; об'єкт, який містить посилання на інший об'єкт як засіб інкапсуляції, що міститься в ньому, може бути непорушним лише у тому випадку, якщо всі інкапсульовані аспекти стану, які він піддається зовнішньому світу, також незмінні. Зауважте, що поля не мають змінних типів. Що важливо - це видимий стан.
supercat

7
Passing pointers because Java is pass-by-referenceЧи не є java "прохідною вартістю?"
Крістіан Гуту

@ CristianGutu так, ти маєш рацію JAVA - це "Pass by Value", а не "Pass by REFERENCE"
Арш Каушаль

Посилання передається як значення !!
devv

31

Насправді, String не є непорушним, якщо ви використовуєте запропоновані вище вікіпедії.

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

Тож, можливо, визначення незмінного має бути об'єктом, який не можна помітити, що змінився.

Якщо стан у незмінному об'єкті змінюється після його створення, але його ніхто не може побачити (без відображення), чи об'єкт все-таки непорушний?


1
Хороша ідея - об’єкт, який не можна помітити, що змінився, а також ніякий спосіб змінити його зовні. Приватне поле для hashCode () - це внутрішня зміна, яка не є істотною для зовнішнього видимого стану об'єкта.
mparaz

2
Насправді можна помітити, що змінився, якщо використовувати рефлексію. Дивіться більше на " Струни Седжевіка" є змінними, якщо ви дозволите рефлексію .
Мігель

24

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

Проте для уточнення, проте, 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. Причиною, чому це може бути погано, наприклад, скажімо, у вас є програма GUI, яка має кілька потоків, і ви використовуєте ColoredStrings для друку даних у вікно. Якщо у вас є примірник ColoredString, який був створений як

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

Тоді ви б очікували, що рядок завжди буде "Синім". Якщо інша нитка, однак, знайшла цей екземпляр і зателефонувала

blueString.setColor("Red");

Ви б раптом, і, мабуть, несподівано, зараз мали б "Червону" струну, коли хотіли "Синьої". Через це незмінні об'єкти майже завжди віддають перевагу при передачі екземплярів предметів навколо. Коли у вас є випадок, коли змінні об'єкти справді необхідні, ви зазвичай захищаєте об'єкт, передаючи лише копії з вашого конкретного поля управління.

Для повторного підбору підсумків у Java, java.lang.String є незмінним об'єктом (його неможливо змінити, коли він створений), а java.lang.StringBuilder - об'єкт, що змінюється, оскільки його можна змінити без створення нового примірника.


Поля слід робити лише заново. Зараз ваш клас незмінний умовно. Для майбутніх розробників немає жодних ознак, що незмінне є навмисним. Ознайомлення з полями лише допоможе з'ясувати ваш намір майбутньому розробнику
JaredPar

@JaredPar - Насправді клас взагалі не є незмінним ... це приклад мутаційного класу, який демонструє, чому це може бути проблемою.
Джейсон Коко

1
@JaredPar - О, це все нормально :) Я хотів би переписати це трохи зрозуміліше, але Дуглас вже добре написаний і, здається, улюблений, тому я просто залишу свою як інший приклад; але хтось насправді його відредагував, щоб зробити властивості остаточними, що я вважав кумедним :)
Джейсон Коко

24
  1. У великих програмах загальним для рядкових літералів є великі біти пам'яті. Таким чином, щоб ефективно обробляти пам'ять, JVM виділяє область, яка називається "String постійний пул". ( Зверніть увагу, що в пам'яті навіть невизначена String несе навколо char [], int для його довжини та інший для hashCode. Для числа навпаки, потрібно максимум вісім безпосередніх байтів )
  2. Коли complier наштовхується на лінійку String, він перевіряє пул, щоб перевірити, чи є вже однаковий літерал. І якщо його знайдено, посилання на новий літерал спрямовується на існуючий String, і не створюється новий "String literal object" (існуючий String просто отримує додаткову посилання).
  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, "Стара струна" все одно залишилася б у пам'яті, але ми не зможемо отримати доступ до неї ...


16

"незмінний" означає, що ви не можете змінити значення. Якщо у вас є екземпляр класу String, будь-який метод, який ви викликаєте, який, здається, змінює значення, фактично створить іншу String.

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

Щоб зберегти зміни, вам слід зробити щось подібне foo = foo.sustring (3);

Immutable vs mutable може бути смішним, коли ви працюєте з колекціями. Подумайте, що буде, якщо ви використовуєте змінний об’єкт як ключ для карти, а потім змініть значення (порада: подумайте equalsі про hashCode).


13

java.time

Це може бути трохи пізно, але для того, щоб зрозуміти, що таке незмінний об’єкт, розглянемо наступний приклад із нового API 8 для дати та часу Java ( 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 ()… 2014-03-18

dateAfterTwoYears.toString ()… 2016-03-18


8

Мені дуже подобаються пояснення від сертифікованого програміста SCJP Sun для Посібника з вивчення Java 5 .

Щоб зробити Java більш ефективною пам'яттю, JVM відкладає спеціальну область пам'яті, яку називають "String постійний пул". Коли компілятор стикається з літералом String, він перевіряє пул, щоб перевірити, чи вже існує ідентична String. Якщо відповідність знайдена, посилання на новий літерал спрямовується на існуючий String, і новий об’єкт Stral literal не створюється.


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

8

Незмінні об'єкти не можуть змінити стан після їх створення.

Існує три основні причини використовувати незмінні об’єкти, коли можна, і все це допоможе зменшити кількість помилок, які ви вводите у свій код:

  • Набагато простіше міркувати про те, як працює ваша програма, коли ви знаєте, що стан об'єкта неможливо змінити іншим методом
  • Обмежувані об'єкти автоматично захищені від потоку (якщо припустимо, що вони опубліковані безпечно), тому вони ніколи не стануть причиною цих важко виправних багатопотокових помилок
  • Змінювані об’єкти завжди матимуть однаковий код Hash, тому їх можна використовувати як ключі в HashMap (або подібних). Якщо хеш-код елемента в хеш-таблиці повинен був змінитися, то запис таблиці фактично буде втрачено, оскільки спроби знайти його в таблиці виявляться в неправильному місці. Це основна причина того, що об'єкти String є незмінними - вони часто використовуються як клавіші HashMap.

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


5

Одне значення має відношення до того, як значення зберігається в комп'ютері. Наприклад, для рядка .Net це означає, що рядок у пам'яті неможливо змінити. Коли ви думаєте, що ви її змінюєте, ви насправді створюєте новий рядок в пам'яті та вказівка ​​на існуючу змінну (яка є лише вказівником на фактичну колекцію символів десь в іншому місці) на новий рядок.


4
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зі значенням "Привіт".

s2=s1 : об'єкт s2створюється з посиланням на s1 об'єкт.

s1="Bye": значення попереднього s1об'єкта не змінюється, оскільки s1тип String і тип String є незмінним типом, замість цього компілятор створює новий об'єкт String зі значенням "Bye" і s1посилається на нього. тут, коли ми друкуємо s2значення, результатом буде "Привіт", а не "До побачення", оскільки s2посилався на попередній s1об'єкт, який мав значення "Привіт".


Ви можете додати трохи пояснень, будь ласка?
minigeek

3

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

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

У наведеному вище коді рядок s1 не змінився, інший об’єкт ( s2) був створений за допомогою s1.


3

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

Розглянемо нижче приклад,

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

Давайте отримаємо ідею з урахуванням наведеної нижче схеми,

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

На цій діаграмі ви можете побачити новий об’єкт, створений як "Світ майбутнього". Але не міняти "Майбутнє". Because String is immutable. s, все ще посилайтесь на "Майбутнє". Якщо вам потрібно зателефонувати "Майбутній світ",

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

Чому струнні об'єкти незмінні в Java?

Тому що Java використовує поняття рядкового літералу. Припустимо, існує 5 опорних змінних, всі посилаються на один об'єкт "Майбутнє". Якщо одна контрольна змінна змінить значення об'єкта, це вплине на всі контрольні змінні. Ось чому струнні об'єкти незмінні в Java.


2

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


0

Незмінні об’єкти

Об'єкт вважається незмінним, якщо його стан не може змінитися після його побудови. Максимальна залежність від незмінних об'єктів широко сприймається як обгрунтована стратегія створення простого надійного коду.

Незмінні об'єкти особливо корисні в одночасних програмах. Оскільки вони не можуть змінити стан, вони не можуть бути пошкоджені втручанням потоку або спостерігатись у непослідовному стані.

Програмісти часто неохоче використовують непорушні об'єкти, оскільки вони турбуються про витрати на створення нового об’єкта на відміну від оновлення об'єкта на місці. Вплив створення об'єктів часто завищується і може компенсуватися деякою ефективністю, пов'язаною з незмінними об'єктами. До них належать зменшення накладних витрат через вивезення сміття та усунення коду, необхідного для захисту змінних об'єктів від корупції.

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

Джерело


0

Оскільки прийнята відповідь не дає відповіді на всі запитання. Я змушений дати відповідь через 11 років і 6 місяців.

Чи може хтось уточнити, що мається на увазі під незмінним?

Сподіваюся, ти мав на увазі непорушний об’єкт (адже ми могли б подумати про незмінне посилання ).

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

Чому Stringнепорушний?

Поважайте наведене вище визначення, яке можна перевірити, переглянувши вихідний код Sting.java .

Які переваги / недоліки незмінних предметів? Незмінними типами є:

  • безпечніше від помилок.

  • легше зрозуміти.

  • і більше готові до змін.

Чому слід змінювати об'єкт, що змінюється, наприклад StringBuilder, над String та віце-стихом?

Звуження питання Чому нам потрібен змінний StringBuilder в програмуванні? Загальним для цього є об'єднання великої кількості рядків разом, як це:

String s = "";
for (int i = 0; i < n; ++i) {
    s = s + n;
}

Використовуючи незмінні рядки, це робить безліч тимчасових копій - перше число рядка ("0") фактично копіюється n разів під час нарощування остаточного рядка, друге число копіюється n-1 разів тощо. на. Насправді це коштує O (n2) часу просто для того, щоб зробити все це копіювання, навіть якщо ми об'єднали лише n елементів.

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

StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; ++i) {
  sb.append(String.valueOf(n));
}
String s = sb.toString();

Отримання хорошої продуктивності - одна з причин, чому ми використовуємо об'єкти, що змінюються. Ще один зручний обмін: дві частини вашої програми можуть зручніше спілкуватися, обмінюючись загальною структурою даних, що змінюється.

Більше можна знайти тут: https://web.mit.edu/6.005/www/fa15/classes/09-immutability/#useful_immutable_types


-1

Незмінний об'єкт - це той, який ви не можете змінити після його створення. Типовим прикладом є рядкові літерали.

Мова програмування AD, яка стає все більш популярною, має поняття "незмінність" через "інваріантне" ключове слово. Перегляньте цю статтю про Dr.Dobb - http://dobbscodetalk.com/index.php?option=com_myblog&show=Invariant-Strings.html&Itemid=29 . Це ідеально пояснює проблему.


Я вважаю, що станом на D 2.020 ключове слово було змінено з інваріантного на незмінне. Я не бачу сенсу, але він говорить: "Незмінний зараз реалізований". digitalmars.com/d/2.0/changelog.html#new2_020
he_the_great
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.