본문 바로가기
혼자 공부하는 자바

혼자 공부하는 자바 (40강 ~ 45강)

by moca7 2024. 5. 31.

40강. 상속(1) - 클래스 상속, 부모 생성자 호출

41강. 상속(2) - 메소드 재정의, final 클래스와 final 메소드

42강. 타입 변환과 다형성(1) - 자동 타입 변환

43강. 타입 변환과 다형성(2) - 필드의 다형성, 매개변수의 다형성

44강. 타입 변환과 다형성(3) - 강제 타입 변환, 객체 타입 확인

45강. 추상 클래스

 

 

 

 

40강. 상속(1) - 클래스 상속, 부모 생성자 호출

 

 

ㅁ 상속

- 상속: 부모 클래스의 멤버(필드, 메서드)를 자식 클래스에게 물려주는 것. 

- 이미 개발된 클래스를 재사용하여 새로운 클래스를 만들기에 중복되는 코드를 줄임.

- 부모 클래스의 한번의 수정으로 모든 자식 클래스까지 수정되는 효과가 있어 유지보수 시간이 줄어듦.

 

ㅁ 클래스 상속

- 자식 클래스 선언 시 부모 클래스를 선택

 

class 자식클래스 extends 부모 클래스 { ... }

 

- 여러 개의 부모 클래스를 상속할 수 없다. 

- 부모 클래스에서 private 접근 제한을 갖는 필드와 메서드는 상속 대상에서 제외.

- 부모와 자식 클래스가 다른 패키지에 존재할 경우 default 접근 제한된 필드와 메서드 역시 제외.

(sec01.exam01과 sec01.exam01.sumb은 그냥 다른 패키지다.)

 

 

ㅁ 예제

package sec01.exam01;

public class CellPhone {


     String model;
     String color;

     void powerOn() { System.out.println("전원을 켭니다."); }
     void powerOff() { System.out.println("전원을 끕니다."); }
     void bell() { System.out.println("벨이 울립니다."); }
     void sendVoice(String message) { System.out.println("자기: " + message); }
     void receiveVoice(String message) { System.out.println("상대방: " + message); }
     void hangUp() { System.out.println("전화를 끊습니다."); }


}

 

 

package sec01.exam01;

public class DmbCellPhone extends CellPhone {


      int channel;

      DmbCellPhone(String model, String color, int channel) {
          this.model = model;
          this.color = color;
          this.channel = channel;
      }

      void turnOnDmb() {
            System.out.println("채널 " + channel + "번 DMB 방송 수신을 시작합니다.");
      }

      void changeChannelDmb(int channel) {
            this.channel = channel;
            System.out.println("채널 " + channel + "번으로 바꿉니다.");
      }

      void turnOffDmb() {
            System.out.println("DMB 방송 수신을 멈춥니다.");
      }
}

 

 

package sec01.exam01;

public class DmbCellPhoneExample {

       public static void main(String[] args) {

              DmbCellPhone dmbCellPhone = new DmbCellPhone("자바폰", "검정", 10);

              System.out.println("모델: " + dmbCellPhone.model);
              System.out.println("색상: " + dmbCellPhone.color);
              System.out.println("채널: " + dmbCellPhone.channel);

              dmbCellPhone.powerOn();
              dmbCellPhone.powerOff();
              dmbCellPhone.sendVoice("여보세요");
              dmbCellPhone.receiveVoice("안녕하세요! 저는 홍길동인데요");
              dmbCellPhone.sendVoice("아~ 예 반갑습니다.");
              dmbCellPhone.hangUp();

              dmbCellPhone.turnOnDmb();
              dmbCellPhone.changeChannelDmb(12);
              dmbCellPhone.turnOffDmb();
       }
}

 

 

ㅁ 부모 생성자 호출

- 자식 객체를 생성할 때 부모 객체가 먼저 생성되고 그 다음 자식 객체가 생성됨.

- 생성자의 호출이 있어야 객체를 생성할 수 있다.

매개변수 없는 생성자면 기본으로 생성되고,

