🤖 25-01-03 진행 사항🤖
- 3주차 과제 : 계산기 만들기 - 추상화 클래스, 클래스 포함 관계 적용
- Java 문법 종합반 - 4주차
- Error / Exception
- Genetic
- 4주차 과제 : 계산기 예외처리
계산기 만들기 - 추상화 클래스, 클래스 포함 관계 적용



step 4의 calculate 메서드에서는 더 이상 연산자 타입을 받아 구분할 필요 없이 주입 받은 연산 클래스의 operate 메서드를 통해 바로 연산을 수행합니다.
이 부분을 어떻게 구현할지 고민하다가
Calculate 클래스에 operator를 저장하는 필드를 생성하고, 생성자로 operator를 전달받도록 설정했다.
Calculator.java
public class Calculator {
// Step 4
// AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기)
// 연산 클래스들을 AbstractOperation(추상 클래스)를 사용하여 추상화하고 Calculator 클래스의 내부 코드를 변경합니다.
AbstractOperation addOperation = new AddOperation();
AbstractOperation substractOperation = new SubstractOperation();
AbstractOperation multiplyOperation = new MultiplyOperation();
AbstractOperation divideOperation = new DivideOperation();
String operator;
public Calculator (String operator) {
this.operator = operator;
}
/* Step 3
// AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기)
// 연산 클래스를 만든 후 클래스 간의 관계를 고려하여 Calculator 클래스와 관계를 맺습니다.
AddOperation addOperation = new AddOperation();
SubstractOperation substractOperation = new SubstractOperation();
MultiplyOperation multiplyOperation = new MultiplyOperation();
DivideOperation divideOperation = new DivideOperation();
*/
// Step 1 : 더하기, 빼기, 나누기, 곱하기 연산을 수행할 수 있는 Calculator 클래스를 만듭니다.
public double calculate(int firstNumber, int secondNumber) {
return switch (this.operator) {
case "+" -> this.addOperation.operate(firstNumber, secondNumber);
case "-" -> this.substractOperation.operate(firstNumber, secondNumber);
case "*" -> this.multiplyOperation.operate(firstNumber, secondNumber);
case "/" -> this.divideOperation.operate(firstNumber, secondNumber);
case "%" -> (double) (firstNumber % secondNumber); // Step 2 : 나머지 연산자(%)를 수행할 수 있게 코드 변경
default -> 0.0;
};
}
}
Main.java
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("첫 번째 숫자를 입력하세요: ");
int num1 = sc.nextInt();
System.out.print("두 번째 숫자를 입력하세요: ");
int num2 = sc.nextInt();
System.out.print("사칙연산 기호(+,-,*,/)를 입력하세요: ");
String operator = sc.next();
Calculator calculator = new Calculator(operator);
System.out.println("결과 : " + calculator.calculate(num1, num2));
}
}
💥IntelliJ Gradle Project "finished with non-zero exit value 1" 해결💥
그런데 main을 실행하면 연산기호를 입력한 후 Calculator 생성자를 호출하는 과정에서 아래와 같은 에러가 발생한다.
위 코드는 문제가 없다고 생각했는데 어디서 에러가 발생했는지 알 수 없었다.

생성자에 브레이크포인트를 걸어놓고 디버그를 해봐도 브레이크 되지 않고
어디에서 오류가 발생했는지도 알려주지않는 상태로 같은 오류(finished with non-zero exit value 1)만 발생해서 해결 방법을 찾아 보았다.
구글링해보니 IntelliJ를 사용하며 같은 오류를 겪은 사람이 많았다.
Gradle Project에서 Gradle 빌드 시스템을 사용할 때 빌드 및 실행 구성에 문제 발견되면
이를 IntelliJ의 자체 빌드 및 실행 시스템으로 해결하고 출력되는 오류 메세지인듯 하다.
해결방법은 Settings > Build, Excution, Deployment > Build Tools > Gradle 에서
Build and run using 과 Run tests using을 Gradle(Default)에서 IntelliJ IDEA로 변경해주는 것이다.

