يمكن للشخص أن يفسر لي ما المنطق وراء المارة "القيمة" و لا من "مرجع" في جافا ؟

StackOverflow https://stackoverflow.com/questions/514478

سؤال

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

String foo = new String();
thisDoesntWork(foo);
System.out.println(foo);//this prints nothing

public static void thisDoesntWork(String foo){
   foo = "howdy";
}

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

يمكن للشخص أن يفسر لي ما المنطق وراء هذا التصميم الاختيار ؟ كما قلت أنا لا أبحث هنا و ربما أنا في عداد المفقودين شيء واضح ؟

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

المحلول

هذا خرف يفسر ذلك أفضل مما كنت قد أي وقت مضى حتى محاولة:

<اقتباس فقرة>   

في جاوة، يتم تمرير الأوليات من حيث القيمة. ومع ذلك، ليست كائنات   تمريرها حسب المرجع. ومن شأن بيان الصحيح أن يكون مراجع كائن   تم تمريرها من حيث القيمة.

نصائح أخرى

عند تمرير "فو" أنت اجتياز المرجعية أن "فو" كقيمة ThisDoesntWork().وهذا يعني أن عند الإحالة إلى "فو" داخل الأسلوب الخاص بك, كنت مجرد وضع متغير محلي (فو)'s إشارة إلى أن الإشارة إلى سلسلة جديدة.

شيء آخر أن نأخذ في الاعتبار عند التفكير في كيفية سلاسل التصرف في جاوة هو أن سلاسل غير قابلة للتغيير.وهو يعمل بنفس الطريقة في C# ، و عن بعض أسباب وجيهة:

  • الأمن:لا أحد يستطيع المربى البيانات في سلسلة يؤدي تجاوز سعة المخزن المؤقت الخطأ إذا كان لا أحد يمكن تعديله!
  • السرعة :إذا كان يمكنك أن تكون على يقين من أن سلاسل الخاص بك غير قابل للتغيير ، تعرف حجمها هو نفسه دائما و لا من أي وقت مضى إلى القيام بخطوة من بنية البيانات في الذاكرة عند التلاعب به.أنت (لغة مصمم) أيضا لا داعي للقلق حول تنفيذ سلسلة بطيئة ربط قائمة أيضا.هذه التخفيضات في كلا الاتجاهين ، على الرغم من.إلحاق سلاسل فقط باستخدام مشغل + يمكن أن تكون مكلفة الذاكرة الحكمة, و سيكون لديك لاستخدام بـ stringbuilder كائن للقيام بذلك في عالية الأداء, الذاكرة-طريقة فعالة.

الآن إلى السؤال الأكبر.لماذا الكائنات مرت من هذا الطريق ؟ حسنا, إذا كان جافا مرت سلسلة الخاص بك بقدر ما كنت تقليديا الدعوة "حسب القيمة" سيكون في الواقع نسخ السلسلة بأكملها قبل تمريرها إلى وظيفة الخاص بك.التي هي بطيئة جدا.إذا اجتاز سلسلة بالرجوع وتمكنك من تغييره (مثل C) سيكون لديك مشاكل أنا مجرد سرد.

منذ بلدي الأصلي كان الجواب "لماذا حدث ما حدث" وليس "لماذا كانت اللغة مصممة لذلك حدث ما حدث ،" سأعطيك هذا آخر يذهب.

إلى تبسيط الأمور ، سأتخلص من استدعاء الأسلوب وإظهار ما يحدث في طريقة أخرى.

String a = "hello";
String b = a;
String b = "howdy"

System.out.print(a) //prints hello

على البيان الأخير إلى طباعة "مرحبا" ، ب يجب أن تشير إلى نفس "ثقب" في الذاكرة a يشير إلى (مؤشر).هذا هو ما تريد عندما تريد تمرير من قبل المرجعية.هناك بضعة أسباب جافا قررت أن لا تذهب في هذا الاتجاه:

  • مؤشرات مربكة المصممين جافا حاول إزالة بعض من أكثر الأمور المربكة عن اللغات الأخرى.المؤشرات هي واحدة من الأكثر يساء فهمها بشكل غير صحيح استخدام بنيات C/C++ جنبا إلى جنب مع عامل الحمولة الزائدة.

  • مؤشرات المخاطر الأمنية مؤشرات يسبب العديد من المشاكل الأمنية عندما يساء استخدامها.برنامج ضار يعين شيئا إلى أن جزءا من الذاكرة ، ثم ما كنت اعتقد وجوه الخاص بك هو في الواقع شخص آخر.(جافا بالفعل تخلصت من أكبر مشكلة أمنية عازلة الفيضانات ، مع فحص المصفوفات)

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

  • الأجسام هي كل ما يهمك في جافا, كل ما هو كائن وليس الفضاء كائن تحتل.إضافة مؤشرات من شأنها أن تجعل الفضاء كائن تحتل importantant ، على الرغم من.......

