[java] 상속과 추상화, 인터페이스 정리

LOST& FOUND·2024년 5월 26일
post-thumbnail

이제 나도 자유롭게 설계할 수 있다?! 호호

생각없이 classType.method(); 쓰던 사람 it's me~ yeah

  1. 상속
  2. 추상클래스
  3. 인터페이스

1. 상속

상속 먼저 얘기해보자.

상속은 부모클래스로부터 메서드와 필드를 내려받는다.


부모클래스가 존재할 때 extends를 사용하여 부모클래스의 자식이 될 수 있다. 자식클래스가 태어나는 순간은 부모클래스를 선택해서 태어날 수 있다.

class 부모클래스 {
 	public int wheatAmount;
 	
 	부모클래스(int w) {
    	this.wheatAmount = w;
    }
    public void sayHello() {
        System.out.println("안녕! 나는 부모클래스 밀가루호빵이야");
    }
    public void showAmounts() {
        System.out.println("wheatAmount: " + wheatAmount);
    }



또한 부모클래스의 메서드를 @overriding(메서드 재정의) 하여 메서드를 다른 로직으로 변경할 수 있다.
메서드재정의를 하면 부모객체의 메서드는 숨겨지기 때문에 자식객체에서 매서드를 호출하면 오버라이딩된 자식 메서드가 호출된다.


🪬 주의 !

  • 부모 메서드와 동일한 리턴타입, 메서드 이름, 매개변수 리스트를 가져야 한다.
  • 접근 제한을 더 강하게 오버라이딩 할 수 없다.
  • 새로운 예외를 throws 할 수 없다.

class 자식클래스 extends 부모클래스 {

 	public int pizzaAmount
    public int tomatoAmount
    
    자식클래스(int pa, int twa, int w) {
    	super(w); //부모클래스를 호출!
    	this.pizzaAmount= pa;
        this.tomatoAmount= twa;
    }

	  @Override
    public void sayHello() {
        System.out.println("안녕! 나는 자식클래스 피자호빵이야");
    }

    public void showAmounts() {
    	super.showAmounts(); //부모클래스의 메서드 호출하기
        System.out.println("Pizza Amount: " + pizzaAmount);
        System.out.println("Tomato Amount: " + tomatoAmount);
    }
}

메인함수에서 sayHello() 와 showAmounts() 를 돌려보면 다음과 같이 나온다.

public class Main {
    public static void main(String[] args) {
        자식클래스 child = new 자식클래스(10, 20, 5);
     	//새로운 생성자 생성하면서 파라미터 입력

        child.sayHello();
        //안녕! 나는 자식클래스 피자호빵이야
       
        child.showAmounts();
         //Pizza Amount: 10, Tomato Amount: 20, wheat Amount: 5
    }




🪬 메서드를 호출할 때 자동으로 타입캐스팅이 일어난다.

  • 코드로 쉽게 보자
Parent parent = new Parent(); 

Child child = new Child();

parent.sayHello(child);
//안녕! 나는 자식클래스 피자호빵이야
//parent 에서 파라미터에서 받는 객체로 자동으로 타입캐스팅
//즉, Parent parent = child;

아래와 같이 강제로 타입을 캐스팅하여 다양한 결과를 도출할 수도 있다.
이것을 매개 변수의 다형성 이라고 한다.


강제로 타입캐스팅을 하는 경우

Parent parent = new Child(); 
// 다형성에 의한 상위 클래스 타입 변수에 하위 클래스 객체 할당

// parent는 Parent 타입이므로 Parent 클래스에 정의된 메서드만 호출 가능
parent.someParentMethod();

// Child 타입으로 다시 캐스팅하여 Child 클래스의 메서드 호출 가능
if (parent instanceof Child) {
    Child child = (Child) parent;
    child.someChildMethod();
}

여기서 알 수 있는 사실!

자식클래스의 메서드에 부모클래스의 필드와 메서드를 받아오면 main에서는 자식클래스를 실행하는 것만으로도 부모도 함께 생성자를 생성하고, 메서드를 실행할 수 있다. 이 기능으로 추상화와 인터페이스를 활용할 수 있다.


2. 추상클래스

추상클래스는 메서드만 선언해놓고, 자식클래스에서 그 메서드를 오버라이딩 하여 메소드 코드를 조작할 수 있다.

  • new 연산자를 이용하여 새로운 객체를 만들지 못하고(인스턴스를 만들지 못함), 오로지 상속을 통해 자식클래스의 객체를 통해 메서드를 호출할 수 있다.
  • 추상클래스의 추상메서드는 자식메서드에서 반드시 구현되어야 한다.
  • 추상클래스는 일반 필드 및 메서드를 가질 수 있다.


public abstract class Animal {
    String name;

