예외처리란(Exception, Error Handling)
코드를 완벽하게 짰다고 해서 항상 프로그램이 성공적으로 도는 것은 아니다. 다양한 예외 상황이 발생할 수 있는데, 이것에 대응하기 위해서 예외 처리 코드가 필요하다.
예외처리의 목적
1. 예외의 발생으로 인한 실행 중인 프로그램의 비정상 종료를 막기 위해서
2. 개발자에게 알려서 코드를 보완할 수 있도록 하게 위해서
자바에서는 상속을 이용해서 모든 예외를 표현합니다. 모든 예외 클래스는 Throwable의 자손 클래스다.
Throwable에는 크게 두 종류의 자식 클래스가 있다.
Error : 프로그램이 종료되어야 하는 심각한 문제를 표현. 대부분 컴퓨터나 JVM이 시스템적으로 동작할 수 없는 상황을 표현
Exception : 프로그램이 종료되지는 않지만 예외나 문제 상황을 표현하기 위해 사용
<Java는 JVM내의 Heap이라는 메모리 공간을 운영체제로부터 할당받아 사용한다. 할당받을 수 있는 최대 메모리 이상을 사용하면, JVM이 다운될 수밖에 없다. 이 경우 OutOfMemoryError가 나면서 프로그램이 종료된다. 자바의 대표적인 에러 상황으로 줄여서 OOM이라고도 한다.>
자바에 미리 정의 되어있는 예외 클래스들이 있다. 기본적으로 이미 있는 것을 사용하되, 필요한 것으로 표현할 수 없거나 구체적인 목적을 가진 예외를 정의하고 싶다면, Throwable 또는 그 하위에 있는 에외 클래스를 상속받아서 자신만의 예외 클래스를 정의할 수 있다.

-우리가 표현하려는 예외 상황은 대부분 Exception 종류일 것이다.
-실행도중 발생하는 Exception은 RuntimeException을 상속받아서 정의해야 한다.
-파일을 읽고 쓰거나, 원격에 있는 저장소로부터 데이터를 읽고 쓸 때 나는 에러를 표현하려면 IOException을 상속받아 정의하면 된다.
try-catch(-finally)형식
try {
// 예외가 발생할 가능성이 있는 코드를 구현합니다.
} catch (FileNotFoundException e) {
// FileNotFoundException이 발생했을 경우,이를 처리하기 위한 코드를 구현합니다.
} catch (IOException e) {
// FileNotFoundException이 아닌 IOException이 발생했을 경우,이를 처리하기 위한 코드를 구현합니다.
} finally {
// 예외의 발생여부에 관계없이 항상 수행되어야하는 코드를 구현합니다.
}
finally 구문은 필수가 아니다.
만약, 예외가 발생하지 않는다면 try → finally 순으로 실행된다.
예외는 중복 catch블럭을 사용하여 다양한 예외처리를 수행할 수 있다. 중복 catch블럭을 사용할 때는 먼저 선언된 catch블럭부터 확인한다. 앞의 catch블럭에서 잡혔다면, 뒤의 catch블럭으로는 전파되지 않는다. 좁은 범위의 예외부터 앞에 선언하는 것이 좋다. 여기서 좁은 범위란 상속관계에서 자식 클래스에 위치할수록 좁은 범위다.
예를 들어 IOException이 발생할 것 같아 예외처리를 하고, 그 외의 예외도 예외처리를 하고 싶다면 IOException을 catch하는 구문을 먼저, Exception을 catch하는 구문을 그 뒤에 작성한다
public class Main {
public static void main(String[] args) {
int number = 10;
int result;
for (int i = 10; i >= 0; i--) {
try {
result = number / i;
System.out.println(result);
} catch (Exception e) {
System.out.println("Exception발생: " + e.getMessage());
} finally {
System.out.println("항상 실행되는 finally 구문");
}
}
}
}

