1. 예외처리

예외처리(Exception Handling)란 프로그램 실행 중 발생할 수 있는 오류 상황을 미리 대비하여 프로그램이 비정상적으로 종료되지 않도록 처리하는 방법입니다. 예외가 발생할 수 있는 코드를 try 블록에 작성하고, 오류 발생 시 실행할 코드를 catch 블록에 작성함으로써 예외를 안전하게 처리할 수 있습니다. 필요에 따라 finally 블록을 사용하여 예외 발생 여부와 상관없이 반드시 실행해야 하는 코드를 넣을 수 있으며, 개발자는 throw 키워드를 사용해 명시적으로 예외를 발생시킬 수도 있습니다. 예외처리는 사용자 경험 향상과 안정적인 프로그램 유지에 필수적인 기능입니다.

 

** 예외처리 : 

 

 

2. 예외의 종류

Checked Exception 컴파일 시 예외처리를 강제
(try, catch를 쓰지 않을 때 무조건 오류나는 코드가 있음)
IOException, SQLException
-- 무조건 try, catch로 예외처리 해야함
Unchecked Exception 실행 중 발생, 컴파일러가 체크하지 않음 NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException 등

 

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ArithmeticException.html

3. 예외 처리 구문

try {
    // 예외가 발생할 수 있는 코드
} catch (ExceptionType e) {
    // 예외 발생 시 처리 코드
} finally {
    // 예외 발생 여부와 무관하게 항상 실행 (선택 사항)
}

 

public class Example1 {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // 0으로 나누기 → 예외 발생
            System.out.println("결과: " + result);
        } catch (ArithmeticException e) {
            System.out.println("예외 발생: " + e.getMessage());
        } finally {
            System.out.println("이 코드는 항상 실행됩니다.");
        }
    }
}

 

public class Example2 {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};

        try {
            System.out.println(numbers[5]); // 존재하지 않는 인덱스
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("배열 범위를 벗어났습니다: " + e);
        }
    }
}

 

public class Example3 {
    public static void main(String[] args) {
        try {
            String s = null;
            System.out.println(s.length());  // NullPointerException
        } catch (ArithmeticException e) {
            System.out.println("산술 오류: " + e.getMessage());
        } catch (NullPointerException e) {
            System.out.println("널 오류: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("기타 오류: " + e.getMessage());
        }
    }
}

 

import java.util.Scanner;

public class DivisionCalculator {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        try {
            System.out.println("=== 나눗셈 계산기 ===");
            System.out.print("첫 번째 숫자를 입력하세요: ");
            int a = scanner.nextInt();

            System.out.print("두 번째 숫자를 입력하세요: ");
            int b = scanner.nextInt();

            int result = a / b;  // b가 0이면 예외 발생
            System.out.println("결과: " + a + " ÷ " + b + " = " + result);
        } catch (ArithmeticException e) {
            System.out.println("오류: 0으로 나눌 수 없습니다!");
        } catch (Exception e) {
            System.out.println("예기치 못한 오류 발생: " + e.getMessage());
        } finally {
            System.out.println("계산기를 종료합니다. 이용해주셔서 감사합니다.");
            scanner.close();
        }
    }
}

 

 

4. 예외 던지기

자바에서 예외가 발생한 상황을 직접 명시적으로 알리고 처리 흐름을 넘기는 방법입니다. 예외가 발생하면, 자바는 해당 예외 객체를 만들어서 던지고(throw), 호출한 쪽은 그 예외를 받아서 처리하거나 다시 위로 넘겨야 합니다. 이때 사용하는 키워드가 바로 throw와 throws입니다.

1. throw 키워드

  • throw는 실제 예외 객체를 생성해서 직접 던질 때 사용합니다.
  • 주로 if 문에서 조건에 따라 예외를 발생시킬 때 사용합니다.
throw new 예외클래스("메시지");

 

public class ThrowExample {
    public static void checkAge(int age) {
        if (age < 18) {
            throw new IllegalArgumentException("18세 미만은 접근할 수 없습니다.");
        }
        System.out.println("접근 허용");
    }

    public static void main(String[] args) {
        checkAge(15);  // 예외 발생!
    }
}

 

2. throws 키워드

  • throws는 예외를 현재 메서드에서 직접 처리하지 않고, 호출한 쪽으로 넘기겠다고 선언할 때 사용합니다.
  • 주로 Checked Exception(컴파일러가 검사하는 예외)에 사용합니다.
리턴타입 메서드이름() throws 예외클래스 {
    // 예외 발생 코드
}

 

// 예외를 던질 수 있는 메서드
public class ThrowsExample {
    public static double calculateSqrt(int number) throws IllegalArgumentException {
        if (number < 0) {
            throw new IllegalArgumentException("음수는 제곱근을 계산할 수 없습니다.");
        }
        return Math.sqrt(number);
    }

    public static void main(String[] args) {
        try {
            double result = calculateSqrt(-9);  // 예외 발생!
            System.out.println("결과: " + result);
        } catch (IllegalArgumentException e) {
            System.out.println("예외 발생: " + e.getMessage());
        }
    }
}
  • calculateSqrt(int number) 메서드는 throws IllegalArgumentException을 통해 예외를 직접 처리하지 않고 위임합니다.
  • main() 메서드에서 try ~ catch를 사용하여 예외를 실제로 처리합니다.
  • IllegalArgumentException은 Unchecked Exception이지만 throws와 함께 사용할 수 있습니다.

 

 

5. 사용자 정의 예외

기본 제공하는 예외 클래스(예: NullPointerException, IOException 등) 외에, 개발자가 직접 만든 예외 클래스입니다. 특정 상황에 대해 명확하게 예외를 구분하거나, 의미 있는 이름과 메시지로 예외를 전달하고 싶을 때 사용합니다.

만드는 방법

  • Exception 또는 RuntimeException을 상속받습니다.
  • 생성자에서 super(메시지)를 호출해 부모 생성자에게 메시지를 전달합니다.

