[JAVA] 상속

SungBum Park·2021년 3월 7일
0

JAVA

목록 보기
6/9
post-thumbnail

자바 상속

상속(Inheriance)이란, 상위 클래스의 정보를 하위 클래스에게 물려주는 것을 말한다. 상속은 객체지향 프로그래밍의 핵심인 다형성을 가능하게 해주는 방법이다.

  • 상위 클래스 = 부모 클래스 = 베이스 클래스
  • 하위 클래스 = 자식 클래스 = 파생 클래스

상속 사용하기

자바는 상속을 하기 위해 extends 라는 키워느를 사용한다. 즉, 상위 클래스를 확장하여 하위 클래스를 만들겠다는 의미이다.(상위 클래스의 정보를 사용하겠다.)

예를들어, 다음과 같은 상속 관계가 있다.

  • Animal(상위 클래스)
    • Dog(하위 클래스)
    • Cat(하위 클래스)
public class Animal {
    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

위 Animal 클래스를 상속하여 Dog, Cat 클래스를 만들어보자.

public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }
}
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
}

상속은 하위 클래스에서 상위 클래스의 정보를 사용할 수 있어야한다. 이 의미는 하위 클래스를 인스턴스화했을 때, 상위 클래스도 같이 인스턴스화를 해야 한다는 의미이다. 이를 이해하고 위 코드를 보면 이해할 수 있을 것이다.

Animal 클래스는 디폴트 생성자를 없애고, name과 age를 받는 생성자를 직접 만들었다. 그러면, 하위 클래스인 Dog과 Cat 클래스는 자신이 생성될 때 상위 클래스인 Animal를 생성해야하기 때문에 명시적으로 Animal의 생성자를 호출해야 한다. 이 동작이 super() 로 이루어진다.(이는 아래에서 더욱 자세히 다룸.)

정리하면, 상속 관계에서 하위 클래스를 인스턴스화하면 상위 클래스 역시 같이 인스턴스화하기 때문에 이를 사용할 수 있다.

자바 상속의 특징

자바에서 상속의 특징은 두 가지 관점에서 살펴보도록 하자.

문법적인 관점

  • 기본적으로 클래스간의 확장을 상속이라고 말한다.
  • 다중 상속이 불가능하다.

자바는 다중상속을 지원하지 않는다. 즉 다음과 같은 상속 관계를 가질 수 없다.

public class Liger extends Tiger, Lion {
	// ...
}

위 코드는 자바에서 컴파일 에러가 발생한다. 문법 오류인 것이다. 만약 위 그림과 같은 상속 관계를 가지면 다음과 같은 문제점이 발생한다.

  1. 여러 개의 상위 클래스에서 같은 이름의 메서드가 있는 경우 누구를 호출할지 모른다.
  2. 여러 개의 상위 클래스가 같은 상위 클래스를 갖고 있는 경우(Animal) 해당 최상위 클래스는 중복되어 인스턴스화하여 메모리를 차지한다.

이와 같은 문제를 다이아몬드 문제라고 부른다. 다중 상속에서 대표적으로 발생하는 문제이다. 이러한 이유로 자바는 다중 상속을 지원하지 않는다. 물론, 모든 언어에서 다중 상속을 지원하지 않는 것은 아니다. 대표적으로 C++은 다중 상속을 지원한다. 따라서 위 문제가 발생하지만, 해결이 가능하다. 하지만 비효율적인 방법이므로 최대한 다중 상속을 피하는 것이 베스트 프랙티스이다.

자바는 다중 상속을 지원하지 않지만, 다중 상속과 같은 상황이 필요할 때가 있다. 이를 위해서 인터페이스가 존재하며, 인터페이스는 다중 구현이 가능하다.(상속과 구현이 동시에 존재할 수도 있다.)

객체지향적인 관점

  • IS-A 관계가 확실할 때만 사용하는 것이 좋다.

상속은 상위 클래스와 하위 클래스의 의존성이 매우 크다. 상위 클래스의 변경이 모든 하위 클래스에 전파된다. 이는 객체지향 프로그래밍에서 추구하는 유연성에 크게 벗어난다. 따라서 대부분 객체지향 프로그래밍에서 상속은 IS-A 관계가 확실하고, 변경가능성을 최대한 살펴본 이후에 하는 것이 좋다. "상속보다는 조합(컴포지션)을 사용하라"(이펙티브 자바) 라는 말이 있듯이, 상속은 신중하게 사용하자.

  • 상속은 sub-typing과 sub-classing으로 나뉜다.

상속은 객체지향에서 중요한 다형성을 할 수 있는 방법이라고 앞서 설명했다. 이는 상속이 sub-typing이 가능하기 때문이다. sub-typing은 상위 클래승의 타입을 하위 클래스에게 물려주는 것을 말한다.

Animal dog = new Dog("바둑이", 3);
dog.cry();