매개변수 있는 생성자를 만들면 매개변수 없는 생성자는 만들어지지 않으므로 그냥 new Car();하면 에러 발생함.

The constructor Car() is undefined.

- 그렇다는 말은 부모 객체의 생성자 호출도 있다는 말인데, 언제 호출되는가.

자식 생성자의 맨 첫 줄에서 부모 생성자가 호출된다.    //     super();

그런데 자식 생성자에서 부모 생성자를 명시하지 않으면 부모 생성자의 매개변수 없는 생성자가 첫 줄에 자동 호출됨. 

- 부모 생성자가 매개변수가 있는 생성자라면, 자식 생성자에 명시적으로 부모 생성자에 매개값을 넣어서 호출해줘야 함. 

 

ㅁ 상속

- 부모 클래스에 변수 2개                //          각각 인스턴스 필드를 가짐 + 생성자 매개값 2개.

- 자식 클래스에 변수 1개 + 부모 클래스의 변수 2개         //          각각 인스턴스 필드를 가짐 + 생성자 매개값 3개.

- 실행 클래스

 

 

 

 

41강. 상속(2) - 메소드 재정의, final 클래스와 final 메소드

 

 

ㅁ 메서드 재정의(오버라이딩 Overriding)

- 부모 클래스의 메서드가 자식 클래스에서 사용하기에 부적합할 경우 자식 클래스에서 수정하여 사용.

- 메서드가 재정의될 경우 부모 객체 메서드가 숨겨지며, 자식 객체에서 메서드 호출하면 재정의된 자식 메서드가 호출됨.

 

ㅁ 메서드 재정의 규칙

(1) 부모 메서드와 동일한 시그니쳐 가져야 함       //       부모에서 선언된 방식대로 자식에서 똑같이 선언해야 한다.

- 재정의하는 메서드는 부모 클래스의 메서드와 동일한 이름, 반환 타입 및 매개변수를 가져야 합니다.

(2) 접근 제한 더 강하게 재정의할 수 없음          //         접근제한자는 변경할 수 있어요. 그러나 더 좁은 범위로는 못바꿈.

- 예: 부모 클래스의 메서드가 protected일 경우, 자식 클래스의 재정의된 메서드는 protected나 public으로 선언할 수 있지만 private으로 선언할 수는 없습니다.

(3) 새로운 예외를 throws할 수 없음            //         새로운 예외를 발생시킬 수 없다. 

- 재정의하는 메서드는 부모 클래스의 메서드에서 던지는 예외와 동일하거나 그 예외의 하위 클래스만을 던질 수 있습니다. 새로운 예외를 추가로 던질 수는 없습니다.

 

ㅁ @Override

- 오버라이딩하려는 메서드 윗 줄에 써주기.

- 해당 메서드가 부모 클래스 또는 인터페이스의 메서드를 재정의하고 있음을 컴파일러에게 알릴 수 있습니다.

- 컴파일러 검사: 메서드가 실제로 부모 클래스나 인터페이스의 메서드를 올바르게 재정의하고 있는지 컴파일러가 확인합니다. 만약 메서드 시그니처가 잘못되었거나 부모 클래스에 존재하지 않는 메서드를 재정의하려고 한다면 컴파일 오류를 발생시킵니다.

 

 

ㅁ 부모 메서드 호출

- 메서드가 재정의되면 자식 객체 내부에서는 항상 재정의된 메서드가 호출되겠지만, 경우에 따라 자식 클래스 내부에서 재정의된 부모 클래스 메서드를 호출해야 하는 경우가 있다. (반드시 자식 객체 내부에서만 부모 메서드 호출 가능.)

- 명시적으로 super 키워드를 붙여 부모 메서드 호출. 

 

- super 키워드는 자식 클래스에서 부모 클래스의 멤버(필드, 메서드, 생성자)에 접근할 때 사용됩니다. 메서드 오버라이딩 시, 자식 클래스 내에서 부모 클래스의 메서드를 호출하려면 super.메서드이름() 형식으로 사용합니다.

 

 

