본문 바로가기

WINK-(Web & App)/JAVA 스터디

Java[2024-2 Java 스터디] 이민형 #4주차 (5-5장)

반응형

자바를 "자바라" (Java "java")

 

 

 

 

 

자바. 상속을 받다.

 

상속이 무슨 뜻인지 아는가?

상속은 뒤를 잇다 혹은 너거들이 아는 그 부모와 자식간의 상속도 있다.

 

자 자바에도 부모와 자식이 존재한다.

다음 코드를 보자.

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Dog extends Animal {  // Animal 클래스를 상속한다.
}

public class Sample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("poppy");
        System.out.println(dog.name);
    }
}

 

전에 한 번 만들었던 Animal 클래스이다.

여기선 부모 클래스가 Animal 클래스가 될 것이다.

 

class Dog extends Animal

 

이 코드는 새로 만든 Dog라는 클래스가 Animal 클래스를 상속하는 코드인데

여기서 Animal은 부모 클래스 Dog는 자식 클래스이다.

 

 

 

 

근데 부모 클래스로부터 상속 받은 자식 클래스는 부모와 같은 기능을 가지는데

굳이 굳이 부모 클래스를 만들어서 자식 클래스로 위와 같이 객체를 만들 필요가 있을까?

 

없지 없다 근데 보통 자식 클래스는 부모 클래스보다 더 많은 기능을 가지는 경우가 많다.

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    void sleep() {
        System.out.println(this.name+" zzz");
    }
}

public class Sample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("poppy");
        System.out.println(dog.name);
        dog.sleep();
    }
}

 

아까 만든 Dog 자식 클래스에 sleep이라는 메서드를 추가해줬다.

setName과 같은 부모 클래스의 메서드도 상속 받으면서

새로운 메서드까지 추가할 수 있다.

 

이렇게 부모 클래스의 기본 축을 위주로

자식 클래스들을 만들어 다양한 기능을 구현하고자 하는 게 주목적이다.

 

 

Dog는 Animal의 하위 개념이라고 할 수 있다. 그럼 Dog는 Animal이라고 할 수 있다.

이런 관계를 IS-A라고 한다 그니까 Dog IS-A Animal. 이거다

 

이렇게 IS-A 관계에 있는 자식 클래스의 객체는 부모 클래스의 자료형인 것처럼 사용할 수 있다.

Animal dog = new Dog();  // Dog is a Animal

이렇게 Dog 클래스의 객체는 Animal 클래스의 자료형인 것처럼 사용할 수 있다.

 

 

 

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    void sleep() {
        System.out.println(this.name + " zzz");
    }
}

class HouseDog extends Dog {
    void sleep() {
        System.out.println(this.name + " zzz in house");
    }
}

public class Sample {
    public static void main(String[] args) {
        HouseDog houseDog = new HouseDog();
        houseDog.setName("happy");
        houseDog.sleep();  // happy zzz in house 출력
    }
}

 

이렇게 Animal 클래스를 상속받은 Dog 클래스를 상속받은 HouseDog 클래스를 만들 수도 있다.

 

HouseDog의 sleep 메서드를 사용하면 그냥 Dog의 sleep메서드가 아니라

HouseDog의 sleep메서드가 우선순위를 갖게 된다.

 

 

이렇게 부모 클래스의 메서드를 자식 클래스가 동일한 형태로 또다시 구현하는 행위를 메서드 오버라이딩

이라고 한다~

 

 

 

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    void sleep() {
        System.out.println(this.name + " zzz");
    }
}

class HouseDog extends Dog {
    void sleep() {
        System.out.println(this.name + " zzz in house");
    }

    void sleep(int hour) {
        System.out.println(this.name + " zzz in house for " + hour + " hours");
    }
}

public class Sample {
    public static void main(String[] args) {
        HouseDog houseDog = new HouseDog();
        houseDog.setName("happy");
        houseDog.sleep();  // happy zzz in house 출력
        houseDog.sleep(3);  // happy zzz in house for 3 hours 출력
    }
}

위 코드를 보면 HouseDog클래스에 sleep이라는 메서드를 추가한 것을 볼 수 있다.

