[Java] 3-1. 상속(Inheritance) / 메서드 재정의(Method Overriding)

루키·2024년 7월 27일

Java

목록 보기
9/10
post-thumbnail

1. Inheritance란?

1-1. 상속의 정의

  • 상속이란? : 기존(상위) 클래스의 자산(멤버)을 자식(하위) 클래스에서 재사용하기 위한 것입니다.

    • 상위 클래스의 생성자와 초기화 블록은 상속하지 않습니다.
  • 상속의 장점

    • 상위 클래스의 맴버를 물려 받기 때문에 코드의 양을 절감할 수 있습니다.
    • 상위 클래스의 코드를 변경하면 모든 하위 클래스들에게도 적용이 되므로 유지 보수성이 향상됩니다.
  • 상속의 적용 방법 : extends 키워드 사용

    public class Person {
        String name;
        
        void eat(){}
        void jump(){}
    }
    
    public class SpiderMan extends Person { // SpiderMan은 Person을 상속받음
        boolean isSpider;
        
        void fireWeb(){}
    }
  • Object 클래스 : 모든 클래스의 조상 클래스

    • 별도의 extends 선언이 없는 클래스들은 extends Object가 생략된 상태입니다.
    • 따라서 모든 클래스에는 Object 클래스에 정의된 메서드를 사용할 수 있습니다.
      <주요 메소드>
      Object.hashCode() : 객체의 주소값을 변환하여 생성한 객체의 고유한 정수 값을 반환
      Object.equals() : 객체의 주소값을 비교해서 true/false를 boolean 타입으로 반환
      Object.toString() : 객체의 정보를 String 타입으로 변환해서 반환

      Object 클래스의 주요 메서드들은 대부분의 클래스에서 Overriding(재정의)해서 사용합니다.

