프로그램의 오류는 크게 컴파일 오류와 실행 오류로 나눌 수 있다.
컴파일 오류는 문법에 맞지 않게 작성된 코드를 컴파일러가 사전에 알려주기 때문에 디버깅이 어렵지 않다. 하지만 실행 오류는 문법은 맞지만 프로그램이 의도와 다르게 흘러가는 오류이기 때문에 예측과 디버깅이 비교적 어렵다.
실행 오류는 다시 한번 자바 가상 머신에서 발생하는 시스템 오류와 프로그래머가 제어할 수 있는 예외로 나뉜다. 예외의 대표적인 예는 다음과 같다.
예외처리를 사용하는 이유 : 일반적으로 에러와 예외는 발생시 프로그램이 종료된다. 다만, 예외의 경우 예외처리를 통해 프로그램을 종료시키지않고 정상실행상태가 유지되게 할 수 있다.
자바에서는 컴파일시 예외가 발생할 가능성이 높은 코드(특정 클래스, 메소드 등)을 사용할 때 적합한 예외처리를 적용하였는지 확인한다. 만약 적합한 예외처리를 하지 않았다면 컴파일 오류를 낸다. 또한, 발생한 예외는 클래스를 통해 관리한다. JVM은 프로그램이 실행되는 도중에 예외가 발생한다면, 예외클래스를 통해 객체를 생성하고, 예외처리코드()에서 예외 객체를 이용 할 수 있게 한다.
오류 클래스는 모두 Throwable
클래스에서 상속 받는다. Throwable
클래스의 하위 클래스는 크게 시스템에서 발생하는 오류를 다루는 Error
클래스와 예외 클래스의 최상위 클래스인 Exception
클래스로 나뉜다. 예외 클래스의 대략적인 구조와, 자주 사용되는 클래스는 다음과 같다.
OutOfMemoryError
: 메모리가 부족한 경우 발생(Error)IOException
: 입출력 동작 실패 또는 인터럽트 시 발생 (인터럽트?)FileNotFoundException
: 존재하지 않는 파일에 접근하려고 할 때 발생SocketException
: 네트워크에 오류가 발생할 때 발생RuntimeException
: 프로그램 실행 중 발생할 수 있는 오류에 대한 예외를 처리ArithmeticException
: 정수를 0으로 나눌 때 발생IndexOutOfBoundsException
: 리스트나 배열의 범위를 벗어난 접근 시 발생ArrayIndexOutOfBoundsException
: 배열의 인덱스 범위를 초가할 경우 실행 예외.NullpointerException
: null 레퍼런스를 참조할 때 발생ClassCastException
: 클래스나 인터페이스를 변환할 수 없는 타입으로 객체를 변환할 때 발생IllegalArgumentException
: 잘못된 인자 전달 시 발생NumberFormatException
: 문자열이 나타내는 숫자와 일치하지 않는 타입의 숫자로 변환 시 발생InputMismatchException
: Scanner 클래스의 nextInt() 메서드를 호출하여 정수를 입력받으려고 했을 때, 정수가 아닌 문자가 입력으로 들어온 경우 발생. NoSuchElementException을 상속받음.예외는 다시 일반 예외(Exception)과 실행예외(Runtime Exception)으로 나뉜다. java.lang.Excption
밑에는 여러 예외들이 있는데, java.lang.Excption.RuntimeException
에 있는 예외만이 실행예외이다. 나머지는 모두 일반예외이다.
이때 일반예외가 발생할 가능성이 있는 코드는 컴파일러단에서 에러를 발생시켜, 강제적으로 예외처리코드를 작성하도록 요구한다. 하지만 실행예외가 발생할 가능성이 있는 코드는 이를 요구하지 않는다.
이미지 출처 : https://tutorial.eyehunts.com/java/try-catch-finally-java-blocks-exception-handling-examples/
코드 출처: Today I Learned. @cheers_hena 치얼스헤나
try { // ... **(1)**
// 예외발생할 가능성이 있는 문장
}catch(Exception1 e1) { // ... **(2)**
// Exception1이 발생했을 경우, 이를 처리하지 위한 문장적는다.
// 보통 이곳에 예외메세지를 출력하고 **로그**로 남김.
}catch(Exception2 e2) {
// Exception2이 발생했을 경우, 이를 처리하지 위한 문장적는다.
}catch(ExceptionN eN) {
// ExceptionN이 발생했을 경우, 이를 처리하지 위한 문장적는다.
}catch(Exception e) { // ... **(3)**
// 지정된 예외타입 이외의 모든 예외처리를 수행할 부분.
}finally{
// 예외발생여부에 관계없이 상항 수행되어야 하는 문장적는다.
// 정리코드(DB 연결종료, 파일닫기, 자원해제 등등)
}
try block
: 예외 발생한 코드가 위치. 예외가 발생하면 즉시 실행을 멈추고 catch block으로 이동한다.catch block
: 예외처리 코드를 입력.final block
(생략 가능) : 예외 발생 여부와 상관 없이 항상 실행할 코드. 심지어 try, catch block내부에 return이 있더라도 final block내부의 내용은 항상 실행된다.출처: Today I Learned. @cheers_hena 치얼스헤나
catch (AuthException e) {
// 예외정보 출력
e.printStackTrace();
// 로그남김
log.error("AuthException ERROR: {} ", e.getMassage());
// 예외 던지기
throw e;
} catch (Exception e ) {
e.printStackTrace();
log.error("Exception ERROR: {} ", e.getMassage());
throw e;
}
참고 : cheershennah의 블로그
사실 다중 예외처리는 위에서 여러 catch문를 쓰면서 작성하였다. 해당 부분에서는 다중 catch를 작성하면서 주의해야할 부분에 대해 서술한다.
try {
...
} catch (하위Exception e) {
...
} catch (상위Exception e) {
...
}
try에서 예외가 발생하면 가장 위의 catch문부터 차례로 내려오면서 확인한다. 따라서 하위의 예외타입(...Exception)을 catch문 위에, 가장 상위의 예외타입(Exception)을 catch문 아래에 작성한다. 가장 상위의 예외타입인 Exception을 위에 작성하면, 모든 예외타입이 해당 타입에 걸리기 때문이다. 이와 같은 이유로, 예외타입을 작성할 때 예외타입간의 관계를 확인하여 부모 예외타입은 아래에, 상속받는 예외타입은 아래에 작성한다.
try {
...
} catch (Exception e) { // 해당 부분에서 모든 예외가 걸리므로,
예외처리문 1 // 예외처리문 2는 결코 수행되지 않는다.
} catch (하위Exception e) {
예외처리문 2
}
→ 최적화를 위해 상위 예외인 Exception보다 적합한 각각의 하위 예외를 권고하는데... 그 이유는 찾기 힘들다.
(Ref - Performance improvement techniques in Exceptions)
자바 1.7(7버전)에 추가된 내용(향상된 예외처리). 외부 자원에 접근하는 경우, try-catch-final문에서는 final문에서 자원을 반환을 한다. 하지만 try-with-resources을 사용하면 더 간결하게 자원 반납 문제를 해결할 수 있다.
자원을 할당한 객체(이하 자원할당객체)를 try문이 끝나면 자동으로 close()메서드를 호출하여 자원을 반환해준다.
try {
**자원할당객체 선언
...**
} catch(...) {
...
} final {
자원 반환 코드들
...
}
try**(자원할당객체 선언)** {
...
} catch(...) {
...
} final {
...
}
이때, try-with-resources문을 사용하기 위해 사용하는 자원할당객체는 AutoCloseable인터페이스를 구현해야 자동으로 close()가 호출되어 try-with-resources문을 사용할 수 있다.
package trywithresource.example;
public class Member implements AutoCloseable{
public void work() {
System.out.println(this + " is working");
}
@Override
public void close() throws Exception {
System.out.println(this + "'s close() is called");
}
}
package trywithresource.example;
public class TestMain {
public static void main(String[] args) {
try (Member mb = new Member()) {
mb.work();
} catch (Exception e) {
e.printStackTrace();
}
// close()를 호출하는 final block이 없다.
}
}
trywithresource.example.Member@3d24753a is working
trywithresource.example.Member@3d24753a's close() is called
참고 : 코끼리를 냉장고에 넣는 방법 블로그, codechacha.com
자바 7부터 추가된 try-with-resources
문은 자바 9에서 더 업그레이드 되었다. 자바 7에서는 자원할당객체의 선언을 반드시 try
문 괄호 안에서 해야했지만, 자바 9부터는 괄호 외부에서도 선언이 가능해졌다.
package exception;
import java.lang.Exception;
public class ResourceException {
public static void main(String[] args) {
try (MyResource myResource = new MyResource()) {
myResource.open();
myResource.use();
} catch (Exception e) {
System.out.println("예외!");
}
}
}
package exception;
import java.lang.Exception;
public class ResourceException {
public static void main(String[] args) {
MyResource myResource = new MyResource();
// 만약 외부에서 선언했다면 또다른 참조변수를 선언해서 대입해야 했었음
try (MyResource myResource2 = myResource) {
myResource2.open();
myResource2.use();
} catch (Exception e) {
System.out.println("예외!");
}
}
}
package exception;
import java.lang.Exception;
public class ResourceException {
public static void main(String[] args) {
MyResource myResource = new MyResource();
// 선언없이 바로 try 문에 대입이 가능해짐
try (myResource) {
myResource.open();
myResource.use();
} catch (Exception e) {
System.out.println("예외!");
}
}
}
리소스가 open() 되었습니다.
리소스를 사용하고 있습니다.
리소스가 close() 되었습니다.
package exception;
import java.lang.Exception;
public class ResourceException {
public static void main(String[] args) {
MyResource myResource = new MyResource();
try (myResource) {
myResource.open();
myResource.use();
throw new Exception();
} catch (Exception e) {
System.out.println("예외!");
}
}
}
리소스가 open() 되었습니다.
리소스를 사용하고 있습니다.
리소스가 close() 되었습니다.
예외!
public void method(String arg1...) throws Exception1, Excepton2... {
...
}
throw키워드는 메소드 선언부 끝에 작성되어, 메소드 내부에서 발생한 예외를 메소드를 호출한 곳으로 떠넘기는 역할을 한다. 따라서, throw키워드를 사용한 메소드는 try-catch문에서 호출되어야한다.
throw
를 사용하지 않은 경우 package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Exception {
public Class loadClass(String fileName, String className) {
try {
FileInputStream fis = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Class c = null;
try {
c = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return c;
}
public static void main(String[] args) {
Exception ex = new Exception();
ex.loadClass("a.txt", "nothing");
}
}
즉, 예외처리를 미루지 않은 경우, 메서드의 사용여부와 상관 없이 메서드를 구현할 때, try-catch
문을 사용하여 예외처리를 해주어야한다. 하지만 throw
를 사용하면 메서드를 사용할 때 예외처리를 하도록 미룰 수 있다. try
문 안에 그 메서드를 감싸는 형식으로 구현된다.
throw
를 사용하여 예외처리 미루기 package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ThrowException {
public Class loadClass(String fileName, String className) throws FileNotFoundException,ClassNotFoundException {
Class c = Class.forName(className);
FileInputStream fis = new FileInputStream(fileName);
return c;
}
public static void main(String[] args) {
ThrowException ex = new ThrowException();
try {
ex.loadClass("a.txt", "nothing");
} catch (ClassNotFoundException e) {
System.out.println(e);
e.printStackTrace();
} catch (FileNotFoundException e) {
System.out.println(e);
e.printStackTrace();
}
}
}
try - catch 문으로 예외를 처리하기에 한계가 드러나기 때문!
public class ThrowException1 {
public void sayNick(String nick) {
if("fool".equals(nick)) {
return;
}
System.out.println("당신의 별명은 "+nick+" 입니다.");
}
public static void main(String[] args) {
ThrowException1 test = new ThrowException1();
test.sayNick("fool");
test.sayNick("genious");
}
}
output=>
당신의 별명은 genious 입니다.
nick 매개변수에 들어온 fool은 equals 에 걸려서 return 되기 때문에 fool 을 제외한 genius만 출력이 된다.
위의 코드를 변형시켜서 Output이 나오는 것이 아닌 예외를 출력시키도록 할 수 있다.
public class FoolExcep extends RuntimeException {
}
public class ThrowException1 {
public void sayNick(String nick) {
if("fool".equals(nick)) {
throw new FoolExcep();
}
System.out.println("당신의 별명은 "+nick+" 입니다.");
}
public static void main(String[] args) {
ThrowException1 test = new ThrowException1();
test.sayNick("fool");
test.sayNick("genious");
}
}
output=>
Exception in thread "main" FoolExcep
at ThrowException1.sayNick(ThrowException1.java:5)
at ThrowException1.main(ThrowException1.java:12)
위의 결과와 다르게 fool에서 발생한 예외로 인해 출력이 정상적으로 작동되지 않는다. FoolExcep에서 RuntimeException을 상속 받았기 때문인데 RuntimeException은 Exception과 다르게 실행시 발생하는 예외를 처리해주는 class이기 때문이다.
public class foolException extends Exception{
}
public class RunTImeExceptionTest {
public void sayNick(String nick)
{
try {
if("fool".equals(nick)) {
throw new foolException();
}
System.out.println("당신의 별명은 "+nick+" 입니다.");
}catch(foolException e) {
System.err.println("FoolException이 발생했습니다.");
};
}
public static void main (String []args)
{
RunTImeExceptionTest test = new RunTImeExceptionTest();
test.sayNick("fool");
test.sayNick("geinus");
}
}
output=>
FoolException이 발생했습니다.
당신의 별명은 geinus 입니다.
RuntimeException을 상속하던 것을 Exception을 상속하도록 변경했다. 이렇게 하면 RunTImeExceptionTest 클래스에서 컴파일 오류가 발생한다. 예측 가능한 Checked Exception이기 때문에 예외처리를 컴파일러가 강제하기 때문이다.
public class foolException extends Exception{
}
public class ThrowException2 {
public void sayNick(String nick) throws foolException
{
if ("fool".equals(nick))
throw new foolException();
System.out.println("당신의 별명은" + nick + "입니다.");
}
public static void main(String []args)
{
ThrowException2 test = new ThrowException2();
try {
test.sayNick("genius");
test.sayNick("fool");
test.sayNick("geniuss");
}
catch (foolException e)
{
System.err.println("에러가 발생하였습니다.");
}
finally {
//test.sayNick("geniusss");
System.out.println("안녕하세요");
}
}
output=>
당신의 별명은genius입니다.
안녕하세요
에러가 발생하였습니다.
ThrowException2 test = new ThrowException2();
try {
test.sayNick("fool");
test.sayNick("genius");
}
catch (foolException e)
{
System.err.println("에러가 발생하였습니다.");
}
output=>
에러가 발생하였습니다.
try-catch 구문을 이용해서 test.sayNick("fool")과 test.sayNick("genius") 가 실행이 되지만 test.sayNick("fool")에서 에러가 발생하기 때문에 catch 문으로 빠져나가게 되어서 test.sayNick("genius")부분은 실행이 되지 않게된다.
System.err가 System.out보다 나중에 뭉쳐서 나오는 이유 : 스택오버플로우
사용자 정의 예외란?
프로그램을 개발하다보면 자바 표준 API가 제공하는 예외 클래스만으로 다양한 종류의 예외를 다 표현할 수 없습니다.
이때, 직접 정의하여 사용하는 예외를 사용자 정의 예외
라고 합니다
java.lang.Exception
클래스를 상속받아 정의해야합니다.throw new 사용자정의예외()
public class ElementException extends Exception{
public ElementException(String str)
{
super(str);
}
}
import java.util.Scanner;
public class TestException {
public static int sum() throws ElementException{
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
if (a < 0 || b < 0)
throw new ElementException("음수값 입력 금지");
return (a + b);
}
public static void main(String []args)
{
try{
System.out.printf("합 : %d", sum());
System.out.println("sum 함수 실행 완료");
}catch (ElementException e)
{
System.out.println(e.getMessage());
}
System.out.println("try catch 문 실행 완료");
}
ElementException
: 사용자 정의 예외 처리를 설정해줍니다. 매개변수인 a는 e.getMessage()에서 출력됩니다.
package exception;
public class MyExceptionTest {
private String userID;
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
if (userID == null) {
try {
throw new MyException("null!");
} catch (MyException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
} else if (userID.length() < 8 || userID.length() > 20) {
try {
throw new MyException("아이디 생성 규칙에 어긋남!");
} catch (MyException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
this.userID = userID;
}
public static void main(String[] args) {
MyExceptionTest test = new MyExceptionTest();
String userID = "jwoo";
test.setUserID(null);
test.setUserID(userID);
}
}
null!
아이디 생성 규칙에 어긋남!
exception.MyException: null!
at exception.MyExceptionTest.setUserID(MyExceptionTest.java:13)
at exception.MyExceptionTest.main(MyExceptionTest.java:33)
exception.MyException: 아이디 생성 규칙에 어긋남!
at exception.MyExceptionTest.setUserID(MyExceptionTest.java:20)
at exception.MyExceptionTest.main(MyExceptionTest.java:34)
Process finished with exit code 0
프로그램 개발 중이나 완료 후 발생할 수 있는 오류에 대해 디버깅하거나 운영 중인 프로그램 상태를 모니터링 하기 위해 필요한 정보(로그)를 기록하는 것
애플리케이션 실행에 대한 추적을 기록하기 위해 어딘가에 메시지 (콘솔, 파일, 데이터베이스 등)를 작성하는 것
Logging을 어디에 이용할까?
예외처리시 log 할때 Framework를 사용한다.