0으로 나눠지는 경우 catch문이 실행되는 예제이다. 마지막 i가 0이 됐을 때 Exception발생 / by zero라고 나온다
by zero가 나오는 이유 : e.getMessage()라는 메소드 때문인데 이 메소드는 예외의 이유를 알려주는 기능을 갖고 있다.
try-with-resource형식
-try-catch문 이외에 try-with-resource문도 존재한다.
-입출력과 함께 자주 쓰이는 구문인데, 일반적으로 사용되었던 자원을 끝난 후에 닫아줘야 하는 것들이 존재하는데
-여기서 try-catch-finally구문보다 편리한 것이 try-with-resource문이다.
-기존의 try-catch문은 자원을 닫을 때 close()를 사용해야 한다.
-try-with-resource문은 try문을 벗어나는 순간 자동적으로 close()가 호출된다.
-try()안의 입출력 스트림을 생성하는 로직을 작성할 때 해당 객체가 AutoClosable인터페이스를 구현한 객체여야 한다.
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (FileOutputStream out = new FileOutputStream("test.txt")) {
// test.txt file 에 Hello Sparta 를 출력
out.write("Hello Sparta".getBytes());
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
→ 이처럼 형식은 try-catch문과 비슷하지만, try()안에 AutoClosable 인터페이스를 구현한 객체를 선언하면 사용할 수 있다 간단하지만, 매우 유용한 예외 형식이 이므로 잘 숙지해야 한다.
<AutoClosable인터페이스에는 예외가 발생할 경우 close()메소드를 호출하기로 정의되어있기 때문에 반드시 AutoClosable인터페이스를 사용해야 한다>
만약에 try-with-resource가 아니라 일반 try catch문을 사용했다면 아래와 같은 코드가 된다. 코드가 길어질 뿐만 아니라
FileOutputStream을 열고 닫을 때 생기는 Exception까지 그 상위에서 catch를 하거나 throws로 감싸줘야 한다.
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
FileOutputStream out = new FileOutputStream("test.txt");
try {
// test.txt file 에 Hello Sparta 를 출력
out.write("Hello Sparta".getBytes());
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
out.close();
}
}
메소드에서의 예외 선언
catch문을 이용해서 예외처리를 하지 않은 경우, 메소드에 throws로 예외가 발생할 수 있다는 것을 알려주어야 한다.
throws 키워드가 있는 함수를 호출한다면, caller 쪽에서 catch와 관련된 코드를 작성해주어야 한다.
void method() throws IndexOutOfBoundsException, IllegalArgumentException {
//메소드의 내용
}
→ 이처럼, 키워드 throws를 사용해서 메소드 내에서 발생할 수 있는 예외를 적어주면 된다
날짜와 시간 다루기
Java에서의 날짜와 시간
- Java를 기반으로 하는 개발을 할 때 날짜를 이용하여 여러 기능을 제공해야 할 때가 있다. 날짜와 시간을 처리하는 클래스들을 다루면서 하나씩 공부를 해보자
- 날짜와 시간을 사용할 때 자주 쓰이는 java.time패키지에 대해서 하나씩 살펴보자
패키지(package)란?
간단하게는 클래스의 묶음이라고 표현할 수 있다. 패키지에는 클래스 혹은 인터페이스를 포함시킬 수 있으며
관련된 클래스끼리 묶어 놓음으로써 클래스를 효율적으로 관리할 수 있다.
public class Main {
public static void main(String[] args) {
System.out.println("now()를 활용하여 생성");
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(date);
System.out.println(time);
System.out.println(dateTime);
System.out.println("of()를 활용하여 생성");
LocalDate newDate = LocalDate.of(2021, 03, 29);
LocalTime newTime = LocalTime.of(22, 50, 55);
System.out.println(newDate);
System.out.println(newTime);
}
}

→ 한번 실제로 실행을 하여 출력 결과를 확인하면 각 클래스가 어떤 정보를 제공하는지 느낌이 온다
now() 와 of()
위 예제에서 사용한 now()와 of()는 객체를 생성할 때 사용된다. now()는 현재의 날짜 시간을, of()는 지정하는 값이 필드에 담긴다.
LocalDate와 LocalTime은 java.time패키지의 가장 기본이 되는 클래스다. 이 두 가지를 사용하는데 익숙해지면 활용하는 것은 훨씬 쉬워질 것이다.
날짜와 시간의 형식 수정
날짜와 시간을 출력하는 방법을 알아봤다. 하지만 출력문의 결과가 원하는 형식이 아닐 수 있다. 이럴 때 우리는 날짜와 시간을 원하는 형식으로 출력할 수 있어야 한다.
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
String shortFormat = formatter.format(LocalTime.now());
System.out.println(shortFormat)

