❗ 개인적으로 공부했던 내용을 복습하고 정리하기 위한 글입니다! 따라서 내용이 정확하지 않을 수 있습니다!
흔히 말하는 객체지향의 3요소, JAVA의 3요소라고 해야 하나.... 학교다닐 때부터 귀에 딱지가 앉을만큼 들었고, 학원에서도 또 듣는다! 그만큼 아주 중요하다는 뜻이다. 객체지향의 3요소는 캡슐화(Encapsulation), 다형성(Polymorphism), 상속(Inheritance) 이 있다. 오늘 이 포스트에서는 그 중에서도 상속에 대해 다뤄보고자 한다.
먼저 상속을 사전에 검색해 보면 다음과 같이 나온다.
- 뒤를 이음.
- 법률 일정한 친족 관계가 있는 사람 사이에서, 한 사람이 사망한 후에 다른 사람에게 재산에 관한 권리와 의무의 일체를 이어 주거나, 다른 사람이 사망한 사람으로부터 그 권리와 의무의 일체를 이어받는 일.
부모의 것을 이어받거나, 뒤를 잇는 것이라고 생각하면 편하다. 자바에서도 사전적 의미와 크게 다르지 않다. 부모 클래스를 상속받아 자식 클래스를 생성하면, 부모의 클래스를 확장하여 생성할 수 있다. +α가 되는 샘이다.
그럼 이 기능은 왜 쓰는 것일까? 위에 말했듯 부모 클래스를 확장하여 자식 클래스를 생성할 수 있다. 중복되는 부분을 제거할 수 있고, 클래스의 재사용을 유용하게 한다. 또한 코드를 공통적으로 관리할 수 있어서 유지보수에 기여한다.
위에서 강조했던 확장이라는 단어를 기억하면 좋을 것 같다. 상속은 부모 클래스를 확장하여 자식 클래스를 정의하는 것이기 때문에, extends
키워드를 사용한다. 아래 예시에 먼저 부모 클래스인 Parent.java를 정의하였다.
// Parent.java
public class Parent {
private int x;
private int y;
public Parent() {}
public Parent(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
@Override
public String toString() {
return "Parent [x=" + x + ", y=" + y + "]";
}
}
다음은 자식 클래스인 Child.java를 정의하였다.
//Child.java
public class Child extends Parent {
private int z;
public Child() {}
public Child(int x, int y, int z) {
super(x, y);
this.z = z;
}
public int getZ() {
return z;
}
public void setZ(int z) {
this.z = z;
}
@Override
public String toString() {
return "Child [z=" + z + "]";
}
}
즉, 상속을 정의할 때에는 다음과 같이 작성한다.
public class 자식클래스명 extends 부모클래스명
흔히 객체는 형변환이 안 된다고 한다. 그러나 상속 구조의 경우에는 클래스간의 형변환이 가능하다. 클래스 형변환에는 두가지가 있는데, 바로 업 캐스팅(Up Casting)과 다운 캐스팅(Down Casting)이 있다. 또한 이런 클래스 형변환은 다형성을 구현하는 것이라고 생각할 수 있다.
상속 관계에 있는 부모, 자식 클래스 간에 부모타입의 참조형 변수가 모든 자식 타입의 객체 주소를 받을 수 있음
즉, 자식 타입 객체를 부모 타입 객체로 사용하려고 하면 별다른 연산자 없이 가능하다.
Child c = new Child(1, 2, 3);
Parent p = c; // 업캐스팅
System.out.println(p.getX());
System.out.println(p.getY());
//System.out.println(p.getZ()); // undefined 오류 발생!!
위의 코드는 Child클래스의 객체를 생성할 때, 매개변수로 1, 2, 3과 같이 주었다. 그러면 Child.java에 선언 된 정수형 숫자 3개를 매개변수로 받는 생성자가 호출되어 c라는 객체가 생성되었다. 그 다음, Parent p라는 객체를 선언할 때, 이 객체에 바로 윗줄에서 생성한 객체 c를 대입하였다. (정확히는 같은 주소를 참조하도록 함!) 그러면 업캐스팅이 일어났고, Parent.java에 선언된 getter 메소드를 사용할 수 있게 되는 것이다. 그러나 Parent.java에는 getZ() 메소드가 선언되어 있지 않아 undefined 오류가 발생하였다.
위의 업 캐스팅과 정반대가 되는 것이 바로 다운 캐스팅이다. 다운 캐스팅의 의미는 다음과 같다.
자식 객체의 주소를 받은 부모 참조형 변수를 가지고 자식의 멤버를 참조해야 할 경우, 부모 클래스 타입의 참조형 변수를 자식 클래스 타입으로 형 변환하는 것
이 때 주의할 점은 다운 캐스팅을 할 때에는 반드시 Casting 연산자가 필요하다는 것이다.
Parent p = new Child(1, 2, 3); // 다운 캐스팅
// System.out.println((Child)p.getZ());
System.out.println(((Child)p).getZ());
이 때 주의할 점은, ((Child)p).getZ()
와 같이 사용하였다는 점인데, 연산자 우선순위에 의해 캐스팅 연산자가 참조 연산자보다 우선순위가 낮아 오류 발생하기 때문이다.
instanceof
연산자는 현재 참조형 변수가 어떤 클래스 형의 객체 주소를 참조하고 있는지 확인할 때 사용한다. 클래스 타입이 맞으면 true, 아니라면 false를 반환한다.
Parent p = new Child(1, 2, 3);
System.out.println(p instanceof Child);
System.out.println(p instanceof Parent);
위 두 출력값은 모두 true가 나오는데, 그 이유는 다운 캐스팅이 일어나서 Child, Parent 클래스를 모두 참조하기 때문이다.
자바에서 너무나도 중요한 개념이고, 절차지향 언어에서 찾아볼 수 없었던 개념이기도 하다. 그래서 예전에 대학교 전공시간에서 배울 때에도 잘 이해가 안 되었었는데, 여전히 어려운 개념이어서 정리하게 되었다.
개인적으로 객체지향의 3요소는 각각 딱 잘라서 이건 상속! 이건 다형성! 이건 캡슐화! 이렇게 나눌 수 없다고 생각한다. 만약 상속을 한 부모 클래스의 필드의 접근지정자가 private
가 아닌 protected
로 선언되었다고 하면, 자식 클래스에서 getter / setter가 아닌 필드 그 자체로 접근할 수 있다. 그런데 이건 캡슐화 중 하나라고도 한다. 또한 위에서 다룬 클래스의 형변환도 다형성의 예시 중 하나라고 할 수 있다. 따라서 "객체지향"을 공부할 때에는 3요소를 딱 나누어 공부하는 것이 아닌, 각각의 연관관계를 생각하며 공부해야 한다고 생각한다.
끝!😎