Sa tingin ko malamang na nakaranas ka ng sitwasyon kung saan nagpapatakbo ka ng code at napupunta sa isang bagay tulad ng NullPointerException , ClassCastException , o mas masahol pa... Sinusundan ito ng mahabang proseso ng pag-debug, pagsusuri, pag-googling, at iba pa. Ang mga pagbubukod ay kahanga-hanga tulad ng: ipinapahiwatig nila ang likas na katangian ng problema at kung saan ito nangyari. Kung gusto mong i-refresh ang iyong memorya at matuto ng kaunti pa, tingnan ang artikulong ito: Exceptions: checked, unchecked, at custom .

Sabi nga, maaaring may mga sitwasyon kung kailan kailangan mong gumawa ng sarili mong exception. Halimbawa, ipagpalagay na ang iyong code ay kailangang humiling ng impormasyon mula sa isang malayuang serbisyo na hindi magagamit sa ilang kadahilanan. O ipagpalagay na may nag-fill out ng application para sa isang bank card at nagbibigay ng numero ng telepono na, aksidente man o hindi, ay nauugnay na sa isa pang user sa system.

Siyempre, ang tamang pag-uugali dito ay nakasalalay pa rin sa mga kinakailangan ng customer at sa arkitektura ng system, ngunit ipagpalagay natin na ikaw ay naatasang suriin kung ang numero ng telepono ay ginagamit na at magtapon ng isang pagbubukod kung ito ay.

Gumawa tayo ng exception:


public class PhoneNumberAlreadyExistsException extends Exception {

   public PhoneNumberAlreadyExistsException(String message) {
       super(message);
   }
}
    

Susunod, gagamitin namin ito kapag ginawa namin ang aming pagsusuri:


public class PhoneNumberRegisterService {
   List<String> registeredPhoneNumbers = Arrays.asList("+1-111-111-11-11", "+1-111-111-11-12", "+1-111-111-11-13", "+1-111-111-11-14");

   public void validatePhone(String phoneNumber) throws PhoneNumberAlreadyExistsException {
       if (registeredPhoneNumbers.contains(phoneNumber)) {
           throw new PhoneNumberAlreadyExistsException("The specified phone number is already in use by another customer!");
       }
   }
}
    

Upang gawing simple ang aming halimbawa, gagamit kami ng ilang hardcoded na numero ng telepono upang kumatawan sa isang database. At sa wakas, subukan nating gamitin ang ating exception:


public class CreditCardIssue {
   public static void main(String[] args) {
       PhoneNumberRegisterService service = new PhoneNumberRegisterService();
       try {
           service.validatePhone("+1-111-111-11-14");
       } catch (PhoneNumberAlreadyExistsException e) {
           // Here we can write to logs or display the call stack
		e.printStackTrace();
       }
   }
}
    

At ngayon ay oras na upang pindutin ang Shift+F10 (kung gumagamit ka ng IDEA), ibig sabihin, patakbuhin ang proyekto. Ito ang makikita mo sa console:

exception.CreditCardIssue
exception.PhoneNumberAlreadyExistsException: Ang tinukoy na numero ng telepono ay ginagamit na ng ibang customer!
sa exception.PhoneNumberRegisterService.validatePhone(PhoneNumberRegisterService.java:11)

Tumingin sa iyo! Gumawa ka ng sarili mong exception at sinubukan mo pa ito ng kaunti. Binabati kita sa tagumpay na ito! Inirerekomenda kong mag-eksperimento nang kaunti sa code upang mas maunawaan kung paano ito gumagana.

Magdagdag ng isa pang check — halimbawa, tingnan kung ang numero ng telepono ay may kasamang mga titik. Tulad ng malamang na alam mo, ang mga titik ay kadalasang ginagamit sa Estados Unidos upang gawing mas madaling matandaan ang mga numero ng telepono, hal. 1-800-MY-APPLE. Ang iyong pagsusuri ay maaaring matiyak na ang numero ng telepono ay naglalaman lamang ng mga numero.

Okay, kaya nakagawa kami ng may check na exception. Magiging maayos at mabuti ang lahat, ngunit...

