ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [9주차]예외처리
    스터디/[whiteship]JAVA 2021. 11. 23. 23:28

    목표

    자바의 예외 처리에 대해 학습하세요.

    학습할 것 (필수)

    • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
    • 자바가 제공하는 예외 계층 구조
    • Exception과 Error의 차이는?
    • RuntimeException과 RE가 아닌 것의 차이는?
    • 커스텀한 예외 만드는 방법

    자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

    자바에서 프로그램 오동작을 막기위해 예외 처리를 제공한다. 아래는 자바에서 제공하는 예외 처리 방법이다.

    예외 처리 방법에 대해서 알게 되면 보다 안전하고 유연한 프로그래밍을 구사할 수 있게된다.(Jump to Java)

     

    Q. 예외는 언제 발생할까?

    문법 오류말고, 실제 프로그램에서 잘 발생하는 오류는 다음과 같은 예가 있다. (없는 파일을 불러올 때)

    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class Test {
        public static void main(String[] args) throws IOException {
    
            long start = System.currentTimeMillis();
            FileReader fileReader = new FileReader("/Users/kim/Documents/compileTest/file1.txt");
            BufferedReader bufferedReader = new BufferedReader(fileReader);
            int c;
            while((c=bufferedReader.read())!=-1){
                System.out.print((char)c);
            }
            long end = System.currentTimeMillis();
            System.out.println("\n");
            System.out.println((end - start));
        }
    }

    오류 구문을 보면 FileNotFoundException이 발생한다.


    또 다른 예는 다음과 같다.

    import java.io.IOException;
    
    public class Test {
        public static void main(String[] args) {
    
            int a = 4 / 0;
            System.out.println(a);
        }
    }

    정수 4를 0으로 나누는 작업은 ArithmeticException이란 오류가 발생한다.


    또 다른예는 우리가 흔히 알고리즘을 공부할 때 발생하는 에러이다.

     

    즉 배열의 인덱스값이 없는데 그 없는 인덱스를 참조할 떄  발생하는 경우이다.

    import java.io.IOException;
    
    public class Test {
        public static void main(String[] args) {
    
            int[] a = {1,2};
            System.out.println(a[2]);
        }
    }

    자바에선 이러한 에러가 발생하면 프로그램을 중단시킨다.

    에러는 예상하지 못하는 경우가 더 많고 빈번히 발생하기 때문에, 좀 더 안정적인 프로그래밍을 하려면 다음과 같이 예외처리 방법을 사용해야 한다. 


    • try,catch자바에서 예외처리를 할 때 흔히 사용하는 키워드로 다음과 같이 활용한다. 
    public class Test {
        public static void main(String[] args) {
            try{
            int[] a = {1,2};
            System.out.println(a[2]);
        }catch (Exception e){
                System.out.println("해당 인덱스가 없습니다.");}
        }
    }

    try키워드 내 구현체에서 예외 즉 오류가 발생하면 catch문의 구현체로 이동후 예외를 처리한다. catch내 발생하는 예외 클래스를 적어주면 되는데, 지금 작성한 코드는 모든 예외클래스의 최상위 클래스인 Exception을 사용했다. 좀 더 구체적인 예외를 받으려면 예상되는 예외클래스를 적어두면 된다.

    • finally

    예외처리에는 위에 try, catch 구문을 사용했다. 하지만 어떤 예외가 발생하더라도 반드시 실행되어야 하는 부분이 있다면 어떻게 해야할까? 다음 예제를 보자.

    import java.io.BufferedReader;
    import java.io.FileReader;
    
    public class Test {
        public static void main(String[] args) {
            try {
    
                FileReader fileReader = new FileReader("/Users/kim/Documents/compileTest/file1.txt");
                BufferedReader bufferedReader = new BufferedReader(fileReader);
                long currentTimeMillis = System.currentTimeMillis();
                System.out.println(currentTimeMillis); //추가된 부분, 현재 시간을 체크하여 프로그램 시간을 측정하기.
    
                int c;
                while ((c = bufferedReader.read()) != -1) {
                    System.out.println((char) c);
                }
    
                long endTimeMillis = System.currentTimeMillis();
                System.out.println(endTimeMillis - currentTimeMillis);
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
    
            }
        }
    }

    현재 시간을 측정하고 콘솔에 출력하는 기능을 추가했다. 하지만 예외가 발생하면 걸리는 시간을 구하는 소스코드가 발생하지 않을것이다.

    Q.그렇다면 try, catch문에 둘 다 System.out.println(endTimeMillis - currentTimeMillis); 구문을 넣으면 되지 않을까?

    A. 그래도 된다 하지만 이런 프로그래밍은 지양된다. 왜? 중복된 코드때문에... 따라서 try, catch구문 상관없이 무조건 실행해야 하는 코드를 위해서 finally키워드를 사용한다.

    import java.io.BufferedReader;
    import java.io.FileReader;
    
    public class Test {
        public static void main(String[] args) {
            long currentTimeMillis = System.currentTimeMillis();
            try {
                FileReader fileReader = new FileReader("/Users/kim/Documents/compileTest/file1.txt");
                BufferedReader bufferedReader = new BufferedReader(fileReader);
                System.out.println(currentTimeMillis); //추가된 부분, 현재 시간을 체크하여 프로그램 시간을 측정하기.
    
                int c;
                while ((c = bufferedReader.read()) != -1) {
                    System.out.println((char) c);
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                long endTimeMillis = System.currentTimeMillis();
                System.out.println(endTimeMillis - currentTimeMillis);
            }
        }
    }

    1이 출력된 결과를 볼 수 있다.

     

    • throw, throws

    throw와 throws 키워드는 예외를 처리를 위해 강제로 예외를 던지는 키워드임은 같으나 차이점은 존재한다.

    • throw
      • 프로그래머가 강제로 Exception을 발생시키는 것
      • throw 키워드 뒤에 예외클래스(정의된 예외클래스거나, 커스텀한 예외클래스)를 인스턴스로 생성후 예외를던진다.
        • throw new 예외클래스
      • throw키워드는 메소드 내에서 상위 블록으로 예외를 전달한다.
    import javax.annotation.processing.FilerException;
    
    public class Test {
    
        static class ExceptionTestClass extends Exception {
            
        }
    
        static void testMethod(String s) {
            try {
                if (s.equals("Exception")) {
                    throw new ExceptionTestClass();
                }
            } catch (ExceptionTestClass e) {
                System.out.println("not Exception");
            }
        }
    
        public static void main(String[] args) {
            testMethod("Exception");
            testMethod("Not Exception");
        }
    
    }

    Exception이란 커스텀한 예외 클래스를 RuntimeExcetpion클래스를 상속받아 설계했다. -> Runtime Exception과 Exception의 차이는 뒤에서 설명하겠다.

    소스코드를 보면, main 메소드에서 Exception이란 문자열을 testMethod에 보내게 되는데, 그 문자열이 "Exception"일 경우 글쓴이가 만든 ExceptionTestClass로 예외를 던진다는 것이다. 이말인 즉슨, 해당 메소드에서 예외를 처리하지 않고 throw키워드로 예외를 던진 클래스에서 예외처리를 할 수 있다는 얘기가 된다. 하지만 throws를 사용하지 않았기에, 해당 상위 블록인 catch에서 예외를 처리했다.

     

    • throws
      • 문법 : 메소드 시그니처 throws 예외클래스
      • throws 키워드 차이점은 해당 메소드를 호출한 지점(밑에 소스코드에선 main메소드)에서 예외처리를 해야 한다.
        • 즉 상위로 throws에 사용된 예외클래스를 보낼 수 있다. -> 이를 예외를 뒤로 미루기라고 한다.
    import javax.annotation.processing.FilerException;
    
    public class Test {
    
        static class ExceptionTestClass extends Exception {
    
        }
    
        static void testMethod(String s) throws ExceptionTestClass {
            if (s.equals("Exception")) {
                throw new ExceptionTestClass();
            }
    
        }
    
        public static void main(String[] args) {
            try {
                testMethod("Exception");
                testMethod("Not Exception");
            } catch (ExceptionTestClass e) {
                System.out.println("here is main Exception");
            }
        }
    
    }


    그렇다면, main메소드에서 예외처리를 하는 게 좋을까, testMethod에서 예외처리를 하면 좋을까?

    둘의 큰 차이점은, main메소드에서 보면 두 번 testMethod를 호출한다. 만약 main메소드에서 예외처리를 하게 된다면, 예외가 발생한 메소드 이후[testMethod("Exception")]의 메소드 호출은 무시가 될 것이다.

    import javax.annotation.processing.FilerException;
    
    public class Test {
    
        static class ExceptionTestClass extends Exception {
    
        }
    
        static void testMethod(String s) {
            try {
                if (s.equals("Exception")) {
                    throw new ExceptionTestClass();
                }
            } catch (ExceptionTestClass e) {
                System.out.println("testMethod 예외처 발생");
            }
            System.out.println(s);
        }
    
        public static void main(String[] args) {
            testMethod("Exception");
            testMethod("Not Exception");
        }
    
    }

    하지만 위와 같이 작성한다면 결과는 다음과 같다.


    자바가 제공하는 예외 계층 구조

    https://reference-m1.tistory.com/246

    • 에러, 예외 클래스는 Throwable 클래스를 상속받는다.
    • Java Exception클래스는 checked, unchked 클래스로 구분한다.
    • 위에 그림에서 빨간색은 checked 예외, 파란색은 unchecked예외 클래스에 해당한다.

    둘의 차이점은 아래 목차('RuntimeException과 RE가 아닌 것의 차이는?')에서 설명하고 있다. 


    Exception과 Error의 차이는?

     

    • Error는 복구 불가능한 심각한 오류를 말하며 복구가 어려운 문제를 말한다.
      • 메모리가 오버플로우 난다던가 메모리 부족관련 문제가 대표적이다.
    •  Exception은 에러보다 덜 심각한 오류로써  프로그래머가 예외처리를 함으로써 방지가 가능하다.

    RuntimeException과 RE가 아닌 것의 차이는?

    • Exception 클래스는 예외와 관련된 클래스의 부모 클래스이다. 
      • Exception은 컴파일 시 발생하는 예외로써 이미 작성된 코드에서 예측가능한 예외를 처리하거나 작성한다.
      • RuntimeException에 해당하지 않는 예외(IO Exception, Class not Found Exception..)를 checked Exception이라한다. 이에 해당하는 예외는 반드시 try, catch문으로 예외처리를 해주어야 한다 (아니면 예외 미루기를 하던가.)
      • 만약 checked Exception을 발생시키는 메소드는 메소드 시그니처다음 throw키워드를 사용해 예외를 던져야한다.
    •  RuntimeException은 Runtime시 발생하는 예외로써, 발생 할 수도 안 할수도 있는 예외일 경우 작성한다.
      • 컴파일이 관여하지 않는 예외이다.

    ※ 다른 말로  RunTimeExcetpion을 UnChecked Exception이라고하며 이에 해당하지 않는 Exception을 Checked Exception이라 한다.

     

    ProTech에선 Checked, UnChecked Exception을 다음과 같이 말하고 있다.

    https://www.protechtraining.com/content/java_fundamentals_tutorial-exceptions

    요약해보면 다음과 같다.

    - Checked Exception은 프로그래머는 유저에게 메세지 오류를 보여주거나 바람직한 처리를 강제화 할 수 있다.

    - Unchekd Exception은 복구할 수 없는 예외로 보통 많은 프로그래머들이 unchekd Exception을 무시하는 경향이 있는데, 만약 모든 예외를 unchecked 타입으로 변경하면 전체 예외처리가 나빠질 수 있으므로 주의해야한다. 

     

     

    아래 코드를 보자

    import javax.annotation.processing.FilerException;
    
    public class Test {
    
        static class ExceptionTestClass extends RuntimeException {
    
        }
    
        static void testMethod(String s) {
            if (s.equals("Exception")) {
                throw new ExceptionTestClass();
            }
    
        }
    
        public static void main(String[] args) {
                testMethod("Exception");
                testMethod("Not Exception");
        }
    }

    결과를 예상해보자. 오류가 나긴 나는데, 컴파일 시점 즉 IDE환경에서 오류라고 표시될까? -> 표시 안된다. 왜? throw로 예외를 던진 예외클래스는 RuntimeException즉 예측이 불가능(런타임시 발생하는 예외)라 컴파일 시점에서 예외를 해야하는지 확인을 못함.

    이렇게 바꾸면 어떨까?

     static class ExceptionTestClass extends Exception {
    
        }

    물론 컴파일 에러가난다. 따라서 예외처리를 반드시 해주어야한다.


    커스텀한 예외 만드는 방법

    커스텀한 예외를 만드는 방법은 위에 설명한 클래스 ExceptionTestClass가 해당된다. 즉, 예외클래스에 사용할 클래스를 Exception or RuntimeException 을 상속받아 구현한다

     

    이렇게 생성된 클래스로 생성자를 통해 간단한 문자열을 추가하는 기능을 한번 해보겠다.

    import javax.annotation.processing.FilerException;
    
    public class Test {
    
        static class ExceptionTestClass extends Exception {
            String s;
            ExceptionTestClass(String s){
                this.s=s+"기본생성자 추가하기";
                System.out.println(this.s);
            }
        }
    
        static void testMethod(String s) {
            try{
                if (s.equals("Exception")) {
                    throw new ExceptionTestClass(s);
                }
            }catch (ExceptionTestClass e){
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            testMethod("Exception");
            testMethod("Not Exception");
        }
    }


    [REFERENCES]

     

    위키독스

    온라인 책을 제작 공유하는 플랫폼 서비스

    wikidocs.net

     

    코딩교육 티씨피스쿨

    4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

    tcpschool.com

     

    Java Fundamentals Tutorial: Exceptions

    Java Fundamentals Tutorial: Exceptions

    www.protechtraining.com

     

    '스터디 > [whiteship]JAVA' 카테고리의 다른 글

    [13주차]IO  (0) 2021.02.20
    [6주차]상속  (0) 2021.02.15
    [5주차]클래스  (0) 2021.02.12
    [4주차]제어문  (0) 2021.01.26
    [3주차]자바가 제공하는 다양한 연산자  (0) 2021.01.23
Designed by Tistory.