다형성

다형성의 사전적 정의는 생물학에서 유기체나 종이 여러 가지 형태나 단계를 가질 수 있는 원리를 말합니다. 이 원리는 객체 지향 프로그래밍과 Java 언어와 같은 언어에도 적용될 수 있습니다. 클래스의 서브클래스는 고유한 동작을 정의하면서도 부모 클래스와 동일한 기능을 일부 공유할 수 있습니다.

다형성은 Bicycle 클래스를 약간만 수정하여 시연할 수 있습니다. 예를 들어, 현재 인스턴스에 저장된 모든 데이터를 표시하는 printDescription() 메서드를 클래스에 추가할 수 있습니다.

public void printDescription(){
    System.out.println("\nBike is " + "in gear " + this.gear
        + " with a cadence of " + this.cadence +
        " and travelling at a speed of " + this.speed + ". ");
}

Java 언어의 다형성 기능을 보여주기 위해 Bicycle 클래스를 MountainBikeRoadBike 클래스로 확장합니다. MountainBike의 경우 자전거에 앞쪽 쇼크 업소버가 있는지 여부를 나타내는 String 값인 서스펜션 필드를 Front에 추가합니다. 또는 자전거에 앞뒤 쇼크 업소버가 있는 경우 Dual입니다.

업데이트된 클래스는 다음과 같습니다:

public class MountainBike extends Bicycle {
    private String suspension;
 
    public MountainBike(
               int startCadence,
               int startSpeed,
               int startGear,
               String suspensionType){
        super(startCadence,
              startSpeed,
              startGear);
        this.setSuspension(suspensionType);
    }
 
    public String getSuspension(){
      return this.suspension;
    }
 
    public void setSuspension(String suspensionType) {
        this.suspension = suspensionType;
    }
 
    public void printDescription() {
        super.printDescription();
        System.out.println("The " + "MountainBike has a" +
            getSuspension() + " suspension.");
    }
} 

재정의된 printDescription() 메서드에 주목하세요. 이전에 제공된 정보 외에도 서스펜션에 대한 추가 데이터가 출력에 포함됩니다.

다음으로 RoadBike 클래스를 생성합니다. 로드바이크나 레이싱 바이크는 타이어가 얇기 때문에 타이어 폭을 추적하는 속성을 추가합니다. 다음은 RoadBike 클래스입니다:

public class RoadBike extends Bicycle{
    // In millimeters (mm)
    private int tireWidth;
 
    public RoadBike(int startCadence,
                    int startSpeed,
                    int startGear,
                    int newTireWidth){
        super(startCadence,
              startSpeed,
              startGear);
        this.setTireWidth(newTireWidth);
    }
 
    public int getTireWidth(){
      return this.tireWidth;
    }
 
    public void setTireWidth(int newTireWidth){
        this.tireWidth = newTireWidth;
    }
 
    public void printDescription(){
        super.printDescription();
        System.out.println("The RoadBike" + " has " + getTireWidth() +
            " MM tires.");
    }
}

다시 한 번 printDescription() 메서드가 재정의되었습니다. 이번에는 타이어 폭에 대한 정보가 표시됩니다.

요약하자면, Bicycle, MountainBike, RoadBike의 세 가지 클래스가 있습니다. 두 개의 서브 클래스는 printDescription() 메서드를 재정의하고 고유한 정보를 인쇄합니다.

다음은 세 개의 Bicycle 변수를 생성하는 테스트 프로그램입니다. 각 변수는 세 개의 자전거 클래스 중 하나에 할당됩니다. 그런 다음 각 변수가 인쇄됩니다.

public class TestBikes {
  public static void main(String[] args){
    Bicycle bike01, bike02, bike03;
 
    bike01 = new Bicycle(20, 10, 1);
    bike02 = new MountainBike(20, 10, 5, "Dual");
    bike03 = new RoadBike(40, 20, 8, 23);
 
    bike01.printDescription();
    bike02.printDescription();
    bike03.printDescription();
  }
}

다음은 테스트 프로그램의 출력입니다:

Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10. 
 
Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10. 
The MountainBike has a Dual suspension.
 
Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20. 
The RoadBike has 23 MM tires.

Java 가상 머신(JVM)은 각 변수에서 참조되는 객체에 대해 적절한 메서드를 호출합니다. 변수의 타입으로 정의된 메서드는 호출하지 않습니다. 이러한 동작을 가상 메서드 호출이라고 하며 Java 언어의 중요한 다형성 기능의 한 측면을 보여줍니다.

 

필드 숨기기

클래스 내에서 수퍼클래스의 필드와 이름이 같은 필드는 타입이 다르더라도 수퍼클래스의 필드를 숨깁니다. 하위 클래스 내에서 슈퍼클래스의 필드는 단순한 이름으로 참조할 수 없습니다. 대신 다음 섹션에서 다룰 슈퍼클래스를 통해 필드에 액세스해야 합니다. 일반적으로 필드를 숨기는 것은 코드를 읽기 어렵게 만들므로 권장하지 않습니다.

 

Super 키워드 사용하기

슈퍼클래스 멤버 접근하기

메서드가 슈퍼클래스의 메서드 중 하나를 재정의하는 경우, super 키워드를 사용하여 재정의된 메서드를 호출할 수 있습니다. super를 사용하여 숨겨진 필드를 참조할 수도 있습니다(필드를 숨기는 것은 권장하지 않습니다). 이 클래스인 Superclass를 생각해 봅시다:

public class Superclass {
 
    public void printMethod() {
        System.out.println("Printed in Superclass.");
    }
}

다음은 Subclass라는 서브클래스가 printMethod()를 재정의하는 것입니다:

public class Subclass extends Superclass {
 
    // overrides printMethod in Superclass
    public void printMethod() {
        super.printMethod();
        System.out.println("Printed in Subclass");
    }
    public static void main(String[] args) {
        Subclass s = new Subclass();
        s.printMethod();    
    }
}

Subclass 내에서 printMethod()라는 단순한 이름은 Subclass에 선언된 것을 참조하며, 이는 Superclass에 있는 것을 재정의합니다. 따라서 Superclass에서 상속된 printMethod()를 참조하려면 Subclass는 그림과 같이 super를 사용하여 한정된 이름을 사용해야 합니다. Subclass를 컴파일하고 실행하면 다음과 같이 출력됩니다:

Printed in Superclass.
Printed in Subclass

서브클래스 생성자

다음 예제는 슈퍼 키워드를 사용하여 슈퍼클래스의 생성자를 호출하는 방법을 보여줍니다. Bicycle 예제에서 MountainBikeBicycle의 서브클래스라는 것을 기억하세요. 다음은 슈퍼클래스 생성자를 호출한 다음 자체 초기화 코드를 추가하는 MountainBike(서브클래스) 생성자입니다:

public MountainBike(int startHeight, 
                    int startCadence,
                    int startSpeed,
                    int startGear) {
    super(startCadence, startSpeed, startGear);
    seatHeight = startHeight;
} 

수퍼클래스 생성자 호출은 서브클래스 생성자의 첫 번째 줄에 있어야 합니다.

수퍼클래스 생성자를 호출하는 구문은 다음과 같습니다.

super();

or

super(parameter list);

super()를 사용하면 인자가 없는 슈퍼클래스 생성자가 호출됩니다. super(parameter list)를 사용하면 일치하는 매개변수 목록을 가진 슈퍼클래스 생성자가 호출됩니다.

Note: 생성자가 명시적으로 수퍼클래스 생성자를 호출하지 않으면 Java 컴파일러는 자동으로 수퍼클래스의 무인수 생성자에 대한 호출을 삽입합니다. 수퍼클래스에 인자 없는 생성자가 없는 경우 컴파일 타임 오류가 발생합니다. Object에는 이러한 생성자가 있으므로 Object가 유일한 수퍼클래스인 경우 문제가 없습니다.

하위 클래스 생성자가 명시적이든 암시적이든 그 상위 클래스의 생성자를 호출하는 경우, Object의 생성자까지 호출되는 전체 생성자 체인이 있을 것이라고 생각할 수 있습니다. 실제로는 그렇습니다. 이를 생성자 연쇄 라고 하며, 클래스 하강이 긴 줄이 있을 때 주의해야 합니다.

 

최종 클래스 및 메서드 작성

클래스의 메서드 중 일부 또는 전부를 최종 메서드로 선언할 수 있습니다. 메서드 선언에 final 키워드를 사용하면 하위 클래스에서 메서드를 재정의할 수 없음을 나타냅니다. Object 클래스는 이렇게 합니다. —클래스의 메서드 중 상당수가 final입니다.

변경해서는 안 되는 구현이 있고 객체의 일관된 상태를 유지하는 것이 중요한 경우 메서드를 final 메서드로 만들 수 있습니다. 예를 들어, 이 ChessAlgorithm 클래스의 getFirstPlayer() 메서드를 final로 만들고 싶을 수 있습니다:

class ChessAlgorithm {
    enum ChessPlayer { WHITE, BLACK }
    ...
    final ChessPlayer getFirstPlayer() {
        return ChessPlayer.WHITE;
    }
    ...
}

생성자에서 호출되는 메서드는 일반적으로 final로 선언해야 합니다. 생성자가 non-final 메서드를 호출하면 서브클래스가 해당 메서드를 재정의하여 예상치 못한 결과가 나올 수 있습니다.

전체 클래스를 최종적으로 선언할 수도 있습니다. 최종 선언된 클래스는 서브클래싱할 수 없습니다. 이는 예를 들어 String 클래스와 같은 불변 클래스를 만들 때 특히 유용합니다.