[백준 14002 | C++] 가장 긴 증가하는 부분 수열 4 :: 다이나믹 프로그래밍(DP)

문제 링크 : 14002번: 가장 긴 증가하는 부분 수열 4문제 태그 : 다이나믹 프로그래밍 풀이dp테이블의 정의를 잘 설정하는 것이 중요합니다.dp[i]는 ai에서 끝나는 가장 긴 증가하는 부분 수열의 최

yeonee911.tistory.com

 

풀이

다익스트라 풀이에 역추적의 원리만 적용하며 되는 문제였습니다.

 

역추적에 관한 개념을 https://www.acmicpc.net/problem/14002를 통해서 익힌 뒤 바로 풀었습니다.

바로 이전에 방문한 노드를 저장하는 prv배열만 추가 선언해서 해결했습니다.

 

그 후 e정점(도착 도시)에서부터 prv배열을 참고하여 역추적을 시작하였습니다.

#include <iostream>
#include <vector>
#include <stack>
#include <queue>

using namespace std;
const int INF = 1 << 30;
int n, m;

void dijkstra(int s, int e, vector<vector<pair<int, int>>>& g);

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	cin >> n;	// 도시의 개수
	cin >> m;	// 버스의 개수
	vector<vector<pair<int, int>>> g(n + 1);

	int f, t, c;	// from, to, cost
	for (int i = 0;i < m;i++) {
		cin >> f >> t >> c;
		g[f].push_back({ c, t });	// cost, node
	}

	int s, e;
	cin >> s >> e;

	dijkstra(s, e, g);
}

void dijkstra(int s, int e, vector<vector<pair<int, int>>> &g) {
	priority_queue<pair<int, int>> pq;
	vector<int>dist(n + 1, INF);
	vector<int>prv(n + 1, -1);

	dist[s] = 0;
	pq.push({ 0, s });	// cost, node

	while (!pq.empty()) {
		int cur_dist = -pq.top().first;
		int cur_node = pq.top().second;
		pq.pop();

		if (dist[cur_node] != cur_dist) continue;

		for (auto x : g[cur_node]) {
			int nxt_dist = x.first + cur_dist;
			int nxt_node = x.second;

			if (nxt_dist < dist[nxt_node]) {
				dist[nxt_node] = nxt_dist;
				pq.push({ -nxt_dist, nxt_node });
				prv[nxt_node] = cur_node;
			}
		}
	}

	cout << dist[e] << '\n';

	stack<int>stk;
	while (e >= 0) {
		stk.push(e);
		e = prv[e];
	}

	cout << stk.size() << '\n';
	while (!stk.empty()) {
		cout << stk.top() << ' ';
		stk.pop();
	}
}

 

 

풀이

dp테이블의 정의를 잘 설정하는 것이 중요합니다.

dp[i]는 ai에서 끝나는 가장 긴 증가하는 부분 수열의 최대 길이로 정의합니다.

 

그리고 역추적으로 위해서 prv라는 배열을 추가로 선언합니다. 

 

마지막으로 역추적을 다시 순서대로 출력하기 위해 stack을 이용하여 수열을 저장합니다.

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int n;
	cin >> n;
	vector<int>v(n, 0);
	for (int i = 0;i < n;i++)cin >> v[i];
	
	vector<int>dp(n, 1);
	vector<int>prv(n, -1);

	for (int i = 0;i < n;i++) {
		for (int j = 0;j < i;j++) {
			if (v[j] >= v[i]) continue;
			if (dp[j] + 1 <= dp[i]) continue;
			
			dp[i] = dp[j] + 1;
			prv[i] = j;
		}
	}


	int idx = 0;
	for (int i = 0;i < n;i++) {
		if (dp[i] <= dp[idx]) continue;
		idx = i;
	}


	stack <int> stk;
	while (idx >= 0) {
		stk.push(v[idx]);
		idx = prv[idx];
	}

	cout << stk.size() << '\n';
	while (!stk.empty()) {
		cout << stk.top() << ' ';
		stk.pop();
	}
}

 

ICPC Sinchon 25W의 중급 강의를 보고 해결했습니다👍

0. 공식 문서

Paths (Java Platform SE 8 )

 

Java Platform SE 8

 

docs.oracle.com

 

public final class Paths
extends Object
  • public final class
  • final이 붙어서 다른 클래스가 상속 및 변경할 수 없는 class이다.
  • 유틸리티(Utility) 목적으로만 사용됨을 명확하게 한다
    • 여러 클래스에서 공통적으로 사용되는 메서드를 모아서 클래스로 만든 것
    • Helper Class라고도 한다.

 

1. 개요

이 클래스는 경로 문자열 또는 URI를 변환하여 경로를 반환하는 정적 메서드로만 구성된다.

즉 정적 유틸리티 클래스이다.

 

2. 주요 메소드

