Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions TIL/2026-06-16-inheritance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# 2026-06-16 상속

## 과제 피드백

- `assertDoesNotThrow(() -> mainTestWand.setPower(setRandomDoubleInRange), "정상 값 입력시 예외발생하는 오류 발생");` 와 같은
랜덤값을 이용한 테스트는 지양하는것이 좋음.
- 말 그대로 랜덤값이다보니, 성공 실패가 갈릴 수 있음
- 경계값 테스트로 전환하여 진행할 것
- `@BeforeEach` 를 이용하여 매 테스트 실행 전에 준비작업 (`Wand testWand = new Wand();` 와 같은) 을 자동화 할 수 있음
- 코드 리뷰나 협업할 때 편하도록 한 눈에 들어올 수 있게 코드 길이를 조절할 것

---

## 상속

- 이전에 만든 클래스와 비슷한 다른 클래스를 만들 필요가 있을 때 사용

### 상속의 기초

- extends (클래스명)을 클래스 뒤에 붙여, 기존 (클래스명)을 기초로 하는 새로운 클래스 정의 가능
- 부모클래스의 멤버는 자동적으로 자식클래스에 상속
- 추가되는 부분만 기술하면 됨
- 부모클래스에 있는 메서드를 자식클래스에서 작성하는 경우를 오버라이드한다고 함
- final을 붙인 클래스는 상속 / 오버라이드 할 수 없다.
- is-a 를 충족해야 올바른 상속
- 예시
1. 차는 탈 것의 한 종류다. (car is a vehicle)
2. IPhone은 Phone의 한 종류다.
- 상속에는 추상적 / 구체적 관계에 있다는 것을 정의하는 역할도 가짐

#### 오버라이드

- 부모 클래스에서 받은 메서드를 자식클래스에 맞게 수정하는 것
- Slime에서의 attack()은 hero의 체력을 10 깍는 함수지만 <br>PoisonSlime의 attack()은 체력을 10 깎음과 동시에 독데미지로 현재 체력의 1/5 만큼 추가로 깍음
- 코드 예시
```java
// Slime 에서의 attack()
public void attack(Hero hero) {
if (hero == null) {
throw new IllegalArgumentException("공격하는 대상이 있어야합니다.");
}

System.out.println("슬라임 " + this.suffix + "이/가 공격했다");
System.out.println("대상 " + hero.getName() + "에게 " + slimeAttackPoint + "포인트 데미지!");

hero.setHP(hero.getHP() - slimeAttackPoint);
}

// PoisonSlime 에서의 attack()
@Override
public void attack(Hero hero) {
super.attack(hero);
if (poisonCount > 0) {
System.out.println("포이즌슬라임" + this.getSuffix() + "의 능력! 추가로, 독 포자를 살포했다!");
System.out.println("대상 " + hero.getName() + "에게 " + hero.getHP() / 5 + "포인트 독 데미지!");
hero.setHP(hero.getHP() * 4 / 5);
poisonCount -= 1;
} else {
System.out.println("포이즌슬라임" + this.getSuffix() + "은 독을 모두 소모했다.");
}
}
```

### 인스턴스

- 내부에 부모클래스의 인스턴스를 가지는 다중구조를 가짐
- 보다 바깥의 인스턴스에 속하는 자식클래스의 오버라이드한 메서드부터 동작
- 오버라이드 한 메서드 -> 자식 클래스의 메서드 -> 부모클래스의 메서드 순으로 검색하여 실행
- 단, 생성자의 경우에는 부모 먼저 실행하고, 자식의 생성자가 실행함
- 부모 클래스인 Hero에 있는 생성자가 자식클래스인 SuperHero에 있는 생성자보다 먼저 동작
- 외측의 인스턴스에 속하는 메서드는 super를 사용하여 내측 인스턴스의 멤버에 접근 가능

### 생성자 동작

