[Java] Exception handling

Tomato·2019년 10월 18일
1

CSE class

목록 보기
6/7

Errors in programs

  • compile-time 에러
    • 컴파일러가 에러를 발생시키고 object 파일을 만들지 않는다.
    • e.g.) syntax error
  • Run-time 에러
    • 컴파일러는 아무런 에러를 발생시키지 않고 object 파일을 생성한다.
    • 런타임에, 프로그램은 에러 때문에 더 동작하지 않고 실행을 멈춘다.
    • e.g.) null인 인스턴스의 메서드를 호출하는 경우
  • Logical 에러
    • 프로그램은 에러 없이 동작하지만, 결과는 프로그래머가 예상하지 못한 것이다.
    • 슬프지만 프로그래머 탓이다.

이 셋 중 가장 중점적으로 다루어질 에러는 Run-time 에러이다.

Run-time error

런타임 에러는 errorsexceptions로 나누어진다.

  • Errors
    • 회복될 수 없고 프로그램은 멈춰야 한다.
    • e.g.) out of memory, stack overflow
  • Exceptions
    • 에러는 회복될 수 있다.
    • e.g.) arithmetic exception, class cast exception, null pointer exception...
    • 프로그래머는 예외(exception)가 발생했을 때 이 예외들을 관리할 수 있는 핸들러(handler)를 구현할 수 있다.

Errors and Exceptions

자바는 에러와 예외를 클래스로 정의한다.

  • 이들은 Throwable이라는 클래스의 subclass들이다. ThrowableObject 클래스의 subclass이다.
  • 예외 클래스는 두 카테고리로 나누어진다.
    • RuntimeException과 다른 예외 클래스로 나뉜다.
    • RuntimeException: 주로 프로그래머의 실수(..)에 의해 발생한다.
      • 0으로 나누기, class cast 등...
    • Others: 환경 그리고 사용자 에러에 의해 주로 발생한다.
      • FileNotFoundException, ClassNotFoundException...

Exception Handling: try-catch

위에서 예외는 관리할 수 있다고 언급했다. 예외를 관리함으로써, 프로그램은 무사히 실행을 계속할 수 있게 된다. 자바에서는 어떻게 예외를 관리할 수 있을까?

  • try-catch 블록을 사용함으로써 가능하다.
try {
    // statements where exceptions can occur
} catch (Exception1 e1) {
    // statements that will be executed when Exception1 occurs
}

Exception Handling: Example

다음 코드는 예외를 발생시킬 수 있다.

public class example {
	public static void main(String[] args){
		int number = 100;
		int result = 0;
		for(int i=0; i<10; i++) {
		result = number / (int)(Math.random() * 10);
		System.out.println(result);
		}
	}
}
  • int(Math.random() * 10)은 0이 될 수도 있다.
  • 만약 예외가 발생하면, 프로그램은 파괴되고 예외 메시지가 출력된다.

위 코드를 ArithmeticException을 다룰 수 있도록 바꿔 보자. 만약 나누는 수가 0이 되면 (그래서 예외가 발생하면), catch 블록이 실행된다. 프로그램은 실행을 계속할 수 있게 되는 것이다.

public class example {
	public static void main(String[] args) {
		int number = 100;
		int result = 0;
		for (int i = 0; i < 10; i++) {
			try {
				result = number / (int) (Math.random() * 10);
				System.out.println(result);
			} catch (ArithmeticException e) {
				System.out.println("0");
			}
		}
	}
}
  • 예외가 발생하면 JVM이 예외 객체를 생성해서 catch 블록에 인자로 넘긴다.

Program Flow in a try-catch Block

  • 만약 try block에서 예외가 발생하면:
    • 대응하는 예외를 잡는 catch block을 찾는다.
    • 찾으면, catch block이 실행되고, try-catch 블록 이후의 명령문이 계속된다.
  • try block에서 아무런 에러가 발생하지 않으면:
    • try block 실행 후, try-catch 블록 이후의 명령문이 계속된다.

The catch Block

  • catch 블록은 매개변수로 특정한 타입의 에러 변수를 가진다.
  • 만약 예외가 발생하면, 첫 catch block이 그것의 매개변수가 현재 예외의 super type인지 확인된다.
    • 맞다면 해당 catch 블록이 실행된다.
    • 아니라면 다음 catch 블록이 확인된다.
  • 다음 예시에서, catch 블록은 ArithmeticException이 발생하면 실행된다.
    • ExceptionArithmeticException의 superclass이다.
public class example {
	public static void main(String[] args) {
		try {
			System.out.println(3);
			System.out.println(0 / 0); // ArithmeticException!
			System.out.println(4);
		} catch (Exception e) {
			System.out.println(5);
		}
	}
}
  • 아래의 예시는 두 개의 catch 블록이 존재한다.
  • 예외는 ArithmeticException이므로, 첫 번째 catch 블록의 exception type과 일치한다.
  • 그러므로, 첫 번째 블록이 실행되고, try-catch 블록 이후로 실행이 계속된다. 즉, 두 번째 블록은 실행되지 않는다.