1-2. 다양한 상속 관계

  • 상속 (is a 관계) : 자바는 단일 상속 (Single Inheritance)만 지원합니다. 다중 상속을 하게 되면, 여러 클래스의 기능을 물려 받으면서 동일한 이름의 메서드가 있다면 어떤 메서드를 사용할 것인지와 같은 복잡한 문제가 발생할 수 있습니다.

  • 합성 (has a 관계) : 이러한 단일 상속 관계로 발생하는 단점을 인터페이스(Interface)와 합성(Composition)으로 극복하고 있습니다. (인터페이스는 추후 포스팅에서 다시 자세하게 다루겠습니다.) 포함 관계는 상속받는 대신, 상위 객체를 멤버 변수로 활용하며 클래스의 인스턴스를 활용하는 방식입니다.

    현재는 Java의 객체지향 프로그래밍에 대해서 학습하기 위해 상속(extends)를 활용하지만, 아래의 몇가지 이유로 인해 현업에서는 상속을 지양하는 편이며 대신 합성을 사용하는 편입니다.

    • 결합도가 높아집니다.
    • 불필요한 기능이 상속됩니다.
    • 부모 클래스의 결함이 그대로 넘어옵니다.
    • 부모 클래스와 자식 클래스의 동시 수정이 문제될 수도 있습니다.
  • 다양한 상속의 활용

    public class Person {
        String name;
        
        void eat(){}
        void jump(){}
    }
    
    public class Spider {
        void fireWeb() {System.out.println("슉슉!");}
    }
    
    public class SpiderMan extends Person {  // SpiderMan is a Person <상속>
        Spider spider = new Spider(); // SpiderMan has a Spider <합성>
        boolean isSpider;
        
        void fireWeb(){
            if(this.isSpider) { this.spider.fireWeb(); }
            else { System.out.println("사람은 거미줄을 쏠 수 없습니다!); }
        }
    }

2. Method Overriding란?

2-1. 메서드 오버라이딩 / 조건

  • 메서드 오버라이딩(Overriding)이란? : 조상 클래스에 정의된 메서드를 자식 클래스에서 적합하게 수정하는 것입니다.
public class Person {
    String name;

    void eat(){}
    void jump(){ System.out.println("두 다리로 힘껏 점프"); }
}

public class Spider {
    void fireWeb() {System.out.println("슉슉!");}
}

public class SpiderMan extends Person {
    Spider spider = new Spider();
    boolean isSpider;

    void fireWeb(){
        if(this.isSpider) { this.spider.fireWeb(); }
        else { System.out.println("사람은 거미줄을 쏠 수 없습니다!); }
    }
    void jump() { // SpiderMan에서 jump()를 재정의
        if(this.isSpider) { spider.jump(); }
        else { System.out.println("두 다리로 힘껏 점프"); }
    }
}
  • 오버라이딩의 조건

    • 메서드 이름이 같아야합니다.
    • 매개 변수의 개수, 타입, 순서가 같아야합니다.
    • 리턴 타입이 같아야합니다.
    • 접근 제한자는 부모보다 범위가 넓거나 같아야합니다.
    • 조상보다 더 큰 예외를 던질 수 없습니다.

    (접근 제한자와 예외에 관한 포스팅은 추후 추가하도록 하겠습니다.)

2-2. super 키워드

  • super 키워드 : 자기 자신에게 접근한 this 키워드와 같이 조상에 접근하기 위해서는 super 키워드를 사용할 수 있습니다. 위의 예시 중 일부를 다시 가져와서 super 키워드를 활용해 보겠습니다.
void jump() { 
    if(this.isSpider) { spider.jump(); }
    // else { System.out.println("두 다리로 힘껏 점프"); }
    else { super.jump(); } // SpiderMan의 조상인 Person 클래스의 jump를 그대로 활용
}
  • 정리 : 위의 SpiderMan 클래스를 예시로 들어 설명하면
    SpiderMan 객체에서 jump 메서드를 호출할 때,
    SpiderMan이 갖는 jump를 활용하기 위해서는 this.jump();를 활용,
    상속받은 Person이 갖는 jump를 활용하기 위해서는 super.jump();를 활용,
    포함된 Spider이 갖는 jump를 활용하기 위해서는 spider.jump();를 활용할 수 있습니다.

  • 변수의 scope : 사용된 위치에서 점점 확장해가며 처음 만난 선언부에 연결됩니다.

class Parent {
    String x = "parent";
}

class Child extends Parent {
    String x = "child";
    
    void method() {
        String x = "method";
        System.out.println(x); // 1
        System.out.println(this.x); // 2
        System.out.println(super.x); // 3
    }
}

// 메인 함수
Child c = new Child();
c.method();

// 실행 결과
//
// method  // 1번이 print되면서 제일 가까운 x인 method 내의 x를 출력합니다.
// child   // 2번이 print되면서 this 키워드를 통해 Child 객체 내의 x를 출력합니다.
// parent  // 3번이 print되면서 super 키워드를 통해 조상인 Parent 객체 내의 x를 출력합니다.
  • super 키워드의 특징
    • this()가 해당 클래스의 다른 생성자를 호출하듯, super()은 조상 클래스의 생성자를 호출합니다. 조상 클래스에서 선언된 맴버들은 super()을 통해 조상클래스의 생성자에서 초기화가 이뤄지도록 합니다.
    • super() 키워드 또한 this()키워드와 마찬가지로 생성자의 첫 줄에서만 호출이 가능합니다. (this()super()은 같이 사용할 수 없습니다.)
    • 명시적으로 this() 또는 super()를 호출하지 않는 경우, 컴파일러가 super() 키워드를 삽입합니다. 이는 결론적으로 맨 상위의 Object 클래스까지 객체가 다 만들어지는 구조가 됩니다.

2-3. Annotation 활용

  • 사전적 의미 : 주석

  • 컴파일러, JVM, 프레임워크 등이 확인하는 용도로 사용됩니다.

  • 소스코드에 메타 데이터를 삽입하는 형태로 소스코드에 붙여놓은 라벨이라고 생각할 수 있습니다.

  • 기본 annotation의 예

    • @Deprecated : 컴파일러에게 해당 메서드가 deprecated 되었다고 알려줍니다.
    • @Override : 컴파일러에게 해당 메서드는 override되었다고 알려줍니다. override는 반드시 super 클래스에 선언되어 있는 메서드여야만 합니다.
    • @SuppressWarnings : 컴파일러에게 사소한 warning의 경우 신경 쓰지 않도록 설정합니다.

    (Annotation에 관한 자세한 내용은 추후 포스팅에서 다시 다루도록 하겠습니다.)

profile
주니어 FrontEnd 개발자가 되기 위한 기록

0개의 댓글