- 다중 구조의 인스턴스가 생성되는데, JVM은 자동으로 가장 외측 인스턴스의 생성자를 호출함.
- 모든 생성자는 부모 인스턴스의 생성자를 호출 할 필요가 있음
- 생성자 선두에 `super()`가 없어도 암묵적으로 `super();` 가 추가됨

## UML

- 아래와 같이 PlantUML을 이용하여 구성도를 그릴 수 있음
- 정보를 주고받을 때 용이함

```plantuml
@startuml

scale 2

class SuperHero extends Hero {
+void setFlying(boolean Flying)
+void boolean isFlying()
}
Comment on lines +89 to +92

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

UML 예시 메서드 시그니처 표기를 수정해 주세요.

Line 91의 +void boolean isFlying()는 반환형 표기가 잘못되었습니다. +boolean isFlying()로 고쳐야 예시가 올바른 형태가 됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TIL/2026-06-16-inheritance.md` around lines 89 - 92, The method signature for
isFlying() in the SuperHero class has an incorrect return type notation. The
current signature shows "+void boolean isFlying()" with both void and boolean
listed as return types, which is invalid UML notation. Remove the "void" keyword
from the isFlying() method signature so it reads "+boolean isFlying()" to
correctly indicate that the method returns a boolean value.


class Hero
{
- int hp

+ void attack()
}