public class example {
	public static void main(String[] args) {
		try {
			System.out.println(3);
			System.out.println(0 / 0); // ArithmeticException!
			System.out.println(4);
		} catch (ArithmeticException e) {
			System.out.println("ArithmeticException occured!");
		} catch (Exception e2) {
			System.out.println("Exception occured!");
		}
	}
}

printStackTrace() and getMessage()

  • 예외는 자주 버그에 의해 발생하고, 프로그래머는 왜 예외가 발생했는지 알고 싶어한다.
  • printStackTracegetMessage()는 디버깅에 유용한 메서드들이다.
    • printStackTrace는 예외가 발생했을 때 콜 스택에 있던 메서드를 출력한다. 이 메서드를 catch 블록에서 명시적으로 호출하지 않아도 예외 발생시 출력된다. 단, catch 블록이 없는 채로 예외가 발생해서 이 메서드가 호출되면 프로그램은 그대로 중단된다.
    • getMessage는 예외 인스턴스에 저장돼 있던 메시지를 가져온다.
public class example {
	public static void main(String[] args) {
		try {
			System.out.println(3);
			System.out.println(0 / 0); // ArithmeticException!
			System.out.println(4);
		} catch (ArithmeticException ae) {
			ae.printStackTrace();
			System.out.println("exception message: " + ae.getMessage());
		}
	}
}

Generating an Exception

  • 프로그래머는 throw 키워드를 사용해서 의도적으로 예외를 만들 수 있다.
    • 예외 객체를 만든다. 생성자에 매개변수로 예외 메시지를 넘길 수 있다.
    • throw 키워드를 사용해서 예외를 일으킬 수 있다.
public class example {
	public static void main(String[] args) {
		try {
			Exception e = new Exception("I created the exception."); // becomes the exception message.
			throw e;
		} catch (Exception e) {
			System.out.println("exception message: " + e.getMessage());
			e.printStackTrace();
		}
		System.out.println("The program terminated normally.");
	}
}

Mandatory and Optional Exception Handling

  • 만약 예외를 일으킬 수 있는 명령문이 다루어지지 않는다면, 프로그래머는 에러를 생성한다.
    • RuntimeException의 subclass를 제외한 모든 예외가 해당된다.
    • 이 예외들은 프로그래머의 실수가 아닐 수 있다. 가령, 프로그래머가 맞는 코드를 작성했어도 FileNotFound 같은 예외는 발생할 수 있다.
    • 이 예외는 Checked exceptions라고 불린다.
public class example {
	public static void main(String[] args) {
		throw new Exception("My exception.");
	}
} // compile error
  • RuntimeException의 subclass인 예외들의 경우, 다루는 게 강제되지 않는다.
    • 이 예외들은 일반적으로 프로그래머의 실수에 의해 발생한다.
    • (프로그래머의 실수로) 많은 명령문들이 RuntimeException을 일으킬 수 있다. 이는 디버깅 등으로 해결할 수 있다.
    • 이 예외들은 Unchecked exception이라고 불린다.
public class example {
	public static void main(String[] args) {
		throw new RuntimeException("My exception.");
	} 
} // no error

Method that throw exceptions

  • try-catch 블록을 사용하는 것 대신에 우리는 메서드가 예외를 던지도록 할 수 있다.
    • 이는 이 예외들이 이 메서드 안에서 일어날 수 있음을 의미한다.
    • 만약 메서드가 Exception1을 던지면, 그것은 이 메서드 안에서 Exception1의 모든 subclass들이 발생할 수 있음을 의미한다.
    • 만약 메서드가 예외를 던지면, 이 메서드를 호출한 다른 메서드는 예외를 처리하거나 그도 예외를 던져야 한다.
public class example {
	public static void main(String args[]) {
		method1();
	}

	static void method1() {
		method2(); // Error: must handle Exception or throw Exception.
	}

	static void method2() throws Exception {
		throw new Exception(); 
        // even if this exception is ArithmeticException(RuntimeException), since method2() 'throws Exception', caller must handle method2() or throw the exception itself
        // if this exception is ArithmeticException and doesn't 'throws Exception', there is no compile error.
	}
} // compile error
  • 만약 method1()throws Exception을 수행하면, 예외를 처리할 필요가 없어진다.
    • 이 경우 method1()을 호출하는 main 메서드가 예외를 처리하거나 throw Exception을 수행해야 한다.
public class example {
	public static void main(String args[]) {
		method1(); // Error: must handle Exception or throw Exception.
	}

	static void method1() throws Exception {
		method2();
	}

	static void method2() throws Exception {
		throw new Exception();
	}
} // compile error
  • 만약 main 메서드가 throws Exception을 수행하면, 이 코드는 문제 없이 컴파일된다.
  • 그러나 예외는 프로그램을 런타임에 망치게 한다. 왜냐하면 다루어지지 않았기 때문이다. (main 메서드는 throw Exception을 해도 그를 다뤄 줄 메서드가 존재하지 않는다!)
public class example {
	public static void main(String args[]) throws Exception{
		method1();
	}