Path 인터페이스를 직접 구현하는 클래스는 아니며, 주로 Paths.get()를 통해 Path 객체를 반환하는 역할을 한다.

0. 공식 문서

Path (Java Platform SE 8 )

 

Java Platform SE 8

 

docs.oracle.com

 

1. Superinterfaces

All Superinterfaces:

Comparable<Path>, Iterable<Path>, Watchable

public interface Path
extends Comparable<Path>, Iterable<Path>, Watchable

 

즉, Comparable, Iterabe, Watchable안에 Path인터페이스가 있다

 

 

superinterface in java

What is super-interface is java? what is the purpose of super-interface?

stackoverflow.com

 

2. 개요

Path 인터페이스는 파일 시스템에서 파일을 찾는 데 사용할 수 있는 객체를 나타낸다.

  • Path는 계층적인 구조를 가지며, 디렉터리 및 파일명 요소가 나열된다.
  • 각 요소는 특정 구분자(예: / 또는 \) 로 구분된다.
  • 루트 경로(파일 시스템 계층 구조를 나타내는 부분)가 포함될 수도 있다.
  • 가장 마지막에 위치한 요소는 파일 또는 디렉터리의 이름을 의미한다.
  • 나머지 요소들은 디렉터리 이름을 나타낸다.
  • 비어 있는 경로(empty path) 는 하나의 빈 이름 요소만 포함하는 경로를 의미하며, 이 경우 기본(default) 디렉터리를 가리칸다.

 

3. 주요 메소드

3.1 요소 접근 메소드

  • getFileName() → 파일 이름 반환
  • getParent() → 부모 디렉터리 경로 반환
  • getRoot() → 루트 디렉터리 반환
  • subpath() → 경로의 특정 부분(하위 경로) 반환

 

3.2 경로 결합/비교 메소드

  • resolve() 및 resolveSibling() → 다른 경로와 결합 가능
  • relativize() → 두 경로 간의 상대 경로 생성
  • startsWith() 및 endsWith() → 특정 경로로 시작하는지 또는 끝나는지 비교 가능

 

3.3 Watchable 인터페이스 확장

  • Path는 Watchable 인터페이스를 확장하여 파일 변경 사항을 감지할 수 있다.
  • 특정 경로의 디렉터리를 WatchService에 등록하면 파일 변경 이벤트 감지 가능 (예: 파일 생성, 삭제, 수정 감지).

 

4. Accessing Files

Paths는 Files 클래스와 함께 사용하면 파일 및 디렉터리 작업을 쉽게 수행할 수 있다.
예제 코드 (UTF-8 인코딩된 access.log 파일을 읽는 방법):

 

 

5. Interoperability

  • 기본 생성자로부터 생성된 Paths 객체는 java.io.File 클래스와 호환된다
  • 하지만 다른 생성자로부터 생성된 Paths 객체는 java.io.File과 호환되지 않을 수 있다.
  • toPath() → java.io.File 객체에서 Path 객체를 얻음.
  • toFile() → Path 객체를 java.io.File 객체로 변환.

 

6. Interoperability

이 인터페이스의 구현체는 불변(immutable)이며, 다중 스레드 환경에서도 안전하게 사용할 수 있다.

 

풀이

RGB거리 문제와의 차이점은

  • 1번 집의 색은 2번, N번 집의 색과 같지 않아야 한다.

조건이 추가되었다는 것입니다. 문제를 좀 더 쉽게 이해해보자면

 

집들이 원을 이루고 있으며(1번 집과 N번 집이 연속하게 만들어준다.)
이웃한 두 집들과 색이 겹치지 않아야한다.

 

 

추가된 조건은 1번과 N번 집에만 적용이 됩니다. 따라서 이 두 집만 신경쓰면 됩니다.

 

1번 집ⓐR을 색칠한 경우, ⓑG를 색칠한 경우, ⓒB를 색칠한 경우로 나누면 각각의 경우는

N번 집ⓐG/B를 색칠한 경우, ⓑR/B를 색칠한 경우, ⓒR/G를 색칠한 경우와 대응됩니다.

 

그리고 2번부터 N-1번까지의 집은 RGB거리 문제와 동일하게 다이나믹 프로그래밍을 사용하여 풀면 됩니다. 

#include <iostream>
#include <vector>

using namespace std;

