JavaRush /Java Blog /Random-KO /Java의 다형성

Java의 다형성

Random-KO 그룹에 게시되었습니다
OOP에 대한 질문은 IT 회사에서 Java 개발자의 직위에 대한 기술 인터뷰의 필수적인 부분입니다. 이번 글에서는 OOP의 원리 중 하나인 다형성(Polymorphism)에 대해 이야기하겠습니다. 인터뷰 중에 자주 묻는 질문에 초점을 맞추고 명확성을 위해 작은 예도 제공합니다.

다형성이란 무엇입니까?

다형성은 이 객체의 특정 유형에 대한 정보 없이 동일한 인터페이스를 가진 객체를 동일하게 사용하는 프로그램의 능력입니다. 이런 식으로 다형성이 무엇인지 묻는 질문에 대답하면 무슨 뜻인지 설명하라는 요청을 받을 가능성이 높습니다. 다시 한 번 추가 질문을 많이하지 않고 면접관을 위해 모든 것을 정리하십시오.

인터뷰에서 Java의 다형성 - 1
OOP 접근 방식에는 클래스 기반 객체의 상호 작용을 기반으로 Java 프로그램을 구축하는 것이 포함된다는 사실부터 시작할 수 있습니다. 클래스는 프로그램에서 생성되는 개체에 따라 미리 작성된 도면(템플릿)입니다. 더욱이 클래스에는 항상 특정 유형이 있으며, 좋은 프로그래밍 스타일을 사용하면 이름으로 목적을 "알려줍니다". 또한 Java는 강력한 유형의 언어이기 때문에 프로그램 코드는 변수를 선언할 때 항상 객체의 유형을 표시해야 합니다. 여기에 엄격한 유형 지정은 코드 안전성과 프로그램 신뢰성을 높이고 컴파일 단계에서 유형 비호환성 오류(예: 문자열을 숫자로 나누려는 시도)를 방지할 수 있게 해줍니다. 당연히 컴파일러는 선언된 유형을 "알고" 있어야 합니다. 이는 JDK의 클래스일 수도 있고 우리가 직접 만든 클래스일 수도 있습니다. 프로그램 코드로 작업할 때 선언할 때 할당한 유형의 개체뿐만 아니라 해당 개체의 하위 항목도 사용할 수 있다는 점을 면접관에게 알려주세요. 이는 중요한 점입니다. 여러 유형을 마치 하나인 것처럼 처리할 수 있습니다(해당 유형이 기본 유형에서 파생되는 한). 이는 또한 슈퍼클래스 유형의 변수를 선언하면 해당 변수의 하위 항목 중 하나의 값을 할당할 수 있음을 의미합니다. 예를 들면 면접관이 좋아할 것 같아요. 객체 그룹에 대해 공통(기본)이 될 수 있는 객체를 선택하고 그로부터 몇 가지 클래스를 상속합니다. 기본 클래스:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + "I dance like everyone else.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. " ;
    }
}
하위 항목에서 기본 클래스 메서드를 재정의합니다.
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance() {
        System.out.println( toString() + "I dance electric boogie!");
    }
}

public class BreakDankDancer extends Dancer{

