Java Language
다형성
수색…
소개
다형성 (Polymorphism)은 주요 OOP (객체 지향 프로그래밍) 개념 중 하나입니다. 다형성 단어는 "poly"와 "morphs"라는 그리스 단어에서 파생되었습니다. Poly는 "many"를 의미하고 morph는 "forms"(여러 형태)를 의미합니다.
다형성을 수행하는 두 가지 방법이 있습니다. 메소드 오버로딩 과 메소드 오버라이드 .
비고
Interfaces
는 클래스 기반 상속과는 별도로 Java에서 다형성을 구현하는 또 다른 방법입니다. 인터페이스는 프로그램의 API를 구성하는 메소드 목록을 정의합니다. 클래스는 모든 메소드를 오버라이드하여 interface
를 implement
해야합니다.
메서드 오버로딩
메서드 오버로드 는 함수 오버로드 라고도하며 클래스의 수 또는 형식이 서로 다른 동일한 이름을 가진 여러 메서드를 갖는 클래스의 기능입니다.
컴파일러는 메서드 오버로드에 대한 메서드 서명 을 확인합니다.
메소드 서명은 세 가지로 구성됩니다.
- 메서드 이름
- 매개 변수의 수
- 매개 변수의 유형
이 세가 클래스의 두 메소드에서 동일하면 컴파일러가 중복 메소드 오류를 발생시킵니다 .
이러한 유형의 다형성은 정적 또는 컴파일 시간 다형성이라고 부릅니다. 호출 할 적절한 메소드가 인수 목록을 기반으로 컴파일 시간 동안 컴파일러에 의해 결정되기 때문입니다.
class Polymorph {
public int add(int a, int b){
return a + b;
}
public int add(int a, int b, int c){
return a + b + c;
}
public float add(float a, float b){
return a + b;
}
public static void main(String... args){
Polymorph poly = new Polymorph();
int a = 1, b = 2, c = 3;
float d = 1.5, e = 2.5;
System.out.println(poly.add(a, b));
System.out.println(poly.add(a, b, c));
System.out.println(poly.add(d, e));
}
}
결과는 다음과 같습니다.
2
6
4.000000
오버로딩 된 메서드는 정적 또는 비 정적 일 수 있습니다. 이것은 메소드 오버로딩에도 영향을 미치지 않습니다.
public class Polymorph {
private static void methodOverloaded()
{
//No argument, private static method
}
private int methodOverloaded(int i)
{
//One argument private non-static method
return i;
}
static int methodOverloaded(double d)
{
//static Method
return 0;
}
public void methodOverloaded(int i, double d)
{
//Public non-static Method
}
}
또한 메서드의 반환 형식을 변경하는 경우 메서드 오버로드로 가져올 수 없습니다.
public class Polymorph {
void methodOverloaded(){
//No argument and No return type
}
int methodOverloaded(){
//No argument and int return type
return 0;
}
메서드 재정의
메소드 겹쳐 쓰기는 부속 유형의 수퍼 유형의 작동을 다시 정의 (재 지정)하는 부속 유형의 기능입니다.
자바에서 이것은 수퍼 클래스에 정의 된 메소드를 오버라이드하는 서브 클래스로 변환된다. 자바에서 모든 프리미티브가 아닌 변수는 실제로 메모리에있는 실제 객체의 위치를 가리키는 포인터와 유사한 references
입니다. references
에는 하나의 유형 만 있으며이 유형은 선언 된 유형입니다. 그러나 선언 된 유형 또는 해당 부속 유형의 오브젝트를 가리킬 수 있습니다.
reference
에서 메소드가 호출되면, 가리키는 실제 오브젝트 의 해당 메소드가 호출됩니다 .
class SuperType {
public void sayHello(){
System.out.println("Hello from SuperType");
}
public void sayBye(){
System.out.println("Bye from SuperType");
}
}
class SubType extends SuperType {
// override the superclass method
public void sayHello(){
System.out.println("Hello from SubType");
}
}
class Test {
public static void main(String... args){
SuperType superType = new SuperType();
superType.sayHello(); // -> Hello from SuperType
// make the reference point to an object of the subclass
superType = new SubType();
// behaviour is governed by the object, not by the reference
superType.sayHello(); // -> Hello from SubType
// non-overridden method is simply inherited
superType.sayBye(); // -> Bye from SuperType
}
}
명심해야 할 규칙
서브 클래스의 메소드를 오버라이드 (override)하려면, 오버라이드 (override) 메소드 (서브 클래스의 메소드)가 반드시 필요합니다 .
- 같은 이름
- 원시 타입의 경우에는 동일한 반환 유형 (클래스에 대해 하위 클래스가 허용되며, 이는 공변 반환 유형이라고도 함).
- 동일한 유형 및 매개 변수 순서
- 수퍼 클래스의 메소드의 throws 절에서 선언 된 예외 또는 선언 된 예외의 서브 클래스 인 예외 만 예외로 throw 할 수 있습니다. 예외를 throw하지 않기로 선택할 수도 있습니다. 매개 변수 유형의 이름은 중요하지 않습니다. 예를 들어, void methodX (int i)는 void methodX (int k)와 동일하다.
- 최종 또는 정적 메서드를 재정의 할 수 없습니다. 우리가 할 수있는 것만이 방법 몸만 바꿉니다.
기존 코드를 건드리지 않고 클래스를 추가하여 비헤이비어 추가하기
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;
public class PolymorphismDemo {
public static void main(String[] args) {
List<FlyingMachine> machines = new ArrayList<FlyingMachine>();
machines.add(new FlyingMachine());
machines.add(new Jet());
machines.add(new Helicopter());
machines.add(new Jet());
new MakeThingsFly().letTheMachinesFly(machines);
}
}
class MakeThingsFly {
public void letTheMachinesFly(List<FlyingMachine> flyingMachines) {
for (FlyingMachine flyingMachine : flyingMachines) {
flyingMachine.fly();
}
}
}
class FlyingMachine {
public void fly() {
out.println("No implementation");
}
}
class Jet extends FlyingMachine {
@Override
public void fly() {
out.println("Start, taxi, fly");
}
public void bombardment() {
out.println("Fire missile");
}
}
class Helicopter extends FlyingMachine {
@Override
public void fly() {
out.println("Start vertically, hover, fly");
}
}
설명
a) MakeThingsFly
클래스는 FlyingMachine
타입의 모든 것을 처리 할 수 있습니다.
b) letTheMachinesFly
메소드는 새 클래스를 추가 할 때 (예 : PropellerPlane
) 아무런 변화없이 작동합니다 (!).
public void letTheMachinesFly(List<FlyingMachine> flyingMachines) {
for (FlyingMachine flyingMachine : flyingMachines) {
flyingMachine.fly();
}
}
}
그것은 다형성의 힘입니다. 이를 사용하여 개방형 폐쇄 원리 를 구현할 수 있습니다.
가상 함수
버추얼 메소드는 비 정적이며 키워드 Final이없는 Java의 메소드입니다. 기본적으로 모든 메소드는 Java에서 가상입니다. 가상 메소드는 다형성에서 중요한 역할을합니다. 왜냐하면 재정의되는 함수가 비 정적이고 메소드 서명이 동일한 경우 자바의 하위 클래스가 상위 클래스의 메소드를 대체 할 수 있기 때문입니다.
그러나 가상이 아닌 몇 가지 방법이 있습니다. 예를 들어, 메서드가 private 또는 final 키워드로 선언 된 경우 메서드는 가상이 아닙니다.
이 StackOverflow 포스트에서 Virtual Methods를 사용하여 상속을 수정 한 다음 예제를 고려하십시오. C # 및 Java에서 가상 함수는 어떻게 작동합니까? :
public class A{
public void hello(){
System.out.println("Hello");
}
public void boo(){
System.out.println("Say boo");
}
}
public class B extends A{
public void hello(){
System.out.println("No");
}
public void boo(){
System.out.println("Say haha");
}
}
클래스 B를 호출하고 hello () 및 boo ()를 호출하면 B가 A와 동일한 메서드를 재정의하므로 "No"와 "Say haha"를 결과로 얻습니다. 위 예제는 메서드 재정의를 위해서는 클래스 A의 메서드가 기본적으로 모두 가상이라는 것을 이해하는 것이 중요합니다.
또한 abstract 키워드를 사용하여 가상 메소드를 구현할 수 있습니다. 키워드 "abstract"로 선언 된 메소드에는 메소드 정의가 없으므로 메소드의 본문이 아직 구현되지 않았습니다. boo () 메서드가 abstract로 선언 된 것을 제외하고는 위의 예제를 다시 고려하십시오.
public class A{
public void hello(){
System.out.println("Hello");
}
abstract void boo();
}
public class B extends A{
public void hello(){
System.out.println("No");
}
public void boo(){
System.out.println("Say haha");
}
}
B에서 boo ()를 호출하면 B는 추상 메소드 boo ()를 상속하고 boo () 출력에 "Say haha"를 출력하므로 "Say haha"가 출력됩니다.
사용 된 소스 및 추가 읽기 :
C # 및 Java에서 가상 함수는 어떻게 작동합니까?
가상 기능에 대한 훨씬 더 완벽한 정보를 제공하는 훌륭한 대답을 확인하십시오.
다형성 및 다른 유형의 재정의
자바 자습서에서
다형성에 대한 사전 정의는 생물 또는 종이 여러 가지 형태 또는 단계를 가질 수있는 생물학의 원리를 나타냅니다. 이 원칙은 객체 지향 프로그래밍 및 Java 언어와 같은 언어에도 적용될 수 있습니다. 클래스의 서브 클래스는 고유 한 비헤이비어를 정의 할 수 있지만 부모 클래스와 동일한 기능을 일부 공유 할 수 있습니다.
다른 우선 순위 유형을 이해하려면이 예제를 살펴보십시오.
- 기본 클래스는 구현을 제공하지 않으며 하위 클래스는 완전한 메소드를 오버라이드해야합니다 - (추상)
- 기본 클래스는 기본 구현을 제공하고 하위 클래스는 동작을 변경할 수 있습니다.
- 하위 클래스는
super.methodName()
을 첫 번째 문으로 호출하여 기본 클래스 구현에 확장을 추가합니다. - 기본 클래스는 알고리즘 (템플릿 메소드)의 구조를 정의하고 하위 클래스는 알고리즘의 일부를 오버라이드합니다
코드 스 니펫 :
import java.util.HashMap;
abstract class Game implements Runnable{
protected boolean runGame = true;
protected Player player1 = null;
protected Player player2 = null;
protected Player currentPlayer = null;
public Game(){
player1 = new Player("Player 1");
player2 = new Player("Player 2");
currentPlayer = player1;
initializeGame();
}
/* Type 1: Let subclass define own implementation. Base class defines abstract method to force
sub-classes to define implementation
*/
protected abstract void initializeGame();
/* Type 2: Sub-class can change the behaviour. If not, base class behaviour is applicable */
protected void logTimeBetweenMoves(Player player){
System.out.println("Base class: Move Duration: player.PlayerActTime - player.MoveShownTime");
}
/* Type 3: Base class provides implementation. Sub-class can enhance base class implementation by calling
super.methodName() in first line of the child class method and specific implementation later */
protected void logGameStatistics(){
System.out.println("Base class: logGameStatistics:");
}
/* Type 4: Template method: Structure of base class can't be changed but sub-class can some part of behaviour */
protected void runGame() throws Exception{
System.out.println("Base class: Defining the flow for Game:");
while (runGame) {
/*
1. Set current player
2. Get Player Move
*/
validatePlayerMove(currentPlayer);
logTimeBetweenMoves(currentPlayer);
Thread.sleep(500);
setNextPlayer();
}
logGameStatistics();
}
/* sub-part of the template method, which define child class behaviour */
protected abstract void validatePlayerMove(Player p);
protected void setRunGame(boolean status){
this.runGame = status;
}
public void setCurrentPlayer(Player p){
this.currentPlayer = p;
}
public void setNextPlayer(){
if (currentPlayer == player1) {
currentPlayer = player2;
}else{
currentPlayer = player1;
}
}
public void run(){
try{
runGame();
}catch(Exception err){
err.printStackTrace();
}
}
}
class Player{
String name;
Player(String name){
this.name = name;
}
public String getName(){
return name;
}
}
/* Concrete Game implementation */
class Chess extends Game{
public Chess(){
super();
}
public void initializeGame(){
System.out.println("Child class: Initialized Chess game");
}
protected void validatePlayerMove(Player p){
System.out.println("Child class: Validate Chess move:" + p.getName());
}
protected void logGameStatistics(){
super.logGameStatistics();
System.out.println("Child class: Add Chess specific logGameStatistics:");
}
}
class TicTacToe extends Game{
public TicTacToe(){
super();
}
public void initializeGame(){
System.out.println("Child class: Initialized TicTacToe game");
}
protected void validatePlayerMove(Player p){
System.out.println("Child class: Validate TicTacToe move:" + p.getName());
}
}
public class Polymorphism{
public static void main(String args[]){
try{
Game game = new Chess();
Thread t1 = new Thread(game);
t1.start();
Thread.sleep(1000);
game.setRunGame(false);
Thread.sleep(1000);
game = new TicTacToe();
Thread t2 = new Thread(game);
t2.start();
Thread.sleep(1000);
game.setRunGame(false);
}catch(Exception err){
err.printStackTrace();
}
}
}
산출:
Child class: Initialized Chess game
Base class: Defining the flow for Game:
Child class: Validate Chess move:Player 1
Base class: Move Duration: player.PlayerActTime - player.MoveShownTime
Child class: Validate Chess move:Player 2
Base class: Move Duration: player.PlayerActTime - player.MoveShownTime
Base class: logGameStatistics:
Child class: Add Chess specific logGameStatistics:
Child class: Initialized TicTacToe game
Base class: Defining the flow for Game:
Child class: Validate TicTacToe move:Player 1
Base class: Move Duration: player.PlayerActTime - player.MoveShownTime
Child class: Validate TicTacToe move:Player 2
Base class: Move Duration: player.PlayerActTime - player.MoveShownTime
Base class: logGameStatistics: