7.1 상속 개념
상속(Inheritance)란 부모가 자식에게 물려주는 행위이다. 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여 개발 시간을 단축시킨다. 클래스의 수정을 최소화할 수도 있다. 부모 클래스를 수정하면 모든 자식 클래스에 수정 효과를 가져온다.
7.2 클래스 상속
자식이 부모를 선택한다. 자식 클래스를 선언할 때 상속받을 부모를 결정하고, 부모 클래스를 extends 뒤에 기술한다. 다중 상속을 허용하지 않으므로 extends 뒤에는 단 하나의 부모 클래스만이 와야 한다.
public class 자식클래스 extends 부모클래스 {
}
7.3 부모 생성자 호출
자식 객체를 생성하면 부모 객체가 먼저 생성된 다음 자식 객체가 생성된다.
자식클래스 변수 = new 자식클래스();
부모 객체를 포함한 모든 객체는 생성자를 호출해야만 생성된다. 부모 객체의 생성자는 자식 생성자의 맨 첫 줄에 숨겨져 있는 super()에 의해 호출된다. 컴파일 과정에서 자동 추가되는 super()는 부모의 기본 생성자를 호출한다. 부모 클래스에 기본 생성자가 없다면 자식 생성자 선언에서 컴파일 에러가 발생한다.
// 자식 생성자 선언
public 자식클래스(···) {
super();
···
}
부모 클래스에 매개변수를 갖는 생성자만 있다면 super(매개값, ···) 코드를 넣어야 한다. 매개값의 타입과 개수가 일치하는 부모 생성자를 호출한다.
// 자식 생성자 선언
public 자식클래스(···) {
super(매개값, ···);
···
}
7.4 메소드 재정의
부모 클래스에서 자식 클래스가 사용하기에 적합하지 않은 몇몇의 메소드는 자식 클래스에서 재정의해서 사용해야 한다. 이것을 메소드 오버라이딩(Overriding)이라고 한다.
메소드 오버라이딩
메소드 오버라이딩은 상속된 메소드를 자식 클래서 재정의하는 것이다. 메소드가 오버라이딩되었다면 해당 부모 메소드는 숨겨지고, 자식 메소드가 우선적으로 사용된다. 자바는 개발자의 실수를 줄여주기 위해 @Override를 붙여 정확히 오버라이딩이 되었는지 체크해준다.
메소드를 오버라이딩할 때는 다음과 같은 규칙을 주의해야 한다.
- 부모 메소드의 선언부(리턴 타입, 메소드 이름, 매개변수)와 동일해야 한다.
- 접근 제한을 더 강하게 오버라이딩할 수 없다(public → private으로 변경 불가).
- 새로운 예외를 throws할 수 없다.
부모 메소드 호출
메소드를 재정의하면, 부모 메소드는 숨겨지고 자식 메소드만 사용되기 때문에 부모 메소드의 일부만 변경된다 하더라도 중복된 내용을 자식 메소드도 가지고 있어야 한다.
이 문제는 자식 메소드 내에서 super 키워드와 도트(.) 연산자를 사용하여 숨겨진 부모 메소드를 호출하면 된다.
// 부모 클래스
class Parent {
public void method() {
// 작업 처리 1
}
}
// Parent 클래스에 상속된 자식 클래스
class Child extends Parent {
// 재정의된 메소드
// 부모 메소드 호출
@Override
public void method() {
super.method();
// 작업 처리 1
}
}
7.5 final 클래스와 fianl 메소드
final 클래스
클래스를 선언할 때 키워드를 class 앞에 붙이면 최종적인 클래스이므로 더 이상 상속할 수 없는 클래스가 된다. 즉 final 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.
public final class 클래스 { ··· }
final 메소드
메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종적인 메소드이므로 오버라이딩할 수 없는 메소드가 된다. 즉 부모 클래스를 상속해서 자식 클래스를 선언할 때 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의할 수 없다.
public final 리턴타입 메소드(매개변수, ···) { ··· }
7.6 protected 접근 제한자
protected는 상속과 관련이 있고, public과 default의 중간쯤에 해당하는 접근 제한을 한다.
접근 제한자 | 제한 대상 | 제한 범위 |
protected | 필드, 생성자, 메소드 | 같은 패키지이거나, 자식 객체만 사용 가능 |
protected는 같은 패키지에서는 default처럼 접근이 가능하나, 다른 패키지에서는 자식 클래스만 접근을 허용한다. protected는 필드와 생성자 그리고 메소드 선언에 사용될 수 있다.
// package1
public class A {
protected 멤버
}
public class A {
// package1에 있는 A 클래스의 멤버 사용 가능
}
// package2
class C {
// package1에 있는 A 클래스의 멤버 사용 불가능
}
class D extends A {
// package1에 있는 A 클래스의 멤버 사용 가능
}
7.7 타입 변환
타입 변환이란 타입을 다른 타입으로 변환하는 것을 말한다. 타입 변환은 클래스도 가능한데, 클래스 타입 변환은 상속 관계에 있는 클래스 사이에서 발생한다.
자동 타입 변환
자동 타입 변환(Promotion)은 자동적으로 타입 변환이 일어나는 것을 말한다. 다음과 같은 조건에서 일어난다.
자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다. 자식 객체를 부모 객체로 선언한 부모 변수에 대입하면 자동 타입 변환이 일어난다. 자식 변수와 부모 변수는 타입만 다를 뿐, 동일한 부모 객체를 참조한다. 따라서 두 참조 변수의 == 연산 결과는 true가 나온다.
자식-변수 == 부모-변수 // 결과 : true
바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다.
B b = new B();
C c = new C();
D d = new D();
E e = new E();
A a1 = b; // (O)
A a2 = c; // (O)
A a3 = d; // (O)
A a4 = e; // (O)
B b1 = d; // (O)
C c1 = e; // (O)
B b3 = e; // (X)
C c2 - d; // (X)
부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다. 변수는 자식 객체를 참조하지만 변수로 접근 가능한 변수로 접근 가능한 멤버는 부모 클래스 멤버로 한정된다.
그러나 자식 클래스에서 오버라이딩된 메소드가 있다면 부모 메소드 대신 오버라이딩된 메소드가 호출된다.
강제 타입 변환
자식 타입은 부모 타입으로 자동 변환되지만, 부모 타입은 자식 타입으로 자동 변환되지 않는다. 대신 다음과 같이 캐스팅 연산자로 강제 타입 변환(Casting)을 할 수 있다.
그렇다고 해서 부모 타입 객체를 자식 타입으로 무조건 강제 변환할 수 있는 것은 아니다. 자식 객체가 부모 타입으로 자동 변환된 후 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다.
Parent parent = new Child(); // 자동 타입 변환
Child child = (Child) parent; // 강제 타입 변환
자식 객체가 부모 타입으로 자동 변환하면 부모 타입에 선언된 필드와 메소드만 사용 가능하다. 자식 타입에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환해야 한다.
7.8 다형성
다형성(多形性)이란 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성칠을 말한다. 객체는 부품과 같아서, 프로그램을 구성하는 객체를 바꾸면 프로그램의 실행 성능이 다르게 나올 수 있다.
객체 사용 방법이 동일하다는 것은 동일한 메소드를 가지고 있다는 뜻이다. 부모 객체를 상속받은 서로 다른 자식들은 부모의 메소드를 동이랗게 가지고 있다고 말할 수 있다.
서로 다른 자식들이 부모의 메소드를 오버라이딩하고 있다면, 타이어 메소드 호출 시 오버라이딩된 메소드가 호출된다. 오버라이딩된 내용은 두 자식이 다 다르기 때문에 결과가 다르게 나온다. 이것이 바로 다형성이다. 다형성을 구현하기 위해서는 자동 타입 변환과 메소드 재정의가 필요하다.
필드 다형성
필드 다형성은 필드 타입은 동일하지만(사용 방법은 동일하지만), 대입되는 객체가 달라져서 실행 결과가 다양하게 나올 수 있는 것을 말한다.
매개변수 다형성
다형성은 필드보다는 메소드를 호출할 때 많이 발생한다. 메소드가 클래스 타입의 매개변수를 가지고 있을 경우, 호출할 때 동일한 타입의 객체를 제공하는 것이 정석이지만 자식 객체를 제공할 수도 있다. 여기서 다형성이 발생한다.
7.9 객체 타입 확인
매개변수의 다형성에서 실제로 어떤 객체가 매개값으로 제공되었는지 확인하는 방법은 instanceof 연산을 사용하는 것이다. 꼭 매개변수가 아니더라도 변수가 참조하는 객체의 타입을 확인할 수 있다.
instanceof 연산자의 좌항에는 객체가, 우항에는 타입이 오는데, 좌항의 객체가 우항의 타입이면 true를 산출하고 그렇지 않으면 false를 산출한다.
boolean result = 객체 instanceof 타입;
7.10 추상 클래스
사전적 의미로 추상(abstract)은 실체 간에 공통되는 특성을 추출한 것을 말한다.
추상 클래스란?
객체를 생성할 수 있는 클래스를 실체 클래스라고 한다면, 이 클래스들의 공통적인 필드나 메소드를 추출해서 선언한 클래스를 추상 클래스라고 한다.
추상 클래스는 실체 클래스의 부모 역할을 한다. 따라서 실체 클래스는 추상 클래스를 상속해서 공통적인 필드나 메소드를 물려받을 수 있다.
추상 클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만들었기 때문에 new 연산자를 사용해서 객체를 직접 생성할 수 없다.
추상 클래스는 extends 뒤에만 올 수 있다. 새로운 실체 클래스를 만들기 위한 부모 클래스로만 사용되기 때문이다.
추상 클래스 선언
public abstract class 클래스명 {
// 필드
// 생성자
// 메소드
}
추상 클래스는 상속을 통해 자식 클래스만 만들 수 있다. 자식 객체가 생성될 때 super()로 추상 클래스의 생성자가 호출되기 때문에 생성자도 반드시 있어야 한다.
추상 메소드의 재정의
자식 클래스들이 가지고 있는 공통 메소드를 뽑아내어 추상 클래스로 작성할 때, 메소드 선언부(리턴타입, 메소드명, 매개변수)만 동일하고 실행 내용은 자식 클래스마다 달라야 하는 경우가 많다.
이런 경우 추상 클래스는 추상 클래스 안에 다음과 같은 추상 메소드를 선언하면 된다. 자식 클래스의 공통 메소드라는 것만 정의할 뿐, 실행 내용을 가지지 않는다. 추상 메소드는 자식 클래스에서 반드시 재정의(오버라이딩)해서 실행 내용을 채워야 한다.
abstract 리턴타입 메소드명(매개변수, ···);
7.11 봉인된 클래스
Java 15부터 무분별한 자식 클래스 생성을 방지하기 위해 봉인된(sealed) 클래스가 도입되었다.
다음과 같이 부모의 자식 클래스는 permits 뒤에 오는 자식 클래스만 가능하고 그 이외는 자식 클래스가 될 수 없도록 부모 클래스를 봉인된 클래스로 선언할 수 있다.
public seald class 부모클래스 permits 자식클래스1, 자식클래스2, ··· { ··· }
sealed 키워드를 사용하게 되면 permits 키워드 뒤에 상속 가능한 자식 클래스를 지정해야 한다. 봉인된 부모 클래스를 상속하는 자식 클래스들은 final 또는 non-sealed 키워드로 다음과 같이 선언하거나, sealed 키워드를 사용해서 또 다른 봉인 클래스로 선언해야 한다.
public final class 자식클래스1 extends 부모클래스 { ··· }
public non-sealed class 자식클래스2 extends 부모클래스 { ··· }
final은 더 이상 상속할 수 없다는 뜻이다. non-sealed 봉인을 해제한다는 뜻이므로 따라서 자식클래스1은 더 이상 자식 클래스를 만들 수 없지만 자식클래스2는 자식 클래스를 만들 수 있다(상속할 수 있다).
'JAVA 개념 > Part 02; 객체 지향 프로그래밍' 카테고리의 다른 글
[이것이 자바다] CHAPTER 05. 참조 타입 (1) | 2024.06.16 |
---|---|
[이것이 자바다] CHAPTER 11. 예외 처리 (1) | 2024.06.11 |
[이것이 자바다] CHAPTER 09. 중첩 선언과 익명 객체 │ 개념 (0) | 2024.06.08 |
[이것이 자바다] CHAPTER 08. 인터페이스 │ 개념 (1) | 2024.06.08 |