    public BreakDankDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance(){
        System.out.println(toString() + "I'm breakdancing!");
    }
}
Java의 다형성과 프로그램에서의 객체 사용의 예:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Anton", 18);

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast to base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// polymorphic method call
        }
    }
}
다음 줄에 있는 내용을 메서드 코드에 main표시합니다 .
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
우리는 슈퍼클래스 유형 변수를 선언하고 여기에 하위 항목 중 하나의 값을 할당했습니다. 아마도 Java에는 엄격한 유형 지정이 있으므로 컴파일러가 할당 기호의 왼쪽과 오른쪽에 선언된 유형 간의 불일치에 대해 불평하지 않는 이유에 대한 질문을 받게 될 것입니다. 여기서는 상향 유형 변환이 작동한다고 설명합니다. 객체에 대한 참조는 기본 클래스에 대한 참조로 해석됩니다. 더욱이, 코드에서 이러한 구성을 발견한 컴파일러는 이를 자동으로 암시적으로 수행합니다. 예제 코드를 보면 할당 기호 왼쪽에 선언된 클래스 타입이 Dancer오른쪽에 선언된 여러 형태(타입)가 있음을 알 수 있다 BreakDankDancer. ElectricBoogieDancer각 양식은 슈퍼클래스 메소드에 정의된 공통 기능에 대해 고유한 동작을 가질 수 있습니다 dance. 즉, 슈퍼클래스에 선언된 메서드는 하위 클래스에서 다르게 구현될 수 있습니다. 이 경우 메소드 재정의를 다루고 있으며 이것이 바로 다양한 형태(동작)를 생성하는 것입니다. 실행을 위해 기본 메소드 코드를 실행하면 이를 확인할 수 있습니다. 프로그램 출력 저는 Anton이고 18세입니다. 나는 다른 사람들처럼 춤을 춘다. 저는 Alexey이고 19세입니다. 나는 브레이크 댄스를 춘다! 저는 이고르이고 20살이에요. 일렉트릭 부기 춤을 춰요! 자손에서 재정의를 사용하지 않으면 다른 동작이 발생하지 않습니다. 예를 들어, 수업 방법을 주석으로 처리 BreakDankDancer하면 프로그램의 출력은 다음과 같습니다. 저는 Anton이고 18세입니다. 나는 다른 사람들처럼 춤을 춘다. 저는 Alexey이고 19세입니다. 나는 다른 사람들처럼 춤을 춘다. 저는 이고르이고 20살이에요. 나는 다른 사람들처럼 춤을 춘다. 이는 새로운 클래스를 생성하는 데 아무런 의미가 없다는 것을 의미합니다 . Java 다형성의 원리는 정확히 무엇입니까? 특정 유형을 알지 못한 채 프로그램에서 객체를 사용하려면 어디에 숨겨져 있습니까? 이 예에서 이는 유형의 객체에 대한 메서드 호출입니다 . Java 다형성은 프로그램이 객체 또는 객체 의 유형이 무엇인지 알 필요가 없음을 의미합니다 . 가장 중요한 것은 클래스의 자손이라는 것입니다 . 그리고 자손에 관해 이야기한다면 Java의 상속은 뿐만 아니라 . 이제 Java는 다중 상속을 지원하지 않는다는 점을 기억해야 합니다. 각 유형은 하나의 상위 클래스(슈퍼클래스)와 무제한의 하위 클래스(하위 클래스)를 가질 수 있습니다. 따라서 인터페이스는 클래스에 여러 기능을 추가하는 데 사용됩니다. 인터페이스는 상속에 비해 객체와 상위 객체의 결합을 줄이고 매우 널리 사용됩니다. Java에서 인터페이스는 참조 유형이므로 프로그램은 해당 유형을 인터페이스 유형의 변수로 선언할 수 있습니다. 지금은 예를 들기에 좋은 시간입니다. 인터페이스를 만들어 보겠습니다. ElectricBoogieDancerdanceBreakDankDancerElectricBoogieDancerd.dance()dDancerBreakDankDancerElectricBoogieDancerDancerextendsimplements
public interface Swim {
    void swim();
}
명확성을 위해 서로 관련되지 않은 객체를 가져와 그 객체에 인터페이스를 구현해 보겠습니다.
public class Human implements Swim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+"I swim with an inflatable ring.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. ";
    }

}

public class Fish implements Swim{
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish " + name + ". I swim by moving my fins.");

    }

public class UBoat implements Swim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
    }
}
방법 main:
public class Main {

