프로그래밍 언어/Java

[스터디 할래] 6주차 - 상속

highright96 2021. 5. 26.

 

본 글은 백기선님의 라이브 스터디 과제를 블로그에 옮기며 다시 작성한 글입니다. 과제 링크

 

highright96/java-livestudy

백기선님의 자바 온라인 스터디. Contribute to highright96/java-livestudy development by creating an account on GitHub.

github.com

 

1. 상속

상속이란

상속이란 상위 클래스에서 정의한 필드와 메서드를 하위 클래스도 동일하게 사용할 수 있게 물려받는 것이다.

상위 클래스를 부모 클래스, 슈퍼 클래스라고 부르며

상속받은 하위 클래스를 자식 클래스, 서브 클래스, 파생 클래스라고 부른다.

 

상속을 사용하는 이유

코드를 재사용하기에 편하고 클래스 간 계층구조를 분류하고 관리하기 쉬워진다.

간단한 예제를 살펴보자.

  • 축구 게임을 만든다고 가정하자.
  • Ronaldo는 스테미너, 속력, 슛 파워라는 스탯을 가지고 있다.
  • Messi는 스테미너, 속력, 드리블이라는 스탯을 가지고 있다.

위의 요구 사항을 보면 Ronaldo와 Messi는 스테미너, 속력이라는 공통적인 스탯을 확인할 수 있다.

따라서 공통적인 스탯은 FootballPlayer에 만들고

Ronaldo와 Messi 모두 FootballPlayer를 상속한 후 나머지를 구현해 코드를 재사용할 수 있게 구현하였다.

FootballPlayer.java

public class FootballPlayer {
    String name;
    int stamina;
    int speed;
}

 

Ronaldo.java

public class Ronaldo extends FootballPlayer{

    int shootPower;

    public Ronaldo(String name, int stamina, int speed, int shootPower) {
        this.name = name;
        this.stamina = stamina;
        this.speed = speed;
        this.shootPower = shootPower;
    }
}

 

Messi.java

public class Messi extends FootballPlayer{

    int dribble;

    public Messi(String name, int stamina, int speed, int dribble) {
        this.name = name;
        this.stamina = stamina;
        this.speed = speed;
        this.dribble = dribble;
    }
}

 

자바 상속의 특징

1) extends 키워드를 사용하여 상속

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

}

 

2) 다중 상속 불가능

//불가능
class 자식 클래스명 extends 부모 클래스1, 부모 클래스2{ 
    
}

 

3) 부모의 private 접근 제한을 갖는 멤버는 상속 불가능

  • private 제한자는 오로지 선언된 클래스 내부에서만 접근 가능하다.
  • 외부에서 사용할 수 있게 하는 상속은 불가능하며, 만약 상속이 필요할 경우, 다른 접근 제한자(public, protected) 사용해야한다.

4) static 메서드 또는 변수도 상속 가능

5) final 클래스는 상속할 수 없고, final 메소드는 오버라이딩 불가능

6) 자바의 모든 클래스는 Object 클래스의 자식/자손 클래스

 

2. 메소드 오버라이딩

메소드 오버라이딩이란?

부모 클래스가 가지고 있는 메서드를 자식 클래스에서 새롭게 다른 로직으로 정의하고 싶을 때 사용하는 문법이다.

SuperClass.java

public class SuperClass {
    public void superMethod(){
        // 기존 로직
    }
}

 

SubClass.java

public class SubClass extends SuperClass{
    @Override
    public void superMethod(){
        // 새로운 로직
    }
}

 

메소드 오버라이딩의 조건?

  • 접근 제어자는 부모 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
    • 만일 부모 클래스에 정의된 메서드의 접근 제어자가 protected라면, 이를 오버라이딩하는 자식 클래스의 메서드는 접근 제어자가 protected나 public이어야 한다.
    • 대부분의 경우 같은 범위의 접근 제어자를 사용한다.
  • 부모 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.

 

3. super 키워드

super 키워드란?

super 키워드는 부모 클래스로부터 상속받은 멤버 변수나 메소드를 자식 클래스에서 참조하는데 사용하는 참조 변수이다.

super 사용 방법

1. super.멤버 변수

  • 부모 클래스의 맴버 변수를 호출할 수 있다.

2. super.멤버 메소드(매개변수)

  • 부모 클래스의 멤버 메소드를 호출할 수 있다.

3. super(매개변수)

  • 부모 클래스의 생성자를 호출할 수 있다.
  • 생성자는 메소드가 아니다.
  • this 키워드는 동일한 클래스 내의 다른 생성자를 호출하는 키워드이다.
  • 주의할 점
    • 반드시 자식 클래스의 생성자 첫 라인에는 부모 생성자를 호출해야 한다.
    • 명시적으로 자식 클래스에서 부모 생성자를 호출하지 않아도 컴파일 시 super 키워드가 자동 삽입되어 부모 클래스의 디폴트 생성자를 호출하게 된다.
    • 만약 별도의 매개 변수를 가진 부모 클래스의 생성자를 호출하려면 생성자의 첫 번째 줄에 super 키워드를 직접 명시해야 한다.

