목차
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문이 돌면서 인스턴스도 같이 초기화 되는 일을 막을 수 있음.
'스파르타 내일배움캠프 > TIL(Today I learned)' 카테고리의 다른 글
25.03.06 TIL - 계산기 과제 회고 (2) | 2025.03.06 |
---|---|
25.03.05 TIL - 캡슐화, 상속, 추상화 (2) | 2025.03.05 |
25.02.28 TIL - Wrapper Class, static, final, Interface (2) | 2025.02.28 |
25.02.27 TIL - 클래스, JVM 메모리, 계산기 과제 (2) | 2025.02.27 |
25.02.26 TIL - 조건문, 반복문, 배열, 메서드 (3) | 2025.02.26 |