Java
10. 예외처리 예외는어떤방법으로든지해결되어야합니다. 직접해결하기위해서는 try~catch 구문을사용할수도있으며 throws 구문을통해예외가발생했을때호출한메서드로예외를넘겨줄수도있습니다. 이장에서는예외의종류와예외처리방법, 사용자정의예외클래스와예외를발생시키는방법들에대해설명합니다. assert 키워드를통해특정조건을만족하는지여부를체크할수있는기능에대해서도알아보겠습니다. 그리고예외처리와 Assertion 기능을통해서요구사항정의단계에서선행조건과후행조건을올바른코드로옮기기위한지침을소개합니다. 10장의주요내용입니다. - try~catch - throws - 사용자정의예외 - throw 문 - Assertion 코드작성 - Assertion 코드컴파일및실행
Java 10.1. 예외처리 예외처리 (Exception Handling) 는오류발생에대한대처방법중의하나입니다. 예외처리는시스템스스로오류를복구하는것이아니고오류발생가능성이있는부분에대한처리를미리프로그램해주는것입니다. 오류의종류는심각한오류 (serious error) 와심각하지않은오류 (mild error) 로나눌수있습니다. 심각한오류는메모리가부족할때발생하는 OutOfMemoryError 등이있습니다. 심각한 오류가발생하면오류는복구될수없습니다. 심각한오류가발생할경우프로그램은중지 됩니다. 자바에서는이런경우에만오류 (Error) 라고합니다. 가벼운오류는예외 (Exception) 라고부르며, 오류로취급은되지만프로그램이중지되지는않게할수있는오류를말합니다. 예를들면존재하지않는파일을읽을때발생하는 FileNotFoundException, 배열의범위를넘어서는 ArrayIndexOutOfBoundsException 등이있습니다. 존재하지않는파일을읽으려고할때에프로그램은멈추지않고파일이없음을알리는오류메시지만출력하고계속진행되게할수있을것입니다. 이와같은예외가발생했을경우처리하는것을예외처리라고합니다. 다음프로그램은실행시예외가발생하도록만든예입니다. ExceptionProblemExample.java 1: public class ExceptionProblemExample { 2: public static void main (String[] args) { 3: int i = 0; 4: String[] greetings = {" 안녕하세요.", " 반갑습니다.", " 또오세요."}; 5: 6: while (i < 4) { 7: System.out.println (greetings[i]); 8: i++; 9: } 10: 11: System.out.println(" 메인의마지막입니다."); 12: } 13: } 이프로그램은오류가발생하는데컴파일발생하는오류가아니고실행시발생하는예외입니다. while 문장은 i값이 3이될때까지 greeting[i] 데이터를출력하는코드블록을실행합니다. 그러나배열 greetings는 greetings[2] 까지데이터가저장되어있기때문에 i값이 3일경우배열의범위를넘어참조하게됩니다. 이렇게배열의범위를넘어선데이터를참조하려할경우자바에서는예외가발생하여프로그램실행이중단됩니다.
Chapter 예외처리 10 프로그램의실행결과는다음과같습니다. 실행결과를보면, greetings[2] 까지출력하고 greetings[3] 을실행할때에 ArrayIndexOutOfBoundsException 이발생했음을알수있습니다. 이들예외상황들은자 바프로그램을개발하는도중에종종만나게됩니다. 앞의프로그램을수정하여오류가발생하지않게하려면 5 라인을다음과같이기술할수 있습니다. 5: while (i < 3) { 그런데 7 라인을수정하지않고문제를해결할수있는방법이있습니다. 즉배열의참조되는 인덱스를수정하지않고프로그램이정상실행되도록하는방법에대하여알아보겠습니다. 예외처리가발생하는이유는프로그램을잘못작성했을수도있지만, 그렇지않을경우도 있습니다. 예외는여러상황에서발생할수있으며, 예외처리는예외사항을찾아내이를처리 한다음계속프로그램이진행되도록하는것입니다. 예외처리방법은크게두가지로나눌수있습니다. 첫번째는 try catch 문을사용하는방법과, 두번째는 throws 선언문을사용하는방법이있습니다. try ~ catch 문을이용하여예외상황을처리하기위해받아들이는것을 예외를잡는다 (catch) 라고표현하고, throws 선언문을이용하여예외처리를자신을호출한메서드에게넘겨주는것을 예외를던진다 (throws) 라고표현합니다. try ~ catch 문장은예외를직접처리하는경우에사용하며, throws 선언문은메서드선언부에표기하여메서드를호출하는호출자에게예외를처리하도록합니다. 이후설명하는내용들은 try ~ catch와 throws 에대해서더자세하게설명하고있습니다. 예외처리에서기억해야할것이있습니다. 예외처리를사용하면예외가발생했을때 JVM 에서예외처리를담당하는부분을자동적으로호출해주지만, 예외를처리하기위한부분 은프로그래머가직접작성해야합니다.
Java 10.1.1. try ~ catch 예외를처리하는방법중에서 try catch 문을이용한예외처리방법을살펴보겠습니다. 다음코드는앞에서배열의범위를넘어선데이터를출력할때오류가발생했던코드를 try catch 문을이용하여예외처리를하는예입니다. while 문의조건문은변경하지않은 상태에서배열의범위를넘어참조했을경우예외가발생하고이를처리하는예입니다. TryCatchExample.java 1: public class TryCatchExample { 2: public static void main (String[] args) { 3: int i = 0; 4: String[] greetings = {" 안녕하세요.", " 반갑습니다.", " 또오세요."}; 5: while (i < 4) { 6: try { 7: System.out.println(greetings[i]); 8: } 9: catch (ArrayIndexOutOfBoundsException e) { 10: System.out.println(" 예외발생했습니다."); 11: System.out.println(" 예외가발생한원인은 " + e.getmessage()); 12: System.out.println(" 예외처리를완료했습니다."); 13: } 14: finally { 15: System.out.println("finally 문은항상실행됩니다."); 16: } 17: i++; 18: } 19: System.out.println(" 메인의마지막입니다."); 20: } 21: } try catch 문장은크게 try, catch, finally 블록으로구성됩니다. 첫번째, try 블록으로예외가발생할가능성이있는문장을포함합니다. 6: try { 7: System.out.println(greetings[i]); 8: } try 블록에서 greeting[3] 의내용을출력하는도중에예외상황이발생할것입니다. 이처럼 예외가발생할것이라고예상되는문장들은 try 블록안에기술합니다. try 블록은단독으 로사용할수없습니다. 반드시 catch 블록또는 finally 블록과함께사용해야합니다.
Chapter 예외처리 10 두번째, catch 블록은 try 블록에서발생한예외상황을감지하여예외가발생했을때적 절한조치를취하기위해사용하는곳입니다. 9: catch (ArrayIndexOutOfBoundsException e) { 10: System.out.println(" 예외발생했습니다."); 11: System.out.println(" 예외가발생한원인은 " + e.getmessage()); 12: System.out.println(" 예외처리를완료했습니다."); 13: } catch 블록의실행은 try블록에서예외가발생했을때입니다. try블록에서예외가발생하지않으면 catch 블록은실행되지않습니다. catch 블록에서기술한예외가아닌다른예외가발생하면 catch 블록의예외는처리되지않습니다. 위의프로그램에서는 ArrayIndexOutOfBoundsException이발생했을경우에 catch 블록이실행됩니다. 이렇게 try블록안에서예외상황이발생하면 catch 블록이수행되며, 이때발생한예외는 ArrayIndexOutOfBoundsException 유형의인스턴스 (instance) 입니다. 따라서 catch 블록에서예외객체를이용할수있습니다. catch 블록에서는예외가발생한원인을출력하고있습니다. 세번째, finally 블록은 catch 블록과는다르게예외상황의발생여부에관계없이무조건 실행됩니다. 14: finally { 15: System.out.println("finally 문은항상실행됩니다."); 16: } try 문이실행되면 finally 블록은항상실행됩니다. 그래서 finally 문은항상실행됩니다. 라는문장은 try 문이실행될때마다계속출력됩니다. 프로그램실행도중에예외가발생해도프로그램이계속실행될수있는이유는프로그래머가예외상황을직접다룰수있기때문입니다. finally 블록은 try 블록에서할당받은자원을반납해야하는코드 35) 등을포함할수있습니다. finally는 try 블록또는 catch 블록에서 return문을만나더라도실행됩니다. 다음의경우에만 finally 블록이실행되지않습니다. System.exit() 구문을호출했을때. 전원이꺼져시스템이멈추었을때. finally 블록내부에서예외상황이발생했을때. try 블록을실행시키는스레드가죽었을때. try 블록에서한가지예외만발생하는것은아닙니다. catch 블록에서처리하려고하는 예외가아닌다른예외가 try 블록에서발생하면그예외는처리되지않습니다. 즉, 위의 35) 파일입 / 출력스트림이나데이터베이스커넥션등을닫아주는코드가있습니다.
Java 예에서 try블록에서 NullPointerException이발생하면그예외는처리되지않습니다. 예외가여러가지가발생할경우에는 catch블록을중복해서사용할수도있습니다. 그럴경우에일반적인예외 (Exception 계층구조에서상위예외클래스 ) 가먼저처리될수있으므로 Exception 계층구조에서상위클래스가하위클래스보다먼저 catch블록의인자로선언되어서는안됩니다. 다음코드는 try 블록에서여러개의예외가발생되는예입니다. CatchExample.java 1: public class CatchExample { 2: public static void main(string[] args) { 3: int a = (int)(math.random() * 100); 4: int b = (int)(math.random() * 4); 5: try { 6: System.out.printf("a=%d, b=%d, result=%d\n", a, b, divide(a, b)); 7: dosomething(a); 8: }catch(arithmeticexception ae) { 9: System.out.println("0 으로나누려고하고있습니다."); 10: System.out.println(ae.getMessage()); 11: }catch(exception e) { 12: System.out.println(" 예외가발생했습니다."); 13: System.out.println(" 예외발생원인 : "+e.getmessage()); 14: } 15: } 16: 17: public static int divide(int a, int b) { 18: return a/b; 19: } 20: 21: public static void dosomething(int a) throws Exception{ 22: if(a>50) { 23: throw new Exception("a 값이 50보다큽니다."); 24: } 25: System.out.println("50% 확률로실행됩니다."); 26: } 27: } 이코드에서 ArithmethicException catch 블록과 Exception catch 블록위치를바꾸면 "Unreachable catch block for ArithmeticException" 컴파일오류가발생합니다. 이코드는실행할때마다결과가다를수있습니다. dosomething() 메서드는인자값이 50보다크면따라서예외를발생합니다. throw 구문은예외를발생시키기위한키워드입니다. dosomething() 메서드에서예외를발생시키면 Exception catch 블록이실행되며, 발생한난수 b 값이 0일경우 ArithmethicException catch 블록이실행됩니다.
Chapter 예외처리 10 10.1.2. 예외의종류 다음그림은예외클래스들의상속관계를클래스다이어그램으로나타낸것입니다. 빈삼각형을갖는실선은클래스일반화관계, 즉상속관계를나타냅니다. 그림은예외와관련된클래스들을개략적으로나타낸것입니다. 여기에나타나있지않은클래스들도많이있으므로예외클래스들에대해서더많이알고싶으면 API문서를참조해야합니다. 오류또는예외와관련된클래스는 Throwable의하위클래스들이며, 이중 Error클래스와그하위클래스들에서정의되어있는오류상황이발생하면프로그램이종료되는것이당연합니다. 그러나 Exception과그하위클래스들에정의되어있는것과관련있는예외상황이발생하면이들예외는처리의대상될수있습니다. 예외클래스계층구조에서 Exception의하위클래스들중에서 RuntimeException은설계상의문제또는구현코드문제들과관련된예외클래스들입니다. 즉, 프로그램이정상적으로실행되고있는상황에서발생해서는안되는실행도중의상태를알려주는예외클래스들입니다. 이런실행중의예외들의예를들면, 나누려고하는숫자가 0인경우, 배열의인덱스를벗어난참조, null인레퍼런스변수참조, 형변환예외, 보안과관련된예외상황등이있습니다. 올바르게설계되어구현된프로그램에서는절대로이런종류의예외가발생하지않으므로처리하지않고그대로두는것이보통입니다. 이들예외가발생하면실행중메시지가나오고프로그램을종료하므로예외를수정할수있습니다. 그리고 RuntimeException과그하위예외들은예외처리를하지않아도컴파일타임에는예외가
Java 발생할가능성이있는지체크하지않습니다. 그러나실행중에예외가발생하면프로그램 의실행은중단될수있습니다. 이들 RuntimeException 과그하위클래스들을컴파일시 예외발생유 / 무를판단하지않으므로비검증예외 (Unchecked Exception) 라고부릅니다. 비검증예외 (Unchecked Exception) 가아닌다른예외들이발생할가능성이있는메서드또는생성자를포함하는구문들은모두컴파일타임에예외처리여부를검사하기때문에반드시예외처리를해주어야합니다. 예를들면파일을찾을수없거나 URL을잘못지정한경우가그러한경우입니다. 이런것은사용자가잘못입력할때발생하며프로그래머가처리해야합니다. 예외들중에서 InterruptedException, IOException, SQLException 등과그하위예외들이발생할가능성이있는문장을작성할경우반드시예외처리코드를포함해주어야합니다. 개발자는 API 에서제공되는메서드를사용할때는사용하고자하는메서드가어떤예외 클래스를 throws 하고있는지확인하고그에맞는예외처리를해주어야할것입니다. 다음코드는검증 (Checked) 예외의예입니다. 검증예외를발생시키는메서드또는생성자 를사용할경우에반드시예외처리를해줘야컴파일오류가발생하지않습니다. CheckedExample.java 1: import java.io.ioexception; 2: 3: public class CheckedExample { 4: 5: public static void main(string[] args) { 6: System.out.println(" 값을입력받는프로그램입니다."); 7: byte[] data = new byte[100]; // 한번에 100byte 씩읽습니다. 8: try { 9: System.in.read(data); 10: } catch (IOException e) { 11: e.printstacktrace(); 12: } 13: System.out.print(" 입력한문자열은 : "); 14: System.out.println(new String(data).trim()); 15: } 16: } 위의프로그램은사용자로부터입력받은문자열을화면에다시출력하는예입니다. 사용자로부터문자열을입력받기위해 read() 메서드를사용했습니다. read() 메서드는 java.io.inputstream 클래스에정의되어있는메서드입니다. API 문서에서 read() 메서드를참고하면 Throws 항목에 IOException과 NullPointerException 이있는것을확인할수있습니다. Throws 항목에있는예외클래스들중에서비검증 (UnChecked) 예외가아닌예외가있을경우에는반드시예외처리를해줘야컴파일오류가발생하지않습니다. 비검증예외가아닌다른예외들은모두검증 (Checked) 예외입니다.
Chapter 예외처리 10 다음은자바언어에서미리정의된몇가지예외중에서발생빈도가높은비검증예외클래스를설명한것입니다. ArithmethicException : 일반적으로정수를 0(zero) 로나눗셈을할때발생합니다. NullPointerException : 인스턴스를만들기전에객체나메서드를액세스하면발생합니다. Image[] img= new Image[4]; System.out.println(img[0].toString()); ArrayIndexOutOfBoundException : 배열의인덱스를벗어난구성요소를액세스할때발생합니다. 10.1.3. throws 예외상황을다루는방법으로 try catch 구문외에 throws 구문이있습니다. try~catch 문이예외가발생했을때직접해결을하고자하는코드라면 throws는메서드를호출한곳으로예외가발생했음을알리는코드입니다. 즉예외처리를직접수행하지않고호출자에게예외를던지는 (throws) 방법입니다. 예외가발생하는원인이실행되는메서드에있지않고호출하는메서드에게있을경우사용하는방법입니다. 그래야만예외가발생되는근본원인을해결할수있을것입니다. 간혹 main() 메서드에서 throws 해놓은코드를보실수도있습니다. main() 메서드에서 throws 하는것은예외처리를 JVM에게넘기겠다는의미입니다. 그러나 JVM이그예외를받아서처리해주지는않습니다. 예외가발생하면 JVM은예외메지를출력하고프로그램을종료시킬것입니다. main() 에서 throws 하는코드가있었다면테스트를위해사용
Java 한코드에서검증 (Checked) 예외를발생시키는구문을포함하기때문이라고생각하시기 바랍니다. throws 구문에서예외상황이여러개있을때는 ","(comma) 로구분하여나열하여표기 합니다. modifier return-type methodname() throws Exception1, Exception2...{ method-body; } 먼저다음의코드를보겠습니다. public void exceptionoccur() throws ArrayIndexOutOfBoundsException {... // 이안에서 ArrayIndexOutOfBoundsException 이발생했다고가정. } exceptionoccur() 메서드안에서 ArrayIndexOutOfBoundsException이발생하면, try catch문을처리할수있지만 try catch문을사용하지않고메서드의선언부에 throws를사용해도처리가가능합니다. 이경우는 exceptionoccur() 를호출한메서드에게발생한예외를던지는것입니다. 다음코드는 throws 구문을이용해예외상황을처리하는예입니다. ThrowsExample.java 1: public class ThrowsExample { 2: 3: static String[] greetings = {" 안녕하세요.", " 반갑습니다.", " 또오세요."}; 4: 5: public static void main(string[] args) { 6: int i = (int)(math.random()*4); //0, 1, 2, 3중하나가랜덤하게생성 7: 8: try { 9: doit(i); 10: }catch(exception e) { 11: System.out.println("main 에서예외처리했습니다."); 12: } 13: } 14: 15: public static void doit(int index) throws ArrayIndexOutOfBoundsException { 16: System.out.println(greetings[index]); 17: } 18: } 소스코드를보면 main() 메서드에서 doit() 메서드를호출했습니다. doit() 메서드호출시 인자의값은랜덤하게발생되어적용됩니다. 그런데만일랜덤하게발생된숫자가 3 일경
Chapter 예외처리 10 우에는 doit() 메서드에서예외가발생합니다. 그런데, doit() 메서드의선언부에서예외를 throws를사용하고있으므로 ArrayIndexOutOfBoundsException이발생할경우, 자신을호출한메서드에게이예외를던져라 (throws) 라는의미로해석할수있습니다. 따라서이경우, doit() 메서드를호출한 main() 메서드로예외가던져지게되는데, main() 메서드의입장에서는결국, doit() 메서드를호출하는곳에서예외가발생한것이고, 그래서이를 try catch를이용해서해결하고있습니다. 처음부터 doit() 메서드안에서 try ~ catch 블록을이용해예외를처리할수있습니다. 그러나 doit() 메서드에서예외가발생하는이유가인수로넘어오는 index 값이기때문에예외가발생된근본원인을해결하기위해메서드를호출하는곳에예외를보내주는것입니다. 참고로재정의 (Overriding) 와관련해서한가지더알고있어야할것이있습니다. throws로선언된메서드를재정의할때에재정의하는자식클래스의메서드에서는부모클래스의메서드에서 throws한예외가아닌새로운예외는 throws 할수없습니다. 즉, 재정의되는자식메서드에서는부모의메서드에서 throws 하는예외에비하여더적은개수의예외를 throws 하거나, 부모의메서드에서 throws 하는예외클래스의하위클래스예외가 throws 될경우만가능합니다. 자세한내용은 메서드재정의와 throws 절에서배웁니다. 10.1.4. 사용자정의예외 앞에서살펴본바와같이예외는시스템에서자동으로발생시켜주기때문에발생된예외를 try catch 혹은 throws 를이용해처리하면됩니다. 뿐만아니라프로그래머가직접예외 클래스를만들수있고, 이를원하는시점에발생시킬수도있습니다. 사용자정의예외 (user defined exception) 클래스를만든다는것은보통의클래스를만 드는것과동일합니다. 그러나유의할점은사용자정의예외클래스는반드시 Exception 클래스또는그하위클래스를상속 36) 받아서만들어야한다는점입니다. 다음프로그램은예외클래스를직접만들어사용하는예를보인것입니다. ServerTimedOutException.java 1: public class ServerTimedOutException extends Exception { 2: private int port; 3: 4: public ServerTimedOutException(String message, int port) { 36) 반드시 Exception 클래스를상속받을필요는없습니다. Exception 클래스뿐만아니라그하위클래스이면어떤클래스라도가능합니다.
Java 5: super(message); 6: this.port = port; 7: } 8: 9: public int getport() { 10: return port; 11: } 12: } 클래스선언부에서 Exception 클래스를상속받은후 ServerTimedOutException 클래스를정의하고, 나머지는일반적인클래스선언과동일합니다. 그리고예외클래스들도보통의클래스들처럼멤버변수를포함할수있습니다. 예외클래스도객체를생성할때멤버변수의값을초기화하기위해생성자를포함할수있습니다. super(message); 구문은사용자정의예외를발생시킬때예외의원인을예외객체가저장할수있도록합니다. 예외클래스도보통클래스들처럼메서드를포함할수있습니다. 10.1.5. throw 사용자가직접선언한예외클래스또는 API에서제공하는예외클래스들을이용해서예외를발생시키려면 new 키워드를이용하여객체를생성하는것만으로는안됩니다. 다음과같이발생시키고싶은예외클래스의인스턴스를만든후, "throw"(throws가아니라 throw입니다.) 키워드를이용하여예외가발생되도록해야합니다. throw는사용자가만든예외를발생시킬때만사용할수있는것은아닙니다. 예외클래스로정의된예외라면어떤예외든지 throw를이용해예외를강제로발생시킬수있습니다. throw new ServerTimedOutException("Could not connect", 80); 다음프로그램은사용자가예외를정의하고발생시키는예입니다. 이전사용자정의예외 에서설명했던클래스 (ServerTimedOutException) 가작성되어있어야합니다. TestUserException.java 1: public class UserExceptionExample { 2: String defaultserver = "javaspecialist.co.kr"; 3: String alternativeserver = "consolbook.com"; 4: 5: public void connectme(string servername) throws ServerTimedOutException { 6: int success; 7: int porttoconnect = 80; 8: success = open(servername, porttoconnect); 9: if (success == 0) { 10: throw new ServerTimedOutException("Could not connect", 80);
Chapter 예외처리 10 11: } 12: } 13: public void findserver() { 14: try { 15: connectme(defaultserver); 16: System.out.println(defaultServer + " 에연결되었습니다."); 17: } 18: catch (ServerTimedOutException e) { 19: System.out.println( "Server timed out, trying alternative..."); 20: try { 21: connectme(alternativeserver); 22: System.out.println(alternativeServer + " 에연결되었습니다."); 23: } 24: catch (ServerTimedOutException e1) { 25: System.out.println(" 서버연결에실패했습니다."); 26: System.out.println( "Error : " + e1.getmessage() + "connecting to port " + e1.getport()); 27: } 28: } 29: } 30: public int open (String servername, int port) { 31: return (int)(math.random() * 2);//0 또는 1이리턴됩니다. 32: } 33: public static void main (String[] args) { 34: UserExceptionExample ue = new UserExceptionExample(); 35: ue.findserver(); 36: } 37: } 이예제는실제서버에접속하는프로그램은아닙니다. 사용자정의예외와 throw 구문을이해시키기위한용도로만들어진예외입니다. 예제코드에서프로그램시작점은 main() 메서드입니다. 이예제는서버에접속을시도하다가접속에문제가발생하면이미정의한 ServerTimedOutException을발생시킵니다. 주의할부분은 connectme() 메서드에서 open() 메서드를통해서버에접속을시도하는코드입니다. 이미정의되어있는 open() 메서드는 0또는 1값을반환합니다. 0을리턴하면접속에실패한것으로간주하여예외를만들고이를 throw합니다. throw 구문은예외를강제로발생시킬때사용하는구문입니다. if 블록에서 ServerTimedOutException이발생할수있으므로 connectme() 메서드선언부에 throws 구문을포함시켰습니다. 예외의발생원인이서버주소에기인한것일수있기때문에예외를 try ~ catch 블록으로처리하지않고 throws 선언문으로처리한것입니다. 예외가발생되면 try ~ catch문을이용예외상황을처리하게됩니다. catch 블록에는다른서버로접속시도를합니다.
Java 10.1.6. 메서드재정의와 throws 메서드를재정의할때부모에서던지지않은새로운예외는던질수없다고했습니다. 메 서드재정의와 throws 대해좀더알아보겠습니다. 아래그림은 4개클래스를보여주고있습니다. 닫힌화살표를갖는실선은일반화 (Generalization) 를의미합니다. 즉, Super와 Sub 클래스는상속관계가있다는의미입니다. 열린화살표를갖는실선은연관관계 (Association) 를의미하며화살표를받는클래스의인스턴스가화살표를주는클래스의멤버로정의되어있음을의미합니다. 즉, Super 클래스의멤버변수로 FileSystem 객체가선언되어있다는것입니다. 전체적으로 Super 클래스는 FileSystem 클래스의기능을사용하고, Sub 클래스는 Database 클래스의기능을사용하고있음을보여주고있습니다. 시스템이초기개발되었을때에는 Super 클래스와 FileSystem 클래스만존재하였고, doit() 메서드는 FileSystem 클래스를이용해데이터를저장하고있었다고가정합니다. 이후시스템업그레이드가필요하여기존을확장할필요가있어 Database 클래스가추가된것입니다. Database의기능을사용하는클래스인 Sub는 Super를상속받아 doit() 메서드를재정의해야하는상황이발생한것입니다. 그런데 FileSystem 클래스의 save() 메서드는 IOException을 throws 하도록정의되어있었는데, Database 클래스의 insert() 메서드는 SQLException을 throws 하도록정의되어있다는것입니다. 이런상황에서 Sub의 doit() 메서드를어떻게재정의해야하는것이주어진과제입니다. 다음그림은 Super 클래스를상속받은 Sub 클래스에서 doit() 메서드가 SQLException 을 throws 할때발생하는오류메시지입니다.
Chapter 예외처리 10 이런상황을해결하는방법은재정의하는메서드에서 try 블록을이용해예외가발생한 코드를작성합니다. 그리고 catch 블록에서부모의예외를 throw 하도록하면재정의하 는메서드에서는부모의메서드에서 throws 한예외를 throws 할수있습니다. try { db.insert(num); } catch (SQLException e) { throw new IOException(e.getMessage()); } 다음설명하는코드들은메서드재정의시예외처리방법을보여주기위한클래스들입니다. FileSystem 클래스는파일에데이터를저장하는것을시뮬레이션하기위한코드입니다. save() 메서드의인자로전달된 num 값이 0 보다작으면예외를발생시키도록하고있습 니다. FileSystem.java 1: import java.io.ioexception; 2: 3: public class FileSystem { 4: public void save(int num) throws IOException { 5: if(num<0) { 6: throw new IOException("num 이 0보다작습니다."); 7: } 8: System.out.println("File 에저장됐습니다."); 9: } 10: } Super 클래스는 FileSystem 클래스의 save() 메서드를호출하고있습니다. save() 메서드 에서 IOException 이발생할가능성이있으므로 throws 를이용하여예외처리하였습니다. try~catch 문을이용하지않는이유는 save() 메서드인자로전달될값을 doit() 메서드를 호출하는곳으로부터전달받기때문입니다. Super.java 1: import java.io.ioexception; 2: 3: public class Super { 4: FileSystem fs = new FileSystem(); 5: 6: public void doit(int num) throws IOException { 7: System.out.println("Super.doIt"); 8: fs.save(num); 9: } 10: }
Java Database 클래스는데이터베이스에데이터를저장하는것을시뮬레이션하기위한코드입 니다. insert() 메서드에서 num 값이 100 보다크면 SQLException 을발생시킵니다. Database.java 1: import java.sql.sqlexception; 2: 3: public class Database { 4: public void insert(int num) throws SQLException { 5: if(num>100) { 6: throw new SQLException("num 이너무큽니다."); 7: } 8: System.out.println(" 데이터베이스에저장되었습니다."); 9: } 10: } Sub 클래스는 Super 클래스를상속받았습니다. 그리고 doit() 메서드를재정의하고있습 니다. insert() 메서드가 SQLException 을발생시키기때문에예외처리를해야합니다. 그 렇다고해서아래코드처럼작성하면오류가발생합니다. 그이유는앞에서언급했던것처 럼부모에서던지지않는새로운예외는재정의하는메서드에서던질수없기때문입니 다. public void doit(int num) throws SQLException {//Error System.out.println("Sub.doIt"); db.insert(num); } 다음코드는바르게작성된 Sub 클래스입니다. Sub.java 1: import java.io.ioexception; 2: import java.sql.sqlexception; 3: 4: public class Sub extends Super { 5: Database db = new Database(); 6: 7: public void doit(int num) throws IOException { 8: System.out.println("Sub.doIt"); 9: try { 10: db.insert(num); 11: } catch (SQLException e) { 12: throw new IOException(e.getMessage()); 13: } 14: } 15: }
Chapter 예외처리 10 다음코드는위의코드들을테스트하기위한코드입니다. Super 로선언된객체변수를이 용해호출하는 doit() 메서드는생성하고참조되는객체의유형에따라다르게동작할것 입니다. Super 클래스의인스턴스를참조하면 FileSystem 클래스를통해데이터를저장하 며, Sub 클래스의인스턴스를참조하면 Database 클래스를통해데이터를저장하는것을 시뮬레이션할수있습니다. ThrowsOverridingExample.java 1: import java.io.ioexception; 2: import java.util.random; 3: 4: public class ThrowsOverridingExample { 5: 6: public static void main(string[] args) { 7: Super[] works = new Super[2]; 8: works[0] = new Super(); 9: works[1] = new Sub(); 10: 11: Random rand = new Random(); 12: int data = rand.nextint(200); 13: for(super s : works) { 14: try { 15: s.doit(data); 16: } catch (IOException e) { 17: System.out.println(e.getMessage()); 18: } 19: } 20: }//end main 21: }//end class 10.1.7. 자원관리자동화와멀티캐치 자원관리자동화 (automatic resource management) 와멀티캐치 (Multi-catch) 는 Java SE 7(JDK 1.7) 버전에추가된기능입니다. 자원관리자동화는 try문뒤에괄호를치고, 변수를선언할수있는데이렇게하면 try가끝날때해당변수에대한 close가자동으로호출됩니다. 사실좀더정확하게는자원관리자동화기능을사용하면컴파일러는자동으로 finally 블록에자원반납코드를넣어줍니다. 역컴파일 (decompile) 해보면알수있습니다. 자원관리자동화기능은 finally 블록에서자원해제를위한코드를넣지않아도된다는장점은있지만 catch문에서해당변수에접근이안되는단점이있습니다. 또한 try 구문에서선언된클래스는 AutoCloseable 인터페이스를구현한클래스이어야합니다. try(fileinputstream in = new FileInputStream("a.txt"))
Java 멀티캐치기능은하나의 catch 블록에다수의예외를기술하는것입니다. 하나의 catch 문으로다수의예외를처리해줄수있습니다. 멀티캐치의예외들은계층구조가없어야합니다. catch(nullpointerexception IOException ex) 다음은자원관리자동화예외처리구문예입니다. 예에서 9 라인은자원관리자동화의예를 보여주고있으며, 11 라인은멀티캐치예를보여주고있습니다. AutoResourceManagerExample.java 1: import java.io.*; 2: 3: public class AutoResourceManagerExample { 4: 5: public static void main(string[] args) { 6: 7: try(fileinputstream in = new FileInputStream("a.txt")) { 8: System.out.println("read data : " + in.read()); 9: }catch(nullpointerexception IOException ex) { 10: // System.out.println(in); //in 변수참조못함 11: System.out.println(" 예외처리합니다 "); 12: System.out.println(ex.toString()); 13: } 14: 15: } 16: } 이예에서 try 블록에괄호를이용해서객체를생성하는코드를넣을것을볼수있습니다. 이렇게하면컴파일러는자동으로 finally 블록에서해당리소스자원을반납하는코드를 넣어줍니다. 이것을보고예외처리시자원관리자동화라부릅니다. 하나의 catch문에서다수의예외를처리해줄수있습니다. 이렇게하면여러예외를처리하기위해 catch 블록을반복해서작성하지않아도됩니다. 이러한문장을멀티캐치문장이라부릅니다. 멀티캐치문장작성시주의할사항이있습니다. 하나의 catch 문에다수의예외를처리할경우기술되는예외들은계층구조가없어야합니다. 예를들면 IOException과 FileNotFoundException을동시에잡을수는없습니다. 자원관리자동화와멀티캐치구문은반드시사용할필요는없습니다. 필요한경우사용할수있습니다. Java SE 7(JDK 1.7) 에추가된예외에대해더자세하게알고싶으면아래주소를참고하 세요. http://www.oracle.com/technetwork/articles/java/java7exceptions-486908.html
Chapter 예외처리 10 10.2. Assertion 이기능은 JDK 1.4 버전에추가되었습니다. Assertion을영어사전에서찾아보면 단정, 단언, 주장 이라고되어있습니다. 자바프로그램에서 assertion 은특정내용을단정짓는것입니다. Assertion을굳이우리말로변역할필요는없습니다. 그냥 어써션 이라고읽으면됩니다. assertion은오류가없는프로그램을작성하기위한하나의기술입니다. 프로그램내에서, 특정조건이성립해야하는장소에검증용코드를넣어그조건에위반하고있는경우는오류를출력해프로그램상태를체크할수있도록합니다. 이러한방법은프로그램의버그의수정을도와, 보다견고한프로그램을작성하는것이가능해집니다. assert 구문은프로그래머가자신의프로그램에대한가정을확신시키는문장으로 boolean 수식을가지고주어진조건을만족하지않을경우즉, 수식의결과가 false 일경우예외를발생시키는키워드입니다. assertion 기능이없었을때개발자들은검증용코드를디버그메시지 (println) 또는예외를 사용했었습니다. 아래의코드들을예를들어설명하겠습니다. 입력값이 0 일때사용자인 증처리를하는코드입니다. 다음코드는검증구문을포함하지않았습니다. int x = new Scanner(System.in).nextInt(); if(x==0) { System.out.println(" 인증되었습니다."); } 위코드는 x 값으로 0 이들어갈경우에는 if 블록이정상적으로실행되지만그렇지않을경 우에는 if 블록은실행되지않습니다. 어떤값이입력이확인할필요가있거나 0 이아닌값 이들어갈경우에예외를발생시킬필요가있을것입니다. 그래서 x값을확인하기위한첫번째방법으로다음과같이디버그메시지를사용할수있습니다. int x = new Scanner(System.in).nextInt(); System.out.println("x=" + x); if(x==0) { System.out.println(" 인증되었습니다."); }
Java 두번째방법으로 x 에 0 이입력되지않았을경우예외를발생시킬수도있습니다. int x = new Scanner(System.in).nextInt(); if(x==0) { System.out.println(" 인증되었습니다."); }else { throw new Exception(" 인증에실패했습니다. x=" + x); } 그런데위두가지방법들중에서첫번째방법은항상 x의값이출력되고, 두번째방법은 x가 0이아니면예외가발생하게됩니다. 이는고객에게이프로그램의신뢰도를떨어뜨리게될것입니다. 물론첫번째방법의출력문을다음코드처럼사용한다면프로젝트가종료될시점에 Util 클래스의 DEBUG 상수값만변경하여메시지를출력하지않도록할수도있습니다. if(util.debug==true) System.out.println("x=" + x); 그런데 assertion 기능을사용하면위두가지를한번에해결할수있습니다. 다음은 assertion 이적용된코드입니다. int x = new Scanner(System.in).nextInt(); assert x==0 : " 인증에실패했습니다. x=" + x; if(x==0) { System.out.println(" 인증되었습니다."); } 위의코드처럼작성해놓으면개발자는개발할때 x 의값이 0 이아닐경우그값을화면 에출력되도록할수있습니다. 그리고시스템이운영될대사용자는 x 의값이 0 이더라도 메시지나예외없이프로그램을사용할수있을것입니다. assertion 기능을사용하기위해서는몇가지요구사항을만족해야합니다. 첫번째컴파일러의버전입니다. 컴파일러의버전이최소 JDK 1.4이상이어야합니다. 그리고코드에 assert 문장이포함되어야합니다. 마지막으로세번째실행시 assertion 기능을사용해야할경우 ( 개발시 ) -ea 옵션으로실행시켜야합니다. assertion 문장은 assert 키워드를이용하며두가지사용법이있습니다. assert Expression 1 ; assert Expression 1 : Expression 2 ;
Chapter 예외처리 10 다음과같은형태에서 Expression 1 의결과는 boolean 형이어야합니다. 만일 Expression 1 의결과값이 false 이면실행시 AssertionError 를발생시킵니다. 이때예 외의메시지는없습니다. assert Expression 1 ; 다음과같은형태즉, 두개의 Expression을가질때는먼저 Expression 1 의결과는 boolean형이어야하며, Expression 1 의결과값이 false이면실행시 Expression 2 의결과를메시지로갖는 AssertionError를발생시킵니다. 여기서주의해야할점은 Expression 2 에는 void형메서드호출이올수없다 는것입니다. assert Expression 1 : Expression 2 ; 10.2.1. assertion 코드컴파일 assert문이있는프로그램은버전이 1.4임을알려주기위해컴파일시다음과같이옵션을넣어서컴파일해야합니다. 그러나이클립스를사용하신다면컴파일걱정은하지않아도됩니다. javac -source 1.4 FileName.java 10.2.2. assertion 코드실행 실행시킬때 -enableassertions 또는 -ea 옵션을사용해야합니다. assertion 코드실행을해제하려면 -disableassertion 또는 -da 옵션을사용해야합니다. 옵션이클래스이름앞에들어가는것을주의하세요. java -ea FileName 10.2.3. assertion 코드예 다음프로그램은 assert 키워드의사용법을알아보기위한 assert 키워드가사용되지않은
Java 예입니다. NoAssertionExample.java 1: public class NoAssertionExample { 2: public static void main(string[] args) { 3: int i = (int)(math.random() * 4) + 1;//1,2,3,4 값이랜덤하게발생됨 4: System.out.println(" 넘어온값 : " + doit(i)); 5: } 6: public static int doit(int a) { 7: switch(a) { 8: case 1: 9: System.out.println("1 이입력되었습니다."); 10: break; 11: case 2: 12: System.out.println("2 가입력되었습니다."); 13: break; 14: case 3: 15: System.out.println("3 이입력되었습니다."); 16: break; 17: } 18: return a; 19: } 20: } 앞의프로그램에서 switch 문이사용되었는데조건을만족하는 case 문이없으면실행되는 문장이없습니다. 따라서프로그램을작성하면서처리할문장이없음을간과할수있습니 다. 다음프로그램은앞의예제에서조건을만족하지않으면실행되는문장이없는예외상황을 처리하기위한기능을부여한예입니다. AssertionExample.java 1: public class AssertionExample { 2: public static void main(string[] args) { 3: int i = (int)(math.random() * 4) + 1;//1,2,3,4 값이랜덤하게발생됨 4: System.out.println(" 넘어온값 : " + doit(i)); 5: } 6: public static int doit(int a) { 7: switch(a) { 8: case 1: 9: System.out.println("1 이입력되었습니다."); 10: break; 11: case 2: 12: System.out.println("2 가입력되었습니다."); 13: break; 14: case 3: 15: System.out.println("3 이입력되었습니다."); 16: break; 17: default:
Chapter 예외처리 10 18: assert false : a; 19: } 20: return a; 21: } 22: } 이프로그램에서는 assert문이사용되었습니다. 실행할때입력값이 case문의값과일치하는값이없을경우에는실행시의옵션에따라예외를발생시킬수도있고, 반대로그렇지않게할수도있습니다. 이렇게하면사용자가메서드호출시예외상황에대하여더다양한방식으로프로그램을작성할수있습니다. 다음은앞의예제를컴파일하고실행한결과를나타낸것입니다. 랜덤값이 4 가발생되었 을경우각각의실행결과를보여주고있습니다. -ea 옵션을주지않고실행할경우 정상적으로실행되는경우입니다. 랜덤값이 4 일경우라도정상실행됩니다. -ea 옵션을주고실행할경우 정상적으로실행되는경우입니다. -ea 옵션을주고실행할경우랜덤값이 4일때 AssertionError를발생시켜사용자에게예외를알릴수있습니다. 프로그램개발단계에는 ea 옵션을주고실행하여테스트하면비정상적인상황들에대해모니터링이가능합니다. 프로그램운영단계에는 ea 옵션을주지않고실행하여예외상황이발생하더라도사용자화면에오류가나타나는일이없을것입니다.
Java 이클립스에서 Assertion 기능사용하기 이클립스는컴파일러의버전이 1.5 이상이면자동으로 Assertion 컴파일을수행합니다. 실행시옵션으로 Assertion 기능을사용할수있습니다. Run -> Run Configuration... 1. 실행시 Assertion 기능을활성화시키고싶은클래스를선택합니다. 2. Arguments 탭을클릭합니다. 3. VM arguments: 항목에 -ea 라고입력합니다. 다음코드는앞의코드의 assert 문장을 throw 문으로수정할수있습니다. AssertionError 가발생한다면 try~catch 문으로처리할수있습니다. AssertionCatchExample.java 1: public class AssertionCatchExample { 2: public static void main(string[] args) { 3: int i = (int)(math.random() * 4) + 1;//1,2,3,4 값이랜덤하게발생됨 4: try { 5: System.out.println(" 넘어온값 : " + doit(i)); 6: }catch(assertionerror ae) { 7: System.out.println(" 데이터를확인하세요. " + ae.getmessage()); 8: } 9: } 10: public static int doit(int a) { 11: switch(a) { 12: case 1: 13: System.out.println("1 이입력되었습니다."); 14: break; 15: case 2: 16: System.out.println("2 가입력되었습니다."); 17: break; 18: case 3: 19: System.out.println("3 이입력되었습니다."); 20: break;
Chapter 예외처리 10 21: default: 22: assert false : a; 23: } 24: return a; 25: } 26: } -ea 옵션을주고실행했을경우랜덤값이 4 이면 AssertionError 가발생하고이를 try ~ catch 블록을이용해처리한결과입니다. 10.2.4. assertion 과 exception 이내용은분석 / 설계시선행조건과후행조건을정의했을경우이를구현하기위한과정에 대한설명입니다. 난이도에비하여자주사용되는기능은아닙니다. 내용이어렵다생각되 면다음장으로넘어가도됩니다. Assertion 기능을후행조건확인및클래스불변조건에사용할수있습니다. 후행조건은리턴문장바로위에놓여메서드의처리결과를확인하기위한조건입니다. 클래스불변조건은클래스의내부상태를체크하여클래스가가지는멤버변수가변경이일어났을경우처리되도록하는코드를포함할수있습니다. 메서드가수행되기전에값의유효성을체크하는선행조건에는사용하는것은바람직하지 않습니다. 선행조건은메서드블록이수행되기위해만족되어야할필요조건입니다. 선행 조건이만족되지않으면이후메서드블록은시작되면안됩니다. 후행조건은메서드종료시만족해야하는조건으로메서드의정상동작여부에대한최소한의판단기준으로사용될수있습니다. 후행조건이만족되지않으면메서드가정상적으로동작하지않았다고할수있습니다. 그러나후행조건을만족하더라도메서드가올바르게수행되었다고판단할수는없습니다. 다음코드는선행조건과후행조건을 Assertion 으로처리한예입니다. PreAndPostConditionExample.java 1: public class PreAndPostConditionExample { 2: public static void main(string[] args) { 3: Account myaccount = new Account(" 홍길동 ", 100); 4: // myaccount.save(-10); 5: System.out.println(myAccount); 6: myaccount.withdraw(10); 7: System.out.println(myAccount); 8: } 9: } 10:
Java 11: class Account { 12: String user; 13: int balance; 14: 15: public Account(String user, int balance) { 16: super(); 17: this.user = user; 18: this.balance = balance; 19: } 20: 21: public void save(int amount) { 22: if(amount<=0) { 23: // 선행조건은확실하게체크해야합니다. 24: throw new IllegalArgumentException(" 입금액오류 - " + amount); 25: } 26: int prebalance = balance; 27: System.out.println(amount + " 원이입급되었습니다."); 28: this.balance = this.balance + amount; 29: 30: assert (this.balance == prebalance + amount); 31: } 32: public void withdraw(int amount) { 33: if(amount<=0) { 34: // 선행조건은확실하게체크해야합니다. 35: throw new IllegalArgumentException(" 출금액오류 - " + amount); 36: } 37: int prebalance = balance; 38: System.out.println(amount + " 원이출금되었습니다."); 39: this.balance = this.balance + amount; 40: 41: assert (this.balance == prebalance + amount); 42: } 43: 44: public String tostring() { 45: return user +" 님의잔고는 " + balance + " 입니다."; 46: } 47: } 위코드에서 save() 메서드와 withdraw() 메서드에서는선행조건을명시적으로비교한후예외를발생시켰습니다. 그러므로 -ea 옵션과는무관하게 amount가 0 이하일경우에는메서드가실행되지않게하였습니다. 후행조건의경우 save() 메서드와 withdraw() 메서드에 assertion 기능을사용했습니다. 입금후행조건은입금후잔액 = 입금전잔액 + 입금액이며출금후행조건은출금후잔액 = 출금전잔액-출금액입니다. 후행조건을만족하지못하면시스템은정상적으로동작했다고판단하기어렵습니다. 그러나후행조건이충족되었다고메서드가올바르게수행되었다고판단할수는없습니다.
Chapter 예외처리 10 시스템의요구사항을구현할때선행조건과후행조건이있다면 Exception 처리와 Assertion 처리를통해구현할수있습니다. Assertion 은현업에서자주사용되는기능은 아니므로참고만하고넘어가도됩니다.
Java 10.3. 마인드맵정리