자바의 다형성과 상속

전설 Venkat Subramaniam에 따르면 다형성은 객체 지향 프로그래밍에서 가장 중요한 개념입니다. 다형성 ( 또는 객체의 유형에 따라 특수한 작업을 실행하는 객체의 능력)은 Java 코드를 유연하게 만듭니다. Command, Observer, Decorator, Strategy와 같은 디자인 패턴과 Gang Of Four가 만든 많은 다른 패턴은 모두 어떤 형태의 다형성을 사용합니다. 이 개념을 마스터하면 프로그래밍 문제에 대한 솔루션을 통해 생각하는 능력이 크게 향상됩니다.

코드 받기

이 챌린지에 대한 소스 코드를 얻고 여기에서 자체 테스트를 실행할 수 있습니다. //github.com/rafadelnero/javaworld-challengers

다형성의 인터페이스 및 상속

이 Java Challenger를 통해 우리는 다형성과 상속 간의 관계에 초점을 맞추고 있습니다. 명심해야 할 중요한 점은 다형성에 상속이나 인터페이스 구현이 필요하다는 입니다. 아래 예에서 Duke와 Juggy가 등장하는 것을 볼 수 있습니다.

 public abstract class JavaMascot { public abstract void executeAction(); } public class Duke extends JavaMascot { @Override public void executeAction() { System.out.println("Punch!"); } } public class Juggy extends JavaMascot { @Override public void executeAction() { System.out.println("Fly!"); } } public class JavaMascotTest { public static void main(String... args) { JavaMascot dukeMascot = new Duke(); JavaMascot juggyMascot = new Juggy(); dukeMascot.executeAction(); juggyMascot.executeAction(); } } 

이 코드의 출력은 다음과 같습니다.

 Punch! Fly! 

특정 구현으로 인해 DukeJuggy의 작업이 모두 실행됩니다.

메서드가 다형성을 오버로딩합니까?

많은 프로그래머가 다형성과 메서드 재정의 및 메서드 오버로딩의 관계에 대해 혼란스러워합니다. 사실, 메서드 재정의 만이 진정한 다형성입니다. 오버로딩은 동일한 메서드의 이름을 공유하지만 매개 변수는 다릅니다. 다형성은 광범위한 용어이므로 항상이 주제에 대한 토론이있을 것입니다.

다형성의 목적은 무엇입니까?

다형성을 사용하는 가장 큰 장점과 목적은 구현 코드에서 클라이언트 클래스를 분리하는 것입니다. 하드 코딩되는 대신 클라이언트 클래스는 필요한 작업을 실행하기 위해 구현을받습니다. 이런 식으로 클라이언트 클래스는 느슨한 결합의 예인 작업을 실행할 수있을만큼만 알고 있습니다.

다형성의 목적을 더 잘 이해하려면 다음을 살펴보십시오 SweetCreator.

 public abstract class SweetProducer { public abstract void produceSweet(); } public class CakeProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cake produced"); } } public class ChocolateProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Chocolate produced"); } } public class CookieProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cookie produced"); } } public class SweetCreator { private List sweetProducer; public SweetCreator(List sweetProducer) { this.sweetProducer = sweetProducer; } public void createSweets() { sweetProducer.forEach(sweet -> sweet.produceSweet()); } } public class SweetCreatorTest { public static void main(String... args) { SweetCreator sweetCreator = new SweetCreator(Arrays.asList(new CakeProducer(), new ChocolateProducer(), new CookieProducer())); sweetCreator.createSweets(); } } 

이 예에서는 SweetCreator클래스가 클래스 만 알고 있음을 알 수 있습니다  SweetProducer . 각각의 구현을 알지 못합니다 Sweet. 이러한 분리는 클래스를 업데이트하고 재사용 할 수있는 유연성을 제공하며 코드를 훨씬 쉽게 유지 관리 할 수 ​​있습니다. 코드를 디자인 할 때 항상 최대한 유연하고 유지 관리 할 수있는 방법을 찾으십시오. 다형성은 이러한 목적으로 사용할 수있는 매우 강력한 기술입니다.

: @Override주석은 프로그래머가 재정의해야하는 동일한 메서드 서명을 사용해야합니다. 메서드가 재정의되지 않으면 컴파일 오류가 발생합니다.

메서드 재정의의 공변 반환 형식

공변 유형 인 경우 재정의 된 메서드의 반환 유형을 변경할 수 있습니다. 공변 유형은 기본적으로 반환 형식의 서브 클래스입니다. 예를 들어 보겠습니다.

 public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } } 