const int INF = 1 << 30;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int n;
	cin >> n;

	vector<vector<int>> v(n, vector<int>(3, 0));
	for (int i = 0;i < n;i++) {
		cin >> v[i][0] >> v[i][1] >> v[i][2];
	}

	vector<vector<int>> dp(n, vector<int>(3, 0));

	int ans = INF;

	for (int j = 0;j < 3;j++) {
		if (j == 0) { dp[0][0] = v[0][0]; dp[0][1] = INF; dp[0][2] = INF; }
		if (j == 1) { dp[0][1] = v[0][1]; dp[0][0] = INF; dp[0][2] = INF; }
		if (j == 2) { dp[0][2] = v[0][2]; dp[0][1] = INF; dp[0][0] = INF; }

		for (int i = 1;i < n - 1;i++) {
			dp[i][0] = v[i][0] + min(dp[i - 1][1], dp[i - 1][2]);
			dp[i][1] = v[i][1] + min(dp[i - 1][0], dp[i - 1][2]);
			dp[i][2] = v[i][2] + min(dp[i - 1][0], dp[i - 1][1]);
		}

		int t;

		if (j == 0) {
			dp[n - 1][1] = v[n - 1][1] + min(dp[n - 2][0], dp[n - 2][2]);
			dp[n - 1][2] = v[n - 1][2] + min(dp[n - 2][0], dp[n - 2][1]);
			t = min(dp[n - 1][1], dp[n - 1][2]);
		}
		if (j == 1) {
			dp[n - 1][0] = v[n - 1][0] + min(dp[n - 2][1], dp[n - 2][2]);
			dp[n - 1][2] = v[n - 1][2] + min(dp[n - 2][0], dp[n - 2][1]);
			t = min(dp[n - 1][0], dp[n - 1][2]);
		}
		if (j == 2) {
			dp[n - 1][1] = v[n - 1][1] + min(dp[n - 2][0], dp[n - 2][2]);
			dp[n - 1][0] = v[n - 1][0] + min(dp[n - 2][2], dp[n - 2][1]);
			t = min(dp[n - 1][1], dp[n - 1][0]);
		}

		ans = min(ans, t);
	}

	cout << ans;
}

 

풀이

태그 그대로 누적합과 투포인터 개념을 이용했습니다...

 

문제 조건으로 N이 10^5 이하이기 때문에 누적합 배열을 이중 for문으로 탐색할 시 시간초과 판정을 받습니다. 따라서 선형 시간 내에 해결해야 했고 이를 위한 알고리즘으로 투 포인터를 떠올렸습니다.😎

 

누적합 배열의 경우 (당연히) 오름차순 배열이기 때문에, right 포인터를 오른쪽으로 움직이는 것은 구간의 합이 커지고 left포인터를 오른쪽으로 움직이는 것은 구간의 합이 작아진다는 것을 의미합니다. 

 

따라서 sum[r] - sum[l],구간의 합이 기준값 s보다 크거나 작을 때 경우를 나누어서 포인터를 움직였습니다. 

#include <iostream>
#include <vector>
#include <stack>
#include <queue>
#include <algorithm>

using namespace std;
const int INF = 1 << 30;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int n, s;
	cin >> n >> s;
	vector<int>v(n + 1, 0);
	vector<int>sum(n + 1, 0);
	for (int i = 1;i <= n;i++) cin >> v[i];

	for (int i = 1;i <= n;i++) {
		sum[i] = sum[i - 1] + v[i];
	}

	int res = INF;
	int l = 0;
	int r = 1;
	while ((l <= r) && l <= n && r <=n) {
		int tmp = INF;

		if (sum[r] - sum[l] >= s) {
			tmp = r - l;
			l++;
		}
		else {
			r++;
		}

		if (tmp < res) {
			res = tmp;
		}
	}

	if (res == INF) cout << 0;
	else cout << res;
}

 

  • 문제 링크 : 2252번: 줄 세우기
  • 문제 태그 : 그래프 이론, 위상 정렬, 방향 비순환 그래프

개인적으로 코드포스에서 만났던 문제와 매우 유사한 것 같습니다. (997라운드 B번 : Problem - B - Codeforces)

 

풀이

위상 정렬에 대한 사전 지식 없이 풀었다가 결국 공부했습니다..

 

처음에 떠올렸던 발상은

  1. DFS 탐색을 하며 최장 거리 배열을 업데이트해야겠다.
  2. 이를 위해서는 들어오는 간선이 적은 순서부터 탐색을 시작해야겠다.
  3. 그리고 노드번호와 함께 최장 거리를 priority_queue에 삽입하며 죄장 거리가 작은 값부터 출력해야겠다

최장 거리 배열을 업데이트하는 구현에 실패했습니다..🥲

위상 정렬을 공부하고 나니 기존에 떠올렸던 풀이가 위상 정렬과 비슷한 것 같아서(가령 진입차수 배열 등..) 신기하고 뿌듯했습니다.

#include <iostream>
#include <vector>
#include <stack>
#include <queue>
#include <algorithm>

using namespace std;

