سؤال

قد يكون هذا هو أغبى سؤال تم طرحه على الإطلاق، لكنني أعتقد أنه مربك جدًا لمبتدئ Java.

  1. يمكن للشخص أن يوضح ما هو المقصود غير قابل للتغيير?
  2. لماذا أ String غير قابل للتغيير؟
  3. ما هي مزايا/عيوب الكائنات غير القابلة للتغيير؟
  4. لماذا يجب أن يكون كائن قابل للتغيير مثل StringBuilder يفضل على السلسلة والعكس؟

مثال جميل (في جافا) سيكون موضع تقدير حقًا.

هل كانت مفيدة؟

المحلول

غير قابل للتغيير يعني أنه بمجرد انتهاء مُنشئ الكائن من التنفيذ، لا يمكن تغيير هذا المثيل.

يعد هذا مفيدًا لأنه يعني أنه يمكنك تمرير المراجع إلى الكائن، دون القلق من قيام شخص آخر بتغيير محتوياته. خاصة عند التعامل مع التزامن، لا توجد مشكلات في قفل الكائنات التي لا تتغير أبدًا

على سبيل المثال

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 الأساسي للطريقة الفرعية () شيئًا مثل هذا:

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

لاحظ أن لا يجب نسخ أي من الشخصيات! إذا كان كائن السلسلة قابلاً للتغيير (يمكن أن تتغير الأحرف لاحقًا)، فسيتعين عليك نسخ جميع الأحرف، وإلا فإن التغييرات التي تطرأ على الأحرف في السلسلة الفرعية ستنعكس في السلسلة الأخرى لاحقًا.

التزامن: إذا كانت البنية الداخلية لكائن غير قابل للتغيير صالحة، فستكون صالحة دائمًا.ليس هناك احتمال أن تتمكن سلاسل الرسائل المختلفة من إنشاء حالة غير صالحة داخل هذا الكائن.ومن ثم، فإن الكائنات غير قابلة للتغيير موضوع آمن.

جمع القمامة: من الأسهل على جامع البيانات المهملة اتخاذ قرارات منطقية بشأن الكائنات غير القابلة للتغيير.

ومع ذلك، هناك أيضًا جوانب سلبية للثبات:

أداء: انتظر، اعتقدت أنك قلت أن الأداء كان بمثابة الجانب الإيجابي من الثبات!حسنا، هو في بعض الأحيان، ولكن ليس دائما.خذ الكود التالي:

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

يستبدل كلا السطرين الحرف الرابع بالحرف "a".ليس الجزء الثاني من التعليمات البرمجية أكثر قابلية للقراءة فحسب، بل إنه أسرع.انظر إلى كيفية تنفيذ الكود الأساسي لـ 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));

لاحظ أنه يتم استدعاء السلسلة مرتين مما يعني أنه يجب تكرار السلسلة بأكملها!قارن هذا برمز 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 الكائن هو مرجع محلي، فلا داعي للقلق بشأن أمان التزامن (يلمسه مؤشر ترابط واحد فقط).وبما أنه لا يتم الرجوع إليه في أي مكان آخر، يتم تخصيصه فقط على المكدس، لذلك يتم إلغاء تخصيصه بمجرد انتهاء استدعاء الوظيفة (لا داعي للقلق بشأن جمع البيانات المهملة).وستحصل على جميع فوائد الأداء لكل من القابلية للتغيير والثبات.

في الواقع، السلسلة ليست غير قابلة للتغيير إذا كنت تستخدم تعريف ويكيبيديا المقترح أعلاه.

حالة السلسلة تتغير بعد البناء.ألقِ نظرة على طريقة hashcode().تقوم السلسلة بتخزين قيمة رمز التجزئة مؤقتًا في حقل محلي ولكنها لا تحسبها حتى الاستدعاء الأول لرمز التجزئة ().هذا التقييم البطيء لرمز التجزئة يضع السلسلة في وضع مثير للاهتمام ككائن غير قابل للتغيير تتغير حالته، ولكن لا يمكن ملاحظة أنه قد تغير دون استخدام الانعكاس.

لذلك ربما ينبغي أن يكون تعريف غير القابل للتغيير كائنًا لا يمكن ملاحظة أنه قد تغير.

إذا تغيرت الحالة في كائن غير قابل للتغيير بعد إنشائه ولكن لا يمكن لأحد رؤيته (بدون انعكاس) فهل يظل الكائن غير قابل للتغيير؟

