본문 바로가기

WINK-(Web & App)/JAVA 스터디

[2024-2 Java 스터디] 이민형 #7주차 (7-4장)

반응형

자바를 "자바라" (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 문장 수행 중 예외 발생 여부에 상관없이 무조건 실행된다.

 

 

 

예외를 직접 만들어 보고 어떻게 활용할 수 있는지 알아보자.

 

예외는 크게 두 가지로 구분된다.

  1. RuntimeException: 실행 시 발생하는 예외
  2. 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) 

 

반응형