내배캠 TIL 12일차

오병택·2025년 3월 6일

내배캠

목록 보기
34/73

12일차 요약

계산기 lv2, 트러블슈팅, JAVA 강의

계산기 lv2

11일차에 했던 클래스 분리를 제대로 한 것이 맞는 지 튜터님에게 물어보러 갔다.

튜터님이 말해주신 개선사항

  • io의 메서드를 접근 제어자로 private하고 io에서 계산해서 밖으로 출력되게끔 하는 것이 좋을 것 같다
  • 정수 받는 메서드와 연산자 받는 메서드는 둘다 계산하는 부분이라 합치는 것이 좋을 것 같다

코드 1

package com.example.calculator;

public class App {
    public static void main(String[] args) {
        Calculatorio calculatorio = new Calculatorio();
        boolean exit = true;
        while (exit) {
            calculatorio.calculate();
            exit = calculatorio.exitIO();
        }
    }
}

코드 2

package com.example.calculator;

import java.util.InputMismatchException;
import java.util.Scanner;

public class Calculatorio {
    private final Calculator cal = new Calculator();
    private final Scanner scanner = new Scanner(System.in);
    private Number result;

    public void calculate() {
        calculatorIO();
        resultSaveIO();
    }

    private void calculatorIO() {
        long x;
        long y;
        while (true) {
            try {
                System.out.print("첫 번째 양의 정수(0 포함)를 입력해주세요: ");
                x = scanner.nextLong();
                System.out.print("두 번째 양의 정수(0 포함)를 입력해주세요: ");
                y = scanner.nextLong();
                if (x < 0 || y < 0) {
                    System.out.println("음수를 적으셨습니다. 다시 입력해주세요.");
                    continue;
                }
            } catch (InputMismatchException e) {
                scanner.nextLine();
                System.out.println("숫자만 입력해주세요");
                continue;
            }
            while (true) {
                try {
                    System.out.print("사칙연산 기호를 입력하세요: ");
                    char oper = scanner.next().charAt(0);
                    result = cal.calculate(x, oper, y); // 계산 값을 반환
                    System.out.println("계산 값: " + result);
                    break;
                } catch (ArithmeticException | IllegalArgumentException e) {
                    System.out.println(e.getMessage());
                }
            }
            break;
        }
    }

    private void resultSaveIO() {
        boolean flag = true;
        while (flag) {
            System.out.println("값 저장(0), 값 삭제(1), 값 조회(2), 넘어가기(아무거나 숫자 입력)");
            int num;
            try {
                num = scanner.nextInt();
            } catch (InputMismatchException e) {
                scanner.nextLine();
                System.out.println("숫자만 넣어주세요");
                continue;
            }
            switch (num) {
                case 0 -> cal.setResultSave(result);
                case 1 -> {
                    if (cal.getResultSave().isEmpty()) {
                        System.out.println("저장소가 비어있습니다.");
                    }
                    cal.removeResult();
                }
                case 2 -> System.out.println(cal.getResultSave());
                default -> flag = false;
            }
        }
    }

    public boolean exitIO() {
        scanner.nextLine();
        boolean exit = true;
        System.out.println("더 계산하시겠습니까? (exit 입력시 종료, 다른 입력시 계속)");
        String q = scanner.nextLine();
        if (q.equals("exit")) {
            System.out.println("종료하겠습니다");
            exit = false;
        }
        return exit;
    }
}

코드 3

package com.example.calculator;

import java.util.ArrayList;
import java.util.List;

public class Calculator {
    private final List<Number> resultSave = new ArrayList<>();

    public Number calculate(long x, char oper, long y) {
        return switch (oper) {
            case '+' -> x + y;
            case '-' -> x - y;
            case '*' -> x * y;
            case '/' -> {
                if (y == 0) {
                    throw new ArithmeticException("0으로 나눌 수 없습니다. 다시 입력해주세요");
                }
                yield x / (double) y;
            }
            default -> throw new IllegalArgumentException("연산기호가 잘못됐습니다. 다시 입력해주세요");

        };
    }


    public List<Number> getResultSave() {
        return this.resultSave;
    }

    public void setResultSave(Number result) {
        this.resultSave.add(result);
    }

    public void removeResult() {
        if (resultSave.isEmpty()) {
            System.out.println("저장소가 비어있습니다");
        }
        resultSave.remove(0);
    }
}

개선사항을 적용시켜서 다시 한번 짜봤다. 이렇게 하니까 캡슐화도 챙기면서 코드도 깔끔해지고 더 좋아진 것 같다.

트러블슈팅

계산기를 만들면서 제일 막혔던 부분을 작성했습니다.

문제 개요 : 팀원분들에게 코드 리뷰를 받았는데 클래스를 분리하면 좋겠다는 피드백을 받아서 클래스를 분리해보려고 했다.

오류 : 오류는 따로 없었지만 팀원분들의 코드를 보고도 메서드를 어떻게 해야 하는 지 어떤 식으로 분리할 지 막막했다.

원인 : 처음 분리하는 것이기도 하고 Main 클래스에서만 작업하는 절차지향적 코드만 작성해보고 객체지향적인 코드를 많이 경험하지 못하여 실력 부족?, 경험 부족이 원인인 것 같다.

