클래스 게시물과 이어지는 내용 이전글. java-클래스
상속이란 기존 클래스의 기능과 속성을 새로운 클래스가 물려받는 것이다. 클래스를 만들고 이와 같은 성질을 가진 클래스가 많이 필요할 때, 상속의 개념을 적용한다.
기존의 클래스를 부모 클래스, 슈퍼 클래스, 상위 클래스라고 하고 새로운 클래스를 자식 클래스, 서브 클래스 또는 하위 클래스라고 부른다.
키워드 extends를 사용해 자식 클래스는 부모 클래스의 멤버를 상속받는다.
class 부모클래스 {
int 속성1;
String 속성2;
void 기능1 () {
System.out.println("기능1 실행");
}
}
class 자식클래스 extends 부모클래스 {
// 멤버가 없음
}
위 코드를 보면 자식 클래스 바디에는 멤버가 없다. 하지만 extends키워드로 부모 클래스의 멤버를 상속 받았기 때문에 자식 클래스는 부모 클래스의 멤버를 사용할 수 있다. 아래와 코드는 위 코드와 이어진다.
자식클래스 자식1 = new 자식클래스();
자식1.속성1 = 10;
System.out.println(자식1.속성1); // 출력 : 10
자식1.기능1(); // 출력 : 기능1 실행
부모 클래스의 멤버를 물리적으로 복사하여 사용할 수 있는 게 아니라 부모와 연결되어 부모의 멤버를 사용할 수 있는 것이다.
상속은 한 번만 가능한 게 아니다. 부모 > 자식(과 동시에 부모) > 자식··· 의 형태로 구성할 수도 있으며, 이러한 상태를 복잡한 상속이라고 한다.
class 동물 {
void 수면 () {
System.out.println("zzz");
}
}
class 고양이 extends 동물 { // 동물 클래스를 상속 받음
}
class 샴 extends 고양이 { // 동물 클래스를 상속 받은 고양이 클래스를 상속받음
}
상속 관계를 구상할 때는 AB테스트를 통해 올바른 상속 관계를 구상할 수 있다. class 고양이 extends 동물 {} 에 AB테스트를 적용해보자.
A(고양이) is(extends) B(동물) -> 통과
하지만 이 테스트의 결과가 참인지 거짓인지는 사람만 알 수 있다. 프로그램은 모른다.
abstract 메서드라고도 한다. 부모 클래스에서 메서드를 정의만 하고(추상적) 이를 각각의 자식 메서드에서 구현(구체적)한다.
상위 클래스에서 하위 클래스의 메서드를 잃지 않기 위해 추상 메서드를 가지기도 한다.
abstract class abClass {
abstract void method1();
}
abstract 키워드를 클래스명 앞에 붙인다.추상클래스는 abstract메서드(추상메서드)를 한개 이상 가진 클래스이다. 추상 메서드와 구현 메서드를 동시에 가질 수 있다.
클래스 내부의 모든 메서드가 abstract메서드로 구성이 된 클래스는 인터페이스다.
인터페이스를 상속 받기 위해서는 extends 대신 implements 키워드를 사용한다.
대신 인터페이스를 impliments 하면 하위 클래스에서 메서드 오버라이딩할 때는 public 키워드를 붙여줘야한다.
인터페이스는 다중 상속과 다중 구현이 가능하다.
만약 추상 클래스 다중 상속이 가능하다고 가정해보자.
abstract class a { // 추상 클래스1
void m();
}
abstract class b { // 추상 클래스2
void m() {
}
}
class c extends a, b { // 하위 클래스
}
위와 같은 클래스 3개를 만들고 c는 a와 b를 상속받는다.
c c1 = new c();
c.m();
c의 인스턴스를 만들고 메서드 m을 호출한다. 그럼 어떤 메서드를 실행해야 할까?
이러한 문제 때문에 추상 클래스는 다중 상속을 지원하지 않는다.
하지만 인터페이스는 다중 상속이가능하다.
왜냐하면 메서드 오버라이딩이 필수이기 때문에 인터페이스를 상속받은 클래스는 메서드를 재정의하여 모호한 상황이 발생하지 않는다.
추상 클래스 한 개와 인터페이스 여러개를 상속 받는 것도 가능하다.
class d extends abClass implements inClass, in2Class {
}
| 차이점 | 추상 클래스 | 인터페이스 |
|---|---|---|
| 목적 | 상속 받아서 기능 확장 | 하위클래스의 동일한 기능 실행 보장 |
| 메서드 | 동시에 구상 메서드 포함 가능 | 오직 추상 메서드만 가능 |
| 상속 키워드 | extends(확장) | implements(구현) |
| 다중상속 여부 | 불가능 | 가능 |
1. abstract 메서드의 리턴 타입
라는 생각이 들었다. 아래의 코드처럼 말이다.
고양이 a = new 검은고양이();
a.울다();
abstract class 고양이 {
abstract void 야옹(); // abstract 메서드 리턴 타입 : void
}
class 검은고양이 impliments 고양이 {
String 야옹() { //
return "야옹"; // 오버라이딩 메서드 리턴 타입 : String
}
}
1-2. 해답
결론부터 말하자면 두 리턴타입은 다를 수도 있다. Java 5.0 이전에는 메서드를 재정의할 때 매개변수와 반환 유형이 모두 정확히 일치해야 했지만 Java 5.0 이후는 다른 리턴타입을 가질 수 있다.
하지만 기본적으로 abstract 메서드의 리턴타입은 오버라이딩 메서드와 같은 리턴타입을 갖고 있어야한다.
일단 리턴타입이 void이거나 기본형인 경우 동일해야한다. abstract 메서드의 리턴타입은 void이고 오버라이딩 메서드의 리턴타입은 String일 수 없다는 것이다.
Java가 허용하는 안에서 두 리턴타입은 다를 수 있다. 두 리턴타입이 다를 수 있는 경우를 정리한다.
리턴타입이 다른 경우
리턴타입이 하위 타입인 경우 리턴타입이 다를 수 있다.
예를 들어 (상위) A > B > C (하위) 클래스 3개가 있다고 가정할 때, abstract 메서드의 리턴타입이 A라면 오버라이딩 메서드는 A, B, C중 한가지 즉, 하위유형을 반환할 수 있다. 이를 공변 반환(Covariant Return)이라고도 한다.
가벼운 궁금증으로 시작했는데 이를 해결하기 위해 해외 사이트까지 변역해서 찾아봤다. 리턴 타입이 어떤 경우에 필요한 지 모르는 단계라 직접 코드를 작성하면서 깨닫지 못하는 게 좀 아쉽다. 찾으면서 도움이 되었던 사이트를 첨부한다. (첫번째 하이퍼링크는 강사님도 추천한 사이트라서 자주 사용하는 것이 좋을 듯)
참고 자료
메서드 오버라이딩 반환 타입
java 튜토리얼
2. interface의 존재 이유
interface 클래스는 상속 받아도 어짜피 내부의 메서드를 무조건 재정의 해야한다. 그럼 중복 코드가 오히려 는다고 볼 수 있지 않은가? interface 클래스는 왜 필요한 걸까.
아래 코드와 함께 보자.
고양이 a = new 검은고양이();
interface 고양이 {
void 야옹();
void 식빵();
void 하악();
}
class 검은고양이 implements 고양이 {
void 야옹(){
System.out.println("야옹");
}
void 식빵(){
System.out.println("식빵 굽기");
}
void 하악(){
System.out.println("캬아아악!!");
}
}
검은고양이 클래스는 interface 클래스(고양이 클래스)를 implements(구현)하기 때문에 고양이 클래스의 메서드를 모두 오버라이딩 해야한다.
객체지향언어는 중복 코드를 줄이고 코드를 재사용하는 목적이 강하다. interface 클래스는 객체지향언어의 목적에 맞지 않는 것 같다.
2-2. 해답
interface를 사용하면 중복 코드가 많아질 수도 있지만 다형성을 활용할 수 있다. 또 interface는 다중 상속이 가능하고 interface로 하위 클래스에 메서드 오버라이딩을 강제할 수 있다.
이렇게 상속의 개념과 종류를 정리하고 공부하면서 생겼던 궁금증까지 정리를 마쳤다... 처음 보는 키워드가 많이 나와 헷갈리기도 하지만 완벽하게 숙지했다는 생각이 들어서 뿌듯하다.