"아미고, 고래 좋아해?"

"고래? 아니, 들어본 적 없어."

"그것은 소와 같으며 더 크고 헤엄칩니다. 덧붙여서 고래는 소에서 나왔습니다. 어, 또는 적어도 그들은 공통 조상을 공유합니다. 그것은 중요하지 않습니다."

다형성 및 재정의 - 1

"잘 들어. OOP의 또 다른 매우 강력한 도구인 다형성 에 대해 이야기하고 싶습니다 . 다형성에는 네 가지 기능이 있습니다."

1) 메서드 재정의.

게임용 "Cow" 클래스를 작성했다고 상상해 보십시오. 많은 멤버 변수와 메서드가 있습니다. 이 클래스의 객체는 걷기, 먹기, 잠자기 등 다양한 일을 할 수 있습니다. 소도 걸을 때 종을 울립니다. 가장 작은 세부 사항까지 클래스의 모든 것을 구현했다고 가정해 보겠습니다.

다형성 및 재정의 - 2

그런 다음 갑자기 고객이 모든 행동이 바다에서 일어나고 주인공이 고래 인 새로운 수준의 게임을 출시하고 싶다고 말합니다.

Whale 클래스를 설계하기 시작했고 그것이 Cow 클래스와 약간만 다르다는 것을 깨달았습니다. 두 클래스 모두 매우 유사한 논리를 사용하므로 상속을 사용하기로 결정합니다.

Cow 클래스는 부모 클래스에 이상적으로 적합합니다. 이미 필요한 모든 변수와 메서드가 있습니다. 고래의 수영 능력을 추가하기만 하면 됩니다. 하지만 문제가 있습니다. 고래는 다리, 뿔, 종을 가지고 있습니다. 결국 Cow 클래스는 이 기능을 구현합니다. 당신은 무엇을 할 수 있나요?

다형성 및 재정의 - 3

메서드 재정의가 구출됩니다. 새 클래스에서 정확히 필요한 작업을 수행하지 않는 메서드를 상속하는 경우 해당 메서드를 다른 메서드로 대체할 수 있습니다.

다형성 및 재정의 - 4

이것은 어떻게 이루어 집니까? 자손 클래스에서 변경하려는 메서드를 선언합니다(부모 클래스와 동일한 메서드 시그니처 사용) . 그런 다음 메서드에 대한 새 코드를 작성합니다. 그게 다야. 마치 부모 클래스의 이전 메서드가 존재하지 않는 것과 같습니다.

작동 방식은 다음과 같습니다.

암호 설명
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
여기서 우리는 두 개의 클래스를 정의합니다:  Cow 및  WhaleWhale상속합니다  Cow.

클래스  는 메서드를 Whale 재정의합니다  printName();.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
이 코드는 화면에 « I'm a cow »를 표시합니다.
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
이 코드는 화면에 « 나는 고래입니다 »를 표시합니다.

상속 Cow및 재정의 후 printName클래스 Whale에는 실제로 다음과 같은 데이터 및 메서드가 있습니다.

암호 설명
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
우리는 오래된 방법에 대해 아무것도 모릅니다.

"솔직히 기대하고 있었어."

2) 그러나 그것이 전부는 아닙니다.

Cow 클래스에 다른 두 메서드를 호출하는 메서드가 있다고  가정합니다 printAll. 그러면 코드는 다음과 같이 작동합니다."

화면에 다음과 같이 표시됩니다.
나는 백인입니다.
나는 고래입니다.

암호 설명
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
화면에 다음과 같이 표시됩니다.
나는 백인입니다.
나는 고래입니다.

Whale 객체에서 Cow 클래스의 printAll() 메서드를 호출하면 Cow가 아닌 ​​Whale의 printName() 메서드가 사용됩니다 .

중요한 것은 메서드가 작성된 클래스가 아니라 메서드가 호출된 객체의 유형(클래스)입니다.

"알겠어요."

"비정적 메서드만 상속하고 재정의할 수 있습니다. 정적 메서드는 상속되지 않으므로 재정의할 수 없습니다."

다음은 상속을 적용하고 메서드를 재정의한 후 Whale 클래스의 모습입니다.

암호 설명
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
다음은 상속을 적용하고 메서드를 재정의한 후 Whale 클래스의 모습입니다. 우리는 오래된 printName방법에 대해 아무것도 모릅니다.

