Post

java 공부 2024.05.16

자바 상속

자바 상속 이미지1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package today0516;

// RPGtest 클래스 정의
public class RPGtest {
    // main 메소드 정의: 프로그램의 실행 시작점
    public static void main(String[] args) {
        // Wizard 클래스의 객체 wizard 생성
        Wizard wizard = new Wizard();
        // wizard 객체의 speed 필드에 2 할당
        wizard.speed = 2;
        // wizard 객체의 move 메소드 호출
        wizard.move();
        
        // Knight 클래스의 객체 knight 생성
        Knight knight = new Knight();
        // knight 객체의 speed 필드에 3 할당
        knight.speed = 3;  
        // knight 객체의 move 메소드 호출
        knight.move();
    }
}

// Novice 클래스 정의: RPG 게임에서의 초보자 캐릭터를 나타내는 클래스
class Novice {
    // 초보자의 이름을 저장하는 필드
    String name;
    // 초보자의 체력을 저장하는 필드
    int hp;
    // 초보자의 이동 속도를 저장하는 필드
    int speed;
    
    // punch 메소드 정의: 펀치를 날리는 행동을 출력하는 메소드
    void punch() {
        // 이름과 체력을 포함한 펀치 액션 메시지 출력
        System.out.printf("%s(HP: %d)의 펀치!\n", name, hp);
    }
    
    // move 메소드 정의: 캐릭터가 이동하는 행동을 출력하는 메소드
    void move() {
        // 이동 속도를 포함한 이동 액션 메시지 출력
        System.out.printf("%d 만큼 이동합니다.\n", speed);
    }
}

// Wizard 클래스 정의: Novice 클래스를 상속받아 마법사 캐릭터를 나타내는 클래스
class Wizard extends Novice {
    // 마법사의 마력을 저장하는 필드
    int mp;
    
    // fireball 메소드 정의: 파이어볼을 사용하는 행동을 출력하는 메소드
    void fireball() {
        // 이름, 체력, 마력을 포함한 파이어볼 액션 메시지 출력
        System.out.printf("%s(HP: %d, MP : %d)의 파이어볼!\n", name, hp, mp);
    }
}

// Knight 클래스 정의: Novice 클래스를 상속받아 기사 캐릭터를 나타내는 클래스
class Knight extends Novice {
    // 기사의 스태미나를 저장하는 필드
    int stamina;
    
    // slash 메소드 정의: 슬래쉬 공격을 사용하는 행동을 출력하는 메소드
    void slash() {
        // 이름, 체력, 스태미나를 포함한 슬래쉬 액션 메시지 출력
        System.out.printf("%s(HP: %d, ST : %d)의 슬래쉬!\n", name, hp, stamina);
    }
}

업 캐스팅 & 메소드 오버라이딩

자바 상속 이미지2

자바 상속 이미지3

자바 상속 이미지4

자바 상속 이미지5

자바 상속 이미지6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// today0516 패키지 선언
package today0516;

// UpCastingTest 클래스 정의
public class UpCastingTest {
    // main 메소드 정의: 프로그램의 실행 시작점
    public static void main(String[] args) {
        // Sqaure 클래스의 인스턴스 생성 및 name 필드에 "정사각형"을 할당
        Sqaure s = new Sqaure();
        s.name = "정사각형";

        // Triangle 클래스의 인스턴스 생성 및 name 필드에 "삼각형"을 할당
        Triangle t = new Triangle();
        t.name = "삼각형";

        // Circle 클래스의 인스턴스 생성 및 name 필드에 "원"을 할당
        Circle c = new Circle();
        c.name = "원";
        
        // Shape 타입의 배열 shapes를 선언하고, s, t, c를 요소로 초기화
        Shape[] shapes = {s, t, c};

        // shapes 배열을 순회하며 각 도형의 이름을 출력
        for (int i = 0; i < shapes.length; i++) {
            System.out.printf("%d번 인덱스의 도형: %s\n", i , shapes[i].name);
        }
    }
}

// Shape 클래스 정의: 기본 도형을 나타내는 클래스
class Shape {
    // 도형의 이름을 저장하는 필드
    String name;
}

// Sqaure 클래스 정의: Shape 클래스를 상속받아 정사각형을 나타내는 클래스
class Sqaure extends Shape {}

// Triangle 클래스 정의: Shape 클래스를 상속받아 삼각형을 나타내는 클래스
class Triangle extends Shape {}

// Circle 클래스 정의: Shape 클래스를 상속받아 원을 나타내는 클래스
class Circle extends Shape {}

Shape 타입의 배열 shapes라는 표현은 Java에서 배열을 선언하고 초기화하는 구조를 설명하고 있습니다. 여기서 Shape 타입의 배열이라는 말은, 배열의 각 요소가 Shape 클래스의 인스턴스 (또는 Shape 클래스를 상속받는 서브클래스의 인스턴스)를 저장할 수 있음을 의미합니다. 즉, 이 배열은 Shape 객체를 저장하는 컨테이너 역할을 합니다.

자세한 설명

  1. 타입 Shape[]:
    • Shape[]Shape 클래스의 객체들을 요소로 가질 수 있는 배열을 의미합니다.
    • 배열의 각 요소는 Shape 타입, 즉 Shape 클래스의 인스턴스나 그 자식 클래스 (Sqaure, Triangle, Circle 등)의 인스턴스가 될 수 있습니다.
  2. 배열의 선언과 초기화:
    • Shape[] shapes = {s, t, c}; 이 부분에서 shapesShape 타입의 배열로 선언되고, 배열 리터럴을 사용하여 초기화됩니다.
    • {s, t, c}는 배열을 초기화하는 부분으로, s, t, c는 각각 Sqaure, Triangle, Circle 타입의 객체입니다.
  3. 업캐스팅 (Upcasting):
    • Sqaure, Triangle, Circle 객체들이 Shape 타입의 배열에 저장될 때, 자동으로 업캐스팅이 발생합니다.
    • 업캐스팅이란 서브클래스의 객체를 슈퍼클래스 타입으로 변환하는 것을 말합니다. 여기서 Sqaure, Triangle, Circle은 모두 Shape의 서브클래스이므로, Shape 타입으로 자동 변환될 수 있습니다.
  4. 다형성의 활용:
    • 이렇게 Shape 타입의 배열에 다양한 도형의 객체를 저장함으로써, 다형성을 활용할 수 있습니다.
    • 배열을 순회하며 동일한 인터페이스 (name 필드나 다른 메소드들)를 사용해 각 도형의 정보를 처리할 수 있게 됩니다.

