[비트캠프] 54일차(11주차4일) - Java(Wrapper 클래스, 예외 처리), myapp-16~18
Wrapper 클래스
Wrapper 클래스 - 오토박싱(auto-boxing)/오토언박싱(auto-unboxing)
package com.eomcs.basic.ex02;
public class Exam0220 {
public static void main(String[] args) {
// 다음과 같이 프로그래밍 중에
// primitive type의 값을 인스턴스에 담고("박싱(boxing)"이라 부른다)
// 인스턴스의 담긴 primitive 값을 다시 꺼내는 일("언박싱(unboxing)"이라 부른다)은
// 매우 불편한다.
// int ==> Integer
int i1 = 100;
Integer obj1 = Integer.valueOf(i1);
// Integer ==> int
Integer obj2 = Integer.valueOf(200);
int i2 = obj2.intValue();
}
}
package com.eomcs.basic.ex02;
public class Exam0221 {
public static void main(String[] args) {
// 오토박싱
// - Java 1.5부터 지원하는 문법이다.
// - 코드의 문맥에 따라 박싱(boxing)과 언박싱(unboxing)을 자동으로 수행한다.
//
// 즉 primitive data type 값을 Wrapper 클래스의 인스턴스에 바로 할당할 수 있다.
//
Integer obj = 100; // ==> Integer.valueOf(100)
// obj는 레퍼런스인데 어떻게 가능한가?
// => 내부적으로 Integer.valueOf(100) 호출 코드로 바뀐다.
// => 즉 int 값이 obj에 바로 저장되는 것이 아니라,
// 내부적으로 Integer 객체가 생성되어 그 주소가 저장된다.
// => 이렇게 int 값을 자동으로 Integer 객체로 만드는 것을
// "오토박싱(auto-boxing)"이라 한다.
}
}
package com.eomcs.basic.ex02;
public class Exam0222 {
public static void main(String[] args) {
// 오토 언박싱
// - Java 1.5부터 지원하는 문법이다.
// - 코드의 문맥에 따라 박싱(boxing)과 언박싱(unboxing)을 자동으로 수행한다.
//
// 즉 Wrapper 객체의 값을 primitive data type 변수에 직접 할당할 수 있다.
//
Integer obj = Integer.valueOf(300);
int i = obj; // ==> obj.intValue()
// obj에 저장된 것은 int 값이 아니라 Integer 객체의 주소인데 어떻게 가능한가?
// => 내부적으로 obj.intValue() 호출 코드로 바뀐다.
// => 즉 obj에 들어있는 인스턴스 주소가 i에 저장되는 것이 아니라,
// obj 인스턴스에 들어 있는 값을 꺼내 i에 저장하는 것이다.
// => 이렇게 Wrapper 객체 안에 들어 있는 값을 자동으로 꺼낸다고 해서
// "오토언박싱"이라 부른다.
}
}
package com.eomcs.basic.ex02;
public class Exam0223 {
public static void main(String[] args) {
int i1 = 100;
Integer obj = Integer.valueOf(200);
printInt(obj);
// 컴파일러가 printInt(obj.intValue())로 바꾼다.
// 즉 "오토 언박싱"을 수행한다.
printObject(i1);
// 컴파일러가 printObject(Integer.valueOf(100)) 으로 바꾼다.
// 즉 "오토 박싱"을 수행한다.
}
static void printInt(int value) {
System.out.println(value);
}
static void printObject(Integer obj) {
System.out.println(obj.toString());
}
}
Wrapper 클래스 - 오토박싱(auto-boxing)/오토언박싱(auto-unboxing) 응용
package com.eomcs.basic.ex02;
public class Exam0224 {
static class Member {
String name;
String tel;
@Override
public String toString() {
return name + "," + tel;
}
}
public static void main(String[] args) {
// wrapper 클래스를 이용하면,
// primitive data type의 값을 객체로 다룰 수 있다.
Object obj;
String str = new String("Hello");
obj = str; // 다형적 변수 문법에 따라 상위 클래스의 레퍼런스에 저장할 수 있다.
Member member = new Member();
member.name = "홍길동";
member.tel = "010-1111-2222";
obj = member; // 다형적 변수 문법에 따라 상위 클래스의 레퍼런스에 저장할 수 있다.
// 위의 코드에서 String이나 Member 처럼
// primitive type의 값을 객체로 다룰 수 있다.
int i = 100;
obj = i; // auto-boxing 규칙에 따라 Integer.valueOf(i) 문장으로 변환한다.
// obj 레퍼런스에 들어 있는 값이
// int의 wrapper 클래스인지 확인해보자!
System.out.println(obj instanceof Integer);
}
}
Wrapper 클래스 - wrapper 객체 생성
package com.eomcs.basic.ex02;
public class Exam0230 {
public static void main(String[] args) {
// new 명령을 사용하여 Integer 객체를 만들면
// 무조건 새 인스턴스를 생성한다.
Integer obj1 = new Integer(100); // Heap에 인스턴스 생성
Integer obj2 = new Integer(100); // Heap에 인스턴스 생성
System.out.println(obj1 == obj2); // false
// 설명:
// => new 연산자를 통해 Integer 객체를 생성하면 Heap에 인스턴스를 생성한다.
// => 그래서 같은 값이더라도 다른 인스턴스가 생성된다.
// auto-boxing 으로 Wrapper 객체를 생성할 경우,
Integer obj3 = 100; // Integer.valueOf(100)
Integer obj4 = 100; // Integer.valueOf(100);
System.out.println(obj3 == obj4); // true
// 설명:
// => 정수 값이 -128 ~ 127 범위일 경우
// 자주 사용되는 수이기 때문에
// String 리터럴처럼 상수 풀에 Integer 객체를 생성한다.
// => 메모리를 효율적으로 사용하기 위해
// 같은 값을 가지는 Integer 객체가 여러 개 존재하지 않게 한다.
// => 그래서 가능한 이 방법을 사용해야 한다.
// auto-boxing은 Wrapper 클래스의 valueOf()를 호출하는 코드로 처리된다.
Integer obj5 = Integer.valueOf(100);
Integer obj6 = Integer.valueOf(100);
System.out.println(obj5 == obj6); // true
// 다음과 같이 auto-boxing으로 생성된 객체와 valueOf()가 리턴한 객체를 비교해 보자!
System.out.println(obj3 == obj5); // true
// 주의!
// -128 ~ 127 범위를 넘어가는 경우 무조건 새 객체를 만든다.
// 이유?
// - 다루는 숫자가 너무 많기 때문에 무조건 상수 풀에 만들기에는
// 오히려 메모리 낭비가 심해지기 때문이다.
// 상수풀에 생성된 객체는 JVM이 종료되기 전까지 유지된다.
// 즉 가비지가 되지 않는다.
// - 그러나 heap에 생성된 객체는 주소를 잃어 버리면 가비지가 되기 때문에
// 메모리를 좀 더 효율적으로 사용할 수 있다.
//
Integer obj7 = 128;
Integer obj8 = 128;
Integer obj9 = 128;
System.out.println(obj7 == obj8); // false
System.out.println(obj7 == obj9); // false
System.out.println(obj8 == obj9); // false
// 따라서 이렇게 생성된 wrapper 객체의 값을 비교할 때는
// String 처럼 equals()로 비교하라!
System.out.println(obj7.equals(obj8));
// 결론!
// - wrapper 객체의 값을 비교할 때 == 연산자를 사용하지 말라!
// - -128 ~ 127 범위 내의 값이라면 == 연산자를 사용하여 비교할 수도 있지만,
// 매번 비교할 때 마다 범위의 유효성을 생각하는 것이 번거롭다.
// - 그냥 equals() 메서드를 사용하여 값을 비교하라!
// - 더 좋은 방법은 auto-unboxing 하여 primitive type 의 값으로 바꾼후에 == 연산자로 비교하라.
}
}
Wrapper 클래스 - wrapper 객체의 값 비교
package com.eomcs.basic.ex02;
public class Exam0231 {
public static void main(String[] args) {
Integer obj1 = Integer.valueOf(100);
Integer obj2 = 100;
System.out.println(obj1 == obj2);
// auto-boxing이나 valueOf()로 생성한 wrapper 객체는
// constants pool에 오직 한 개만 생성되기 때문에
// 값을 비교할 때 그냥 == 연산자를 사용하여 인스턴스 주소로 비교해도 된다.
// 단, -128 ~ 127 범위의 값에 대해서만 적용된다.
// 그러나 다음과 같이 new 연산자로 만든 wrapper 객체는
// 값이 같더라도 인스턴스가 따로 생성되기 때문에,
Integer obj3 = new Integer(100); // Heap에 인스턴스 생성
Integer obj4 = new Integer(100); // Heap에 인스턴스 생성
// 다음과 같이 == 연산자를 사용하여 비교하면 안된다.
System.out.println(obj3 == obj4);
// String 클래스의 경우처럼
// Integer 클래스가 오버라이딩한 equals()를 호출해야 한다.
System.out.println(obj3.equals(obj4));
// 결론:
// => wrapper 객체를 생성할 때는 new 를 사용하지 말고,
// valueOf() 나 auto-boxing 기능을 이용하라.
// => 값을 비교할 때는 반드시 equals()를 사용하라!
//
// '==' 연산자를 사용하면 안되는가?
// => auto-boxing으로 객체를 만들 경우
// -128 ~ 127 범위 내의 숫자인 경우는 캐시에 보관하기 때문에
// 같은 값은 같은 객체이지만,
// 이 범위를 벗어나면 값이 같더라도 객체가 다르다.
// 따라서 일관성을 위해 값을 비교할 때 equals()를 사용하라!
//
// 참고:
// => 모든 wrapper 클래스는 String 클래스처럼
// 상속 받은 Object의 equals()를 오버라이딩 하였다.
// => 즉 인스턴스를 비교하는 것이 아니라 값이 같은지를 비교한다.
}
}
package com.eomcs.basic.ex02;
public class Exam0232 {
public static void main(String[] args) {
// wrapper 객체에 대해 == 연산자를 사용할 때 주의할 점!
// -128 ~ 127 범위의 값으로 Integer 객체를 만들 때는
// 내부적으로 한 개의 인스턴스만 생성한다.
// 그래서 값을 비교할 때 그냥 인스턴스를 비교해도 된다.
Integer obj1 = 100;
Integer obj2 = 100;
System.out.println(obj1 == obj2); // true
// 문제는, -128 ~ 127 범위를 벗어나는 경우 무조건 새 인스턴스를 만든다.
// 따라서 위와 같이 == 연산자로 비교해서는 안된다.
Integer obj3 = 200;
Integer obj4 = 200;
System.out.println(obj3 == obj4); // false
// 결론!
// String이나 Wrapper 인스턴스의 값을 비교할 때는 무조건 equals()를 사용하라!
}
}
java.util.Date 클래스 - 생성자 활용
package com.eomcs.basic.ex02;
import java.util.Date;
public class Exam0310 {
public static void main(String[] args) {
// Date() 기본 생성자
Date d1 = new Date(); // 현재 시간을 저장한다.
System.out.println(d1);
// Date(long) : 1970-01-01 00:00:00 부터 지금까지 경과된 밀리초
Date d2 = new Date(1000);
System.out.println(d2);
Date d3 = new Date(System.currentTimeMillis());
System.out.println(d3);
Date d4 = new Date(123, 0, 19);
System.out.println(d4);
// java.sql.Date
java.sql.Date d5 = new java.sql.Date(System.currentTimeMillis());
System.out.println(d5);
// 간접적으로 객체를 생성하기
java.sql.Date d6 = java.sql.Date.valueOf("2023-1-19");
System.out.println(d6);
}
}
java.util.Calendar 클래스 - 생성자 활용
package com.eomcs.basic.ex02;
import java.util.Calendar;
public class Exam0410 {
public static void main(String[] args) {
Calendar c1;
// 생성자가 있다하더라도 접근 권한이 없으면 호출할 수 없다.
// c1 = new Calendar(); // 컴파일 오류!
// Calendar는 인스턴스 생성을 도와주는 별도의 클래스 메서드(스태틱 메서드)를 제공한다.
c1 = Calendar.getInstance();
System.out.println(c1.get(1)); // year
System.out.println(c1.get(2) + 1); // month
System.out.println(c1.get(5)); // date
System.out.println(c1.get(10)); // hour
System.out.println(c1.get(9)); // am/pm
System.out.println(c1.get(12)); // minute
System.out.println(c1.get(13)); // seconds
System.out.println(c1.get(Calendar.YEAR));
System.out.println(c1.get(Calendar.MONTH) + 1);
System.out.println(c1.get(Calendar.DATE));
System.out.println(c1.get(Calendar.HOUR));
System.out.println(c1.get(Calendar.AM_PM));
System.out.println(c1.get(Calendar.MINUTE));
System.out.println(c1.get(Calendar.SECOND));
}
}
객체 생성 디자인 패턴 중 일부 소개
1) 팩토리 메서드(factory method)
- GoF(Gang of Four)의 23가지 디자인 패턴(design pattern) 중 하나이다.
- 인스턴스를 생성해주는 메서드이다.
- 인스턴스 생성 과정이 복잡할 경우에 인스턴스를 생성해주는 메서드를 미리 정의해 둔다.
- 그래서 인스턴스가 필요할 때 마다 메서드를 호출하여 인스턴스를 리턴 받는다.
2) 싱글톤(singleton)
- GoF(Gang of Four)의 23가지 디자인 패턴(design pattern) 중 하나이다.
- 인스턴스를 한 개만 생성하도록 제한할 때 사용한다.
- 생성자를 private으로 처리하여 직접 인스턴스를 생성하지 못하도록 만든다.
- 메서드를 통해 인스턴스를 생성하도록 유도한다.
예외 처리 (try ~ catch ~ finally)
예외 발생과 catch

