계산기 lv2, 트러블슈팅, JAVA 강의
11일차에 했던 클래스 분리를 제대로 한 것이 맞는 지 튜터님에게 물어보러 갔다.
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();
}
}
}
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;
}
}
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라는 클래스로 분리시키자라는 생각을 가지고 계산 메서드, 저장소 관련 메서드, 종료 관련 메서드로 큰 틀을 짜서 하나씩 완성시켜서 전체를 분리하게 되었다.
교훈 및 주의사항 : 위와 같은 사항이 벌어지지 않으려면 객체 지향적인 코드를 많이 짜봐야 할 것 같다. 그리고 무언가를 함에 있어서 지레 겁을 먹고 시작하게 되면 전체적으로 안될 수 있다는 것을 깨달았다. 그래서 숨을 한번 고르듯이 릴렉스하고 아무 생각없이 일단 도전해보는 것이 중요한 것 같다.
프로그램 내에서 독립적으로 실행되는 하나의 작업 단위
한 명의 일꾼이 작업을 처리하는 것
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("작업 끝");
}
}
여러 명의 일꾼이 작업을 처리하는 것
쓰레드 실행시킬 때 start() 사용
이유 : start()는 새로운 쓰레드에서 실행하지만 run()을 직접 호출하면 현재 쓰레드에서 실행되기 때문.
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);
}
}
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부터는 뭔가 달랐다. 계속 집중하려고 하는 것보다 조금 쉬는 시간을 가지는 것이 효율적으로 공부하기에 더 좋을 것 같다.