본문 바로가기
스파르타 내일배움캠프/TIL(Today I learned)

25.03.04 TIL - final, interface, Lv2. 계산기 과제

by pandastic 2025. 3. 4.
반응형

 

 

 

1. Chapter 2 - 5 : final - 변하지 않는 값

1. final

    final의 용도

      1) 변수는 변경이 불가능하게 만듦.

           - 변수에 final을 붙이면 변수를 한 번만 설정할 수 있음.

final int a = 100;
a = 200; // ❌ 오류 발생!

 

      2) 클래스는 상속할 수 없게 만듦.

           - final로 선언된 클래스는 상속할 수 없음.

final class Animal {
    void sound() {
        System.out.println("Animal sound!");
    }
}
// class Dog extends Animal {} // ❌ 오류! final 클래스는 상속할 수 없음

 

      3) 메서드는 수정할 수 없게 만듦(오버라이딩 불가)

           - final로 선언된 메서드는 오버라이딩 할 수 없음.

class Parent {
    final void show() {
        System.out.println("Hello from Parent");
    }
}
class Child extends Parent {
		
	@Override
        void show() { // ❌ 오류! final 메서드를 재정의할 수 없음
            System.out.println("Hello from Child");
        }
}

 

 

2. 상수(Constant)

 - 상수는 변하지 않고 항상 일정한 값을 갖는 수.

 - Java에서 상수는 "대문자로 표현하는 것" 이 관례.

 - 프로그램 실행 중에 절대 변경되어서는 안되기 때문에 static final 키워드를 사용해 선언함.

-  static으로 선언된 변수는 프로그램 시작 시 한 번만 초기화되고 모든 인스턴스에서 같은 값 공유.

 

* static으로 선언하는 이유

  - 보통 상수는 여러 곳에서 값을 공유해 쓰일 목적으로 활용됨.

  - 인스턴스 변수를 static 없이 선언할 경우 인스턴스마다 값이 중복 저장됨!!!

  - 꼭 static을 넣어서 공용으로 활용할 수 있게 하는 것이 올바른 상수 활용 방법!!!

 

 

3. 불변 객체(Immutable Object)

특징

   - 내부 상태를 변경할 수 없는 객체.

   - final을 속성(property, field)에 활용.

   - 세터(setter) 없이 설계.

   - 변경이 필요할 경우 새로운 객체를 만들어야함.

      예) String, Integer, 래퍼 클래스 등 → 불변 객체에 속함.

 

잘못된 불변 객체 사용

   - final은 참조 변경을 막지만 내부 상태 변경은 막지 않음.

     →  final이 붙어있지 않으면 외부에서 데이터 값을 마구잡이로 변경할 수 있음.

     → 아무리 변수에 final이 붙어 있어도 내부 변경을 막아주지 않음.

     → 그렇지만 final이 붙은 경우 c1의 참조값은 변경되지 않음.

 

예시)

public class Circle {

    final static double PI = 3.14159; // ✅ 직접 만든 원주율 상수
    double radius; // ⚠️ final 로 선언되어 있지 않기 때문에 외부에서 변경 가능

    Circle(double radius)  {
        this.radius = radius;
    }
}
final Circle c1 = new Circle(2);
c1 = new Circle(3); // ❌ final은 변수 c1이 한 번 참조한 객체는 다른 객체로 변경될 수 없음을 의미함 (참조 불변)

// 하지만 객체 내부의 속성 값은 변경 가능 (불변 객체가 아님)
c1.radius = 3; // ⚠️ 내부 상태 변경 가능 (객체 자체가 불변이 아님)

 

 

올바른 불변 객체 활용

   - 속성을 final로 선언.

 

예시)

public final class Circle {
    final static double PI = 3.14159; 
    final double radius; // ✅ final 로 선언해서 값이 변경되지 않도록 합니다.
    Circle(double radius)  {
        this.radius = radius;
    }
}

 

 

불변 객체의 값 변경이 필요한 이유

  - 불변성을 유지하면서 값을 변경하는 효과를 얻을 때 활용.

  - 기존 객체의 상태를 직접 변경할 수 없기 때문에 새로운 객체를 생성해야함.

  - 생성자를 새로 호출하거나 아래의 방법 사용 가능.

public final class Circle {
    public static final double PI = 3.14159;
    private final double radius;
    public Circle(double radius) {
        this.radius = radius;
    }
    // ✅ 반지름이 다른 새로운 Circle 생성 (불변 객체 유지)
    public Circle changeRadius(double newRadius) {
        return new Circle(newRadius); // 생성자 호출: 기존 객체 변경 X, 새 객체 생성
    }
}

 

 

 

