백기선 자바 스터디 1기 6주차

2022. 5. 12. 10:09Java/java-live-study

728x90
반응형

목표

자바의 상속에 대해 학습하세요.

학습할 것

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스

자바 상속의 특징

자바에는 자식 클래스가 부모 클래스의 멤버를 그대로 물려받을 수 있는 상속(Inheritance)이라는 개념이 있다. 상속은 코드의 재사용과 변경을 용이하게 해준다.

 

자바에서 상속은 자식 클래스에서 클래스 명 뒤에 extends 키워드를 적고 부모 클래스의 클래스 명을 적어주면 된다.

class Parent {
    // ...
}

class Child extends Parent {
    // ...
}

위와 같이 상속을 받게 되면 자식 클래스는 부모 클래스의 private 멤버를 제외한 다른 멤버들 또한 사용 가능하다.

class Parent {

    private int p1;
    protected int p2;
    int p3;
    public int p4;

    private void pf1() {
        System.out.println("pf1 func");
    }

    protected void pf2() {
        System.out.println("pf2 func");
    }

    public void pf3() {
        System.out.println("pf3 func");
    }
}

class Child extends Parent {

}

public class InherTest {

    public static void main(String[] args) {
        Child child = new Child();
        Parent parent = new Parent();

        child.p1 = 0; //error
        child.p2 = 0;
        child.p3 = 0;
        child.p4 = 0;

        child.pf1(); //error
        child.pf2();
        child.pf3();
    }
}

super 키워드

 super 키워드는 자식 클래스에서 부모 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다. 멤버변수와 지역변수의 이름이 같을 때 this 키워드를 붙여서 구별했듯이 상속받은 멤버와 자신의 클래스에 정의된 멤버의 이름이 같을 때는 super를 붙여서 구별할 수 있다. 물론 부모 클래스로부터 상속받은 멤버도 자식 클래스의 멤버이므로 this를 사용할 수 있다.

class Parent {

    int num = 10;

    public void func() {
        System.out.println(num);
    }
}

class Child extends Parent {

    int num = 100;

    @Override
    public void func() {
    	super.func();
        System.out.println(super.num);
    }
}

public class InherTest {

    public static void main(String[] args) {
        Child child = new Child();
        child.func(); //10 100 출력
    }
}

this() 키워드가 자신의 생성자를 호출하는 것과 마찬가지로 super() 키워드는 부모의 생성자를 호출한다.

class Parent {

    int num;
    
    Parent(int num) {
        this.num = num;
    }
}

class Child extends Parent {

    int num;
    
    Child(int num) {
        super(num);
        this.num = num;
    }
}

메소드 오버라이딩

자식 클래스는 부모 클래스의 메소드를 재정의할 수 있는데 상속받은 부모 클래스가 정의한 메소드를 자식 클래스가 가져와 변경하거나 확장할 수 있다. 이를 오버라이딩(Overriding)이라고 한다.

public class Person {
    
    public void talk() {
        System.out.println("사람입니다.");
    }
}

class Man extends Person {
    
    @Override
    public void talk() {
        System.out.println("남자입니다.");;
    }
}

class Woman extends Person {
    
    @Override
    public void talk() {
        System.out.println("여자입니다.");;
    }
}

메소드 오버라이딩은 부모 클래스의 메소드를 자식 클래스에서 메소드를 재정의하기 때문에 확장과 변경에 용이하다는 장점이 있다.(다형성)


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

메소드 디스패치는 어떤 메소드를 호출할 지 결정하여 실행시키는 과정을 말한다.

 

메소드 디스패치의 종류는 3가지가 있는데 정적 메소드 디스패치(Static Method Dispatch), 동적 메소드 디스패치(Dynamic Method Dispatch), 더블 디스패치(Double Dispatch)가 있다.

 

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

class Parent {
    public void hello() {
        System.out.println("parent!");
    }
}

class Child extends Parent { //메소드 오버라이딩
    @Override
    public void hello() {
        System.out.println("child!");
    }
}

public class StaticDispatchTest {

    public static void main(String[] args) {
        Child child = new Child();
        child.hello(); //"child!"를 출력
    }
}

메인 함수에서 child.hello() 메소드를 호출했을때 우리는 Child 클래스의 오버라이딩 된 hello()가 호출될 것을 알고 있다. 컴파일러 또한 이 메소드를 호출하고 실행시켜야되는 것을 알고 있는데 이를 정적 메소드 디스패치라고 한다.

 

동적 메소드 디스패치(Dynamic Method Dispatch)

class Parent {
    public void hello() {
        System.out.println("parent!");
    }
}

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