const int INF = 1 << 30;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int n, m;
	cin >> n >> m;
	vector<vector<int>> g(n + 1);
	vector<int>inDgree(n + 1, 0);
	int a, b;
	for (int i = 0;i < m;i++) {
		cin >> a >> b;
		g[a].push_back(b);
		inDgree[b] += 1;
	}

	priority_queue<pair<int, int>> pq;
	queue<int>q;
	for (int i = 1;i <= n;i++) {
		if (inDgree[i] == 0) {
			q.push(i);
		}
	}

	while (!q.empty()) {
		int t = q.front();
		q.pop();

		cout << t << ' ';
		for (int x : g[t]) {
			inDgree[x]--;
			if (inDgree[x] == 0) {
				q.push(x);
			}
		}
	}
}

 

풀이

플로이드-워셜을 이용하여 각 정점에서 다른 정점으로 가는 최단 거리를 모두 구해두었습니다. 그리고 해당 거리가 수색 범위 이하면 아이템을 얻는다고 가정하여 아이템 개수를 늘렸습니다.

 

이를 위하여 최단 거리를 저장할 인접 행렬, ans와 각 지역에 있는 아이템 개수를 저장할 배열 item을 선언하였습니다.

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>

using namespace std;

const int INF = 1 << 30;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int n, m, r;
	cin >> n >> m >> r;
	vector<int>item(n + 1, 0);
	for (int i = 1;i <= n;i++) cin >> item[i];

	int a, b, l;
	vector<vector<int>>ans(n + 1, vector<int>(n + 1, INF));
	for (int i = 0;i < r;i++) {
		cin >> a >> b >> l;
		if (ans[a][b] > l) ans[a][b] = l;
		if (ans[b][a] > l) ans[b][a] = l;
	}

	for (int i = 1;i <= n;i++) ans[i][i] = 0;

	for (int k = 1;k <= n;k++) {
		for (int i = 1;i <= n;i++) {
			for (int j = 1;j <= n;j++) {
				if (ans[i][k] != INF && ans[k][j] != INF &&
					ans[i][k] + ans[k][j] < ans[i][j]) {
					ans[i][j] = ans[i][k] + ans[k][j];
				}
			}
		}
	}

	int res = 0;
	for (int i = 1;i <= n;i++) {
		int tmp = 0;
		for (int j = 1;j <= n;j++) {
			if (ans[i][j] <= m) {
				tmp += item[j];
			}
		}

		if (res < tmp) res = tmp;
	}
	
	cout << res;
}

 

플로이드-워셜 개념 문제였습니다. 이 과정에서 특이한 점을 발견해서 두 번 제출했습니다.

 

풀이1

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>

using namespace std;

const int INF = 1 << 30;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int n, m;
	cin >> n >> m;
	vector<vector<pair<int, int>>> g(n + 1);
	vector<vector<int>> ans(n + 1, vector<int>(n + 1, INF));
	for (int i = 0;i < m;i++) {
		int a, b, c;
		cin >> a >> b >> c;

		g[a].push_back({ b, c });
		if (ans[a][b] > c) {
			ans[a][b] = c;
		}
	}

	for (int i = 1;i <= n;i++) {
		ans[i][i] = 0;
	}

	for (int k = 1;k <= n;k++) {	
		for (int i = 1;i <= n;i++) {
			for (int j = 1;j <= n;j++) {
				if (ans[i][k] != INF && ans[k][j] != INF && 
					ans[i][k] + ans[k][j] < ans[i][j]) {
					ans[i][j] = ans[i][k] + ans[k][j];
				}
			}
		}
	}

	for (int i = 1;i <= n;i++) {
		for (int j = 1;j <= n;j++) {
			if (ans[i][j] == INF) cout << 0 << ' ';
			else cout << ans[i][j] << ' ';
		}
		cout << '\n';
	}
}

 

 

풀이2

그래프 문제라서 당연히 인접리스트를 구성했는데, 사실은 필요하지 않은 내용이었습니다. 

대신 인접 행렬을 통해 구성하였습니다.

 

플로이드-워셜 알고리즘 (Floyd-Warshall)

모든 노드간에 최단거리를 구하는 알고리즘다익스트라, 벨만포드는 한 노드에서 모든노드까지의 최단경로Floyd-Warshall은 모든노드에서 모든노드까지의 최단 경로벨만포드와 같이 가중치에 음수

velog.io

 

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>

using namespace std;

const int INF = 1 << 30;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int n, m;
	cin >> n >> m;
	vector<vector<pair<int, int>>> g(n + 1);
	vector<vector<int>> ans(n + 1, vector<int>(n + 1, INF));
	for (int i = 0;i < m;i++) {
		int a, b, c;
		cin >> a >> b >> c;

		g[a].push_back({ b, c });
		if (ans[a][b] > c) {
			ans[a][b] = c;
		}
	}

	for (int i = 1;i <= n;i++) {
		ans[i][i] = 0;
	}

	for (int k = 1;k <= n;k++) {	
		for (int i = 1;i <= n;i++) {
			for (int j = 1;j <= n;j++) {
				if (ans[i][k] != INF && ans[k][j] != INF && 
					ans[i][k] + ans[k][j] < ans[i][j]) {
					ans[i][j] = ans[i][k] + ans[k][j];
				}
			}
		}
	}

	for (int i = 1;i <= n;i++) {
		for (int j = 1;j <= n;j++) {
			if (ans[i][j] == INF) cout << 0 << ' ';
			else cout << ans[i][j] << ' ';
		}
		cout << '\n';
	}
}