코드 설명

1
2
3
4
5
6
7
8
// Shape 타입의 배열 shapes를 선언하고, s, t, c를 요소로 초기화
Shape[] shapes = {s, t, c};

// shapes 배열을 순회하며 각 도형의 이름을 출력
for (int i = 0; i < shapes.length; i++) {
    System.out.printf("%d번 인덱스의 도형: %s\n", i, shapes[i].name);
}

여기서 shapes 배열은 Shape 타입이므로, Shape 클래스 또는 Shape 클래스를 상속받는 어떤 클래스의 인스턴스도 요소로 포함할 수 있습니다. 이를 통해 코드의 유연성과 확장성이 증가하며, 여러 종류의 도형을 하나의 배열로 관리할 수 있게 됩니다.

자바 상속 이미지7

오버라이딩이 필요한 이유? :

오버라이딩(Overriding)은 객체지향 프로그래밍에서 매우 중요한 개념으로, 서브클래스가 슈퍼클래스로부터 상속받은 메소드를 자신의 상황에 맞게 재정의하는 행위입니다. 오버라이딩은 여러 이유로 필요하며, 프로그래밍에서 다음과 같은 역할을 합니다.

오버라이딩이 필요한 주요 이유

  1. 다형성 구현:
    • 오버라이딩은 다형성의 핵심적인 부분으로, 같은 메소드 호출에 대해 다양한 클래스의 객체가 다른 방식으로 반응할 수 있게 합니다.
    • 예를 들어, Shape 클래스에 draw() 메소드가 정의되어 있고, 이를 Circle, Square, Triangle 클래스에서 각각 다르게 오버라이딩하면, 각 도형을 그릴 때 각각의 도형에 맞는 그리기 로직을 실행할 수 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
     class Shape {
         void draw() {
             System.out.println("도형을 그립니다.");
         }
     }
        
     class Circle extends Shape {
         @Override
         void draw() {
             System.out.println("원을 그립니다.");
         }
     }
        
     class Square extends Shape {
         @Override
         void draw() {
             System.out.println("정사각형을 그립니다.");
         }
     }
        
     class Triangle extends Shape {
         @Override
         void draw() {
             System.out.println("삼각형을 그립니다.");
         }
     }
        
    
  2. 메소드 동작 재정의:
    • 오버라이딩을 통해 상속받은 클래스의 기능을 수정하거나 확장할 수 있습니다. 이는 상속받은 메소드가 서브클래스의 목적에 맞지 않을 때 유용합니다.
    • 예를 들어, 모든 동물이 소리를 낸다고 할 때, 각 동물의 소리는 다를 수 있습니다 (Dog 클래스에서는 “멍멍”, Cat 클래스에서는 “야옹”).
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
     class Animal {
         void sound() {
             System.out.println("동물이 소리를 냅니다.");
         }
     }
        
     class Dog extends Animal {
         @Override
         void sound() {
             System.out.println("멍멍");
         }
     }
        
     class Cat extends Animal {
         @Override
         void sound() {
             System.out.println("야옹");
         }
     }
        
    
  3. 인터페이스 구현:
    • 인터페이스의 모든 메소드는 기본적으로 추상 메소드입니다. 구현 클래스에서는 이 메소드들을 오버라이딩하여 구체적인 행동을 정의해야 합니다.
    • 이를 통해 다양한 구현이 가능한 일관된 프로그래밍 인터페이스를 제공할 수 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
     interface Movable {
         void move();
     }
        
     class Car implements Movable {
         @Override
         public void move() {
             System.out.println("자동차가 움직입니다.");
         }
     }
        
     class Person implements Movable {
         @Override
         public void move() {
             System.out.println("사람이 걸어갑니다.");
         }
     }
        
    
  4. 코드 유지보수성 향상:
    • 오버라이딩을 사용하면 기존 코드를 변경하지 않고도 새로운 기능을 추가하거나 기존 기능을 수정할 수 있습니다. 이는 코드의 유지보수성을 크게 향상시킵니다.
    • 예를 들어, 시스템에서 사용하는 로깅 메커니즘을 변경해야 할 때, 로깅 클래스의 메소드를 오버라이딩하여 새로운 로깅 방식을 적용할 수 있습니다.
  5. 서브클래스의 특화된 행동 정의:
    • 상속받은 일반적인 행동을 서브클래스에서 특화된 행동으로 재정의함으로써, 객체의 다양성을 보다 효과적으로 표현할 수 있습니다.
    • 각 클래스의 실제 역할에 맞게 동작을 조정하여, 더 정확하고 명확한 모델링이 가능해집니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// today0516 패키지 선언
package today0516;

// OverridingTest 클래스 정의
public class OverridingTest {
    // main 메소드 정의: 프로그램의 실행 시작점
    public static void main(String[] args) {
        // Archer 클래스의 인스턴스 생성
        Archer a = new Archer();
        // MasterArcher 인스턴스를 Archer 타입으로 캐스팅하여 생성
        // 주의: 여기서는 코드상 문맥 오류가 있습니다. MasterArcher를 생성하려면 new MasterArcher()만 사용해야 합니다.
        // Archer 타입의 ma 변수에 MasterArcher 인스턴스를 대입
        Archer ma = new MasterArcher();

        // Archer 인스턴스의 shoot 메소드 호출
        a.shoot();
        // MasterArcher 인스턴스의 shoot 메소드 호출 (Archer 타입으로 업캐스팅 되었으나, 오버라이딩된 메소드가 호출됩니다.)
        ma.shoot();
    }
}

// Archer 클래스 정의: 아처 캐릭터의 기본 행동을 정의
class Archer {
    // shoot 메소드 정의: 활을 쏘는 행동을 출력
    void shoot() {
        System.out.println("[아처]의 활 공격이 10만큼의 피해를 주었습니다.");
    }
}

// MasterArcher 클래스 정의: Archer 클래스를 상속받아 마스터 아처의 특화된 행동을 정의
class MasterArcher extends Archer {
    // shoot 메소드 오버라이딩: 활을 쏘는 행동을 마스터 아처에 맞게 재정의
    @Override
    void shoot() {
        System.out.println("[마스터 아처]의 활 공격이 30의 피해를 주었습니다.");
    }
}

