java مشكلة - هل يتم تنفيذ حظر نهائي دائمًا في جافا؟




حل application (25)

بالنظر إلى هذا الكود ، هل يمكنني أن أكون متأكداً تمامًا من أن الجزء finally تنفيذه دائمًا ، بغض النظر عن something() ما something() ؟

try {  
    something();  
    return success;  
}  
catch (Exception e) {   
    return failure;  
}  
finally {  
    System.out.println("i don't know if this will get printed out.");
}

Answers

لأنه سيتم دوماً استدعاء كتلة أخيراً إلا إذا قمت باستدعاء System.exit() (أو تعطل مؤشر الترابط).


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

public class TestTryCatchFinally {
    static int x = 0;

    public static void main(String[] args){
        System.out.println(f1() );
        System.out.println(f2() );
    }

    public static int f1(){
        try{
            x = 1;
            return x;
        }finally{
            x = 2;
        }
    }

    public static int f2(){
        return x;
    }
}

نعم ، في finally سيتم استدعاؤها بعد تنفيذ محاولات التعليمة البرمجية أو المحاولة.

الأوقات الوحيدة التي لن finally هي:

  1. إذا قمت باستدعاء System.exit() ؛
  2. إذا تعطل JVM أولاً ؛
  3. إذا وصلت JVM إلى حلقة لا نهائية (أو بعض العبارات الأخرى غير القابلة للانقطاع ، غير المنتهية) في كتلة try أو catch ؛
  4. إذا قام نظام التشغيل بإنهاء عملية JVM بالقوة ؛ على سبيل المثال "kill -9" على UNIX.
  5. إذا مات النظام المضيف ؛ على سبيل المثال انقطاع التيار الكهربائي ، خطأ في الأجهزة ، الذعر OS ، إلى آخره.
  6. إذا كان سيتم تنفيذ الحظر في النهاية من قبل الخيط الخفي وجميع الخيط غير الخفي آخر الخروج قبل أن يسمى أخيرا.

من الطرق المنطقية للتفكير في هذا:

  1. يجب تنفيذ الشفرة الموضوعة في كتلة أخيرة ما يحدث داخل كتلة المحاولة
  2. لذا إذا حاولت الكود الموجود في مجموعة المحاولة إرجاع قيمة أو رمي استثناء ، فسيتم وضع العنصر "على الرف" حتى يمكن تنفيذ الحظر أخيرًا
  3. لأن الكود الموجود في المربع النهائي له (أولوية) أولوية عالية يمكنه العودة أو التخلص من أي شيء يحبه. في هذه الحالة يتم تجاهل أي شيء ترك على "الرف".
  4. الاستثناء الوحيد لهذا هو أنه إذا تم إيقاف VM بالكامل أثناء كتلة المحاولة ، على سبيل المثال "System.exit"

كود المثال:

public static void main(String[] args) {
    System.out.println(Test.test());
}

public static int test() {
    try {
        return 0;
    }
    finally {
        System.out.println("finally trumps return.");
    }
}

انتاج:

finally trumps return. 
0

ها هي الكلمات الرسمية من مواصفات لغة جافا.

14.20.2. إعدام المحاولة وأخيرًا

يتم تنفيذ عبارة try مع كتلة في finally عن طريق تنفيذ كتلة try أولاً. ثم هناك خيار:

  • إذا اكتمل تنفيذ كتلة try طبيعي ، [...]
  • إذا اكتمل تنفيذ كتلة try فجأة بسبب throw القيمة V ، [...]
  • إذا اكتمل تنفيذ كتلة try فجأة لأي سبب آخر R ، فسيتم تنفيذ الحظر finally . ثم هناك خيار:
    • إذا اكتمل الشكل النهائي بشكل طبيعي ، try عبارة try فجأة للسبب R.
    • في حالة اكتمال كتلة finally فجأة للسبب S ، فإن عبارة try تكتمل فجأة للسبب S ( والسبب R يتم تجاهله ).

مواصفات return الواقع تجعل هذا صريحًا:

JLS 14.17 بيان الإرجاع

ReturnStatement:
     return Expression(opt) ;

يحاول بيان return بدون Expression نقل التحكم إلى invoker من الأسلوب أو منشئ يحتوي عليه.

يحاول بيان return مع Expression نقل التحكم إلى منشئ الأسلوب الذي يحتوي عليه؛ تصبح قيمة Expression قيمة استدعاء الأسلوب.

تشير الأوصاف السابقة إلى " محاولات نقل التحكم " بدلاً من " التحكّم في التحكّم " فقط لأنه إذا كانت هناك أية عبارات try ضمن الأسلوب أو المُنشئ الذي تحتوي كتله المحولة على بيان return ، فسيتم تنفيذ أي عبارات أخيرة من عبارات try هذه ، النظام ، الأعمق إلى الأبعد ، قبل أن يتم نقل السيطرة إلى منشئ الأسلوب أو منشئ. يمكن للانهيار المفاجئ لبند finally أن يعطل نقل السيطرة الذي بدأه بيان return .


هذه هي الفكرة الكاملة لكتلة أخيرة. فهو يتيح لك التأكد من قيامك بالتنظيفات التي قد يتم تخطيها لأنك تعود ، من بين أمور أخرى ، بالطبع.

وأخيراً يتم استدعاؤه بغض النظر عما يحدث في كتلة المحاولة ( إلا إذا قمت باستدعاء System.exit(int) أو تم تشغيل Java Virtual Machine لسبب آخر).