1. 예외

1.1 try-catch

  • try : 예외 발생이 예측되는 부분을 넣는다.
  • catch : 예외를 처리한다. 
    • catch는 자바 가상 머신에 의해 호출 된다.
    • try에서 발생한 에러 상황에 대한 정보를 담은 객체를 인자로 전달
    • 이때 객체는 데이터 타입이 Exception 클래스이다

 

1.2 다양한 예외와 다중 캐치

  • try에서 발생한 문제가 무엇이냐에 따라 각 catch에서 실행될 수 있게 분기하는 역할을 하는 것. 다중 catch
  • Exception은 모든 예외를 포괄하는 예외이다.
package org.opentutorials.javatutorials.exception;

class A {
	private int[] arr = new int[3];

	A() {
		arr[0] = 0;
		arr[1] = 10;
		arr[2] = 20;
	}

	public void z(int first, int second) {
		try {
			System.out.println(arr[first] / arr[second]);
		}
		catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("ArrayIndexOutOfBoundsException");
		}
		catch (ArithmeticException e) {
			System.out.println("ArithmeticException");
		}
		catch (Exception e) {
			System.out.println("Exception");
		}
	}
}

public class ExceptionDemo {
	public static void main(String[] args) {
		A a = new A();
		a.z(10, 0);
		a.z(1, 0);
		a.z(2, 1);
	}
}

 

1.3 finally

  • try/catch와 함께 올 수 있다
  • 언제나 try와 catch 뒤에 나와야한다. 
  • try에서 아무런 문제가 발생하지 않은 경우에도 finally 영역의 로직이 실행된다. 
    • 즉 try/catch 문의 결과와는 무관하게 언제나 실행되도록 약속된 로직
package org.opentutorials.javatutorials.exception;

class A {
	private int[] arr = new int[3];

	A() {
		arr[0] = 0;
		arr[1] = 10;
		arr[2] = 20;
	}

	public void z(int first, int second) {
		try {
			System.out.println(arr[first] / arr[second]);
		}
		catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("ArrayIndexOutOfBoundsException");
		}
		catch (ArithmeticException e) {
			System.out.println("ArithmeticException");
		}
		catch (Exception e) {
			System.out.println("Exception");
		}
		finally {
			System.out.println("finally");
		}
	}
}

public class ExceptionDemo {
	public static void main(String[] args) {
		A a = new A();
		a.z(10, 0);
		a.z(1, 0);
		a.z(2, 1);
	}
}

// ===== output =====
// ArrayIndexOutOfBoundsException
// finally
// ArithmeticException
// finally
// 2
// finally

 

 

2. 예외 던지기 : throws

  • throws : 다음 사용자에게 예외 처리 책임을 넘긴다.
package org.opentutorials.javatutorials.exception;

import java.io.*;

class B{
	void run() {
		BufferedReader bReader = null;
		String input = null;
		try {
			bReader = new BufferedReader(new FileReader("out.txt"));
		}catch (FileNotFoundException e) {
			e.printStackTrace();
		}

		try {
			input = bReader.readLine();
		}catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println(input);
	}
}

class C {
	void run() {
		B b = new B();
		b.run();
	}
}

public class ThrowExceptionDemo {
	public static void main(String[] args) {
		C c = new C();
		c.run();
	}
}

 

B에서 예외를 직접 처리하는 대신 B 클래스를 사용하는 C 클래스에게 예외 처리를 넘길 수 있다

package org.opentutorials.javatutorials.exception;

import java.io.*;

class B{
	void run() throws FileNotFoundException, IOException{
		BufferedReader bReader = null;
		String input = null;
		bReader = new BufferedReader(new FileReader("out.txt"));
		input = bReader.readLine();
		System.out.println(input);
	}
}

class C {
	void run() {
		B b = new B();
		try {
			b.run();
		}
		catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}
}

public class ThrowExceptionDemo {
	public static void main(String[] args) {
		C c = new C();
		c.run();
	}
}

 

 

3. 예외 만들기

  • throw를 사용하여 예외 발생시키기
if (right == 0) {
	throw new IllegalArgumentException("두 번째 인자의 값은 0이 될 수 없습니다.")
}

 

예외 사용해야 할 상황
IllegalArgumentException 매개변수가 의도하지 않은 상황을 유발할 때
IllegalStateException 메소드를 호출할 수 있는 상태가 아닐 때
NullPointerException 매개변수 값이 null 일 때
IndexOutOfBoundsException 인덱스 매개변수 값이 범위를 벗어날 때
ArithmeticException 산술적인 연산에 오류가 있을 때

 