1. Checked 사용자 정의 예외 (Exception 상속)

class InvalidScoreException extends Exception{
    public InvalidScoreException(String message){
        super(message);
    }
}

public class Ex06_Main {
    public static  void checkScore(int score) throws InvalidScoreException {
        if (score < 0 || score > 100) {
            throw new InvalidScoreException("점수는 0에서 100사이여야 합니다.");
        }
        System.out.println("유효한 점수입니다." + score);
    }
    public static void main(String[] args) {
        try{
            checkScore(150);
        }catch(InvalidScoreException e){
            System.out.println("예외 처리됨 :"+e.getMessage());
        }
    }
}

 

2. Unchecked 사용자 정의 예외 (RuntimeException 상속)

package lesson06;

class InvalidUserException extends RuntimeException {
    public InvalidUserException(String message) {
        super(message);
    }
}
class UncheckedException extends RuntimeException {
    public static void validateUsername(String name){
        if(name==null || name.isEmpty()){
            throw new InvalidUserException("사용자 이름은 공백이 있을 수 없습니다.");
        }
        System.out.println("사용자 이름: " + name);
    }
}

public class Ex07_Main {
    public static void main(String[] args) {
//        UncheckedException ue = new UncheckedException();
        try{
            UncheckedException.validateUsername("");
        }catch(InvalidUserException e){
            System.out.println("예외 처리됨 : " + e.getMessage());
        }
    }
}

'Server > Java' 카테고리의 다른 글

Generic  (0) 2025.05.27
java.lang package  (0) 2025.05.27
Interface  (0) 2025.05.27
abstract  (0) 2025.05.21
Static  (1) 2025.05.20

1. 인터페이스

자바에서 인터페이스(Interface)는 클래스가 구현해야 할 동작(기능)의 틀만 정의해놓은 일종의 "설계서"입니다. 인터페이스는 메서드의 선언만 포함하고, 구현은 하지 않으며, 해당 인터페이스를 implements 키워드로 구현한 클래스가 모든 메서드를 오버라이딩하여 실제 동작을 정의해야 합니다. 자바는 다중 상속을 지원하지 않지만, 인터페이스를 통해 다중 구현이 가능하므로 다양한 클래스들이 공통적인 동작을 약속받고 서로 다른 방식으로 동작하도록 설계할 수 있습니다. 이는 다형성과 확장성 있는 구조를 만드는 데 매우 유용합니다.

 

모든 메서드는 기본적으로 public abstract 구현하지 않고 선언만 한다는 뜻
모든 필드는 public static final (상수) 값을 바꿀 수 없는 상수만 선언 가능
다중 구현 가능 클래스는 여러 인터페이스를 implements 할 수 있음
인터페이스는 new로 인스턴스화할 수 없음 반드시 구현 클래스가 필요

 

public interface Animal {
    void sound();            // 추상 메서드
    void move();             // 추상 메서드
}
  • 여기서 void sound() 와 void move()는 구현하지 않은 메서드입니다.
  • 이 인터페이스를 구현(implements)한 클래스는 반드시 이 메서드들을 오버라이딩해야 합니다.

 

public interface Animal {
    void sound();    // 소리내기
    void move();     // 움직이기
}

public class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("멍멍!");
    }

    @Override
    public void move() {
        System.out.println("개가 네 발로 뜁니다.");
    }
}

public class Bird implements Animal {
    @Override
    public void sound() {
        System.out.println("짹짹!");
    }

    @Override
    public void move() {
        System.out.println("새가 날아갑니다.");
    }
}

public class InterfaceExample {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal bird = new Bird();

        dog.sound();  // 멍멍!
        dog.move();   // 개가 네 발로 뜁니다.

        bird.sound(); // 짹짹!
        bird.move();  // 새가 날아갑니다.
    }
}

 

 

2. 종합예제

객체지향 설계 실습 : 다기능 로봇 시스템

public interface Cleaner {
    void clean();
}

public interface Cooker {
    void cook();
}

public interface Singer {
    void sing();
}

public interface Dancer {
    void dance();
}

 

public abstract class Robot {
    String modelName;

    public Robot(String modelName) {
        this.modelName = modelName;
    }

    public void identify() {
        System.out.println("🤖 안녕하세요! 저는 " + modelName + " 로봇입니다.");
    }
}

 

public class MultiRobot extends Robot implements Cleaner, Cooker, Singer, Dancer {

    public MultiRobot(String modelName) {
        super(modelName);
    }

    @Override
    public void clean() {
        System.out.println("🧹 [" + modelName + "] 청소를 시작합니다.");
    }

    @Override
    public void cook() {
        System.out.println("🍳 [" + modelName + "] 요리를 시작합니다.");
    }

    @Override
    public void sing() {
        System.out.println("🎵 [" + modelName + "] 노래를 부릅니다.");
    }

    @Override
    public void dance() {
        System.out.println("💃 [" + modelName + "] 멋지게 춤을 춥니다!");
    }
}

 

public class RobotTest {
    public static void performFunction(Cleaner cleaner) {
        cleaner.clean();
    }

    public static void performFunction(Cooker cooker) {
        cooker.cook();
    }

    public static void performFunction(Singer singer) {
        singer.sing();
    }

    public static void performFunction(Dancer dancer) {
        dancer.dance();
    }

    public static void main(String[] args) {
        MultiRobot roboX = new MultiRobot("Robo-X");
        roboX.identify();

        System.out.println("\n[일반 호출]");
        roboX.clean();
        roboX.cook();
        roboX.sing();
        roboX.dance();

        System.out.println("\n[다형성 활용: 기능별 인터페이스로 호출]");
        performFunction((Cleaner) roboX);
        performFunction((Cooker) roboX);
        performFunction((Singer) roboX);
        performFunction((Dancer) roboX);
    }
}
 