public class StaticDispatchTest {

    public static void main(String[] args) {
        Parent p = new Child();
        p.hello(); //"child!" 출력
    }
}

우리는 정적 메소드 디스패치 예제와 마찬가지로 Child 클래스의 hello() 메소드가 호출 될 것을 알고 있다. 하지만 컴파일러는 컴파일 타임에 그 사실을 알지 못하고 객체가 생성되는 런타임 때 할당 된 Object를 보고 Parent 클래스의 hello() 메소드를 실행할지 Child 클래스의 hello() 메소드가 호출될지 알 것이다. 이를 동적 메소드 디스패치라고 한다.

 

더블 메소드 디스패치(Double Method Distpatch)

추가 공부 필요

https://roeldowney.tistory.com/486

https://www.notion.so/e5c33507880b4d098f83a2c4f8f02c04


추상 클래스

추상 클래스는 추상 클래스를 상속 받은 자식 클래스에서 메소드를 구현(오버라이딩)하는 것을 강제한다. 

 

추상 클래스는 abstract 키워드를 사용하여 만들 수 있다.

제어자 대상 의미
abstract 클래스 클래스 내에 추상메소드가 선언되어 있음을 의미한다.
메소드 선언부만 작성하고 구현부는 작성하지 않은 추상메소드이다.

자식 클래스에서 오버라이딩하여 자신의 클래스에 맞게 재정의하기 때문에 추상 메서드가 필요없다고 생각할 수도 있지만 추상 클래스는 해당 메소드를 구현하도록 강제하여 자식 클래스에게 내용을 구현해주어야 한드는 것을 알려주는 것이다.

abstract class Foo {

    abstract void func1();

    public void func2(){
        System.out.println("hello func2");
    }
}

class Bar extends Foo {

    //func1을 재정의하지 않으면 error
    @Override
    void func1() {
        System.out.println("hello func1");
    }
}

final 키워드

final은 '변경될 수 없는'의 의미를 가지고 있고 거의 모든 대상에 사용될 수 있다.

제어자 대상 의미
final 클래스 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. (상속 불가)
메소드 변경될 수 없는 메소드, final로 지정된 메소드는 오버라이딩을 통해 재정의 될 수 없다.
멤버변수 변수 앞에 final이 붙으면 값을 변경할 수 없는 상수가 된다.
지역변수
final class Dink {
    void dinkFunc() {
        System.out.println("Dink func");
    }
}

class Parent {

    void parentFunc() {
        System.out.println("Parent func");
    }

    final void finalParentFunc() {
        System.out.println("final Parent func");
    }
}

class Child1 extends Dink{ // 상속 불가 error

}

class Child2 extends Parent {

    final int val1 = 0;
    final int val2; //바로 초기화해주지 않았다면 생성자에서 초기화해주어야한다.

    Child2(int num) {
        this.val1 = num; //error final 변수의 초기화는 한번만!
        this.val2 = num;
    }

    @Override
    void parentFunc() {
        System.out.println("Child2 func");
    }

    @Override
    protected void finalParentFunc() { // 재정의 불가 error
        System.out.println("Child2 func");
    }
}

Object 클래스

Object 클래스는 모든 클래스의 상속 관계 가장 상위에 있는 부모 클래스이다. 다른 클래스로부터 상속받지 않는 모든 클래스들은 자동으 Object 클래스를 상속하게 된다.

 

toString(), equals()와 같은 메소드를 따로 정의하지 않고 사용할 수 있었던 이유는 이 메소드들이 Object 클래스의 멤버들이기 때문이다. Object 클래스에는 모든 클래스가 가져되는 기본적인 11개의 메소드가 정의되어 있다.

메소드 설명
protected Object clone() 객체 자신의 복사본을 반환한다.
boolean equals(Object o) 객체 자신과 매개변수로 들어온 객체가 같은 객체인지 알려준다
protected void finalize() 객체가 소멸될 때 가비지 컬렉터에 의해 자동적으로 호출된다.
final Class<?> 객체 자신의 클래스 정보를 담고 있는 Class 인스턴스를 반환한다.
int hashCode() 객체 자신의 해시코드를 반환한다.
final void notify() 객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다.
final void notifyAll() 객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다.
String toString() 객체 자신의 정보를 문자열로 반환한다.
final void wait() 다른 쓰레드가 notify()나 notifyAll()을 호출할 때까지 현재 쓰레드를 무한히 또는 지정된 시간(timeout, nanos)동안 기다리게 한다.
final void wait(long timeoutMillis)
final void wait(long timeoutMillis, int nanos)

728x90
반응형