@enduml
```

134 changes: 134 additions & 0 deletions game/game.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
@startuml

scale 2

class SuperHero extends Hero {
- boolean isFlying

+ SuperHero(String name, int hp)
+ SuperHero(String name)
+ SuperHero()

+ void setFlying(boolean Flying)
+ boolean isFlying()
+ void run()
}

class Hero
{
- int hp
- int maxHP
- String name

+ String getName()
+ int getHP()
+ int getMaxHP()
+ void setName(String name)
+ void setHP(int hp)
+ void setMaxHP(int maxHP)

+ void attack()
+ void run()
+ void slip()
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.


class Wizard
{
- String name
- int hp
- int mp
- Wand wand

+ int defaultWizardMP = 100

+ {Constructor} Wizard(String name, int hp, int mp, Wand wand)
+ {Constructor} Wizard(String name, int hp, int mp)
+ {Constructor} Wizard(String name, int hp)
+ {Constructor} Wizard(String name)

+ String getName()
+ void setName(String name)
+ int getHP()
+ void setHP(int hp)
+ int getMP()
+ void setMP(int mp)
+ Wand getWand()
+ void setWand(Wand wand)

+ void heal(Hero hero)
}

class GreatWizard extends Wizard
{
+ int defaultGreatWizardMP = 150
+ int recoverPoint = 25

+ {Constructor} GreatWizard(String name, int hp, int mp, Wand wand)
+ {Constructor} GreatWizard(String name, int hp, int mp)
+ {Constructor} GreatWizard(String name, int hp)
+ {Constructor} GreatWizard(String name)

+ heal(Hero hero)
+ superHeal(Hero hero)
}

class Slime
{
~ int slimeAttackPoint
- String suffix
- int hp
- int maxHP

+ {Constructor} Slime(String suffix, int hp)

+ String getSuffix()
+ int getHP()
+ int getMaxHP()

+ void setSuffix(String suffix)
+ void setHP(int hp)
+ void setMaxHP(int MaxHP)
}

class PoisonSlime extends Slime
{
- int poisonCount = 5

+ {Constructor} PoisonSlime(String suffix, int hp)

+ int getPoisonCount()
+ void attack(Hero hero)
}

class Cleric
{
~ int maxHP
~ int maxMP
~ int selfAidMPCost = 5
~ int randBoundary = 3
~ Random rand

- String name
- int hp
- int mp

+ {Constructor} Cleric(String name, int hp, int mp)
+ {Constructor} Cleric(String name, int hp)
+ {Constructor} Cleric(String name)

+ int getSelfAidMPCost()
+ String getName()
+ int getHP()
+ int getMP()

+ void setName(String name)
+ void setHP(int hp)
+ void setMP(int mp)

+ void selfAid()
+ int pray(int second)

}

@enduml
53 changes: 38 additions & 15 deletions game/src/main/java/com/survivalcoding/Cleric.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,14 @@ public class Cleric {
final int randBoundary = 3;
final Random rand = new Random();

String name;
int hp;
int mp;
private String name;
private int hp;
private int mp;

Cleric(String name, int hp, int mp) {
if (hp <= 0 || hp > 50) {
throw new IllegalArgumentException("HP는 최소 1, 최대 50이어야 합니다.");
} else if (mp < 0 || mp > 10) {
throw new IllegalArgumentException("MP는 최대 10이여야 합니다. 음수는 입력할 수 없습니다.");
}
this.name = name;
this.hp = hp;
this.mp = mp;
this.setName(name);
this.setHP(hp);
this.setMP(mp);
}

Cleric(String name, int hp) {
Expand All @@ -32,17 +27,45 @@ public class Cleric {
this(name, maxHP, maxMP);
}

Cleric() {
throw new IllegalArgumentException("스탯을 입력하지 않은 클레릭은 생성될 수 없습니다.");

public int getSelfAidMPCost() {
return selfAidMPCost;
}

public String getName() {
return name;
}

public void setName(String name) {
if (name == null) {
throw new IllegalArgumentException("이름은 null값 일 수 없습니다.");
}
this.name = name;
}

public int getHP() {
return hp;
}

public void setHP(int hp) {
this.hp = Math.clamp(hp, 0, maxHP);
}

public int getMP() {
return mp;
}

public void setMP(int mp) {
this.mp = Math.clamp(mp, 0, maxMP);
Comment on lines +50 to +59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Potential Java toolchain files =="
fd -HI 'pom.xml|build.gradle|build.gradle.kts|gradle.properties|settings.gradle|mvnw|.tool-versions|.java-version|.sdkmanrc' | sort

echo
echo "== Java version declarations =="
TOOLCHAIN_FILES="$(fd -HI 'pom.xml|build.gradle|build.gradle.kts|gradle.properties|settings.gradle|.tool-versions|.java-version|.sdkmanrc' || true)"
if [ -n "${TOOLCHAIN_FILES}" ]; then
  rg -n --no-heading 'sourceCompatibility|targetCompatibility|toolchain|languageVersion|maven.compiler.source|maven.compiler.target|<release>|java.version|--release' ${TOOLCHAIN_FILES} || true
else
  echo "No toolchain declaration files found."
fi

echo
echo "== Math.clamp usage sites =="
rg -n --no-heading 'Math\.clamp\s*\('

Repository: SurvivalCodingCampus/polytech_java2

Length of output: 541


🏁 Script executed:

#!/bin/bash

echo "=== game/build.gradle ===" 
cat game/build.gradle

echo -e "\n=== root build.gradle ===" 
cat build.gradle

echo -e "\n=== settings.gradle ===" 
cat settings.gradle

Repository: SurvivalCodingCampus/polytech_java2

Length of output: 931


🏁 Script executed:

#!/bin/bash

echo "=== Checking for gradle.properties ==="
if [ -f gradle.properties ]; then
    cat gradle.properties
else
    echo "No gradle.properties found"
fi

echo -e "\n=== Checking for JAVA_HOME or java version in system ==="
which java && java -version 2>&1 || echo "Java not available in sandbox"

echo -e "\n=== Double-checking all Math.clamp usages with context ==="
rg -B 2 -A 2 'Math\.clamp\s*\(' game/src/

Repository: SurvivalCodingCampus/polytech_java2

Length of output: 1412


Math.clamp 사용을 위해 Java 21 이상으로 빌드 버전을 명시해야 합니다.

현재 build.gradlesourceCompatibility 또는 targetCompatibility가 선언되어 있지 않습니다. Math.clamp는 Java 21 이상에서만 사용 가능하므로, 다음 중 하나를 수행하세요:

  • game/build.gradle에 명시적으로 Java 버전을 선언: sourceCompatibility = '21'targetCompatibility = '21' 추가
  • 또는 Java 20 이하를 지원해야 하면 별도의 clamp 구현으로 대체
현재 Math.clamp 사용 위치
game/src/main/java/com/survivalcoding/Slime.java:26
game/src/main/java/com/survivalcoding/Cleric.java:51
game/src/main/java/com/survivalcoding/Cleric.java:59
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@game/src/main/java/com/survivalcoding/Cleric.java` around lines 50 - 59, The
code uses Math.clamp in the setHP and setMP methods of the Cleric class (and
also in Slime.java at line 26), but Math.clamp is only available in Java 21 and
above. To fix this, do one of the following: (1) Update game/build.gradle by
adding sourceCompatibility = '21' and targetCompatibility = '21' to explicitly
require Java 21 as the minimum version, or (2) if Java 20 or lower must be
supported, create a custom clamp utility method that implements the same logic
(constraining a value between a minimum and maximum) and replace all Math.clamp
calls in Cleric.java's setHP and setMP methods and in Slime.java with calls to
this custom method.

}