    // 생성자
    public Animal(String animalName) {
        this.name = animalName;
    }

    // 추상 메서드
    public abstract void makeSound();

    // 구현된 메서드
    public void sleep() {
        System.out.println(name + " 자는중...");
    }
}

class Dog extends Animal {

    // 생성자
    public Dog(String animalName) {
        super(animalName); // Animal 클래스의 생성자를 호출
    }

    // 메서드
    public void lifeCycle() {
        System.out.println("강아지는 보통 주행성 동물이에요.");
    }

    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

이렇게 추상클래스와 자식클래스를 작성했을 때, 자식클래스에서 객체를 새로 생성하여 추상클래스의 메서드를 간접적으로 사용할 수 있다.
public class Execute {
    public static void main(String[] args) {
        // 추상 클래스의 객체는 직접 생성할 수 없음
        // Animal animal = new Animal(); // 오류: 추상 클래스는 인스턴스화할 수 없음

        // 대신 하위(자식)클래스의 객체를 생성하여 메서드 호출
        Dog dog = new Dog("진도리");

        dog.sleep();       // 진도리 자는중...
        dog.makeSound();   // Woof!
        dog.lifeCycle();   // 강아지는 보통 주행성 동물이에요.
    }
}

여기서 알 수 있는 사실!

객체를 생성할 수 없고 자식클래스를 통해서 구현하며 단일상속이다.
이 특성을 아직 어디에 활용해야 하는지 감은 잘 오지 않지만 분명 메서드가 적용되는 좁은 폭의특정한 조건 이 있을 거다.
그래서 기능을 만들었고 아직까지 있는거겠지 -_-



3. 인터페이스

메서드 시그니쳐만 모아놓은 클래스, 인터페이스. (거의 설계자 직급)


추상화클래스에서는 필드와 일반메서드를 작성할 수 있어서 인터페이스보다 적용되는 폭이 좁다.

  • 인터페이스는 메서드를 구현해놓지 않고, 시그니쳐만 구현해놓으며 자세한 구현 기능은 다른 자식 클래스에서 받아서 그 자식 클래스에서 구현한다.
  • 인터페이스는 필드 속성을 가질 수 없다. (하지만 static 상수는 가능)
  • 다중상속이 가능하다. (자식이 여러명(?)일 수 있다.)

인터페이스에서 다른 자식클래스에서 활용할 시그니처 메서드와 메서드에서 받을 파라미터를 설정한다.
interface Animal {
    // 추상 메서드
    void makeSound();
    
    // 디폴트 메서드
    default void sleep() {
        System.out.println("자는중...");
    }
    
    // 정적 메서드
    static void staticMethod() {
        System.out.println("인터페이스의 static 메서드 이옵니다");
    }
    
    // 상수 필드
    static final String DEFAULT_NAME = "동물";
}

자식클래스에서 인터페이스에서 확장(implements)받아 뚝딱뚝딱 설계도의 기초에서 더 자세한 코드와 기능을 추가한다.


class Dog implements Animal {
    String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }

    // sleep() 메서드는 디폴트 메서드를 그대로 사용할 수 있다.
     @Override
    public void sleep() {
        System.out.println("Dog is sleeping...");
        //영문번역으로 오버라이딩 해버리기 ;;
    }
}

메인클래스에서 자식클래스에서 뚝딱뚝딱 만든 새로운 메서드 로직으로 값을 도출할 수 있다.


public class Execute {
    public static void main(String[] args) {
        Dog dog = new Dog("진도리");

        dog.makeSound();   // Woof!
        dog.sleep();       // Dog is sleeping...
        // 오버라이딩 하기 전의 디폴트 메서드 호출
        Animal.originalSleep(); // 자는중 ...
        
        // 인터페이스의 정적 메서드 호출
        Animal.staticMethod(); // 인터페이스의 static 메서드 이옵니다
        
        // 인터페이스의 상수 필드 사용
        System.out.println(Animal.DEFAULT_NAME); // 동물
    }
}

여기서 알 수 있는 사실!

인터페이스를 활용하여 어떤 메서드를 어떤 파라미터를 받아서 활용할 건지 메서드 시그니쳐만 작성해 둔 뒤, 자식클래스에서 상세히 구현할 수 있다.
이는 초반 프로젝트 설계 기간을 줄여줄 수 있을 것으로 기대된다.






출처

이것이 자바다
oracle java 11 interface


profile
지금은 코부작

0개의 댓글