الكائنات غير القابلة للتغيير هي كائنات لا يمكن تغييرها برمجياً.إنها جيدة بشكل خاص للبيئات متعددة الخيوط أو البيئات الأخرى حيث تكون أكثر من عملية واحدة قادرة على تغيير (تحوير) القيم الموجودة في كائن ما.

فقط للتوضيح، StringBuilder هو في الواقع كائن قابل للتغيير، وليس كائنًا غير قابل للتغيير.سلسلة جافا العادية غير قابلة للتغيير (بمعنى أنه بمجرد إنشائها لا يمكنك تغيير السلسلة الأساسية دون تغيير الكائن).

على سبيل المثال، لنفترض أن لدي فئة تسمى ColoredString تحتوي على قيمة سلسلة ولون سلسلة:

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 هو كائن غير قابل للتغيير (it لا تستطيع يمكن تغييره بمجرد إنشائه) و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]   ------------------------^

The original string 'in memory' didn't change, but the reference variable was changed so that it refers to the new string. And if we didn't have s2, "Old String" would still be in the memory but we'll not be able to access it...

"غير قابل للتغيير" يعني أنه لا يمكنك تغيير القيمة.إذا كان لديك مثيل لفئة السلسلة، فإن أي طريقة تستدعيها ويبدو أنها تعدل القيمة، ستؤدي في الواقع إلى إنشاء سلسلة أخرى.

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

To preserve changes you should do something like this foo = foo.sustring(3);

يمكن أن يكون Immutable vs mutable أمرًا مضحكًا عند العمل مع المجموعات.فكر فيما سيحدث إذا استخدمت كائنًا قابلاً للتغيير كمفتاح للخريطة ثم قمت بتغيير القيمة (نصيحة:فكر في ذلك equals و hashCode).

java.time

قد يكون الوقت متأخرًا بعض الشيء، ولكن لفهم ماهية الكائن غير القابل للتغيير، فكر في المثال التالي من Java 8 Date and Time API الجديد (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/03/2014

dateAfterTwoYears.toString()... 18-03-2016

أنا حقا أحب التوضيح من مبرمج معتمد من SCJP Sun لدليل دراسة Java 5.

لجعل جافا أكثر كفاءة في الذاكرة ، تضع JVM جانبًا منطقة خاصة من الذاكرة تسمى "سلسلة سلسلة ثابتة". عندما يواجه المترجم سلسلة حرفية ، فإنه يتحقق من التجمع لمعرفة ما إذا كانت سلسلة متطابقة موجودة بالفعل.إذا تم العثور على تطابق، فسيتم توجيه المرجع إلى الحرفي الجديد إلى السلسلة الموجودة، ولا يتم إنشاء كائن حرفي جديد للسلسلة.

الكائنات غير القابلة للتغيير لا يمكن أن تتغير حالتها بعد إنشائها.

هناك ثلاثة أسباب رئيسية لاستخدام الكائنات غير القابلة للتغيير كلما استطعت، وكلها ستساعد في تقليل عدد الأخطاء التي تدخلها في التعليمات البرمجية الخاصة بك:

  • من الأسهل التفكير في كيفية عمل برنامجك عندما تعلم أن حالة الكائن لا يمكن تغييرها بطريقة أخرى
  • تكون الكائنات غير القابلة للتغيير آمنة تلقائيًا (بافتراض أنها منشورة بأمان) لذلك لن تكون أبدًا سببًا في حدوث أخطاء تعدد مؤشرات الترابط التي يصعب تثبيتها
  • ستحتوي الكائنات غير القابلة للتغيير دائمًا على نفس رمز التجزئة، بحيث يمكن استخدامها كمفاتيح في HashMap (أو ما شابه).إذا تم تغيير رمز التجزئة لعنصر في جدول التجزئة، فسيتم فقدان إدخال الجدول فعليًا، نظرًا لأن محاولات العثور عليه في الجدول ستنتهي بالبحث في المكان الخطأ.هذا هو السبب الرئيسي لكون كائنات السلسلة غير قابلة للتغيير - فهي تُستخدم بشكل متكرر كمفاتيح 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="Bye" :السابق s1 قيمة الكائن لا تتغير بسبب s1 يحتوي على نوع سلسلة ونوع السلسلة هو نوع غير قابل للتغيير، وبدلاً من ذلك يقوم المترجم بإنشاء كائن سلسلة جديد بقيمة "وداعا" و s1 المشار إليه.هنا عندما نطبع s2 القيمة، ستكون النتيجة "مرحبًا" وليس "وداعًا" لأنه s2 المشار إليها في السابق s1 كائن له قيمة "Hi".

غير قابل للتغيير يعني أنه بمجرد إنشاء الكائن، لن يتغير أي من أعضائه. 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  
  }  
 }  

