[백기선님과 함께하는 Live-Study] 6주차 - 상속

JoonYoung Maeng·2020년 12월 28일
0
post-thumbnail

✔️ 목표

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

✔️ 학습할 것 (필수)

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

💡 자바 상속의 특징

상속객체지향 프로그래밍(OOP)의 핵심적인 특징 중 하나이다.

상속의 사전적 정의는 "뒤를 있는 일, 물려준다" 라는 뜻이다. 이러한 뜻과 비슷하게 프로그래밍에서의 상속은 부모 클래스가 자신의 기능을 자식 클래스에게 물려주는 것을 말한다.

상속에는 여러 종류가 있는데 자바는 단일 상속만을 지원한다.

📌 단일 상속과 다중 상속

단일 상속이란 자식 클래스가 하나의 부모 클래스로부터 기능을 물려받는 것을 말하며, 가장 일반적인 상속 방법이다.

image

반대로 다중 상속하나의 자식 클래스가 여러개의 부모 클래스로부터 기능을 물려받는 것을 말한다.

아래 그림의 경우 클래스 C가 부모 클래스인 A와 B로부터 run()이라는 메소드를 상속받고 있다.

이런 다중 상속이 생긴 경우, 클래스 C에서 run() 메소드를 호출한다면, 어떤 부모클래스의 run() 메소드를 호출해야 할 지 모호하다. 자바는 이러한 모호한 상황을 미연에 방지하기 위해 다중 상속을 지원하지 않는다.

image

자바에서 상속을 선언하기 위해서 extends 키워드를 사용한다. 단일 상속 사진으로 예를 들면, "class B extends A"로 상속을 표시할 수 있다.

class A {
	public void run() {...}
}

class B extends A {
	@Override
	public void run() {...}
	public void walk() {...}
}

📌 자바 상속의 특징 요약

  1. 부모 클래스는 자식 클래스에게 필드와 메소드를 모두 물려준다.
  2. 자식 클래스의 생성자를 호출하는 경우 부모 클래스의 생성자와 초기화 블록도 같이 호출된다.
  3. 하나의 부모 클래스가 여러개의 자식 클래스에게 상속받는 것이 가능하다.
  4. 상속을 받은 클래스가 부모 클래스가 되어 자식 클래스에게 상속해주는 계층적 상속이 가능하다.

💡Super 키워드

Super 키워드는 부모 클래스를 참조할 때 사용하는 키워드이다. 5주차에서 학습한 this 키워드가 객체 자신을 나타내는 키워드였다면, super 키워드는 반대로 부모를 나타내는 키워드라 생각하면 된다.

super 키워드는 부모 클래스의 인스턴스 변수를 참조하는 경우, 부모 클래스의 메소드를 호출하는 경우, 부모 클래스 생성자를 호출하는 경우에 사용된다.

✏️ 1) 부모 클래스의 인스턴스 변수를 참조하는 경우

클래스 A를 상속하고 있는 클래스 B의 run()메소드를 호출하였을 때 첫번째 출력문은 객체 자신의 speed를 참조하지만 2번째 출력문은 super 키워드를 통해 부모 클래스인 A의 speed 변수를 참조한다.

class A {
  int speed = 10;
  public void run() {
	  System.out.println("run");
	}
}

class B extends A {
	int speed = 50;
  public void run() {
	  System.out.println("B run"+speed);       // speed = 50
    System.out.println("B run"+super.speed); // speed = 10
    super.run();
	}
}

✏️ 2) 부모 클래스의 메소드를 호출하는 경우

클래스 A를 상속하고 있는 클래스 B에서 클래스 A의 run() 메소드를 호출하고 싶은 경우에 super 키워드를 이용해 호출해준다.

class A {
  int speed = 10;
  public void run() {
	  System.out.println("run");
	}
}

class B extends A {
	int speed = 50;
  public void run() {
    super.run();      // 클래스 A의 run() 메소드 호출
	}
}

✏️ 3) 부모 클래스 생성자를 호출하는 경우

클래스 A를 상속하고 있는 클래스 B의 인스턴스가 생성되면 자바에서 자동으로 컴파일 시에 super()메소드를 이용해 클래스 A의 생성자 또한 호출한다.

하지만, 클래스 A의 생성자가 파라미터가 있거나, 명시된 생성자라면 자식 클래스의 생성자에 super(파라미터)와 같은 형식으로 생성자를 반드시 호출해야한다.

class A {
  int speed = 10;

//public A() {
//
//}
	
	//명시된 생성자
	public A(int speed) {
		this.speed = speed;
	}
	public void run() {
	  System.out.println("run");
	}
}

class B extends A {
	int speed = 50;

	//파라미터를 가지는 생성자가 있는 경우(부모 클래스)
  public B(int speed) {
	  //반드시 명시필요
    super(speed);
  }

  public void run() {
	  System.out.println("B run"+speed);
	  System.out.println("B run"+super.speed);
    super.run();
	}

}

💡메소드 오버라이딩

