Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[비트캠프] 54일차(11주차4일) - Java(Wrapper 클래스, 예외 처리), myapp-16~18 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[비트캠프] 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 학습하기