دعونا نحصل على فكرة النظر في الرسم البياني أدناه،

enter image description here

في هذا الرسم البياني، يمكنك رؤية كائن جديد تم إنشاؤه باسم "عالم المستقبل".ولكن ليس تغيير "المستقبل".Because String is immutable. s, ، لا تزال تشير إلى "المستقبل".إذا كنت بحاجة إلى الاتصال بـ "عالم المستقبل"،

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

لماذا كائنات السلسلة غير قابلة للتغيير في جافا؟

لأن Java تستخدم مفهوم السلسلة الحرفية.لنفترض أن هناك 5 متغيرات مرجعية، تشير جميعها إلى كائن واحد "المستقبل". إذا قام متغير مرجعي واحد بتغيير قيمة الكائن، فسوف يتأثر بجميع المتغيرات المرجعية.هذا هو السبب في أن كائنات السلسلة غير قابلة للتغيير في Java.

بمجرد إنشاء مثيل، لا يمكن تغييره.ضع في اعتبارك فئة يمكن استخدام مثيل لها كمفتاح لجدول تجزئة أو ما شابه.تحقق من أفضل ممارسات جافا.

كائنات غير قابلة للتغيير

يعتبر الكائن غير قابل للتغيير إذا كانت حالته لا يمكن أن تتغير بعد إنشائه.إن الاعتماد الأقصى على الكائنات غير القابلة للتغيير مقبول على نطاق واسع كإستراتيجية سليمة لإنشاء تعليمات برمجية بسيطة وموثوقة.

تعتبر الكائنات غير القابلة للتغيير مفيدة بشكل خاص في التطبيقات المتزامنة.وبما أنها لا تستطيع تغيير الحالة، فلا يمكن إتلافها عن طريق تداخل الخيوط أو ملاحظتها في حالة غير متناسقة.

غالبًا ما يتردد المبرمجون في استخدام كائنات غير قابلة للتغيير، لأنهم قلقون بشأن تكلفة إنشاء كائن جديد بدلاً من تحديث كائن في مكانه.غالبًا ما يتم المبالغة في تقدير تأثير إنشاء الكائنات، ويمكن تعويضه ببعض الكفاءات المرتبطة بالكائنات غير القابلة للتغيير.يتضمن ذلك انخفاض الحمل بسبب جمع البيانات المهملة، وإزالة التعليمات البرمجية اللازمة لحماية الكائنات القابلة للتغيير من الفساد.

تأخذ الأقسام الفرعية التالية فئة تكون مثيلاتها قابلة للتغيير وتشتق منها فئة ذات مثيلات غير قابلة للتغيير.وبذلك، فإنهم يقدمون قواعد عامة لهذا النوع من التحويل ويوضحون بعض مزايا الكائنات غير القابلة للتغيير.

مصدر

يقول مستندات أوراكل

يعتبر كائن غير قابل للتغيير يتم قبول الحد الأقصى للاعتماد على الكائنات غير القابلة للتغيير كاستراتيجية صوتية لإنشاء رمز بسيط وموثوق.

تعتبر الكائنات غير القابلة للتغيير مفيدة بشكل خاص في التطبيقات المتزامنة.نظرًا لأنهم لا يستطيعون تغيير الحالة ، فلا يمكن أن يفسدهم من خلال تداخل الخيط أو ملاحظته في حالة غير متناسقة.

أعجبتني هذه العبارة من بريد

تعمل الكائنات غير القابلة للتغيير على تسهيل البرمجة المتزامنة

الكائن غير القابل للتغيير هو الذي لا يمكنك تعديله بعد إنشائه.والمثال النموذجي هو سلسلة حرفية.

لغة البرمجة D، والتي أصبحت ذات شعبية متزايدة، لديها مفهوم "الثبات" من خلال الكلمة الرئيسية "الثابتة".تحقق من مقالة Dr.Dobb حول هذا الموضوع - http://dobbscodetalk.com/index.php?option=com_myblog&show=Invariant-Strings.html&Itemid=29 .ويشرح المشكلة تماما.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top