다음은 부모 클래스 생성자를 호출하는 super() 예시이다.

Ronaldo는 FootballPlayer의 멤버 변수(private)에 접근을 하지 못하기 때문에 super() 키워드를 사용했고,

호출하려는 부모 클래스의 생성자가 디폴트 생성자가 아니기 때문에 직접 명시했다.

 

FootballPlayer.java

public class FootballPlayer {
    private String name;
    private int stamina;
    private int speed;

    public FootballPlayer(String name, int stamina, int speed) {
        this.name = name;
        this.stamina = stamina;
        this.speed = speed;
    }
}

 

Ronaldo.java

public class Ronaldo extends FootballPlayer{
    int shootPower;

    public Ronaldo(String name, int stamina, int speed, int shootPower) {
        super(name, stamina, speed);
        this.shootPower = shootPower;
    }
}

 

4. 메소드 디스패치(Method Dispatch)

메소드 디스패치란?

자바에서 디스패치(dispatch)의 의미는 메서드를 호출하는 것이다.

디스패치는 정적 디스패치(static dispatch)와 동적 디스패치(dynamic dispatch)가 있는데

정적(static)은 구현 클래스를 이용해 컴파일 시점에서부터 어떤 메서드가 호출될 지 정해져 있는 것이고,

동적(dynamic)은 인터페이스를 이용해 참조함으로서 호출되는 메서드가 동적으로 정해지는 것을 의미한다.

참고
자바에서 객체 생성은 Runtime 시에 호출된다. 즉, 컴파일 시점에 알 수 있는 것은 타입에 대한 정보이다.

 

정적 메소드 디스패치(Static Method Dispatch)

Parent.java

public class Parent {
    public void method(){
        System.out.println("Parent");
    }
}

Child.java

public class Child extends Parent{
    @Override
    public void method(){
        System.out.println("Child");
    }
}

Main.java

public static void main(String[] args){
        Child child = new Child();
        child.method(); //정적 메소드 디스패치
}

위의 코드에서 Child 클래스의 method 메소드는 부모 클래스 Parent 의 method을 오버라이딩을 하였다.

Main 클래스에서 child.method()을 호출했을 때 Child 타입의 객체를 생성했기 때문에 우리는 Child 클래스의 오버라이딩 된 함수가 불릴 것을 알고 있다.

따라서 컴파일러 역시 이 메소드를 호출하고 실행시켜야되는 것을 명확하게 알고 있다.

이를 정적 메소드 디스패치라고 한다.

 

다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

Main.java

public static void main(String[] args){
        Parent parent = new Child();
        parent.method(); //동적 메소드 디스패치
}

자바에서는 Parent parent = new Child()와 같은 객체의 생성과 바인딩을 허락한다.

이 코드에서 parent.method() 을 사용하면 어떤 메소드가 호출될까?

위에서 말했던 것처럼 컴파일러는 타입만 체크한다.

따라서 parent 객체는 Parent 이라는 클래스 타입이기 때문에 Child 클래스를 할당할지라도 Child 클래스의 method()에 접근할 수가 없다. Parent 객체이기 때문이다.

하지만 결과는 Child 클래스의 method() 이 호출된다.

그 이유는 컴파일러가 어떤 메소드를 호출해야 되는지 모르지만 런타임에 정해져서 메서드를 호출하기 때문이다.

이를 동적 메소드 디스패치라고 부른다.

 

더블 디스패치 (Double Dispatch)

www.bsidesoft.com/2843 를 참고하여 작성했습니다.

동적 디스패치를 두 번하는 기법이다.

4가지 조합이 나오는 추상 Post레벨과 이를 활용하는 SNS레벨간의 조합처리되는 예제이다.

1. 구현체에 따라 로직이 다르지 않은 경우

interface Post {
    void postOn(SNS sns);
}

class Text implements Post{
    @Override
    public void postOn(SNS sns) {
        System.out.println("text -> " + sns.getClass().getSimpleName());
    }
}

class Picture implements Post{
    @Override
    public void postOn(SNS sns) {
        System.out.println("picture -> " + sns.getClass().getSimpleName());
    }
}
interface SNS {}

class FaceBook implements SNS {}

class Twitter implements SNS {}
public class DynamicTest {
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new FaceBook(), new Twitter());

        posts.forEach(post-> {
            sns.forEach(s -> post.postOn(s));
        });
    }
}
  • SNS 구현체에 따라 로직이 달라지는 경우를 고려하지 않았다.

 

2. SNS의 구현체에 따라 로직이 다른 경우 (분기문 사용)

interface Post {
    void postOn(SNS sns);
}

class Text implements Post{
    @Override
    public void postOn(SNS sns) {
        if(sns instanceof Facebook){
            System.out.println("text -> facebook");
        }
        if(sns instanceof Twitter){
            System.out.println("text -> twitter");
        }
    }
}