어? 근데 sleep 메서드는 아까 오바라이딩을 해서 하나 있지 않는가 저래도 되나... 싶다.

 

노노 괜찮다.

 

이미 sleep이라는 메서드가 있지만 동일한 이름의 sleep 메서드를 또 생성할 수 있는 이유는

메서드의 입력이 다를 경우만 가능하다.

새로 만든 sleep 메서드는 입력 항목으로 int 자료형이 추가되었다.

이를 메서드 오버로딩(method overloading)이라고 부른다.

 

 

 

생성자. 그의 권력

 

 

 

class HouseDog extends Dog {
    HouseDog(String name) {
        this.setName(name);
    }

    void sleep() {
        System.out.println(this.name + " zzz in house");
    }

    void sleep(int hour) {
        System.out.println(this.name + " zzz in house for " + hour + " hours");
    }
}

 

메서드 이름이 클래스 이름과 같고 리턴을 정의하지 않는 메서드를 생성자라고 하는데

여기선 HouseDog메서드가 생성자가 되겠다.

void도 사용하지 않는다.

 

이렇게 되면 새로운 변수를 만들 때 무조건 객체 변수의 값을 만들어야만하게 된다.

 

 

class Dog extends Animal {
    Dog() {
    }

    void sleep() {
        System.out.println(this.name + " zzz");
    }
}

Dog 생성자를 만들었는데 아무런 입력도 없고 아무런 기능도 없다.

이런 생성자를 디폴트 생성자라고 부른다.

 

그래 이게 우리가 아는 그 Dog dog = new Dog();

이거잖냐.

만약 클래스에 저런 생성자가 없다면 컴파일러는 자동으로 디폴트 생성자를 추가한다.

 

 

 

인터페이스의 페이스

 

 

 

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Tiger extends Animal {
}

class Lion extends Animal {
}

class ZooKeeper {
    void feed(Tiger tiger) {  // 호랑이가 오면 사과를 던져 준다.
        System.out.println("feed apple");
    }

    void feed(Lion lion) {  // 사자가 오면 바나나를 던져준다.
        System.out.println("feed banana");
    }
}

public class Sample {
    public static void main(String[] args) {
        ZooKeeper zooKeeper = new ZooKeeper();
        Tiger tiger = new Tiger();
        Lion lion = new Lion();
        zooKeeper.feed(tiger);  // feed apple 출력
        zooKeeper.feed(lion);  // feed banana 출력
    }
}

 

호랑이와 사자라는 Animal클래스를 상속받은 클래스를 만들어 주고

ZooKeeper의 메서드를 각 메서드마다 정의해준다.

메서드의 이름이 같긴한데 입력이 다르기 때문에 메서드의 오버로딩이다.

 

 

위 코드에서는 일일히 메서드를 추가를 해주었다.

근데 만약 동물의 수가 더 늘어난다면? 골치 아플 것이다.

 

 

interface Predator {
}

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

 

이와 같이 Predator 인터페이스를 추가해보자.

 

 

(... 생략 ...)

class Tiger extends Animal implements Predator {
}

class Lion extends Animal implements Predator {    
}

(... 생략 ...)

 

그리고 각 동물의 클래스를 생성하는 코드도 이렇게 바꾸어주자.

 

 

(... 생략 ...)

class ZooKeeper {
    void feed(Predator predator) {
        System.out.println("feed apple");
    }
}

(... 생략 ...)

 

그럼 이렇게만으로도 ZooKeeper의 메서드 작성이 끝나게 된다.

아아 자바란 참 아름다운 언어이다.
여기서도 IS-A 관계가 성립한다.

 

근데 저렇게 하면 문제가 있는게 

사자가 오든 호랑이가 오든 사과만 준다.....

 

 

interface Predator {
    String getFood();
}

(... 생략 ...)

인터페이스 안에 다음과 같은 몸통이 없는 메서드를 추가하면

getFood메서드는 이 인터페이스를 implements한 사자 호랑이들이 강제로 구현해야하는 규칙이 된다.

 

(... 생략 ...)

class Tiger extends Animal implements Predator {
    public String getFood() {
        return "apple";
    }
}

class Lion extends Animal implements Predator {
    public String getFood() {
        return "banana";
    }
}