Ang komunidad ng programming ay nahahati sa dalawang kampo — ang mga pabor sa mga nasuri na eksepsiyon at ang mga sumasalungat sa kanila. Ang magkabilang panig ay gumagawa ng malakas na argumento. Parehong kinabibilangan ng mga nangungunang developer: Pinuna ni Bruce Eckel ang mga naka-check na exception, habang ipinagtatanggol sila ni James Gosling. Mukhang hindi na tuluyang maaayos ang usaping ito. Sabi nga, tingnan natin ang mga pangunahing disadvantage ng paggamit ng mga naka-check na exception.

Ang pangunahing kawalan ng mga naka-check na eksepsiyon ay dapat silang pangasiwaan. At narito, mayroon kaming dalawang opsyon: alinman ay hawakan ito sa lugar gamit ang isang try-catch , o, kung gagamitin namin ang parehong exception sa maraming lugar, gumamit ng mga throws upang itapon ang mga exception, at iproseso ang mga ito sa mga top-level na klase.

Gayundin, maaari tayong magkaroon ng code na "boilerplate", ibig sabihin, ang code na kumukuha ng maraming espasyo, ngunit hindi gumagawa ng mabigat na pag-angat.

Lumilitaw ang mga problema sa medyo malalaking application na may maraming mga pagbubukod na hinahawakan: ang listahan ng mga throws sa isang top-level na paraan ay madaling lumaki upang magsama ng isang dosenang mga eksepsiyon.

public OurCoolClass() throws FirstException, SecondException, ThirdException, ApplicationNameException...

Karaniwang hindi ito gusto ng mga developer at sa halip ay nag-o-opt para sa isang lansihin: ginagawa nilang lahat ng kanilang mga naka-check na exception ay magmana ng isang karaniwang ninuno — ApplicationNameException . Ngayon ay dapat din nilang mahuli iyon ( nasuri !) exception sa isang handler:


catch (FirstException e) {
    // TODO
}
catch (SecondException e) {
    // TODO
}
catch (ThirdException e) {
    // TODO
}
catch (ApplicationNameException e) {
    // TODO
}
    

Narito tayo ay nahaharap sa isa pang problema — ano ang dapat nating gawin sa huling catch block? Sa itaas, naproseso na namin ang lahat ng inaasahang sitwasyon, kaya sa puntong ito, ang ApplicationNameException ay wala nang iba pang ibig sabihin sa amin kundi ang " Exception : may naganap na hindi maintindihang error". Ganito namin pinangangasiwaan ito:


catch (ApplicationNameException e) {
    LOGGER.error("Unknown error", e.getMessage());
}
    

At sa huli, hindi natin alam kung ano ang nangyari.

Ngunit hindi ba natin maitatapon ang bawat pagbubukod nang sabay-sabay, tulad nito?


public void ourCoolMethod() throws Exception {
// Do some work
}
    

Oo, kaya namin. Ngunit ano ang sinasabi sa atin ng "throws Exception"? Na may sira. Kailangan mong siyasatin ang lahat mula sa itaas hanggang sa ibaba at maging komportable sa debugger sa mahabang panahon upang maunawaan ang dahilan.

Maaari ka ring makatagpo ng isang construct na kung minsan ay tinatawag na "exception swallowing":


try {
// Some code
} catch(Exception e) {
   throw new ApplicationNameException("Error");
}
    

Walang gaanong idaragdag dito sa pamamagitan ng pagpapaliwanag — ginagawang malinaw ng code ang lahat, o sa halip, ginagawa nitong hindi malinaw ang lahat.

Siyempre, maaari mong sabihin na hindi mo ito makikita sa totoong code. Well, tingnan natin ang bituka (ang code) ng klase ng URL mula sa java.net package. Follow me kung gusto mong malaman!

Narito ang isa sa mga construct sa klase ng URL :


public URL(String spec) throws MalformedURLException {
   this(null, spec);
}
    

Tulad ng nakikita mo, mayroon kaming isang kawili-wiling naka-check na exception — MalformedURLException . Narito kung kailan ito maaaring ihagis (at sinipi ko):
"kung walang tinukoy na protocol, o natagpuan ang isang hindi kilalang protocol, o null ang spec, o nabigong sumunod ang na-parse na URL sa partikular na syntax ng nauugnay na protocol."