	static void method1() throws Exception {
		method2();
	}

	static void method2() throws Exception {
		throw new Exception();
	}
} // no error at compile time, but will cause program to crash at run time.
  • 적절한 프로그래밍 방법은 예외를 콜 스택 어딘가에서 처리하는 것이다.
public class example {
	public static void main(String args[]) {
		method1();
	}

	static void method1() {
		try {
			method2();
		} catch (Exception e) {
			System.out.println("Exception handled in method1");
			e.printStackTrace();
		}
	}

	static void method2() throws Exception {
		throw new Exception();
	}
}
  • File input/output에서 메인 메서드에 throws IOException을 적어 줬다.
  • 이는 FileOutputStream 생성자가 FileNotFoundException을 일으키기 때문이다.
    • FileNotFoundException은 IOException의 subclass이다.
  • 어떤 예외든 예외는 처리되어야 한다. 이는 IOException도 마찬가지이므로, try-catch 블록이 사용되어야 한다.
import java.io.FileOutputStream;
import java.io.IOException;

public class example {
	public static void main(String[] args) throws IOException {
		try {
			FileOutputStream output = new FileOutputStream("out.txt");
			String str = "hello world";
			byte[] bytes = str.getBytes();
			output.write(bytes);
			output.close();
		} catch (IOException io){
			System.out.println("IOException occured");
		}
	}
}

The finally Block

  • try-catch 블록 안에서, finally 블록이 끝에 포함될 수 있다.
    • 이 블록은 예외가 try 블록에서 일어나든 일어나지 않든 실행된다.
try {
	// statements that can cause exceptions.
} catch (Exception1 e1) {
	// statements for handling Exception1
} finally {
	// this block is executed whether or not an exception occurs in the try block.
	// this block must be placed at the end of a try-catch block.
}
  • return문이 try 블록에 있어도, 그 메서드가 반환되기 전에 finally 블록이 실행된다.
public class example {
	public static void main(String args[]) {
		example.method1();
		System.out.println("returned to main method after calling method1.");
	}

	static void method1() {
		try {
			System.out.println("the try block of method 1 is being executed.");
			return;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			System.out.println("the finally block of method 1 is being executed.");
		}
	}
}
  • 파일에서 읽을 때 메서드가 예외를 일으키지 않아도 예외는 반드시 다루어져야 한다.
import java.io.FileInputStream;
import java.io.IOException;

public class example {
	public static void main(String args[]) {
		byte[] b = new byte[1024];
		FileInputStream input = null;
		try {
			input = new FileInputStream("aaa.txt");
			input.read(b);
			System.out.println(new String(b));
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				input.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.out.println("The program exited normally.");
	}

}

try-with-resources

위의 코드는 여러 try-catch 블록을 써야 해서 번거롭다.

  • 이런 번거로움을 줄이기 위해, try-with-resources 방식을 사용한다.
    • try 이후 괄호에 리소스를 생성하는 문장을 넣는다.
      • 이 리소스는 AutoCloseable interface를 구현해야 한다.
    • 여러 리소스를 생성하기 위해서, 우리는 세미콜론(;)으로 나누어진 여러 문장을 쓸 수 있다.
    • 프로그램이 try 블록을 빠져나갈 때 자동으로 close() 메서드가 호출된다.
      • close()는 catch 블록이나 finally 블록이 실행되기 전에 호출된다.
import java.io.FileInputStream;
import java.io.IOException;

public class example {
	public static void main(String args[]) {
		byte[] b = new byte[1024];
		try (FileInputStream input = new FileInputStream("out.txt")) {
			input.read(b);
			System.out.println(new String(b));
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("The program exited normally.");
	}

}

Creating Custom Exceptions

  • 자바에서는 사용자가 직접 자신만의 새로운 예외를 생성할 수 있다.
class MyException extends Exception {
    MyException(String msg) {
        super(msg); // calls constructor of class Exception
    }
}
  • 다른 subclass와 유사하게, 우리는 클래스 정의에 변수와 메서드를 정의할 수 있다.
class MyException extends Exception {
    private final int ERR_CODE;
    MyException(String msg, int errCode){
        super(msg); // calls constructor of class Exception
        ERR_CODE = errCode;
    }
    MyException(String msg){
        this(msg, 100) // calls overloaded contructor
    }
    public int getErrCode(){
        return ERR_CODE;
    }
}
  • 직접 만든 예외를 다른 예외와 같이 사용할 수 있다.
    • RuntimeException에서 상속했다면, 그 예외는 unchecked exception이 된다.
    • Exception에서 상속했다면, checked exception이 된다.
class MyException extends Exception {
    MyException(String msg){
        super(msg); // calls constructor of class Exception
    }
}

public class example {
    public static void main(String args[]) {
        try{
            method1();
        } catch(MyException e) {
            System.out.println("exception msg: " + e.getMessage());
        }
    }
    
    static void method1() throws MyException {
        throw new MyException("throwing MyException!");
    }
}
profile
토마토는 맛있어

0개의 댓글