LocalDate.now()를 했을 때와는 다르게 오전/오후가 추가되었으며 보다 직관적인 형태가 되었다.
형식을 변환하는 데 사용한 DateTimeFormatter 클래스는 SHORT 이외에도 다양한 FormatStyle 종류가 있다.
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String myDate = newFormatter.format(LocalDate.now());
System.out.println(myDate);

만약 우리가 사용하고자 하는 형식이 없거나 생각하는 형식이 따로 있을 수 있다. 그럴 경우 우리가 지정한 형식대로 출력이 될 수 있도록 해보는 코드이다.
날짜와 시간 차이 계산
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(2022, 8, 9);
Period period = Period.between(today, birthday);
System.out.println(period.getMonths());
System.out.println(period.getDays());

→ 오늘 일자와 생일 일자 간의 날짜 차이를 계산하기 위해서는 between()을 사용하면 구할 수 있다.
작성하는 날 기준으로 7개월 2일 후면 birthday가 된다는 뜻이다.
컬렉션
이번엔 자료구조를 표현하는 인터페이스인 컬렉션(Collection),그리고 구체적인 자료구조의 종류인 리스트(List), 스택(Stack), 큐(Queue), 셋(Set), 맵(Map)의 개념을 배워볼 것이다.
컬렉션의 개념과 클래스는 코딩을 하면서 숨 쉬는 것처럼 계속해서 사용해야 하는 것이니 반드시 잘 익혀야 한다.
컬렉션 프레임워크란?
-다수의 데이터를 다루기 위한 자료구조를 표현하고 사용하는 클래스의 집합을 의미.
-데이터를 다루는데 필요한 풍부하고 다양한 클래스와 기본 함수를 제공하기 때문에 많이 유용하다.
실제 자바 어플리케이션을 개발할 때 가장 많이 사용할 클래스와 함수가 여기에 포함되어있다.
-컬렉션 프레임워크의 모든 클래스는 Collection interface를 구현(implement)하는 클래스 또는 인터페이스다.
컬렉션 인터페이스와 자료구조
Collection은 모든 자료구조가 구현(implement)하는 인터페이스다. 아래 배우는 모든 자료구조에 해당하는 클래스, 인터페이스는 언제나 Collection 인터페이스를 구현하고 있다.
- List : 순서가 있는 데이터의 집합이며 데이터의 중복을 허용 → ArrayList, LinkedList, Stack 등
- Set : 순서를 유지하지 않는 데이터의 집합이며 데이터의 중복을 허용하지 않는다. → HashSet, TreeSet 등
- Map : 키(key)와 값(value)의 쌍으로 이루어진 데이터의 집합. 순서는 유지되지 않으며 키는 중복을 허용되지 않고 값은 중복을 허용. → HashMap, TreeMap 등
- Stack : 마지막에 넣은 데이터를 먼저 꺼내는 자료구조. LIFO(Last In First Out) → Stack, ArrayDeque 등
- Queue : 먼저 넣은 데이터를 먼저 꺼내는 자료구조. FIFO(First In First Out) → Queue, ArrayDeque 등
컬렉션 인터페이스에는 컬렉션 클래스에 저장된 데이터를 읽고, 추가하고 삭제하는 등 데이터를 다루는데 기본적인
메소드들을 정의하고 있다. 이러한 메소드들을 실제로 구현을 해보면 빠르게 익힐 수 있다고 생각한다.
List : 순서가 있는 나열된 데이터를 표현한다.
public class Main {
public static void main(String[] args) {
List list = new ArrayList(10);
list.add(1);
list.add(5);
list.add(4);
list.add(11);
list.add(10); // ArrayList에 값 한개씩 입력
System.out.println(list); // [1,5,4,11,10]
Collections.sort(list); // list 정렬
System.out.println(list); // [1,4,5,10,11]
System.out.println(list.size()); // arrayList의 크기 출력
list.remove(4); // 인덱스를 활용하여 해당하는 값 제거
System.out.println(list);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i)); // get을 이용하여 값 1개씩 출력
}
for (int current : list) {
System.out.println(current);
}
}
}