3.1 Throwable 클래스

  • Error : 자바 가상 머신/하드 웨어에 문제가 발생해서 더 이상 프로그램을 실행할 수 없는 상태. try/catch처리 필요 없음
  • Exception
    • unchecked exception : 부모 클래스로 RuntimeException이 있음. 예외 처리할 필요 없다.
    • checked exception : 부모 클래스로 RuntimeException이 없음. 반드시 예외 처리해야 한다.
  • 참고하면 좋을 글 : ☕ 자바 에러(Error) 와 예외 클래스(Exception) 💯 이해하기
 

☕ 자바 에러(Error) 와 예외 클래스(Exception) 💯 이해하기

프로그래밍의 오류 종류 프로그램에서 오류가 발생하면 시스템 레벨에서 프로그램에 문제를 야기하여 원치 않는 버그를 일으키거나, 심각하면 실행 중인 프로그램을 강제로 종료시키도 한다.

inpa.tistory.com

 

3.2 사용자 정의 예외

  • 검사 예외 or 비검사 예외 결정
    • 검사 예외 : 예외 처리를 강제 → 사용자가 예외 상황에서 복구/개선할 수 있는 경우
    • 비검사 예외 : 예외 처리 강제하지 않음 → 사용자가 할 수 있는 일이 없거나 종료시키는 것이 바람직한 경우
  • 비검사 예외로 만들기 위해서는 RuntimeException을 상속해야 한다. 
  • Exception을 상속할 경우 try/catch 또는 throws를 통해 예외 처리를 한다.

 

  • [DivideException에서 RuntimeException을 상속]
package org.opentutorials.javatutorials.exception;

class DivideException extends RuntimeException {
    DivideException() {
       super();
    }

    DivideException(String message) {
       super(message);    // 부모 클래스의 생성자를 호출할 때 메시지 전달
    }
}

class Calculator {
    int left, right;

    public void setOperands(int left, int right) {
       this.left = left;
       this.right = right;
    }

    public void divide() {
       if (this.right == 0) {
          throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
       }
       System.out.print(this.left/this.right);
    }
}

public class CalculatorDemo {
    public static void main(String[] args) {
       Calculator c1 = new Calculator();
       c1.setOperands(10, 0);
       c1.divide();
    }
}

 

  • [DivideException에서 Exception을 상속 : try/catch로 검사 예외를 처리]
package org.opentutorials.javatutorials.exception;

class DivideException extends Exception {
    DivideException() {
       super();
    }

    DivideException(String message) {
       super(message);    // 부모 클래스의 생성자를 호출할 때 메시지 전달
    }
}

class Calculator {
    int left, right;

    public void setOperands(int left, int right) {
       this.left = left;
       this.right = right;
    }

    public void divide() {
       try {
          if (this.right == 0) {
             throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
          }
          System.out.print(this.left/this.right);
       }
       catch(DivideException e) {
          e.printStackTrace();
       }
    }
}

public class CalculatorDemo {
    public static void main(String[] args) {
       Calculator c1 = new Calculator();
       c1.setOperands(10, 0);
       c1.divide();
    }
}

 

  • [DivideException에서 Exception을 상속 : try/catch로 검사 예외를 처리]
package org.opentutorials.javatutorials.exception;

class DivideException extends Exception {
    DivideException() {
       super();
    }

    DivideException(String message) {
       super(message);    // 부모 클래스의 생성자를 호출할 때 메시지 전달
    }
}

class Calculator {
    int left, right;

    public void setOperands(int left, int right) {
       this.left = left;
       this.right = right;
    }

    public void divide() throws DivideException{
       if (this.right == 0) {
          throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
       }
       System.out.print(this.left/this.right);
    }
}

public class CalculatorDemo {
    public static void main(String[] args) {
       Calculator c1 = new Calculator();
       c1.setOperands(10, 0);
       try {
          c1.divide();
       }
       catch (DivideException e) {
          e.printStackTrace();
       }
    }
}

 

 

☕ Exception Handling - 자바 예외를 처리하는 3가지 기법

Exception Handling 3가지 기법 자바의 예외를 try - catch 블럭으로 잡았다고 해서 끝이 아니다. 예외가 발생하였으면 코드를 수정하여 문제점을 고쳐야 되는 것은 맞지만, 예상할 수 없는 예외인 경우

inpa.tistory.com

 

 

4. Object 클래스

 

☕ 자바 Object 클래스와 메서드 오버라이딩

Object 클래스 모든 자바의 클래스는 Object 클래스로 부터 시작된다. 즉, Object 클래스가 모든 클래스의 조상 혹은 base 라고 할 수 있다. 사실 우리가 클래스 파일을 만들어 클래스명을 작성하면 자

inpa.tistory.com

 

