[7/8] Java 기본 Summary ( w. 인프런 김영한)

차재현·2025년 1월 8일
0

[ Backend Study - Java ]

목록 보기
8/11
post-thumbnail

Section 11. 다형성 1

Chapter 1. 다형성 활용 1

  • 다형성을 활용하기 좋은 예제인 “동물 울음 소리” 코드를 활용해보자
  • 예시 코드 - 다형성 적용 전
    package poly.ex1;
    
    public class Dog 
        public void sound() {
            System.out.println("멍멍");
        }
    }
    package poly.ex1;
    
    public class Cat {
        public void sound() {
            System.out.println("냐옹");
        }
    }
    package poly.ex1;
    
    public class Caw {
        public void sound() {
            System.out.println("음매");
        }
    }
    
    package poly.ex1;
    
    import poly.ex2.Duck;
    
    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("동물 소리 테스트 종료");
            
            soundCat(cat);
            soundCaw(caw);
        }
    
        private static void soundCat(Cat cat) {
            System.out.println("동물 소리 테스트 시작");
            cat.sound();
            System.out.println("동물 소리 테스트 종료");
        }
    
        private static void soundCaw(Caw caw) {
            System.out.println("동물 소리 테스트 시작");
            caw.sound();
            System.out.println("동물 소리 테스트 종료");
        }
    }
    • 클래스가 각각 존재하는 상황이다. 🔥 클래스 타입이 다르다는 것이 문제의 핵심이다. 🔥
      • 출력문을 쓰려고 보니 코드가 중복된다.
      • 메서드로 만들자니, 매개변수를 이용하려해도 클래스가 달라 메서드 코드가 중복된다.

Chapter 2. 다형성 활용 2

  • 이때, Animal 이라는 부모 클래스를 만들어 dog, cat, caw를 상속하여 다형적 참조 & 메서드 오버라이딩을 활용한다면?
    • 예시 코드
      package poly.ex2;
      public class Animal { //부모 클래스
          public void sound() {
              System.out.println("동물 울음 소리");
          }
      }
      // dog, cat, caw, duck 모두 Animal을 상속받는다.
      package poly.ex2;
      
      public class Dog extends Animal { //Cat, Caw, Duck
          @Override
          public void sound() {
              System.out.println("멍멍"); // 옹, 음메, 꽉꽉
          }
      }
      
      package poly.ex2;
      
      public class AnimalPolyMain1 {
      
          public static void main(String[] args) {
              Dog dog = new Dog();
              Cat cat = new Cat();
              Caw caw = new Caw();
              Duck duck = new Duck();
      
              soundAnimal(dog);
              soundAnimal(cat);
              soundAnimal(caw);
              soundAnimal(duck);
          }
      
          private static void soundAnimal(Animal animal) {
              System.out.println("동물 소리 테스트 시작");
              animal.sound();
              System.out.println("동물 소리 테스트 종료");
          }
      }
      • Animal 클래스는 부모 클래스이기에 soundAnimal() 메서드의 매개변수로 dog, cat, caw, duck과 같은 자식 클래스 변수들을 모두 받을 수 있다.
  • 이젠 위 코드를 배열과 반복문을 활용하여 더욱 간소화해보자
    • 예시 코드
      package poly.ex2;
      
      public class AnimalPolyMain3 {
      
          public static void main(String[] args) {
      		Animal[] animalArr = {new Dog(), new Cat(), new Caw(), new Duck(), new Pig()};
              for (Animal animal : animalArr) {
                  soundAnimal(animal);
              }
          }
      
          //변하지 않는 부분
          private static void soundAnimal(Animal animal) {
              System.out.println("동물 소리 테스트 시작");
              animal.sound();
              System.out.println("동물 소리 테스트 종료");
          }
      }
      • Dog, Cat, Caw, Duck 모두 Animal의 자식 클래스이기에 Animal[] 에 넣을 수 있다.
      • 그리고 for문을 통해 Animal[]을 돌며 각 인스턴스의 sound() 메서드를 실행할 수 있다.
      • 이때도 sound() 메서드는 오버라이딩 되었기에 Animal의 sound() 메서드가 아닌, 각 자식 클래스의 sound() 메서드가 실행됨을 잊지 말자.