'Server > Java' 카테고리의 다른 글

java.lang package  (0) 2025.05.27
Exception Handling  (0) 2025.05.27
abstract  (0) 2025.05.21
Static  (1) 2025.05.20
String 클래스  (0) 2025.05.20

1. abstract

abstract는 자바에서 추상 클래스나 추상 메서드를 정의할 때 사용하는 키워드입니다. 추상 클래스는 객체를 직접 생성할 수 없고, 공통적인 속성과 동작을 정의하여 상속을 통해 구체화되도록 설계된 클래스입니다. 이 안에 선언된 추상 메서드는 본체(구현부)가 없는 메서드로, 자식 클래스에서 반드시 오버라이딩(재정의)해야 합니다. 즉, abstract 키워드는 구체적인 동작을 미리 정하지 않고, 자식 클래스가 각자의 방식으로 구현하도록 강제하는 역할을 합니다. 이를 통해 객체지향 프로그래밍에서 다형성과 설계 유연성을 높일 수 있습니다.

1. 추상 클래스 (abstract class)

  • 인스턴스를 생성할 수 없는 클래스입니다.
  • 다른 클래스들이 공통적으로 가져야 할 속성과 메서드를 정의합니다.
  • 하위 클래스에서 상속하여 구체화됩니다
  • 추상 메서드가 존재하면 클래스 앞에도 존재해야 한다. (반대로 없으면 존재하면 안된다.)
