[Java 기본]Section 11, 12 - 다형성 활용1

우영·2024년 4월 12일

JAVA 기본편

목록 보기
11/13

다형성 활용1

다형성을 왜 사용하는지, 장점을 알아보기 위해 다형성을 사용하지 않고 프로그램을 개발한 다음에 다형성을 사용하도록 코드를 변경

package poly.ex1;

public class Caw {
    public void sound() {
        System.out.println("음메~");
    }
}
package poly.ex1;

public class Cat {
    public void sound() {
        System.out.println("야옹");
    }
}
package poly.ex1;

public class Dog {
    public void sound() {
        System.out.println("멍멍");
    }
}
package poly.ex1;

public class AnimalSoundMain {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();
        System.out.println("동물 소리 테스트 시작");
        dog.sound();
        System.out.println("동물 소리 테스트 종료");
        System.out.println("동물 소리 테스트 시작");
        cat.sound();
        System.out.println("동물 소리 테스트 종료");
        System.out.println("동물 소리 테스트 시작");
        caw.sound();
        System.out.println("동물 소리 테스트 종료");
    }

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

}

단순하게 개,고양이,소 울음소리를 출력하는 프로그램이다. 만약 새로운 동물이 추가되면 Caw를 생성하는 부분은 당연히 필요하니 크게 상관 없지만 Dog,Caw,Cat를 사용해서 출력하는 부분은 계속 중복이 늘어난다.

중복을 제거하기 위해서는 메서드를 사용하거나, 또는 배열과 for문을 사용하면 된다. 하지만 Dog,Cat,Caw는 서로 완전히 다른 클래스이다.

핵심 : 타입이 서로 다르다. 모두 같은 타입을 사용할 수 있다면 메서드와 배열을 활용해서 코드의 중복을 제거 가능

다형성참조와 메서드 오버라이딩을 쓰면 해결이 가능하다!


다형성 활용2

package poly.ex2;

public class Animal {
    public void sound() {
        System.out.println("동물 울음 소리");
    }
}
package poly.ex2;

public class Cat extends Animal{
    @Override
    public void sound() {
        System.out.println("야옹");
    }
}
package poly.ex2;

public class Caw extends Animal {
    @Override
    public void sound() {
        System.out.println("음메");
    }
}
package poly.ex2;

public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}
package poly.ex2;

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

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


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


핵심

  • 다형적 참조 덕분에 animal 변수는 자식인 Dog , Cat , Caw 의 인스턴스를 참조할 수 있다. (부모는 자식을 담을 수 있다)
  • 메서드 오버라이딩 덕분에 animal.sound() 를 호출해도 Dog.sound() , Cat.sound(), Caw.sound() 와 같이 각 인스턴스의 메서드를 호출할 수 있다. 만약 자바에 메서드 오버라이딩이 없었다면 모두 Animal 의 sound() 가 호출되었을 것이다


다형성 활용3

배열과 for문을 사용해서 중복 제거

package poly.ex2;

public class AnimalPolyMain2 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();
        Duck duck = new Duck();
        Animal[] animalArr = {dog, cat, caw};

        //변하지 없는 부분
        for (Animal animal : animalArr) {
            System.out.println("동물 소리 테스트 시작");
            animal.sound();
            System.out.println("동물 소리 테스트 종료");
        }
    }
}

  • 1) 배열은 같은 타입의 데이터를 나열할 수 있다.

    • Animal 타입의 배열을 만들고 다형적 참조를 사용
  • 2) 배열을 for문을 사용해서 반복 -> for each문(iter)

  • 3) 개선

    • 배열을 사용해서 코드의 간결하게 작성(ctrl + alt + N -> 변수 인라인)

variables inline = 이름이 원래의 표현식과 다를바가 없을 때는 불필요한 변수가 될 수 있고, 이런 변수는 주변 코드를 리팩터링하는데 방해가 될 수도 있으니 인라인시켜주는 것이 좋습니다.

  • 드래그한 부분을 외부 메소드로 만들어주는 extract 메소드(Ctrl + alt + m)를 활용해서 간결하고 최소화
package poly.ex2;

public class AnimalPolyMain3 {

    public static void main(String[] args) {

        
        Animal[] animalArr = {new Dog(), new Cat(), new Caw(),new Duck()};
        for (Animal animal : animalArr) {
            soundAnimal(animal);
        }
    }

    //변하지 없는 부분
    private static void soundAnimal(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }


}

추상 클래스

추상 클래스