🔥 다형성을 이용하면 각자 다른 타입의 클래스지만 마치 하나의 타입인것 처럼 간편하게 사용할 수있다.
➕ 데이터를 추가해야 할 떄, 코드의 변화가 적을수록 좋은 코드이다

[ TIP 유용한 단축키 ]

  • command + option + N
    • 변수에 커서를 두고 사용하면 변수를 선언한 라인을 삭제하고, new ~ 코드를 바로 가져와 합친다.
    • 예시 코드
      // 단축키 사용 전
      Dog dog = new Dog();
      Dog puppy = dog; //dog에 커서를 두고 단축키 입력하면
      // 단축키 사용 후
      Dog puppy = new Dog(); // 맨 윗줄이 사라지고, dog에 new Dog()가 온다.
  • command + option + M
    • 특정 부분의 코드를 드래그하고 단축키를 사용하면 메서드로 추출해준다.

‼️ 동물 울음 소리 코드에서 발생할 수 있는 문제점 ‼️

  1. Animal 인스턴스를 생성하여 사용 가능한 점
    • Animal 인스턴스를 생성하고 사용하는데는 제약이 전혀 없다. (그냥 가능하다)
    • 하지만, Animal 클래스는 Dog, Cat, Caw, Duck와 같은 여러 클래스들의 부모 클래스로서 존재하는 것이지, Animal 클래스를 실제로 활용하기 위함은 아니다.
  2. 자식 클래스에서 메서드 오버라이딩을 하지 않을 수 있는 점
    • 만약 Pig 클래스를 추가하고 Animal 클래스를 상속받는다고 하자.
    • 이때, 개발자가 실수로 Pig에서 sound() 메서드를 오버라이딩 하지 않았다면,
      Pig.sound()는 부모 클래스인 Animal의 sound() 메서드를 호출하게된다.
    • 이것은 원래 코드 로직에서 의도치 않은 상황이다.

위의 다형성에서 발생할 수 있는 문제점을 한번에 해결할 수 있는 방법이 있다. 다음 Section에서 살펴보자

Section 12. 다형성 2

Chapter 1. 추상 클래스 1

  • 추상 클래스란?
    • 부모 클래스의 역할이지만, 실제로 인스턴스로 생성되서는 안되는 클래스이다.
      public abstract class AbstractAnimal {... }
      • Class 앞에 abstract를 붙인다.
      • 추상 클래스는 인스턴스로 생성하면 컴파일 에러가 발생한다.
      • 추상 클래스는 기존 클래스와 동일하다. 단지 인스턴스로 생성하지 못할 뿐이다.
  • 추상 메서드
    • 자식 클래스에서 무조건 오버라이딩 해야하는 부모 클래스의 메서드
      • 만약 자식 클래스에서 오버라이딩하지 않을 것이라면, 자식 클래스도 추상 클래스로 선언해야한다.
    • 추상적인 메서드이기에 메서드의 바디부분이 없다.
      public abstract class AbstractAnimal {
      
      	public abstract void sound();
      }
      • 메서드이지만 바디부분(중괄호 {} 부분)을 만들면 컴파일 에러가 발생한다
      • 추상 메서드가 하나라도 있는 클래스라면 무조건 추상 클래스로 선언해야한다.
        • 바디 부분이 없는 불완전한 메서드를 가지고 있기에, 인스턴스로 생성되어 추상 메서드를 실행하면 안된다.
        • 그래서 인스턴스로 생성되지 못하도록 추상 클래스로 선언하는 것이다.
      • 추상 메서드는 기존 메서드와 동일하다. 단지 바디부분이 없고, 자식 클래스에서는 해당 메서드를 반드시 오버라이딩해야 할 뿐이다.