2. Chapter 2 - 6 : 인터페이스 - 표준화의 시작

1. 인터페이스

    - 클래스가 따라야 할 최소한의 공통 규칙을 정의하는 역할.

 

1) 인터페이스를 사용하는 이유

  - 개발자마다 서로 다른 방식으로 메서드를 만든다면 일관성이 깨질 수 있음.

  - 인터페이스를 활용해서 최소한의 규격 정의.

  - 세부 구현은 각 클래스에 맡김.

  - 일관성을 유지하면서 클래스가 고유한 특색을 확장할 수 있도록 도움.

 

2) 인터페이스 적용

  - 인터페이스는 모든 클래스가 지켜야 할 최소한의 규칙 정의.

  - implements 키워드로 인터페이스 활용 가능.

  - 인터페이스를 구현한 클래스를 "구현체" 라고 함.

 

 

3) 인터페이스의 다양한 기능

  1. 인터페이스 다중 구현

       - implements 키워드로 다수의 인터페이스 구현 가능.

       - 한 개의 클래스가 여러 인터페이스를 구현하는 것.

 

  2. 인터페이스 다중 상속

       - extends 키워드로 상속 구현.

       - 인터페이스가 다른 인터페이스들을 상속받음

          → 다른 인터페이스들을 상속받은 인터페이스를 implements 하면 다 가져다 쓸 수 있는 것.

 

 

4) 인터페이스에 변수를 선언하는 경우

     - 변수 선언 시 형식에 관계 없이 자동으로 상수(public static final) 로 선언됨.

     - static으로 선언되기 때문에 구현체 없이도 활용 가능.

     - 인터페이스에 변수를 선언하는 것은 권장하지 않음 → 인터페이스는 표준의 역할을 해야하기 때문.

 

 

[실습 과제]

더보기

Q1. 다양한 가전제품을 일관된 방식으로 조작할 수 있는 인터페이스를 설계하세요.

요구사항

  • 가전제품 종류: Tv, 에어컨, 컴퓨터
  • turnOn(); → 기기를 켜는 기능
  • turnOff(); → 기기를 끄는 기능
  • 각 가전제품의 기능을 확장해보세요.

 

Electronic.java

Electronic.java
public interface Electronic {
    void turnOn();
    void turnOff();
}

 

Television.java

public class Television implements Electronic{
    @Override
    public void turnOn() {
        System.out.println("전원을 켭니다.");
    }
    @Override
    public void turnOff() {
        System.out.println("전원을 끕니다.");
    }
    void volUp(){
        System.out.println("볼륨을 높입니다.");
    };
    void volDown(){
        System.out.println("볼륨을 낮춥니다.");
    };
}

 

Airconditioner.java

public class AirConditioner implements Electronic{
    void tempUp(){
        System.out.println("온도를 높입니다.");
    };
    void tempDown(){
        System.out.println("온도를 낮춥니다.");
    };
    @Override
    public void turnOn() {
        System.out.println();
    }
    @Override
    public void turnOff() {
    }
}

 

Computer.java

public class Computer implements Electronic{
    public void onProgram() {
        System.out.println("프로그램 실행");
    }
    public void offProgram() {
        System.out.println("프로그램 종료");
    }
    @Override
    public void turnOn() {
        System.out.println("전원을 켭니다.");
    }
    @Override
    public void turnOff() {
        System.out.println("전원을 끕니다.");
    }
}

 

정답)

// 📌 가전제품의 기본 기능을 정의한 인터페이스
interface ElectronicDevice {
    void turnOn();  // 전원 켜기 기능
    void turnOff(); // 전원 끄기 기능
}

 

// ✅ TV 클래스 (ElectronicDevice 구현)
class TV implements ElectronicDevice {

    @Override
    void turnOn() {
        System.out.println("TV 전원이 켜졌습니다.");
    }

    @Override
    void turnOff() {
        System.out.println("TV 전원이 꺼졌습니다.");
    }
    
    // 추가 기능 (인터페이스에는 없는 기능)
    void changeChannel() {
        System.out.println("채널을 변경합니다.");
    }
}

 

// ✅ 에어컨 클래스 (ElectronicDevice 구현)
class AirConditioner implements ElectronicDevice {

    @Override
    void turnOn() {
        System.out.println("에어컨이 가동됩니다.");
    }

    @Override
    void turnOff() {
        System.out.println("에어컨이 꺼졌습니다.");
    }
    