4.1 toString

  • 인스턴스에 대한 정보를 문자열로 제공
  • 기본적으로 해당 인스턴스에 대한 정보와 주소(해시코드)를 문자열로 반환한다.
  • 오버라이딩을 통해 toString()의 출력 내용을 바꿀 수 있다
  • 인스턴스가 담긴 변수만 println() 메소드의 인자로 전달하면 내부적으로 toString() 메소드를 호출한다.
  • 실제로 toString() 메서드 내부를 본다면 다음과 같이 구현되어있다
    • return getClass().getName() + "@" + Integer.toHexString(hashCode());

 

4.2 equals

  • 어떤 두 개의 인스턴스가 서로 같은 인스턴스인지 비교
  • 객체 간의 동일성을 비교하고 싶을 때는 ==보다 equals() 메소드를 이용하자
  • equals() 메소드를 직접 구현해야 한다면 hashCode() 메소드도 함께 구현해야 한다.
  • 자식이 부모가 될 때는 자동으로 형변환 된다.
    • Object obj = s2
  • 부모가 자식이 될 때는 명시적으로 형변환해야 한다.
    • Student s = (Student)obj;

 

4.3 finalize

  • 객체가 소멸될 때 호출되기로 약속한 메소드
  • 사용할 일이 거의 없는 메소드이다. 사용을 권장하지 않는다.

 

4.4 clone

  • 객체를 복제한다.
  • 복제 가능한 객체라는 사실을 자바 가상 머신에 알려줘야 한다. 따라서 Cloneable이라는 인터페이스를 구현한다.
  • 그러나 Cloneable은 비어있는 인터페이스로 클래스가 복제 가능함을 자바 가상머신에게 알려주기 위한 구분자에 불과하다.
  •  clone() 메소드의 접근 제어자는 protected이기 때문에 반드시 메소드를 public 접근 제어자로 오버라이딩해야 한다.
  • clone() 메소드는 Exception을 상속해서 예외 처리가 필수적이다.

 

 

5. enum

  • enum은 열거형이라고도 한다.
  • 열거형은 서로 연관된 상수의 집합이다.
package org.opentutorials.javatutorials.constant;

enum Fruit {
    APPLE, PEACH, BANANA;
}

enum Company {
    GOOGLE, APPLE, ORACLE;
}

public class ConstantDemo {
    public static void main(String[] args) {
       Fruit type = Fruit.APPLE;
       switch (type) {
          case APPLE:
             System.out.println(57 + " kcal");
             break;
          case PEACH:
             System.out.println(34 + " kcal");
             break;
          case BANANA:
             System.out.println(93 + " kcal");
             break;
       }
    }
}

 

  • enum은 사실상 클래스이므로 생성자를 가질 수 있다.
  • 열거형에 포함된 상수를 values() 메소드를 통해 상수가 담긴 배열을 가져올 수 있다.

 

 

6. 참조

  • 데이터 타입을 생성할 때 new를 통해 생성하는 것들은 기본 데이터 타입이 아니고 참조형 또는 참조 데이터 타입이다. 
package org.opentutorials.javatutorials.reference;

class A {
    public int id;

    A(int id) {
       this.id     = id;
    }
}

public class ReferenceParameterDemo {
    static void _value(int b) {
       b = 2;
    }

    public static void runValue() {
       int a = 1;
       _value(a);
       System.out.println("runvalue, " + a);
    }

    static void _reference1(A b) {
       b = new A(2);
    }

    public static void runReference1() {
       A a = new A(1);
       _reference1(a);
       System.out.println("runReference1, " + a.id);
    }

    static void _reference2(A b) {
       b.id = 2;
    }

    public static void runReference2() {
       A a = new A(1);
       _reference2(a);
       System.out.println("runReference2, " + a.id);
    }

    public static void main(String[] args) {
       runValue();    // runValue, 1
       runReference1();   // runReference1, 1
       runReference2();   // runReference2, 2
    }
}

 

 

7. 제네릭

  • 정의 : 클래스 내부에서 사용할 데이터 타입을 인스턴스를 생성할 때 확정하는 것
  • 제네릭에는 참조형 데이터 타입만 가능하다.
  • 기본 데이터 타입은 제네릭을 사용하려면 래퍼(wrapper) 클래스를 사용한다
 

☕ 자바 Wrapper 클래스와 Boxing & UnBoxing 총정리

래퍼 클래스 (Wrapper Class) 이전 강의글에서 우리는 자바의 자료형은 기본 타입(primitive type)과 참조 타입(reference type) 으로 나누어지고 각 특징에 대해 알아보았다. 프로그래밍을 하다 보면 기본 타

inpa.tistory.com

package org.opentutorials.javatutorials.generic;

import java.net.Inet4Address;

class EmployeeInfo {
    public int rank;

    EmployeeInfo(int rank) {
       this.rank = rank;
    }
}