Prompt.inputInt() 에서 ① 예외 발생하면 ② 보고를 goMainMenu() 에게 한다. 예외 처리를 하지 않으면 ③ 보고를 App.main() 에 한다. 예외 처리를 하지 않으면 ④ 보고를 JVM 에 한다. ⑤ 예외 정보 출력 하고 ⑥ 종료 한다.

Prompt.inputInt() 에서 ① 예외 발생하면 ② 보고를 goMainMenu() 에게 한다. ③ catch 하고 ④ 실행한다. 상위로 보고되지 않는다.

BoardHandler.service() 에서 ① 예외 발생하면 service() 에서 ② catch 해야 보고가 되지 않는다. 그러면 JVM이 종료되지 않는다.
TeacherHandler.service(), StudentHandler.service() 도 마찬가지다.
예외 발생과 예외 리턴

BoardHandler.service(), TeacherHandler.service(), StudentHandler.service() 에서 ① 예외 발생하면 ② 보고를 goMainMenu() 로 하고 여기서 ③ catch 한다. 그리고 다시 ④ 돌아간다.
☆예외가 발생하면 즉시 service()를 나간다. 메인 메뉴로 돌아간다.
BoardHandler.service(), TeacherHandler.service() 에서 ① 예외 발생하면 ② 보고를 goMainMenu() 로 한다. ③ catch 하고 ④ 실행 한다.
StudentHandler.service() 에서 ① 예외 발생하면 ② catch 하여 ③ 실행한다. ☆예외가 발생하더라도 service()를 나가지 않고 계속 실행할 수 있다.
예외 상황을 알리는 고전적인 방법

오류 상황을 리턴 값으로 알린다. 정상적인 리턴 값과의 혼동을 피하기 위해서 일반적으로 리턴하지 않을 값을 리턴하여 오류 상황을 알린다.
예외를 알리는 현대적인 방법

throw 예외 정보를 담은 객체; → 반드시 Throwable 객체여야 한다.
예) throw new Throwable("예외 내용");
→ ☆예외를 던지는 메서드는 선언부에 표시해야 한다.
예외를 던지는 메서드의 signature