abstract class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    // 추상 메서드
    abstract void sound();

    // 일반 메서드
    void sleep() {
        System.out.println(name + "이(가) 잠을 잡니다.");
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    void sound() {
        System.out.println(name + "이(가) 멍멍 짖습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Animal a = new Animal("동물"); // ❌ 에러! 추상 클래스는 객체 생성 불가
        Animal dog = new Dog("루시");
        dog.sound();  // 루시이(가) 멍멍 짖습니다.
        dog.sleep();  // 루시이(가) 잠을 잡니다.
    }
}

 

2. 추상 메서드 (abstract method)

  • 메서드 선언만 있고, 구현(바디)가 없는 메서드입니다.
  • 반드시 추상 클래스 내에서만 정의할 수 있습니다.
  • 이 메서드는 자식 클래스에서 반드시 오버라이딩해야 합니다.
abstract class Shape {
    abstract double getArea(); // 추상 메서드: 면적 계산
}

class Circle extends Shape {
    double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double getArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    double width, height;

    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    double getArea() {
        return width * height;
    }
}

public class AreaTest {
    public static void main(String[] args) {
        Shape s1 = new Circle(3);
        Shape s2 = new Rectangle(4, 5);
        System.out.println("원 면적: " + s1.getArea());
        System.out.println("사각형 면적: " + s2.getArea());
    }
}

주의사항

  • 추상 클래스 내에는 일반 메서드, 필드, 생성자도 정의할 수 있습니다.
  • 추상 클래스를 상속한 클래스는 모든 추상 메서드를 구현해야 합니다. 단, 구현하지 않는다면 그 클래스도 추상 클래스로 선언해야 합니다.
  • 인터페이스와는 달리 생성자, 상태(필드)를 가질 수 있습니다.  --> 다중 상속이 안되기 때문에, 추상 클래스만 여러개 만들 수 있음. abstract
  • abstract는 자바에서 추상 클래스나 추상 메서드를 정의할 때 사용하는 키워드입니다. 추상 클래스는 객체를 직접 생성할 수 없고, 공통적인 속성과 동작을 정의하여 상속을 통해 구체화되도록 설계된 클래스입니다. 이 안에 선언된 추상 메서드는 본체(구현부)가 없는 메서드로, 자식 클래스에서 반드시 오버라이딩(재정의)해야 합니다. 즉, abstract 키워드는 구체적인 동작을 미리 정하지 않고, 자식 클래스가 각자의 방식으로 구현하도록 강제하는 역할을 합니다. 이를 통해 객체지향 프로그래밍에서 다형성과 설계 유연성을 높일 수 있습니다.

1. 추상 클래스 (abstract class)

인스턴스를 생성할 수 없는 클래스입니다.

다른 클래스들이 공통적으로 가져야 할 속성과 메서드를 정의합니다.

하위 클래스에서 상속하여 구체화됩니다

추상 메서드가 존재하면 클래스 앞에도 존재해야 한다. (반대로 없으면 존재하면 안된다.)

abstract class Animal {

    String name;



    public Animal(String name) {

        this.name = name;

    }



    // 추상 메서드

    abstract void sound();



    // 일반 메서드

    void sleep() {

        System.out.println(name + "이(가) 잠을 잡니다.");

    }

}



class Dog extends Animal {

    public Dog(String name) {

        super(name);

    }



    @Override

    void sound() {

        System.out.println(name + "이(가) 멍멍 짖습니다.");

    }

}



public class Main {

    public static void main(String[] args) {

        // Animal a = new Animal("동물"); // ❌ 에러! 추상 클래스는 객체 생성 불가

        Animal dog = new Dog("루시");

        dog.sound(); // 루시이(가) 멍멍 짖습니다.

        dog.sleep(); // 루시이(가) 잠을 잡니다.

    }

}





2. 추상 메서드 (abstract method)

메서드 선언만 있고, 구현(바디)가 없는 메서드입니다.

반드시 추상 클래스 내에서만 정의할 수 있습니다.

이 메서드는 자식 클래스에서 반드시 오버라이딩해야 합니다.

abstract class Shape {

    abstract double getArea(); // 추상 메서드: 면적 계산

}



class Circle extends Shape {

    double radius;



    Circle(double radius) {

        this.radius = radius;

    }



    @Override

    double getArea() {

        return Math.PI * radius * radius;

    }

}



class Rectangle extends Shape {

    double width, height;



    Rectangle(double width, double height) {

        this.width = width;

        this.height = height;

    }



    @Override

    double getArea() {

        return width * height;

    }

}



public class AreaTest {

    public static void main(String[] args) {

        Shape s1 = new Circle(3);

        Shape s2 = new Rectangle(4, 5);

        System.out.println("원 면적: " + s1.getArea());

        System.out.println("사각형 면적: " + s2.getArea());

    }

}

주의사항

추상 클래스 내에는 일반 메서드, 필드, 생성자도 정의할 수 있습니다.

추상 클래스를 상속한 클래스는 모든 추상 메서드를 구현해야 합니다. 단, 구현하지 않는다면 그 클래스도 추상 클래스로 선언해야 합니다.

인터페이스와는 달리 생성자, 상태(필드)를 가질 수 있습니다. --> 다중 상속이 안되기 때문에, 추상 클래스만 여러개 만들 수 있음

 

2. 종합예제

콘솔 기반 턴제 동물 배틀 게임 만들기

  • 동물(Animal) 클래스를 기반으로 한 콘솔 기반 턴제 배틀 게임을 구현합니다.
  • 추상 클래스와 상속, 메서드 오버라이딩을 활용하여 동물들의 다양한 능력을 표현합니다.
  • 플레이어와 적 팀은 각각 2마리의 동물로 구성되어 있으며, 서로 공격하고 교체하며 승패를 겨루게 됩니다.

1. 추상 클래스 Animal

  • 이름(name), 체력(hp), 최대 체력(maxHp), 공격력(power) 등의 공통 속성을 가짐
  • 다음 메서드를 abstract로 정의:
    • attack(): 일반 공격 출력
    • getAttackPower(): 랜덤 공격력 반환
    • getSkillName(): 스킬 이름 반환
    • useSkill(Animal enemy): 적에게 스킬 사용

2. 3종의 동물 클래스 만들기 (Animal을 상속)

  • 예: Dragon, Wolf, Bear
  • 각 동물은:
    • 서로 다른 기본 체력
    • 서로 다른 일반 공격력 범위
    • 서로 다른 스킬 구현
    • 서로 다른 스킬 사용 가능 횟수

3. 플레이어 팀 구성

  • 시작 시 2마리의 동물을 선택하도록 구현
  • 선택한 동물은 각각 독립적인 객체로 생성해야 함

4. 적 팀 구성

  • 컴퓨터가 2마리 동물을 무작위로 선택 (중복 없음)

5. 턴제 전투 시스템

  • 플레이어와 적이 번갈아 가며 공격
  • 한 쪽 동물의 체력이 0이 되면 다음 동물로 교체
  • 두 마리 모두 쓰러지면 패배

6. 행동 선택 (플레이어 턴)

  • 매 턴 다음 중 하나 선택 가능:
    1. 일반 공격
    2. 스킬 사용 (남은 횟수 있을 경우만 사용 가능)
    3. 다른 동물로 교체 (이미 쓰러진 동물은 교체 불가)

7. AI 턴 로직 (컴퓨터 턴)

  • 다음 기준에 따라 컴퓨터가 행동 결정:
    • 체력이 60 이하이고 스킬 가능 → 70% 확률로 스킬 사용
    • 그렇지 않으면 일반 공격

8. 전투 종료 조건

  • 한 팀의 동물 2마리 모두 쓰러지면 전투 종료
  • 결과 메시지를 출력:
    • 🎉 승리!
    • 💀 패배...
import java.util.Random;
import java.util.Scanner;

abstract class Animal {
    String name;
    int maxHp;
    int hp;
    int power;
    int maxSkillCount;
    int skillCount;

    public Animal(String name, int power, int maxHp, int maxSkillCount) {
        this.name = name;
        this.power = power;
        this.maxHp = maxHp;
        this.hp = maxHp;
        this.maxSkillCount = maxSkillCount;
        this.skillCount = maxSkillCount;
    }

    abstract void attack();
    abstract int getAttackPower();
    abstract String getSkillName();
    abstract void useSkill(Animal enemy);

    boolean isAlive() {
        return hp > 0;
    }

    boolean canUseSkill() {
        return skillCount > 0;
    }

    void takeDamage(int damage) {
        hp -= damage;
        if (hp < 0) hp = 0;
    }

    void printStatus() {
        System.out.println(name + " 체력: " + hp + "/" + maxHp + " | 스킬 남은 횟수: " + skillCount + "/" + maxSkillCount);
    }
}

class Dragon extends Animal {
    public Dragon() {
        super("드래곤", 30, 180, 3); // 체력 상향, 스킬 3회
    }

    @Override
    void attack() {
        System.out.println("🔥 드래곤이 불을 내뿜는다!");
    }

    @Override
    int getAttackPower() {
        return power + new Random().nextInt(20); // 30~49
    }

    @Override
    String getSkillName() {
        return "파이어 브레스";
    }

    @Override
    void useSkill(Animal enemy) {
        if (canUseSkill()) {
            skillCount--;
            int damage = 40 + new Random().nextInt(20); // 40~59
            System.out.println("🔥 드래곤의 스킬! 파이어 브레스!! (불꽃 데미지 " + damage + ")");
            enemy.takeDamage(damage);
        } else {
            System.out.println("❌ 드래곤은 더 이상 스킬을 사용할 수 없습니다!");
        }
    }
}

class Wolf extends Animal {
    public Wolf() {
        super("늑대", 25, 160, 2); // 체력 상향, 스킬 2회
    }

    @Override
    void attack() {
        System.out.println("🌕 늑대가 어둠 속에서 덮친다!");
    }

    @Override
    int getAttackPower() {
        return power + new Random().nextInt(15); // 25~39
    }

    @Override
    String getSkillName() {
        return "달빛 송곳니";
    }

    @Override
    void useSkill(Animal enemy) {
        if (canUseSkill()) {
            skillCount--;
            int damage = 35 + new Random().nextInt(15); // 35~49
            System.out.println("🌙 늑대의 스킬! 달빛 송곳니!! (강력 데미지 " + damage + ")");
            enemy.takeDamage(damage);
        } else {
            System.out.println("❌ 늑대는 더 이상 스킬을 사용할 수 없습니다!");
        }
    }
}

class Bear extends Animal {
    public Bear() {
        super("곰", 20, 200, 1);
    }

    @Override
    void attack() {
        System.out.println("🐻 곰이 앞발로 내려친다!");
    }

    @Override
    int getAttackPower() {
        return power + new Random().nextInt(10); // 20~29
    }

    @Override
    String getSkillName() {
        return "대지 강타";
    }

    @Override
    void useSkill(Animal enemy) {
        if (canUseSkill()) {
            skillCount--;
            int damage = (int)(enemy.hp * 0.3);
            if (damage < 25) damage = 25;
            System.out.println("🌍 곰의 스킬! 대지 강타!! (적 체력의 30% 데미지: " + damage + ")");
            enemy.takeDamage(damage);
        } else {
            System.out.println("❌ 곰은 더 이상 스킬을 사용할 수 없습니다!");
        }
    }
}

// === 메인 게임 ===

public class AnimalBattleGame {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Animal[] allAnimals = { new Dragon(), new Wolf(), new Bear() };
        Animal[] playerTeam = new Animal[2];
        Animal[] enemyTeam = new Animal[2];
        Random rand = new Random();

        System.out.println("🧙 전설의 포켓동물 배틀에 오신 걸 환영합니다!");
        System.out.println("두 마리의 포켓동물을 선택하세요:");

        for (int i = 0; i < 2; i++) {
            System.out.println("1. 드래곤 🐉\n2. 늑대 🐺\n3. 곰 🐻");
            System.out.print((i + 1) + "번째 동물 선택: ");
            int choice = sc.nextInt();
            playerTeam[i] = copyAnimal(allAnimals[choice - 1]);
        }

        // 적 팀 랜덤 구성
        for (int i = 0; i < 2; i++) {
            while (true) {
                Animal enemy = copyAnimal(allAnimals[rand.nextInt(3)]);
                boolean exists = false;
                for (int j = 0; j < i; j++) {
                    if (enemy.name.equals(enemyTeam[j].name)) {
                        exists = true;
                        break;
                    }
                }
                if (!exists) {
                    enemyTeam[i] = enemy;
                    break;
                }
            }
        }

        int playerIdx = 0;
        int enemyIdx = 0;

        while (true) {
            Animal player = playerTeam[playerIdx];
            Animal enemy = enemyTeam[enemyIdx];

            System.out.println("\n====================================");
            System.out.println("👊 현재 전투 중인 포켓동물");
            player.printStatus();
            enemy.printStatus();

            // 플레이어 턴
            System.out.println("\n[당신의 턴]");
            System.out.println("1. 일반 공격");
            System.out.println("2. 스킬 사용 (" + player.getSkillName() + ")");
            System.out.println("3. 동물 교체");
            System.out.print("선택 >> ");
            int action = sc.nextInt();

            if (action == 1) {
                player.attack();
                int damage = player.getAttackPower();
                System.out.println("💥 공격력: " + damage);
                enemy.takeDamage(damage);
            } else if (action == 2) {
                player.useSkill(enemy);
            } else if (action == 3) {
                int otherIdx = 1 - playerIdx;
                if (!playerTeam[otherIdx].isAlive()) {
                    System.out.println("⚠️ 다른 동물이 전투불능입니다!");
                    continue;
                }
                playerIdx = otherIdx;
                System.out.println("🔁 포켓동물 교체!");
                continue;
            }

            if (!enemy.isAlive()) {
                System.out.println("✅ 적 " + enemy.name + "이(가) 쓰러졌습니다!");
                enemyIdx++;
                if (enemyIdx >= 2) {
                    System.out.println("🎉 당신이 승리했습니다!!");
                    break;
                }
            }

            // 적 턴
            Animal currEnemy = enemyTeam[enemyIdx];
            Animal currPlayer = playerTeam[playerIdx];
            System.out.println("\n[👾 적의 턴: " + currEnemy.name + "]");

            int enemyChoice;
            if (currEnemy.hp < 60 && currEnemy.canUseSkill() && rand.nextInt(100) < 70) {
                enemyChoice = 1;
            } else {
                enemyChoice = 0;
            }

            if (enemyChoice == 0) {
                System.out.println("👾 적이 일반 공격을 선택합니다!");
                currEnemy.attack();
                int damage = currEnemy.getAttackPower();
                System.out.println("💥 공격력: " + damage);
                currPlayer.takeDamage(damage);
            } else {
                System.out.println("👾 적이 스킬 '" + currEnemy.getSkillName() + "'을(를) 사용합니다!");
                currEnemy.useSkill(currPlayer);
            }

            if (!currPlayer.isAlive()) {
                System.out.println("☠️ 당신의 " + currPlayer.name + "이(가) 쓰러졌습니다!");
                if (!playerTeam[1 - playerIdx].isAlive()) {
                    System.out.println("💀 모든 포켓동물이 쓰러졌습니다. 패배...");
                    break;
                } else {
                    playerIdx = 1 - playerIdx;
                    System.out.println("🔁 자동으로 다른 포켓동물로 교체합니다!");
                }
            }
        }

        sc.close();
    }

    public static Animal copyAnimal(Animal original) {
        if (original.name.equals("드래곤")) return new Dragon();
        else if (original.name.equals("늑대")) return new Wolf();
        else return new Bear();
    }
}