- 개념: 동물과 같이 부모 클래스는 제공되지만, 실제 생성되면 안되는 클래스를 추상 크래스라 한다. 추상적인 개념을 제공하는 클래스이므로 인스턴스가 존재하지 않는다. 대신에 상속을 목적으로 사용하고 부모 클래스 역할을 담당한다.

- 사용 방법: 클래스를 선언할 때 abstract 키워드를 붙여준다.

아래의 2가지 문제를 한꺼번에 해결 할 수 있어서 추상 메서드를 사용

  • Animal 클래스를 생성할 수 있는 문제 = Animal 클래스는 다형성을 위해서 필요한 것이지 직접 인스턴스를 생성해서 사용할 일은 없다. 하지만 누군가 실수로 new animal()을 사용해서 인스턴스를 생성 할 수도 있다.
  • Animal 클래스를 상속 받는 곳에서 sound() 메서드 오버라이딩을 하지 않을 가능성 = pig 클래스를 만든다는 가정한다. 꿀꿀 소리를 나게 오버라이딩을 해야 하는데 실수로 빠뜨려 부모의 기능을 상속 받게 된다. 코드상으로는 문제가 없지만 좋은 프로그램은 제약이 있는 프로그램이기 때문에 해결 해야 한다.

추상 메서드

정의: 부모 클래스를 상속 받는 자식 클래스가 반드시 오버라이딩 해야 하는 메서드를 부모 클래스에 정의할 수 있는 것. 실체가 존재하지 않고 메서드 바디가 없다.

  • 선언 시 메서드 앞에 abstract 키워드를 붙여준다.
  • 추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야 한다.
    • 그렇지 않을 시 컴파일 오류 발생, 작동하지 않는 메서드를 사진 불완전한 클래스이므로 직접 생성하지 못하도록 추상 클래스로 선언해야 한다.
  • 추상 메서드는 상속 받는 자식 클래스가 반드시 오버라이딩 해서 사용해야 한다.
    • 그렇지 않을 시 컴파일 오류 발생, 추상 메서드는 자식 클래스가 반드시 오버라이딩 해야 하기 때문에 메서드 바디 부분이 없다.

정리: 추상 메서드는 기존 메서드와 완전히 같다. 다만 메서드 바디가 없고, 자식 클래스가 해당 메서드를 반드시 오버라이딩 해야 한다는 제약이 추가된 것.

package poly.ex3;

public abstract class AbstractAnimal {

        public abstract void sound();

        public void move(){
            System.out.println("동물이 움직입니다.");
        }
}
package poly.ex3;// dog 하나만 넣음 나머진 변수명과 이름만 다름

public class Dog extends AbstractAnimal {
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}
package poly.ex3;

public class AbstractMain {

    public static void main(String[] args) {
        //추상클래스 생성 불가
        //AbstractAnimal abstractAnimal = new AbstractAnimal();

        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();
        cat.sound();
        cat.move();
        soundAnimal(dog);
        soundAnimal(cat);
        soundAnimal(caw);
    }

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


}

추상 클래스2

순수 추상 클래스

: 모든 메서드가 추상 메서드인 추상 클래스

  • 앞서 만든 예제에서 move()도 추상 메서드로 만들어야 한다고 가정!
package poly.ex4;

public abstract class AbstractAnimal {

        public abstract void sound();

        public abstract void move();
}
package poly.ex4;

public class AbstractMain {

    public static void main(String[] args) {
        //추상클래스 생성 불가
        //AbstractAnimal abstractAnimal = new AbstractAnimal();

        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();
        cat.sound();
        cat.move();
        soundAnimal(dog);
        soundAnimal(cat);
        soundAnimal(caw);
        moveAnimal(dog);
        moveAnimal(cat);
        moveAnimal(caw);
    }

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

    private static void moveAnimal(AbstractAnimal animal) {
        System.out.println("동물 이동 테스트 시작");
        animal.move();
        System.out.println("동물 이동 테스트 종료");
    }

}
package poly.ex4; // dog 하나만 넣음 나머진 변수명과 이름만 다름

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

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

순수 추상 클래스

  • 순수 추상 클래스는 실행 로직을 전혀 가지고 있지 않다. 단지 다형성을 위한 부모 타입으로써 껍데기 역할만 할 뿐!

순수 추상 클래스의 개념은 매우 중요! 하지만 자바에선 더 편리한 인터페이스라는 개념을 더 자주 사용하고 이용!

인터페이스

class가 아닌 interface 키워드를 사용! 하지만 둘 다 .java파일!

package poly.ex5;