때문에 Duke입니다 JavaMascot, 우리는 오버라이드 (override) 할 때 반환 형식을 변경할 수 있습니다.

핵심 Java 클래스를 사용한 다형성

우리는 핵심 Java 클래스에서 항상 다형성을 사용합니다. 하나의 매우 간단한 예는 인터페이스를 유형으로 ArrayList선언하는 클래스를   인스턴스화하는 것입니다 List.

 List list = new ArrayList(); 

더 나아가려면 다형성 없이 Java Collections API 사용하는 다음 코드 샘플을 고려하십시오 .

 public class ListActionWithoutPolymorphism { // Example without polymorphism void executeVectorActions(Vector vector) {/* Code repetition here*/} void executeArrayListActions(ArrayList arrayList) {/*Code repetition here*/} void executeLinkedListActions(LinkedList linkedList) {/* Code repetition here*/} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList copyOnWriteArrayList) { /* Code repetition here*/} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector()); listAction.executeArrayListActions(new ArrayList()); listAction.executeLinkedListActions(new LinkedList()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList()); } 

추악한 코드, 그렇지 않습니까? 그것을 유지하려고한다고 상상해보십시오! 이제 다형성이 있는 동일한 예제 를보십시오 .

 public static void main(String … polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List list) { // Execute actions with different lists } } public class ListActionInvoker { public static void main(String... masterPolymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(new Vector()); listAction.executeListActions(new ArrayList()); listAction.executeListActions(new LinkedList()); listAction.executeListActions(new CopyOnWriteArrayList()); } } 

다형성의 이점은 유연성과 확장 성입니다. 여러 가지 다른 메서드를 만드는 대신 제네릭 List형식 을받는 하나의 메서드 만 선언 할 수 있습니다 .

다형성 메서드 호출에서 특정 메서드 호출

다형성 호출에서 특정 메서드를 호출 할 수 있지만이를 수행하려면 유연성이 저하됩니다. 예를 들면 다음과 같습니다.

 public abstract class MetalGearCharacter { abstract void useWeapon(String weapon); } public class BigBoss extends MetalGearCharacter { @Override void useWeapon(String weapon) { System.out.println("Big Boss is using a " + weapon); } void giveOrderToTheArmy(String orderMessage) { System.out.println(orderMessage); } } public class SolidSnake extends MetalGearCharacter { void useWeapon(String weapon) { System.out.println("Solid Snake is using a " + weapon); } } public class UseSpecificMethod { public static void executeActionWith(MetalGearCharacter metalGearCharacter) { metalGearCharacter.useWeapon("SOCOM"); // The below line wouldn't work // metalGearCharacter.giveOrderToTheArmy("Attack!"); if (metalGearCharacter instanceof BigBoss) { ((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!"); } } public static void main(String... specificPolymorphismInvocation) { executeActionWith(new SolidSnake()); executeActionWith(new BigBoss()); } } 

여기서 사용하는 기술은 캐스팅 또는 런타임에 의도적으로 개체 유형을 변경하는 것입니다.

제네릭 유형을 특정 유형으로 캐스팅 할 때만 특정 메서드를 호출 할 수 있습니다 . 좋은 비유는 컴파일러에게 "이봐, 내가 여기서 무엇을하는지 알고 있으므로 객체를 특정 유형으로 캐스트하고 특정 메소드를 사용할 것"이라고 명시 적으로 말하는 것입니다.  

위의 예를 참조하면 컴파일러가 특정 메서드 호출을 거부하는 중요한 이유가 있습니다 SolidSnake. 전달되는 클래스는 . 이 경우 컴파일러에서의 모든 하위 클래스에 메서드가 선언 되었는지 확인할 수 MetalGearCharacter있는 giveOrderToTheArmy방법이 없습니다.

instanceof예약 된 키워드

예약어에주의하십시오 instanceof. 특정 메서드를 호출하기 전에 MetalGearCharacter" instanceof" 인지 질문했습니다 BigBoss. 이 경우 아니었다BigBoss 예, 우리는 다음과 같은 예외 메시지가 나타날 것입니다 :

 Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss 

super예약 된 키워드

Java 수퍼 클래스에서 속성 또는 메소드를 참조하려면 어떻게해야합니까? 이 경우 super예약어를 사용할 수 있습니다 . 예를 들면 :

 public class JavaMascot { void executeAction() { System.out.println("The Java Mascot is about to execute an action!"); } } public class Duke extends JavaMascot { @Override void executeAction() { super.executeAction(); System.out.println("Duke is going to punch!"); } public static void main(String... superReservedWord) { new Duke().executeAction(); } } 

Using the reserved word super in Duke’s executeAction method  invokes the superclass method.  We then execute the specific action from Duke. That’s why we can see both messages in the output below:

 The Java Mascot is about to execute an action! Duke is going to punch! 

Take the polymorphism challenge!

Let’s try out what you’ve learned about polymorphism and inheritance. In this challenge, you’re given a handful of methods from Matt Groening’s The Simpsons, and your challenge is to deduce what the output for each class will be. To start, analyze the following code carefully:

 public class PolymorphismChallenge { static abstract class Simpson { void talk() { System.out.println("Simpson!"); } protected void prank(String prank) { System.out.println(prank); } } static class Bart extends Simpson { String prank; Bart(String prank) { this.prank = prank; } protected void talk() { System.out.println("Eat my shorts!"); } protected void prank() { super.prank(prank); System.out.println("Knock Homer down"); } } static class Lisa extends Simpson { void talk(String toMe) { System.out.println("I love Sax!"); } } public static void main(String... doYourBest) { new Lisa().talk("Sax :)"); Simpson simpson = new Bart("D'oh"); simpson.talk(); Lisa lisa = new Lisa(); lisa.talk(); ((Bart) simpson).prank(); } } 

What do you think? What will the final output be? Don’t use an IDE to figure this out! The point is to improve your code analysis skills, so try to determine the output for yourself.

Choose your answer and you’ll be able to find the correct answer below.

 A) I love Sax! D'oh Simpson! D'oh B) Sax :) Eat my shorts! I love Sax! D'oh Knock Homer down C) Sax :) D'oh Simpson! Knock Homer down D) I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

