상속

yanju·2022년 12월 2일
0
post-thumbnail

학습할 것

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

자바 상속

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

상속은 코드의 재사용성을 통해 코드의 간결성을 확보해준다.

상속의 특징

  • 상속은 단일 상속만 가능하다.
class 자식클래스명 extends 부모클래스명1, 부모클래스명2{}  //불가능
  • 최상위 부모는 Object 클래스다.
  • 부모 클래스의 필드와 메소드만 상속된다.

super 키워드

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

상속 받은 멤버와 자식 클래스에 정의된 멤버의 이름이 같을 때는 super를 이용해서 구별할 수 있다.

public class SuperTest {

    public static void main(String[] args) {
        Child c = new Child();
        c.method();;
    }
}

class Parent {
    int x = 10;
}

class Child extends Parent {
    int x = 20;

    void method() {
        System.out.println("x " + x);  // 20
        System.out.println("this.x " + this.x);  // 20
        System.out.println("super.x " + super.x);  // 10
    }
}

메소드 오버라이딩

메소드 오버라이딩은 부모의 메소드를 자식 클래스에서 재정의하는 기능이다.

오버라이딩은 메서드의 내용만을 새로 작성하는 것이므로 메서드의 선언부는 조상의 것과 완전히 일치해야 한다.

자식 클래스에서 오버라이딩하는 메서드는 다음과 같은 조건을 만족해야 한다.

  1. 이름이 같아야 한다.
  2. 매개변수가 같아야 한다.
  3. 반환 타입이 같아야 한다.

다만 접근 제어자와 예외는 제한된 조건 하에서만 다르게 변경할 수 있다.

  • 접근 제어자는 부모 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
    • 부모 클래스의 메서드가 protected라면, 자식 클래스에서 메서드는 protected, public 이어야 한다.
  • 부모 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
    • 아래는 자식 클래스가 부모 클래스보다 예외의 개수가 적으므로 바르게 오버라이딩 했다.
      class Parent {
      		void parentMethod() throws IOException, SQLExcpetion
      }
      
      class Child extends Parent {
      		void parentMethod() throws IOException
      }
    • 단순히 예외의 개수가 아니라 예외의 종류도 중요하다. 아래처름 Exception은 모든 예외의 최고 부모이므로 가장 많은 개수의 예외를 던질 수 있다. 이것은 잘못된 오버라이딩이다.
      class Child extends Parent {
      		void parentMethod() throws Exception
      }

메소드 디스패치

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

자바는 런타임 시 객체를 생성하고, 컴파일 시 생성할 객체 타입에 대한 정보만 보유한다.

메소드 디스패치에는 정적 메소드 디스패치, 동적 메소드 디스패치, 더블 디스패치가 존재한다.

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

컴파일 시점에 컴파일러가 특정 메소드를 호출할 것이라고 명확하게 알고있는 경우다.

컴파일 시 생성된 바이트 코드에도 이 정보가 그대로 남아있다.

public class ACar {
    public void print() { 
        System.out.println("A");
    }
}

public class BCar extends ACar { //메소드 오버라이딩 - ACar상속 후 함수 재정의
    public void print() {
        System.out.println("BCar");
    }
}

public static void main(String[] args) {
    BCar bcar = new BCar();
    System.out.println(bcar.print());  //BCar를 출력.
}

동적 메서드 디스패치

동적 디스패치는 호출할 메서드를 런타임 시점에 결정한다.

컴파일러는 타입만 체크한다.

child1 객체는 Parent 클래스 타입이기 때문에 Child 클래스의 메소드에 접근할 수 없다.

하지만 Child 클래스의 메소드를 호출한다.

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

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

class Child1 extends Parent {
    @Override
    void run() {
        System.out.println("Child1.run()");
    }
}

class Child2 extends Parent {
    @Override
    void run() {
        System.out.println("Child2.run()");
    }
}

class Main {
    public void main(String[] args) {
        Parent child1 = new Child1();
        Parent child2 = new Child2();
        
        child1.run();
        child2.run();
    }
}

추상 클래스

abstract는 미완성의 의미를 가지고 있다.

메서드의 선언부만 작성하고 실제 수행 내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다.

//추상클래스 선언방법
abstract class 클래스이름{
}

추상 클래스는 반드시 하나 이상의 추상 메서드를 포함하고 있다.

생성자와 멤버변수, 일반 메서드 모두 가질 수 있다.

추상 클래스는 자체 인스턴스 생성이 불가능하다.

추상 메서드

추상 메서드는 선언부만 작성하고 구현부는 미완성인 채로 남겨두는 메소드다.

abstract 리턴타입 메서드이름();

자식 클래스에서 반드시 추상메더를 구현해야한다.

만약 구현하지 않을 경우 자식 클래스도 추상클래스여야 한다.

추상 메서드의 접근 지정자는 private이 될 수 없다.


final 키워드

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

final은 클래스, 메서드, 멤버변수, 지역변수에 사용할 수 있다.