Yan ay:

  1. Kung walang tinukoy na protocol.
  2. May nakitang hindi kilalang protocol.
  3. Ang spec ay null .
  4. Hindi sumusunod ang URL sa partikular na syntax ng nauugnay na protocol.

Gumawa tayo ng paraan na lumilikha ng URL object:


public URL createURL() {
   URL url = new URL("https://codegym.cc");
   return url;
}
    

Sa sandaling isulat mo ang mga linyang ito sa IDE (nagco-coding ako sa IDEA, ngunit gumagana ito kahit na sa Eclipse at NetBeans), makikita mo ito:

Nangangahulugan ito na kailangan nating maghagis ng exception, o ibalot ang code sa isang try-catch block. Sa ngayon, iminumungkahi kong piliin ang pangalawang opsyon upang mailarawan kung ano ang nangyayari:


public static URL createURL() {
   URL url = null;
   try {
       url = new URL("https://codegym.cc");
   } catch(MalformedURLException e) {
  e.printStackTrace();
   }
   return url;
}
    

Tulad ng nakikita mo, ang code ay medyo verbose. At binanggit namin iyon sa itaas. Ito ay isa sa mga pinaka-halatang dahilan para gumamit ng mga hindi naka-check na exception.

Maaari tayong lumikha ng isang hindi naka-check na exception sa pamamagitan ng pagpapalawak ng RuntimeException sa Java.

Ang mga hindi naka-check na exception ay minana mula sa Error class o sa RuntimeException class. Maraming mga programmer ang nararamdaman na ang mga pagbubukod na ito ay maaaring pangasiwaan sa aming mga programa dahil ang mga ito ay kumakatawan sa mga error na hindi namin inaasahan na mababawi habang tumatakbo ang programa.

Kapag naganap ang isang hindi na-check na exception, kadalasan ay sanhi ito ng hindi wastong paggamit ng code, pagpasa sa isang argumento na null o kung hindi man ay hindi wasto.

Well, isulat natin ang code:


public class OurCoolUncheckedException extends RuntimeException {
   public OurCoolUncheckedException(String message) {
       super(message);
   }

   public OurCoolUncheckedException(Throwable cause) {
       super(cause);
   }
  
   public OurCoolUncheckedException(String message, Throwable throwable) {
       super(message, throwable);
   }
}
    

Tandaan na gumawa kami ng maraming constructor para sa iba't ibang layunin. Nagbibigay-daan ito sa amin na bigyan ang aming exception ng higit pang mga kakayahan. Halimbawa, magagawa namin ito upang ang isang exception ay nagbibigay sa amin ng error code. Upang magsimula, gumawa tayo ng isang enum upang kumatawan sa ating mga error code:


public enum ErrorCodes {
   FIRST_ERROR(1),
   SECOND_ERROR(2),
   THIRD_ERROR(3);

   private int code;

   ErrorCodes(int code) {
       this.code = code;
   }

   public int getCode() {
       return code;
   }
}
    

Ngayon magdagdag tayo ng isa pang tagabuo sa aming klase ng pagbubukod:


public OurCoolUncheckedException(String message, Throwable cause, ErrorCodes errorCode) {
   super(message, cause);
   this.errorCode = errorCode.getCode();
}
    

At huwag nating kalimutang magdagdag ng field (halos nakalimutan natin):


private Integer errorCode;
    

At siyempre, isang paraan upang makuha ang code na ito:


public Integer getErrorCode() {
   return errorCode;
}
    

Tingnan natin ang buong klase upang masuri natin ito at maihambing:

public class OurCoolUncheckedException extends RuntimeException {
   private Integer errorCode;

   public OurCoolUncheckedException(String message) {
       super(message);
   }

   public OurCoolUncheckedException(Throwable cause) {
       super(cause);
   }

   public OurCoolUncheckedException(String message, Throwable throwable) {

       super(message, throwable);
   }

   public OurCoolUncheckedException(String message, Throwable cause, ErrorCodes errorCode) {
       super(message, cause);
       this.errorCode = errorCode.getCode();
   }
   public Integer getErrorCode() {
       return errorCode;
   }
}
    

Ta-da! Tapos na ang aming exception! Tulad ng nakikita mo, walang partikular na kumplikado dito. Tingnan natin ito sa aksyon:


   public static void main(String[] args) {
       getException();
   }
   public static void getException() {
       throw new OurCoolUncheckedException("Our cool exception!");
   }
    

