OOP - Inheritance, Constructor, Interface

5w31892p·2022년 12월 27일
0

Java

목록 보기
14/17

:: 상속 (Inheritance)

  • 자식이 부모 기능 그대로 물려 받는 기능
  • 클래스 상속은 extends
  • Animal을 상속했기 때문에 Dog에서 객체 변수와, setName메서드를 만들지 않아도 사용 가능
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);
    }
}

:: 부모 클래스 기능을 확장

  • 자식클래스는 부모클래스의 기능에 더하여 좀 더 많은 기능을 가질 수 있다.
  • Dog 클래스에 메서드 추가 가능
...
class Dog extends Animal {
    void sleep() {
        System.out.println(this.name+" zzz");
    }
}
...

:: IS-A 관계

  • Dog는 Animal의 하위 개념
  • 개는 동물이다 라고 표현 가능
  • Dog is a Animal
  • IS-A 관계 == 상속관계
  • 자식의 객체를 부모의 자료형인 것처럼 사용 가능
  • 하지만, Dog 객체를 Animal 자료형으로 사용할 경우 Dog 클래스에만 존재하는 메서드는 사용 할 수 없다.
  • 아래와 같은 경우 Animal 클래스에 구현된 setName 메서드만 사용 가능하다.
Animal dog = new Dog();
// 개로 만든 객체는 동물의 자료형이다.

Object 클래스

  • 모든 클래스는 object 클래스를 상속 받는다.
  • 굳이 상속하도록 하지 않아도 자동 상속 받는다.
  • 그러므로 모든 객체는 object의 자료형으로 사용 가능하다.
Object animal = new Animal();  // Animal is a Object
Object dog = new Dog();  // Dog is a Object

:: 메서드 오버라이딩 (Method overrriding)

  • Dog을 상속 받는 HouseDog 클래스가 있을 때, Dog에 있는 메서드를 수정, 변경하여 구현하고 실행이 가능하다.
  • 이때 Dog에 있는 sleep Method보다 HouseDog에 있는 sleep Method가 더 높은 우선 순위를 갖게 된다. (HouseDog의 sleep 호출)
  • 아래와 같이 자식이 부모 메서드를 동일한 형태로 또 다시 구현하는 행위를 메서드 오버라이딩이라고 한다.
  • 메서드 덮어쓰기
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");
    }
}

:: 메서드 오버로딩(Method overloading)

  • 입력항목이 다른 경우 동일한 이름의 메서드 만드는 것
  • 변경이 아닌 추가
  • sleep 메서드가 있지만 동일한 이름으로 또 성생 가능하다. (메서드 입력 항목이 다를 때만)
...
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");
    }
}
...

:: 다중 상속

  • 동시에 여러 클래스 상속 받는 것
  • 자바는 다중 상속 지원 안된다.

:: 생성자 (Constructor)

  • Animal에서 선언한 객체변수 name을 dog 객체의 name 변수에 아무런 값도 설정하지 않았기 때문에 null
  • name이라는 객체변수에 값을 무조건 설정해야만 객체 생성할 수 있도록 강제하는 방법은 Constructor
HouseDog dog = new HouseDog();
System.out.println(dog.name);

// null
  • 메서드명이 클래스명과 동일하고 리턴자료형을 정의하지 않는 메서드가 생성자이다.
class HouseDog extends Dog {
    HouseDog(String name) {
        this.setName(name);
    }
...
HouseDog dog = new HouseDog("뭉뭉이");

// 생성자 호출 시 문자열을 전달해야 한다.
// HouseDog dog = new HouseDog(); -> error

생성자 규칙
1. 클래스명과 메서드명 동일
2. 리턴 타입 정의 놉! (void도 놉!)

생성자 호출
new 클래스명 (입력인수, ...)

:: 디폴트(default) 생성자

  • 생성자의 입력항목이 없고, 생성자 내부에 아무 내용이 없는 것이 디폴트 생성자이다.
  • HouseDog이 name 입력 받는 생성자 만든 후에는 new HouseDog() 사용이 불가하다.
  • new HouseDog()이 가능하게 라하면 아래와 같이 기본 생성자를 수동으로 추가해야한다.
class HouseDog extends Dog {
	
    HouseDog() {
    }
    
    HouseDog(String name) {
        this.setName(name);
    }
...

:: 생성자 오버로딩