What just happened? Understanding polymorphism

For the following method invocation:

 new Lisa().talk("Sax :)"); 

the output will be “I love Sax!” This is  because we are passing a String to the method and Lisa has the method.

For the next invocation:

 Simpson simpson = new Bart("D'oh");

simpson.talk();

The output will be "Eat my shorts!" This is because we’re instantiating  the Simpson type with Bart.

Now check this one, which is a little trickier:

 Lisa lisa = new Lisa(); lisa.talk(); 

Here, we are using method overloading with inheritance. We are not passing anything to the talk method, which is why the Simpson talk method is invoked.  In this case the output will be:

 "Simpson!" 

Here’s one more:

 ((Bart) simpson).prank(); 

In this case, the prank String was passed when we instantiated the Bart class with new Bart("D'oh");. In this case,  first the super.prank method will be invoked, followed by the specific prank method from Bart. The output will be:

 "D'oh" "Knock Homer down" 

Video challenge! Debugging Java polymorphism and inheritance

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the Java polymorphism challenge:

Common mistakes with polymorphism

It’s a common mistake to think it’s possible to invoke a specific method without using casting.

Another mistake is being unsure what method will be invoked when instantiating a class polymorphically. Remember that the method to be invoked is the method of the created instance.

Also remember that method overriding is not method overloading.

It’s impossible to override a method if the parameters are different. It is possible to change the return type of the overridden method if the return type is a subclass of the superclass method.

다형성에 대해 기억해야 할 사항

  • 생성 된 인스턴스는 다형성을 사용할 때 호출 할 메서드를 결정합니다.
  • @Override주석은 재정의 된 방법을 사용하는 프로그래머를 의무화; 그렇지 않으면 컴파일러 오류가 발생합니다.
  • 다형성은 일반 클래스, 추상 클래스 및 인터페이스와 함께 사용할 수 있습니다.
  • 대부분의 디자인 패턴은 어떤 형태의 다형성에 의존합니다.
  • 다형성 하위 클래스에서 특정 메서드를 사용하는 유일한 방법은 캐스팅을 사용하는 것입니다.
  • 다형성을 사용하여 코드에서 강력한 구조를 디자인 할 수 있습니다.
  • 테스트를 실행하십시오. 이렇게하면이 강력한 개념을 마스터 할 수 있습니다!

정답

이 Java Challenger에 대한 대답은 D 입니다. 출력은 다음과 같습니다.

 I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

이 이야기 "자바의 다형성과 상속"은 원래 JavaWorld에 의해 출판되었습니다.