'Server > Java' 카테고리의 다른 글

Exception Handling  (0) 2025.05.27
Interface  (0) 2025.05.27
Static  (1) 2025.05.20
String 클래스  (0) 2025.05.20
접근 제한자  (0) 2025.05.20

1. static

static은 자바에서 클래스에 속하는 멤버(변수나 메서드)를 정의할 때 사용하는 키워드입니다. static으로 선언된 변수나 메서드는 객체를 생성하지 않고도 클래스 이름으로 직접 접근할 수 있으며, 모든 객체가 공유하는 공통된 데이터로 사용됩니다. 예를 들어, static int count는 생성된 모든 객체가 같은 count 값을 공유하게 되며, static 메서드는 인스턴스 변수에 직접 접근할 수 없고 this 키워드도 사용할 수 없습니다. 주로 유틸리티 함수나 공통 속성을 정의할 때 사용됩니다.

혼자 따로 노는 녀석...

 

1. static 변수(정적 변수)

모든 객체가 공유하는 변수입니다. 보통 객체 수 카운트 등에 사용됩니다.

class Car {
    static int count = 0;  // 클래스 변수 (공용)

    public Car() {
        count++;  // 객체가 생성될 때마다 count 증가
    }
}

public class Main {
    public static void main(String[] args) {
        new Car();
        new Car();
        new Car();
        System.out.println("총 생성된 자동차 수: " + Car.count);  // 출력: 3
    }
}

 