Kapag pinatakbo namin ang aming maliit na application, makikita namin ang isang bagay tulad ng sumusunod sa console:

Ngayon, samantalahin natin ang karagdagang functionality na idinagdag namin. Magdaragdag kami ng kaunti sa nakaraang code:


public static void main(String[] args) throws Exception {

   OurCoolUncheckedException exception = getException(3);
   System.out.println("getException().getErrorCode() = " + exception.getErrorCode());
   throw exception;

}

public static OurCoolUncheckedException getException(int errorCode) {
   return switch (errorCode) {
   case 1:
       return new OurCoolUncheckedException("Our cool exception! An error occurred: " + ErrorCodes.FIRST_ERROR.getCode(), new Throwable(), ErrorCodes.FIRST_ERROR);
   case 2:
       return new OurCoolUncheckedException("Our cool exception! An error occurred: " + ErrorCodes.SECOND_ERROR.getCode(), new Throwable(), ErrorCodes.SECOND_ERROR);
   default: // Since this is the default action, here we catch the third and any other codes that we have not yet added. You can learn more by reading Java switch statement
       return new OurCoolUncheckedException("Our cool exception! An error occurred: " + ErrorCodes.THIRD_ERROR.getCode(), new Throwable(), ErrorCodes.THIRD_ERROR);
}

}
    

Maaari kang gumawa ng mga pagbubukod sa parehong paraan na gumagana sa mga bagay. Siyempre, sigurado ako na alam mo na ang lahat sa Java ay isang bagay.

At tingnan mo ang ginawa namin. Una, binago namin ang pamamaraan, na ngayon ay hindi nagtatapon, ngunit sa halip ay lumilikha lamang ng isang pagbubukod, depende sa parameter ng input. Susunod, gamit ang switch-case statement, bumubuo kami ng exception na may gustong error code at mensahe. At sa pangunahing pamamaraan, nakukuha namin ang nilikha na pagbubukod, kunin ang error code, at itinapon ito.

Patakbuhin natin ito at tingnan kung ano ang makukuha natin sa console:

Tingnan - nai-print namin ang error code na nakuha namin mula sa exception at pagkatapos ay inihagis ang exception mismo. Higit pa rito, masusubaybayan pa natin kung saan mismo itinapon ang pagbubukod. Kung kinakailangan, maaari mong idagdag ang lahat ng may-katuturang impormasyon sa mensahe, gumawa ng mga karagdagang error code, at magdagdag ng mga bagong feature sa iyong mga exception.

Well, ano sa tingin mo diyan? Sana naging maayos ang lahat para sa iyo!

Sa pangkalahatan, ang mga pagbubukod ay isang medyo malawak na paksa at hindi malinaw na hiwa. Marami pang pagtatalo tungkol dito. Halimbawa, ang Java lang ang nagsuri ng mga exception. Sa mga pinakasikat na wika, wala pa akong nakikitang gumagamit nito.

Mahusay na isinulat ni Bruce Eckel ang tungkol sa mga eksepsiyon sa kabanata 12 ng kanyang aklat na "Thinking in Java" — Inirerekomenda kong basahin mo ito! Tingnan din ang unang volume ng "Core Java" ni Horstmann — Mayroon din itong maraming kawili-wiling bagay sa kabanata 7.

