1. ประเภทของข้อยกเว้น

ประเภทของข้อยกเว้น

ข้อยกเว้นทั้งหมดแบ่งออกเป็น 4 ประเภทซึ่งเป็นคลาสที่สืบทอดซึ่งกันและกัน

Throwableระดับ

คลาสพื้นฐานสำหรับข้อยกเว้นทั้งหมดคือThrowableคลาส คลาสThrowableประกอบด้วยโค้ดที่เขียน call stack ปัจจุบัน (stack trace ของเมธอดปัจจุบัน) ไปยังอาร์เรย์ เราจะเรียนรู้ว่าการติดตามสแต็กคืออะไรในภายหลัง

ตัว ดำเนิน การโยนสามารถยอมรับวัตถุที่มาจากThrowableชั้นเรียน เท่านั้น และแม้ว่าในทางทฤษฎีแล้วคุณสามารถเขียนโค้ดได้ แต่throw new Throwable();มักจะไม่มีใครทำสิ่งนี้ จุดประสงค์หลักของThrowableคลาสคือการมีคลาสพาเรนต์เดียวสำหรับข้อยกเว้นทั้งหมด

Errorระดับ

คลาสยกเว้นถัดไปคือErrorคลาสซึ่งสืบทอดThrowableคลาส โดยตรง เครื่อง Java สร้างอ็อบเจกต์ของคลาสError(และคลาสรอง) เมื่อเกิดปัญหาร้ายแรง ตัวอย่างเช่น ฮาร์ดแวร์ทำงานผิดปกติ หน่วยความจำไม่เพียงพอ เป็นต้น

โดยปกติแล้ว ในฐานะโปรแกรมเมอร์คุณไม่สามารถทำอะไรได้ในสถานการณ์ที่เกิดข้อผิดพลาด (ซึ่งErrorควรจะเกิดขึ้น) ในโปรแกรม ข้อผิดพลาดเหล่านี้ร้ายแรงเกินไป สิ่งที่คุณทำได้คือแจ้งให้ผู้ใช้ทราบว่าโปรแกรมขัดข้อง และ/หรือเขียนข้อมูลที่ทราบทั้งหมดเกี่ยวกับข้อผิดพลาดลงในบันทึกของโปรแกรม

Exceptionระดับ

และ คลาสสำหรับข้อผิดพลาดทั่วไปที่เกิด Exceptionขึ้นRuntimeExceptionในการทำงานของเมธอดต่างๆ เป้าหมายของการโยนข้อยกเว้นแต่ละครั้งคือการถูกcatchบล็อกที่รู้วิธีจัดการอย่างถูกต้อง

เมื่อเมธอดไม่สามารถทำงานให้เสร็จได้ด้วยเหตุผลบางประการ เมธอดควรแจ้งเมธอดการโทรทันทีโดยส่งข้อยกเว้นประเภทที่เหมาะสม

กล่าวอีกนัยหนึ่ง ถ้าตัวแปรเท่ากับnullเมธอดจะโยนNullPointerExceptiona หากมีการส่งผ่านอาร์กิวเมนต์ที่ไม่ถูกต้องไปยังเมธอด มันจะโยนไฟล์InvalidArgumentException. หากเมธอดนั้นหารด้วยศูนย์โดยไม่ตั้งใจ มันจะโยนไฟล์ArithmeticException.

RuntimeExceptionระดับ

RuntimeExceptionsเป็นส่วนย่อยExceptionsของ เราอาจพูดได้ด้วยซ้ำว่าRuntimeExceptionเป็นข้อยกเว้นธรรมดารุ่นเล็ก ( Exception) — มีการกำหนดข้อกำหนดและข้อจำกัดน้อยลงสำหรับข้อยกเว้นดังกล่าว

คุณจะได้เรียนรู้ความแตกต่างระหว่างExceptionและRuntimeExceptionในภายหลัง


2. Throws: ตรวจสอบข้อยกเว้น

โยน: ตรวจสอบข้อยกเว้น

ข้อยกเว้น Java ทั้งหมดแบ่งออกเป็น 2 ประเภท: ทำเครื่องหมายและไม่ได้ทำเครื่องหมาย

ข้อยกเว้นทั้งหมดที่สืบทอดRuntimeExceptionหรือErrorถือเป็นข้อยกเว้นที่ไม่ได้ตรวจสอบ ข้อยกเว้นอื่นๆ ทั้งหมดได้ รับการตรวจสอบ แล้ว

สำคัญ!