2. static 메서드 (정적 메서드)

객체를 생성하지 않고 사용할 수 있는 클래스 메서드입니다. 대표적인 예는 Math 클래스입니다.

class Calculator {
	// heap에 올라감 --> static이 없으면 따로 작용한다.
    public static int add(int a, int b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        int result = Calculator.add(5, 3);  // 객체 생성 없이 사용
        System.out.println("결과: " + result);  // 출력: 8
    }
}

 

3. static 블록( = static initializer)

클래스가 처음 로딩될 때 딱 한 번 실행되는 초기화 블록입니다. 복잡한 static 변수 초기화에 사용됩니다.

  • 클래스가 JVM에 의해 처음 로딩될 때 자동으로 실행됩니다.
  • 주로 static 변수의 복잡한 초기화에 사용됩니다.
  • 생성자보다 먼저, 단 한 번만 실행됩니다.
  • static initializer 안에서는 인스턴스 변수나 메서드 사용 불가합니다.
class Config {
    static int maxUsers;

    static {
        // 복잡한 초기화 코드
        maxUsers = 100;
        System.out.println("Config 클래스가 로딩되면서 초기화됨!");
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("최대 사용자 수: " + Config.maxUsers);
    }
}

 

class ServerConfig {
    static int port;
    static String mode;

    static {
        System.out.println("static initializer 실행됨");
        // 보통은 외부파일에 받아서 함
        port = 8080;
        mode = "production";
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("포트: " + ServerConfig.port);
        System.out.println("모드: " + ServerConfig.mode);
    }
}

 

4. static import

static import를 사용하면 클래스명을 생략하고 static 멤버를 직접 쓸 수 있습니다.

import static java.lang.Math.*;

public class Main {
    public static void main(String[] args) {
        System.out.println(sqrt(16));  // Math.sqrt(16) → sqrt만 사용 가능
        System.out.println(pow(2, 3)); // Math.pow(2, 3)
    }
}

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Math.html

** java.lang.Math.* 에 대해 검색해보기~

 

Math (Java SE 17 & JDK 17)

public final class Math extends Object The class Math contains methods for performing basic numeric operations such as the elementary exponential, logarithm, square root, and trigonometric functions. Unlike some of the numeric methods of class StrictMath,

docs.oracle.com

  • Java SE ( Standard Edition )
  • Java ME ( Moblie Edition )
  • Java EE ( Enterprise Edition ) : 기업용 서버 만드는 용도

 

2. 싱글톤(Singleton) 패턴이란?

하나의 클래스에 대해 오직 하나의 인스턴스만 생성되도록 보장하는 디자인 패턴입니다.

  • 공통된 설정, 리소스, 서비스 등을 한 객체로 공유하고 싶을 때 사용
  • 예: 설정 클래스, 로깅, DB 연결 관리자 등

cf. 디자인 패턴

  • 자주 반복되는 문제 해결 방법을 재사용 가능한 코드 형태로 정리해 놓은 설계
  • 객체 지향 원칙을 기반으로 설계

사용이유?

  • 유지보수가 쉬운 코드를 만들기 위해
  • 재사용 가능하고 확장성 있는 구조를 만들기 위해
  • 여러 개발자가 협업할 때 일관성 있는 코드 설계를 위해 

디자인 패턴 3가지

  • 생성 패턴 : 객체 생성 방식에 관련된 패턴
  • 구조 패턴 : 클래스나 객체를 조합하여 더 큰 구조를 만드는 패턴
  • 행위 패턴 : 객체들 간의 책임 분배와 메세지 교환 방식을 정의하는 패턴

GoF 디자인 패턴 ( Gang of Four ) - 23가지 패턴 분류

  • 생성(Creational) 패턴 (객체 생성 방식을 캡슐화)
    • Singleton, Factory Method, Abstract Factory, Builder, Prototype
  • 구조(Structural) 패턴 (클래스나 객체 조합해서 더 큰 구조를 형성)
    • Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
  • 행동(Behavioral) 패턴 (객체 간 책임 분배 및 상호작용 정의)
    • Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
class Singleton {
    // 1. 클래스 내부에서 static으로 단 하나의 인스턴스 생성
    private static final Singleton instance = new Singleton();

    // 2. 생성자는 private으로 외부에서 생성 못 하게 막음
    private Singleton() {
        System.out.println("싱글톤 객체 생성됨");
    }

    // 3. 외부에서 instance에 접근할 수 있는 static 메서드 제공
    public static Singleton getInstance() {
        return instance;
    }

    // 예시 기능
    public void printMessage() {
        System.out.println("싱글톤 패턴으로 실행 중입니다!");
    }
}

 

public class Main {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        s1.printMessage();

        System.out.println(s1 == s2); // true → 같은 객체
    }
}

 

'Server > Java' 카테고리의 다른 글