- 부모 메서드의 내용에 자식 메서드의 내용을 추가로 붙이거나 어떤 때에는 부모 메서드, 어떤 때에는 자식 메서드를 호출해야할 때 많이 쓴다. 

 

 

ㅁ ctrl + 스페이스바 

- 자식객체에서 ctrl + 스페이스바를 누르면 목록이 나오는데 여기서 부모 메서드를 고르면 쉽게 오버라이딩할 수 있음.

 

 

ㅁ 모든 클래스는 부모 메서드가 될 수 있는가? 모든 메서드는 재정의될 수 있는가?

- 클래스를 상속하지 못하게 막을 수 있고, 메서드를 재정의 못하게 막을 수 있다.

- final 클래스와 final 메서드.

 

ㅁ final 키워드

- 해당 선언이 최종 상태이며 수정될 수 없음을 의미한다.

- 클래스 및 메서드 선언 시 final 키워드를 사용하면 상속과 관련됨.

- final 클래스는 상속할 수 없다. 부모 클래스가 될 수 없다. 

- final 메서드는 재정의될 수 없다. 부모 클래스에 선언된 final 메서드는 자식 클래스에서 재정의할 수 없다.

 

 

 

 

42강. 타입 변환과 다형성(1) - 자동 타입 변환

 

 

ㅁ 기본 타입과 마찬가지로 클래스도 타입 변환이 있다.

이를 활용하여 객체지향 프로그래밍의 다형성을 구현할 수 있다.

 

A a = new A();

B b = a;               //        A라는 객체를 생성해서 B타입에 대입. 자동 타입 변환. 

 

다형성

- 사용 방법은 동일하지만 다양한(여러) 객체를 활용해 여러 실행결과가 나오도록 하는 성질.

- 메서드 재정의타입 변환으로 구현.

 

 

ㅁ 자동 타입 변환(promotion)

- 프로그램 실행 도중 자동으로 타입 변환이 일어나는 것.

- 부모타입 변수를 선언하고 자식타입 객체를 부모타입 변수에 대입할 때 자동 타입 변환이 일어남.

- 조건이 반드시 부모와 자식이어야 함. 

 

부모타입 변수 = 자식타입;

Cat cat = new Cat();

Animal animal = cat;                     //               Animal animal = new Cat();도 가능.   

 

- 스택 영역에 cat, animal 변수가 생기고 둘 다 Cat을 참조함.         //        결국 번지 복사가 일어난 것.

힙 영역에서 Cat 객체는 Animal 객체를 상속받음. 

(Cat은 Animal을 상속받기 때문에 Cat을 생성할 때 Animal이 (먼저) 생성이 됨.) 

 

- 자동 타입 변환은 바로 위 부모가 아니더라도 상속 계층에서 상위 타입인 경우 자동 타입 변환이 일어날 수 있다.

상속 관계가 아니면 불가능. 

 

- 부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드 및 메서드만 접근이 가능하다.

예외적으로, 메서드가 자식 클래스에서 재정의될 경우에는 자식 클래스의 메서드가 대신 호출된다. 

(상속 관계에서, 부모 타입 변수에 자식 객체를 할당한 경우에, 부모 클래스의 메서드를 호출하는데 오버라이딩된 경우엔, 자식 클래스의 메서드가 호출된다.)

 

Child child = new Child();

Parent parent = child;

 

자식 클래스의 객체를 부모 클래스 타입 변수에 할당해도 실제 객체는 변하지 않습니다.

참조 변수의 타입이 변한다고 해서 객체 자체가 변하지 않습니다.

 

parent 변수는 Parent 타입이므로 Parent 클래스에 정의된 메서드와 필드에만 접근할 수 있습니다. 

하지만 실제로는 여전히 Child 객체를 가리키고 있습니다.

 

 

ㅁ 업캐스팅, 다운캐스팅

- 업캐스팅: 하위 클래스 객체를 상위 클래스 타입 변수에 할당하는 것입니다. 