Set : 순서를 유지하지 않는 데이터의 집합이며 데이터의 중복을 허용하지 않는다.
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> integerSet = new HashSet<>(); // Collection의 자료형에는 primitive 타입은 올 수 없습니다. primitive 타입에 해당하는 class 가 존재하니 그것을 사용하세요.
integerSet.add(1);
integerSet.add(3);
integerSet.add(2);
integerSet.add(9);// 하나씩 값을 삽입합니다.
System.out.println(integerSet); // 출력을 해보면 순서가 지켜지지 않는 것을 알 수 있습니다.
Set<String> stringSet = new HashSet<>();
stringSet.add("LA");
stringSet.add("New York");
stringSet.add("LasVegas");
stringSet.add("San Francisco");
stringSet.add("Seoul");
System.out.println(stringSet);
stringSet.remove("Seoul"); //Seoul을 HashSet에서 제거해보겠습니다.
System.out.println(stringSet);
ArrayList<String> target = new ArrayList<String>();
target.add("New York");
target.add("LasVegas");//제거할 항목을 ArrayList에 삽입하겠습니다.
stringSet.removeAll(target);//제거항목에 삽입된 도시들을 삭제하겠습니다.
System.out.println(stringSet);
System.out.println("LA가 포함되어있나요? " + stringSet.contains("LA"));
System.out.println("LA가 포함되어있나요? " + stringSet.contains("LasVegas"));
//LA가 HashSet에 포함되어있으면 true를, 그렇지 않으면 false를 반환합니다.
System.out.println("현재 HashSet의 크기는 : " + stringSet.size() + "입니다.");
//HashSet의 크기를 반환합니다.
stringSet.clear();//HashSet의 모든 아이템들을 삭제합니다.
System.out.println(stringSet);
}
}

Map : HashMap은 키(key)와 값(value)을 하나의 데이터로 저장하는 특징을 가진다. 이를 통하여 해싱(hashing)을 가능하게 하여 데이터를 검색하는데 뛰어난 성능을 보인다.
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "apple");
map.put(2, "berry");
map.put(3, "cherry");
System.out.println(map);
System.out.println("1st in map: " + map.get(1));
map.remove(2);
System.out.println(map);
System.out.println(map.containsKey(2));
System.out.println(map.containsValue("cherry"));
map.clear();
System.out.println(map);
}
}

stack : 마지막에 저장한 데이터를 가장 먼저 꺼내는 자료구조, LIFO(Last In First Out)라고 불리기도 한다.
-스택의 예 : -웹브라우저의 앞 페이지 이동 뒤페이지 이동 / 그릇 쌓기
-아래 그림을 보면. 먼저 삽입된 값인 17이 가장 아래로, 이후 , pop()을 통해 값을 반환할 때도 마지막에 삽입된 값인 45가 가장 먼저 반한 된다.

import java.util.Stack;
public class Main {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(3);
stack.push(5);
stack.push(7);
System.out.println(stack); // Stack을 출력합니다
System.out.println(stack.peek()); // Stack의 가장 상단 값을 출력합니다.(삭제는 하지 않습니다.)
stack.pop(); // Stack의 가장 상단 값을 제거합니다.
System.out.println(stack);
System.out.println(stack.size()); // Stack의 크기를 반환합니다.
System.out.println(stack.contains(1)); // Stack에 1이라는 값이 있으면 true를, 그렇지 않으면 false를 반환합니다.
System.out.println(stack.empty()); // STack이 비어있으면 true를, 그렇지 않으면 false를 반환합니다.
System.out.println(stack);
}
}