Interface  (0) 2025.05.27
abstract  (0) 2025.05.21
String 클래스  (0) 2025.05.20
접근 제한자  (0) 2025.05.20
상속  (2) 2025.05.19

1. String 클래스

자바에서 String 클래스는 문자열 데이터를 표현하기 위한 클래스로, 문자들의 집합을 객체 형태로 다룰 수 있도록 해줍니다. String은 불변(immutable) 특성을 가지며, 한 번 생성된 문자열은 수정할 수 없고, 변경이 필요한 경우 새로운 문자열 객체가 생성됩니다. 문자열 리터럴은 자동으로 String 객체로 취급되며, 다양한 문자열 조작 메서드(length(), substring(), charAt(), equals(), toUpperCase() 등)를 제공하여 문자열을 효율적으로 처리할 수 있습니다. 또한 String은 자바에서 매우 중요한 클래스이기 때문에 기본 타입처럼 사용할 수 있는 특수한 참조형 객체이며, + 연산자를 통한 문자열 연결, ==과 equals()의 차이 등은 중요한 개념으로 자주 등장합니다.

 

1. 문자열 불변(Immutable)

  • String 객체는 한 번 생성되면 수정이 불가능
  • 문자열을 변경하면 새로운 String 객체가 생성됨
String str = "Hello";
str = str + " World";  // 기존 "Hello"는 버려지고 새로운 "Hello World" 객체가 생성됨

 

2. 문자열은 리터럴 풀에서 공유

  • 같은 문자열 리터럴은 JVM이 하나만 생성해서 공유
  • == 연산자는 주소(참조값) 비교이고, equals()는 내용 비교
// stack | heap(  | 리터럴풀)
// a와 b는 리터럴 풀에 Java를 생성하여 참조함


String a = "Java";
String b = "Java";
System.out.println(a == b);        // true (같은 주소)
System.out.println(a.equals(b));  // true (같은 내용)

 

3. 문자열 생성

String s1 = "hello";                 // 리터럴 방식 (문자열 풀에 저장)
String s2 = new String("hello");     // 명시적 생성 (heap에 새 객체)

 

리터럴 방식

String s1 = "hello";

 

  • 문자열 "hello"는 "상수 풀(String Constant Pool)" 이라는 특별한 메모리 공간에 저장됩니다.
  • 같은 리터럴을 사용하는 경우 이미 존재하면 재사용합니다 (메모리 절약).
  • 상수 풀은 힙 메모리 영역 내에 존재하지만 일반 객체처럼 관리되지 않음.

객체 생성 방식

String s2 = new String("hello");
  • "hello" 리터럴은 상수 풀에 존재하고,
  • new String("hello")는 힙 영역에 별도로 새 객체를 생성합니다.
  • 즉, 같은 "hello"라는 문자열이 상수 풀과 힙에 각각 존재하게 됩니다.

따라서 s1 == s2 → false (주소 다름), s1.equals(s2) → true (내용 같음)

 

4. 상수 풀(Constant Pool)

메모리를 절약하고 성능을 향상시키기 위해 만들어진 특수한 저장 공간입니다. 문자열뿐만 아니라 다른 종류의 상수와 참조 정보도 저장됩니다.

 

문자열 리터럴 (String Literals): 문자열 리터럴은 자동으로 상수 풀에 저장되며, 동일한 문자열은 공유됨

String s1 = "hello";
String s2 = "hello"; // 기존 "hello" 재사용됨 (상수 풀 공유)

 

기본형 상수 (정수, 실수, 문자, boolean 등): final로 선언된 기본형 상수값들은 컴파일 시 상수 풀에 저장됩니다.

final int A = 100;
final double PI = 3.14;

 

클래스, 메서드, 필드 이름 등 (바이트코드 수준에서): 컴파일된 .class 파일 안에는 java/lang/System, out, println, "Hello" 같은 문자열이나 심볼이 상수 풀 테이블(Constant Pool Table)에 저장되어 있음

System.out.println("Hello");

 

위 코드가 컴파일되면 .class 파일 안의 상수 풀에는 다음 정보들이 저장됩니다.

  • "Hello" → 문자열 리터럴
  • java/lang/System → 클래스 이름
  • out → 필드 이름
  • java/io/PrintStream → 필드 타입
  • println → 메서드 이름
  • (Ljava/lang/String;)V → 메서드 시그니처

중요한 건 이 정보들이 저장된다고 해서 객체가 만들어진 건 아니라는 점입니다. JVM이 클래스를 로딩하고 실행할 때 이 정보들을 사용해서 객체를 만들거나 메서드를 호출하는 것입니다.

 

 

2. 주요 메서드

1. 문자열 길이, 문자 추출

String s = "Hello";
System.out.println("길이: " + s.length());        // 5
System.out.println("첫 글자: " + s.charAt(0));    // H

 

2. 문자열 비교

String a = "Java";
String b = new String("Java");

System.out.println(a == b);        // false (주소 다름)
System.out.println(a.equals(b));  // true  (내용 같음)

 

3. 문자열 자르기와 대소문자 변경

String s = "HelloWorld";
// 0번째부터 5번째 직전까지
System.out.println(s.substring(0, 5));        // Hello
System.out.println(s.toUpperCase());          // HELLOWORLD
System.out.println(s.toLowerCase());          // helloworld

 

4. 문자열 포함 여부, 치환

String msg = "오늘은 자바를 공부하는 날입니다.";

System.out.println(msg.contains("자바"));           // true
System.out.println(msg.replace("자바", "파이썬"));   // 오늘은 파이썬을 공부하는 날입니다.

 

5. 문자열 나누기

String csv = "사과,바나나,딸기";
String[] fruits = csv.split(",");

for (String fruit : fruits) {
    System.out.println(fruit);
}

 

6. 문자열 합치기

String str = "hi";
str.concat(" there");  // 원본 문자열에는 변화 없음
System.out.println(str);  // "hi"