참고: https://catch-115.tistory.com/m/8 , https://suzuworld.tistory.com/320
설정을 바꾸고 main을 재실행해보니 원인과 위치를 확인할 수 있었다.

뭔가 했더니,, 추상화 클래스가 헷갈려서 이것저것 해보다가 안지운 코드가 문제였다... 하하!
추상화 클래스의 멤버필드에 자식 클래스 객체가 선언이 되나 싶어서 적어봤던건데 완전히 까먹고있었다 ㅎ
선언에는 오류가 안나는데 실행시키면 StackOverflowError가 발생하는군!

해당 부분 삭제하니 정상 작동 되었다
제출 코드
추상 클래스 AbstractOperation.java
package com.example.online.calculator;
// Step 4 : AbstractOperation 추상 클래스를 만들고 operate 추상 메서드를 만듭니다.
// AbstractOperation 추상 클래스
public abstract class AbstractOperation {
// operate 추상 메서드
abstract double operate(int firstNumber, int secondNumber);
}
AbstractOperation를 상속받는 AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스들
AddOperation.java
package com.example.online.calculator;
public class AddOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return firstNumber + secondNumber;
}
}
SubstractOperation.java
package com.example.online.calculator;
public class SubstractOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return firstNumber - secondNumber;
}
}
MultiplyOperation.java
package com.example.online.calculator;
public class MultiplyOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return firstNumber * secondNumber;
}
}
DivideOperation.java
package com.example.online.calculator;
public class DivideOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return (double) (firstNumber / secondNumber);
}
}
AbstractOperation를 포함하는 Calculator 클래스
Calculator.java
package com.example.online.calculator;
public class Calculator {
// Step 4
// AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기)
// 연산 클래스들을 AbstractOperation(추상 클래스)를 사용하여 추상화하고 Calculator 클래스의 내부 코드를 변경합니다.
AbstractOperation addOperation = new AddOperation();
AbstractOperation substractOperation = new SubstractOperation();
AbstractOperation multiplyOperation = new MultiplyOperation();
AbstractOperation divideOperation = new DivideOperation();
String operator;
public Calculator (String operator) {
this.operator = operator;
}
/* Step 3
// AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기)
// 연산 클래스를 만든 후 클래스 간의 관계를 고려하여 Calculator 클래스와 관계를 맺습니다.
AddOperation addOperation = new AddOperation();
SubstractOperation substractOperation = new SubstractOperation();
MultiplyOperation multiplyOperation = new MultiplyOperation();
DivideOperation divideOperation = new DivideOperation();
*/
// Step 1 : 더하기, 빼기, 나누기, 곱하기 연산을 수행할 수 있는 Calculator 클래스를 만듭니다.
public double calculate(int firstNumber, int secondNumber) {
return switch (this.operator) {
case "+" -> this.addOperation.operate(firstNumber, secondNumber);
case "-" -> this.substractOperation.operate(firstNumber, secondNumber);
case "*" -> this.multiplyOperation.operate(firstNumber, secondNumber);
case "/" -> this.divideOperation.operate(firstNumber, secondNumber);
case "%" -> (double) (firstNumber % secondNumber); // Step 2 : 나머지 연산자(%)를 수행할 수 있게 코드 변경
default -> 0.0;
};
}
}
실행을 위한 Main 클래스
Main.java
package com.example.online.calculator;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("첫 번째 숫자를 입력하세요: ");
int num1 = sc.nextInt();
System.out.print("두 번째 숫자를 입력하세요: ");
int num2 = sc.nextInt();
System.out.print("사칙연산 기호(+,-,*,/)를 입력하세요: ");
String operator = sc.next();
Calculator calculator = new Calculator(operator);
System.out.println("결과 : " + calculator.calculate(num1, num2));
}
}
Java 문법 종합반 - 4주차
1. 오류(Error)와 예외(Exception)
오류 (Error)
- 일반적으로 회복이 불가능한 문제
- 시스템 레벨에서 주로 환경적인 이유로 발생
- 어떠한 에러로 프로그램이 종료되었는지를 확인하고 대응해야함.
예외 (Exception)
- 일반적으로 회복이 가능한 문제
- 문제상황에 대한 대응은 "예외처리"에 속함
예외의 종류
- 컴파일 에러
- .java 파일을 .class 파일로 컴파일할 때 발생하는 에러
- 대부분 프로그래밍 언어의 규칙을 지키지 않았기 떄문에 발생
- 컴파일 에러가 발생하는 경우 해결 방법은 문법에 맞게 다시 작성하는 것.
- 런타임 에러
- 문법적은 오류는 아니지만, 프로그램이 실행도중 맞닥뜨리게 되는 예외
예외처리 관점에서 예외의 종류
- 확인된 예외 (Checked Exception)
- 컴파일 시점에 확인하는 예외
- 반드시 예외 처리를 해줘야 하는 예외
- != 컴파일 에러
- Checked Exception에 대한 예외 처리를 하지 않으면 컴파일 에러가 발생하는 것.
- 미확인된 예외 (Unchecked Exception)
- 런타임 시점에 확인되는 예외
- 예외 처리가 반드시 필요하지는 않은 예외
2. try-catch, finally 문
예외 클래스 정의
class OurBadException extends Exception {
public OurBadException() {
super("위험한 행동을 하면 예외처리를 꼭 해야합니다!");
}
}
try-catch, finally로 예외 핸들링
public class StudyException {
public static void main(String[] args) {
OurClass ourClass = new OurClass();
try {
// 1. 위험한 메소드의 실행을 "시도" 해 봅니다.
// "시도" 해보는 코드가 들어가는 블럭입니다.
ourClass.thisMethodIsDangerous();
} catch (OurBadException e) {
// 2. 예외가 발생하면, "잡아서" handling 합니다.
// 예외가 발생하는경우 "handling" 하는 코드가 들어가는 블럭입니다.
// 즉 try 블럭 내의 구문을 실행하다가 예외가 발생하면
// 예외가 발생한 줄에서 바로 코드 실행을 멈추고
// 여기 있는 catch 블럭 내의 코드가 실행됩니다.
System.out.println(e.getMessage());
} finally {
// 3. 예외의 발생 여부와 상관없이, 실행시켜야 하는 코드가 들어갑니다.
// 무조건 실행되는 코드가 들어가는 블럭입니다.
System.out.println("우리는 방금 예외를 handling 했습니다!");
}
}
}
3. Generic
public class Generic<T> { ... }
Generic<String> stringGeneric = new Generic<>();
- Generic<T>의 클래스처럼, 제네릭을 사용한 클래스를 제네릭 클래스라고 함
- 제네릭에서 <>사이에 들어가는 변수명 T는 타입 변수라고 함
- Generic 클래스를 원시 타입이라고 함
제네릭의 제한
- 객체의 static 멤버에 사용 불가 (타입 변수는 인스턴스 변수로 간주되기 때문)
- 제네릭 배열을 생성할 수 없음
제네릭 문법
- 다수의 타입 변수를 사용할 수 있다. (Gemeric<T,U,V,E>)
public class Generic<T, U, E> {
public E multiTypeMethod(T t, U u) { ... }
}
Generic<Long, Integer, String> instance = new Generic();
instance.multiTypeMethod(longVal, intVal);
- 다형성 즉 상속과 타입의 관계는 그대로 적용됨
- 부모 클래스로 제네릭 타입 변수를 지정하고, 그 안에 자식 클래스를 넘기는 것은 잘 동작
- 와일드카드를 통해 제네릭의 제한을 구체적으로 정할 수 있다.
- <? extends T> : T와 그 자손들만 사용 가능
- <? super T> : T와 그 조상들만 사용 가능
- <?> : 제한 없음 (<? extends Object>와 같음)
static Juice makeJuice(FruitBox<? extends Fruit> f) {}
// 이렇게 쓰면 매개변수로 Fruit 클래스와 그 자손 클래스들만 가능해진다.
계산기 예외 처리
과제 시작전 프로젝트 구조 변경
계산기 과제들을 같은 프로젝트에서 작성하고 있는데, 구분이 명확치 않아서 프로젝트 구조를 변경해줬다.



