이 글에서는 Java의 inheritance와 연관된 것들에 대해 알아볼 것이다.
상속 Inheritance
앞선 포스팅에서 상속을 다음과 같이 정의했다.
parent class로부터 새로운 child class를 만드는 것이다. 또한 상속받는 child class는 새로운 attribute나 method를 추가해 확장해 나갈 수 있다. 상속을 통해 class hierarchy를 만들 수 있다.
access modifier에 따른 상속
parent class member의 access modifier에 따라 child class에서 사용여부가 달라진다. [public 또는 protected access modifier로 선언된 member]들만이 child class에서 바로 접근할 수 있으며, [private로 선언된 member] 또는 [default로 선언되고 child package가 parent package와 다른 경우]에는 해당 member에 접근할 수 없다. 그러나 그 member에 접근할 수 없는 것이지 해당 member들은 memory가 할당되어 있기 때문에 getter나 setter를 사용해 값을 변경할 수 있다.
사실 별 특별한 내용은 아니고 access modifier의 역할을 그대로 따라갈 뿐이다.
- private : 상속되지만 child class에서 직접 접근 불가. parent class의 method로 접근해야 한다
- default : 같은 package 내에서 있을 땐 직접 접근 가능. 다른 package일 때는 직접 접근 불가
- protected : child class에서 해당 member에 직접 접근 가능
- public : 어디에서도 해당 member에 접근 가능
아래는 inheritance 예시이다. Java는 extends keyword를 이용해 inheritance를 한다.
private로 선언된 member에 직접 접근은 불가하지만 method를 통해 해당 member에 접근할 수 있다.
// Parent.java
public class Parent {
private int v = 1;
public void setV(int v){
this.v = v;
}
public int getV(){
return this.v;
}
private int privateAttribute = 1;
protected int protectedAttribute = 2;
int defaultAttribute = 3;
public int publicAttribute = 4;
}
// Child.java
public class Child extends Parent {
public void set(){
super.setV(10);
}
public int get(){
// return this.v;
// Parent에서 attribute v는 private로 선언되었기 때문에 직접 호출 불가능, compile error!
return super.getV();
}
Child(){
this.privateAttribute = 10; // compile error : private는 상속되지 않음
this.protectedAttribute = 20; // OK
this.defaultAttribute = 30; // OK : cause on same package
this.publicAttribute = 40; // OK
}
}
// App.java
public class App {
public static void main(String[] args) throws Exception {
Child child = new Child();
System.out.println(child.get()); // 1
child.set();
System.out.println(child.get()); // 10
// child class에서 보이지 않는 것 뿐이지 메모리에 값은 할당되어 있고,
// method로 해당 값을 수정하고 읽어올 수도 있다.
}
}
super
inheritance에서 super는 2가지 목적으로 사용된다.
parent class constructor
super()로 호출해 parent class의 constructor를 호출할 수 있다. 상속 시 constructor는 상속하지 않기 때문에 parent class의 constructor가 필요한 경우에는 explicitly 호출해야 한다.
무조건 첫 줄에 위치해야 한다.
// Child.java
public class Child extends Parent {
// ...
Child(){
super(); // Parent의 constructor 호출
// ...
}
}
parent method
this를 사용하는 것처럼 super를 사용하면 parent의 method를 explicitly 호출할 수 있다.
// Parent.java
public class Parent {
// ...
public int getV(){
return this.v;
}
}
public class Child extends Parent {
// ...
public int get(){
return super.getV(); // call getV() method in Parent Class
}
}
재정의 Overriding
overriding은 parent class에 있는 method를 child class에 맞게 재정의하는 것이다. overriding하는 경우 parent class method는 숨겨지고 child class method로 실행된다. dynamic binding된다.
동일한 method signature를 가져야 한다. 또한, access modifier를 더 강하게 할 수 없다. 사용 시 @Override annotation을 붙이지 않아도 작동하긴 한다. 그러나 가독성을 위해, 그리고 컴파일러가 parent class의 어떤 method를 override했는지 검사하고 오류가 있을 시 알려주기 때문에 붙이는 것이 안정성에 더 좋다.
- parent method가 public이면 child method도 public이어야 한다
- parent method가 default면 child method는 default 또는 public이어야 한다.
- parent method가 protected이면 child method는 protected, default, public이어야 한다.
- parent method가 private이면 child method는 private, protected, default, public이어야 한다.
// Parent.java
public class Parent {
public int get(){
return 20;
}
}
// Child.java
public class Child extends Parent {
@Override
public int get(){
return 10;
}
}
// App.java
public class App {
public static void main(String[] args) throws Exception {
Parent parent = new Parent();
Child child = new Child();
System.out.println(parent.get()); // 20
System.out.println(child.get()); // 10
}
}
위 예시에서, 같은 get() method를 부르지만 다른 결과가 나온다. 이것이 overriding이다.
Overriding private method
private method는 overriding이 불가하다. private method는 child class에서는 접근할 수 없기 때문이다.
만약 child class에서 동일한 method signature를 가진 method를 선언할 경우에는, 이는 parent class의 private method를 overriding한 것이 아니라, child class에서 새로운 private method를 선언한 것이며, parent class의 private method와는 전혀 다른 method로 취급된다. 실제로 해당 method에 @Override annotation을 붙여 보면 오류가 난다.
// Parent.java
public class Parent {
private void print(){
System.out.println("parent print");
}
}
// Child.java
public class Child extends Parent {
@Override
// compile error : The method print() of type Child must override or implement a supertype method
private void print(){
System.out.println("child print");
}
}
* Java에서 다중상속 (Diamond Problem)
class의 다중상속은 금지되어 있다. 그 이유는 diamond problem 때문이다. 아래 예시를 생각해 보자.
class A가 있다. class B는 A를 상속, class C도 A를 상속했다. class D가 B, C를 상속했다고 생각해 보자. 만약 B와 C가 같은 method를 overriding했을 떄, D는 어떤 method를 상속해야 하는지 알 수 없다. 이러한 문제를 막기 위해 class 다중상속은 금지한다.
다만, interface의 다중 상속은 허용한다. interface는 method의 구현이 이루어지지 않고, 구현 class에서만 일어나기 때문에 해당 method를 정의할 수 있다. 따라서 다중상속이 허용된다.
inheritance의 한계
inheritance는 object를 modeling하는 데 중요한 개념이지만 이것만으로 프로그래밍을 하기에는 부족하다. 특히 OOP의 중요한 개념 중 하나인 객체의 상호작용으로 프로래밍한다를 사용할 경우, 객체들이 서로서로 너무 높은 의존성과 결합도를 가지게 된다.
'Development > Java' 카테고리의 다른 글
[Java] abstract class vs interface (0) | 2023.02.27 |
---|---|
[Java] Polymorphism - static polymorphism, dynamic polymorphism, casting (0) | 2023.02.23 |
[Java] static, final (0) | 2023.02.23 |
[Java] Class - constructor, access modifier, identity & equality & hashCode (2) | 2023.02.22 |
[Java] Data types, 예외적인 String, 그리고 call by value (0) | 2023.02.21 |