// 특정 변수에 따로 저장해야함
str = str.concat(" there");
System.out.println(str);  // "hi there"

 

'Server > Java' 카테고리의 다른 글

abstract  (0) 2025.05.21
Static  (1) 2025.05.20
접근 제한자  (0) 2025.05.20
상속  (2) 2025.05.19
클래스와 객체  (3) 2025.05.19

1. 접근 제한자

접근 제한자(Access Modifier)는 클래스, 변수, 메서드 등에 대해 다른 클래스나 객체가 접근할 수 있는 범위를 지정하는 키워드입니다. 대표적인 접근 제한자로는 public, protected, default(아무 것도 안 쓴 상태), private이 있으며, 각각의 제한자는 접근 허용 범위가 다릅니다. public은 어디서든 접근 가능하고, protected는 같은 패키지 또는 자식 클래스에서 접근 가능하며, default는 같은 패키지 내에서만 접근할 수 있고, private은 선언된 클래스 내부에서만 접근할 수 있습니다. 이를 통해 개발자는 코드의 캡슐화와 정보 은닉을 구현하고, 클래스 간의 불필요한 의존성을 줄일 수 있습니다.

 

접근 제한자의 종류와 범위

 

1. public

  • 어디서든 접근 가능
  • 클래스, 메서드, 필드 등에 사용 가능
public class Car {
    public String brand = "Hyundai";

    public void drive() {
        System.out.println("운전 중입니다.");
    }
}

 

2. private

  • 오직 같은 클래스 내에서만 접근 가능
  • 외부에서 직접 접근할 수 없음 → 정보 은닉에 필수
public class Account {
    private int balance = 1000;

    public int getBalance() {
        return balance;
    }
}

 

3. default (package-private, 아무 것도 안 씀)

  • 같은 패키지 내에서만 접근 가능
  • 키워드를 명시하지 않으면 default로 간주됨
class PackageExample {
    void show() {
        System.out.println("같은 패키지에서만 접근 가능");
    }
}

 

4. protected

  • 같은 패키지 또는 다른 패키지의 자식 클래스에서 접근 가능
public class Animal {
    protected void eat() {
        System.out.println("먹고 있어요");
    }
}

class Dog extends Animal {
    void barkAndEat() {
        eat(); // 부모의 protected 메서드 접근 가능
        System.out.println("멍멍!");
    }
}

 

많이 사용하는 예

 

// 같은 패키지 내의 클래스
public class Person {
    public String name = "김사과";       // 어디서든 접근 가능
    private int age = 20;             // 클래스 내부에서만 접근 가능
    protected String hobby = "코딩";  // 같은 패키지나 자식 클래스에서만
    String job = "개발자";            // default: 같은 패키지에서만

    public void showInfo() {
        System.out.println("이름: " + name);
        System.out.println("나이: " + age);
        System.out.println("취미: " + hobby);
        System.out.println("직업: " + job);
    }
}

// 같은 패키지 내의 다른 클래스
public class Test {
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(p.name);      // ✅ public → 접근 가능
        // System.out.println(p.age);   // ❌ private → 접근 불가
        System.out.println(p.hobby);     // ✅ protected → 같은 패키지 OK
        System.out.println(p.job);       // ✅ default → 같은 패키지 OK
    }
}

 

2. 객체지향 프로그래밍의 4가지 특징 ⭐⭐⭐

캡슐화(Encapsulation) 데이터(변수)와 기능(메서드)을 하나의 클래스로 묶고, 외부에서 직접 접근하지 못하도록 보호하는 것
(은닉성도 포함되어있음)
상속(Inheritance) 기존 클래스의 속성과 메서드를 새로운 클래스가 물려받아 재사용할 수 있는 것
다형성(Polymorphism) 하나의 메서드나 객체가 여러 형태로 동작할 수 있는 것 (오버라이딩, 오버로딩 포함)
추상화(Abstraction) 복잡한 내부 구현은 숨기고, 중요한 기능만 외부에 제공하는 것
(Ex. coolSms - 발송되는 내부 로직은 숨기고(가지고 있는 모듈을 감춘 채로) 사용자에게 제공)

 

1. 캡슐화

public class Account {
    private int balance = 0; // 외부에서 직접 접근 불가

    // public 메서드를 통해서만 접근 가능
    public void deposit(int amount) {
        if (amount > 0) balance += amount;
    }

    public int getBalance() {
        return balance;
    }
}

public class Main {
    public static void main(String[] args) {
        Account acc = new Account();
        acc.deposit(1000); // 안전한 방식으로 입금
        // acc.balance = 10000; // ❌ private이므로 직접 접근 불가
        System.out.println("잔액: " + acc.getBalance());
    }
}

 

2. 상속

class Animal {
    public void eat() {
        System.out.println("먹는다.");
    }
}

class Dog extends Animal {
    public void bark() {
        System.out.println("멍멍!");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();  // Animal 클래스에서 상속받은 메서드
        dog.bark(); // Dog 클래스의 메서드
    }
}

 

3. 다형성

class Animal {
    public void speak() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

class Cat extends Animal {
    @Override
    public void speak() {
        System.out.println("야옹~");
    }
}

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("멍멍!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal a1 = new Cat();  // 업캐스팅
        Animal a2 = new Dog();

        a1.speak(); // 야옹~
        a2.speak(); // 멍멍!
    }
}

 

4. 추상화

abstract class Vehicle {
    abstract void move(); // 구현은 하지 않음 (추상 메서드)

    public void start() {
        System.out.println("시동을 겁니다.");
    }
}

class Car extends Vehicle {
    @Override
    void move() {
        System.out.println("도로를 달립니다.");
    }
}

'Server > Java' 카테고리의 다른 글

Static  (1) 2025.05.20
String 클래스  (0) 2025.05.20
상속  (2) 2025.05.19
클래스와 객체  (3) 2025.05.19
메서드  (0) 2025.05.19

+ Recent posts