Override의 사전적 의미는 기각하다, 무시하다라는 뜻이다. 메소드 오버라이딩은 부모클래스를 상속하는 자식 클래스가 부모클래스의 메소드의 기능을 무시하고 새롭게 정의하는 것(재정의)하는 것을 의미한다.

예를 들어, Car 클래스와 Car 클래스를 상속하는 Santafe 클래스가 있다고하자.

public class Car {
    private int speed = 10;
    private String name = "car";

    public int accel() {
        return this.speed + 10;
    }

    public void run() {
        System.out.println(name+" is running");
        System.out.println("speed : "+ this.speed);
    }
}

public class Santafe extends Car{
    private int speed = 50;
    private String name = "Santafe";

    @Override
    public int accel() {
        return this.speed + 50;
    }

    @Override
    public void run() {
        super.run();
        System.out.println("after Overriding speed : "+this.speed);
    }

		public void call() {
        System.out.println("test not call");
    }
}

Car 클래스를 상속하는 Santafe 클래스의 run() 메소드를 호출하면 super.run()메소드에 의해 부모 클래스인 Car 클래스의 run() 메소드가 먼저 호출되고, 이후 Santafe 클래스의 run() 메소드가 호출됨을 확인할 수 있다.

또한, 부모 클래스타입으로 Santafe 객체를 참조하는 경우와, 자식 클래스타입 그대로 객체를 참조하는 경우를 확인해보면, 부모 클래스 타입으로 참조하는 경우에는 자식 클래스의 call() 메소드를 호출하지 못하지만, 자식 클래스 타입 그대로 참조하는 경우에는 call() 메소드를 호출할 수 있다.

Car car = new Car();
Car santafe = new Santafe();
Santafe santafe1 = new Santafe();

car.run();      //Car 클래스의 run()메소드 호출
santafe.run();  //Santafe 클래스의 run() 메소드 호출

santafe1.call();  // 메소드 호출 가능
santafe.call();   // 메소드 호출 불가

이를 통해 알 수 있는 사실은,

  1. 자식 클래스에서 오버라이딩한 메소드에서 부모 클래스의 메소드를 호출하고자 한다면 super키워드를 이용해 호출한다.
  2. 부모 클래스 타입의 참조 변수를 통해 자식 클래스 타입의 인스턴스를 참조하는 경우 부모 클래스의 메소드만 호출이 가능하다.

Car 클래스에 각각 static 키워드와 final 키워드로 선언된 메소드가 있다.

static 메소드의 경우 static 메모리 영역 내에 저장되기 때문에 상속 관계라 하더라도 상속 자체가 되지않는다.

final 메소드의 경우 상속은 시키되 상수처럼 기능을 바꾸게 하고싶지 않을 때 선언하면 오버라이딩이 되지 않게 할 수 있다.

public class Car {
    private int speed = 10;
    private String name = "car";

    public int accel() {
        return this.speed + 10;
    }

    public void run() {
        System.out.println(name+" is running");
        System.out.println("speed : "+ this.speed);
    }

    public static void stop() {
        System.out.println("stop");
    }

    public final void test() {
        System.out.println("forbid overriding");
    }
}

public class Santafe extends Car{
    private int speed = 50;
    private String name = "Santafe";

    @Override
    public int accel() {
        return this.speed + 50;
    }

    @Override
    public void run() {
        super.run();
        System.out.println("after Overriding speed : "+this.speed);
    }

		public void call() {
        System.out.println("test not call");
    }

		// static 키워드의 경우 상속이 불가능하고, final 키워드의 경우 오버라이딩이 금지된다.
}

📌 메소드 오버라이딩 규칙

  1. 상속 관계에서 성립한다.
  2. static 키워드로 선언된 메소드의 경우 static 메모리에서 관리하기 때문에 상속이 불가능 하므로 오버라이딩 되지 않는다.
  3. final 키워드로 선언된 메소드의 경우 오버라이딩이 불가능하다.
  4. private 접근 지정자를 사용하는 경우 상속이 불가능하다.
  5. 메소드의 이름, 파라미터 타입,파라미터 개수, 리턴 타입이 모두 동일해야한다.

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

디스패치는 어떤 메소드를 호출할 지를 결정하여 해당 메소드를 실행하는 과정을 말한다. 다이나믹 메소드 디스패치의 경우 어떤 메소드를 호출할 지를 컴파일시에 결정하는 것이 아닌 런타임 시점에 결정하는 것을 말한다.

Car car = new Santafe();

car.run();

Car 클래스 타입 참조 변수가 자식 클래스 Santafe 인스턴스를 참조하고 있다.

컴파일 시에는 부모 클래스 타입의 참조변수는 Car 클래스를 참조하고 있기 때문에 car.run() 메소드를 호출하면 Car 클래스의 run() 메소드를 호출할 것으로 예상된다.

하지만, 런타임 시에 Car 클래스를 참조하고 있던 변수가 Santafe 클래스 타입의 객체를 생성하고 참조하기 때문에 Santafe 클래스의 run() 메소드가 실제로 호출됨을 확인할 수 있다.

따라서, 다이나믹 메소드 디스패치는 참조변수가 컴파일 시에 참조하고 있는 클래스 타입의 메소드와 런타임 시에 참조하고 있는 클래스 타입의 메소드가 다르기 때문에 런타임 시에 메소드를 결정하여 호출한다.


💡 추상 클래스

추상 클래스는 연관된 클래스 사이에 비슷한 필드와 메소드들을 공통으로 추출하여 만든 클래스이다.

예를 들어, 투싼, 산타페, 스포티지가 있다고 생각하자. 각각의 차들은 주행하는 기능, 멈추는 기능이 있을 것이다. 이 때, 공통되는 기능인 주행, 멈춤 기능을 추출하여 자동차라고 칭할 수 있다. 이렇게 만들어진 자동차를 추상 클래스라고 할 수 있다.

추상 클래스는 abstract 키워드를 이용하며, 객체를 생성할 수 없다는 특징을 가지고 있다.

또한, 추상 메소드를 가지고 있는 클래스라면 반드시 추상 클래스로 정의해야한다.

public abstract class AbstractCar {
    abstract public void run();
    abstract public void stop();
    public void accel() {
        System.out.println("액셀");
    }
}

//추상 메소드 반드시 구현
class Tucson extends  AbstractCar{

    @Override
    public void run() {
        System.out.println("투싼이 달린다");
    }

    @Override
    public void stop() {
        System.out.println("투싼이 멈춘다.");
    }
}

//추상 메소드 반드시 구현
class Sportage extends AbstractCar{

    @Override
    public void run() {
        System.out.println("스포티지가 달린다");
    }

    @Override
    public void stop() {
        System.out.println("스포티지가 멈춘다");
    }
}

추상 클래스를 사용하는 이유

  1. 공통된 메소드와 필드를 통일하여 유지보수 용이 및 통일성 유지
  2. 추상 클래스를 가지고 구체 클래스를 구현만 하면 되기 때문에 확장성 용이
  3. 인터페이스 처럼 메소드를 구현해야하지만, 추상 클래스에 메소드를 정의하는 것이 가능하기 때문에 모든 추상 메소드 이외에는 구현이 필요한 메소드만 구현하면 된다.

💡 final 키워드

단어 뜻 그대로, 변수, 메소드, 클래스를 변경하지 못하도록 막아주는 키워드이다.

변경하지 못하도록 하는 것이기 때문에 반드시 초기화가 필요하다.

  1. final 클래스 : 상속이 불가능한 클래스이며, 어떤 클래스의 부모 클래스가 될 수 없음을 뜻한다.

    public final class FinalClass {
        int x = 10;
        int y = 20;
        int sum() {
            return this.x + this.y;
        }
    }
    
    //컴파일 에러 
    class Test extends FinalClass {...}
  2. final 메소드 : 상속 시 오버라이딩이 불가능한 메소드를 말한다.

    sum() 메소드는 오버라이딩이 가능하지만, mul() 메소드는 오버라이딩이 불가능함을 확인할 수 있다.

image

  1. final 변수 : 상수라고 불리며, 값을 변경할 수 없고 항상 할당된 값만 사용할 수 있다.

    final double PI = 3.141592;
    PI = 1.11; //컴파일 에러 

💡 Object 클래스

Object 클래스는 자바 내 모든 클래스의 최상위 클래스를 말하며 Object 클래스를 제외한 모든 클래스들은 Object 클래스로부터 상속받는다.

그렇기 때문에, Object 클래스에서 제공하는 모든 메소드들은 자동으로 상속받기 때문에 어떤 클래스에서든지 모두 사용이 가능하다.

Object 클래스의 메소드

  • boolean equlas(Object obj) : 두 개의 객체를 비교해 결과를 반환
  • String toString() : 현재 객체의 문자열 반환
  • protected Object clone() : 객체를 복사
  • protected void finalize() : GC 직전에 객체의 리소스를 정리
  • Class getClass() : 객체의 클래스 타입 반환
  • int hashCode() : 객체의 코드값 반환
  • void wait() : 스레드를 일시중지
  • void wait(long timeout) : 파라미터에 주어진 시간만큼 스레드를 일시중지
  • void wait(long timeout, int nanos) : 파라미터에 주어진 시간만큼 스레드를 일시 중지
  • void notify() : wait 상태의 스레드를 다시 실행
  • void notifyAll() : wait상태의 모든 스레드를 다시 실행

📃 Reference

super키워드 : https://tworab.tistory.com/62

상속 특징 : https://m.blog.naver.com/PostView.nhn?blogId=justkukaro&logNo=220773464579&proxyReferer=https:%2F%2Fwww.google.com%2F

메소드 오버라이드 : https://velog.io/@polynomeer/JAVA의-오버라이딩Override

다이나믹 메소드 디스패치 : https://brunch.co.kr/@mystoryg/60

Object 클래스 : https://hyeonstorage.tistory.com/178

profile
백엔드 개발자 지망생입니다!

0개의 댓글