class Person<T, S> {
    public T info;
    public S id;

    Person(T info, S id) {
       this.info = info;
       this.id = id;
    }
}

public class GenericDemo {
    public static void main(String[] args) {
       EmployeeInfo e = new EmployeeInfo(1);
       Integer i = 10;

       Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
       
       // 제네릭을 명시적으로 언급하지 않고 생략할 수 있다.
       // Person<EmployeeInfo, Integer> p1 = new (e, i);
    }
}

 

7.1 제네릭의 제한

  • extends 키워드를 이용해 특정 데이터 타입만 오도록 할 수 있다.
package org.opentutorials.javatutorials.generic;

abstract class Info {
    public abstract int getLevel();
}

class EmployeeInfo extends Info {
    public int rank;

    EmployeeInfo(int rank) {
       this.rank = rank;
    }

    public int getLevel() {
       return this.rank;
    }
}

class Person<T extends Info> {  // Info 클래스의 자식만 T로 올 수 있다
    public T info;

    Person(T info) {
       this.info = info;
    }
}

public class GenericDemo {
    public static void main(String[] args) {
       Person p1 = new Person(new EmployeeInfo(1));

       // String은 Info의 자식이 아니기 때문에 에러 발생
       // Person<String> p2 = new Person<String>("부장");
    }
}

 

 

8. 컬렉션 프레임워크

 

🧱 Java Collections Framework 종류 💯 총정리

Java Collection Framework 자바 새내기분들은 컬렉션 프레임워크라는 단어에 뭔가 거창하고 어려운 느낌이 들수 있겠지만, 그냥 자료 구조(Data Structure) 종류의 형태들을 자바 클래스로 구현한 모음집

inpa.tistory.com

 

8.1 ArrayList

  • 배열의 문제점 : 몇 개의 값을 가질 수 있는지 지정하게 되어 있다.
  • 해결책 : ArrayList를 사용한다.
    • 몇 개의 값을 담을 수 있는지 지정할 필요가 없다.
    •  java.util.ArrayList를 import해야 한다.
    • add() 메소드를 호출해서 데이터를 추가할 수 있다.
    • get() 메소드를 호출해서 값을 가져올 수 있다.
    • add() 메소드의 매개변수 타입이 Object이다. 따라서 변수에 담으려고 할 때 원하는 데이터 타입으로 강제 형변환 또는 제네릭을 통해 지정해야 한다.

 

8.2 컬렉션 프레임워크 구성

출처 : 🧱 Java Collections Framework 종류 💯 총정리

 

8.3 List / Set

  • List : 추가하는 모든 값이 들어간다.
    • Set과 달리 순서가 존재하기 때문에 인덱스와 관련된 API를 가지고 있다. 
  • Set : 중복되는 값은 들어가지 않고 고유한 값만 저장된다.
    • Collection 인터페이스를 상속받고 있지만 내용이 없다. 즉 Collection 인터페이스와 Set 인터페이스는 동일한 API를 제공한다.
    • Set의 특징은 순서에 상관없이 값이 지정되어 있다는 것이다. 따라서 순서를 기반으로 작업이 실행되는 메소드는 없다.

 

8.4 Iterator

  • 반복자 : 컨테이너에 담긴 값을 하나씩 꺼내서 처리할 수 있게 한다.
  • iterator() 메소드는 Iterator 타입의 객체를 반환한다. 이때 인스턴스에 담긴 값은 참조 값에 불과해서 원본 데이터까지 삭제되지 않는다. 
  • hasNext(), next(), remove() 3개의 메소드를 포함한다.

 

8.5 Map

  • key 값은 중복될 수 없지만 value의 값은 중복될 수 있다.
package org.opentutorials.javatutorials.collection;

import java.util.*;

public class MapDemo {
    public static void main(String[] args) {
       HashMap<String , Integer> a = new HashMap<String, Integer>();
       a.put("one", 1);
       a.put("two", 2);
       a.put("three", 3);
       a.put("four", 4);

       System.out.println(a.get("one"));
       System.out.println(a.get("two"));
       System.out.println(a.get("three"));

       iteratorUsingForEach(a);
       iteratorUsingIterator(a);
    }

	// 방법 1
    static void    iteratorUsingForEach(HashMap map) {
       Set<Map.Entry<String, Integer>> entries = map.entrySet();
       for (Map.Entry<String, Integer> entry : entries) {
          System.out.println(entry.getKey() + " : " + entry.getValue());
       }
    }

	// 방법 2 
    static void    iteratorUsingIterator(HashMap map) {
       Set<Map.Entry<String, Integer>> entries = map.entrySet();
       Iterator<Map.Entry<String, Integer>> i = entries.iterator();
       while (i.hasNext()) {
          Map.Entry<String, Integer> entry = i.next();
          System.out.println(entry.getKey() + " : " + entry.getValue());
       }
    }
}

+ Recent posts