캐스팅과 관련된 Java의 규칙

Java에서 캐스팅은 주로 두 가지 상황에서 사용:

  1. 업캐스팅 (Upcasting):
    • 서브클래스의 객체를 슈퍼클래스 타입으로 참조할 때 사용합니다.
    • 이 경우, 캐스팅은 자동으로 이루어지기 때문에 명시적으로 캐스팅을 할 필요가 없습니다.
    • 예: Archer a = new MasterArcher();는 자동으로 업캐스팅 됩니다.
  2. 다운캐스팅 (Downcasting):
    • 슈퍼클래스 타입의 참조가 실제로 가리키는 객체가 서브클래스 타입일 때, 그 참조를 서브클래스 타입으로 캐스팅합니다.
    • 이 경우 명시적으로 캐스팅을 해야 하며, 그렇지 않으면 컴파일 에러가 발생합니다.
    • 예: MasterArcher ma = (MasterArcher) a; 여기서 aArcher 타입이지만 실제로 MasterArcher 객체를 참조하고 있어야 합니다.
주어진 코드에서의 캐스팅

주어진 코드에서 Archer ma = (Archer) new MasterArcher();는 문법적으로는 전혀 문제가 없습니다. 하지만, 이 코드에서 (Archer) 캐스팅은 필요하지 않습니다. 왜냐하면 MasterArcher는 이미 Archer의 서브클래스이므로, 자동으로 Archer 타입으로 업캐스트 됩니다.

  • 원래 코드:

    1
    
      Archer ma = (Archer) new MasterArcher();
    
  • 캐스팅 없이도 동일하게 동작하는 코드:

    1
    
      Archer ma = new MasterArcher();
    
왜 명시적인 캐스팅을 피해야 할까요?
  1. 가독성: 명시적인 캐스팅은 코드의 가독성을 떨어트립니다. 특히 업캐스팅에서는 불필요한 캐스팅으로 코드가 더 복잡해 보일 수 있습니다.
  2. 유지보수: 불필요한 캐스팅은 나중에 코드를 수정할 때 혼란을 줄 수 있습니다. 다른 개발자가 코드를 보았을 때, 특별한 이유 없이 캐스팅이 사용된 이유를 이해하기 어려울 수 있습니다.
  3. 성능: 현대의 JIT 컴파일러는 매우 효율적이기 때문에 이러한 캐스팅이 성능에 큰 영향을 주지는 않지만, 불필요한 캐스팅은 이론적으로는 불필요한 CPU 사이클을 소모할 수 있습니다.
올바른 사용 예

올바르게 캐스팅을 사용하는 예는 다음과 같습니다.

  • 업캐스팅 (자동으로 일어남):

    1
    2
    
      Archer a = new MasterArcher();
        
    
  • 다운캐스팅 (명시적으로 필요):

    1
    2
    3
    4
    5
    6
    
      Archer a = new MasterArcher();
      if (a instanceof MasterArcher) {
          MasterArcher ma = (MasterArcher) a;
          ma.shoot();
      }
        
    
결론

Archer ma = (Archer) new MasterArcher();에서 (Archer)는 문맥상 오류는 아니지만, 불필요한 명시적 캐스팅입니다. 이는 코드의 의도를 명확하게 표현하는 데 있어서 좋은 습관이 아니며, 보통 생략하는 것이 좋습니다.

캐스팅 문제

캐스팅 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// today0516 패키지 선언
package today0516;

// AreaCalculator 클래스 정의
public class AreaCalculator {
    // main 메소드 정의: 프로그램의 실행 시작점
    public static void main(String[] args) {
        // Sqaure 클래스의 인스턴스 생성 및 필드 초기화
        Sqaure s = new Sqaure();
        s.name = "정사각형"; // 정사각형 이름 설정
        s.langth = 3; // 정사각형 한 변의 길이 설정

        // Triangle 클래스의 인스턴스 생성 및 필드 초기화
        Triangle t =  new Triangle();
        t.name = "삼각형"; // 삼각형 이름 설정
        t.base = 4; // 삼각형 밑변 길이 설정
        t.height = 4; // 삼각형 높이 설정

        // Circle 클래스의 인스턴스 생성 및 필드 초기화
        Circle c = new Circle();
        c.name = "원"; // 원 이름 설정
        c.radius = 4; // 원의 반지름 설정

        // Shape 타입의 배열 shapes를 선언하고, s, t, c를 요소로 초기화
        Shape[] shapes = {s, t, c};

        // shapes 배열을 순회하며 각 도형의 넓이를 출력
        for (int i = 0; i < shapes.length; i++) {
            System.out.printf("%s의 넓이: %.2f\n", shapes[i].name, shapes[i].area());
        }
    }
}

// Shape 클래스 정의: 기본 도형을 나타내는 클래스
class Shape {
    // 도형의 이름을 저장하는 필드
    String name;

    // 도형의 넓이를 계산하는 메소드
    // 기본 구현에서는 0을 반환
    double area() {
        return 0;
    }
}

// Sqaure 클래스 정의: Shape 클래스를 상속받아 정사각형을 나타내는 클래스
class Sqaure extends Shape {
    // 정사각형의 한 변의 길이를 저장하는 필드
    int langth;

    // 정사각형의 넓이를 계산하는 메소드
    // 넓이 = 한 변의 길이 * 한 변의 길이
    double area() {
        return langth * langth;
    }
}

// Triangle 클래스 정의: Shape 클래스를 상속받아 삼각형을 나타내는 클래스
class Triangle extends Shape {
    // 삼각형의 밑변 길이를 저장하는 필드
    int base;
    // 삼각형의 높이를 저장하는 필드
    int height;

    // 삼각형의 넓이를 계산하는 메소드
    // 넓이 = 0.5 * 밑변 * 높이
    double area() {
        return 0.5 * base * height;
    }
}

// Circle 클래스 정의: Shape 클래스를 상속받아 원을 나타내는 클래스
class Circle extends Shape {
    // 원의 반지름을 저장하는 필드
    int radius;

    // 원의 넓이를 계산하는 메소드
    // 넓이 = π * 반지름 * 반지름
    double area() {
        return Math.PI * radius * radius;
    }
}

생성자 호출

생성자호출 흐름

생성자호출 흐름1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// today0516 패키지 선언
package today0516;

// ConstructorWithInheritance 클래스 정의
public class ConstructorWithInheritance {
    // main 메소드 정의: 프로그램의 실행 시작점
    public static void main(String[] args) {
        // BBB 클래스의 인스턴스 생성
        new BBB();
    }
}

// AAA 클래스 정의
class AAA {
    // AAA 클래스의 생성자
    AAA() {
        // 생성자 호출 시 "AAA() 생성자 호출 완료"를 출력
        System.out.println("AAA() 생성자 호출 완료");
    }
}

// BBB 클래스 정의: AAA 클래스를 상속받음
class BBB extends AAA {
    // BBB 클래스의 생성자
    BBB() {
        // super(); // 슈퍼클래스(AAA)의 생성자를 호출하는 명령어, 생략 가능
        // 생성자 호출 시 "BBB() 생성자 호출 완료"를 출력
        System.out.println("BBB() 생성자 호출 완료");
    }
}

super

Java에서 super()는 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출할 때 사용됩니다. super()의 호출은 자식 클래스의 생성자 코드에서 가장 먼저 이루어져야 합니다. 이는 자바의 상속 규칙에 따른 것으로, 부모 클래스의 생성자를 호출하여 부모 객체의 초기화를 먼저 완료한 후에 자식 클래스의 필드나 메소드에 대한 초기화 작업이 이루어져야 하기 때문입니다.

올바른 super()의 사용 위치

다음은 super() 사용의 올바른 위치를 보여주는 예제입니다.

1
2
3
4
5
6
7
class BBB extends AAA {
    // 생성자
    BBB() {
        super(); // super()는 생성자의 첫 번째 실행문이어야 합니다.
        System.out.println("BBB() 생성자 호출 완료");
    }
}

위 코드에서 super(); 호출은 BBB() 생성자의 첫 번째 실행문으로 위치합니다. 이는 부모 클래스 AAA의 생성자를 호출하고, 그 후에 “BBB() 생성자 호출 완료”를 출력합니다.

잘못된 super()의 사용 위치

반면에, super()가 생성자의 첫 번째 실행문이 아닌 위치에 있을 경우, 컴파일러는 오류를 발생시킵니다. 예를 들어:

1
2
3
4
5
6
7
8
class BBB extends AAA {
    // 생성자
    BBB() {
        System.out.println("BBB() 생성자 호출 완료"); // 여기서 먼저 출력을 하려고 하면 오류 발생
        super(); // Error: Constructor call must be the first statement in a constructor
    }
}

위 코드 예에서 super();System.out.println("BBB() 생성자 호출 완료"); 라인 뒤에 오므로, 컴파일 오류가 발생합니다. 자바에서는 모든 클래스의 생성자가 부모 클래스의 생성자를 호출하여야 하며, 이 super(); 호출은 반드시 자식 클래스 생성자의 첫 번째 문장이어야 합니다.

super(); 생략

자식 클래스의 생성자에서 super();를 명시적으로 호출하지 않는 경우, 컴파일러는 자동으로 부모 클래스의 기본 생성자 super();를 자식 클래스 생성자의 첫 번째 문장으로 삽입합니다. 따라서, 아래 두 경우는 실질적으로 동일하게 작동합니다.

  1. super();를 명시적으로 적은 경우:

    1
    2
    3
    4
    5
    6
    7
    
     class BBB extends AAA {
         BBB() {
             super();
             System.out.println("BBB() 생성자 호출 완료");
         }
     }
        
    
  2. super();를 생략한 경우:

    1
    2
    3
    4
    5
    6
    7
    
     class BBB extends AAA {
         BBB() {
             // super(); 가 여기에 암묵적으로 삽입됩니다.
             System.out.println("BBB() 생성자 호출 완료");
         }
     }
        
    
결론

super();는 자식 클래스 생성자의 맨 처음에 위치해야 하며, 그렇지 않으면 컴파일 오류가 발생합니다. 부모 클래스의 생성자를 명시적으로 다른 위치에서 호출하는 것은 허용되지 않습니다. 대부분의 경우, super();는 생략되어도 좋으며, 생략할 경우 자동으로 생성자의 첫 줄에 삽입됩니다.

this

this 의미

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// today0516 패키지 선언
package today0516;

// MoreKeywords 클래스 정의
public class MoreKeywords {
    // main 메소드 정의: 프로그램의 실행 시작점
    public static void main(String[] args) {
        // Item 클래스의 인스턴스 생성
        // "마법사의 지팡이"라는 이름과 1000 골드라는 가격으로 item1 객체 초기화
        Item item1 = new Item("마법사의 지팡이", 1000);
        
        // item1의 이름과 가격을 포맷에 맞춰 출력
        System.out.printf("[%s](%d 골드) 생성 완료\n", item1.name, item1.price);
    }
}

// Item 클래스 정의
class Item {
    // 아이템의 이름을 저장하는 필드
    String name;
    // 아이템의 가격을 저장하는 필드
    int price;
    
    // Item 클래스의 생성자
    // name과 price 매개변수를 받아서 Item 객체를 초기화
    Item(String name, int price) {
        this.name = name; // this 키워드를 사용하여 인스턴스 필드 name을 초기화
        this.price = price; // this 키워드를 사용하여 인스턴스 필드 price를 초기화
    }
}

this 대신 super이 들어갈 수 있어서 this를 명시 해 주는 거임

protected & private 코드 비교

protected

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//MoreKeywords 클래스 정의
public class MoreKeywords {
 // main 메소드 정의: 프로그램의 실행 시작점
 public static void main(String[] args) {
     // Weapon 클래스의 인스턴스 생성
     // "장검"이라는 이름과 1200 골드라는 가격, 그리고 10의 공격력으로 w1 객체 초기화
     Weapon w1 = new Weapon("장검", 1200, 10);

     // Weapon 클래스의 또 다른 인스턴스 생성
     // 기본 생성자를 사용하여 기본값으로 w2 객체 초기화 ("이름없음", -1 가격, -1 공격력)
     Weapon w2 = new Weapon();

     // w1 객체의 정보 출력
     w1.printInfo();

     // w2 객체의 정보 출력
     w2.printInfo();
 }
}