class에 final 선언

해당 클래스를 상속할 수 없다.

method에 final 선언

해당 메소드를 더 이상 override 할 수 없다.

변수에 final 선언

값이 변하지 않게 상수값을 지정한다.

인스턴스 변수나 static으로 선언된 클래스 변수는 초기화를 무조건 해줘야한다.

객체 타입에 final 선언

객체 변수에 final로 선언하면 다른 참조 값을 지정할 수 없다.

객체 자체가 immutable 하다는 의미는 아니다.

객체의 속성은 변경 가능하다.

final Pet pet = new Pet();
// pet = new Pet(); //다른 객체로 변경할수 없음
pet.setWeight(3); //객체 필드는 변경할 수 있음

Object 클래스

최상위 클래스이며, 모든 클래스는 Objcet 클래스를 상속받는다.

자바의 모든 클래스들은 Object 클래스에 정의된 멤버들을 사용할 수 있다.

메서드설명
protected Object clone()객체 자신의 복사본을 반환한다.
public boolean equals(Object obj)객체 자신과 객체 obj가 같은 객체인지 알려준다.
protected void finalize()객체가 소멸될 때 가비지 컬렉터에 의해 자동적으로 호출한다.
public Class getClass()객체 자신의 클래스 정보를 담고 있는 인스턴스를 반환한다.
public int hashCode()객체 자신의 해시코드를 반환한다.
public String toString()객체 자신의 정보를 문자열로 반환한다.
public void notify()기다리고 있는 쓰레드 하나만 깨워준다.
public void notifyAll()기다리고 있는 쓰레드를 모두 깨워준다.
public void wait()쓰레드를 무한히 또는 지정된 시간 동안 기다린다.
public void wait(long timeout)"
public void wait(long timeout, int nanos)"

final 객체를 불변으로

자바에서 불변 객체를 생성하기 위해서 다음과 같은 규칙에 따라서 클래스를 생성해야 한다.

  1. 클래스를 final로 선언하라
  2. 모든 클래스 변수를 private와 final로 선언하라
  3. 객체를 생성하기 위한 생성자 또는 정적 팩토리 메소드를 추가하라
  4. 참조에 의해 변경가능성이 있는 경우 방어적 복사를 이용하여 전달하라
public final class ImmutableClass {
    private final int age;
    private final String name;
    private final List<String> list;

    private ImmutableClass(int age, String name) {
        this.age = age;
        this.name = name;
        this.list = new ArrayList<>();
    }

    public static ImmutableClass of(int age, String name) {
        return new ImmutableClass(age, name);
    }
    
    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public List<String> getList() {
        return Collections.unmodifiableList(list);
    }
    
}

상속과 위임

상속은 속성이나 기능의 상속, 즉 재사용을 위해 존재한다고 오해하고 있다.

이는 사실이 아니다.

만약 ArrayList를 상속 받아 Stack 클래스를 만들었다고 가정을 하면, Stack은 ArrayList에 있는 isEmptysizeaddremove 메서드를 그대로 사용하길 원한다.

기능의 재사용이라는 측면으로만 보면 성공적이라고 볼 수 있다.

그러나 ArrayList 클래스에 정의된 스택과 관련 없는 수많은 연산이나 속성도 같이 상속 받는다.

이런 불필요한 속성은 도움이 되기보다는 도움이 안 될 가능성이 크다.

class MyStack<String> extends ArrayList<String> {
		public void push(String e) {
			add(element);  // ArrayList의 메소드
		}

		public String pop() {
			return move(size() - 1);  // ArrayList의 메소드
		}
}

기본적으로 상속 관계는 ‘is a kind of 관계’가 성립되어야 한다.

다음 문장은 성립하지 않는다.

Stack is a kind of ArrayList

클래스의 일부 기능만 재사용하고 싶은 경우에는 위임(delegation)을 사용하면 된다.

위임은 자신이 직접 기능을 실행하지 않고 다른 클래스의 객체가 기능을 실행하도록 하는 것이다.

상속 관계는 클래스 사이의 관계지만 위임은 객체 사이의 관계다.

다음은 위임을 사용해 상속을 대신하는 과정이다.

  1. 자식 클래스에 부모 클래스의 인스턴스를 참조하는 속성을 만든다.
  2. 자식 클래스에 정의된 각 메서드에 1번에서 만든 위임 속성 필드를 참조하도록 변경한다.
  3. 자식 클래스에서 상속 관계를 제거하고, 위임 속성 필드에 부모 클래스의 객체를 생성해 대입한다.
  4. 자식 클래스에서 사용된 부모 클래스의 메소드에서도 위임 메서드를 추가한다.
  5. 컴파일하고 잘 동작하는지 확인한다.
class MyStack<String> {
	private ArrayList<String> arrList = new ArrayList<>();

	public void push(String e) {
		arrList.add(element);
	}

	public String pop() {
		return arrList.remove(arrList.size() - 1);
	}

}

0개의 댓글