3) 유형 캐스팅.

여기에 더 흥미로운 점이 있습니다. 클래스는 부모 클래스의 모든 메서드와 데이터를 상속하기 때문에 이 클래스의 객체는 부모 클래스 (및 부모 클래스의 부모 등, Object 클래스까지) 의 변수에 의해 참조될 수 있습니다 . 다음 예를 고려하십시오.

암호 설명
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
화면에 다음과 같이 표시됩니다.
저는 백인입니다.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
화면에 다음과 같이 표시됩니다.
저는 백인입니다.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
화면에
Whale@da435a가 표시됩니다.
toString() 메서드는 Object 클래스에서 상속됩니다.

"좋은 물건이야. 그런데 이게 왜 필요해?"

"귀중한 기능입니다. 나중에 매우 귀중하다는 것을 알게 될 것입니다."

4) 지연 바인딩(동적 디스패치).

다음과 같습니다.

암호 설명
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
화면에 다음과 같이 표시됩니다.
저는 고래입니다.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
화면에 다음과 같이 표시됩니다.
저는 고래입니다.

우리가 호출하는 특정 printName 메서드(Cow 또는 Whale 클래스의 메서드)를 결정하는 것은 변수의 유형이 아니라 변수가 참조하는 개체의 유형입니다.

Cow 변수 Whale 객체 에 대한 참조를 저장 하고 Whale 클래스 에 정의된 printName 메서드가 호출됩니다.

"글쎄, 그들은 명확성을 위해 그것을 추가하지 않았습니다."

"예, 그렇게 명백하지 않습니다. 이 중요한 규칙을 기억하십시오."

변수에 대해 호출할 수 있는 메서드 집합은 변수 유형에 따라 결정됩니다. 그러나 호출되는 특정 메소드/구현은 변수가 참조하는 객체의 유형/클래스에 따라 결정됩니다.

"노력하겠습니다."

"이 문제를 계속 접하게 되므로 빠르게 이해하고 잊지 못할 것입니다."

5) 유형 캐스팅.

캐스팅은 참조 유형, 즉 클래스에 대해 기본 유형에 대해 다르게 작동합니다. 그러나 확장 및 축소 변환은 참조 유형에도 적용됩니다. 다음 예를 고려하십시오.

확대 변환 설명
Cow cow = new Whale();

고전적인 확대 변환. 이제 Whale 개체의 Cow 클래스에 정의된 메서드만 호출할 수 있습니다.

컴파일러는 Cow 변수를 사용하여 Cow 유형으로 정의된 메서드를 호출할 수 있도록 합니다.

축소 변환 설명
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
유형 검사 가 포함된 고전적인 축소 변환입니다 . Cow 유형의 cow 변수 는 Whale 개체에 대한 참조를 저장합니다.
이것이 사실인지 확인한 다음 (확대) 유형 변환을 수행합니다. 이를 유형 캐스팅 이라고도 합니다 .
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
개체의 형식을 확인하지 않고 참조 형식의 축소 변환을 수행할 수도 있습니다.
이 경우 암소 변수가 Whale 객체가 아닌 다른 것을 가리키고 있으면 예외(InvalidClassCastException)가 발생합니다.

6) 그리고 이제 맛있는 것을 위해. 원래 방법을 호출합니다.

상속된 메서드를 재정의할 때 완전히 대체하고 싶지 않은 경우가 있습니다. 때로는 약간만 추가하고 싶을 수도 있습니다.

이 경우 새 메서드의 코드가 기본 클래스에서 동일한 메서드를 호출하기를 원할 것입니다. 그리고 자바는 당신이 이것을 하도록 합시다. 이것이 수행되는 방법입니다.  super.method().

여기 몇 가지 예가 있어요.

암호 설명
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
화면에 다음이 표시됩니다.
저는 백인입니다.
이것은 거짓입니다. 저는 소입니다.
저는 고래입니다.

"흠. 뭐, 교훈이 있었군. 내 로봇 귀가 녹을 뻔했어."

"예, 이것은 간단한 것이 아닙니다. 접하게 될 가장 어려운 자료 중 하나입니다. 교수는 다른 저자의 자료에 대한 링크를 제공하겠다고 약속하여 여전히 이해하지 못하는 것이 있으면 채울 수 있습니다. 틈."