20 ปีหลังจากเปิดตัวข้อยกเว้นที่ตรวจสอบแล้ว โปรแกรมเมอร์ Java เกือบทุกคนคิดว่าสิ่งนี้เป็นข้อผิดพลาด ในเฟรมเวิร์กสมัยใหม่ยอดนิยม 95% ของข้อยกเว้นทั้งหมดไม่ถูกเลือก ภาษา C# ซึ่งเกือบจะคัดลอก Java ทุกประการ ไม่ได้เพิ่มข้อยกเว้นที่ตรวจสอบแล้ว

อะไรคือข้อแตกต่างที่สำคัญระหว่างข้อยกเว้นที่ตรวจสอบและไม่ได้ตรวจสอบ

มีข้อกำหนดเพิ่มเติมสำหรับข้อยกเว้นที่ตรวจสอบ พูดโดยคร่าว ๆ คือ:

ข้อกำหนด 1

หากเมธอดแสดงข้อยกเว้นที่ตรวจสอบจะ ต้อง ระบุประเภทของข้อยกเว้นในลายเซ็น ด้วยวิธีนี้ ทุกเมธอดที่เรียกใช้จะทราบว่า "ข้อยกเว้นที่มีความหมาย" นี้อาจเกิดขึ้นในนั้น

ระบุ ข้อยกเว้น ที่ตรวจสอบหลังพารามิเตอร์เมธอดหลังthrowsคีย์เวิร์ด (อย่าใช้throwคีย์เวิร์ดโดยไม่ได้ตั้งใจ) ดูเหมือนว่า:

type method (parameters) throws exception

ตัวอย่าง:

ตรวจสอบข้อยกเว้น ข้อยกเว้นที่ไม่ได้ตรวจสอบ
public void calculate(int n) throws Exception
{
   if (n == 0)
      throw new Exception("n is null!");
}
public void calculate(n)
{
   if (n == 0)
      throw new RuntimeException("n is null!");
}

ในตัวอย่างด้านขวา โค้ดของเราแสดง ข้อยกเว้น ที่ไม่ได้ตรวจสอบ — ไม่จำเป็นต้องดำเนินการใดๆ เพิ่มเติม ในตัวอย่างทางด้านซ้าย เมธอดจะส่ง ข้อยกเว้น ที่ตรวจสอบดังนั้นthrowsคีย์เวิร์ดจึงถูกเพิ่มลงในลายเซ็นเมธอดพร้อมกับประเภทของข้อยกเว้น

หากเมธอดคาดว่าจะส่ง ข้อยกเว้น ที่ตรวจสอบ หลายรายการ จะต้องระบุทั้งหมดหลังthrowsคำสำคัญ โดยคั่นด้วยเครื่องหมายจุลภาค ลำดับไม่สำคัญ ตัวอย่าง:

public void calculate(int n) throws Exception, IOException
{
   if (n == 0)
      throw new Exception("n is null!");
   if (n == 1)
      throw new IOException("n is 1");
}

ข้อกำหนด 2

หากคุณเรียกใช้เมธอดที่มีการตรวจสอบข้อยกเว้นในลายเซ็นคุณจะไม่สามารถเพิกเฉยต่อข้อเท็จจริงที่ว่าเมธอดนั้นโยนทิ้งได้

คุณต้องจับข้อยกเว้นดังกล่าวทั้งหมดโดยเพิ่มcatchบล็อกสำหรับแต่ละรายการ หรือโดยการเพิ่มในthrowsส่วนคำสั่งสำหรับวิธีการของคุณ

เหมือนกับว่าเรากำลังพูดว่า " ข้อยกเว้นเหล่านี้สำคัญมากที่เราจะต้องจับมันให้ได้ และถ้าเราไม่รู้ว่าจะจัดการกับมันอย่างไร ใครก็ตามที่อาจเรียกใช้วิธีการของเรา จะต้องได้รับแจ้งว่าข้อยกเว้นดังกล่าวสามารถเกิดขึ้นได้ในนั้น

ตัวอย่าง:

ลองนึกภาพว่าเรากำลังเขียนวิธีการสร้างโลกที่มีมนุษย์อาศัยอยู่ จำนวนคนเริ่มต้นจะถูกส่งผ่านเป็นอาร์กิวเมนต์ เราจึงต้องเพิ่มข้อยกเว้นหากมีคนน้อยเกินไป

สร้างโลก บันทึก
public void createWorld(int n) throws EmptyWorldException, LonelyWorldException
{
   if (n == 0)
      throw new EmptyWorldException("There are no people!");
   if (n == 1)
      throw new LonelyWorldException ("There aren't enough people!");
   System.out.println("A wonderful world was created. Population: " + n);
}
เมธอดนี้อาจมี ข้อยกเว้น ที่ตรวจสอบแล้ว สอง รายการ:

  • EmptyWorldException
  • ข้อยกเว้นโลกเดียวดาย

การเรียกใช้เมธอดนี้สามารถจัดการได้ 3 วิธี:

1. อย่าจับข้อยกเว้นใด ๆ

สิ่งนี้มักทำเมื่อวิธีการนี้ไม่ทราบวิธีจัดการกับสถานการณ์อย่างเหมาะสม

รหัส บันทึก
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
เมธอดการโทรไม่จับข้อยกเว้นและต้องแจ้งให้ผู้อื่นทราบ: มันเพิ่มเข้าไปในthrowsอนุประโยค ของมันเอง

2. จับข้อยกเว้นบางประการ

เราจัดการกับข้อผิดพลาดที่เราจัดการได้ แต่อันที่เราไม่เข้าใจก็โยนไปตามวิธีการเรียก ในการทำเช่นนี้ เราจำเป็นต้องเพิ่มชื่อของพวกเขาในส่วนคำสั่งโยน:

รหัส บันทึก
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
ผู้โทรจับข้อยกเว้นที่เลือกไว้เพียงข้อเดียวLonelyWorldExceptionต้องเพิ่มข้อยกเว้นอื่นๆ ลงในลายเซ็น โดยระบุหลังthrowsคำหลัก

3. จับข้อยกเว้นทั้งหมด

หากเมธอดไม่ส่งข้อยกเว้นไปยังเมธอดการโทร แสดงว่าเมธอดการโทรนั้นมั่นใจเสมอว่าทุกอย่างทำงานได้ดี และจะไม่สามารถดำเนินการใด ๆ เพื่อแก้ไขสถานการณ์พิเศษได้

รหัส บันทึก
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
ข้อยกเว้นทั้งหมดถูกจับได้ในวิธีนี้ ผู้โทรจะได้มั่นใจว่าทุกอย่างผ่านไปด้วยดี


3. การตัดคำยกเว้น

ข้อยกเว้น ที่ได้รับการตรวจสอบนั้นดูยอดเยี่ยมในทางทฤษฎี แต่กลายเป็นความยุ่งยากอย่างมากในทางปฏิบัติ

สมมติว่าคุณมีวิธียอดนิยมในโครงการของคุณ มันถูกเรียกจากหลายร้อยแห่งในโปรแกรมของคุณ และคุณตัดสินใจที่จะเพิ่ม ข้อยกเว้น ที่เลือกไว้ ใหม่ เข้าไป และอาจเป็นไปได้ว่า ข้อยกเว้น ที่ได้รับการตรวจสอบ นี้ มีความสำคัญมากและพิเศษมากจนมีเพียงmain()เมธอดเท่านั้นที่รู้ว่าต้องทำอย่างไรหากถูกจับได้

ซึ่งหมายความว่าคุณจะต้องเพิ่มข้อยกเว้นที่เลือกไว้throwsในส่วนคำสั่งของทุกวิธีที่เรียกใช้วิธีการยอดนิยมของคุณ รวมทั้งในthrowsอนุประโยคของเมธอดทั้งหลายที่เรียกเมธอดเหล่านั้น. และของเมธอดที่เรียกเมธอดเหล่านั้น

เป็นผลให้throwsส่วนคำสั่งของครึ่งหนึ่งของวิธีการในโครงการได้รับข้อยกเว้นการตรวจสอบ ใหม่ และแน่นอนว่าโปรเจ็กต์ของคุณอยู่ภายใต้การทดสอบ และตอนนี้การทดสอบจะไม่คอมไพล์ และตอนนี้คุณต้องแก้ไขคำสั่งโยนในการทดสอบของคุณด้วย

จากนั้นรหัสทั้งหมดของคุณ (การเปลี่ยนแปลงทั้งหมดในไฟล์หลายร้อยไฟล์) จะต้องได้รับการตรวจสอบโดยโปรแกรมเมอร์รายอื่น และ ณ จุดนี้เราถามตัวเองว่าทำไมเราถึงทำการเปลี่ยนแปลงมากมายในโครงการ? วัน (s?) ของการทำงานและการทดสอบที่เสียหาย - ทั้งหมดนี้เพื่อเพิ่ม ข้อยกเว้น ที่ตรวจสอบหรือไม่?

และแน่นอนว่ายังคงมีปัญหาเกี่ยวกับการสืบทอดและวิธีการแทนที่ ปัญหาที่มาจาก ข้อยกเว้น ที่ตรวจสอบมีขนาดใหญ่กว่าผลประโยชน์ บรรทัดล่างคือตอนนี้มีเพียงไม่กี่คนที่รักพวกเขาและมีคนไม่กี่คนที่ใช้พวกเขา