void selfAid() {
// 5 미만일 때 실패하는 로직 구현
if (mp < selfAidMPCost) {
if (this.getMP() < selfAidMPCost) {
System.out.println("MP가 부족합니다.");
return;
}
mp -= selfAidMPCost;
this.setMP(this.getMP() - selfAidMPCost);
hp = maxHP;
}

Expand Down
65 changes: 65 additions & 0 deletions game/src/main/java/com/survivalcoding/GreatWizard.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
연습문제 5.
GreatWizard 클래스 요구사항:
Wizard 클래스를 상속받음
필드:
mp의 초기값은 150
메서드:
void heal(Hero hero) 재정의 : hp를 25 회복시키고 자신의 mp를 5 소모
void superHeal(Hero hero) : hp를 최대로 회복시키고 자신의 mp를 50 소모
mp가 부족하면 "마나가 부족합니다" 출력
힐을 성공하면 "슈퍼 힐을 시전했습니다. 대상 HP: XX" 출력
*/

package com.survivalcoding;

public class GreatWizard extends Wizard {
public static final int defaultGreatWizardMP = 150;
public static int recoverPoint = 25;
public static final int healMPCost = 5;
public static final int superHealMPCost = 50;


public GreatWizard(String name, int hp, int mp, Wand wand) {
super(name, hp, mp, wand);
}

public GreatWizard(String name, int hp, int mp) {
super(name, hp, mp);
}

public GreatWizard(String name, int hp) {
super(name, hp, defaultGreatWizardMP);
}

public GreatWizard(String name) {
super(name, Wizard.defaultWizardHP, defaultGreatWizardMP);
}

@Override
public void heal(Hero hero) {
if (hero == null) {
throw new IllegalArgumentException("Hero는 null 값이 올 수 없습니다.");
}
if (this.getMP() >= healMPCost) {
hero.setHP(hero.getHP() + recoverPoint);
System.out.println("힐을 시전했습니다. " + hero.getName() + " HP : " + hero.getHP());
this.setMP(this.getMP() - healMPCost);
} else {
System.out.println("마나가 부족합니다.");
}
}

public void superHeal(Hero hero) {
if (hero == null) {
throw new IllegalArgumentException("Hero는 null 값이 올 수 없습니다.");
}
if (this.getMP() >= superHealMPCost) {
this.setMP(this.getMP() - superHealMPCost);
hero.setHP(hero.getMaxHP());
System.out.println("슈퍼 힐을 시전했습니다. " + hero.getName() + " HP : " + hero.getHP());
} else {
System.out.println("마나가 부족합니다.");
}
}
}
Loading