Chapter 2. 추상 클래스 2

  • 추상 클래스를 활용하여 실습해보자
    package poly.ex4;
    
    public abstract class {
        public abstract void sound();
        public abstract void move();
        // 추상 메서드는 바디부분이 없다.
    }
    package poly.ex4;
    
    public class Cat extends AbstractAnimal { // Dog, Caw
        @Override
        public void sound() {
            System.out.println("냐옹"); // 멍멍, 음메
        }
    
        @Override
        public void move() {
            System.out.println("고양이 이동"); // 개 이동, 소 이동
        }
        // 만약 move()를 오버라이딩 하지 않는다면, Cat 클래스는 추상 클래스로 선언해야 한다.
        // public class Cat extends AbstractAnimal 라인에서 컴파일 에러 발생
    }
    package poly.ex4;
    
    public class AbstractMain {
    
        public static void main(String[] args) {
            //추상클래스 생성 불가
            //AbstractAnimal animal = new AbstractAnimal();
    
            Dog dog = new Dog();
            Cat cat = new Cat();
            Caw caw = new Caw();
    
            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("동물 이동 테스트 종료");
        }
    
    }
    • 위 코드처럼 AbstractAnimal 클래스는 추상 클래스이며, sound()와 move()라는 추상 메서드를 가지고 있다.
      • 순수 추상 클래스 : 메서드가 모두 추상 메서드인 추상 클래스
    • 추상 클래스는 인스턴스로서 생성될 수 없기에, 주석 처리된 코드처럼 선언한다면 컴파일 에러가 발생한다.
    • 따라서 자식 클래스인 Cat, Dog, Caw는 AbstractAnimal의 모든 메서드를 오버라이딩 해야 한다.

Chapter 3. 인터페이스

  • 순수 추상 클래스를 보고 있자니, 모든 메서드를 오버라이딩 하도록 강제하고있다. 마치 상속관계에서 모든 자식 클래스를 정해놓은 규격에 맞추어 선언하도록 하는 것 처럼 보인다.

💡 사실.. Java 에서는 “순수 추상 클래스”라는 말이 없다. 이를 대신하는 “인터페이스”가 있기 때문이다.

  • 복잡한 프로그램을 개발하게되면 앞으로 상황에 따라 멤버 변수와 멤버 메서드를 가지는 다양한 클래스들을 선언하고 이용할 것이다. 이전에 다룬 동물 울음 소리 코드처럼 다양한 클래스를 더욱 간편하게 사용하기 위해
    Java에서는 “인터페이스(순수 추상 클래스)”를 제공한다.

  • 인터페이스 UML 표현

    • 인터페이스는 점선으로 연결하며, 자식 → 부모 방향인 화살표로 표현한다.

    • 인터페이스는 “상속”관계 대신 “구현”관계로 칭한다.

    • “상속”이라 하면 부모에 특정 기능이나 변수가 있고, 자식이 물려받아야 하는데
      인터페이스의 경우, 껍데기만 있고 실제 작동 부분은 자식이 “구현”해야 하기 때문에
      ”구현”관계로 칭한다!

      상속과 구현은 사람이 표현하는 단어만 다를 뿐이지 자바 입장에서는 똑같다.
      일반 상속 구조와 동일하게 작동한다.

  • 예시 코드

    package poly.ex5;
    
    public interface InterfaceAnimal {
        void sound(); //public abstract
        void move();  //public abstract
    }
    • abstract class 대신 interface를 사용

    • 메서드 앞에 public abstract를 자동으로 넣어주기에 생략 가능

    • 추상 메서드가 아닌 메서드를 선언하면 컴파일 에러 발생

    • 멤버변수 선언 시, 인터페이스에서는 앞에 무조건 public static final을 자동으로 넣어주기에
      인터페이스에서는 상수로서만 멤버변수를 사용할 수 있다.

      package poly.ex5;
      
      public class Cat 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("동물 소리 테스트 종료");
          }
      }
    • 인터페이스는 사실상 순수 추상 클래스와 동일하다고 보면 된다. 키워드만 다를 뿐!

      • 추상 클래스이기에 인스턴스로 생성 불가능도 동일!
      • 구현(상속)관계이기에 부모 클래스는 자식 클래스를 품을 수 있는것도 동일! 그래서 soundAnimal 메서드의 매개변수처럼 타입을 통일하여 사용 가능도 동일!

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

  • 제약 : 인터페이스를 만드는 이유는 인터페이스를 구현하는 곳에서 인터페이스의 메서드를 반드시 구현해라는 규약(제약)을 주는 것이다. 그런데 순수 추상 클래스의 경우 미래에 누군가 그곳에 실행 가능한 메서드를 끼워 넣을 수 있다. 이렇게 되면 추가된 기능을 자식 클래스에서 구현하지 않을 수도 있고, 또 더는 순수 추상 클래스가 아니게 된다. 인터페이스는 모든 메서드가 추상 메서드이다. 따라서 이런 문제를 원천 차단할 수 있다. (위의 동물 울음 소리 코드의 문제점 2번을 원천 차단!)
  • 다중 구현 : 자바에서 클래스 상속은 부모를 하나만 지정할 수 있다. 반면에 인터페이스는 부모를 여러명 두는 다중 구현(다중 상속)이 가능하다.

