이 글에서는 Java의 inheritance와 연관된 것들에 대해 알아볼 것이다.
다형성 Polymorphism
앞선 포스팅에서 상속을 다음과 같이 정의했다.
하나의 component(variable, method, class, ...)가 casting, overloading, 또는 overriding을 통해 상황에 따라 다르게 사용되는 것을 말한다.
Overloading은 이름은 같지만 parameter를 다르게 해, 이름만 같은 함수로 사용하는 것이며, static binding이다.
Overriding은 parent class의 method를 child class에서 재정의하는 것이며, dynamic binding이다.
polymorphism을 이용하면 code reuse, 유지보수성, 유연성이 증가한다.
Static polymorphism - overloading
parameter를 다르게 하여 method signature를 다르게 만들고 각각의 method를 정의해 같은 이름을 가지고 있지만 parameter에 따라 다른 동작을 하게 할 수 있다. - 이를 overloading이라고 하며 compile time에 모든 내용이 결정되는 static binding이다. compiler는 같은 이름의 method라도 method signature가 다르면 다른 method라고 판단하기 때문이다.
overloading 시 null 오류
// Example.java
public class Example {
public void print(Integer i){
System.out.println("print integer " + i);
}
public void print(Double d){
System.out.println("print double " + d);
}
}
// App.java
public class App {
public static void main(String[] args) throws Exception {
Example example = new Example();
example.print(null); // compile error : method print(Integer) is ambiguous
Integer i = null;
example.print(i); // OK
}
}
재밌는 예시를 하자 보자. 위와 같이 Example class에서 print()를 overloading했다. 그러나 App에서 print() method에 null을 넣으니 ambiguous하다는 오류가 나온다. 이는 Integer와 Double이 모두 nullable이기 때문에 두 print() 함수 중 어느 것인지 모호하기 때문이다. 아래와 같이 명확하게 parameter type을 지정하면 오류가 나지 않는다.
null을 넣을 경우는 많이 없겠지만 알아두자.
Dynamic polymorphism - overriding
Java에서는 parent class 변수로 child class instance를 참조할 수 있다. (그 역은 성립하지 않음) 다만 이 경우, parent class 변수에 없는 member는 호출할 수 없다. child class에서만 정의한 member는 호출할 수 없다는 것이다.
runtime에 어떤 method가 실행될지 결정되기 때문에 dynamic binding이다.
Upcasting
// App.java
public class App {
public static void main(String[] args) throws Exception {
Child child = new Child();
Parent p = child; // upcasting from child to parent
Parent parent = new Parent();
Child c = parent; // compile error : annot convert from Parent to Child
}
}
위 코드와 같이 child class instance를 parent class에 대입할 수 있다. 이렇게 하는 게 왜 다형성이냐? 다음 예시를 보자.
// Parent.java
public class Parent {
public int get(){
return 0;
}
}
// Child.java
public class Child extends Parent {
@Override
public int get(){
return 100;
}
}
// App.java
public class App {
public static void main(String[] args) throws Exception {
Parent parent = new Parent();
Child child = new Child();
System.out.println(child.get()); // 100
System.out.println(parent.get()); // 0
// variable polymorphism
Parent p = child; // upcasting
System.out.println(p.get()); // 100
// identity
System.out.println(p == child); // true
}
}
main함수에서 위 2개의 sysout은 자명하다. 그러나 child class를 parent class로 upcasting한다면 p 변수에는 Parent 객체가 아니라 Child 객체가 들어가게 된다! 따라서 overriding한 method를 부르면 parent class의 method가 아니라 child class의 method가 호출된다.
이 작동 방식은 variable이 아니라 parameter에도 똑같이 적용할 수 있다!
// App.java
public class App {
public static int helper(Parent parent){
return parent.get();
}
public static void main(String[] args) throws Exception {
Parent parent = new Parent();
System.out.println(helper(parent)); // 0
// parameter polymorphism
Parent p = new Child(); // casting
System.out.println(helper(p)); // 100
}
}
polymorphism의 이러한 작동 방식을 통해 객체지향의 유지보수성이 확 올라가게 된다. 한 class 참조 변수에 해당 class를 상속하는 모든 class를 upcasting해 저장할 수 있기 때문 요구사항이 변화해도 child class만 새로 만들고 upcasting하는 부분만 수정해 주면 되기 때문이다.
Downcasting
upcasting은 child class type을 parent class type으로 변환시켜 변수에 넣는 방법이었다. 그러나 이 경우 child class에만 정의한 member는 호출할 수 없었다. 그러나 child class에 정의한 member가 필요할 수 있다. 이 때 downcasting을 사용한다.
downcasting은 parent class type을 child class type으로 강제 변환시키는 방법이며, 이 때 parent class type은 child class type이 upcasting된 상태여야 한다. 그렇지 않다면 아래와 같이 오류가 난다.
// App.java
public class App {
public static void main(String[] args) throws Exception {
Parent p = new Child(); // upcasting
Child c = (Child) p; // downcasting
System.out.println(p.get()); // 100
Child child = (Child) new Parent();
System.out.println(child.get()); // runtime error: class Parent cannot be cast to class Child
}
}
instanceof
instanceof는 해당 instance가 어떤 class에 속해있는지를 나타내 준다.
downcasting은 child class type이 parent class type으로 upcasting된 상태여야만 작동하고, 그렇지 않다면 runtime error가 발생한다고 했다. instanceof를 사용해 runtime에 upcasting된 상태인지 아닌지 알 수 있다.
// App.java
public class App {
public static void main(String[] args) throws Exception {
Parent p = new Child(); // upcasting
Child c = (Child) p; // downcasting
System.out.println(p.get()); // 100
Parent parent1 = new Parent();
if(parent1 instanceof Child){
Child child1 = (Child) parent1; // not reached
System.out.println("1 downcasting");
System.out.println(child1.get());
}
Parent parent2 = c;
if(parent2 instanceof Child){
Child child2 = (Child) parent2; // reached here
System.out.println("2 downcasting");
System.out.println(child2.get()); // 100
}
}
}
static method의 polymorphism
static method 또한 method이기 때문에 overloading은 가능하다.
그러나 static method는 runtime 이전에 binding이 일어나기 때문에 overloading은 불가하다. - static method의 경우 type에 따라 method가 호출되며, compile binding이다.
// Parent.java
public class Parent {
public static void method1(){
System.out.println("parent method 1");
}
public void method2(){
System.out.println("parent method 2");
}
}
// Child.java
public class Child extends Parent {
// @Override : static method cannot be override
public static void method1() {
System.out.println("child method 1");
}
public void method2(){
System.out.println("child method 2");
}
}
// App.java
public class App {
public static void main(String[] args) throws Exception {
Parent parent = new Parent();
parent.method1(); // parent method 1
parent.method2(); // parent method 2
Child child = new Child();
child.method1(); // child method 1
child.method2(); // child method 2
Parent c = new Child();
c.method1(); // parent method 1
c.method2(); // child method 2
}
}
위 예시를 보자. @Override annotation을 사용하지 않아도 overriding은 작동하기는 한다고 했다.
c.method2() 실행 결과가 child method 2인 것을 보면, method2는 overriding이 잘 되어 실제 할당된 class에 따라 잘 나오는 것을 볼 수 있다.
그러나 c.method1()은 parent method 1이 튀어나온다. 이는 c의 type이 Parent이기 때문이며 method1이 static이기 때문에 overriding이 일어나지 않아서 그렇다.
'Development > Java' 카테고리의 다른 글
[Java] Exception Handling (0) | 2023.03.01 |
---|---|
[Java] abstract class vs interface (0) | 2023.02.27 |
[Java] Inheritance - access modifier, super, overriding (0) | 2023.02.23 |
[Java] static, final (0) | 2023.02.23 |
[Java] Class - constructor, access modifier, identity & equality & hashCode (2) | 2023.02.22 |