- 다운캐스팅: 상위 클래스 타입 변수를 하위 클래스 타입으로 변환합니다.

 

Parent parent = new Child();        //          Child  타입 객체를 Parent 타입 변수에 할당 (업캐스팅)

Child child = (Child) parent;          //          Parent 타입 변수를 Child 타입으로 캐스팅 (다운캐스팅)

 

 

 

 

43강. 타입 변환과 다형성(2) - 필드의 다형성, 매개변수의 다형성

 

 

ㅁ 필드의 다형성

- 필드 타입을 부모 타입으로 선언할 경우.

다양한 자식 객체가 저장되어 필드 사용 결과가 달라질 수 있음. 

 

 

class Car {

    Tire frontLeftTire = new Tire();               //       4개의 필드

    Tire frontRightTire = new Tire();

    Tire backLeftTire = new Tire();

    Tire backRightTire = new Tire();

 

    void run() {

    frontLeftTire.roll();

    frontRighttTire.roll();

    backLeftTire.roll();

    backRighttTire.roll();

    }

}

 

Car myCar = new Car();

myCar.frontRightTire = new HankookTire();

myCar.backLeftTire = new KumhoTire();

myCar.run();

 

- 자동차 객체를 만들고 나서, 4개의 필드들에 다른 타이어 객체를 만들어서 대입할 수 있음. 

(HankookTire, KumhoTire와 Tire가 반드시 상속관계여야 함.)

(부모 타입 변수에 자식 객체 대입.)

 

- Tire 클래스에 roll() 메서드가 있는데, 자식 클래스에서 roll() 메서드를 재정의.

 

- 필드에 부모 타입을 선언해 놓고, 여기에 자식 객체를 대입해서, 오버라이딩된 메서드를 호출하면 다양한 실행결과가 나온다. 이게 필드의 다형성. 

 

 

ㅁ 매개 변수의 다형성

- 매개 변수(생성자나 메서드가 외부로부터 값을 저장하기 위해 사용하는 변수)를 부모 타입으로 선언하면

- 메서드 호출 시 매개값으로 부모 객체 및 모든 자식 객체를 제공할 수 있음.

- 자식의 재정의된 메서드가 호출 <- 다형성

 

 

 

 

- Vehicle이라고하는 매개 변수의 타입과 동일한 객체를 만들어서 대입하는 것이 일반적인 방법.

그러나 Vehicle을 상속받는 자식 객체가 있다면 자식 객체를 대입해도 된다는 것.

(자식 객체는 부모 타입으로 대입이 될 수 있으니까)

- 자식 객체에서 run() 메서드를 재정의한 경우 다양한 실행 결과가 나올 수 있다. 

(타입 변환 + 오버라이딩)

 

 

 

 

44강. 타입 변환과 다형성(3) - 강제 타입 변환, 객체 타입 확인

 

 

ㅁ 강제 타입 변환(Casting)                      //           Casting은 타입을 변환하는 것을 말함. 

- 부모 타입을 자식 타입으로 변환.

- 조건이 있음. 모든 부모 타입을 자식 타입으로 변환할 수 있는 것은 아님. 

자식 타입이 부모 타입으로 자동타입 변환한 후, 다시 반대로 변환할 때만 가능.

 

Parent parent = new Child();       //       자동 타입 변환

Child child = (Child) parent;        //        부모 타입 변환

 

- 업캐스팅 됐다가 다운캐스팅되면 그냥 처음부터 Child child = new Child(); 한 것과 똑같다. 

 

 

ㅁ 부모 클래스에 a라는 변수가 있고, 자식 클래스에도 a라는 클래스가 있음.

(1) 자식클래스 child = new 자식클래스();일 때 child.a; 하면?                //         자식 클래스의 a

(2) 부모클래스 parent = new 자식클래스();일 때 child.a; 하면?              //         부모 클래스의 a

 

- 변수의 유효범위, 하이딩은 왼쪽 클래스를 따라간다.

 

 

ㅁ instacneof 연산자

