다형성
다형성은 동일한 코드로 경우에 따라 다른 동작을 하게 되는 것을 말한다.
다형성은 하나의 코드가 타입에 따라 다르게 해석되어 다른 일을 할 수 있는 것을 말한다.
상속 클래스의 작성에서 코드의 재사용이 가능하게 해주는 중요한 개념이다.
출처:
https://plas.tistory.com/29
[프로그래밍노리터]
Upcasting 은 이런 다형성(Polymorphism)을 사용하고 싶을 때, 부모의 타입에 자식의 인스턴스를 넣는 것을 가르킨다.
상속
상속의 사용 시기
:
1. IS-A(inheritence)
is a relationship 자식 - 부모
2. HAS-A(composition) : 하위 클래스가 상위 클래스를 포함하는 경우
is-a (상속)
has-a (상속 X)
지금은 안쓰지만 예전 코드를 이해하려면 알아야한다.객체합성으로 대체 및 해결한다. (아주 과거에는 상속으로 해결했다)
-------> has-a에 대한 대안 : 객체 합성
출처 : https://wayhome25.github.io/cs/2017/04/09/cs-09/
상속의 단점 -
첫번째 : 런타임에 상속받은 부모 클래스의 구현을 변경할 수는 없다는 점입니다.
- 상속은 컴파일 시점에 결정되는 사항이기 때문입니다
두번째 : 부모 클래스는 서브클래스의 물리적 표현의 최소 부분만을 정의합니다. 서브 클래스는 부모 클래스의 구현이 서브클래스에 다 드러나는 것이기 때문에 상속은 캡슐화를 파괴한다고 주장하는 의견도 있다. 서브클래스는 부모 클래스의 구현에 종속될 수밖에 없으므로 부모 클래스 구현에 변경이 생기면 서브클래스도 변경해야 합니다.
객체 합성은 클래스 상속에 대한 대안 입니다. 다른 객체를 여러개 붙여서 새로운 기능 혹은 객체를 구성하는 것입니다. 객체를 합성하려면, 합성에 들어가는 객체들의 인터페이스를 명확하게 정의해 두어야 합니다. 이런 스타일의 재사용을
블랙박스 재사용(black-box reuse)이라고 하는데, 객체의 내부는 공개되지 않고 인터페이스를 통해서만 재사용되기 때문입니다.
※ 클래스 상속에서 구현의 종속성 해결 방법 - 추상 클래스에서만 상속 받는 것
출처 : https://wikidocs.net/894
코드 재사용, 포함
class Animal{
public void sleep(){
System.out.println("잔다");
}
public void move(){
System.out.println("동물");
}
}
class Human extends Animal{
public void move(){
System.out.println("사람");
}
}
class Dog extends Animal{
public void move(){
System.out.println("개");
}
public void bark(){
System.out.println("왈왈");
}
}
public class Polymorphism {
public static void main(String args[]){
Animal humanAnimal = new Human();
Animal dogAnimal = new Dog();
Animal animal = new Animal();
Polymorphism test = new Polymorphism();
test.move(humanAnimal);
test.move(dogAnimal);
test.move(animal);
dogAnimal.sleep();
// dogAnimal.bark(); --> ERROR !
}
public void move(Animal animal){
animal.move();
}
}
출력결과:
사람
개
동물
잔다
- 위와 같이 한가지 클래스로 공통해서 사용하고 싶을 때(다형성을 이용하고 싶을 때) 업캐스팅을 사용한다.
- 그렇지만 dogAnimal.bark() ( dog class에만 있는 매소드 ) 는 Animal 로 만들어진 인스턴스는 사용할 수 없다.
- 다운캐스팅은 업캐스팅된 인스턴스의 원래 인스턴스를 찾기 위해 사용한다
- ArrayList를 사용하면 다음과 같이 좀 더 깔끔한 코드로 될 수 있다.
ArrayList<Animal> animalArrayList = new ArrayList<>();
animalArrayList.add(humanAnimal);
animalArrayList.add(dogAnimal);
animalArrayList.add(animal);
for (Animal aanimal : animalArrayList) {
aanimal.move();
}
}
public void move(Animal animal) {
animal.move();
}
출력결과:
사람
개
동물
가상함수
Human person;
if ( ... ) person = new Student();
else if (...) person = new Gamer();
person.play();
- play()라는 메소드 호출에 대해, 실행 시점에 호출된 객체의 타입을 보고 어떤 메소드가 호출될 것인지 결정하는 것을 가상함수라고 한다.
What is a virtual function?
In object-oriented programming, a virtual function or virtual method is a function or method whose behaviour can be overridden within an inheriting class by a function with the same signature to provide the polymorphic behavior.
출처 : https://medium.com/@namangupta01/virtual-function-in-java-vs-c-d75874d23
( overriding 될 수 있는 함수를 가상함수라고 한다 )
가상 함수(virtual function)C++에서 가상 함수(virtual function)는 파생 클래스에서 재정의할 것으로 기대하는 멤버 함수를 의미합니다.
이러한 가상 함수는 자신을 호출하는 객체의 동적 타입에 따라 실제 호출할 함수가 결정됩니다.
출처 : http://tcpschool.com/cpp/cpp_polymorphism_virtual
- 객체에 따라서 다른 method()가 호출되는 것을 가상함수 호출이라고 한다.
동적바인딩
C++ 컴파일러는 함수를 호출할 때, 어느 블록에 있는 함수를 호출해야 하고, 해당 함수가 저장된 정확한 메모리 위치까지도 알아야 합니다.이처럼 함수를 호출하는 코드에서 어느 블록에 있는 함수를 실행하라는 의미로 해석하는 것을 바인딩(binding)이라고 합니다.
하지만 C++에서는 함수가 오버로딩될 수 있으므로 이 작업이 조금 복잡해집니다.
대부분 함수를 호출하는 코드는 컴파일 타임에 고정된 메모리 주소로 변환됩니다.
이것을 정적 바인딩(static binding) 또는 초기 바인딩(early binding)이라고 합니다.
C++에서는 가상 함수가 아닌 멤버 함수는 모두 이러한 정적 바인딩을 하게 됩니다.
하지만 가상 함수의 호출은 컴파일러가 어떤 함수를 호출해야 하는지 미리 알 수 없습니다.
왜냐하면, 가상 함수는 프로그램이 실행될 때 객체를 결정하므로, 컴파일 타임에 해당 객체를 특정할 수 없기 때문입니다.
따라서 가상 함수의 경우에는 런 타임에 올바른 함수가 실행될 수 있도록 해야 합니다.
이것을 동적 바인딩(dynamic binding) 또는 지연 바인딩(late binding)이라고 합니다.
하지만 가상 함수도 결합하는 타입이 분명할 때에는 일반 함수와 같이 정적 바인딩을 합니다.
이러한 가상 함수는 기초 클래스 타입의 포인터나 참조를 통하여 호출될 때만 동적 바인딩을 하게 됩니다
동적바인딩이라는 말은 언어의 구현 관점에서 실행시점에 뭔가를 결정하는 것을 얘기한다. 프로그램에서 실행시키기 전에 결정되지 않는 것을 동적바인딩이라고 한다.
예를 들면 가상함수에 따라서 동적할당 주소나 변수의 값, 함수호출스택 등은 동적으로 결정된다.
출처: https://plas.tistory.com/29 [프로그래밍노리터]
마지막으로 한 가지 언급할 것은 C++은 가상함수로 재정의할 수 있는 함수의 정의부 앞에는 virtual이라고 붙이게 되어 있다. 즉 virtual 키워드가 없으면 일반 함수고 있어야 오버라이드할 수 있는 함수가 된다는 뜻이다. 그러므로 가상함수테이블에는 virtual이 붙은 함수들만 있고 다른 일반 함수는 컴파일할 때 어느 주소의 코드를 실행해야 할지 알고 바로 호출할 수 있다. 메소드를 호출할 때마다 가상함수 테이블을 찾아서 해당 주소로 가야 한다면 실행 시간이 상당히 늦어질 수 있기 때문이다.
자바는 모든 메소드를 가상함수라고 본다. 즉 모든 메소드는 반드시 가상함수 테이블에 가서 주소를 찾은 다음에 호출을 위해 점프할 수 있다. 이것은 당연히 C++에 비해 효율성이 많이 떨어진다. 그러나 자바는 virtual이라든가 다른 키워드를 사용하지 않아도 되고 구별없이 오버라이드할 수 있다. (최근에는 @Override를 써주라고 하는데, 이것은 성능과는 관계없이 컴파일러가 메소드 선언을 슈퍼에 맞게 잘 했는지 검사해 주는 역할과 가독성을 위한 문서화 역할을 한다.1 여전히 모든 메소드는 가상함수다.)
그럼 왜 자바는 모든 메소드를 가상함수로 구현할까? 앞에서 얘기한 대로 어느 게 가상이고 어느 게 아닌지 구별없이 무조건 오버라이드할 수 있다. 하위 클래스에서는 무엇이든 자기가 필요한 메소드는 오버라이드해서 사용할 수 있다. 그러므로 상속 클래스를 작성할 때나 코드를 설계할 때 훨씬 자유롭게 (구별없이) 오버라이드를 따로 고려하지 않아도 된다.
출처:
https://plas.tistory.com/29
[프로그래밍노리터]
'정리하기 이전 자료 ( before 20-11 ) > Java' 카테고리의 다른 글
DownCasting & instance of (0) | 2020.03.29 |
---|---|
상속에서의 생성자 (0) | 2020.03.29 |
ArrayList 기초 (0) | 2020.03.28 |
배열 ( Array ) (0) | 2020.03.27 |
Singleton Pattern (0) | 2020.03.25 |
댓글