class A { // 외부 클래스 code... class B { // 내부 클래스 code... } code... }
B는 A의 내부 클래스(inner class)이고, A는 B를 감싸고 있는 외부(outer class)가 된다.
내부 클래스의 장점
내부 클래스의 종류와 특징
내부 클래스의 제어자와 접근성
class Outher {
private class InstanceInner {}
protected static class StaticInner {}
void myMethod() {
class LocalInner {}
}
인스턴스클래스와 스태틱클래스는 외부 클래스의 멤버변수(인스턴스변수와 클래스변수)와 같은 위치에 선언되며, 또한 멤버변수와 같은 성질을 갖는다. 따라서 내부 클래스가 외부 클래스의 멤버와 같이 간주되고, 인스턴스멤버와 static멤버 간의 규칙이 내부 클래스에도 똑같이 적용된다.
그리고 내부 클래스에서도 abstract나 final과 같은 제어자를 사용할 수 있을 뿐만 아니라, 멤버변수들처럼 private, protected와 접근제어자도 사용 가능하다.
익명 클래스(anonymous class)
선언과 객체의 생성을 동시에 하는 클래스이다. 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.
new 조상클래스() {
member;
code...
}
new 구현인터페이스() {
member;
code...
}
이름이 없기 때문에 생성자도 가질 수 없고, 조상클래스 또는 인터페이스의 이름을 사용해서 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다.
익명 클래스 사용 예시
class ClassName {
Object iv = new Object() { code... }; // 익명 클래스
static Object cv = new Object() { code... }; // 익명 클래스
void myMethod() {
Object lv = new Object () { code... }; // 익명 클래스
}
}
프로그램 오류
프로그램 실행 중 어떤 원인에 의해 오작동 하거나 비정상적으로 종료되는 경우 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다.
이를 발생시키는 시점에 따라 '컴파일 에러(compile-time error)'와 '런타임 에러(runtime error)'로 나눌 수 있다.
이 외에도 컴파일과 실행이 잘 되지만 의도한 것과 다르게 동작하는 '논리적 에러(logical error)'가 있다. 예를들어 창고의 재고가 음수가 되거나, 게임에서 비행기가 총알을 맞아도 죽지 않는 경우가 있다.
- 컴파일 에러 : 컴파일 시에 발생하는 에러
- 런타임 에러 : 실행 시에 발생하는 에러
- 논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것
컴파일러가 알려준 에러들을 모두 수정해서 컴파일을 성공적으로 마쳤다고 해서 프로그램의 실행 시에도 에러가 발생하지 않는 것은 아니다. 컴파일러가 소스코드의 기본적인 사항은 컴파일 시에 모두 걸러 줄 수는 있지만, 실행도중에 발생할 수 있는 잠재적인 오류까지 검사할 수 없기 때문이다.
런타임 에러를 방지하기 위해서는 프로그램의 실행도중 발생할 수 있는 모든 경우의 수를 고려하여 이에 대한 대비를 하는 것이 필요하다.
자바에서는 실행 시(runtime) 발생할 수 있는 프로그램 오류를 '에러(error)'와 예외(exception)' 두 가지로 구분하였다.
- 에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
- 예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
예외 클래스의 계층구조
자바에서는 실행 시 발생할 수있는 오류(Exception과 Error)를 클래스로 정의하였다.
모든 예외의 최고 조상은 Exception클래스이고, 사용자의 실수와 같은 외적인 요인에 의해 발상하는 예외이다.
그 외에 RuntimeException클래스와 그 자손 클래스는 프로그래머의 실수로 발생하는 예외 이다.
종류는
ArithmeticException, NullPointerException, ClassCastException, NegativeArraySizeException, OutOfMemoryException, NoClassDefFoundException, ArrayIndexOutOfBoundsEception
등이 있다.
예외처리 try-catch
정의
프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것이다.
목적
은 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.
try {
// 예외가 발생할 수 있는 코드
} catch (Exception1 e1) {
// Exception1이 발생할 경우 이를 처리하기 위한 코드
} catch (Exception2 e2) {
// Exception2가 발생할 경우 이를 처리하기 위한 코드
} catch (ExceptionN eN) {
// ExceptionN이 발생할 경우 이를 처리하기 위한 코드
}
try에 있는 코드를 우선 실행 후 예외가 발생시 발생한 예외와 일치하는 한개의 catch블럭이 수행된다. 예외 종류와 일치하는 블럭이 없으면 예외는 처리되지 않는다.
중첩 for문이나 중첩 if문과 같이 try내부에도 중첩해서 try-catch문이 사용될 수 있다.
try {
try { } catch (Exception e1) { }
} catch (Exception e2) {
}
예제
class Except {
public static void main(String[] args) {
int num = 100;
int result = 0;
for (int i = 0; i < 10; i++) {
result = num / (int) (Math.random() * 10);
System.out.println(result);
}
}
}
결과
result = num / (int) (Math.random() * 10); System.out.println(result);
ArithmeticException은 산술연산과정에서 오류가 있을때 발생하는 예외 이며, 해당 예외는 정수를 0으로 나누었을 때 발생하는 예외이다.
하지만, 실수는 0으로 나누어도 예외가 발생하지 않는다.try { result = num / (int) (Math.random() * 10); System.out.println(result); } catch (ArithmeticException e) { System.out.println("0"); }
해당 코드를 try-catch해주면 해당 예외가 0으로 바뀌어 출력되고 정상적으로 동작하게 된다.
결과
예외가 발생하면 첫 번째 catch블럭부터 차례로 내려가면서 catch블럭의 괄호()내에 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof연산자를 이용해서 검사하게 되는데, 검사결과가 true인 catch블럭을 만날 때까지 검사는 계속된다.
만약 검사결과가 true인 catch블럭이 하나도 없으면 예외는 처리되지 않는다.
모든 예외 클래스는 Exception클래스의 자손이므로, catch블럭의 괄호()에 Exception클래스 타입의 참조변수를 선언해 놓으면 어떤 종류의 예외가 발생해도 해당catch블럭에 의해 처리된다.
printStackTrace()와 getMessage()
예외 발생시 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨있으며, printStackTrace()와 getMessage()를 통해 얻을 수 있다.
- printStackTrace()
예외 발생시 호출스택(Call Stack)에 있었던 메서드의 정보와 예외를 화면에 출력한다.- getMessage()
발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
class Except {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0/0); // 예외발생
System.out.println(4); // 실행되지 않음
} catch (Exception e) {
e.printStackTrace(); // 참조변수 e를 통해 메서드의 정보 출력
System.out.println("예외 메시지 : " + e.getMessage());
// 예외 메시지 출력
}
System.out.println(6);
}
}
결과
이처럼 예외가 발생해도 비정상적으로 종료하지 않도록 해주는 동시에, printStackTrace()또는 getMessage()와 같은 메서드를 통해 예외의 발생 원인을 알 수 있다.
멀티 catch블럭
JDK1.8부터 여러catch 블럭을 '|'기호를 이용해서, 하나의 catch블럭으로 합칠수 있게 되었다.
try {
code...
} catch(ExceptionA | ExceptionB e) {
e.printStackTrace();/
}
예외 발생시키기
Exception e = new Exception("고의로 발생 시킴"); throw e; // 에러발생 // 위의 코드를 한 줄로 줄일 수도 있다. throw new Exception("고의 발생"); // 에러발생
try {
throw new Exception("고의 발생");
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
System.out.println("프로그램 정상 종료");
결과
이처럼 Exception에러와 그 자손 클래스의 에러는 반듯이 예외처리를 해줘야한다. 하지만, RuntimeException클래스와 그 자손 클래스들은 프로그래머에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는다. 이를 'unchecked예외'라고 하고 Exception과 그 자손 클래스는 'checked예외'라고 부른다.
메서드에 예외 선언
try-catch문을 사용하는 것 외에, 예외를 메서드에 선언하는 방법이 있다. 메서드 선언부에 throws키워드를 사용해 메서드 내에서 발생할 수 있는 예외를 적어주기만 하면 된다. 그리고 예외가 여러 개일 경우 쉼표(,)로 구분한다.
void method() throws Exception1, Exception2, ... {
code...
}
메서드의 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있다.
예제
class Except {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main에서 예외처리 되었음");
e.printStackTrace();
}
System.out.println("정상 종료");
}
static void method1 () throws Exception {
method2();
}
static void method2 () throws Exception {
throw new Exception(); // 예외 발생
}
}
결과
각각 어느 부분에서 예외가 발생했는지 알수 있고 main에서 예외가 처리되어 정상 종료된 것을 볼 수 있다.
finally블럭
finally 블럭은 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다. try-catch문의 끝에 선택적으로 덧붙여 사용할 수 있으며, try-catch-finally의 순서로 구성된다.
try {
throw new Exception();
} catch (Exception e) {
System.out.println("예외 처리");
e.printStackTrace();
} finally {
System.out.println("Finally!!");
}
System.out.println("정상 종료");
결과
예외처리 후 Finally블럭 실행 후 종료가 되었다.
만약 메서드의 try-catch블럭에 return문이 있어도 finally블럭의 문장들이 먼저 실행된 후에, 현재 실행 중인 메서드를 종료한다.
예제
class Except {
public static void main(String[] args) {
method1();
System.out.println();
method2();
}
static void method1() {
try {
throw new Exception(); // 예외 발생
} catch (Exception e) {
e.printStackTrace();
return;
} finally {
System.out.println("나까지 실행하고 return해!!");
}
}
static void method2() {
try {
System.out.println("예외 없음~");
return;
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("나까지 실행하고 return해!!");
}
}
}
결과
두 메서드를 다 실행했을 때 return하기 전에 finally블럭은 수행된 것을 볼 수 있다.