해결 방법 : 일단 어떤 식으로 해야 할 지 생각을 많이 하여 스트레스를 받으니 더 집중이 안 됐다. 그래서 잠시 릴렉스하기 위해 자바 강의를 들어준 다음 다시 시작했다. 일단 메인에서 입출력 부분을 Calculatorio라는 클래스로 분리시키자라는 생각을 가지고 계산 메서드, 저장소 관련 메서드, 종료 관련 메서드로 큰 틀을 짜서 하나씩 완성시켜서 전체를 분리하게 되었다.

교훈 및 주의사항 : 위와 같은 사항이 벌어지지 않으려면 객체 지향적인 코드를 많이 짜봐야 할 것 같다. 그리고 무언가를 함에 있어서 지레 겁을 먹고 시작하게 되면 전체적으로 안될 수 있다는 것을 깨달았다. 그래서 숨을 한번 고르듯이 릴렉스하고 아무 생각없이 일단 도전해보는 것이 중요한 것 같다.

JAVA 강의

쓰레드

프로그램 내에서 독립적으로 실행되는 하나의 작업 단위

싱글 쓰레드

한 명의 일꾼이 작업을 처리하는 것

  • 일꾼이 한명이기 때문에 여러 개의 작업이 있다면 순차적으로 처리
  • main() 메서드는 프로그램 시작과 동시에 생성되는 하나의 쓰레드

ex)

public class Main {
    public static void main(String [] args) {
        System.out.println("메인 스레드 시작");

        String threadName = Thread.currentThread().getName(); 

        for (int i = 0; i < 10 ; i++) {
            System.out.println("현재 쓰레드 이름: " + threadName + " - "+ i);
            try {
                Thread.sleep(500); // 시간 0.5초 늦춰주는 메서드
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (int i = 0; i < 10 ; i++) {
            System.out.println("현재 쓰레드 이름: " + threadName + " - "+ i);
            try {
                Thread.sleep(500);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("작업 끝");
    }
}

멀티 쓰레드

여러 명의 일꾼이 작업을 처리하는 것

  • 여러 작업을 병렬로 처리 가능
  • Thread 클래스를 상속 받아 쓰레드 구현
  • Thread.run() 메서드를 오버라이드 해서 쓰레드가 수행할 작업을 정의
  • start()메서드를 호출하면 새로운 쓰레드가 생성되고 run()의 작업 내용이 실행

주의사항⚠

쓰레드 실행시킬 때 start() 사용

이유 : start()는 새로운 쓰레드에서 실행하지만 run()을 직접 호출하면 현재 쓰레드에서 실행되기 때문.

join()

main() 쓰레드가 다른 쓰레드가 종료될 때까지 기다리게 하는 메서드

ex)

public class Main {
    public static void main(String[] args) {
        System.out.println("main 쓰레드 시작");

        long startTime = System.currentTimeMillis();

        Mythread thread0 = new Mythread();
        Mythread thread1 = new Mythread();

        System.out.println("thread0 시작");
        thread0.start();

        System.out.println("thread1 시작");
        thread1.start();

        try {
            thread0.join(); 
            thread1.join(); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        long totalTime = endTime - startTime;
        System.out.println("작업 소요시간: " + totalTime + "ms");
        System.out.println("main 쓰레드 종료");

    }
}
public class Mythread extends Thread{
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println("현재 시작된 쓰레드: " + threadName);
        for (int i = 0; i < 10 ; i++) {
            System.out.println("현재 쓰레드 이름: " + threadName + " - "+ i);
            try {
                Thread.sleep(500);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("종료된 쓰레드: " + threadName);

    }
}

Runnable 인터페이스

Runnalbe 인터페이스를 활용해 쓰레드를 구현하는 것을 권장

이유:

유지보수성과 재사용성 향상

  • Thread는 쓰레드를 제어하기 위해 존재하는 클래스
  • Thread 클래스를 상속받아 MyThread를 구현하면 실행 기능과 쓰레드 제어 기능이 결합하여 한 클래스에서 두 가지 역할을 하게 됨.
  • 하나의 클래스는 하나의 책임만 가지는 것이 유지보수하기 좋음
  • Runnable을 활용하면 실행 기능을 분리할 수 있음
    Thread는 쓰레드를 제어하는 역할
    Runnable 구현체 는 실행 로직을 관리

ex)

public class Main {
    public static void main(String[] args) {
        MyRunnable myTask = new MyRunnable();

        Thread thread0 = new Thread(myTask);
        Thread thread1 = new Thread(myTask);

        thread0.start();
        thread1.start();
    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10 ; i++) {
            String threadName = Thread.currentThread().getName();
            System.out.println("현재 쓰레드 이름: " + threadName + " - "+ i);
            try {
                Thread.sleep(500);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

확장 가능성

  • Thread를 상속해서 MyThread를 구현하면 다중 상속이 불가능하기 때문에 다른 클래스를 상속받지 못하여 확장성 떨어짐
  • Runnable은 인터페이스이므로 기존 클래스의 기능을 유지하면서 상속을 통해 확장가능

느낀 점

어제 TIL을 써야 됐는데 벨로그 자체가 다운됐는 지 접속이 안 돼서 오늘 아침에 쓰게 되었다. 계산기 lv2를 완료하고 lv3를 하려고 enum을 공부하기는 했지만 코드에 적용하기는 어려워서 하지 못해서 아쉬웠다. 확실히 lv3부터는 뭔가 달랐다. 계속 집중하려고 하는 것보다 조금 쉬는 시간을 가지는 것이 효율적으로 공부하기에 더 좋을 것 같다.

profile
걱정하지 말고 일단 해봐!

0개의 댓글