//Item 클래스 정의 (추상 클래스)
abstract class Item {
 // 아이템의 이름을 저장하는 protected 필드
 protected String name;
 // 아이템의 가격을 저장하는 protected 필드
 protected int price;

 // Item 클래스의 생성자
 // name과 price 매개변수를 받아서 Item 객체를 초기화
 public Item(String name, int price) {
     this.name = name; // this 키워드를 사용하여 인스턴스 필드 name을 초기화
     this.price = price; // this 키워드를 사용하여 인스턴스 필드 price를 초기화
 }

 // Item 클래스의 기본 생성자 오버로딩
 // 기본값 "이름없음"과 -1로 Item 객체를 초기화
 public Item() {
     this.name = "이름없음";
     this.price = -1;
 }

 // getName 메소드: 아이템의 이름을 반환하는 메소드
 public String getName() {
     return this.name;
 }

 // getPrice 메소드: 아이템의 가격을 반환하는 메소드
 public int getPrice() {
     return this.price;
 }
}

//Weapon 클래스 정의: Item 클래스를 상속받음
class Weapon extends Item {
 // 무기의 공격력을 저장하는 protected 필드
 protected int power;

 // Weapon 클래스의 생성자
 // name, price, power 매개변수를 받아서 Weapon 객체를 초기화
 Weapon(String name, int price, int power) {
     super(name, price); // Item 클래스의 생성자 호출 (name, price 초기화)
     this.power = power; // this 키워드를 사용하여 인스턴스 필드 power를 초기화
 }

 // Weapon 클래스의 기본 생성자 오버로딩
 // 기본값으로 Weapon 객체를 초기화
 Weapon() {
     this("이름없음", -1, -1); // 이 생성자 내에서 다른 생성자 호출
 }

 // printInfo 메소드 정의: Weapon 객체의 정보를 출력
 void printInfo() {
     // 객체의 이름, 가격, 공격력을 출력
     System.out.printf("[%s] 가격: %d 골드, 공격력: %d\n", super.getName(), super.getPrice(), this.power);
 }
}

private

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// MoreKeywords 클래스 정의
public class MoreKeywords {
    // main 메소드 정의: 프로그램의 실행 시작점
    public static void main(String[] args) {
        // Weapon 클래스의 인스턴스 생성
        // "장검"이라는 이름과 1200 골드라는 가격, 그리고 10의 공격력으로 w1 객체 초기화
        Weapon w1 = new Weapon("장검", 1200, 10);

        // Weapon 클래스의 또 다른 인스턴스 생성
        // 기본 생성자를 사용하여 기본값으로 w2 객체 초기화 ("이름없음", -1 가격, -1 공격력)
        Weapon w2 = new Weapon();

        // w1 객체의 정보 출력
        w1.printInfo();

        // w2 객체의 정보 출력
        w2.printInfo();
    }
}

// Item 클래스 정의 (추상 클래스)
abstract class Item {
    // 아이템의 이름을 저장하는 private 필드
    private String name;
    // 아이템의 가격을 저장하는 private 필드
    private int price;

    // Item 클래스의 생성자
    // name과 price 매개변수를 받아서 Item 객체를 초기화
    public Item(String name, int price) {
        this.name = name; // this 키워드를 사용하여 인스턴스 필드 name을 초기화
        this.price = price; // this 키워드를 사용하여 인스턴스 필드 price를 초기화
    }

    // Item 클래스의 기본 생성자 오버로딩
    // 기본값 "이름없음"과 -1로 Item 객체를 초기화
    public Item() {
        this.name = "이름없음";
        this.price = -1;
    }

    // getName 메소드: 아이템의 이름을 반환
    public String getName() {
        return name;
    }

    // getPrice 메소드: 아이템의 가격을 반환
    public int getPrice() {
        return price;
    }
}

// Weapon 클래스 정의: Item 클래스를 상속받음
class Weapon extends Item {
    // 무기의 공격력을 저장하는 private 필드
    private int power;

    // Weapon 클래스의 생성자
    // name, price, power 매개변수를 받아서 Weapon 객체를 초기화
    Weapon(String name, int price, int power) {
        super(name, price); // Item 클래스의 생성자 호출 (name, price 초기화)
        this.power = power; // this 키워드를 사용하여 인스턴스 필드 power를 초기화
    }

    // Weapon 클래스의 기본 생성자 오버로딩
    // 기본값으로 Weapon 객체를 초기화
    Weapon() {
        super(); // Item 클래스의 기본 생성자 호출 ("이름없음", -1 초기화)
        this.power = -1; // 공격력을 -1로 초기화
    }

    // printInfo 메소드 정의: Weapon 객체의 정보를 출력
    void printInfo() {
        // 객체의 이름, 가격, 공격력을 출력
        System.out.printf("[%s] 가격: %d 골드, 공격력: %d\n", getName(), getPrice(), this.power);
    }
}
주요 차이점
  1. 필드의 접근 제한자:
    • 첫 번째 코드 스니펫:
      • Item 클래스에서 nameprice 필드가 protected로 선언되어 있습니다. 이는 같은 패키지 내부와 서브클래스에서 접근할 수 있음을 의미합니다.
      • Weapon 클래스의 power 필드 역시 protected로 선언되어 있습니다.
    • 두 번째 코드 스니펫:
      • Item 클래스에서 nameprice 필드가 private로 선언되어 있습니다. 이는 이 필드들을 Item 클래스 내부에서만 접근할 수 있게 제한합니다.
      • Weapon 클래스의 power 필드는 private으로 선언되어, 이 필드 역시 Weapon 클래스 내에서만 접근할 수 있습니다.
  2. 생성자 호출 방식:
    • 첫 번째 코드 스니펫:
      • Weapon 클래스의 기본 생성자에서 this("이름없음", -1, -1);를 사용하여 같은 클래스의 다른 생성자를 호출합니다. 이는 필드 name, price, power를 각각 "이름없음", 1, 1로 초기화합니다.
    • 두 번째 코드 스니펫:
      • Weapon 클래스의 기본 생성자에서 super();를 호출하여 부모 클래스(Item)의 기본 생성자를 호출하고, this.power = -1;를 사용하여 power 필드만 초기화합니다. 부모 클래스의 기본 생성자는 nameprice"이름없음", 1로 초기화합니다.
  3. 정보 출력 방식 (printInfo 메소드):
    • 첫 번째 코드 스니펫:
      • printInfo 메소드에서 super.getName(), super.getPrice()를 사용하여 부모 클래스의 getName(), getPrice() 메소드를 호출합니다. 이는 클래스 상속의 메소드 접근 방식을 보여줍니다.
    • 두 번째 코드 스니펫:
      • printInfo 메소드에서 getName(), getPrice()를 직접 호출합니다. private 필드에 대한 접근 제한으로 인해, Weapon 클래스에서 nameprice에 직접 접근할 수 없으므로 이와 같이 메소드를 사용합니다.