public interface InterfaceAnimal {
    void sound();//public abstract 생략 void sound 앞에
    void move();//public abstract 생략 void move 앞에 
}

추상 클래스와 같지만 편의기능이 추가!
- 인터페이스의 메서드는 모두 public, abstract
- 메서드에 public abstract를 생략 가능! 아니 거의 생략 권장!
- 인터페이스는 다중 구현(상속)을 지원한다.

인터페이스와 멤버 변수

public interface InterfaceAnimal {
 public static final double MY_PI = 3.14;
}

인터페이스에서 멤버 변수는 public, static, final이 모두 포함되었다고 간주!
자바에서 static final은 정적이면서 고칠 수 없는 상수, final은 변수의 값을 한번 설정하면 수정할 수 없다는 뜻!

  • 결국엔 생략된다는 말씀
package poly.ex5;

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

    @Override
    public void move() {
        System.out.println("개 이동");
    }
}
  • 인터페이스를 상속 받을 때는 extends 대신에 implements 라는 구현이라는 키워드를 사용!
package poly.ex5;

public class InterfaceMain {
    public static void main(String[] args) {
        //InterfaceAnimal interfaceAnimal = new InterfaceAnimal()
        // 인터페이스 생성 불가

        Cat cat = new Cat();
        Dog dog = new Dog();
        Caw caw = new Caw();

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

    }
    private static void soundAnimal(InterfaceAnimal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}
  • 순수 클래스 예제와 거의 유사하다. 이로써 개념은 같지만 좀 더 편리한 기능을 추가한 것이 인터페이스이다.

상속 vs 구현

클래스에서는 상속 받는다 표현, 인터페이스에선 구현 한다고 표현
상속은 부모의 기능을 물려받는 것이 목적, 인터페이스는 모든 메서드가 추상 메서드여서 물려받을 수 있는 기능이 없고, 오하려 인터페이스에 정의한 모든 메서드를 자식이 오버라이딩 해서 기능을 구현 해야 한다.

인터페이스를 사용해야 하는 이유

제약: 문제를 차단하고, 인터페이스의 메서드를 반드시 구현해라는 규약을 주는 것
다중구현: 자바에서 클래스 상속은 부모를 하나만 지정, 반면에 인터페이스는 부모를 여러명 두는 다중 구현(상속)이 가능!


인터페이스 - 다중 구현

package poly.diamond;

public interface interfaceA {
    void methodA();

    void methodCommon();

}
package poly.diamond;

public interface interfaceA {
    void methodA();

    void methodCommon();

}
package poly.diamond;

public class Child implements interfaceA, interfaceB{
    @Override
    public void methodA() {
        System.out.println("Child.methodA");
    }

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

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

methodCommon()의 경우 양쪽 인터페이스에 다 있지만 같은 메서드이므로 구현은 하나만 하면 된다.

package poly.diamond;

public class DiamondMain {

    public static void main(String[] args) {
        interfaceA a = new Child();
        a.methodA();
        a.methodCommon();
        interfaceB b = new Child();
        b.methodB();
        b.methodCommon();
    }
}


클래스와 인터페이스 활용

  • 클래스 상속과 인터페이스 구현을 함께 사용하는 예
package poly.ex6;

public abstract class AbstractAnimal {
    public abstract void sound();
    public void move() {
        System.out.println("동물이 이동합니다.");
    }
}
package poly.ex6;

public interface Fly {
    void fly();
}
package poly.ex6;

public class Dog extends AbstractAnimal {
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}
package poly.ex6;

public class Dog extends AbstractAnimal {
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}
package poly.ex6;

public class Bird extends AbstractAnimal implements Fly{
    @Override
    public void sound() {
        System.out.println("짹짹");
    }

    @Override
    public void fly() {
        System.out.println("새 날기");
    }
}

BirdAbstractAnimal 클래스를 상속하고 Fly 인터페이스를 구현한다.

package poly.ex6;

public class SoundFlyMain {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Bird bird = new Bird();
        Chicken chicken = new Chicken();

        soundAnimal(dog);
        soundAnimal(bird);
        soundAnimal(chicken);

        flyAnimal(bird);
        flyAnimal(chicken);
    }
    //변하지 않는 부분
    private static void soundAnimal(AbstractAnimal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }

    //Fly 인터페이스가 있으면 사용가능
    private static void flyAnimal(Fly fly) {
        System.out.println("날기 테스트 시작");
        fly.fly();
        System.out.println("날기 테스트 종료");
    }
}



0개의 댓글