JAVA에는 대표적으로 두가지의 에러가 존재하는데 Error
와 Exception
두 가지가 있다.
Error
: 컴퓨터의 메모리가 부족하는 등 코드의 문제가 아닌 시스템에 비정상적인 상황이 발생한 오류를 주로 말한다. 보통 잘 일어나지 않지만 일어나더라도 개발자가 코드로 해결할 수 없는 영역이다.Exception
: 사용자의 잘못된 조작이나 개발자의 코딩 실수로인해 발생하는 프로그램 오류이다. 대부분 코드를 수정하면 고칠 수 있다. (Exception 에러를 띄울때는 에러라고 표현하지않고 예외라고 표현한다.)
자바의 예외에는 일반예외(Checked Exception)
와 실행예외(Unchecked Exception)
으로 부를 수 있다.
RuntimeException
을 상속받는다.예외 | 일반예외 (Checked Exception) | 실행예외(Unchecked Exception) |
---|---|---|
정의 | Exception의 상속받는 하위 클래스 중 RuntimeException을 제외한 모든 Exception | RuntimeException을 상속받는 모든 Exception |
처리여부 | 필수 | 생략가능 (필요에 따라 처리) |
확인시점 | Compile Time(컴파일시점), 이미 컴파일 시점 에러가 표시되기 때문에 확인하여 예외처리를 할 수 있다. | Runtime(실행시점), 컴파일 이후 런타임 도중 예외를 확인할 수 있다. (로직상으로는 생략이 가능하지만 미리 막아두는 것이 좋다.) |
트랜잭션 | 예외 발생 시 롤백(rollback)을 진행하지 않는다. | 예외 발생 시 롤백(rollback)을 진행한다. |
자주 출몰하는 대표적인 실행예외들을 소개해보겠다.
- 객체 참조가 없는 상태일 때 발생한다.
- null값을 갖는 참조변수로 객체 접근 연산자인 도트(.)를 사용했을때 발생한다.
- 객체가 없는 상태에서 객체를 사용할 때 발생한다.
public class Main {
// static은 static을 부를 수 있다.
// non-static인 경우 아직 떳떳한 클래스/메소드로써 태어나지 않은거다.
public static void m1() {
// NullPointerException : Null값이 어떤 메소드를 호출할 때 발생한다.
String[] hobbies = new String[5];
// hobbies[0]에 값을 할당해주지않아서 Null값인 상태
hobbies[1] = "수영";
hobbies[2] = "골프";
hobbies[3] = "영화";
hobbies[4] = "집콕";
for(int i = 0; i < hobbies.length; i++) {
if(hobbies[i].equals("수영")) {
System.out.println("취미가 나와 같군요");
}
}
}
public static void main(String[] args) {
m1();
}
}
사진을 보면 Exception의 종류와함께 Exception이 일어난 곳을 추적(trace)
해서 알려준다.
public class Main {
public static void m2() {
// NullPointerException 회피
String[] hobbies = new String[5];
hobbies[1] = "수영";
hobbies[2] = "골프";
hobbies[3] = "영화";
hobbies[4] = "집콕";
for(int i = 0; i < hobbies.length; i++) {
if(hobbies[i] != null && hobbies[i].equals("수영")) {
System.out.println("취미가 나와 같군요");
}
}
}
public static void main(String[] args) {
m2();
}
}
문자열로 되어있는 데이터를 숫자로 변경하는 경우가 많은데, 매개값인 문자열이 숫자로 변환될 수 있다면 숫자를 정상적으로 리턴하지만, 숫자로 변환할 수 없는 문자열이 포함되어있으면 발생한다.
import java.util.Scanner;
public class Main {
public static void m1() { }
public static void m2() { }
public static void m3() {
//NumberFormatException : String을 Number타입으로 변경할 때 발생
Scanner sc = new Scanner(System.in);
System.out.print("이름 입력(필수) >>> ");
String name = sc.nextLine();
System.out.print("나이 입력(선택) >>> ");
String strAge = sc.nextLine(); // 입력없이 Enter만 누르면 strAge는 빈 문자열을 가진다.
int age = Integer.parseInt(strAge);
System.out.println("이름 : " + name + ", 나이 : " + age + "살");
}
public static void main(String[] args) {
m3();
}
}
나이 입력을 공백란으로 두었을 때 이런 예외가 발생한다.
import java.util.Scanner;
public class Main {
public static void m1() { }
public static void m2() { }
public static void m3() {
//NumberFormatException : String을 Number타입으로 변경할 때 발생
Scanner sc = new Scanner(System.in);
System.out.print("이름 입력(필수) >>> ");
String name = sc.nextLine();
System.out.print("나이 입력(선택) >>> ");
String strAge = sc.nextLine(); // 입력없이 Enter만 누르면 strAge는 빈 문자열을 가진다.
int age;
if(strAge.isEmpty()) {
age = 0;
} else {
age = Integer.parseInt(strAge);
}
System.out.println("이름 : " + name + ", 나이 : " + age + "살");
}
public static void main(String[] args) {
m3();
}
}
배열에서 인덱스 범위를 초과하여 사용할 때 발생합니다.
public class Main {
public static void main(String[] args) {
int[] arr = new int[3];
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4 // 배열의 크기는 3인데 4번째 index까지 사용하여 예외발생
}
}
예외적인 산술 조건이 발생하면 발생한다. 예를 들어 정수 0으로 나누기를 실행하면 주로 발생한다.
public static void m3() {
Scanner sc = new Scanner(System.in);
System.out.print("정수 1 >>> ");
int a = sc.nextInt();
System.out.print("정수 2 >>> ");
int b = sc.nextInt();
System.out.println(a + "+" + b + "=" + (a + b));
System.out.println(a + "-" + b + "=" + (a - b));
System.out.println(a + "*" + b + "=" + (a * b));
System.out.println(a + "/" + b + "=" + (a / b));
System.out.println(a + "%" + b + "=" + (a % b));
}
정수를 입력해야 하는데 문자나 실수를 입력한 경우 예외 발생 (데이터타입을 다르게입력)
public static void m3() {
try {
Scanner sc = new Scanner(System.in);
System.out.print("정수 1 >>> ");
int a = sc.nextInt();
System.out.print("정수 2 >>> ");
int b = sc.nextInt();
System.out.println(a + "+" + b + "=" + (a + b));
System.out.println(a + "-" + b + "=" + (a - b));
System.out.println(a + "*" + b + "=" + (a * b));
System.out.println(a + "/" + b + "=" + (double)(a / b));
System.out.println(a + "%" + b + "=" + (double)(a % b));
} catch (ArithmeticException e) {
System.out.println("ArithmeticException이 발생했다.");
}
}
이상태에서 문자열을 아무거나 입력한 경우 발생한다.
객체배열에 배열 유형이 허락하지 않는 객체를 저장하려는 경우 발생한다.
배열을 참조하는 인덱스가 잘못된 경우 발생한다.
Class의 형변환를 적절하지 못하게 할 경우
배열의 크기가 음수인 경우
클래스를 찾을 수 없는 경우
사용 가능한 메모리가 없는 경우
객체의 범위를 벗어난 색인(Index)를 사용하는 경우
메서드에 유형이 일치하지 않는 매개변수를 전달하는 경우
스레드가 스레드에 속하지 않는 객체를 모니터 하려고 기다리는 경우
적절하지 않은 때에 메서드를 호출하는 경우
- 입출력 스트림과 관련한 명령어를 사용할 때 사용하는 예외처리이다.
- 입출력 동작이 실패하는 경우에 발생한다.
클래스 이름을 찾을 수 없는 경우에 발생한다.
데이터베이스 처리가 실패하는 경우에 발생한다.
파일을 찾을 수 없는 경우에 발생한다.
자원(Resource)의 이름을 확인할 수 없는 경우에 발생한다.
예외처리(Exception Handling) 하는것에 대해 공부해보겠다.
예외가 발생했을때 예외를 해결할 수 있는 코드를 지정해줄 수 있다.
try{
// 예외가 발생할 가능성이 있는 코드 작성
}
catch(예외클래스 e) {
// 예외를 처리할 문장
}
발생하는 예외별로 다중 catch문을 구성할 수 있다.
Catch가 여러개 나오면 위에서부터 순서대로 방문한다.
- 주의할 점 : 상위 예외 클래스가 하위 예외 클래스보다 밑에 위치하게 작성해야 한다.
try {
//예외 1이 발생할 가능성이 있는 코드
//예외 2가 발생할 가능성이 있는 코드
}
catch(예외1 e) {
//예외1을 발생했을 때 처리할 코드
}
catch(예외2 e) {
//예외2가 발생했을 때 처리할 코드
}
NullPointerException과 Try-Catch문
public class Main {
public static void m1() {
try {
String[] hobbies = new String[3];
hobbies[1] = "swimming";
hobbies[2] = "running";
for(String hobby : hobbies) {
System.out.println(hobby.substring(0, 2));
}
} catch (NullPointerException e) { // RuntimeException, NullPointerException 가능
System.out.println("NullPointerException 발생");
}
}
public static void main(String[] args) {
m1();
}
}
RuntimeException을 이용한 NullPointerException 해결
public class Main {
public static void m1() {
try {
String[] hobbies = new String[3];
hobbies[1] = "swimming";
hobbies[2] = "running";
for(String hobby : hobbies) {
System.out.println(hobby.substring(0, 2));
}
} catch (RuntimeException e) { // RuntimeException, NullPointerException 가능
System.out.println("NullPointerException 발생");
}
}
public static void main(String[] args) {
m1();
}
}
finally는 자원을 반납할 때 주로 사용한다.
try {
//예외가 발생할 가능성이 있는 코드
}
catch(예외클래스 e) {
//예외처리;
}
finally {
//무슨일이 있든 항상 실행
}
아래 예제는 Scanner를 닫아주는 것을 표현해보았다.
실제로 Scanner는 close()처리로 닫아주지않아도 정상적으로 잘 실행이 되지만, 다른메소드들 중에서는 close()처리를 안해주면 데이터를 손실낼 수도 있는 것도 많기 때문에 주의해야한다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// fanally 블록
// 1. try-catch문 마지막에 추가하는 블록
// 2. 언제나 마지막에 실행되는 블록
Scanner sc = new Scanner(System.in);
try {
System.out.print("나이 입력 >>> "); // null이나 다른데이터타입을 입력하면 예외가 발생한다.
String strAge = sc.nextLine();
int age = Integer.parseInt(strAge);
System.out.println(age >= 20 ? "성인" : "미성년자");
// sc.close(); // 13번에서 예외가 발생했을 경우 바로 catch문으로 가기 때문에 닫지못한다.
}
catch(Exception e) {
System.out.println("예외 발생");
}
finally {
sc.close();
System.out.println("finally 블록 실행");
}
}
}
만약 예외가 발생한다면 그 즉시 바로 catch문으로 가기 때문에 닫지못하기 때문에 예외와 상관없이 꼭 실행해야 하는 코드를 finally
안에 배치한다.
메소드 내에서 직접 에러를 처리하지 않고, 해당 메소드를 호출한 쪽으로 예외처리를 던져 호출한 쪽에서 예외처리를 하게 회피하는 방식이다.
- 예외 객체를 만들어서 직접 throw 할 수 있다.
- 자바는 예외로 인식하지 않지만 실제로는 예외인 경우 주로 사용한다.
아래는 나이에 따른 신분을 출력하는 문장이다. 하지만 20000을 입력하더라도 JAVA는 오류를 띄우지 않을 것이다. 그 부분을 throw문으로 처리해보았다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try {
System.out.print("나이 입력 >>> "); // null이나 다른데이터타입을 입력하면 예외가 발생한다.
String strAge = sc.nextLine();
int age = Integer.parseInt(strAge);
if(age < 0 || age > 100) {
throw new RuntimeException("나이는 0 이상 100 이하만 가능합니다."); // 예외 객체 생성.
} // throw가 던지면 catch가 받는다.
System.out.println(age >= 20 ? "성인" : "미성년자");
// sc.close(); // 13번에서 예외가 발생했을 경우 바로 catch문으로 가기 때문에 닫지못한다.
}
catch(Exception e) {
System.out.println(e.getMessage());
}
finally {
sc.close();
System.out.println("finally 블록 실행");
}
}
}
메소드에서 예외를 처리하는 방식이다.
- 메소드 내부에 try-catch문을 두고 직접 처리하는 방식
- 메소드 외부로 예외를 던져서 메소드를 호출하는 곳에서 try-catch문으로 처리하는 방식
- 2개 이상의 예외를 명시할 수 있기 때문에 throw가 아닌
throws
라고 한다.
메소드 내부에서 예외를 처리
public class ThrowEx {
public static void method() {
try{
// 메소드 내부 코드
} catch {
// 예외 처리 코드
}
}
public static void main(String[] args) {
method();
}
}
메소드 외부로 예외를 던지고, 호출영역에서 예외를 처리
public class ThrowEx {
public static void method() throws Exception {
// 메소드 내부 코드
}
public static void main(String[] args) {
try{
method();
} catch {
// 예외 처리 코드
}
}
}