  • 하나의 클래스에 여러개의 입력항목이 다른 생성자 만들 수 있다.
  • 아래와 같이 입력 항목이 다른 생성자를 여러 개 만드는 것을 생성자 오버로딩이라고 한다.
...
class HouseDog extends Dog {
    HouseDog(String name) {
        this.setName(name);
    }

    HouseDog(int type) {
        if (type == 1) {
            this.setName("yorkshire");
        } else if (type == 2) {
            this.setName("bulldog");
        }
    }
...
HouseDog happy = new HouseDog("happy"); // 문자열로 생성
HouseDog yorkshire = new HouseDog(1); // 숫자로 생성

System.out.println(happy.name);  
// happy 출력

System.out.println(yorkshire.name); 
// yorkshire 출력
// new HouseDog(2)로 한다면 bulldog 출력

:: 인터페이스 (Interface)

  • 인터페이스는 일반 클래스와 달리 extends 이용해서 여러개의 인터페이스 동시 상속이 가능하다.
  • 다중 상속 지원

인터페이스가 필요한 이유

난 동물원의 사육사이다.
육식동물이 들어오면 난 먹이를 던져준다.
호랑이가 오면 사과를 던져준다.
사자가 오면 바나나를 던져준다.
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 출력
    }
}
  • 하지만 육식동물이 추가 될 때마다 feed 메서드를 추가해야할 때, 동물이 1억마리라면 끔찍하다.
  • 이럴 때 인터페이스의 도움이 필요하다.
  • 인터페이스 구현은 implements
  • tiger
    -> Tiger 클래스의 객체
    -> Predator 인터페이스의 객체
  • lion
    -> Lion 클래스의 객체
    -> Predator 인터페이스의 객체
interface Predator {
}
...
class Tiger extends Animal implements Predator {
}

class Lion extends Animal implements Predator {    
}
...
class ZooKeeper {
    void feed(Predator predator) {
        System.out.println("feed apple"); // 항상 apple 출력
    }
}
...

:: 인터페이스의 메서드

  • 인터페이스의 메서드는 메서드명과 입출력에 대한 정의만 있고, 내용은 없다.
  • 내용은 인터페이스를 implements한 클래스들이 구현해야한다.
  • 인터페이스 메서드의 구현은 항상 public으로
interface Predator {
    String getFood();
}
...
class Tiger extends Animal implements Predator {
    public String getFood() { // 구현은 public
        return "apple";
    }
}

class Lion extends Animal implements Predator {
    public String getFood() { // 구현은 public
        return "banana";
    }
}
...
class ZooKeeper {
    void feed(Predator predator) {
        System.out.println("feed "+predator.getFood()); 
        // 각 클래스에 구현된 내용이 출력됨
    }
}
...

:: 인터페이스 핵심과 개념

  • 중요한 점은 메서드의 갯수가 줄어든 것이 아니다.
  • ZooKeeper 클래스가 동물들의 종류에 의존적인 클래스에서 상관 없는 독립적인 클래스가 되었다는 것이 중요하다.
  • 이것이 바로 인터페이스 핵심

상속을 통해서 사용해도 동일한 효과

Animal 클래스에 getFood 메서드 추가 하고,
Tiger, Lion 등에서 getFood 오버라이딩 한 후,
Zookeeper의 feed 메서드가 Animal의 자료형으로 사용

하지만, 상속을 통해서 한다면 해당 메서드를 반드시 구현해야 한다는 강제성을 갖지 못한다.

이처럼 인터페이스는 인터페이스의 메서드를 반드시 구현해야 하는 강제성을 갖는다

:: 디폴트 메서드

  • 인터페이스 메서드는 구현체를 가질 수 없지만 디폴트 메서드를 사용하면 실제 구현된 형태의 메서드 가질 수 있다.
  • 가장 앞에 default라고 표기해야 한다.
  • 각 동물의 클래스에서 printFood 메서드 구현하지 않아도 사용이 가능하다.
interface Predator {
    String getFood();

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

:: 스태틱 메서드

  • 인터페이스에 스태틱 메서드 구현하면 일반 클래스의 스태틱 메서드를 사용하는 것과 동일하게 사용 가능
  • 인터페이스명.스태틱메서드명
    • Predator.speed();
  • 인터페이스에 정의한 상수는 public static final을 생략해도 자동 적용된다.
    • 아래와 같은 상수 정의만 가능
interface Predator {
...
    int LEG_COUNT = 4;  // 인터페이스 상수

    static int speed() {
        return LEG_COUNT * 30;
    }
}

0개의 댓글