객체 선언은 분명히 Animal인데, 객체 생성은 Dog을 하고 있다. 그리고 cry() 메서드를 호출하며 정상적으로 동작이 가능하다. Dog 타입을 사용하는 것이 아닌, 상위 클래스인 Animal 타입을 사용하는 것이다. 사실 이 모습이 다형성의 전부이다. 그리고 객체지향에서 매우 중요한 부분이다. (물론, 위 상황은 Animal 클래스에서 cry() 메서드가 있고, Dog 클래스에서 이를 재정의하거나 해야한다.)

sub-classing은 단순히 상위 클래스의 코드를 하위 클래스에게 물려주는 것을 말한다. 이는 단순히 코드 중복을 줄이기 위한 방법으로 사용한다.

super 키워드

super 키워드는 이전 주차에서 this 키워드를 살펴보면서 간단히 설명했다. this 키워드가 자기 자신의 객체를 참조하여 사용하는 참조 변수라면, super 키워드는 상위 클래스로부터 상속받은 필드나 메소드를 다식 클래스에서 참조하는데 사용하는 참조 변수이다.

public class Animal {
	protected String name;
	protected int age;

	Animal(String name, int age) {
		this.name = name;
		this.age = age;
	}
}
public class Dog extends Animal {
	private String ownerName;

	Dog() {
		super("바둑이", 3);
		this.ownerName = "홍길동";
	}

	public String getName() {
		return super.name;
	}

	// ...
}
  • super: 상위 클래스의 멤버 변수에 접근할 때 사용한다.
    • 상위 클래스의 멤버 변수에 접근할 때는 항상 해당 멤버 변수의 접근제어자를 확인한다.
  • super(): 상위 클래스의 생성자를 호출할 때 사용한다.

메소드 오버라이딩

메서드 오버라이딩은 메서드를 재정의를 하는 것이다. 메서드 재정의는 같은 선언부를 가진 메서드지만 내부 구현을 다르게 하는 것을 말한다. 따라서 같은 클래스 내에서는 오버라이딩이 불가능하며, 상속 또는 인터페이스 구현을 통해 다른 클래스에서 수행할 수 있다.(같은 클래스 내부에서 오버라이딩을 하면, 외부에서 해당 메서드를 호출할 때 둘 중 어느 것을 선택할 지 알 수 없으므로 컴파일 에러가 발생한다.)

public abstract class Animal {
	public abstract void speak();
}
public class Dog extends Animal {
	// ...

	@Override
	public void speak() {
		System.out.println("멍멍!");
	}
}

메서드 오버라이딩은 객체지향 프로그래밍 언어인 자바에서 다형성을 구현하기 위해 사용되는 매우 중요한 개념이다.

오버로딩(Overloading)

메서드 오버로딩은 메서드의 시그니처 중 매개변수를 다르게 선언하여, 같은 이름의 메서드를 여러 개 구현할 수 있는 방법이다.

public int calculate(int a, int b) {
	return a + b;
}

public long calculate(Long a, Long b) {
	return a + b;
}

public float calculate(float a, float b) {
	return a + b;
}
assertThat(calculate(1, 2)).isEqualTo(3);
assertThat(calculate(10L, 20L)).isEqualTo(30);
assertThat(calculate(1.2f, 2.3f)).isEqualTo(3.5);

위는 메서드 오버로딩을 사용하는 예제이다. 주의할 점은 <반환 타입>은 메서드 시그니처에 포함되지 않으므로, <반환 타입>만 다르게 해서는 오버로딩을 할 수 없다.

메서드 오버로딩을 사용하는 대표적인 곳은 생성자이다. 흔히 생성자 오버로딩이라 부르며, 방법은 메서드 오버로딩을 하는 방법과 같다.

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

메소드 디스패치는 어떤 메소드를 호출할지 결정하여 실제로 실행시키는 과정을 말한다. 이는 크게 정적 메소드 디스패치(Static Method Dispatch)와 동적 메소드 디스패치(Dynamic Method Dispatch)로 나뉜다. 이 용어는 정적 바인딩 그리고 동적 바인딩이라고 부를 수도 있다.

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

프로그래밍에서 정적은 대부분 컴파일 시간에 결정되는 것을 의미한다. 정적 메소드 디스패치 역시, 컴파일 시간에 메소드를 결정하여 실행하는 것을 말한다. 핵심은 컴파일 시간에 메소드를 결정할 수 있다는 것이다.

class Note {
	private String memo;
	
	public void printMemo() {
		System.out.println(memo);
	}
}

// ...

Note note = new Note();
note.printMemo();

정적 메소드 디스패치는 위와 같이 객체의 선언 타입과 실제 생성한 객체 타입이 같을 때이다. 컴파일은 선언 타입과 생성 타입이 같으므로, 명확히 호출할 메소드를 알 수 있다.

동적 메소드 디스패치

정적 메소드 디스패치와 반대로, 동적 메소드 디스패치는 컴파일 시간이 아닌 런타임 시간에 메소드를 결정하여 실행하는 것을 말한다. 즉, 객체의 선언 타입과 생성 타입이 다른 경우이다.