유사점
  • 두 스니펫 모두 MoreKeywords 클래스에서 main 메소드를 통해 프로그램 실행을 시작합니다.
  • Weapon 클래스는 두 버전 모두에서 Item 클래스를 상속받습니다.
  • 두 코드에서 Weapon 클래스의 인스턴스를 생성하고, printInfo 메소드를 사용하여 인스턴스의 정보를 출력합니다.
  • 각 클래스의 기능 (생성자, 메소드)는 구조적으로 유사하며, 기본적인 로직은 변경되지 않았습니다.
요약

첫 번째 코드 스니펫에서는 protected 접근 제한자를 사용하여 상속받은 클래스에서 더 유연하게 필드를 활용할 수 있는 반면, 두 번째 코드 스니펫에서는 private 접근 제한자를 사용하여 강력한 캡슐화를 유지하고 클래스 외부 또는 서브클래스에서의 직접 접근을 제한합니다. 생성자와 메소드 호출 방식에서도 차이를 보이며, 이는 객체의 초기화와 정보 은닉에 영향을 줍니다.

추상클래스

추상 클래스는 Java에서 클래스의 상속을 설계하는 데 사용되는 중요한 방법 중 하나입니다. 추상 클래스는 하나 이상의 추상 메소드를 포함하거나, 단순히 상속을 통해 기능을 확장하기 위해 사용될 수 있는 클래스입니다. 추상 메소드는 몸체가 없는 메소드로, 하위 클래스에서 반드시 구현(오버라이딩)되어야 합니다.

추상 클래스의 주요 특징

  1. 추상 메소드를 포함할 수 있음: 추상 클래스는 구현되지 않은 추상 메소드를 포함할 수 있습니다. 이러한 메소드는 선언만 있고 본체가 {}로 정의되지 않습니다.
  2. 인스턴스 생성 불가: 추상 클래스는 완전하지 않기 때문에 직접 인스턴스화할 수 없습니다. 즉, new 키워드를 사용하여 추상 클래스의 객체를 직접 생성할 수 없습니다.
  3. 상속을 통한 사용: 추상 클래스는 다른 클래스에서 상속하고 추상 메소드를 구현함으로써 사용됩니다. 이를 통해 코드의 재사용성과 확장성을 높일 수 있습니다.
  4. 추상 클래스에도 생성자가 있음: 추상 클래스에는 생성자가 있을 수 있으며, 이는 주로 상속받는 하위 클래스에서 호출됩니다.
  5. 구체적 메소드 포함 가능: 추상 클래스는 구체적인 메소드도 포함할 수 있으며, 이 메소드들은 하위 클래스에서 공통적으로 사용할 수 있습니다.

예제: 추상 클래스와 추상 메소드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// 추상 클래스 Shape 정의
abstract class Shape {
    String color;

    // 추상 클래스의 생성자
    Shape(String color) {
        this.color = color;
    }

    // 추상 메소드 area()
    abstract double area();

    // 추상 메소드 perimeter()
    abstract double perimeter();

    // 구체적인 메소드
    void displayColor() {
        System.out.println("Color: " + color);
    }
}

// Circle 클래스가 Shape 클래스를 상속받음
class Circle extends Shape {
    double radius;

    // Circle 클래스의 생성자
    Circle(String color, double radius) {
        super(color); // 상위 클래스의 생성자 호출
        this.radius = radius;
    }

    // area() 추상 메소드 구현
    @Override
    double area() {
        return Math.PI * radius * radius;
    }

    // perimeter() 추상 메소드 구현
    @Override
    double perimeter() {
        return 2 * Math.PI * radius;
    }
}

// Rectangle 클래스가 Shape 클래스를 상속받음
class Rectangle extends Shape {
    double width;
    double height;

    // Rectangle 클래스의 생성자
    Rectangle(String color, double width, double height) {
        super(color); // 상위 클래스의 생성자 호출
        this.width = width;
        this.height = height;
    }

    // area() 추상 메소드 구현
    @Override
    double area() {
        return width * height;
    }

    // perimeter() 추상 메소드 구현
    @Override
    double perimeter() {
        return 2 * (width + height);
    }
}

// CarTest 클래스에서 Shape의 인스턴스를 사용하는 예
public class ShapeTest {
    public static void main(String[] args) {
        Shape c = new Circle("Red", 2.5);
        Shape r = new Rectangle("Blue", 3.0, 4.0);

        System.out.println("Circle Area: " + c.area());
        System.out.println("Circle Perimeter: " + c.perimeter());
        c.displayColor();

        System.out.println("Rectangle Area: " + r.area());
        System.out.println("Rectangle Perimeter: " + r.perimeter());
        r.displayColor();
    }
}

예제: 추상 클래스 동물

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// today0516 패키지 선언
package today0516;

// AnimalTest 클래스 정의
public class AnimalTest {
    // main 메소드 정의: 프로그램의 실행 시작점
    public static void main(String[] args) {
        // Tiger 클래스의 인스턴스 생성
        Tiger t = new Tiger();
        // Lion 클래스의 인스턴스 생성
        Lion l = new Lion();
        // Dog 클래스의 인스턴스 생성
        Dog d = new Dog();
        // Cat 클래스의 인스턴스 생성
        Cat c = new Cat();
        
        // Animal 타입의 배열 animals를 선언하고, t, l, d, c를 요소로 초기화
        // 업 캐스팅
        Animal[] animals = {t, l, d, c};
        
        // animals 배열을 순회하며 각 동물의 cry 메소드를 호출
        for (int i = 0; i < animals.length; i++) {
            animals[i].cry();
        }
    }
}