- 메서드 내 강제 타입 변환이 필요한 경우, 타입을 확인하지 않고 강제 타입 변환 시도 시 ClassCastException이 발생할 수 있음. (업캐스팅 후 다운캐스팅이 아니면 발생)

- instanceof 연산자 통해 확인 후 안전하게 실행

 

boolean result = 좌항(객체) instanceof 우항(타입)

 

 

- instanceof는 좌항의 객체가 우항의 타입인지 여부를 확인하는 연산자입니다.      //     맞다면 true, 틀리면 false.

- 부모 타입 변수에 자식 객체가 대입되어있는 경우에만, 다시 자식 타입으로 (명시적으로) 변환이 가능함.

 

 

 

 

45강. 추상 클래스

 

 

ㅁ 여러 클래스의 공통된 특성(필드, 메서드)를 추출해서 선언한 것을 추상 클래스라고 한다.

 

ㅁ 추상 클래스

- 실체 클래스(객체 생성용 클래스)들의 공통적인 특성(필드, 메서드)을 추출하여 선언한 것.

(사실은 추상 클래스 먼저 만드는게 일반적임)

- 추상 클래스와 실체 클래스는 부모, 자식 클래스로서 상속 관계를 가짐.

 

 

 

- 추상 클래스는 인스턴스화할 수 없는 클래스입니다.

- 대신, 다른 클래스가 이 클래스를 상속받아 구체적인 구현을 제공해야 합니다.

- 일반 메서드, 변수, 생성자를 가질 수 있다. 

 

 

ㅁ 추상 클래스의 용도

- 실체 클래스에 반드시 존재해야할 필드와 메서드를 선언함.

실체 클래스를 만들기 위한 설계 규격을 만드는 것. 객체 생성용이 아님.

- 실체 클래스에는 공통된 내용은 빠르게 물려받고, 다른 점만 선언하면 되므로 시간 절약.

 

ㅁ 추상 클래스에 반드시 추상 메서드가 있어야 하는 것은 아니지만, 추상 메서드가 있으면 반드시 추상 클래스로 선언해야 한다. 

 

ㅁ 추상 클래스 선언

- abstract 키워드

- 상속을 통해 자식 클래스만 만들 수 있게 만듦(부모로서의 역할만 수행 = 직접 객체를 만들 수 없다.)

 

public abstract class 클래스 {

    // 필드

    // 생성자

    // 메서드

}

 

- 추상 클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메서드 선언할 수 있다.

 

- 객체를 생성할 수 없는데 생성자가 있는 이유는?

직접 객체를 생성할 수 없지만  자식 객체를 생성할 때 객체화 됨.

객체로 만들어지려면 생성자가 반드시 있어야해서.

 

 

ㅁ 추상 메서드

- 메서드 선언만 통일하고 실행 내용은 실체 클래스마다 달라야 하는 경우

- abstract 키워드로 선언되고 중괄호가 없는 메서드

- 하위 클래스는 반드시 재정의해서 실행 내용을 채워야 함.

 

[public | protected] abstract 리턴타입 메서드이름(매개변수, ...);

 

public abstract class Animal {

    public abstract void sound();

}

 

- 추상 클래스에서만 선언할 수 있고, 메서드 선언부만 있고, 메서드 실행부(중괄호 블록)가 없는 메서드. 

일반 클래스는 추상 메서드를 가질 수 없음. 

 

- 추상 클래스를 상속받은 자식 클래스에서, 추상메서드를 재정의하지 않으면, 자식 클래스를 컴파일할 수 없음.

무조건 추상 메서드를 자식 클래스는 재정의를 해야 함. 

 

- 이렇게 사용하는 이유. 

추상 클래스에서 추상 메서드를 작성할 때 구체적으로 어떤 내용을 작성할지 상위 클래스인 추상 클래스에서 결정할 수 없을 때.

추상 클래스는 하위 클래스가 특정 메서드를 구현하도록 강제할 수 있습니다. 이는 코드 일관성을 유지하고, 하위 클래스에서 반드시 필요한 기능을 구현하도록 보장합니다.