커밋 메세지 여러줄 써보는거 처음인데, 뭔가 정갈함.. 보기 좋다❗
이번 과제부터 브랜치를 활용해보려고 한다.
혼자하는 과제라 git push origin main을 애용했는데 연습할 겸 브랜치 생성해서 커밋/푸시하는 걸 익혀야겠다.
제출 코드
입력값이 잘못됐을 때 예외 클래스
BadInputException.java
package com.example.online.week4;
public class BadInputException extends Exception {
public BadInputException(String type) {
super("잘못된 입력입니다! " + type + " 입력해주세요!");
}
}
계산기를 시작시키는 클래스
CalculatorApp.java
package com.example.online.week4;
import java.util.Scanner;
public class CalculatorApp {
public static boolean start() throws Exception{
Parser parser = new Parser();
Scanner scanner = new Scanner(System.in);
System.out.println("첫번째 숫자를 입력해주세요!");
String firstInput = scanner.nextLine();
parser.parseFirstNum(firstInput);
System.out.println("연산자를 입력해주세요!");
String operator = scanner.nextLine();
parser.parseOperator(operator);
System.out.println("두번째 숫자를 입력해주세요!");
String secondInput = scanner.nextLine();
parser.parseSecondNum(secondInput);
System.out.println("연산 결과 : " + parser.executeCalculator());
return true;
}
}
입력값이 정상적인지 판단하는 클래스
Parser.java
package com.example.online.week4;
import java.util.regex.Pattern;
public class Parser {
private static final String OPERATION_REG = "[+\\-*/]";
private static final String NUMBER_REG = "^[0-9]*$";
private final Calculator calculator = new Calculator();
public void parseFirstNum(String firstInput) throws BadInputException {
// 구현 1.
if (!Pattern.matches(NUMBER_REG, firstInput)) {
throw new BadInputException("정수 타입을");
}
this.calculator.setFirstNumber(Integer.parseInt(firstInput));
}
public void parseSecondNum(String secondInput) throws BadInputException {
// 구현 1.
if (!Pattern.matches(NUMBER_REG, secondInput)) {
throw new BadInputException("정수 타입을");
}
this.calculator.setSecondNumber(Integer.parseInt(secondInput));
}
public void parseOperator(String operationInput) throws BadInputException {
// 구현 1.
if (!Pattern.matches(OPERATION_REG, operationInput)) {
throw new BadInputException("+,-,*,/ 중 원하는 연산자를");
}
switch (operationInput) {
case "+" -> calculator.setOperation(new AddOperation());
case "-" -> calculator.setOperation(new SubstractOperation());
case "*" -> calculator.setOperation(new MultiplyOperation());
case "/" -> calculator.setOperation(new DivideOperation());
}
}
public double executeCalculator() {
return calculator.calculate();
}
}
- 입력 값이 적절하면 setter 실행
- 적절하지 않으면 BadInputException 실행
계산기 실행
Main.java
package com.example.online.week4;
public class Main {
public static void main(String[] args) {
boolean calculateEnded = false;
// 구현 2.
while (!calculateEnded) {
try {
calculateEnded = CalculatorApp.start();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
'TIL (Today I Learned)' 카테고리의 다른 글
| [Java] Generic의 원리와 활용법 (0) | 2025.01.06 |
|---|---|
| [Java] Enum 사용법과 활용 예시 (0) | 2025.01.06 |
| [TIL] Java-객체지향 프로그래밍, 계산기 만들기 (25-01-02) (0) | 2025.01.02 |
| [TIL] 자료구조(List / Set / Map)를 활용한 입/출력 (25-01-01) (2) | 2025.01.01 |
| [TIL] JVM 개념 및 JDK 설치, Git 심화 특강 (24-12-31) (0) | 2024.12.31 |