// Animal 클래스 정의: 추상 클래스
abstract class Animal {
    // 동물의 이름을 저장하는 필드
    String name;
    
    // cry 메소드 정의: 동물이 우는 소리를 출력
    // 기본 구현에서는 "동물이 웁니다"를 출력
    void cry() {
        System.out.println("동물이 웁니다");
    }
}

// Tiger 클래스 정의: Animal 클래스를 상속받음
class Tiger extends Animal {
    // cry 메소드 오버라이딩: 호랑이 우는 소리 "어흥!"을 출력
    @Override
    void cry() {
        System.out.println("어흥!");
    }
}

// Lion 클래스 정의: Animal 클래스를 상속받음
class Lion extends Animal {
    // cry 메소드 오버라이딩: 사자 우는 소리 "크아앙!"을 출력
    @Override
    void cry() {
        System.out.println("크아앙!");
    }
}

// Dog 클래스 정의: Animal 클래스를 상속받음
class Dog extends Animal {
    // cry 메소드 오버라이딩: 개 짖는 소리 "멍멍!"을 출력
    @Override
    void cry() {
        System.out.println("멍멍!");
    }
}

// Cat 클래스 정의: Animal 클래스를 상속받음
class Cat extends Animal {
    // cry 메소드 오버라이딩: 고양이 우는 소리 "야옹~"을 출력
    @Override
    void cry() {
        System.out.println("야옹~");
    }
}

사용 시 고려사항

  • 추상 클래스 활용: 추상 클래스를 사용할 때는 하위 클래스에서 모든 추상 메소드를 구현해야 합니다. 만약 구현하지 않으면 하위 클래스도 추상 클래스가 됩니다.
  • 디자인과 문서화: 추상 클래스를 설계할 때는 해당 클래스와 메소드의 용도를 명확하게 문서화하는 것이 좋습니다. 이는 상속받는 클래스가 메소드를 올바르게 구현할 수 있도록 돕습니다.
  • 추상 클래스와 인터페이스 선택: 특정 메소드만 강제로 구현하게 하고 싶다면 인터페이스를, 일부 공통 구현을 제공하면서 상속을 통해 확장하고자 할 때는 추상 클래스를 사용합니다.

인터페이스

인터페이스(Interface) 개념

Java에서 인터페이스는 클래스와는 달리 객체의 인스턴스를 생성할 수 없으며, 다른 클래스가 구현할 수 있는 추상 메소드의 집합을 정의하는 구조입니다. 인터페이스를 사용하는 주된 목적은 구현과 정의를 분리하여, 다형성을 통한 유연하고 확장 가능한 코드 설계를 가능하게 하는 것입니다.

인터페이스의 주요 특징

  1. 추상 메소드 정의: 인터페이스에 선언된 모든 메소드는 기본적으로 추상 메소드입니다. 즉, 메소드 본체(body)가 없고, 세미콜론(;)으로 끝나는 메소드 시그니처만 존재합니다.
  2. 다중 구현 지원: 한 클래스가 여러 인터페이스를 구현할 수 있습니다. 이를 통해 Java의 단일 상속 한계를 극복하고, 다중 상속과 유사한 효과를 낼 수 있습니다.
  3. 디폴트 메소드와 정적 메소드 (Java 8 이상): Java 8부터 인터페이스는 디폴트 메소드와 정적 메소드를 포함할 수 있게 되었습니다. 디폴트 메소드는 default 키워드를 사용하여 본체를 포함하는 메소드를 정의할 수 있으며, 이를 통해 인터페이스를 구현하는 클래스에 영향을 주지 않고 새 기능을 추가할 수 있습니다. 정적 메소드는 static 키워드를 사용하여 정의되며, 인터페이스 이름으로 직접 호출됩니다.
  4. 상속: 인터페이스는 다른 인터페이스를 상속할 수 있으며, extends 키워드를 사용합니다. 다만, 클래스와 달리 다중 상속이 가능합니다.

인터페이스 사용 이유

  • 추상화와 유연성 제공: 인터페이스를 통해 구체적인 구현을 강제하지 않고, 다양한 구현을 가능하게 하여 코드의 유연성을 높입니다.
  • 다형성 구현: 인터페이스를 구현하는 모든 클래스는 인터페이스 타입으로 처리할 수 있어, 다형적인 프로그래밍이 가능합니다.
  • 계약 기반 설계: 인터페이스는 클래스가 외부와 소통하는 방법을 정의하는 계약과 같으며, 이를 통해 더 안정적이고 관리하기 쉬운 코드 베이스를 구축할 수 있습니다.

인터페이스의 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 인터페이스 정의 예시
interface Drivable {
    // 추상 메소드 정의
    void drive();
    void stop();
}

// 다중 인터페이스 정의 예시
interface Flyable {
    void fly();
}

// Car 클래스는 Drivable 인터페이스를 구현
class Car implements Drivable {
    public void drive() {
        System.out.println("The car is driving.");
    }

    public void stop() {
        System.out.println("The car has stopped.");
    }
}

// Airplane 클래스는 Drivable과 Flyable 인터페이스를 모두 구현
class Airplane implements Drivable, Flyable {
    public void drive() {
        System.out.println("The airplane is driving on the runway.");
    }

    public void fly() {
        System.out.println("The airplane is flying.");
    }

    public void stop() {
        System.out.println("The airplane has landed and stopped.");
    }
}

// Java 8 이상에서의 디폴트 메소드 사용 예시
interface Movable {
    void move();

    // 디폴트 메소드 정의
    default void start() {
        System.out.println("Starting the movement.");
    }

    // 정적 메소드 정의
    static void stop() {
        System.out.println("Stopping all movement.");
    }
}

class Animal implements Movable {
    public void move() {
        System.out.println("The animal is moving.");
    }
}

인터페이스 다중 구현(다형성)

인터페이스 다형성1

인터페이스 다형성2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package today0516;

// 프로그램의 메인 클래스입니다.
public class VariousImplementations {
    // main 메서드는 프로그램의 시작점입니다.
    public static void main(String[] args) {
        // SmartPhone 클래스의 인스턴스를 생성합니다.
        martPhone sp = new martPhone();
        // 음악 재생 기능을 테스트하기 위해 '상어송'을 재생합니다.
        sp.playMusic("상어송");
        // 전화 기능을 테스트하기 위해 '010-1234-1234' 번호로 전화를 걸어봅니다.
        sp.call("010-1234-1234");
        // 메시징 기능을 테스트하기 위해 '010-1234-1234' 번호로 "자니?" 메시지를 보냅니다.
        sp.sendMsg("010-1234-1234", "자니?");
    }
}