هل يمكن أن تحاكي ما تريد من خلال خلق "حفرة" الكائن.حتى انك تستطيع استخدام الأدوية تجعل من نوع آمنة.على سبيل المثال:

public class Hole<T> {
   private T objectInHole;

   public void putInHole(T object) {
      this.objectInHole = object;
   }
   public T getOutOfHole() {
      return objectInHole;
   }

   public String toString() {
      return objectInHole.toString();
   }
   .....equals, hashCode, etc.
}


Hole<String> foo = new Hole<String)();
foo.putInHole(new String());
System.out.println(foo); //this prints nothing
thisWorks(foo);
System.out.println(foo);//this prints howdy

public static void thisWorks(Hole<String> foo){
   foo.putInHole("howdy");
}

السؤال كما طلبت لا يجب أن نفعل مع المارة القيمة ، ويمر من قبل المرجعية ، أو حقيقة أن سلاسل ثابتة (كما الآخرين وقد ذكر).

داخل الأسلوب في الواقع إنشاء متغير محلي (سأتصل أن أحد "localFoo") يشير إلى نفس المرجع الأصلي الخاص بك متغير ("originalFoo").

عند تعيين "مرحبا" إلى localFoo, لا تغيير فيها originalFoo لافتا.

إذا كنت فعلت شيئا مثل:

String a = "";
String b = a;
String b = "howdy"?

هل تتوقع:

System.out.print(a)

طباعة "مرحبا" ?فإنه يطبع "".

لا يمكنك تغيير ما originalFoo النقاط عن طريق تغيير ما localFoo نقاط.يمكنك تعديل كائن أن كل نقطة (إذا لم تكن ثابتة).على سبيل المثال ،

List foo = new ArrayList();
System.out.println(foo.size());//this prints 0

thisDoesntWork(foo);
System.out.println(foo.size());//this prints 1

public static void thisDoesntWork(List foo){   
    foo.add(new Object);
}

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

.

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

وتشمل بعض الفوائد: - لا حاجة لعمل نسخ الدفاعية. - Threadsafe - لا داعي للقلق حول تأمين فقط في حالة شخص آخر يريد تغيير الكائن

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

فإنه لا علاقة له مع ثبات.بالضبط نفس الشيء حدث بالنسبة قابلة للتغيير نوع مرجع.

إذا نحن من شأنه أن يجعل الخام C والمجمع القياس:

void Main()
{ 
     // stack memory address of message is 0x8001.  memory address of Hello is 0x0001.  
     string message = "Hello"; 
     // assembly equivalent of: message = "Hello";
     // [0x8001] = 0x0001

     // message's stack memory address
     printf("%d", &message); // 0x8001

     printf("%d", message); // memory pointed to of message(0x8001): 0x0001
     PassStringByValue(message); // pass the pointer pointed to of message.  0x0001, not 0x8001
     printf("%d", message); // memory pointed to of message(0x8001): 0x0001.  still the same

     // message's stack memory address doesn't change
     printf("%d", &message); // 0x8001
}

void PassStringByValue(string foo)
{
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)

    // foo(0x4001) contains the memory pointed to of message, 0x0001
    printf("%d", foo);  // 0x0001
    // World is in memory address 0x0002
    foo = "World";  // on foo's memory address (0x4001), change the memory it pointed to, 0x0002
    // assembly equivalent of: foo = "World":
    // [0x4001] = 0x0002

    // print the new memory pointed by foo
    printf("%d", foo); // 0x0002

    // Conclusion: Not in any way 0x8001 was involved in this function.  Hence you cannot change the Main's message value.
    // foo = "World"  is same as [0x4001] = 0x0002

}

void Main()
{
     // stack memory address of message is 0x8001.  memory address of Hello is 0x0001.  
     string message = "Hello"; 
     // assembly equivalent of: message = "Hello";
     // [0x8001] = 0x0001

     // message's stack memory address
     printf("%d", &message); // 0x8001

     printf("%d", message); // memory pointed to of message(0x8001): 0x0001
     PassStringByRef(ref message); // pass the stack memory address of message.  0x8001, not 0x0001
     printf("%d", message); // memory pointed to of message(0x8001): 0x0002. was changed

     // message's stack memory address doesn't change
     printf("%d", &message); // 0x8001
}


void PassStringByRef(ref string foo)
{
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)

    // foo(0x4001) contains the address of message(0x8001)
    printf("%d", foo);  // 0x8001
    // World is in memory address 0x0002
    foo = "World"; // on message's memory address (0x8001), change the memory it pointed to, 0x0002
    // assembly equivalent of: foo = "World":
    // [0x8001] = 0x0002;


    // print the new memory pointed to of message
    printf("%d", foo); // 0x0002

    // Conclusion: 0x8001 was involved in this function.  Hence you can change the Main's message value.
    // foo = "World"  is same as [0x8001] = 0x0002

}

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

