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("동물 소리 테스트 종료");
}
}
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("동물 소리 테스트 종료");
}
}
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 dog = new Dog();
Dog puppy = dog; //dog에 커서를 두고 단축키 입력하면
// 단축키 사용 후
Dog puppy = new Dog(); // 맨 윗줄이 사라지고, dog에 new Dog()가 온다.
위의 다형성에서 발생할 수 있는 문제점을 한번에 해결할 수 있는 방법이 있다. 다음 Section에서 살펴보자
public abstract class AbstractAnimal {... }
public abstract class AbstractAnimal {
public abstract void sound();
}
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("동물 이동 테스트 종료");
}
}
💡 사실.. 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("동물 소리 테스트 종료");
}
}
인터페이스는 사실상 순수 추상 클래스와 동일하다고 보면 된다. 키워드만 다를 뿐!
💡 인터페이스를 사용해야 하는 이유
- 제약 : 인터페이스를 만드는 이유는 인터페이스를 구현하는 곳에서 인터페이스의 메서드를 반드시 구현해라는 규약(제약)을 주는 것이다. 그런데 순수 추상 클래스의 경우 미래에 누군가 그곳에 실행 가능한 메서드를 끼워 넣을 수 있다. 이렇게 되면 추가된 기능을 자식 클래스에서 구현하지 않을 수도 있고, 또 더는 순수 추상 클래스가 아니게 된다. 인터페이스는 모든 메서드가 추상 메서드이다. 따라서 이런 문제를 원천 차단할 수 있다. (위의 동물 울음 소리 코드의 문제점 2번을 원천 차단!)
- 다중 구현 : 자바에서 클래스 상속은 부모를 하나만 지정할 수 있다. 반면에 인터페이스는 부모를 여러명 두는 다중 구현(다중 상속)이 가능하다.
Java가 다중 상속을 지원하지 않는 이유 : 다이아몬드 문제
인터페이스의 경우, 다중 구현을 지원한다.
예시 코드
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()
호출 시
b.methodB()
, b.methodCommon()
호출 시
다중 구현 시, 힙 영역 상태
Child
인스턴스 내부에 InterfaceA
와 InterfaceB
의 인스턴스가 모두 생성된다.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("닭 날기");
}
}
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("날기 테스트 종료");
}
}