// Larm 인터페이스는 음악 재생과 비프음 재생 기능을 정의합니다.
interface Larm {
    void playMusic(String title); // 음악을 재생하는 메서드입니다.
    void beep(); // 비프음을 재생하는 메서드입니다.
}

// Phone 인터페이스는 전화 기능을 정의합니다.
interface Phone {
    void call(String phoneNum); // 전화를 거는 메서드입니다.
    void receive(); // 전화를 받는 메서드입니다.
}

// Messenger 인터페이스는 메시징 기능을 정의합니다.
interface Messenger {
    void sendMsg(String phoneNum, String msg); // 메시지를 보내는 메서드입니다.
    void receiveMsg(); // 메시지를 받는 메서드입니다.
}

// SmartPhone 클래스는 Larm, Phone, Messenger 인터페이스를 구현하여
// 음악 재생, 전화, 메시징 기능을 모두 포함합니다.
class martPhone implements Larm, Phone, Messenger {
    // 제목을 입력받아 음악을 재생합니다.
    public void playMusic(String title) {
        System.out.printf("[%s]이 재생됩니다 ..\n", title);
    }

    // 비프음을 재생합니다.
    public void beep() {
        System.out.println("[비프음]이 재생됩니다. 삐빕~ 삐비비빕! (x10)");
    }

    // 입력받은 전화번호로 전화를 걸어 연결을 시도합니다.
    public void call(String phoneNum) {
        System.out.printf("[%s]로 전화를 겁니다 ..rrrr\n", phoneNum);
    }

    // 전화가 걸려오면 전화를 받는 동작을 합니다.
    public void receive() {
        System.out.println("전화를 받습니다.");
    }

    // 입력받은 전화번호로 메시지를 보내고 메시지 내용을 출력합니다.
    public void sendMsg(String phoneNum, String msg) {
        System.out.printf("[%s]로 메시지 전송: %s\n", phoneNum, msg);
    }

    // 메시지가 도착하면 이를 알리는 메시지를 출력합니다.
    public void receiveMsg() {
        System.out.println("메시지가 도착하였습니다.");
    }
}

인터페이스 다형성3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package today0516;

// FlyWithInterface 클래스는 메인 메서드를 포함하여 프로그램의 실행 시작점을 제공합니다.
public class FlyWithInterface {
    // main 메서드에서 프로그램의 실행이 시작됩니다.
    public static void main(String[] args) {
        // Bird 클래스의 인스턴스 bird를 생성합니다.
        Bird bird = new Bird();
        // Helicopter 클래스의 인스턴스 copter를 생성합니다.
        Helicopter copter = new Helicopter();
        // Rocket 클래스의 인스턴스 rocket를 생성합니다.
        Rocket rocket = new Rocket();
        
        // Flyable 인터페이스를 구현하는 객체들의 배열 arr를 생성합니다.
        Flyable[] arr = {bird, copter, rocket};
        // arr 배열에 있는 모든 Flyable 객체들의 fly 메서드를 호출합니다.
        for (int i = 0; i < arr.length; i++) {
            arr[i].fly();
        }
    }
}

// Flyable 인터페이스는 날다(fly)라는 기능을 정의합니다.
interface Flyable {
    void fly(); // 날다라는 기능을 정의하는 메서드입니다.
}

// Bird 클래스는 Flyable 인터페이스를 구현합니다.
class Bird implements Flyable {
    @Override // fly 메서드를 오버라이드하여 구체적으로 구현합니다.
    public void fly() {
        // 새가 날아가는 동작을 출력합니다.
        System.out.println("[새]가 날개를 퍼덕이며 날아갑니다!");
    }
}

// Helicopter 클래스는 Flyable 인터페이스를 구현합니다.
class Helicopter implements Flyable {
    @Override // fly 메서드를 오버라이드하여 구체적으로 구현합니다.
    public void fly() {
        // 헬리콥터가 날아가는 동작을 출력합니다.
        System.out.println("[헬리콥터]가 날개를 퍼덕이며 날아갑니다!");
    }
}

// Rocket 클래스는 Flyable 인터페이스를 구현합니다.
class Rocket implements Flyable {
    @Override // fly 메서드를 오버라이드하여 구체적으로 구현합니다.
    public void fly() {
        // 로켓이 날아가는 동작을 출력합니다.
        System.out.println("[로켓]가 추진력을 사용하여 하늘을 날아갑니다!");
    }
}

같은 이름일때만 바로 객체 실행과 동시에 실행이 되는데 그 안의 메서드가 class의 이름과 다르면 바로 호출하지 않음

인터페이스 디폴트 메소드

인터페이스에 명시된건 무조건 사용해야함 디폴트로 명시된건 사용안해도됨

디폴트메소드

인터페이스 static 메소드

static 메소드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package today0516;

// StaticMethodInterface 클래스는 프로그램의 메인 클래스입니다.
public class StaticMethodInterface {
    // main 메서드에서 프로그램의 실행이 시작됩니다.
    public static void main(String[] args) {
        // Calculator 인터페이스의 정적 메서드 plus를 호출하여 3과 7을 더합니다.
        System.out.printf("3더하기 7은 %d 입니다.\n", Calculator.plus(3, 7));
        // Calculator 인터페이스의 정적 메서드 minus를 호출하여 4에서 6을 뺍니다.
        System.out.printf("4빼기 6은 %d 입니다.\n", Calculator.minus(4, 6));
    }
}

// Calculator 인터페이스는 계산기 관련 메서드를 정의합니다.
interface Calculator {
    // plus 정적 메서드는 두 정수 a와 b를 입력받아 그 합을 반환합니다.
    static int plus(int a, int b) {
        return a + b;
    }
    
    // minus 정적 메서드는 두 정수 a와 b를 입력받아 a에서 b를 뺀 결과를 반환합니다.
    static int minus(int a, int b) {
        return a - b;
    }
}

인터페이스 상수

인터페이스상수

This post is licensed under CC BY 4.0 by the author.