queae : 처음에 저장한 데이터를 가장 먼저 꺼내게 된다 FIFO(First In First Out)라고 불리기도 한다.
-큐의 예 : 은행 창구 줄 서기/ 인쇄 작업 대기목록
-아래 그림을 보면 큐는 양쪽 끝의 통로가 뚫려있다고 생각하면 된다. 가장 먼저 들어온 Data가 반환이 될 때도 가장 먼저 반환이 되는 구조다.

import java.util.LinkedList;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.add(1);
queue.add(3);
queue.add(5);//Queue에 값 삽입합니다.
System.out.println(queue);//Queue 출력합니다.
System.out.println(queue.poll()); // Queue에서 객체를 꺼내서 반환합니다.
queue.add(7);
queue.add(11);
queue.add(9);
System.out.println(queue);
System.out.println(queue.peek()); //Queue에서 삭제 없이 요소를 반환합니다.
System.out.println(queue);
}
}

ArrayDeque
실무에서는 단순히 Stack, Queue 클래스 대신 ArrayDeque를 더 많이 사용한다고 한다. 기본 Stack, Queue의 기능을 모두 포함하면서도 성능이 더 좋기 때문이다.

deque
우리가 앞서 배운 큐는 한쪽에서만 값이 삽입되고 다른 한쪽에서만 값을 반환하는 자료구조였다.
하지만 deque의 경우 양 끝에서 삽입과 반환이 가능하다.
→ 아래 사진은 deque 구조를 띄는 사진이다. 정말 양 끝에서 삽입과 삭제가 이루어지고 있는데
우리가 예제로 확인해볼 ArrayDeque가 바로 이러한 형태다

import java.util.ArrayDeque;
public class Main {
public static void main(String[] args) {
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(); // ArrayDeque를 이용한 선언(제네릭스 이용)
arrayDeque.addFirst(1);
arrayDeque.addFirst(2);
arrayDeque.addFirst(3);
arrayDeque.addFirst(4); // arrayDeque의 앞에 값을 삽입
System.out.println(arrayDeque);
arrayDeque.addLast(0); // arrayDeque의 끝에 값을 삽입
System.out.println(arrayDeque);
arrayDeque.offerFirst(10); // addFirst와 비슷하지만 큐의 크기 문제가 생길 때, offerFirst는 false를,
// addFrist는 exception을 반환합니다.
System.out.println(arrayDeque);
arrayDeque.offerLast(-1); // arrayDeque의 끝에 값을 삽입
System.out.println(arrayDeque);
System.out.println(arrayDeque.size()); // 7
System.out.println(arrayDeque.removeFirst()); // 첫번째 값을 제거하면서 그 값을 리턴
System.out.println(arrayDeque.removeLast()); // 마지막 값을 제거하면서 그 값을 리턴
System.out.println(arrayDeque);
System.out.println(arrayDeque.size()); // 5
System.out.println(arrayDeque.pollFirst()); // 첫번째 값을 반환 및 제거하면서 그 값을 리턴
System.out.println(arrayDeque);
System.out.println(arrayDeque.size()); // 4
System.out.println(arrayDeque.pollLast()); // 마지막 값을 반환 및 제거하면서 그 값을 리턴
System.out.println(arrayDeque);
System.out.println(arrayDeque.size()); // 3
System.out.println(arrayDeque.peekFirst()); // 첫번째 값을 반환, 제거하지 않음
System.out.println(arrayDeque.peekLast()); // 마지막 값을 반환, 제거하지 않음
System.out.println(arrayDeque.size()); // 3
}
}

'공부기록 > Java' 카테고리의 다른 글
| Java 해쉬함수 (0) | 2022.01.15 |
|---|---|
| Java (네트워킹) (0) | 2022.01.10 |
| Java (제네릭스)(람다)(스트림) (0) | 2022.01.09 |
| Java 객체지향언어 (클래스,인스턴스,메소드,생성자,상속,접근제어자,추상클래스,인터페이스) (0) | 2022.01.06 |
| Java문법 (변수와 상수)(자료형)(연산자)(조건문과반복문) (0) | 2022.01.05 |