อย่างไรก็ตาม ยังมีโค้ดจำนวนมาก (รวมถึงโค้ดไลบรารี Java มาตรฐาน) ที่มีข้อยกเว้นที่ตรวจสอบ เหล่านี้ จะทำอย่างไรกับพวกเขา? เราไม่สามารถเพิกเฉยต่อสิ่งเหล่านี้ได้ และเราไม่รู้ว่าจะจัดการกับมันอย่างไร

โปรแกรมเมอร์ Java เสนอให้ห่อ ข้อยกเว้น ที่ตรวจสอบแล้วในRuntimeException. กล่าวอีกนัยหนึ่งคือ จับ ข้อยกเว้น ที่ตรวจสอบ ทั้งหมด แล้วสร้าง ข้อยกเว้น ที่ไม่ได้ตรวจสอบ (เช่นRuntimeException) แล้วโยนทิ้งไปแทน การทำเช่นนั้นมีลักษณะดังนี้:

try
{
   // Code where a checked exception might occur
}
catch(Exception exp)
{
   throw new RuntimeException(exp);
}

มันไม่ใช่วิธีแก้ปัญหาที่สวยงามนัก แต่ที่นี่ไม่มีอาชญากร: ข้อยกเว้นถูกยัดไว้ในไฟล์RuntimeException.

หากต้องการ คุณสามารถเรียกคืนจากที่นั่นได้อย่างง่ายดาย ตัวอย่าง:

รหัส บันทึก
try
{
   // Code where we wrap the checked exception
   // in a RuntimeException
}
catch(RuntimeException e)
{
   Throwable cause = e.getCause();
   if (cause instanceof Exception)
   {
      Exception exp = (Exception) cause;
      // Exception handling code goes here
   }
}







รับข้อยกเว้นที่เก็บไว้ในRuntimeExceptionวัตถุ ตัวแปรcauseอาจจะnull

กำหนดประเภทของมันและแปลงเป็นประเภทข้อยกเว้น ที่ตรวจสอบ


4. จับข้อยกเว้นหลายรายการ

โปรแกรมเมอร์เกลียดการทำซ้ำรหัส พวกเขายังมาพร้อมกับหลักการพัฒนาที่สอดคล้องกัน: Don't Repeat Yourself (DRY ) แต่เมื่อจัดการข้อยกเว้น มีโอกาสบ่อยครั้งที่tryบล็อกหลายcatchบล็อกตามด้วยรหัสเดียวกัน

หรืออาจมี 3 catchบล็อกที่มีรหัสเดียวกัน และอีก 2 catchบล็อกที่มีรหัสเหมือนกัน นี่เป็นสถานการณ์มาตรฐานเมื่อโครงการของคุณจัดการกับข้อยกเว้นอย่างมีความรับผิดชอบ

เริ่มต้นด้วยเวอร์ชัน 7 ในภาษา Java เพิ่มความสามารถในการระบุข้อยกเว้นหลายประเภทในcatchบล็อก เดียว ดูเหมือนว่า:

try
{
   // Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
   // Exception handling code
}

คุณสามารถมีบล็อกได้มากเท่าcatchที่คุณต้องการ อย่างไรก็ตามcatchบล็อกเดียวไม่สามารถระบุข้อยกเว้นที่สืบทอดกันได้ กล่าวอีกนัยหนึ่ง คุณไม่สามารถเขียน catch ( Exception| RuntimeExceptione) ได้ เนื่องจากRuntimeExceptionคลาสสืบทอดExceptionมา



5. ข้อยกเว้นที่กำหนดเอง

คุณสามารถสร้างคลาสยกเว้นของคุณเองได้ตลอดเวลา คุณเพียงแค่สร้างคลาสที่สืบทอดRuntimeExceptionคลาส มันจะมีลักษณะดังนี้:

class ClassName extends RuntimeException
{
}

เราจะพูดถึงรายละเอียดเมื่อคุณเรียนรู้ OOP, การสืบทอด, ตัวสร้าง และการแทนที่เมธอด

อย่างไรก็ตาม แม้ว่าคุณจะมีเพียงคลาสง่ายๆ แบบนี้ (ไม่มีโค้ดทั้งหมด) คุณก็ยังสามารถโยนข้อยกเว้นตามคลาสนี้ได้:

รหัส บันทึก
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




โยนเครื่องหมาย MyException .

ใน ภารกิจ Java Multithreadingเราจะเจาะลึกการทำงานกับข้อยกเว้นที่กำหนดเองของเรา