Unntakshåndtering i Java

1. Oversikt

I denne opplæringen vil vi gå gjennom det grunnleggende om unntakshåndtering i Java, så vel som noen av dens gotchas.

2. Første prinsipper

2.1. Hva er det?

For å bedre forstå unntak og unntakshåndtering, la oss sammenligne i virkeligheten.

Tenk deg at vi bestiller et produkt på nettet, men mens du er underveis, er det en feil i leveransen. Et godt selskap kan håndtere dette problemet og omdirigere pakken vår slik at den fremdeles kommer i tide.

På samme måte, i Java, kan koden oppstå feil mens vi utfører instruksjonene våre. God avvikshåndtering kan håndtere feil og omdirigere programmet på en elegant måte for å gi brukeren fortsatt en positiv opplevelse.

2.2. Hvorfor bruke den?

Vi skriver vanligvis kode i et idealisert miljø: filsystemet inneholder alltid filene våre, nettverket er sunt, og JVM har alltid nok minne. Noen ganger kaller vi dette "lykkelig vei".

I produksjonen kan filsystemer imidlertid ødelegge, nettverk brytes ned og JVM-er går tom for minne. Kodenes velvære avhenger av hvordan den håndterer "ulykkelige veier".

Vi må håndtere disse forholdene fordi de påvirker flyten av applikasjonen negativt og form unntak:

offentlig statisk liste getPlayers () kaster IOException {Path path = Paths.get ("players.dat"); Liste spillere = Files.readAllLines (sti); return players.stream () .map (Player :: new) .collect (Collectors.toList ()); }

Denne koden velger å ikke håndtere IO Unntak, sender den opp samtalestakken i stedet. I et idealisert miljø fungerer koden bra.

Men hva kan skje i produksjonen hvis spillere.dat mangler?

Unntak i tråden "hoved" java.nio.file.NoSuchFileException: Players.dat <- Players.dat-filen eksisterer ikke på sun.nio.fs.WindowsException.translateToIOException (ukjent kilde) på sun.nio.fs.WindowsException .rethrowAsIOException (ukjent kilde) // ... mer stabelspor på java.nio.file.Files.readAllLines (ukjent kilde) på java.nio.file.Files.readAllLines (ukjent kilde) på Exceptions.getPlayers (Exceptions.java : 12) <- Unntak oppstår i getPlayers () -metoden, på linje 12 ved Unntak. Hoved (Unntak.java:19) <- getPlayers () kalles av main (), på linje 19

Uten å håndtere dette unntaket, kan et ellers sunt program slutte å kjøre helt! Vi må sørge for at koden vår har en plan for når ting går galt.

Vær også oppmerksom på en annen fordel her til unntak, og det er selve stabelsporet. På grunn av dette stabelsporet kan vi ofte finne krenkende kode uten å måtte koble til en feilsøkingsprogram.

3. Unntakshierarki

Til syvende og sist, unntak er bare Java-objekter der alle strekker seg fra Kastbar:

 ---> Kastbar unntaksfeil | (avkrysset) (ukontrollert) | RuntimeException (ukontrollert)

Det er tre hovedkategorier av eksepsjonelle forhold:

  • Sjekket unntak
  • Uavmerkede unntak / Runtime-unntak
  • Feil

Runtime og ukontrollerte unntak refererer til det samme. Vi kan ofte bruke dem om hverandre.

3.1. Sjekket unntak

Merkede unntak er unntak som Java-kompilatoren krever at vi håndterer. Vi må enten erklærende kaste unntaket opp i samtalestakken, eller så må vi håndtere det selv. Mer om begge disse på et øyeblikk.

Oracles dokumentasjon forteller oss at vi bruker avmerkede unntak når vi med rimelighet kan forvente at den som ringer til vår metode skal kunne komme seg.

Et par eksempler på avmerkede unntak er IO Unntak og ServletException.

3.2. Uavmerkede unntak

Uavmerkede unntak er unntak som Java-kompilatoren gjør ikke krever at vi håndterer.

Enkelt sagt, hvis vi lager et unntak som strekker seg RuntimeException, det vil være ukontrollert; ellers blir det sjekket.

Og selv om dette høres praktisk ut, forteller Oracles dokumentasjon at det er gode grunner for begge konseptene, som å skille mellom en situasjonsfeil (avkrysset) og en bruksfeil (ukontrollert).