리턴타입 메서드명 (...) throws 예외타입, ... { - } → Throwable 클래스
int compute (...) throws Throwable { - } → 어떤 예외를 던지는지 표시한다.
예외를 받는 방법
try {
예외가 발생하는 코드
} catch (Throwable e) { // 예외 객체(Throwable 객체 + 서브클래스 포함)를 받는 파라미터
예외 처리 코드
} fianlly {
예외가 발생하든 정상적으로 수행하든 상관없이 반드시 실행할 코드
}
예제 소스
com.eomcs.exception.ex1
예외 처리 문법을 적용하기 전 - 리턴 값을 이용한 오류 알림!
package com.eomcs.exception.ex1;
public class Exam0110 {
public static void main(String[] args) {
// 유효한 값을 지정하여 메서드를 호출할 때,
int result = Calculator.compute("+", 100, 200);
System.out.println(result);
}
}
예외 처리 문법을 적용하기 전 - 리턴 값을 확인하여 오류 여부를 파악하기
package com.eomcs.exception.ex1;
public class Exam0120 {
public static void main(String[] args) {
// 유효하지 않은 연산자를 지정할 때,
int result = Calculator.compute("#", 100, 200);
// 제대로 계산을 수행했는지 검사하기 위해서 보통 리턴 값을 검사한다.
if (result == -1) {
System.out.println("유효하지 않은 연산자입니다!");
} else {
System.out.println(result);
}
}
}
예외 처리 문법을 적용하기 전 - 리턴 값으로 오류 여부를 알릴 때의 문제점
package com.eomcs.exception.ex1;
public class Exam0121 {
public static void main(String[] args) {
// 리턴 값을 검사하여 오류 여부를 파악하는 방식은
// 다음과 같은 문제가 있다.
// 정상적인 계산 결과도 오류로 취급할 수 있다는 점이다.
int result = Calculator.compute("-", 6, 7);
// 위의 계산 결과는 정상적인 값이다.
// 그럼에도 불구하고 다음과 같이 -1을 리턴하는 경우 오류로 간주하기 때문에
// 잘못된 결과를 출력한다.
if (result == -1) {
System.out.println("유효하지 않은 연산자입니다!");
} else {
System.out.println(result);
}
}
}
예외 처리 문법을 적용하기 전 - 리턴 값으로 오류를 알릴 때의 문제를 극복
package com.eomcs.exception.ex1;
public class Exam0130 {
public static void main(String[] args) {
int result = Calculator2.compute("#", 100, 200);
// 예전에는 작업 실행중에 오류가 발생하면 희귀한 값을 리턴하여 알려줬다.
if (result == -1212121212) {
System.out.println("유효하지 않은 연산자입니다!");
} else {
System.out.println(result);
}
// 일반적인 결과는 정상적으로 수행된다.
result = Calculator2.compute("-", 6, 7);
if (result == -1212121212) {
System.out.println("유효하지 않은 연산자입니다!");
} else {
System.out.println(result);
}
}
}
package com.eomcs.exception.ex1;
public class Exam0131 {
public static void main(String[] args) {
// 아무리 희귀한 값을 리턴한다 하더라도 결국에는 그 희귀한 값 또한
// 정상 값일 수 있다.
// 이것은 해결할 수 없다.
// => 다음 예는 계산 결과가 -1212121212 이다.
// => 그럼에도 불구하고 연산자가 유효하지 않다고 출력된다.
int result = Calculator2.compute("+", -2078654356, 866533144);
if (result == -1212121212) {
System.out.println("유효하지 않은 연산자입니다!");
} else {
System.out.println(result);
}
// 결국 리턴 값을 검사하여 오류 여부를 처리하는 것으로는
// 이런 문제를 해결할 순 없다.
// 리턴 값으로 작업 오류를 알려주는 방식의 한계를 극복하기 위해 만든 문법이
// "예외처리" 문법이다.
// => 예외 상황이 발생했을 때 호출자에게 리턴 값으로 알려주는 것이 아니라,
// 따로 예외 정보를 던져주는 문법이다.
}
}
예외 처리 문법을 적용하기 전 - 예외 발생 시 시스템 멈춤 문제
package com.eomcs.exception.ex1;
import java.util.Scanner;
public class Exam0210 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
while (true) {
System.out.print("입력> ");
String op = keyScan.next();
if (op.equalsIgnoreCase("quit"))
break;
int v1 = keyScan.nextInt();
int v2 = keyScan.nextInt();
// 다음과 일반적인 예외의 경우 리턴 값을 검사하여 처리하면 된다.
// 문제는 0으로 나누는 경우에서 처럼 계산할 수 없는 예외 상황이 발생한 경우,
// JVM은 실행을 종료하게 된다.
// => 0으로 나눌 때처럼 예외가 발생하더라도 JVM을 멈추지 않고
// 계속 정상적으로 실행되게 만드는 문법이 "예외처리"이다.
int result = Calculator2.compute(op, v1, v2);
if (result == -1212121212) {
System.out.println("유효하지 않은 연산자입니다!");
} else {
System.out.println(result);
}
}
keyScan.close();
}
}
com.eomcs.exception.ex2
예외 처리 문법을 적용한 후 - 오류일 때 예외 정보를 별도로 호출자에게 전달한다.
package com.eomcs.exception.ex2;
public class Calculator3 {
public static int compute(String op, int a, int b) throws Throwable {
switch (op) {
case "+": return a + b;
case "-": return a - b;
case "*": return a * b;
case "/": return a / b;
case "%": return a % b;
default:
// 유효하지 않은 연산자인 경우 throw 명령을 이용하여 호출자에게
// 오류 상황을 알린다.
// => 오류 내용은 java.lang.Throwable 객체에 담아 넘긴다.
// => 이때 메서드 선언부에 Throwable 예외를 던지는 메서드임을 표시해야 한다.
throw new Throwable("해당 연산자를 지원하지 않습니다.");
}
}
}
예외 처리 문법을 적용한 후 - 메서드가 던지는 예외 정보를 받는다.
package com.eomcs.exception.ex2;
public class Exam0110 {
public static void main(String[] args) {
String op = "#";
int a = 100;
int b = 200;
// 리턴 값으로 예외 상황을 알리는 것이 아니라,
// 예외 정보를 던지는 방식으로 호출자에게 알린다.
try {
// 예외를 던질 수 있는 메서드를 호출할 때는 try 블록 안에서 호출한다.
// => 예외가 발생하면 리턴 값으로 받는 게 아니라 catch 블록에서 따로 받는다.
int result = Calculator3.compute(op, a, b);
System.out.println(result);
} catch (Throwable e) {
// try 블록 안에서 메서드를 호출하다가 예외가 발생하면
// catch 블록에서 예외 객체를 파라미터로 받는다.
System.out.println(e.getMessage());
}
}
}
예외 처리 문법을 적용한 후 - 시스템을 멈추지 않고 계속 실행할 수 있다.
package com.eomcs.exception.ex2;
import java.util.InputMismatchException;
import java.util.Scanner;
public class Exam0120 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
while (true) {
System.out.print("입력> ");
String op = keyScan.next();
if (op.equalsIgnoreCase("quit"))
break;
try {
int v1 = keyScan.nextInt();
int v2 = keyScan.nextInt();
int result = Calculator3.compute(op, v1, v2);
System.out.println(result);
} catch (InputMismatchException e) {
System.out.println("입력 값이 유효하지 않습니다.");
keyScan.nextLine(); // 입력이 잘못되었을 경우, 나머지 입력을 무시한다.
} catch (Throwable e) {
System.out.println(e.getMessage());
}
}
keyScan.close();
}
}
com.eomcs.exception.ex3
예외 처리 - 개념
예외 상황을 알리는 문법 : throw
예외가 발생했을 때 시스템을 멈추지 않고 적절한 조치를 취해서 시스템이 계속 실행되도록 하는 문법 : try ~ catch
package com.eomcs.exception.ex3;
public class Exam0110 {
static void m() {
// 예외를 호출자에게 알려주는 문법
// => throw [Throwable 객체];
// throw new String("예외가 발생했습니다!"); // 컴파일 오류!
throw new RuntimeException("예외가 발생했습니다!");
}
public static void main(String[] args) {
// 예외를 받았을 때 처리하는 문법
try {
m();
} catch (RuntimeException e) {
// 예외가 발생하면 catch 블록이 실행된다.
// 메서드에서 던진 예외 객체는 catch의 파라미터가 받는다.
// catch 블록에는 예외에 대한 적절한 조치를 수행하는 코드를 둔다.
// 예) 다음과 같이 예외가 발생된 이유를 간단히 출력할 수 있다.
System.out.println(e.getMessage());
}
System.out.println("시스템을 종료합니다.");
}
}
package com.eomcs.exception.ex3;
public class Exam0111 {
static void m() {
// 예외를 호출자에게 알려주는 문법
// => throw [Throwable 객체];
// throw new String("예외가 발생했습니다!"); // 컴파일 오류!
throw new RuntimeException("예외가 발생했습니다!");
}
static void test() {
m();
// m() 메서드가 던진 예외를 받지 않으면?
// 즉시 현재 메서드의 실행을 멈추고 호출자에게 예외 처리를 위임한다.
// 즉, m() 메서드로부터 받은 예외 객체를 이 메서드를 호출한 호출자에게 넘겨 버린다.
// 따라서 다음 출력 코드는 실행되지 않는다.
System.out.println("test() 호출됨!");
}
public static void main(String[] args) {
// 예외를 받았을 때 처리하는 문법
try {
test();
} catch (RuntimeException e) {
// 예외가 발생하면 catch 블록이 실행된다.
// 코드에서 던진 예외 객체는 catch의 파라미터가 받는다.
// catch 블록에는 예외에 대한 적절한 조치를 수행하는 코드를 둔다.
// 예) 다음과 같이 예외가 발생된 이유를 간단히 출력할 수 있다.
System.out.println(e.getMessage());
}
System.out.println("시스템을 종료합니다.");
}
}
package com.eomcs.exception.ex3;
public class Exam0112 {
static void m() {
throw new RuntimeException("예외가 발생했습니다!");
}
static void test() {
m();
// m() 메서드가 던진 예외를 받지 않으면?
// 즉시 현재 메서드의 실행을 멈추고 호출자에게 예외 처리를 위임한다.
// 따라서 다음 출력 코드는 실행되지 않는다.
System.out.println("test() 호출됨!");
}
public static void main(String[] args) {
// 애플리케이션 실행의 최후의 보루인 main()에서 예외를 처리하지 않는다면?
test();
// 위와 같이 test()를 호출하는 main()에서 예외를 처리하지 않는다면,
// 그 예외는 main()을 호출한 JVM에게 전달된다.
// JVM이 예외를 받는다면 무조건 예외 정보를 출력한 후 프로그램을 종료한다.
System.out.println("시스템을 종료합니다.");
}
}
JVM → main() → test() → m() 순으로 호출했다.
Throwable 계층도
Throwable 에는 다음 두 가지가 있다.
- Error → "시스템 예외"
- JVM이 발생시키는 예외
- 정상적인 실행상태로 복귀 불가!
→ 예외 처리 후 즉시 종료해야 한다.
예외 처리 : 현재까지 작업한 내용 임시 저장, 사용자에게 안내 메시지 출력, 오류 기록 남김 - App에서 던지면 안된다.
- Exception → "Application 예외"
- App.에서 발생시키는 예외
- 적절한 조치를 취한 후 정상적인 실행상태로 복귀할 수 있다.
- 개발자가 던지는 예외
- 호출하는 쪽에서 반드시 try ~ catch 로 받아야 한다.
- RuntimeException → "Unchecked Exception"- try ~ catch로 받지 않아도 된다.
- 물론 안 받으면 상위 호출자에게 전달된다(떠넘긴다).
- 예외 처리 편이성
예외 던지기 - 예외 상황을 호출자에게 알려주기
package com.eomcs.exception.ex3;
import java.io.FileNotFoundException;
public class Exam0210 {
// throw 명령어를 사용하여 예외 정보를 호출자에게 던진다.
// => throw [java.lang.Throwable 타입의 객체];
//
// java.lang.Throwable
// => Throwable에는 두 부류의 서브 클래스가 있다.
// 1) java.lang.Error (시스템 오류)
// => JVM에서 발생된 오류이다.
// => 개발자가 사용하는 클래스가 아니다.
// => 이 오류가 발생하면 현재의 시스템 상태를 즉시 백업하고, 실행을 멈춰야 한다.
// => JVM에서 오류가 발생한 경우에는 계속 실행해봐야 소용이 없다.
// 근본적으로 문제를 해결할 수 없다.
// => 오류의 예:
// 스택 오버 플로우 오류, VM 관련 오류, AWT 윈도우 관련 오류, 스레드 종료 오류 등
//
// 2) java.lang.Exception (애플리케이션 오류)
// => 애플리케이션에서 발생시킨 오류이다.
// => 개발자가 사용하는 클래스이다.
// => 적절한 조치를 취한 후 계속 시스템을 실행하게 만들 수 있다.
// => 오류의 예:
// 배열의 인덱스가 무효한 오류, I/O 오류, SQL 오류, Parse 오류, 데이터 포맷 오류 등
//
// 오류를 던진다면 반드시 메서드 선언부에 어떤 오류를 던지는지 선언해야 한다.
// => 메서드 호출자에게 알려주는 것이다.
static void m1() throws Throwable {
throw new Throwable(); // OK!
// 예외를 던질 때 Throwable 클래스를 직접 사용하지 말라!
// 그 하위 클래스를 사용하라.
// 특히 애플리케이션 오류를 의미하는 Exception 클래스를 사용하라.
}
// 여러 개의 오류를 던지는 경우 메서드 선언부에 그대로 나열하라.
static void m2() throws FileNotFoundException, RuntimeException {
int a = 100;
if (a < 0)
throw new FileNotFoundException(); // OK!
else
throw new RuntimeException(); // OK!
}
public static void main(String[] args) {}
}
package com.eomcs.exception.ex3;
public class Exam0211 {
// Error 계열의 예외를 던져서는 안되지만,
// 혹 던진다고 한다면
// 다음과 같이 메서드 선언부에 던지는 예외를 표시(선언)해도 되고,
static void m1() throws Error {
throw new Error();
// OK! 하지만 이 계열의 클래스를 사용하지 말라!
// 왜? JVM 관련 오류일 때 사용하는 클래스이다.
}
// 또는 다음과 같이 선언하지 않아도 된다.
static void m2() {
throw new Error();
}
// 즉 Error 계열의 예외를 던질 경우, 메서드 선언부에 표시하는 것은 선택사항이다.
public static void main(String[] args) {}
}
package com.eomcs.exception.ex3;
public class Exam0212 {
// Exception 계열의 예외를 던질 경우,
// 반드시 메서드 선언부에 어떤 예외를 던지는지 지정해야 한다.
// => 보통 개발자가 애플리케이션을 작성하면서
// 예외를 던질 경우 이 클래스(및 하위 클래스)를 사용한다.
static void m1() throws Exception {
throw new Exception();
// OK! 보통 개발자가 사용하는 예외 클래스이다.
}
// Exception 예외를 던질 경우 반드시 메서드 선언부에 표시해야 한다.
static void m2() { // 컴파일 오류!
throw new Exception();
}
// 메서드의 throws 에 선언할 수 있는 클래스는 Throwable 타입만 가능한다.
static void m3() throws String {
throw new String(); // 컴파일 오류!
// throw 로 던질 수 있는 객체는 오직 java.lang.Throwable 타입만 가능하다.
}
public static void main(String[] args) {}
}
예외 던지기 - RuntimeException 예외를 던질 경우
package com.eomcs.exception.ex3;
public class Exam0220 {
// RuntimeException 클래스는 Exception의 서브클래스이다.
static void m() throws RuntimeException {
throw new RuntimeException(); // OK!
}
// Exception의 서브 클래스임에도 불구하고
// RuntimeException 객체를 던질 경우,
// 메서드 선언부에 예외를 던진다고 표시하지 않아도 된다.
// => "Unchecked Exception"이라 부른다.
// 즉, 해당 메서드를 예외를 던지는지 검사하지 않는 예외라는 뜻이다.
// 왜?
// => 보통 스텔스 모드(비유!)로 예외를 전달할 때 사용한다.
static void m2() {
throw new RuntimeException();
}
public static void main(String[] args) {}
}
던지는 예외를 메서드에 선언하기
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0310 {
// 메서드에서 발생되는 예외는 메서드 선언부에 모두 나열해야 한다.
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else
throw new IOException();
}
public static void main(String[] args) {}
}
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0320 {
// 공통 분모를 사용하여 퉁치는 방법
// => 메서드에서 발생하는 예외의 공통 수퍼 클래스를 지정하여
// 여러 개를 나열하지 않을 수 있다.
// => 그러나 호출자에게 어떤 오류가 발생하는지 정확하게 알려주는 것이
// 유지보수에 도움이 된다.
// 따라서 가능한 그 메서드에서 발생하는 예외는 모두 나열하라!
//
static void m(int i) throws Exception {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else
throw new IOException();
}
public static void main(String[] args) {}
}
던지는 예외 받기 - 예외 처리 안하면 컴파일 오류!
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0410 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else
throw new IOException();
}
public static void main(String[] args) {
// 예외를 던질 수 있다고 선언된 메서드를 호출할 때
// 그 예외 상황에 대한 처리를 하지 않으면 컴파일 오류가 발생한다.
m(1);
}
}
던지는 예외 받기 - 예외 처리 책임을 상위 호출자에게 위임
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0420 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else
throw new IOException();
}
public static void main(String[] args) throws Exception, RuntimeException, SQLException, IOException {
// 예외 처리 방법 1:
// - 예외를 처리하고 싶지 않다면 상위 호출자에게 책임을 떠넘길 수 있다.
m(1);
// 컴파일 오류는 발생하지 않지만,
// - main() 호출자는 JVM이고,
// JVM은 main()에서 던지 예외를 받는 순간 즉시 실행을 멈춘다.
// 그래서 main()의 호출자에게 책임을 떠넘기는 것은 바람직하지 않다.
// - main()은 예외 처리의 마지막 보루이다.
// main()에서 마저 예외 처리를 하지 않으면 프로그램은 멈.춘.다!
}
}
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0421 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else
throw new IOException();
}
// 메서드 안에서 발생하는 예외에 대해
// 메서드 선언부에 모두 적지 않고,
// 그 예외들의 공통 분모에 해당하는 수퍼 클래스만 적어도 된다.
public static void main(String[] args) throws Exception {
m(1);
}
}
던지는 예외 받기 - try ~ catch ~
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0430 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
}
public static void main(String[] args) {
// 예외 처리 방법 2:
// - try ~ catch 를 사용하여 코드 실행 중에 발생된 예외를 중간에 가로챈다.
//
try {
// try 블록에는 예외가 발생할 수 있는 코드를 둔다.
m(3);
System.out.println("실행 성공!");
// try 블록에 있는 코드를 실행하는 중에
// 예외가 발생하면,
// 그 예외 객체를 파라미터로 받을 수 있는
// catch 문을 찾아 실행한다.
//
} catch (IOException e) {
// catch 블록에서 그 예외를 받아서 처리한다.
// 메서드가 던지는 예외 개수 만큼 catch 블록을 선언하면 된다.
System.out.println("IOException 발생");
} catch (SQLException e) {
System.out.println("SQLException 발생");
} catch (RuntimeException e) {
System.out.println("RuntimeException 발생");
} catch (Exception e) {
System.out.println("기타 Exception 발생");
}
}
}
던지는 예외 받기 - catch 블록의 순서
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0440 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
}
public static void main(String[] args) {
try {
// try 블록에서 예외가 발생할 수 있는 메서드를 호출한다.
m(1);
} catch (Exception e) {
// 여러 개의 예외를 받을 때 수퍼 클래스 변수로 먼저 받지 말라!
// 그러면 그 클래스의 모든 서브 클래스 객체도 다 받게 된다.
// 즉 서브 클래스의 변수에서 받을 기회조차 없다.
// => 예외 객체를 정확하게 받고 싶다면 Exam0430.java 처럼
// 서브 클래스 예외부터 받아라.
//
} catch (IOException e) {
} catch (SQLException e) {
} catch (RuntimeException e) {
}
}
}
던지는 예외 받기 - 다형적 변수의 특징을 이용하여 여러 예외를 한 catch에서 받을 수 있다.
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0450 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
}
public static void main(String[] args) {
try {
// try 블록에서 예외가 발생할 수 있는 메서드를 호출한다.
m(1);
} catch (Exception e) {
// RuntimeException, SQLException, IOException 모두
// Exception의 서브 클래스이기 때문에 이 블록에서 모두 처리할 수 있다.
//
}
}
}
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0460 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
}
public static void main(String[] args) {
try {
// try 블록에서 예외가 발생할 수 있는 메서드를 호출한다.
m(1);
} catch (RuntimeException | SQLException | IOException e) {
// OR 연산자를 사용하여 여러 개의 예외를 묶어 받을 수 있다.
//
} catch (Exception e) {
}
}
}
던지는 예외 받기 - Throwable 변수로 예외를 받지 말라!
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0470 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
else if (i < 0)
throw new Error(); // 시스템 오류가 발생하다고 가정하자!
}
public static void main(String[] args) {
try {
// try 블록에서 예외가 발생할 수 있는 메서드를 호출한다.
m(-1);
} catch (Exception e) {
System.out.println("애플리케이션 예외 발생!");
} catch (Error e) {
System.out.println("시스템 예외 발생!");
// 가능한 Error 계열의 시스템 예외를 받지 말라!
// 혹 받더라도 현재 프로그램을 종료하기 전에
// 필수적으로 수행해야 하는 마무리 작업만 수행하도록 하라.
// 왜?
// 시스템 예외는 당장 프로그램을 정상적으로 실행할 수 없는 상태일 때 발생한다.
// 정상적인 복구가 안되는 예외이다.
// 따라서 이 예외를 처리하려 해서는 안된다.
// 시스템을 멈출 수 밖에 없다.
}
}
}
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0471 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
else if (i < 0)
throw new Error(); // 시스템 오류가 발생하다고 가정하자!
}
public static void main(String[] args) {
try {
// try 블록에서 예외가 발생할 수 있는 메서드를 호출한다.
m(-1);
} catch (Throwable e) {
System.out.println("애플리케이션 예외 발생!");
// catch 문을 작성할 때
// 이처럼 무심코 Throwable 변수로 선언하면
// 시스템 오류인 Error 까지 받기 때문에
// JVM에서 발생된 오류에 대해서도 예외 처리를 하는 문제가 발생한다.
// 시스템 오류는 애플리케이션에서 처리할 수 없다.
// 따라서 실무에서는 예외를 받을 때
// Throwable 변수를 사용하지 않는다.
}
}
}
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0472 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
else if (i < 0)
throw new Error(); // 시스템 오류가 발생하다고 가정하자!
}
public static void main(String[] args) {
try {
m(-1);
} catch (Exception e) {
System.out.println("애플리케이션 예외 발생!");
// 이렇게 Exception 변수를 사용하면
// 애플리케이션 예외를 처리하고,
// 시스템 예외는 main() 호출자에게 위임하게 된다.
// 즉 JVM에게 전달한다.
// 이렇게 Exception 계열의 애플리케이션 예외만 처리하라.
}
}
}
예외 처리 후 마무리 작업 - finally 블록
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0510 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
}
public static void main(String[] args) {
try {
System.out.println("try");
m(0);
return;
} catch (RuntimeException | SQLException | IOException e) {
System.out.println("catch 1");
} catch (Exception e) {
System.out.println("catch 2");
} finally {
// 정상적으로 실행하든, 아니면 예외가 발생하여 catch 블록을 실행하든
// finally 블록은 무조건 실행한다.
// 즉 try ~ catch ~ 블록을 나가기 전에 반드시 실행한다.
// 그래서 이 블록에는
// try 에서 사용한 자원을 해제시키는 코드를 주로 둔다.
// => 자원 해제 코드를 둔다.
// => 자원? 파일, DB 커넥션, 소켓 커넥션, 대량의 메모리 등
//
System.out.println("finally");
}
System.out.println("안녕히가세요!");
}
}
package com.eomcs.exception.ex3;
import java.io.IOException;
import java.sql.SQLException;
public class Exam0520 {
static void m(int i) throws Exception, RuntimeException, SQLException, IOException {
if (i == 0)
throw new Exception();
else if (i == 1)
throw new RuntimeException();
else if (i == 2)
throw new SQLException();
else if (i == 3)
throw new IOException();
}
public static void main(String[] args) throws Exception {
try {
m(0);
System.out.println("try");
// m()에서 발생된 예외는
// try 블록에서 받지 않는다.
// 따라서 main() 호출자에게 위임한다.
// => 물론 main() 메서드 선언부에 위임할 예외의 종류를 표시해야 한다.
//
} finally {
// try 블록을 나가기 전에 무조건 실행해야 할 작업이 있다면
// catch 블록이 없어도 finally 블록만 사용할 수 있다.
System.out.println("마무리 작업 실행!");
}
// 이렇게 catch 블록이 없는 try ~ finally ~ 블록을 작성하는 상황:
// - 예외가 발생하면 그 처리는 호출자에게 맡긴다.
// - 그러나 이 메서드를 호출하는 동안 사용한 자원은 이 메서드를 종료하기 전에 해제시킨다.
//
}
}
예외 처리 후 마무리 작업 - finally 블록과 자원 해제
package com.eomcs.exception.ex3;
import java.util.Scanner;
public class Exam0610 {
public static void main(String[] args) {
// 키보드 입력을 읽어 들이는 도구 준비
Scanner keyScan = new Scanner(System.in);
// 스캐너 객체를 사용하여 키보드 입력을 읽어들인다.
// => 예외가 발생한다면?
System.out.print("입력> ");
int value = keyScan.nextInt();
System.out.println(value * value);
// 프로그램을 즉시 종료한다면,
// 스캐너를 다 사용하고 난 다음에 굳이 스캐너에 연결된 키보드와 연결을 끊을 필요는 없다.
// JVM이 종료되면 OS는 JVM이 사용한 모든 자원을 자동으로 회수하기 때문에
// 굳이 close()를 호출하지 않아도 된다.
//
// 그러나 365일 24시간 내내 실행되는 시스템이라면,
// 키보드 입력을 사용하지 않는 동안에는
// 다른 프로그램에서 사용할 수 있도록
// 스캐너와 연결된 키보드를 풀어줘야 한다.
//
// 이것을 "자원해제"라고 부른다.
//
// 보통 자원해제시키는 메서드의 이름이 "close()"이다.
// 당연히 Scanner 클래스에도 자원을 해제시키는 close() 메서드가 있다.
// 그런데 우리는 지금까지 Scanner를 사용하면서 close()를 호출하지 않았다.
//
// 왜?
// 프로그램이 바로 종료되기 때문이다.
// 그러나 우리가 자바로 프로그램을 짤 영역은 서버쪽이다.
// 즉 365일 24시간 내내 실행되는 서버쪽 프로그램을 개발하는 것이기 때문에,
// 항상 자원을 사용한 후 해제시키는 것을 습관적으로 해야 한다.
//
keyScan.close();
// 문제는 close()를 호출하기 전에 예외가 발생한다면,
// 제대로 자원을 해제시키지도 못한다는 것이다.
// 이것을 해결하는 방법은 finally 블록을 사용하는 것이다.
}
}
package com.eomcs.exception.ex3;
import java.util.Scanner;
public class Exam0620 {
public static void main(String[] args) {
Scanner keyScan = null;
try {
keyScan = new Scanner(System.in);
System.out.print("입력> ");
int value = keyScan.nextInt();
System.out.println(value * value);
} finally {
// 이렇게 정상적으로 실행되든 예외가 발생하든 상관없이
// 자원해제 같은 일은 반드시 실행해야 한다.
keyScan.close();
System.out.println("스캐너 자원 해제!");
}
}
}
finally 와 자원 해제
Scanner keyScan;
try {
keyScan = ...;
} finally {
keyScan.close(); // 자원 해제!
}
자동 자원 해제
try (Scanner keyScan = ...;) { // 단, AutoClosable 규칙에 따라 만든 클래스만 사용 가능!
-
}
↑ finally는 어디에? finally에서 close()를 호출하지 않아도 된다. 왜? try { } 을 나갈때 자동으로 close() 를 호출해 준다!
예외 처리 후 마무리 작업 - try-with-resources
package com.eomcs.exception.ex3;
import java.io.FileReader;
import java.util.Scanner;
public class Exam0630 {
static void m() throws Exception {
// 자원해제시키는 코드를 매번 finally 블록을 만들어 작성하기가 귀찮다!
// => try-with-resources 라는 문법을 사용하면
// 굳이 finally 블록에서 close()를 직접 호출할 필요가 없다.
// 자동으로 처리한다.
// => 단 java.lang.AutoCloseable 구현체에 대해서만 가능하다!
// => 문법
// try (java.lang.AutoCloseable 구현체) {...}
try (Scanner keyScan = new Scanner(System.in); // OK!
// FileReader 클래스도 java.lang.AutoCloseable 구현체이다.
FileReader in = new FileReader("Hello.java"); // OK!
// 반드시 AutoCloseable 구현체이어야 한다.
// String s = "Hello"; // 컴파일 오류!
// 변수 선언만 올 수 있다.
// if (true) {} // 컴파일 오류!
) {
System.out.print("입력> ");
int value = keyScan.nextInt();
System.out.println(value * value);
}
}
public static void main(String[] args) throws Exception {
m();
}
}
package com.eomcs.exception.ex3;
public class Exam0640 {
static class A {
}
static class B {
public void close() throws Exception {
System.out.println("B 클래스의 자원을 해제하였습니다.");
}
}
static class C implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("C 클래스의 자원을 해제하였습니다.");
}
}
public static void main(String[] args) throws Exception {
try (
// A 클래스는 AutoCloseable 구현체가 아니기 때문에 여기에 선언할 수 없다.
// A obj = new A(); // 컴파일 오류!
// B 클래스에 close() 메서드가 있어도
// AutoCloseable 구현체가 아니기 때문에 여기에 선언할 수 없다.
// B obj2 = new B(); // 컴파일 오류!
C obj3 = new C(); // OK
) {
System.out.println("try 블록 실행...");
}
// finally 블록에서 C의 close()를 호출하지 않아도,
// 자동으로 호출될 것이다.
// 실행하여 확인하라!
}
}
package com.eomcs.exception.ex3;
public class Exam0641 {
static class B implements AutoCloseable {
public void m(int value) throws Exception {
if (value < 0) {
throw new Exception("음수입니다!");
}
System.out.println("m() 호출!");
}
@Override
public void close() throws Exception {
System.out.println("close() 호출!");
}
}
public static void main(String[] args) {
try (B obj = new B() /* 마지막 문장에는 세미콜론을 붙이지 않아도 된다. */) {
System.out.println("try 블록 실행...시작");
obj.m(-100);
// 예외가 발생하면 try{} 블록을 나가기 전에 close()가 먼저 호출된다.
System.out.println("try 블록 실행...종료");
} catch (Exception e) {
// close()가 호출된 후 catch 블록이 실행된다.
System.out.println("예외 발생!: " + e.getMessage());
}
}
}
package com.eomcs.exception.ex3;
public class Exam0650 {
static class A {
}
static class B implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("B 클래스의 자원을 해제하였습니다.");
}
}
public static void main(String[] args) throws Exception {
B obj2 = null;
try (
obj2 = new B(); // 컴파일 오류!
// 이유: 변수 선언은 반드시 괄호 안에 해야 한다.
B obj3 = new B(); // OK
) {
System.out.println("try 블록 실행...");
}
}
}
com.eomcs.exception.ex4
예외 던지고 받기
package com.eomcs.exception.ex4;
public class Exam0110 {
static void m1() {
m2();
}
static void m2() {
m3();
}
static void m3() {
m4();
}
static void m4() {
System.out.println("m4() 실행됨!");
}
public static void main(String[] args) {
m1();
}
}
Exception 예외 던지고 받기
package com.eomcs.exception.ex4;
public class Exam0120 {
static void m1() throws Exception {
// m2()와 같다.
m2();
}
static void m2() throws Exception {
// m3()에서 발생된 예외를 상위 호출자에게 던지려면
// m2() 메서드에서도 선언부에 예외를 기술해야 한다.
// => m2() 안에서 Exception 예외가 발생합니다" 라고!
m3();
}
static void m3() throws Exception {
// m4()에서 발생된 예외를 상위 호출자에게 던지려면
// m3() 또한 메서드 선언부에 해당 예외를 기술해야 한다.
// => "m3() 안에서 Exception 예외가 발생합니다" 라고!
m4();
}
static void m4() throws Exception {
// 이 메서드에서 발생된 예외를 main()에 전달하는 방법
// => 상위 호출자에게 전달하려면 여기서 try ~ catch ~ 해서는 안된다.
// => 여기서 예외를 처리하지 않으면
// 메서드 선언부에 어떤 예외가 발생하는 지 선언해줘야 한다.
throw new Exception("m4()에서 예외 발생!");
}
public static void main(String[] args) {
try {
m1();
} catch (Exception e) {
e.printStackTrace();
}
// Exception 계열의 예외를 상위 호출자에게 전달하려면,
// 그 호출 경로에 있는 모든 메서드에
// throws 문장을 선언해야 하는 버거로움이 있다.
// 어쩔 수 없다!
// 무조건 선언해야 한다.
}
}
예외 던지고 받기
m4() 실행시 예외 발생하면 throws Exception 선언에 따라 호출한 m3() 에 예외를 throw 한다.
m3() 에 throws Exception 선언에 따라 호출한 m2() 에 예외를 throw 한다.
m2() 에 throws Exception 선언에 따라 호출한 m1() 에 예외를 throw 한다.
m1() 에 throws Exception 선언에 따라 호출한 main() 에 예외를 throw 한다.
main() 에서 catch 한다.
RuntimeException 예외 던지고 받기
package com.eomcs.exception.ex4;
public class Exam0130 {
static void m1() {
// RuntimeException 예외를 받을 경우
// try ~ catch ~ 예외를 처리하지 않으면
// 자동으로 상위 호출자에게 던진다.
m2();
}
static void m2() {
// RuntimeException 예외를 받을 경우
// try ~ catch ~ 예외를 처리하지 않으면
// 자동으로 상위 호출자에게 던진다.
m3();
}
static void m3() {
// m4()에서 어떤 예외가 발생하는지 구체적으로 선언되어 있지 않기 때문에
// m4()를 호출할 때는 예외처리를 고민할 필요가 없다.
// 그냥 예외를 던지지 않는 메서드인 것 처럼 사용하면 된다.
// => 그러나 명심하라!
// m4() 예외가 발생되면 상위 호출자에게 예외를 던질 것이다.
m4();
}
static void m4() /*throws RuntimeException*/ {
// RuntimeException을 상위 호출자에게 전달할 때는
// Error 예외의 경우처럼
// 굳이 메서드 선언부에 지정하지 않아도 된다.
throw new RuntimeException("m4()에서 예외 발생!");
}
public static void main(String[] args) {
try {
m1();
} catch (RuntimeException e) {
// m4() 에서 발생된 예외가 여기까지 도달한다.
System.out.println(e.getMessage());
}
// RuntimeException 계열의 예외를 던지는 메서드를 사용할 때는
// 그 호출 경로에 있는 모든 메서드에 굳이 throws 문장을 선언할 필요가 없다.
// 예외를 처리하고 싶은 곳에서
// catch 블록으로 받아 처리하면 된다.
// 즉 중간에 끼어 있는 메서드를 만들 때
// throws 문장을 선언하지 않아도 되기 때문에 편하다!
// => 스텔스처럼 조용히 예외를 전달한다.
}
}
RuntimeException
m4() 실행시 예외 발생하면 호출한 m3() 에 예외를 throw 한다.
m3() 에서 m2() 로 throw, m2() 에서 m1() 으로 throw, m1() 에서 main() 으로 throw 하고, main() 에서 catch 한다.
예외를 던지겠다는 throws RuntimeException이 없어도 예외를 던질 수 있다.
com.eomcs.exception.ex5
예외 처리 전 - 1) 수동으로 자원 해제
package com.eomcs.exception.ex5;
import java.sql.Date;
import java.util.Scanner;
public class Exam0110 {
static Board read() {
Scanner keyScan = new Scanner(System.in);
Board board = new Board();
System.out.print("번호> ");
board.setNo(Integer.parseInt(keyScan.nextLine()));
System.out.print("제목> ");
board.setTitle(keyScan.nextLine());
System.out.print("내용> ");
board.setContent(keyScan.nextLine());
System.out.print("등록일> ");
board.setCreatedDate(Date.valueOf(keyScan.nextLine()));
keyScan.close(); // 개발자가 직접 자원을 해제시킨다.
// 주의!
// => close()를 호출하기 전에 예외가 발생한다면,
// Scanner 자원이 해제되지 못한다.
// 해결책?
// => 정상적으로 실행되든 예외가 발생하든지 간에
// 무조건 close()가 실행되게 만들라!
// => 어떻게?
// finally {} 에 자원 해제시키는 코드를 담아라!
return board;
}
public static void main(String[] args) {
Board board = read();
System.out.println("---------------------");
System.out.printf("번호: %d\n", board.getNo());
System.out.printf("제목: %s\n", board.getTitle());
System.out.printf("내용: %s\n", board.getContent());
System.out.printf("등록일: %s\n", board.getCreatedDate());
}
}
Java API 문서를 보면 NumberFormatException은 RuntimeException를 상속한다. RuntimeException은 메서드에 throws로 발생한다고 선언하지 않아도 된다.
예외 처리 전 - 2) finally {} 를 이용하여 자원 해제를 자동화 하기
package com.eomcs.exception.ex5;
import java.sql.Date;
import java.util.Scanner;
public class Exam0111 {
static Board read() {
Scanner keyScan = null;
try {
keyScan = new Scanner(System.in);
Board board = new Board();
System.out.print("번호> ");
board.setNo(Integer.parseInt(keyScan.nextLine()));
System.out.print("제목> ");
board.setTitle(keyScan.nextLine());
System.out.print("내용> ");
board.setContent(keyScan.nextLine());
System.out.print("등록일> ");
board.setCreatedDate(Date.valueOf(keyScan.nextLine()));
return board;
} finally {
// 정상적으로 실행하든 예외가 발생하든지 간에 무조건 close()를 실행한다.
keyScan.close();
System.out.println("Scanner 자원을 해제시켰다.");
}
}
public static void main(String[] args) {
Board board = read();
System.out.println("---------------------");
System.out.printf("번호: %d\n", board.getNo());
System.out.printf("제목: %s\n", board.getTitle());
System.out.printf("내용: %s\n", board.getContent());
System.out.printf("등록일: %s\n", board.getCreatedDate());
}
}
예외 처리 전 - 3) try-with-resources 를 이용하여 자원 해제를 자동화 하기
package com.eomcs.exception.ex5;
import java.sql.Date;
import java.util.Scanner;
public class Exam0112 {
static Board read() {
try (Scanner keyScan = new Scanner(System.in)) {
Board board = new Board();
System.out.print("번호> ");
board.setNo(Integer.parseInt(keyScan.nextLine()));
System.out.print("제목> ");
board.setTitle(keyScan.nextLine());
System.out.print("내용> ");
board.setContent(keyScan.nextLine());
System.out.print("등록일> ");
board.setCreatedDate(Date.valueOf(keyScan.nextLine()));
return board;
}
}
public static void main(String[] args) {
Board board = read();
System.out.println("---------------------");
System.out.printf("번호: %d\n", board.getNo());
System.out.printf("제목: %s\n", board.getTitle());
System.out.printf("내용: %s\n", board.getContent());
System.out.printf("등록일: %s\n", board.getCreatedDate());
}
}
예외 처리 후 - 1) 기존 예외 클래스를 사용하여 예외 객체를 받기
package com.eomcs.exception.ex5;
import java.sql.Date;
import java.util.Scanner;
public class Exam0120 {
//
// read() 메서드에서 발생할 수 있는 예외:
// Integer.parseInt() => NumberFormatException 이 발생할 수 있다.
// Data.valueOf() => IllegalArgumentException 이 발생할 수 있다.
//
// 위 두 개의 예외 모두 RuntimeException 계열의 예외이기 때문에
// 메서드 선언부에 throws 문장을 표시할 필요가 없다.
// 즉 생략해도 된다.
//
static Board read() /*throws NumberFormatException, IllegalArgumentException*/ {
// RuntimeException 계열의 예외는 굳이 throws 문장을 선언하지 않아도 되지만,
// read()를 호출하는 개발자에게 어떤 예외가 발생할 수 있는지
// 명확하게 제시해주는 것이 유지보수에 도움이 되기 때문에
// 가능한 메서드 선언부에 발생되는 예외를 명시하는 것이 좋다.
try (Scanner keyScan = new Scanner(System.in)) {
Board board = new Board();
System.out.print("번호> ");
board.setNo(Integer.parseInt(keyScan.nextLine()));
System.out.print("제목> ");
board.setTitle(keyScan.nextLine());
System.out.print("내용> ");
board.setContent(keyScan.nextLine());
System.out.print("등록일> ");
board.setCreatedDate(Date.valueOf(keyScan.nextLine()));
return board;
}
}
public static void main(String[] args) {
try {
Board board = read();
System.out.println("---------------------");
System.out.printf("번호: %d\n", board.getNo());
System.out.printf("제목: %s\n", board.getTitle());
System.out.printf("내용: %s\n", board.getContent());
System.out.printf("등록일: %s\n", board.getCreatedDate());
} catch (RuntimeException e) {
// read()에서 발생된 예외를 받을 때는
// 두 예외를 모두 받을 수 있도록 두 클래스의 부모인
// RuntimeException 파라미터로 받는다.
System.out.println(e.getMessage());
System.out.println("게시물 입력 중에 오류 발생!");
// e.printStackTrace();
}
}
}
=> 그런데 조금 아쉬움이 있는 점은 read() 메서드를 사용하는 개발자가 이 메서드에서 RuntimeException을 던진다는 의미에 대해 직관적으로 이해하기는 어렵다. 그냥 RuntimeException을 던진다고 하니, 예외를 던진다는 것은 이해하지만, 그 예외가 의미하는 바가 무엇인지 즉시 알아보기 힘들다는 얘기다.
예외 처리 후 - 생각해 볼 문제
package com.eomcs.exception.ex5;
import java.sql.Date;
import java.util.Scanner;
public class Exam0121 {
static Board read() {
try (Scanner keyScan = new Scanner(System.in)) {
Board board = new Board();
System.out.print("번호> ");
board.setNo(Integer.parseInt(keyScan.nextLine()));
System.out.print("제목> ");
board.setTitle(keyScan.nextLine());
System.out.print("내용> ");
board.setContent(keyScan.nextLine());
System.out.print("등록일> ");
board.setCreatedDate(Date.valueOf(keyScan.nextLine()));
return board;
}
}
public static void main(String[] args) {
// 이 프로그램을 실행할 때,
// 1) 번호를 입력할 때 숫자가 아닌 것을 입력하면, NumberFormatException 예외가 발생한다.
// 2) 날짜를 입력할 때 yyyy-MM-dd 형식에 맞지 않게 입력하면, IllegalArgumentException 예외가 발생한다.
//
// 예외 클래스 이름을 보면 오류의 원인이 상세하게 나와 있지만,
// 전체적인 그림에서 어느 객체에서 발생된 예외인지 직관적으로 알 수 없다.
//
Board board = read();
System.out.println("---------------------");
System.out.printf("번호: %d\n", board.getNo());
System.out.printf("제목: %s\n", board.getTitle());
System.out.printf("내용: %s\n", board.getContent());
System.out.printf("등록일: %s\n", board.getCreatedDate());
}
}
예외 처리 - 2) 예외에 대해 의미를 부여하기 - 사용자 정의 예외 만들고 사용하기
package com.eomcs.exception.ex5;
import java.sql.Date;
import java.util.Scanner;
public class Exam0130 {
// 실무에서는 개발자에게 예외의 의미를 직관적으로 전달하기 위해
// RuntimeException 같은 평범한, 의미가 모호한 이름의 클래스를 사용하지 않고
// 대신에 기존 예외를 상속 받아 의미있는 이름으로 서브 클래스를 정의한 다음에
// 그 예외 클래스를 던지도록 프로그래밍 한다.
//
static Board read() throws BoardException {
try (Scanner keyScan = new Scanner(System.in)) {
Board board = new Board();
System.out.print("번호> ");
board.setNo(Integer.parseInt(keyScan.nextLine()));
System.out.print("제목> ");
board.setTitle(keyScan.nextLine());
System.out.print("내용> ");
board.setContent(keyScan.nextLine());
System.out.print("등록일> ");
board.setCreatedDate(Date.valueOf(keyScan.nextLine()));
return board;
} catch (Exception 원본오류) {
// 예외가 발생되면 원본 예외를 그대로 던지지 말고,
// 예외의 의미를 직관적으로 파악할 수 있도록 별도의 예외 객체를 만들어 던진다.
// 즉 게시물 관리 작업을 하다가 오류가 발생했음을 직관적으로 알게 한다.
// 어떤 방법?
// => 게시물 예외를 직관적으로 알 수 있는 클래스를 만든다.
// => 그 클래스가 BoardException 이다.
//
throw new BoardException("게시물 입력 도중 오류 발생!", 원본오류);
}
}
public static void main(String[] args) {
try {
Board board = read();
// read() 메서드의 선언부를 보면 BoardException 던진다고 되어 있다.
//
System.out.println("---------------------");
System.out.printf("번호: %d\n", board.getNo());
System.out.printf("제목: %s\n", board.getTitle());
System.out.printf("내용: %s\n", board.getContent());
System.out.printf("등록일: %s\n", board.getCreatedDate());
} catch (BoardException ex) {
ex.printStackTrace();
// 예외 내용이 출력된 것을 보면,
// BoardException 클래스 이름이 나온다.
// 자세한 내용을 파악하기 전에
// 대략적으로 어떤 작업을 하다가 오류가 발생했는지 빠르게 파악할 수 있어 좋다.
}
}
}
package com.eomcs.exception.ex5;
public class BoardException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BoardException() {
super();
}
public BoardException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public BoardException(String message, Throwable cause) {
super(message, cause);
}
public BoardException(String message) {
super(message);
}
public BoardException(Throwable cause) {
super(cause);
}
// 이 클래스는 생성자가 호출될 때 그와 대응하는 수퍼 클래스의 생성자를 호출하는
// 일 외에는 다른 작업을 수행하지 않는다.
//
// 아니, 기능을 추가할 것도 아니면서 왜 RuntimeException을 상속 받았는가?
// => 이 클래스는 기존의 예외 클래스 기능을 확장하기 위함이 아니라,
// 의미있는 이름을 가진 예외 클래스를 만드는 것이 목적이다.
// => 즉 예외가 발생했을 때 클래스 이름으로 어떤 예외인지
// 쉽게 추측할 수 있도록 하기 위함이다.
// => 일종의 분류표로서 사용한다.
}
BoardEaception에서 RuntimeException을 상속한 다음 생성자를 자동으로 생성하려면 아래와 같이 한다.
우클릭 > Source > Generate Constructors from Superclass...
RuntimeException을 상속하면 경고가 뜨는데 serial Version ID 를 추가하라고 나온다.
Add default serial version ID 를 선택해서 추가한다.
public class DaoException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
myapp
16. 예외를 처리하는 방법 1
### 16.예외를 처리하는 방법
- 예외가 발생했을 때 시스템을 멈추지 않게 하는 방법
- 리턴 값을 통해서 예외 상황을 호출자에게 알린다.
- 리턴 값을 검사하여 예외 상황에 대해 조치를 취한다.
package bitcamp.myapp;
import bitcamp.myapp.handler.BoardHandler;
import bitcamp.myapp.handler.StudentHandler;
import bitcamp.myapp.handler.TeacherHandler;
import bitcamp.util.Prompt;
public class App {
public static void main(String[] args) {
goMainMenu();
System.out.println("안녕히 가세요!");
Prompt.close();
} // main()
private static void goMainMenu() {
StudentHandler studentHandler = new StudentHandler("학생");
TeacherHandler teacherHandler = new TeacherHandler("강사");
BoardHandler boardHandler = new BoardHandler("게시판");
loop: while (true) {
System.out.println("1. 학생관리");
System.out.println("2. 강사관리");
System.out.println("3. 게시판");
System.out.println("9. 종료");
int menuNo;
try {
menuNo = Prompt.inputInt("메뉴> ");
} catch (Exception e) {
System.out.println("메뉴 번호가 옳지 않습니다!");
continue;
}
try {
switch (menuNo) {
case 1: studentHandler.service(); break;
case 2: teacherHandler.service(); break;
case 3: boardHandler.service(); break;
case 9: break loop; // loop 라벨이 붙은 while 문을 나간다.
default:
System.out.println("잘못된 메뉴 번호 입니다.");
}
} catch (Exception e) {
System.out.printf("명령 실행 중 오류 발생! - %s : %s\n",
e.getMessage(),
e.getClass().getSimpleName());
}
}
}
} // class App
package bitcamp.myapp.dao;
public class DaoException extends RuntimeException {
private static final long serialVersionUID = 1L;
public DaoException() {
super();
}
public DaoException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public DaoException(String message, Throwable cause) {
super(message, cause);
}
public DaoException(String message) {
super(message);
}
public DaoException(Throwable cause) {
super(cause);
}
}
package bitcamp.myapp.handler;
import bitcamp.myapp.dao.StudentDao;
import bitcamp.myapp.vo.Student;
import bitcamp.util.Prompt;
public class StudentHandler {
// 중략
public void service() {
while (true) {
System.out.printf("[%s]\n", this.title);
System.out.println("1. 등록");
System.out.println("2. 목록");
System.out.println("3. 조회");
System.out.println("4. 변경");
System.out.println("5. 삭제");
System.out.println("6. 검색");
System.out.println("0. 이전");
int menuNo;
try {
menuNo = Prompt.inputInt(String.format("%s> ", this.title));
} catch (Exception e) {
System.out.println("메뉴 번호가 옳지 않습니다.");
continue;
}
try {
switch (menuNo) {
case 0: return;
case 1: this.inputMember(); break;
case 2: this.printMembers(); break;
case 3: this.printMember(); break;
case 4: this.modifyMember(); break;
case 5: this.deleteMember(); break;
case 6: this.searchMember(); break;
default:
System.out.println("잘못된 메뉴 번호 입니다.");
}
} catch (Exception e) {
System.out.printf("명령 실행 중 오류 발생! - %s : %s\n",
e.getMessage(),
e.getClass().getSimpleName());
}
}
}
}
17. 배열 크기 늘리기
배열을 copyOf() 로 복제하면 복제 후 이전 배열은 Garbage가 된다.
→ 단점 : Garbage를 많이 생성하는 방식
### 17. 배열의 크기를 늘리기
- 배열 복제를 이용하여 배열의 크기를 늘리기
package bitcamp.myapp.dao;
import java.util.Arrays;
public abstract class ObjectDao {
private static final int SIZE = 3;
private int count;
protected Object[] objects = new Object[SIZE];
public void insert(Object object) {
if (count == objects.length) {
objects = Arrays.copyOf(objects, objects.length + (objects.length >> 1));
}
this.objects[this.count++] = object;
}
public Object[] findAll() {
return Arrays.copyOf(objects, count);
}
public void update(Object object) {
this.objects[this.indexOf(object)] = object;
}
public void delete(Object object) {
for (int i = this.indexOf(object) + 1; i < this.count; i++) {
this.objects[i - 1] = this.objects[i];
}
this.objects[--this.count] = null;
}
protected abstract int indexOf(Object b);
public int size() {
return this.count;
}
public Object get(int i) {
if (i < 0 || i >= this.count) {
throw new DaoException("인덱스가 무효합니다!");
}
return objects[i];
}
}
18. LinkedList 알고리즘으로 객체 목록 다루기
LinkedList는 data와 next로 구성된 객체가 연결된 방식이다. data 에 값을 저장하고, next에 다음 노드 주소를 저장한다. head 는 맨 앞 노드를 가리키고, tail 은 맨 끝 노드를 가리킨다. size 에 전체 크기를 저장한다.
add 구현 방법이다. 새 노드를 생성하고 값을 tail.data에 저장하고 그 주소를 tail.next 에 저장한다. tail 에 새 노드 주소를 저장하고 size++ 한다.
delete 구현 방법이다. 중간 노드 삭제시 이전 노드의 next에 삭제한 노드의 다음 노드 주소를 저장한다. 삭제한 노드의 next에 null을 저장해서 확실한 garbage가 되도록 한다.
*특징
- 크기를 고정하지 않는다. → 무제한
- 삭제할 때 속도가 빠르다.
### 18. Linked List 알고리즘으로 객체 목록 다루기
- ObjectDao에서 객체 목록을 다룰 때 배열 대신 Linked List 방식을 사용
- 크기를 고정하는 배열 대신 크기를 변경할 수 있는 동적 리스트를 구현
- Linked List의 구현 방법과 동작 원리를 이해
package bitcamp.myapp.dao;
public class Node {
Object value;
Node next;
public Node() {}
public Node(Object value) {
this.value = value;
}
}
package bitcamp.myapp.dao;
public abstract class ObjectDao {
private Node head;
private Node tail;
private int size;
public void insert(Object value) {
// 노드(상자)를 만들어 값을 저장한다.
Node node = new Node(value);
// 리스트의 마지막 노드와 연결한다.
// => 만약 리스트에 노드가 없으면 현재 노드를 시작 노드 겸 마지막 노드로 설정한다.
if (this.tail == null) { // size == 0, head == null
this.head = this.tail = node;
} else {
// => 마지막 노드의 next 필드에 새 노드 주소를 담는다.
this.tail.next = node;
// => 새 노드가 마지막 노드가 되도록 tail 의 주소를 바꾼다.
this.tail = node;
}
// 값을 저장할 때 마다 개수를 카운트 한다.
this.size++;
}
public Object[] findAll() {
// 리스트의 각 노드에 보관된 값을 담을 배열을 준비한다.
Object[] values = new Object[this.size];
// 리스트의 각 노드를 따라 가면서, 노드에서 값을 꺼내 배열에 담는다.
int index = 0;
// 시작 노드의 주소를 가져온다.
Node cursor = this.head;
while (cursor != null) {
// 커서가 가리키는 노드의 value 필드의 값을 꺼내 배열에 담는다.
values[index++] = cursor.value;
// 커서가 가리키는 노드의 next 필드에는 다음 노드의 주소가 들어 있다.
// 이것을 커서에 저장한다.
cursor = cursor.next;
}
// 배열에 값을 담았으면 리턴한다.
return values;
}
public void update(Object value) {
// 변경할 값이 보관된 노드의 인덱스를 알아낸다.
int index = this.indexOf(value);
if (index == -1) {
// 해당 값이 보관된 위치를 찾지 못했으면 예외를 발생시킨다.
throw new DaoException("변경할 값을 찾을 수 없습니다.");
}
// 시작 노드의 주소를 가져온다.
Node cursor = head;
int i = 0;
// 해당 인덱스의 노드를 찾는다.
while (i < index) {
// 커서를 다음 노드로 이동한다.
cursor = cursor.next;
// index를 증가시킨다.
i++;
}
// 커서가 가리키는 노드의 값을 파라미터로 받은 값으로 바꾼다.
cursor.value = value;
}
public void delete(Object value) {
// 삭제할 값을 보관하고 있는 노드의 인덱스를 알아낸다.
int index = this.indexOf(value);
if (index == -1) {
// 삭제할 값이 보관된 위치를 찾지 못했으면 예외를 발생시킨다.
throw new DaoException("삭제할 값을 찾을 수 없습니다.");
}
// 이전 노드의 주소를 저장할 레퍼런스를 준비한다.
Node prevNode = null;
// 시작 노드를 가져온다.
Node cursor = head;
int i = 0;
// 해당 인덱스의 노드를 찾는다.
while (i < index) {
// 커서를 다음 노드를 이동시키기 전에 노드의 주소를 보관한다.
prevNode = cursor;
// 커서를 다음 노드로 이동한다.
cursor = cursor.next;
// index를 증가시킨다.
i++;
}
if (prevNode == null) {
// 삭제할 노드가 시작 노드라면,
// 현재 head가 가리키는 다음 노드를 시작 노드로 설정한다.
head = head.next;
// 이전의 시작 노드의 next 필드 값을 지운다.
cursor.next = null;
// 리스트의 개수가 0이라면 tail의 주소를 지운다.
if (head == null) {
tail = null;
}
} else {
// 이전 노드가 커서의 다음 노드를 가리키도록 한다.
prevNode.next = cursor.next;
// 삭제할 노드의 다음 노드 주소를 지운다.
cursor.next = null;
// 삭제한 노드가 마지막 노드인 경우
if (prevNode.next == null) {
// 마지막 노드의 주소를 바꾼다.
tail = prevNode;
}
}
// 목록의 개수를 하나 줄인다.
this.size--;
}
// 객체의 위치를 찾는 것은
// 객체의 타입에 따라 다를 수 있기 때문에
// 이 클래스에서 정의하지 말고,
// 서브 클래스에서 정의할 수 있도록
// 그 구현의 책임을 위임해야 한다.
protected abstract int indexOf(Object b);
public int size() {
return this.size;
}
public Object get(int index) {
if (index < 0 || index >= this.size) {
throw new DaoException("인덱스가 무효합니다!");
}
// 시작 노드의 주소를 가져온다.
Node cursor = head;
int i = 0;
while (i < index) {
// 커서를 다음 노드로 이동한다.
cursor = cursor.next;
// index를 증가시킨다.
i++;
}
// 현재 커서가 가리키는 노드에서 value 필드의 값을 꺼내 리턴한다.
return cursor.value;
}
}
LinkedList 의 insert()
값이 없을 때 head, tail = null, size = 0 이다. tail == 0 일때 insert() 하면 Node 객체를 생성하고 value에 값을 넣고 head와 tail 에 객체 주소를 넣는다. size++ 한다.
insert 로 값 추가시 새 객체를 생성하고 value에 값을 넣고 그 주소를 이전 객체의 next에 넣는다. tail에 해당 객체 주소를 넣는다. size++ 한다.
LinkedList 의 findAll()
findAll() 하면 size 크기로 배열을 만든다. cursor 로 head를 가리키게 하고 cursor.value의 값을 배열을 첫번째에 넣고 index++한다. cursor에 cursor.next를 넣는다.
다시 cursor.value의 값을 배열의 다음에 넣는다. 이를 index < size 만큼 반복한다.
LinkedList 의 get(2)
get(2) 로 값을 꺼내는 방법이다. index = 2 를 넣고 i = 0 에서 시작한다. cursor가 head를 가리킨다. i++ 시키고 head 의 next 를 cursor에 넣는다. 이를 i <= index 까지 반복한다. 반복문이 끝났을 때 cursor.value를 return 한다.
LinkedList의 delete(2)
delete는 중간 삭제, 맨 앞 삭제, 맨 뒤 삭제가 다르다.
delete(2) 의 중간 삭제는 먼저 get(2) 처럼 cursor가 가리키게 한다. 그리고 이전 Node를 저장한 prevNode.next에 cursor.next 를 저장한다. cursor.value = 0 및 cursor.next = null 한다. size-- 한다.
맨 앞 삭제는 head를 cursor에 넣는다. head = head.next 저장하고 cursor.value = 0 및 cursor.next = null 한다. size-- 한다.
맨 뒤 삭제는 get(size) 로 cursor에 tail 저장한다. prevNode.next = null 한다. cursor.value = 0 한다. tail = prevNode 한다. size-- 한다.
조언
*계속 성장할 거면 관리직도 괜찮다. 그러나 아니면 엔지니어로 가야한다.
*네카라쿠배 코딩시험시 LinkedList 의 delete() 구현하는 것이 손코딩으로 나올 수 있다.
과제
com.eomcs.exception.ex91 ~ 97 학습하기