    // 추가 기능 (인터페이스에는 없는 기능)
    void setTemperature() {
        System.out.println("온도를 설정합니다.");
    }
}

 

// ✅ 세탁기 클래스 (ElectronicDevice 구현)
class WashingMachine implements ElectronicDevice {

    @Override
    void turnOn() {
        System.out.println("세탁기가 작동을 시작합니다.");
    }

    @Override
    void turnOff() {
        System.out.println("세탁기가 작동을 멈춥니다.");
    }
    
    // 추가 기능 (인터페이스에는 없는 기능)
    void setTime() {
        System.out.println("세탁 시간을 설정합니다.");
    }
}

 

3. Lv2. 클래스를 적용해 기본적인 연산을 수행할 수 있는 계산기 만들기

1. 구현

  • 사칙연산을 수행 후, 결과값 반환 메서드 구현 & 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성
    • 사칙연산을 수행한 후, 결과값을 반환하는 메서드 구현
    • 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성
    • 1) 양의 정수 2개(0 포함)와 연산 기호를 매개변수로 받아 사칙연산(➕,➖,✖️,➗) 기능을 수행한 후 2) 결과 값을 반환하는 메서드와 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성합니다.

구현 코드

더보기
public void setNum1(int num1){
        this.num1 = num1;
    }

    public void setNum2(int num2){
        this.num2 = num2;
    }

    public int sum() {
        result = this.num1 + this.num2;
        return result;
    }
    public int subtract(){
        result = this.num1 - this.num2;
        return result;
    }
    public int multiply(){
        result = this.num1 * this.num2;
        return result;
    }
    public int division(){
        try{
            result = this.num1 / this.num2;
            // 계산이 될 경우에만 list에 값 저장.
            setList(result);
        }catch (ArithmeticException e){
            System.out.println("나눗셈 연산에서 두번째 정수에 0을 입력할 수 없습니다.");
        }
        return result;
    }

 

  • Lv 1에서 구현한 App 클래스의 main 메서드에 Calculator 클래스가 활용될 수 있도록 수정
    • 연산 수행 역할은 Calculator 클래스가 담당
    • 연산 결과는 Calculator 클래스의 연산 결과를 저장하는 필드에 저장
    • 소스 코드 수정 후에도 수정 전의 기능들이 반드시 똑같이 동작해야합니다.

[구현 코드]