(... 생략 ...)

이렇게 하면 메서드를 잘 사용할 수 있다.

 

 

다형성

 

 

객체지향 프로그래밍의 특징 중에서 다형성이 있다고 했었다.

class Bouncer {
    void barkAnimal(Animal animal) {
      if (animal instanceof Tiger) {
            System.out.println("어흥");
        } else if (animal instanceof Lion) {
            System.out.println("으르렁");
        } else if (animal instanceof Crocodile) {
          System.out.println("쩝쩝");
        } else if (animal instanceof Leopard) {
          System.out.println("캬옹");
        }
    }
}

 

정말 귀여운 코드이다.

animal instanceof Tiger 이 코드는 그냥 생긴대로

내가 받은 이 객체가 이 클래스의 인스턴스인가?

이 뜻이다.

 

그러나 이 같은 방식은 동물 클래스가 추가될 때마다 문장이 추가되어야 하므로 좋지 않다.

우리는 이미 인터페이스를 배웠으므로 나은 방법을 찾을 수 있다.

 

interface Predator {
    (... 생략 ...)
}

interface Barkable {
    void bark();
}

class Animal {
    (... 생략 ...)
}

class Tiger extends Animal implements Predator, Barkable {
    public String getFood() {
        return "apple";
    }

    public void bark() {
        System.out.println("어흥");
    }
}

class Lion extends Animal implements Predator, Barkable {
    public String getFood() {
        return "banana";
    }

    public void bark() {
        System.out.println("으르렁");
    }
}

class ZooKeeper {
    (... 생략 ...)
}

class Bouncer {
    void barkAnimal(Barkable animal) {  // Animal 대신 Barkable을 사용
        animal.bark();
    }
}

public class Sample {
    (... 생략 ...)
}

이렇게 인터페이스를 사용하여 수고를 덜 수 있다.

 

그리고 또 새로운 특징은 한번에 여러 인터페이스를 implements 할 수 있다는 것이다.

 

예제에서 사용한 tiger, lion 객체는 각각 Tiger, Lion 클래스의 객체이면서

Animal 클래스의 객체이기도 하고

Barkable과 Predator 인터페이스의 객체이기도 하다.

 

이러한 이유로 barkAnimal 메서드의 입력 자료형을 Animal에서 Barkable로 바꾸어 사용할 수 있는 것이다.

 

이렇게 하나의 객체가 여러 개의 자료형 타입을 가질 수 있는 것을 객체 지향 세계에서는 다형성이라고 한다.

 

 

 

추상 클래스는 과연 추상적일까?

 

 

추상 클래스는 인터페이스의 역할도 하면서 클래스의 기능도 가지고 있는 클래스이다. 

 

abstract class Predator extends Animal {
    abstract String getFood();

    void printFood() {  
        System.out.printf("my food is %s\n", getFood());
    }
}

(... 생략 ...)

 

추상 클래스를 만드려면 다음과 같이 abstract 키워드를 표기해야만한다.

 

한 인터페이스의 메서드와 같은 역할을 하는 메서드에도 역시 abstract를 붙여야 한다.

abstract 메서드는 인터페이스의 메서드와 마찬가지로 구현체가 없다.

즉, abstract 클래스를 상속하는 클래스에서 해당 abstract 메서드를 구현해야만 한다. 

 

 

추상 클래스는 일반 클래스와 달리 단독으로 객체를 생성할 수 없다.

반드시 추상 클래스를 상속한 실제 클래스를 통해서만 객체를 생성할 수 있다.

 

 

추상 클래스에는 abstract 메서드 외에 실제 메서드도 사용할 수 있다.

추상 클래스에 실제 메서드를 추가하면 Tiger, Lion 등으로 만들어진 객체에서 그 메서드들을 모두 사용할 수 있게 된다.

 

원래 인터페이스에서 default 메서드로 사용했던 printFood가 추상 클래스의 실제 메서드에 해당된다.

 

 

 

 

 

 

 

Quiz

 

 

 

 

1. 자바에서 한 클래스가 다른 클래스를 상속받을 때 사용하는 키워드는 무엇인가요?

답 : b) 

 

2. 상속을 통해 자식 클래스가 부모 클래스에서 얻을 수 있는 이점은 무엇인가요?