يتم دائمًا تنفيذ الحظر أخيرًا ما لم يكن هناك إنهاء برنامج غير طبيعي ، إما ناتج عن عطل JVM أو من استدعاء System.exit(0) .

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


وأخيراً يتم دائماً تنفيذ ما لم يكن هناك إنهاء برنامج غير طبيعي (مثل استدعاء System.exit (0) ..). لذلك ، سيتم طباعة sysout الخاص بك


خذ بعين الاعتبار البرنامج التالي:

public class someTest {

    private static StringBuilder sb = new StringBuilder();

    public static void main(String args[]) {

        System.out.println(someString());
        System.out.println("---AGAIN---");
        System.out.println(someString());
    }

    private static String someString() {

        try {
            sb.append("-abc-");
            return sb.toString();

        } finally {
            sb.append("xyz");
        }
    }
}

اعتبارًا من Java 1.8.162 ، فإن كتلة الكود أعلاه تعطي الناتج التالي:

-abc-
---AGAIN---
-abc-xyz-abc-

هذا يعني أن استخدام finally لتحرير الكائنات هو ممارسة جيدة مثل الكود التالي:

private static String someString() {

    StringBuilder sb = new StringBuilder();

    try {
        sb.append("abc");
        return sb.toString();

    } finally {
        sb = null;
    }
}

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


أيضا ، على الرغم من أنه من الممارسات السيئة ، إذا كان هناك بيان عودة داخل الكتلة في النهاية ، فإنه سوف تتفوق على أي عائد آخر من الكتلة العادية. بمعنى ، سيتم إرجاع كتلة التالية false:

try { return true; } finally { return false; }

نفس الشيء مع رمي الاستثناءات من كتلة في النهاية.


الجواب بسيط نعم .

إدخال:

try{
    int divideByZeroException = 5 / 0;
} catch (Exception e){
    System.out.println("catch");
    return;    // also tried with break; in switch-case, got same output
} finally {
    System.out.println("finally");
}

انتاج:

catch
finally

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


نعم سوف يطلق عليه. هذا هو بيت القصيد من الكلمة أخيرا. إذا كان القفز من كتلة المحاولة / الالتقاط يمكن أن يتخطى الحظر النهائي ، فهو نفس وضع System.out.println خارج المحاولة / المصيد.


باختصار ، في وثائق جافا الرسمية (اضغط here ) ، هو مكتوب أن -

إذا تم إنهاء JVM أثناء تنفيذ محاولة أو رمز الالتقاط ، فقد لا يتم تنفيذ الحظر النهائي. وبالمثل ، إذا تمت مقاطعة أو تنفيذ سلسلة تنفيذ المحاولة أو رمز الالتقاط ، فقد لا يتم تنفيذ الحظر النهائي حتى إذا استمر التطبيق ككل.


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

public static void main(String[] args) {
    System.out.println(Test.test());
}

public static int printX() {
    System.out.println("X");
    return 0;
}

public static int test() {
    try {
        return printX();
    }
    finally {
        System.out.println("finally trumps return... sort of");
    }
}

انتاج:

X
finally trumps return... sort of
0

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


نعم. بغض النظر عما يحدث في المحاولة الخاصة بك أو كتلة الصيد ما لم تتحقق System.exit () أو JVM. إذا كان هناك أي بيان إرجاع في الكتلة (الكتل) ، فسيتم تنفيذها أخيراً قبل بيان الإرجاع.


جربت المثال أعلاه بتعديل طفيف

public static void main(final String[] args) {
    System.out.println(test());
}

public static int test() {
    int i = 0;
    try {
        i = 2;
        return i;
    } finally {
        i = 12;
        System.out.println("finally trumps return.");
    }
}

مخرجات الكود المذكورة أعلاه:

أخيرا ينسخ العودة.
2

هذا لأن عند return i; يتم تنفيذ i له قيمة 2. بعد ذلك يتم تنفيذ كتلة finally حيث يتم تعيين 12 إلى i ثم يتم تنفيذ System.out .

بعد تنفيذ حظر finally ، يُرجع مربع try 2 ، بدلاً من إرجاع 12 ، لأن بيان الإرجاع هذا لم يتم تنفيذه مرة أخرى.

إذا قمت بتصحيح هذه التعليمة البرمجية في Eclipse ، فستحصل على شعور بأنه بعد تنفيذ System.out في finally يتم تنفيذ بيان return الخاص بـ block try مرة أخرى. ولكن هذا ليس هو الحال. ببساطة إرجاع القيمة 2.


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


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



بالإضافة إلى الردود الأخرى ، من المهم أن نشير إلى أن "أخيرًا" لديه الحق في تجاوز أي قيمة استثناء / مرتدة من خلال كتلة try..catch. على سبيل المثال ، تقوم التعليمة البرمجية التالية بإرجاع 12:

public static int getMonthsInYear() {
    try {
        return 10;
    }
    finally {
        return 12;
    }
}

وبالمثل ، لا تقوم الطريقة التالية بطرح استثناء:

public static int getMonthsInYear() {
    try {
        throw new RuntimeException();
    }
    finally {
        return 12;
    }
}

بينما تقوم الطريقة التالية بإلقاء:

public static int getMonthsInYear() {
    try {
        return 12;          
    }
    finally {
        throw new RuntimeException();
    }
}

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

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

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





java return try-catch-finally