هل يتم دائمًا تنفيذ الكتلة النهائية في Java؟
-
09-06-2019 - |
سؤال
وبالنظر إلى هذا الرمز، هل يمكنني أن أكون كذلك متأكد تماما أن finally
يتم تنفيذ الكتلة دائمًا، مهما كان الأمر something()
يكون؟
try {
something();
return success;
}
catch (Exception e) {
return failure;
}
finally {
System.out.println("I don't know if this will get printed out");
}
المحلول
نعم، finally
سيتم استدعاؤه بعد تنفيذ try
أو catch
كتل التعليمات البرمجية.
الأوقات الوحيدة finally
لن يتم استدعاؤها هي:
- إذا استدعيت
System.exit()
- إذا تعطل JVM أولاً
- إذا وصل JVM إلى حلقة لا نهائية (أو بعض العبارات الأخرى غير القابلة للمقاطعة وغير المنتهية) في
try
أوcatch
حاجز - إذا أنهى نظام التشغيل عملية JVM بالقوة؛على سبيل المثال،
kill -9 <pid>
على يونيكس - إذا مات النظام المضيف؛على سبيل المثال، انقطاع التيار الكهربائي، خطأ في الأجهزة، الذعر في نظام التشغيل، وما إلى ذلك
- إذا
finally
سيتم تنفيذ الكتلة بواسطة خيط خفي وستخرج جميع سلاسل الرسائل الأخرى غير الخفي من قبلfinally
يسمى
نصائح أخرى
رمز المثال:
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
أيضًا، على الرغم من أنها ممارسة سيئة، إذا كان هناك بيان إرجاع داخل الكتلة النهائية، فسوف يتفوق على أي إرجاع آخر من الكتلة العادية.أي أن الكتلة التالية ستعيد خطأ:
try { return true; } finally { return false; }
نفس الشيء مع رمي الاستثناءات من الكتلة النهائية.
إليك الكلمات الرسمية من مواصفات لغة Java.
14.20.2.تنفيذ المحاولة النهائية ومحاولة الالتقاط أخيرًا
أ
try
بيان مع أfinally
يتم تنفيذ الكتلة عن طريق تنفيذ الأمر أولاًtry
حاجز.ثم هناك خيار:
- إذا كان تنفيذ
try
اكتمال الكتلة بشكل طبيعي، [...]- إذا كان تنفيذ
try
اكتمال الكتلة فجأة بسبب أthrow
ذات قيمة الخامس, [...]- إذا كان تنفيذ
try
اكتمال الكتلة فجأة لأي سبب آخر ر, ، ثمfinally
يتم تنفيذ الكتلة.ثم هناك خيار:
- إذا اكتملت الكتلة النهائية بشكل طبيعي، فسيتم
try
يكتمل البيان فجأة لسبب ما ر.- إذا
finally
اكتمال الكتلة فجأة لسبب ما س, ، ثمtry
يكتمل البيان فجأة لسبب ما س (والسبب ر يتم التخلص منها).
المواصفات ل return
في الواقع يوضح هذا:
ReturnStatement: return Expression(opt) ;
أ
return
بيان مع لاExpression
محاولات لنقل التحكم إلى مستحضر الطريقة أو المُنشئ الذي يحتوي عليها.أ
return
بيان معExpression
محاولات لنقل التحكم إلى مستحضر الطريقة التي تحتوي عليه؛قيمةExpression
تصبح قيمة استدعاء الطريقة.تقول الأوصاف السابقة "محاولات لنقل السيطرة" افضل من مجرد "التحكم في التحويلات"لأنه إذا كان هناك أي
try
البيانات داخل الطريقة أو المنشئ الذيtry
تحتوي الكتل علىreturn
بيان، ثم أيfinally
أحكام تلكtry
سيتم تنفيذ العبارات بالترتيب من الأعمق إلى الأبعد، قبل أن يتم نقل التحكم إلى من يستدعي الطريقة أو المنشئ.الانتهاء المفاجئ من أfinally
يمكن أن يؤدي الشرط إلى تعطيل نقل السيطرة الذي بدأه أreturn
إفادة.
بالإضافة إلى الاستجابات الأخرى، من المهم الإشارة إلى أن كلمة "أخيرًا" لها الحق في تجاوز أي استثناء/قيمة تم إرجاعها بواسطة كتلة 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();
}
}
لقد جربت المثال أعلاه مع تعديل طفيف-
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
بيان try
يتم تنفيذ الكتلة مرة أخرى.ولكن هذا ليس هو الحال.إنها ببساطة ترجع القيمة 2.
وهنا تفصيل ل إجابة كيفن.من المهم معرفة أن التعبير المراد إرجاعه يتم تقييمه من قبل 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(int)
أو يتم تشغيل Java Virtual Machine لسبب آخر).
الطريقة المنطقية للتفكير في هذا هي:
- يجب تنفيذ التعليمات البرمجية الموضوعة في الكتلة النهائية مهما حدث داخل كتلة المحاولة
- لذا، إذا حاول الكود الموجود في كتلة المحاولة إرجاع قيمة أو طرح استثناء، فسيتم وضع العنصر "على الرف" حتى يمكن تنفيذ الكتلة النهائية
- نظرًا لأن الكود الموجود في الكتلة الأخيرة له (بحكم التعريف) أولوية عالية، فيمكنه إرجاع أو رمي ما يحلو له.وفي هذه الحالة يتم التخلص من أي شيء متبقي على الرف.
- الاستثناء الوحيد لذلك هو إذا تم إيقاف تشغيل الجهاز الافتراضي بالكامل أثناء كتلة المحاولة، على سبيل المثال.بواسطة "System.exit"
كما أن العودة أخيرًا سوف تتخلص من أي استثناء. http://jamesjava.blogspot.com/2006/03/dont-return-in-finally-clause.html
أخيرًا يتم تنفيذه دائمًا ما لم يكن هناك إنهاء غير طبيعي للبرنامج (مثل استدعاء System.exit(0)..).لذلك، ستتم طباعة نظامك
يتم تنفيذ الكتلة الأخيرة دائمًا ما لم يكن هناك إنهاء غير طبيعي للبرنامج، إما نتيجة لتعطل JVM أو من استدعاء إلى System.exit(0)
.
علاوة على ذلك، فإن أي قيمة يتم إرجاعها من داخل الكتلة النهائية ستتجاوز القيمة التي تم إرجاعها قبل تنفيذ الكتلة الأخيرة، لذا كن حذرًا من التحقق من جميع نقاط الخروج عند استخدام المحاولة أخيرًا.
لا ، ليست دائمًا حالة استثناء واحدة هي // system.exit (0) ؛قبل أن تمنع الكتلة الأخيرة تنفيذها أخيرًا.
class A {
public static void main(String args[]){
DataInputStream cin = new DataInputStream(System.in);
try{
int i=Integer.parseInt(cin.readLine());
}catch(ArithmeticException e){
}catch(Exception e){
System.exit(0);//Program terminates before executing finally block
}finally{
System.out.println("Won't be executed");
System.out.println("No error");
}
}
}
أخيرًا يتم تشغيله دائمًا، وهذا هو بيت القصيد، فمجرد ظهوره في الكود بعد الإرجاع لا يعني أن هذه هي الطريقة التي يتم بها تنفيذه.يتحمل وقت تشغيل Java مسؤولية تشغيل هذا الرمز عند الخروج من ملف try
حاجز.
على سبيل المثال إذا كان لديك ما يلي:
int foo() {
try {
return 42;
}
finally {
System.out.println("done");
}
}
سيولد وقت التشغيل شيئًا مثل هذا:
int foo() {
int ret = 42;
System.out.println("done");
return 42;
}
إذا تم طرح استثناء لم يتم اكتشافه finally
سيتم تشغيل الكتلة وسيستمر الاستثناء في الانتشار.
وذلك لأنك قمت بتعيين قيمة i على أنها 12، ولكنك لم تقم بإرجاع قيمة i إلى الدالة.الكود الصحيح هو كما يلي:
public static int test() {
int i = 0;
try {
return i;
} finally {
i = 12;
System.out.println("finally trumps return.");
return i;
}
}
لأنه سيتم دائمًا استدعاء الكتلة النهائية ما لم تتصل بها System.exit()
(أو تعطل الخيط).
الجواب بسيط نعم.
مدخل:
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 خارج كتلة المحاولة/الالتقاط.
بإيجاز، في وثائق جافا الرسمية (انقر فوق هنا)، مكتوب أن -
إذا تم خروج JVM أثناء تنفيذ المحاولة أو الرمز ، فقد لا يتم تنفيذ الكتلة أخيرًا.وبالمثل ، إذا تم مقاطعة أو قتل مؤشر الترابط الذي ينفذ رمز التجربة أو القتل ، فقد لا يتم تنفيذ الكتلة أخيرًا على الرغم من أن التطبيق ككل يستمر.
نعم، أخيرًا يتم تنفيذ الحظر دائمًا.يستخدم معظم المطورين هذه الكتلة لإغلاق اتصال قاعدة البيانات وكائن مجموعة النتائج وكائن البيان ويستخدم أيضًا في إسبات Java للتراجع عن المعاملة.
نعم.بغض النظر عما يحدث في كتلة المحاولة أو الالتقاط ما لم يتم استدعاء System.exit() أو تعطل JVM.إذا كان هناك أي بيان إرجاع في الكتلة (الكتل)، فسيتم تنفيذه أخيرًا قبل بيان الإرجاع هذا.
نعم.الحالة الوحيدة التي لن تكون هي خروج JVM أو تعطله
خذ بعين الاعتبار البرنامج التالي:
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());
System.out.println("---PRINT THE RESULT---");
System.out.println(sb.toString());
}
private static String someString() {
try {
sb.append("-abc-");
return sb.toString();
} finally {
sb.append("xyz");
}
}
}
اعتبارًا من Java 1.8.162، تعطي كتلة التعليمات البرمجية أعلاه المخرجات التالية:
-abc-
---AGAIN---
-abc-xyz-abc-
---PRINT THE RESULT---
-abc-xyz-abc-xyz
وهذا يعني أن استخدام finally
يعد تحرير الكائنات ممارسة جيدة مثل الكود التالي:
private static String someString() {
StringBuilder sb = new StringBuilder();
try {
sb.append("abc");
return sb.toString();
} finally {
sb = null; // Just an example, but you can close streams or DB connections this way.
}
}
وهذا صحيح بالفعل في أي لغة... وأخيرًا سيتم تنفيذه دائمًا قبل عبارة الإرجاع، بغض النظر عن مكان وجود هذا الإرجاع في نص الطريقة.إذا لم يكن الأمر كذلك، فلن يكون للكتلة النهائية معنى كبير.
finally
سيتم التنفيذ وهذا أمر مؤكد.
finally
لن يتم التنفيذ في الحالات التالية:
حالة 1 :
عندما تقوم بالتنفيذ System.exit()
.
الحالة 2 :
عندما يتعطل JVM / Thread.
الحالة 3 :
عندما يتم إيقاف التنفيذ يدويًا.
إذا لم تتعامل مع الاستثناء، فقبل إنهاء البرنامج، يقوم JVM بتنفيذ الحظر أخيرًا.لن يتم تنفيذه إلا في حالة فشل التنفيذ العادي للبرنامج، مما يعني إنهاء البرنامج للأسباب التالية..
عن طريق التسبب في خطأ فادح يؤدي إلى إحباط العملية.
إنهاء البرنامج بسبب تلف الذاكرة.
عن طريق استدعاء System.exit()
إذا ذهب البرنامج إلى حلقة اللانهاية.
نعم لأن لا يوجد بيان السيطرة يمكن أن تمنع finally
من إعدامه.
فيما يلي مثال مرجعي، حيث سيتم تنفيذ جميع كتل التعليمات البرمجية:
| x | Current result | Code
|---|----------------|------ - - -
| | |
| | | public static int finallyTest() {
| 3 | | int x = 3;
| | | try {
| | | try {
| 4 | | x++;
| 4 | return 4 | return x;
| | | } finally {
| 3 | | x--;
| 3 | throw | throw new RuntimeException("Ahh!");
| | | }
| | | } catch (RuntimeException e) {
| 4 | return 4 | return ++x;
| | | } finally {
| 3 | | x--;
| | | }
| | | }
| | |
|---|----------------|------ - - -
| | Result: 4 |
في البديل أدناه ، return x;
سيتم تخطي.النتيجة لا تزال 4
:
public static int finallyTest() {
int x = 3;
try {
try {
x++;
if (true) throw new RuntimeException("Ahh!");
return x; // skipped
} finally {
x--;
}
} catch (RuntimeException e) {
return ++x;
} finally {
x--;
}
}
المراجع، بطبيعة الحال، تتبع حالتها.يُرجع هذا المثال مرجعًا بـ value = 4
:
static class IntRef { public int value; }
public static IntRef finallyTest() {
IntRef x = new IntRef();
x.value = 3;
try {
return x;
} finally {
x.value++; // will be tracked even after return
}
}
try
- catch
- finally
هي الكلمات الأساسية لاستخدام حالة معالجة الاستثناء.
كتفسير عادي
try {
//code statements
//exception thrown here
//lines not reached if exception thrown
} catch (Exception e) {
//lines reached only when exception is thrown
} finally {
// always executed when the try block is exited
//independent of an exception thrown or not
}
الكتلة النهائية تمنع التنفيذ ...
- عندما قمت بالاتصال
System.exit(0);
- إذا خرج JVM.
- أخطاء في JVM
اضافة الى إجابة @vibhash حيث لا توجد إجابة أخرى تشرح ما يحدث في حالة وجود كائن قابل للتغيير مثل الموجود أدناه.
public static void main(String[] args) {
System.out.println(test().toString());
}
public static StringBuffer test() {
StringBuffer s = new StringBuffer();
try {
s.append("sb");
return s;
} finally {
s.append("updated ");
}
}
سوف الإخراج
sbupdated
لقد جربت هذا ، فهو واحد مترابط.
class Test {
public static void main(String args[]) throws Exception {
Object obj = new Object();
try {
synchronized (obj) {
obj.wait();
System.out.println("after wait()");
}
} catch (Exception e) {
} finally {
System.out.println("finally");
}
}
}
سيكون الخيط الرئيسي في حالة انتظار إلى الأبد، وبالتالي لن يتم استدعاؤه أبدًا،
لذلك لن يقوم إخراج وحدة التحكم بطباعة السلسلة: after wait()
أو finally
متفق عليه مع @Stephen C، المثال أعلاه هو أحد الحالات الثالثة المذكورة هنا:
إضافة المزيد من إمكانيات الحلقة اللانهائية في الكود التالي:
// import java.util.concurrent.Semaphore;
class Test {
public static void main(String[] args) {
try {
// Thread.sleep(Long.MAX_VALUE);
// Thread.currentThread().join();
// new Semaphore(0).acquire();
// while (true){}
System.out.println("after sleep join semaphore exit infinite while loop");
} catch (Exception e) {
} finally {
System.out.println("finally");
}
}
}
الحالة 2:إذا تعطل JVM أولاً
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class Test {
public static void main(String args[]) {
try {
unsafeMethod();
// Runtime.getRuntime().halt(123);
System.out.println("After Jvm Crash!");
} catch (Exception e) {
} finally {
System.out.println("finally");
}
}
private static void unsafeMethod() throws NoSuchFieldException, IllegalAccessException {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
unsafe.putAddress(0, 0);
}
}
المرجع: كيف يمكنك تعطل JVM؟
الحالة 6:إذا تم تنفيذ الحظر أخيرًا بواسطة مؤشر ترابط خفي وستخرج جميع سلاسل الرسائل الأخرى غير الخفي قبل أن يتم استدعاؤها أخيرًا.
class Test {
public static void main(String args[]) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
printThreads("Daemon Thread printing");
// just to ensure this thread will live longer than main thread
Thread.sleep(10000);
} catch (Exception e) {
} finally {
System.out.println("finally");
}
}
};
Thread daemonThread = new Thread(runnable);
daemonThread.setDaemon(Boolean.TRUE);
daemonThread.setName("My Daemon Thread");
daemonThread.start();
printThreads("main Thread Printing");
}
private static synchronized void printThreads(String str) {
System.out.println(str);
int threadCount = 0;
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
for (Thread t : threadSet) {
if (t.getThreadGroup() == Thread.currentThread().getThreadGroup()) {
System.out.println("Thread :" + t + ":" + "state:" + t.getState());
++threadCount;
}
}
System.out.println("Thread count started by Main thread:" + threadCount);
System.out.println("-------------------------------------------------");
}
}
انتاج:هذا لا يطبع كلمة "أخيرًا" مما يعني عدم تنفيذ "الحظر أخيرًا" في "خيط البرنامج الخفي".
main Thread Printing Thread :Thread[My Daemon Thread,5,main]:state:BLOCKED Thread :Thread[main,5,main]:state:RUNNABLE Thread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLE Thread count started by Main thread:3 ------------------------------------------------- Daemon Thread printing Thread :Thread[My Daemon Thread,5,main]:state:RUNNABLE Thread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLE Thread count started by Main thread:2 ------------------------------------------------- Process finished with exit code 0