class Picture implements Post{
    @Override
    public void postOn(SNS sns) {
        if(sns instanceof Facebook){
            System.out.println("picture -> facebook");
        }
        if(sns instanceof Twitter){
            System.out.println("picture -> twitter");
        }
    }
}
interface SNS {}

class FaceBook implements SNS {}

class Twitter implements SNS {}

class GooglePlus implements SNS{}
public class DynamicTest {
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new FaceBook(), new Twitter(), new GooglePlus());

        posts.forEach(post-> {
            sns.forEach(s -> post.postOn(s));
        });
    }
}
  • SNS의 새로운 구현체가 생기면 분기문을 추가해야한다.
  • 만약 실수로 분기문을 추가하지 않으면 의도치 않게 exception이 발생한다.

 

3. SNS의 구현체에 따라 로직이 다른 경우 (메소드 오버로딩 사용 static dispatch)

interface Post {
    void postOn(SNS sns);
}

class Text implements Post{
    @Override
    public void postOn(SNS sns) {
        sns.post(this);
    }
}

class Picture implements Post{
    @Override
    public void postOn(SNS sns) {
        sns.post(this);
    }
}
interface SNS {
    void post(Text text);
    void post(Picture picture);
}

class FaceBook implements SNS {

    @Override
    public void post(Text text) {
        System.out.println("FaceBook text");
    }

    @Override
    public void post(Picture picture) {
        System.out.println("FaceBook picture");
    }
}

class Twitter implements SNS {

    @Override
    public void post(Text text) {
        System.out.println("Twitter text");
    }

    @Override
    public void post(Picture picture) {
        System.out.println("Twitter picture");
    }
}
public class DynamicTest {
    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new FaceBook(), new Twitter());

        posts.forEach(post-> {
            sns.forEach(s -> post.postOn(s));
        }
    }
}
  • 두 번의 다이나믹 디스패치를 사용했다. 
    • Post 중 어떤 구현체의 postOn 메소드를 실행할지 다이나믹 디스패치 한 번 사용
    • postOn 메소드 내부에서 어떤 SNS 구현체의 post 메소드를 실행할지 다이나믹 디스패치 한 번 사용
  • 새로운 구현체가 생기면 다음 코드만 추가하면 된다.
  • 다른 코드는 수정할 필요가 없으며, SNS의 구현체에 따라 로직을 분리할 수 있다.
      class GooglePlus implements SNS {
          public void post(Text text) {
              System.out.println("GooglePlus text");
          }
          public void post(Picture picture) {
              System.out.println("GooglePlus text");
          }
      }​

 

5. final 키워드

final 사용 방법

1. final 필드

  • 변수 앞에 final 이 붙으면, 값을 변경할 수 없는 상수가 된다.
  • final 이 붙은 변수는 상수이므로 일반적으로 선언과 동시에 초기화를 동시에 하지만, 인스턴스 변수의 경우 생성자에서 초기화 되도록 할 수 있다.
  • 클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할 때 final 이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로부터 제공받는 것이다.
  • 이 기능을 활용하면 각 인스턴스마다 final 이 붙은 멤버변수가 다른 값을 갖도록 하는 것이 가능하다.

2. final 클래스

  • 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. 따라서 final로 지정된 클래스는 다른 클래스의 부모가 될 수 없다.

3. final 메소드

  • 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.

final을 언제 사용해야 할까?

final을 쓰던, 안쓰던 코드를 이해하고 작성하면 문제없이 코딩이 가능하다.

그러나 다른 사람들과 오해를 최소화하고 도움을 줄 수 있는지 고민하면 좋을 것 같다.

  • 개발의 의도를 나타내기 위함
    • 코드 리뷰 등을 통해 명시적으로 변경, 상속, 확장을 막음으로서 실수를 최소화하고 버그를 줄일 수 있다.
  • 코드의 가독성을 위함.

 

6. Object 클래스

java.lang.Object 클래스는 모든 클래스의 최상위 클래스이다.

메소드 설명
boolean equals(Object obj) 두 객체가 같은지 비교한다.
String toString() 객체의 문자열을 반환한다.
protected Object clone() 객체를 복사한다.
protected void finalize() 가비지 컬렉션 직전에 객체의 리소스를 정리할때 호출한다.
Class getClass() 객체의 클레스형을 반환한다.
int hashCode() 객체의 코드값을 반환한다.
void notify() wait된 스레드 실행을 재개할 때 호출한다.
void notifyAll() wait된 모든 스레드 실행을 재개할 때 호출한다.
void wait() 스레드를 일시적으로 중지할 때 호출한다.
void wait(long timeout) 주어진 시간만큼 스레드를 일시적으로 중지할 때 호출한다.

 

7. 참고

'프로그래밍 언어 > Java' 카테고리의 다른 글

[Java] JVM  (0) 2021.10.25
자바(Java) 버전별 특징  (0) 2021.10.19
[스터디 할래] 5주차 - 클래스  (0) 2021.05.26
java.util.Optional이란?  (1) 2021.05.26
int와 Integer의 차이(Wrapper Class)  (0) 2021.05.26

댓글