더보기
public static void main(String[] args) {

        // 반복문 실행 시에 초기화되는 일을 막기 위해 반복문 밖에 작성.
        Calculator calculator = new Calculator();
        Scanner input = new Scanner(System.in);

        while (true) {
            // Lv 1. 클래스 없이 기본적인 연산을 수행할 수 있는 계산기 만들기
            // 양의 정수(0 포함)를 입력받기
            System.out.print("첫번째 숫자를 입력해주세요: ");
            int num1 = input.nextInt();
            System.out.print("두번째 숫자를 입력해주세요: ");
            int num2 = input.nextInt();

            // setter로 calculator 클래스에 값 넘기기.
            calculator.setNum1(num1);
            calculator.setNum2(num2);
            if (num1 < 0 || num2 < 0) {
                System.out.println("0을 포함한 양의 정수만 입력하세요!");
                continue;
            }
            System.out.print("사칙연산 기호(+, -, *, /)를 입력하세요: ");
            String operator = input.next();
            //버퍼 비우기
            input.nextLine();
            char oper = operator.charAt(0);
            int result = 0;

            switch (oper) {
                case '+':
                    result = calculator.sum();
                    calculator.setList(result);
                    break;
                case '-':
                    result = calculator.subtract();
                    calculator.setList(result);
                    break;
                case '*':
                    result = calculator.multiply();
                    calculator.setList(result);
                    break;
                case '/':
                    // try~catch 문 Calculator 클래스로 이동.
                    result = calculator.division();
                    break;
                default:
                    System.out.println("잘못된 연산자를 입력하셨습니다!");
            }

 

  • App 클래스의 main 메서드에서 Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정 (캡슐화)
    • 간접 접근을 통해 필드에 접근하여 가져올 수 있도록 구현합니다. (Getter 메서드)
    • 간접 접근을 통해 필드에 접근하여 수정할 수 있도록 구현합니다. (Setter 메서드)
    • 위 요구사항을 모두 구현 했다면 App 클래스의 main 메서드에서 위에서 구현한 메서드를 활용 해봅니다.

 

[구현 코드]

더보기
// 결과 값을 담을 list
// 캡슐화
private ArrayList<Integer> list = new ArrayList<Integer>();

// list의 getter, setter

public void setList(int result){
    this.list.add(result);
}

public ArrayList<Integer> getList(){
    return this.list;
}

 

 

[전체 코드]

더보기

Calculator.java

package calculator;
import java.util.ArrayList;

public class Calculator {
    int num1;
    int num2;
    int result;

    // 결과 값을 담을 list
    // 캡슐화
    private ArrayList<Integer> list = new ArrayList<Integer>();

   public void setNum1(int num1){
        this.num1 = num1;
    }

    public void setNum2(int num2){
        this.num2 = num2;
    }

    public int sum() {
        result = this.num1 + this.num2;
        return result;
    }
    public int subtract(){
        result = this.num1 - this.num2;
        return result;
    }
    public int multiply(){
        result = this.num1 * this.num2;
        return result;
    }
    public int division(){
        try{
            result = this.num1 / this.num2;
            // 계산이 될 경우에만 list에 값 저장.
            setList(result);
        }catch (ArithmeticException e){
            System.out.println("나눗셈 연산에서 두번째 정수에 0을 입력할 수 없습니다.");
        }
        return result;
    }

    // list의 getter, setter

    public void setList(int result){
        this.list.add(result);
    }

    public ArrayList<Integer> getList(){
        return this.list;
    }
    // 가장 먼저 저장된 데이터 삭제
    public Integer removeList(){
        return this.list.remove(0);
    }

}

 

Main.java

import calculator.Calculator;

import java.util.ArrayList;
import java.util.Scanner;


public class Main {


    public static void main(String[] args) {

        // 반복문 실행 시에 초기화되는 일을 막기 위해 반복문 밖에 작성.
        Calculator calculator = new Calculator();
        Scanner input = new Scanner(System.in);

        while (true) {
            // Lv 1. 클래스 없이 기본적인 연산을 수행할 수 있는 계산기 만들기
            // 양의 정수(0 포함)를 입력받기
            System.out.print("첫번째 숫자를 입력해주세요: ");
            int num1 = input.nextInt();
            System.out.print("두번째 숫자를 입력해주세요: ");
            int num2 = input.nextInt();

            // setter로 calculator 클래스에 값 넘기기.
            calculator.setNum1(num1);
            calculator.setNum2(num2);
            if (num1 < 0 || num2 < 0) {
                System.out.println("0을 포함한 양의 정수만 입력하세요!");
                continue;
            }
            System.out.print("사칙연산 기호(+, -, *, /)를 입력하세요: ");
            String operator = input.next();
            //버퍼 비우기
            input.nextLine();
            char oper = operator.charAt(0);
            int result = 0;

            switch (oper) {
                case '+':
                    result = calculator.sum();
                    calculator.setList(result);
                    break;
                case '-':
                    result = calculator.subtract();
                    calculator.setList(result);
                    break;
                case '*':
                    result = calculator.multiply();
                    calculator.setList(result);
                    break;
                case '/':
                    // try~catch 문 Calculator 클래스로 이동.
                    result = calculator.division();
                    break;
                default:
                    System.out.println("잘못된 연산자를 입력하셨습니다!");
            }
            System.out.println("결과: " + result);
            System.out.println("저장된 계산결과: " + calculator.getList());
            //계속 계산할건지 체크
            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
            String answer = input.nextLine();
            if (answer.equals("exit")) {
                System.exit(0);
            }
            System.out.println("계산한 값을 지우시겠습니까? yes 입력 시 가장 먼저 저장된 데이터가 삭제됩니다.");
            String erase = input.nextLine();
            if(erase.equals("yes")){
                calculator.removeList();
            }
        }
    }
}

 

2. 트러블 슈팅(문제 해결)

 

1. 문제 상황

  • 계산 결과 값을 List 에 담는 과정에서 값이 계속 초기화 되는 문제 발생.

 

2. 해결 과정

  • 처음에는 getter와 setter이 문제인 줄 알고 계속 그 부분의 코드만 수정하였으나 개선되지 않음.
  • while문 안에 있던 Calculator calculator = new Calculator(); 코드를 while문 밖으로 이동시켜보았더니 해결됨.

 

3. 문제 발생 이유

  • while 문이 반복하면서 Calculator.java를 다시 인스턴스화 하기 때문에 list 안에 있던 기존값들이 초기화 될 수 밖에 없음.

 

4. 새로 알게 된 것

  • while문 밖에서 인스턴스화를 해야 while문이 돌면서 인스턴스도 같이 초기화 되는 일을 막을 수 있음.

 

반응형