وانهم يفضلون أن يكون لك تصميم عدد صحيح المقايض باستخدام كائنات من بينها توفير الدعم من الدرجة الأولى لرحيل-بالرجوع، الشيء نفسه بالنسبة لمندوب (فرخ الوز يشعر رديء مع مؤشر للعمل، وقال انه ليس الالزام هذه الوظيفة إلى كائنات) و التعداد.

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

هل أنت متأكد من أنه يطبع باطل؟ وأعتقد أنه سوف يكون مجرد فارغة كما عند تهيئة المتغير فو التي قدمتها سلسلة فارغة.

ووتكليف فو في طريقة thisDoesntWork لم يتم تغيير الإشارة المتغير فو المحددة في الصف حتى فو في System.out.println (فو) لا تزال تشير إلى كائن سلسلة فارغة القديم.

وديف، عليك أن يغفر لي (حسنا، أعتقد أنك لا "تضطر"، ولكن أنا أفضل أن فعلت)، ولكن هذا التفسير غير مقنع للغاية. المكاسب الأمن ضئيلة إلى حد ما منذ لمن يحتاج لتغيير القيمة من السلسلة سوف تجد طريقة للقيام بذلك مع بعض الحل القبيح. والسرعة ؟! أنت نفسك (صحيح تماما) يؤكدون أن الأعمال كلها مع + غير مكلفة للغاية.

وبقية يا رفاق، يرجى نفهم أن أحصل كيف يعمل، وأنا أسأل لماذا يعمل بهذه الطريقة ... يرجى التوقف عن شرح الفرق بين المنهجيات.

(وأنا بصراحة أنا لا أبحث عن أي نوع من المعركة هنا، راجع للشغل، وأنا لا أرى كيف كان هذا قرار عقلاني).

وAxelle

وماتي هل تعرف حقا الفرق بين عابرة من حيث القيمة وبالرجوع؟

في جافا حتى يتم تمرير إشارات من حيث القيمة. عند تمرير إشارة إلى كائن كنت تحصل على نسخة من مؤشر مرجع في المتغير الثاني. Tahts ماذا يمكن تغيير المتغير الثاني دون أن يؤثر ذلك أولا.

والسبب هو أنه يخلق متغير محلي داخل الأسلوب. ما يمكن أن يكون وسيلة سهلة (الذي أنا متأكد من أن العمل) ستكون كما يلي:

String foo = new String();    

thisDoesntWork(foo);    
System.out.println(foo); //this prints nothing

public static void thisDoesntWork(String foo) {    
   this.foo = foo; //this makes the local variable go to the main variable    
   foo = "howdy";    
}

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

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

class Test {

    public static void main(String[] args) {
        String [] fooArray = new String[1];
        fooArray[0] = new String("foo");

        System.out.println("main: " + fooArray[0]);
        thisWorks(fooArray);
        System.out.println("main: " + fooArray[0]);
    }

    public static void thisWorks(String [] foo){
        System.out.println("thisWorks: " + foo[0]);
        foo[0] = "howdy";
        System.out.println("thisWorks: " + foo[0]);
    }
}

والنتائج في الإخراج التالي:

main: foo
thisWorks: foo
thisWorks: howdy
main: howdy
يتم تمرير الحجج

والمرجعي كتابة كما يشير إلى الأشياء نفسها (وليس كل ما يشير إلى غير ذلك من <م> متغيرات التي تشير إلى الكائنات). يمكنك استدعاء الأساليب على الكائن الذي تم تمريره. ومع ذلك، في نموذج التعليمات البرمجية الخاصة بك:

public static void thisDoesntWork(String foo){
    foo = "howdy";
}

ويتم تخزين سوى إشارة إلى "howdy" سلسلة في متغير غير المحلي إلى الأسلوب. تم تهيئة هذا المتغير المحلي (foo) إلى قيمة foo المتصل عندما تم استدعاء الأسلوب، ولكن لا يوجد لديه إشارة إلى المتغير المتصل نفسه. بعد التهيئة:

caller     data     method
------    ------    ------
(foo) -->   ""   <-- (foo)

وبعد الاحالة في أسلوب الخاص بك:

caller     data     method
------    ------    ------
(foo) -->   ""
          "hello" <-- (foo)

لديك قضايا أخرى هناك: حالات String هي ثابتة (حسب التصميم للأمن) لذلك لا يمكنك تعديل قيمته

.

إذا كنت تريد حقا طريقة لتوفير قيمة أولية لسلسلة (أو في <م> أي الوقت في حياتها، لهذه المسألة)، وبعد ذلك طريقتك <م> عودة قيمة String التي تقوم بتعيينها إلى المتغير المتصل عند نقطة المكالمة. شيء من هذا القبيل، على سبيل المثال:

String foo = thisWorks();
System.out.println(foo);//this prints the value assigned to foo in initialization 

public static String thisWorks(){
    return "howdy";
}

والذهاب تفعل تعليمي كبير حقا على شموس الموقع.

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

وتحديد نطاق مهم خصوصا وأنت لا تريد كل شيء لتكون مرئية على كل شيء آخر في النظام الخاص بك.

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