개발자입니다
[비트캠프] 54일차(11주차4일) - Java(Wrapper 클래스, 예외 처리), myapp-16~18 본문
[비트캠프] 54일차(11주차4일) - Java(Wrapper 클래스, 예외 처리), myapp-16~18
끈기JK 2023. 1. 19. 10:37
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()를 나가지 않고 계속 실행할 수 있다.
예외 상황을 알리는 고전적인 방법
Caller가 Calculator.compute() 를 ① call 한다. ② 계산 수행하고 ③ 결과 리턴한다.
오류 상황을 리턴 값으로 알린다. 정상적인 리턴 값과의 혼동을 피하기 위해서 일반적으로 리턴하지 않을 값을 리턴하여 오류 상황을 알린다.
예외를 알리는 현대적인 방법
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 학습하기