Java/[inflearn] 자바 기본

[김영한_자바기본][11. 다형성2] 다형성 사용 이유 및, 순수 추상 클래스(p.1-20)

줌인. 2024. 2. 13. 16:09
1. 다형적 참조 / 메서드 오버라이딩을 통해 각자 다른 타입을 모두 동일화하여 중복을 제거할 수 있다.
2. 추상 클래스 : 상속을 목적으로 사용되고, 부모 클래스 역할을 담당, 인스턴스 존재X
3. 추상 메서드 : 반드시 오버라이딩 해야하는 메서드
4. 인터페이스 : 순수 추상클래스와 동일하며, 적절한 제약과 다중구현이 가능하다.
5. 인터페이스와 클래스가 같이사용될때, extends 후 implements를 사용해라

6. 추가적으로 객체를 생성하고 배열에 바로 담는 것은 가능

 

 

 

 

*다형성을 사용하는 이유 

- 다향적 참조 / 메서드 오버라이딩을 통해 각자 다른 타입(클래스)를 모두 같은 타입화하여 중복을 제거할 수 있다.

  1) 부모 클래스를 생성 방지 대비 - 추상 클래스 사용

  2) 부모 클래스를 상속받는 곳에서 특정 기능을 오버라이딩을 누락할 문제 대비 - 추상 매서드 사용

 

[p.5 / AinmalPolyMain] 다형성 사용을 위한 상속 관계 코드 사용, 부모는 자식을 담을 수 있다.

public class AnimalSoundMain {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();

        soundAnimal(dog);
        soundAnimal(cat);
        soundAnimal(caw);
    }

    //동물이 추가되어도 변하지 않는 코드
    public static void soundAnimal(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

- 매서드 부분을 보면 매개변수에 Animal 타입의 변수만 들어갈 수 있음을 확인할 수 있다.

- 매개변수에 dog가 들어간다는 것은 Animal animal = new Dog(); 같은 의미이다.

- 즉 매서드 오버로딩을 통해 dog의 오버로딩에 접근하고, animal은 자식의 인스턴스를 참조할 수 있다.

 

ⓛ 다형성 + 메서드 이용

[p. 8 / AnimalPolyMain2] 배열 + for문 ⇒ 상위 부모 변수에 자식 변수를 담은 후 반복 작업한다.

public class AnimalPolyMain2 {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();
        Animal[] animalArr = {dog, cat, caw}; //상위 상속 변수에 변수를 담을 수 있다.

        for (Animal animal : animalArr) {
            System.out.println("동물 소리 테스트 시작");
            animal.sound();
            System.out.println("동물 소리 테스트 종료");
        }
    }
}

 

② 다형성 + 메서드 + 배열 이용

[p. 8 / AnimalPolyMain2] 배열 + for문 ⇒ 상위 부모 변수에 자식 변수를 담은 후 반복 작업한다.

public class AnimalPolyMain3 {

    public static void main(String[] args) {
        Animal[] animalArr = {new Dog(), new Cat(), new Caw()}; 
        
        for (Animal animal : animalArr) {
            animalSound(animal);
        }
    }
    
    public static void animalSound(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

 

[p. 8 / AnimalPolyMain3] 배열 + for문 + 메서드 ⇒ 배열에 객채 생성을 담은 후 반복 작업한다.

public class AnimalPolyMain3 {

    public static void main(String[] args) {
        Animal[] animalArr = {new Dog(), new Cat(), new Caw()};

        for (Animal animal : animalArr) {
            animalSound(animal);
        }
    }

    public static void animalSound(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

- Animal이라는 추상적인 부모를 참조한다.

- 변하는 부분을 최소화하는 것이 잘 작성한 코드이다.


 

객체를 생성하고 배열에 바로 담는 것은 가능하다.
Animal[] animalArr = {new Dog(), new Cat(), new Caw()};

 

- 각각 Dog, Cat, Caw 클래스의 객체를 생성하고, 이를 배열에 담고 있다.
- 이는 객체를 생성하고 그 결과를 배열에 저장하는 것을 한 번에 수행는 방법이다.

- 만약 이후에 이 객체들을 변경할 필요가 없고, 한 번 사용하고 마는 경우에 유용하다.


 

[p.5 / Animal]

public class Animal {

    public void sound() {
            System.out.println("동물 울음 소리");
    }
}

 

[p.5 / Cat]

public class Cat extends Animal {

    @Override
    public void sound() {
        System.out.println("야옹");
    }
}

 

 


1) 타입이 같을 경우 중복 제거 방법

 ① 메서드 

 ② 배열, For문 사용

 

2) 반면 타입이 다를 경우 중복 제거 방법을 사용한다면

 ① 메서드 : 타입(클래스)이 다름에 따라 호출이 불가하다.

public class AnimalSoundMain1 {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();

       soundAnimal(caw);
        //soundAnimal(cat); //type(Class가)이 달라서 호출이 불가함.
        //soundAnimal(dog); //type(Class가)이 달라서 호출이 불가함.

    }

    public static void soundAnimal(Caw caw) {
        System.out.println("동물 소리 테스트 시작");
        caw.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

  

 ② 배열, for : 타입(클래스)가 다름에 따라 같은 배열에 넣기 어렵다.

public class AnimalSoundMain2 {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();
        Caw[] caws = {dog, cat, caw}; //type이 다름(Class 다름)

        System.out.println("동물 소리 테스트 시작");
        for (Caw caw : caws) {
            caw.sound();
        }
        System.out.println("동물 소리 테스트 종료");
    }
}

 

추상 클래스 : 상속을 목적으로 사용되고, 부모 클래스 역할을 담당, 인스턴스 존재X
추상 메서드 : 반드시 오버라이딩 해야하는 메서드
즉 각 제약들이 추가된 것으로 이해하자

1) 추상 클래스 사용 - 부모 클래스를 생성할 수 있는 문제 대비

public abstract class AbstractAnimal

- 직접 인스턴스를 추가하지 못하는 제약이 추가된 것

- class 선언시, abstract라는 키워드를 붙여주면 된다.


Animal animal = new Animal();

- 다형성을 위해 필요한 기능이지 실제 animal 인스턴스를 사용할 일은 거의 없다.


 

2) 추상 메서드 사용 - 매서드 오버라이딩 누락 문제 대비

