자바를 "자바라" (Java "java")
7. 자 우리 한 번 자바에 날개를 달아볼까?
예외를 처리하는 방법!
프로그램을 만들다 보면 수없이 많은 예외 상황이 발생한다.
원하는 대로 예외를 처리하기 위해서 try ~ catch, throws 구문을 이용해 보자.
먼저, try ~ catch 문의 기본 구조를 살펴보자.
try {
<수행할 문장 1>;
<수행할 문장 2>;
...
} catch(예외1) {
<수행할 문장 A>;
...
} catch(예외2) {
<수행할 문장 a>;
...
}
try 문 안의 수행할 문장 중에서 예외가 발생하지 않는다면 catch 문에 속한 문장들은 수행되지 않는다.
하지만 try 문 안의 문장을 수행하는 도중에 예외가 발생하면 예외에 해당되는 catch 문이 수행된다.
public class Sample {
public void shouldBeRun() {
System.out.println("ok thanks.");
}
public static void main(String[] args) {
Sample sample = new Sample();
int c;
try {
c = 4 / 0;
sample.shouldBeRun(); // 이 코드는 실행되지 않는다.
} catch (ArithmeticException e) {
c = -1;
}
}
}
여기서 sample.shouldBeRun()는 절대로 실행될 수 없다.
ArithmeticException이 발생하여 catch 구문으로 넘어가기 때문이다.
이런 경우를 처리하기 위해 자바에서는 다음과 같이 finally 문을 사용한다.
public class Sample {
public void shouldBeRun() {
System.out.println("ok thanks");
}
public static void main(String[] args) {
Sample sample = new Sample();
int c;
try {
c = 4 / 0;
} catch (ArithmeticException e) {
c = -1;
} finally {
sample.shouldBeRun(); // 예외에 상관없이 무조건 수행된다.
}
}
}
finally 문은 try 문장 수행 중 예외 발생 여부에 상관없이 무조건 실행된다.
예외를 직접 만들어 보고 어떻게 활용할 수 있는지 알아보자.
예외는 크게 두 가지로 구분된다.
- RuntimeException: 실행 시 발생하는 예외
- Exception: 컴파일 시 발생하는 예외
class FoolException extends RuntimeException {
}
public class Sample {
public void sayNick(String nick) {
if("바보".equals(nick)) {
throw new FoolException();
}
System.out.println("당신의 별명은 "+nick+" 입니다.");
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.sayNick("바보");
sample.sayNick("야호");
}
}
단순히 return했던 부분을 throw new FoolException()이라는 문장으로 변경하였다.
sayNick을 호출한 곳에서 FoolException을 처리하도록 예외를 위로 던질 수 있는 방법이 있다.
public class Sample {
public void sayNick(String nick) throws FoolException {
try { // try .. catch 문을 삭제할수 있다.
if("바보".equals(nick)) {
throw new FoolException();
}
System.out.println("당신의 별명은 "+nick+" 입니다.");
}catch(FoolException e) {
System.err.println("FoolException이 발생했습니다.");
}
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.sayNick("바보");
sample.sayNick("야호");
}
}
sayNick 메서드 뒷부분에 throws라는 구문을 이용하여 FoolException을 위로 보낼 수가 있다(이를 ‘예외를 뒤로 미루기’라고도 한다).
throw와 throws는 예외 처리와 관련된 키워드로 다음과 같은 차이점이 있다.
-throw: 메서드 내에서 예외를 발생시키는 데 사용된다.(예: throw new FoolException())
-thorws: 메서드 선언부에서 사용되며, 해당 메서드가 처리하지 않은 예외를 호출자에게 전달함을 나타낸다.(예: public void sayNick(String nick) throws FoolException)
트랜잭션과 예외 처리가 서로 어떤 관련이 있는지 알아보자.
상품발송() {
try {
포장();
영수증발행();
발송();
}catch(예외) {
모두취소(); // 하나라도 실패하면 모두 취소한다.
}
}
포장() throws 예외 {
...
}
영수증발행() throws 예외 {
...
}
발송() throws 예외 {
...
}
이와 같이 코드를 작성하면 포장, 영수증발행, 발송이라는 세 개의 단위 작업 중 하나라도 실패할 경우 예외가 발생되어 상품발송이 모두 취소될 것이다.
Thread
동작하고 있는 프로그램을 프로세스(process)라고 한다.
보통 한 개의 프로세스는 한 가지의 일을 하지만, 스레드(thread)를 이용하면
한 프로세스 내에서 두 가지 또는 그 이상의 일을 동시에 할 수 있다.
public class Sample extends Thread {
public void run() { // Thread 를 상속하면 run 메서드를 구현해야 한다.
System.out.println("thread run.");
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.start(); // start()로 쓰레드를 실행한다.
}
}
이렇게 스레드가 하나인 경우에는 뭐 동시에 실행하는지 뭔지 알 수가 없다.
public class Sample extends Thread {
int seq;
public Sample(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq + " thread start."); // 쓰레드 시작
try {
Thread.sleep(1000); // 1초 대기한다.
} catch (Exception e) {
}
System.out.println(this.seq + " thread end."); // 쓰레드 종료
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { // 총 10개의 쓰레드를 생성하여 실행한다.
Thread t = new Sample(i);
t.start();
}
System.out.println("main end."); // main 메서드 종료
}
}
이렇게 하면 스레드의 장점을 알 수 있다.
위 코드는 다음과 같이 출력된다.
0 thread start.
4 thread start.
6 thread start.
2 thread start.
main end.
3 thread start.
7 thread start.
8 thread start.
1 thread start.
9 thread start.
5 thread start.
0 thread end.
4 thread end.
2 thread end.
6 thread end.
7 thread end.
3 thread end.
8 thread end.
9 thread end.
1 thread end.
5 thread end.
0번 스레드부터 9번 스레드까지 순서대로 실행되지 않고, 그 순서가 일정치 않은 것을 보면
스레드는 순서에 상관없이 동시에 실행된다는 사실을 알 수 있다.
더욱 재밌는 사실은 스레드가 종료되기 전에 main 메서드가 종료되었다는 사실이다.
main 메서드가 종료될 때 ‘main end.’라는 문자열이 출력되는데 여기서는 중간쯤에 출력되어 있다.
그렇다면 모든 스레드가 종료된 후에 main 메서드를 종료하고 싶은 경우에는 어떻게 해야 할까?
import java.util.ArrayList;
public class Sample extends Thread {
int seq;
public Sample(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq+" thread start.");
try {
Thread.sleep(1000);
}catch(Exception e) {
}
System.out.println(this.seq+" thread end.");
}
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<>();
for(int i=0; i<10; i++) {
Thread t = new Sample(i);
t.start();
threads.add(t);
}
for(int i=0; i<threads.size(); i++) {
Thread t = threads.get(i);
try {
t.join(); // t 쓰레드가 종료할 때까지 기다린다.
}catch(Exception e) {
}
}
System.out.println("main end.");
}
}
스레드를 활용한 프로그래밍을 할 때 가장 많이 실수하는 부분이
스레드가 종료되지 않았는데 스레드가 종료된 줄 알고 그다음 작업을 진행하게 만드는 일이다.
스레드가 모두 종료된 후 그다음 작업을 진행해야 할 때 join 메서드를 꼭 기억하자.
함수형 프로그래밍밍이
람다와 스트림을 사용하는 이유는 작성하는 코드의 양이 줄어들고 읽기 쉬운 코드를 만들 수 있기 때문이다.
람다(lambda)는 익명 함수(anonymous function)를 의미한다.
일반적인 코드와 람다를 적용한 코드를 비교하며 람다에 대해서 자세히 알아보자.
interface Calculator {
int sum(int a, int b);
}
class MyCalculator implements Calculator {
public int sum(int a, int b) {
return a+b;
}
}
public class Sample {
public static void main(String[] args) {
MyCalculator mc = new MyCalculator();
int result = mc.sum(3, 4);
System.out.println(result); // 7 출력
}
}
위 코드가 일반적인 코드라면.
interface Calculator {
int sum(int a, int b);
}
public class Sample {
public static void main(String[] args) {
Calculator mc = (int a, int b) -> a +b; // 람다를 적용한 코드
int result = mc.sum(3, 4);
System.out.println(result);
}
}
이 코드는 람다를 적용한 코드이다 훨씬더 읽기 쉽다는 게 특징이다.
import java.util.Arrays;
import java.util.Comparator;
public class Sample {
public static void main(String[] args) {
int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
int[] result = Arrays.stream(data) // IntStream을 생성한다.
.boxed() // IntStream을 Stream<Integer>로 변경한다.
.filter((a) -> a % 2 == 0) // 짝수만 뽑아낸다.
.distinct() // 중복을 제거한다.
.sorted(Comparator.reverseOrder()) // 역순으로 정렬한다.
.mapToInt(Integer::intValue) // Stream<Integer>를 IntStream으로 변경한다.
.toArray() // int[] 배열로 반환한다.
;
}
}
위 코드는 스트림을 사용한 코드이다
이렇게 람다와 스트림을 사용하면 보다 읽기 쉬운 코드를 만들 수 있다.
Quiz
1. Java에서 try 블록에 포함할 수 없는 것은 무엇인가요?
답 : B)
2. finally 블록의 실행 시점은 언제인가요?
답 : C)
3. 다음 코드에서 출력 결과는?
답 : A)
4. 다음 코드에서 catch 블록이 처리할 수 있는 예외는?
답 : C)
5. throw와 throws의 차이는 무엇인가요?
답: B)
6. catch 블록에서 예외 처리 후 프로그램이 실행을 계속하기 위해 필요한 것은?
답 : C)
7. finally 블록이 실행되지 않을 경우는 언제인가요?
답 : C)
8. 다음 코드에서 출력 결과는?
답 : A)
9. 다음 코드에서 출력 결과는?
답 : C)
10. 다음 코드에서 출력 결과는?
답 : A)
11. Java 8에서 스트림의 주요 특징이 아닌 것은?
답 : B)
12. 람다 표현식의 문법 중 올바르지 않은 것은?
답 : D)
13. 다음 코드에서 출력 결과는?
답 : B)
14. 람다 표현식과 메서드 참조의 차이는?
답 : C)
15. 다음 스트림 코드의 출력 결과는?
답 : B)
16. Java의 함수형 프로그래밍에서 "고차 함수"란 무엇인가요?
답 : B)
17. 다음 코드는 어떤 스트림 연산을 사용하고 있나요?
답 : B)
1.
답 : B)
2.
답 : B)
3.
답 : A)
4.
답 : A)
5.
답 : B)
'WINK-(Web & App) > JAVA 스터디' 카테고리의 다른 글
[2024-2 Java 스터디] 김지수 #7주차 (1) | 2024.11.28 |
---|---|
[2024-2 Java 스터디] 김민서 #7주차 (0) | 2024.11.27 |
[2024-2 Java 스터디] 김태일 #7주차 (0) | 2024.11.27 |
[2024-2 Java 스터디] 김지나 #7주차 (0) | 2024.11.27 |
[2024-2 Java 스터디] 이민형 #6주차 (7장) (0) | 2024.11.22 |