public class Animal {
    protected String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void cry() {
        System.out.println("ddd");
    }
}
public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    public void cry() {
        System.out.println("멍멍");
    }
}
Animal dog = new Dog("바둑이", 3);
dog.cry();

위 코드에서 객체의 선언 타입은 Animal 이고, 생성 타입은 Dog 이다. 컴파일 시간에는 호출한 cry() 메소드가 Animal.cry() 일지, Dog.cry() 일지 판단하지 못한다. 따라서 실제 런타임 시점에 어떤 객체가 생성되어 할당되었는지 판단하게 된다.

사실, 위 코드만 봐서도 Animal을 사용할지 Dog을 사용할지 명시적으로 알려주는 코드는 없다. 왜냐하면 자바는 기본적으로 객체의 선언 타입과 생성 타입이 다를 경우 생성 타입을 사용하도록 되어 있다. (반면에 C++은 이와 같은 상황에서 생성 타입을 사용하려면 virtual 이라는 키워드를 명시해야 한다.)

추상 클래스

추상 클래스(Abstract Class)는 추상 메서드(abstract method)를 하나 이상 가지고 있는 클래스를 말하며, class 앞에 abstract 키워드를 붙여주어 선언한다.

추상이라는 단어는 복잡성을 이해하기 쉬운 정도로 단순화하는 것을 말한다. 따라서 추상 클래스와 추상 메서드는 복잡한 구현부는 제외하고 선언부만을 구현하여 단순화한 모습이다.

public abstract class Animal {
    protected String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public abstract void cry();
}

위 코드는 추상 클래스 Animal 클래스와 추상 메서드 cry() 메서드의 모습이다. 둘 다 abstract 키워드를 앞에 선언했으며, 추상 메서드는 구현부가 없다. 여기서 추상 메서드가 왜 쓰이는지 감을 잡을 수 있을 것이다. 동물은 종류에 따라 우는 모습이 다르다. 따라서 Animal 클래스는 그 다른 모습의 구현부를 일일이 신경쓰지 않고 선언부만으로 추상화하여, 실제 구현은 이를 상속하는 객체에게 책임을 분리했다. 따라서 코드는 좀 더 깔끔하고 이해하기 좋다.

public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void cry() {
        System.out.println("멍멍");
    }
}
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void cry() {
        System.out.println("야옹");
    }
}

DogCat 은 각각 cry() 추상 메서드를 실제 구현한 모습이다.

추상 클래스는 다음과 같은 특징을 갖고 있다.

  • 추상 메서드는 추상 클래스를 상속하는 하위 클래스에서 반드시 구현되어야 한다.(하지 않으면 컴파일 에러)
  • 추상 클래스 자체는 객체를 생성할 수 없다.(인스턴스화 불가능)

사실, 추상 메서드로 선언하지 않아도 하위 클래스에서는 오버라이딩이 기본적으로 가능하다. 하지만 추상 클래스를 통해 좀 더 코드를 직관적으로 구현할 수 있다. 대부분의 구현은 여러 사람이 함께 하므로, 강제성을 통해 의도를 확실히 나타내는 것이 중요하다. 따라서 추상 메서드를 통해 이를 반드시 구현하여 사용해야 한다는 강제성을 부여할 수도 있다.

final 키워드

자바에서 final은 단 한 번만 할당할 수 있도록 제한해주는 키워드이다. 그리고 클래스, 메서드, 변수에 선언함에 따라 각각 다음과 같은 작업을 수행한다.

  • 클래스: 해당 클래스는 다른 클래스가 상속할 수 없다.
  • 메서드: 해당 메서드는 오버라이딩(@Override)을 할 수 없다.(오버로딩은 해당되지 않는다.)
  • 변수: 해당 변수는 반드시 초기화되어야 하며, 그 후 재할당은 할 수 없다.
    • primitive type은 불변으로 취급된다.
    • reference type은 내부의 멤버 변수 값은 변경할 수 있다.(완전한 불변이 아니다.)

만약 이를 시도할 경우 컴파일 에러가 발생한다.

Object 클래스

Object 클래스는 java.lang 패키지의 최고 조상 클래스이다. Object 클래스는 java.lang 패키지뿐 아니라 자바의 모든 클래스가 기본적으로 상속하고 있다. 따라서 어떤 객체든지 Object 클래스 내부의 메서드를 사용하거나 재정의할 수 있다.

java.lang 패키지는 자바에서 가장 기본적인 동작을 수행하는 클래스의 집합다. 따라서 java.lang 패키지를 사용하기 위해서는 따로 import 문을 선언할 필요가 없다.

Object 클래스의 메서드

참고자료


profile
https://parker1609.github.io/ 블로그 이전

1개의 댓글

comment-user-thumbnail
2023년 8월 17일

귀하의 블로그는 환상적입니다. 여기에 매우 흥미로운 게시물이 있습니다. 시간을 내어 이 정보를 공유해 주셔서 감사합니다. 매우 도움이 됩니다. free games

답글 달기