public abstract void sound();

- 매서드 선언시, abstract라는 키워드를 붙여주면 된다.

- 추상 메서드가 하나라도 있는 클래스는 추상 클래스를 선언해야한다.

- 메서드 바디가 없음에 따라 불완전한 클래스로 볼 수 있으므로, 직접 생성하지 못하도록 선언해야한다.

- 상속받는 자식 클래스가 반드시 오버라이딩 해야한다.

순수 추상 클래스(모든 메서드가 추상적), 모든 메서드를 구현해야 한다.
1) 인스턴스를 생성할 수 없다.

2) 주로 다형성을 위해 사용된다.
3) 상속시 자식은 모든 메서드를 오버라이딩 해야한다.

 

 

3) 인터페이스 사용 - 다중 구현을 지원하며 순수 추상클래스와 같다.

public interface InterfaceAnimal {
	void sound();
	void move();
}

- class가 아닌 interface 키워드 사용

- void 앞에 public abstract 키워드 생략 가능

인터페이스는 앞서 설명한 순수 추상 클래스와 같지만, 더 나아가 적절한 제약을 준다.
또한 다중 구현을 지원하며,
인터페이스에서 멤버변수는 public/static/final이 포함되었다고 간주한다.

 

 

[p.24 / interfaceMain] 순수 추상 클래스와 동일하게 진행

public class InterfaceMain {

    public static void main(String[] args) {
        //InterfaceAnimal animal = new InterfaceAnimal();
        Cat cat = new Cat();
        Caw caw = new Caw();
        Dog dog = new Dog();

        soundAnimal(cat);
        soundAnimal(dog);
        soundAnimal(caw);
    }

    private static void soundAnimal(InterfaceAnimal animal) {
        System.out.println("동물 울음 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 울음 소리 테스트 종료");
    }

}

- 클래스 기능 자체를 상속받는 것이 아닌 내가 직접 세부 내용을 설계하는 것을 구현이라고 한다.

 

[p.23 / interfaceAnimal] Interface생성 

public interface InterfaceAnimal {
    void sound();
    void move();
}

 

[p.23 / Dog] 구현을 위해 implements 사용 : 부모 타입 여러개 사용 가능 

public class Dog implements InterfaceAnimal{

    @Override
    public void sound() {
        System.out.println("멍멍");
    }

    @Override
    public void move() {
        System.out.println("개 이동");
    }
}

 

 

*다중구현

[p.27 / interfaceA] 다중 구현 예시

public interface interfaceA {

    void methodA();
    void methodCommon();
}

 

[p.27 / interfaceB]

public interface interfaceB {

    void methodB();
    void methodCommon();
}

 

[p. 27 / Child] 다중 구현 A, B

public class Child implements InterfaceA, InterfaceB {

    @Override
    public void methodA() {
        System.out.println("Child. methodA");
    }

    @Override
    public void methodB() {
        System.out.println("Child. methodA");
    }

    @Override
    public void methodCommon() {
        System.out.println("Child. methodCommon");
    }
}

 

[p.28 / Main]

public static void main(String[] args) {
    InterfaceA a = new Child();
    a.methodA();
    a.methodCommon();

    InterfaceB b = new Child();
    b.methodB();
    b.methodCommon();
}

 

 

 

▶ 상속과 메모리 구조 이해

https://zoooom-in.tistory.com/61

 

[김영한_자바기본][10. 다형성1] 상속과 메모리 구조(p.1-23)

1. 자식 객체를 부모 참조 변수에 할당하면 부모 메서드에 접근이 가능하다. 2. 다운캐스팅은 '일시적으로' 부모 타입의 참조 변수를 자식 타입으로 형변환하는 것이다. 3. 다운 캐스팅 수행 전 ins

zoooom-in.tistory.com

 

728x90