Noen eksempler på ukontrollerte unntak er NullPointerException, IllegalArgumentException, og SecurityException.

3.3. Feil

Feil representerer alvorlige og vanligvis uopprettelige forhold som bibliotekskompatibilitet, uendelig rekursjon eller minnelekkasjer.

Og selv om de ikke utvider seg RuntimeException, de er også ukontrollerte.

I de fleste tilfeller vil det være rart for oss å håndtere, sette i gang eller utvide Feil. Vanligvis vil vi at disse skal forplante seg helt opp.

Et par eksempler på feil er a StackOverflowError og OutOfMemoryError.

4. Håndtering av unntak

I Java API er det mange steder der ting kan gå galt, og noen av disse stedene er merket med unntak, enten i signaturen eller Javadoc:

/ ** * @exception FileNotFoundException ... * / public Scanner (String fileName) kaster FileNotFoundException {// ...}

Som nevnt litt tidligere, når vi kaller disse "risikable" metoder, vi håndtere de sjekket unntakene, og vi kan håndter de ukontrollerte. Java gir oss flere måter å gjøre dette på:

4.1. kaster

Den enkleste måten å "håndtere" et unntak på er å omgjøre det:

public int getPlayerScore (String playerFile) kaster FileNotFoundException {Scanner content = new Scanner (new File (playerFile)); return Integer.parseInt (contents.nextLine ()); }

Fordi FileNotFoundException er et sjekket unntak, dette er den enkleste måten å tilfredsstille kompilatoren, men det betyr at alle som kaller metoden vår nå må håndtere den også!

parseInt kan kaste en NumberFormatException, men fordi det ikke er merket av, er vi ikke pålagt å håndtere det.

4.2. prøvefangst

Hvis vi vil prøve å håndtere unntaket selv, kan vi bruke en prøvefangst blokkere. Vi kan takle det ved å omgjøre unntaket vårt:

public int getPlayerScore (String playerFile) {try {Scanner contents = new Scanner (new File (playerFile)); return Integer.parseInt (contents.nextLine ()); } fange (FileNotFoundException noFile) {kast ny IllegalArgumentException ("Filen ble ikke funnet"); }}

Eller ved å utføre gjenopprettingstrinn:

public int getPlayerScore (String playerFile) {try {Scanner contents = new Scanner (new File (playerFile)); return Integer.parseInt (contents.nextLine ()); } catch (FileNotFoundException noFile) {logger.warn ("Filen ble ikke funnet, tilbakestiller poengsummen."); retur 0; }}

4.3. endelig

Nå er det tider når vi har kode som må kjøres uavhengig av om det oppstår et unntak, og det er her endelig nøkkelord kommer inn.

I våre eksempler så langt har det skjedd en stygg bug i skyggene, som at Java som standard ikke vil returnere filhåndtak til operativsystemet.

Uansett om vi kan lese filen eller ikke, vil vi forsikre oss om at vi gjør riktig opprydding!

La oss prøve dette på "lat" måte først:

public int getPlayerScore (String playerFile) kaster FileNotFoundException {Skannerinnhold = null; prøv {contents = new Scanner (new File (playerFile)); return Integer.parseInt (contents.nextLine ()); } til slutt {if (contents! = null) {contents.close (); }}} 

Her, den endelig blokk angir hvilken kode vi vil at Java skal kjøre uavhengig av hva som skjer med å prøve å lese filen.

Selv om en FileNotFoundException blir kastet opp samtalestakken, vil Java ringe innholdet i endelig før du gjør det.

Vi kan også begge håndtere unntaket og sørg for at ressursene våre blir stengt:

public int getPlayerScore (String playerFile) {Skannerinnhold; prøv {contents = new Scanner (new File (playerFile)); return Integer.parseInt (contents.nextLine ()); } catch (FileNotFoundException noFile) {logger.warn ("Filen ble ikke funnet, tilbakestiller poengsummen."); retur 0; } til slutt {prøv {if (contents! = null) {contents.close (); }} fange (IOException io) {logger.error ("Kunne ikke lukke leseren!", io); }}}

Fordi Lukk er også en “risikabel” metode, vi må også fange unntaket!

Dette kan se ganske komplisert ut, men vi trenger hvert stykke for å håndtere hvert potensielle problem som kan oppstå riktig.

4.4. prøve-med ressurser

Heldigvis kan vi fra Java 7 forenkle syntaksen ovenfor når vi jobber med ting som strekker seg Kan lukkes automatisk:

public int getPlayerScore (String playerFile) {try (Scanner contents = new Scanner (new File (playerFile))) {return Integer.parseInt (contents.nextLine ()); } catch (FileNotFoundException e) {logger.warn ("Filen ble ikke funnet, tilbakestiller poengsummen."); retur 0; }}

Når vi plasserer referanser som er Kan lukkes automatisk i prøve erklæring, så trenger vi ikke å stenge ressursen selv.

Vi kan fortsatt bruke en endelig blokkere, for å gjøre noen annen form for opprydding vi ønsker.

Sjekk ut vår artikkel dedikert til prøve-med ressurser for å lære mer.

4.5. Flere å fange Blokker

Noen ganger kan koden kaste mer enn ett unntak, og vi kan ha mer enn ett å fange blokkhåndtak hver for seg:

public int getPlayerScore (String playerFile) {try (Scanner contents = new Scanner (new File (playerFile))) {return Integer.parseInt (contents.nextLine ()); } catch (IOException e) {logger.warn ("Spillerfilen ville ikke lastes!", e); retur 0; } catch (NumberFormatException e) {logger.warn ("Player-filen ble ødelagt!", e); retur 0; }}

Flere fangster gir oss sjansen til å håndtere hvert unntak forskjellig hvis behovet skulle oppstå.

Legg også merke til her at vi ikke fikk med oss FileNotFoundException, og det er fordi det utvider IOException. Fordi vi fanger IO Unntak, Java vil vurdere at noen av underklassene også blir håndtert.

La oss imidlertid si at vi trenger å behandle FileNotFoundException forskjellig fra det mer generelle IO Unntak:

public int getPlayerScore (String playerFile) {try (Scanner contents = new Scanner (new File (playerFile))) {return Integer.parseInt (contents.nextLine ()); } fange (FileNotFoundException e) {logger.warn ("Spillerfil ikke funnet!", e); retur 0; } catch (IOException e) {logger.warn ("Spillerfilen ville ikke lastes!", e); retur 0; } catch (NumberFormatException e) {logger.warn ("Spillerfilen ble ødelagt!", e); retur 0; }}

Java lar oss håndtere unntak fra underklasse hver for seg, husk å plassere dem høyere i listen over fangster.

4.6. Union å fange Blokker

Når vi vet at måten vi håndterer feil på vil være den samme, introduserte Java 7 muligheten til å fange flere unntak i samme blokk:

public int getPlayerScore (String playerFile) {try (Scanner contents = new Scanner (new File (playerFile))) {return Integer.parseInt (contents.nextLine ()); } catch (IOException | NumberFormatException e) {logger.warn ("Kunne ikke laste poengsum!", e); retur 0; }}

5. Kaste unntak

Hvis vi ikke vil håndtere unntaket selv, eller vi vil generere unntak for andre å håndtere, må vi bli kjent med kaste nøkkelord.

La oss si at vi har følgende sjekket unntak vi har laget selv:

offentlig klasse TimeoutException utvider Unntak {public TimeoutException (strengmelding) {super (melding); }}

og vi har en metode som potensielt kan ta lang tid å fullføre:

public List loadAllPlayers (String playersFile) {// ... potensielt lang drift}

5.1. Kaster et avkrysset unntak

Som å komme tilbake fra en metode, kan vi kaste når som helst.

Selvfølgelig bør vi kaste når vi prøver å indikere at noe har gått galt:

public List loadAllPlayers (String playersFile) kaster TimeoutException {while (! tooLong) {// ... potensielt lang drift} kaster nytt TimeoutException ("Denne operasjonen tok for lang tid"); }

Fordi TimeoutException er sjekket, må vi også bruke kaster nøkkelord i signaturen slik at innringere av vår metode vet å håndtere det.

5.2. Kasteing et ukontrollert unntak

Hvis vi vil gjøre noe som, si, validere inndata, kan vi bruke et ukontrollert unntak i stedet:

public List loadAllPlayers (String playersFile) kaster TimeoutException {if (! isFilenameValid (playersFile)) {throw new IllegalArgumentException ("Filnavn er ikke gyldig!"); } // ...} 

Fordi IllegalArgumentException er ukontrollert, trenger vi ikke merke metoden, selv om vi er velkomne til det.

Noen merker metoden uansett som en form for dokumentasjon.

5.3. Innpakning og omkasting

Vi kan også velge å omlegge et unntak vi har fått:

public List loadAllPlayers (String playersFile) kaster IOException {try {// ...} catch (IOException io) {throw io; }}

Eller gjør en vikling og omlegging:

public List loadAllPlayers (String playersFile) kaster PlayerLoadException {try {// ...} catch (IOException io) {throw new PlayerLoadException (io); }}

Dette kan være fint for å konsolidere mange forskjellige unntak i ett.

5.4. Kaster om Kastbar eller Unntak

Nå for en spesiell sak.

Hvis de eneste mulige unntakene som en gitt kodeblokk kan øke, er ukontrollert unntak, så kan vi fange og omlegge Kastbar eller Unntak uten å legge dem til vår metodesignatur:

public List loadAllPlayers (String playersFile) {try {throw new NullPointerException (); } fange (kaste t) {kaste t; }}

Selv om det er enkelt, kan ovennevnte kode ikke kaste et avkrysset unntak, og selv om vi kaster et avkrysset unntak, trenger vi ikke merke signaturen med en kaster klausul.

Dette er praktisk med proxy-klasser og metoder. Mer om dette finner du her.

5.5. Arv

Når vi merker metoder med en kaster nøkkelord, det påvirker hvordan underklasser kan overstyre metoden vår.

Under den omstendighet hvor metoden vår gir et avkrysset unntak:

offentlig klasse Unntak {public List loadAllPlayers (String playersFile) kaster TimeoutException {// ...}}

En underklasse kan ha en "mindre risikabel" signatur:

offentlig klasse FewerExceptions utvider unntak {@Override public List loadAllPlayers (String playersFile) {// overridden}}

Men ikke en “mer mer risikofylt ”signatur:

offentlig klasse MoreExceptions utvider unntak {@Override public List loadAllPlayers (String playersFile) kaster MyCheckedException {// overstyrt}}

Dette er fordi kontrakter bestemmes ved kompileringstidspunktet av referansetypen. Hvis jeg oppretter en forekomst av More Unntak og lagre den til Unntak:

Unntak unntak = nye More Unntak (); exception.loadAllPlayers ("fil");

Da vil JVM bare fortelle meg det å fange de TimeoutException, som er galt siden jeg har sagt det MoreExceptions # loadAllPlayers kaster et annet unntak.

Enkelt sagt, underklasser kan kaste færre sjekket unntak enn superklassen deres, men ikke mer.

6. Antimønstre

6.1. Svelgende unntak

Nå er det en annen måte som vi kunne ha fornøyd kompilatoren:

public int getPlayerScore (String playerFile) {try {// ...} catch (Unntak e) {} // <== catch and svelg return 0; }

Ovennevnte kallessvelger et unntak. Det meste av tiden ville det være litt vondt for oss å gjøre dette fordi det ikke tar opp problemet og det holder også annen kode i stand til å løse problemet.

Det er tider når det er et kontrollert unntak som vi er sikre på at bare aldri vil skje. I disse tilfellene bør vi fortsatt i det minste legge til en kommentar om at vi med vilje spiste unntaket:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {// this will never happen}}

En annen måte vi kan "svelge" et unntak er å skrive ut unntaket fra feilstrømmen ganske enkelt:

public int getPlayerScore (String playerFile) {try {// ...} catch (Unntak e) {e.printStackTrace (); } returner 0; }

Vi har forbedret vår situasjon litt ved i det minste å skrive feilen ut et sted for senere diagnose.

Det ville imidlertid være bedre for oss å bruke en logger:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {logger.error ("Kunne ikke laste poengsummen", e); retur 0; }}

Selv om det er veldig praktisk for oss å håndtere unntak på denne måten, må vi sørge for at vi ikke svelger viktig informasjon som innringere av koden vår kan bruke for å løse problemet.

Til slutt kan vi utilsiktet sluke et unntak ved ikke å ta det med som en årsak når vi kaster et nytt unntak:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {throw new PlayerScoreException (); }} 

Her klapper vi oss selv på ryggen for å varsle innringeren vår om en feil, men vi klarer ikke å inkludere IO Unntak som årsak. På grunn av dette har vi mistet viktig informasjon som innringere eller operatører kan bruke til å diagnostisere problemet.

Vi gjør det bedre:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {throw new PlayerScoreException (e); }}

Legg merke til den subtile forskjellen med å inkludere IO Unntak som årsaken av PlayerScoreException.

6.2. Ved hjelp av komme tilbake i en endelig Blokkere

En annen måte å svelge unntak på er å komme tilbake fra endelig blokkere. Dette er dårlig fordi JVM ved å returnere brått vil slippe unntaket, selv om det ble kastet fra av koden vår:

public int getPlayerScore (String playerFile) {int score = 0; prøv {kast ny IOException (); } til slutt {retur score; // <== IOException er droppet}}

I henhold til Java Language Specification:

Hvis utførelsen av prøveblokken fullføres brått av annen grunn R, så blir den endelige blokken utført, og så er det et valg.

Hvis den endelig blokken fullføres normalt, så fullføres prøveerklæringen brått av grunn R.

Hvis den endelig blokken fullføres brått av grunn S, deretter fullføres prøveuttalelsen brått av grunn S (og grunn R blir forkastet).

6.3. Ved hjelp av kaste i en endelig Blokkere

I likhet med bruk komme tilbake i en endelig blokk, unntaket kastet i en endelig blokk vil ha forrang over unntaket som oppstår i fangstblokken.

Dette vil "slette" det opprinnelige unntaket fra prøve blokkerer, og vi mister all den verdifulle informasjonen:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException io) {throw new IllegalStateException (io); // <== spist av endelig} til slutt {kast ny OtherException (); }}

6.4. Ved hjelp av kaste som en gå til

Noen ga seg også i fristelsen til å bruke kaste som en gå til uttalelse:

offentlig ugyldig doSomething () {prøv {// haug med kode kaste ny MyException (); // andre haug med kode} fangst (MyException e) {// tredje haug med kode}}

Dette er rart fordi koden prøver å bruke unntak for strømningskontroll i motsetning til feilhåndtering.

7. Vanlige unntak og feil

Her er noen vanlige unntak og feil som vi alle støter på innimellom:

7.1. Sjekket unntak

  • IO Unntak - Dette unntaket er vanligvis en måte å si at noe på nettverket, filsystemet eller databasen mislyktes.

7.2. RuntimeExceptions

  • ArrayIndexOutOfBoundsException - Dette unntaket betyr at vi prøvde å få tilgang til en ikke-eksisterende matriseindeks, som når vi prøvde å få indeks 5 fra en matrise med lengde 3.
  • ClassCastException - dette unntaket betyr at vi prøvde å utføre en ulovlig rollebesetting, som å prøve å konvertere en String inn i en Liste. Vi kan vanligvis unngå det ved å utføre defensiv tilfelle av sjekker før avstøpning.
  • IllegalArgumentException - dette unntaket er en generell måte for oss å si at en av de angitte metodene eller konstruktorparametrene er ugyldig.
  • IllegalStateException - Dette unntaket er en generell måte for oss å si at vår indre tilstand, som tilstanden til vårt objekt, er ugyldig.
  • NullPointerException - Dette unntaket betyr at vi prøvde å referere til a null gjenstand. Vi kan vanligvis unngå det ved å enten utføre defensiv null sjekker eller ved hjelp av Valgfri.
  • NumberFormatException - Dette unntaket betyr at vi prøvde å konvertere en String til et tall, men strengen inneholdt ulovlige tegn, som å prøve å konvertere “5f3” til et tall.

7.3. Feil

  • StackOverflowError - dette unntaket betyr at stabelsporet er for stort. Dette kan noen ganger skje i massive applikasjoner; imidlertid betyr det vanligvis at vi har noen uendelig rekursjon som skjer i koden vår.
  • NoClassDefFoundError - dette unntaket betyr at en klasse ikke kunne lastes enten på grunn av ikke å være på klassestien eller på grunn av feil i statisk initialisering.
  • OutOfMemoryError - dette unntaket betyr at JVM ikke har mer minne tilgjengelig for å tildele flere objekter. Noen ganger skyldes dette en minnelekkasje.

8. Konklusjon

I denne artikkelen har vi gått gjennom det grunnleggende om unntakshåndtering, samt noen eksempler på god og dårlig praksis.

Som alltid kan all kode som finnes i denne artikkelen finnes på GitHub!


$config[zx-auto] not found$config[zx-overlay] not found