Chapter 4. 인터페이스 - 다중 구현

  • Java가 다중 상속을 지원하지 않는 이유 : 다이아몬드 문제

    • 동일한 이름의 부모 메서드를 호출한다면, 어떤 부모의 메서드를 호출해야할지 모호해지는 문제
  • 인터페이스의 경우, 다중 구현을 지원한다.

    • 인터페이스에는 실제로 작동하는 메서드가 없고, 자식은 무조건 인터페이스의 모든 메서드를 오버라이딩 해야 한다.
    • 동일한 부모 메서드가 존재하더라도 오버라이딩이 되어있기에 무조건 자식 클래스의 메서드가 실행된다.
      • 만약 인터페이스 2개에 동일한 메서드(methodCommon)가 있어도 자식입장에선 한번만 오버라이딩 하면 된다.
  • 예시 코드

    package poly.diamond;
    
    public interface InterfaceA {
        void methodA();
        void methodCommon();
    }
    package poly.diamond;
    
    public interface InterfaceB {
        void methodB();
        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");
        }
    }
    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();
    
            Child c = new Child();
            c.methodCommon();
        }
    }
    • a.methodA(), a.methodCommon() 호출 시

      • 힙 영역에서 “호출자”의 타입인 InterfaceA의 인스턴스를 찾으러 간다.
      • methodA와 methodCommon이 오버라이딩 되어있음을 확인
      • Child 인스턴스에서 오버라이딩된 methodA와 methodCommon을 실행
    • b.methodB(), b.methodCommon() 호출 시

      • 힙 영역에서 “호출자”의 타입인 InterfaceB의 인스턴스를 찾으러 간다.
      • methodB와 methodCommon이 오버라이딩 되어있음을 확인
      • Child 인스턴스에서 오버라이딩된 methodB와 methodCommon을 실행
    • 다중 구현 시, 힙 영역 상태

      • 이미지처럼, 힙 영역의 Child 인스턴스 내부에 InterfaceAInterfaceB의 인스턴스가 모두 생성된다.
      • 메서드 호출 시 작동 흐름은 이전과 동일하다.

Chapter 5. 클래스와 인터페이스 활용

  • 클래스를 상속받으며 인터페이스도 구현하는 실습을 해보자
  • 클래스 상속/구현 관계도
  • 예시 코드
    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 Bird extends AbstractAnimal implements Fly {
        @Override
        public void sound() {
            System.out.println("짹짹");
        }
    
        @Override
        public void fly() {
            System.out.println("새 날기");
        }
    }
    package poly.ex6;
    
    public class Chicken extends AbstractAnimal implements Fly {
        @Override
        public void sound() {
            System.out.println("꼬기오");
        }
    
        @Override
        public void fly() {
            System.out.println("닭 날기");
        }
    }
    • 여기서 보면 상속을 위해 extends를 사용하고, 뒤에 이어서 구현을 위해 implements를 사용함
      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);
          }
      
          //AbstractAnimal 사용 가능
          //AbstractAnimal animal = bird;
          private static void soundAnimal(AbstractAnimal animal) {
              System.out.println("동물 소리 테스트 시작");
              animal.sound();
              System.out.println("동물 소리 테스트 종료");
          }
      
          //Fly 인터페이스가 있으면 사용 가능 (따라서 dog는 사용 불가능)
          //Fly fly = bird;
          private static void flyAnimal(Fly fly) {
              System.out.println("날기 테스트 시작");
              fly.fly();
              System.out.println("날기 테스트 종료");
          }
      }
    • 여기서 bird와 chicken은 AbstractAnimal과 Fly라는 두 부모 클래스를 둔 것으로 볼 수있다.
      따라서 soundAnimal에서는 AbstractAnimal로서, flyAnimal에서는 Fly로서 매개변수로 작동된다.

profile
Develop what? and why?

0개의 댓글