Isang maliit na buod

  1. Isulat ang lahat sa isang log! Mag-log ng mga mensahe sa itinapon na mga pagbubukod. Ito ay kadalasang makakatulong ng malaki sa pag-debug at magbibigay-daan sa iyong maunawaan kung ano ang nangyari. Huwag mag-iwan ng isang catch block na walang laman, kung hindi, ito ay "lalamunin" lamang ang pagbubukod at wala kang anumang impormasyon upang matulungan kang maghanap ng mga problema.

  2. Pagdating sa mga eksepsiyon, masamang ugali na hulihin silang lahat nang sabay-sabay (tulad ng sabi ng isang kasamahan ko, "hindi ito Pokemon, ito ay Java"), kaya iwasang mahuli (Exception e) o mas masahol pa, mahuli ( Throwable t ) .

  3. Magtapon ng mga pagbubukod sa lalong madaling panahon. Ito ay magandang kasanayan sa Java programming. Kapag nag-aral ka ng mga balangkas tulad ng Spring, makikita mong sinusunod nila ang prinsipyong "mabilis na mabibigo". Iyon ay, "nabibigo" sila nang maaga hangga't maaari upang gawing posible na mabilis na mahanap ang error. Siyempre, nagdudulot ito ng ilang mga abala. Ngunit nakakatulong ang diskarteng ito na lumikha ng mas matatag na code.

  4. Kapag tumatawag sa iba pang bahagi ng code, pinakamahusay na kumuha ng ilang partikular na pagbubukod. Kung ang tinatawag na code ay naghagis ng maraming mga pagbubukod, ito ay hindi magandang kasanayan sa programming upang mahuli lamang ang pangunahing klase ng mga pagbubukod na iyon. Halimbawa, sabihin nating tumawag ka ng code na naghagis ng FileNotFoundException at IOException . Sa iyong code na tumatawag sa module na ito, mas mainam na magsulat ng dalawang catch block upang mahuli ang bawat isa sa mga exception, sa halip na isang catch para mahuli ang Exception .

  5. Mahuli lamang ang mga pagbubukod kapag epektibo mong mapangasiwaan ang mga ito para sa mga user at para sa pag-debug.

  6. Huwag mag-atubiling sumulat ng sarili mong mga pagbubukod. Siyempre, ang Java ay may maraming mga handa, isang bagay para sa bawat okasyon, ngunit kung minsan kailangan mo pa ring mag-imbento ng iyong sariling "gulong". Ngunit dapat mong malinaw na maunawaan kung bakit mo ito ginagawa at siguraduhin na ang karaniwang hanay ng mga pagbubukod ay wala pa kung ano ang kailangan mo.

  7. Kapag lumikha ka ng sarili mong mga klase ng exception, mag-ingat sa pagbibigay ng pangalan! Marahil ay alam mo na na napakahalaga na wastong pangalanan ang mga klase, variable, pamamaraan at pakete. Ang mga pagbubukod ay walang pagbubukod! :) Palaging magtapos sa salitang Exception , at ang pangalan ng exception ay dapat na malinaw na naghahatid ng uri ng error na kinakatawan nito. Halimbawa, FileNotFoundException .

  8. Idokumento ang iyong mga pagbubukod. Inirerekomenda namin ang pagsulat ng @throws Javadoc tag para sa mga exception. Lalo itong magiging kapaki-pakinabang kapag ang iyong code ay nagbibigay ng anumang uri ng mga interface. At mas madaling maunawaan mo ang sarili mong code sa ibang pagkakataon. Ano sa palagay mo, paano mo matutukoy kung tungkol saan ang MalformedURLException ? Mula sa Javadoc! Oo, ang pag-iisip ng pagsulat ng dokumentasyon ay hindi masyadong nakakaakit, ngunit maniwala ka sa akin, magpapasalamat ka sa iyong sarili kapag bumalik ka sa iyong sariling code pagkalipas ng anim na buwan.

  9. Ilabas ang mga mapagkukunan at huwag pabayaan ang pagbuo ng try-with-resources .

  10. Narito ang pangkalahatang buod: matalinong gumamit ng mga pagbubukod. Ang paghahagis ng eksepsiyon ay isang medyo "mahal" na operasyon sa mga tuntunin ng mga mapagkukunan. Sa maraming mga kaso, maaaring mas madaling iwasan ang paghagis ng mga pagbubukod at sa halip ay ibalik, sabihin, isang boolean variable na kung ang operasyon ay nagtagumpay, gamit ang isang simple at "mas mura" if-else .

    Maaaring nakakaakit din na itali ang lohika ng aplikasyon sa mga pagbubukod, na malinaw na hindi mo dapat gawin. Tulad ng sinabi namin sa simula ng artikulo, ang mga pagbubukod ay para sa mga pambihirang sitwasyon, hindi inaasahan, at mayroong iba't ibang mga tool para maiwasan ang mga ito. Sa partikular, mayroong Opsyonal upang maiwasan ang isang NullPointerException , o Scanner.hasNext at katulad nito upang maiwasan ang isang IOException , na maaaring itapon ng read() na paraan.