답 : a)

 

3. 다중 상속이 허용되지 않는 이유 중 하나는 무엇인가요?

답 : c)

 

4. 상속된 메서드를 자식 클래스에서 재정의하려면 어떤 키워드를 사용하나요?

답 : d) 

 

5. 자바에서 모든 클래스의 상위 클래스는 무엇인가요?

답 : a)

 

6. 생성자의 기본 역할은 무엇인가요?

답 : b) 

 

7. 생성자는 언제 호출되나요?

답 : b) 

 

8. 생성자를 오버로딩할 때 중요한 조건은 무엇인가요?

답 : b) 

 

9. 인터페이스의 특징으로 옳은 것은 무엇인가요?

답 : b) 

 

10. 인터페이스의 메서드 구현을 클래스에서 강제하기 위한 키워드는 무엇인가요?

답 : a) 

 

11. 다형성의 주요 특징은 무엇인가요?

답 : a) 

 

12. 오버라이딩과 오버로딩의 차이점으로 올바른 것은 무엇인가요?

답 : c) 

 

13. 추상 클래스의 주요 특징으로 옳은 것은 무엇인가요?

답 : c) 

 

14. 추상 클래스와 인터페이스의 공통점은 무엇인가요?

답 : a) 

 

15. 추상 메서드는 어떤 키워드를 사용하여 선언하나요?

c) 

 

16. 추상 클래스와 일반 클래스의 차이점을 설명하세요

답 : 추상 클래스는 인스턴스를 생성할 수 없고, 일반 클래스는 인스턴스를 생성할 수 있다.

 

17. 추상클래스와 인터페이스를 함께 사용하는 이유와 예를 설명하세요

답 : 추상 클래스는 기본 구현을 제공하고, 인터페이스는 다중 상속을 가능하게 하여 유연성을 높인다.

 

18. 추상 클래스를 상속받은 자식 클래스에서 추상 메서드를 구현해야 하는 이유를 설명하세요

답 : 자식 클래스가 추상 메서드를 구현하지 않으면, 자식 클래스도 추상 클래스가 되어 인스턴스를 생성할 수 없기 때문이다. 

 

19. 추상 클래스에서 선언된 일반 메서드와 추상 메서드의 차이점을 설명하세요.

답 : 일반 메서드는 구현이 있고, 추상 메서드는 구현이 없이 시그니처만 정의된다.

 

20. 상속과 구성의 차이점과 각각의 장단점을 비교하세요

답 : 상속은 "IS-A" 관계로, 재사용이 용이하지만 결합도가 높고, 구성은 "HAS-A" 관계로, 결합도가 낮고 유연하다.

 

21. 생성자에서 this와 super 키워드의 차이점에 대해 설명하세요

답 : this는 현재 클래스의 다른 생성자나 메서드를 호출하고, super는 부모 클래스의 생성자나 메서드를 호출한다.

 

22. 자바에서 인터페이스와 추상 클래스의 차이점을 설명하세요

답 : 인터페이스는 메서드 시그니처만 정의하고 다중 상속이 가능하며, 추상 클래스는 일부 구현을 제공하고 단일 상속만 가능하다.

 

23. 다형성의 개념과 객체지향 프로그래밍에서의 중요성을 설명하세요.

답 : 다형성은 같은 메시지로 다양한 객체를 처리할 수 있는 능력으로, 코드의 유연성과 재사용성을 높인다.

 

24. 메서드 오버로딩과 오버라이딩의 차이점을 비교하세요

답 : 오버로딩은 같은 이름의 메서드를 매개변수로 구분하고, 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 재정의한다.

 

25. 동적 바인딩의 개념을 설명하고 다형성과의 관계를 설명하세요

답 : 동적 바인딩은 메서드 호출 시점에 실행할 메서드를 결정하며, 다형성을 구현하는 중요한 기법이다.

 

[1~7] 아래 코드를 실행했을 때 출력되는 결과를 적으시오.

1. generic sound

 

2. Child

 

3. 10

 

4.

A's constructor

B's constructor

 

5.

A's no-arg constructor B's no-arg constructor

 

6. C's display

 

7. B

반응형