    public static void main(String[] args) {
        Swim human = new Human("Anton", 6);
        Swim fish = new Fish("whale");
        Swim boat = new UBoat(25);

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
인터페이스에 정의된 다형성 메서드를 실행한 결과를 통해 해당 인터페이스를 구현하는 유형의 동작 차이를 확인할 수 있습니다. 그것들은 메소드 실행의 다른 결과로 구성됩니다 swim. 우리의 예를 연구한 후 면접관은 코드를 실행할 때 이유를 물을 수 있습니다.main
for (Swim s : swimmers) {
            s.swim();
}
이러한 클래스에 정의된 메소드가 객체에 대해 호출됩니까? 프로그램을 실행할 때 원하는 메소드 구현을 어떻게 선택합니까? 이러한 질문에 대답하려면 후기(동적) 바인딩에 대해 이야기해야 합니다. 바인딩이란 메서드 호출과 클래스의 특정 구현 간에 연결을 설정하는 것을 의미합니다. 기본적으로 코드는 클래스에 정의된 세 가지 메서드 중 어느 것이 실행될지 결정합니다. Java는 기본적으로 후기 바인딩을 사용합니다(초기 바인딩의 경우처럼 컴파일 타임이 아닌 런타임에). 즉, 코드를 컴파일할 때
for (Swim s : swimmers) {
            s.swim();
}
컴파일러는 코드가 어느 클래스에서 왔는지 Human, Fish또는 Uboat코드가 swim. 이는 동적 디스패치 메커니즘 덕분에 프로그램이 실행될 때만 결정됩니다. 즉, 프로그램 실행 중에 개체 유형을 확인하고 이 유형에 대해 원하는 메서드 구현을 선택합니다. 이것이 어떻게 구현되는지 묻는다면 객체를 로드하고 초기화할 때 JVM이 메모리에 테이블을 구축하고 그 테이블에서 변수를 값과 연결하고 객체를 메소드와 연결한다고 대답할 수 있습니다. 또한 객체가 상속되거나 인터페이스를 구현하는 경우 해당 클래스에 재정의된 메서드가 있는지 먼저 확인합니다. 있는 경우 이 유형에 연결되고, 그렇지 않은 경우 한 수준 더 높은 클래스(상위 클래스)에 정의된 메서드가 검색되며 다단계 계층 구조의 루트까지 검색됩니다. OOP의 다형성과 프로그램 코드의 구현에 대해 말하면서 추상 클래스와 인터페이스를 사용하여 기본 클래스를 정의하기 위해 추상 설명을 사용하는 것이 좋습니다. 이 관행은 추상화 사용을 기반으로 합니다. 즉, 공통 동작과 속성을 분리하고 이를 추상 클래스 내에 포함하거나 공통 동작만 분리합니다. 이 경우 인터페이스를 만듭니다. 인터페이스와 클래스 상속을 기반으로 객체의 계층 구조를 구축하고 설계하는 것은 OOP 다형성 원칙을 충족하기 위한 전제 조건입니다. Java의 다형성 및 혁신 문제와 관련하여 Java 8부터 추상 클래스 및 인터페이스를 생성할 때 키워드를 사용하여 기본 클래스에 추상 메서드의 기본 구현을 작성할 수 있다는 점을 언급할 수 있습니다 default. 예를 들어:
public interface Swim {
    default void swim() {
        System.out.println("Just floating");
    }
}
때로는 다형성 원칙을 위반하지 않도록 기본 클래스에서 메서드를 선언하기 위한 요구 사항에 대해 질문할 수도 있습니다. 여기에서는 모든 것이 간단합니다. 이러한 메서드는 static , privatefinal 이 아니어야 합니다 . Private을 사용하면 해당 메서드를 클래스에서만 사용할 수 있으며 하위 항목에서는 재정의할 수 없습니다. Static은 메서드를 객체가 아닌 클래스의 속성으로 만들기 때문에 항상 슈퍼클래스 메서드가 호출됩니다. Final은 메서드를 변경할 수 없게 만들고 상속자에게 숨겨집니다.

다형성은 Java에서 우리에게 무엇을 제공합니까?

다형성을 사용하면 무엇을 얻을 수 있는지에 대한 질문도 발생할 가능성이 높습니다. 잡초에 너무 깊이 들어가지 않고도 간단히 대답할 수 있습니다.
  1. 객체 구현을 대체할 수 있습니다. 이것이 테스트의 기반입니다.
  2. 프로그램 확장성을 제공합니다. 미래를 위한 기반을 구축하는 것이 훨씬 쉬워집니다. 기존 유형을 기반으로 새로운 유형을 추가하는 것은 OOP 스타일로 작성된 프로그램의 기능을 확장하는 가장 일반적인 방법입니다.
  3. 일반적인 유형이나 동작을 가진 개체를 하나의 컬렉션이나 배열로 결합하고 이를 균일하게 관리할 수 있습니다(예제에서처럼 모든 사람을 춤추게 만드는 방법 dance또는 수영하게 만드는 방법 swim).
  4. 새로운 유형을 생성할 때의 유연성: 상위에서 메소드를 구현하거나 하위에서 이를 재정의하도록 선택할 수 있습니다.

여행에 대한 이별의 말

다형성의 원리는 매우 중요하고 광범위한 주제입니다. 이는 Java OOP 의 거의 절반 과 언어 기본 사항의 상당 부분을 다룹니다. 인터뷰에서 이 원칙을 정의하지 않을 수는 없습니다. 이에 대한 무지나 오해로 인해 인터뷰가 끝날 가능성이 높습니다. 그러므로 시험 전에 게으르지 말고 지식을 확인하고 필요한 경우 새로 고치십시오.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION