자바의 변수의 타입은 컴파일 시점에 만들어지고,
객체는 인터프리터(런타임 시점)에 만들어짐
변수의 다형성
하나의 변수로 다양한 객체를 생성할 수 있음 (단, 상속을 전제로 해야 함)
// 부모의 데이터 타입으로 자식의 객체 생성이 가능하다
// teacher와 student가 person을 상속 받고 있을 때 아래와 같이 타입의 다형성이 허용됨
PersonDTO teacher = new TeacherDTO();
PersonDTO student = new StudentDTO();PersonDTO [] personArr = new PersonDTO[10]; // 배열을 생성하면서 배열의 요소를 Person 타입으로 지정한 것
personArr[0] = teacher;
personArr[1] = student;
// 단일 타입만 가능하다는 배열의 원칙에 위배될까? -> No. 둘 다 Person 타입을 담은 것이기에 가능함
⭐️ 다운 캐스팅
- 상속에서 캐스팅의 기본 : 자식은 부모의 구성요소에 접근할 수 있다
- 자식 —→ 부모
- 자식 <-x- 부모 (다운 캐스팅)
 
- 그러나 다운 캐스팅을 명시적으로 표시하여 가능하게 할 수 있다
PersonDTO [] personArr = new PersonDTO[10];
    personArr[0] = teacher;
    personArr[1] = student;
for (int idx = 0; idx < personArr.length; idx++) {
        PersonDTO person = personArr[0];
        // person.teacherInfo(); // ❌ : teacherInfo는 TeacherDTO의 메소드, person은 PersonDTO 타입으로, 그냥은 사용할 수 없음.
        ((TeacherDTO)person).teacherInfo(); // ✅ : 다운캐스팅
}이 경우 personArr[0]에 teacher가 할당되었지만, personArr 배열 객체는 PersonDTO 타입으로 선언되었기 때문에 PersonDTO의 자식인 TeacherDTO의 요소인 teacherInfo()를 person.teacherInfo()와 같이 사용할 수는 없다.
`((TeacherDTO)person).teacherInfo();` 와 같이 해당 요소가 PersonDTO를 상속 받은 TeacherDTO 타입임을 명시해서 teacherInfo()를 사용할 수 있다.
이 개념을 해당 예제에 제대로 적용해보면 아래와 같이 완성할 수 있다.
for (int idx = 0 ; idx < personArr.length ; idx++) {
        PersonDTO person = personArr[idx] ;
      if (person == null) { // 참조 타입의 경우 null 체크 필요. (배열 = 참조 타입)
              break;
    } else if (person instanceof StudentDTO) {
        System.out.println(((StudentDTO)person).studentInfo());
    } else if( person instanceof TeacherDTO) {
        System.out.println(((TeacherDTO)person).teacherInfo());
    }
}personArr에 할당된 각 요소가 StudentDTO인지 TeacherDTO인지 먼저 체크해주고, 각각의 분기문에서 다운캐스팅을 통해 자식이 가진 메소드를 사용할 수 있도록 한다.
메서드의 다형성
= 메서드 오버라이딩
⭐️ Overloading vs Overriding
| 오버로딩 | 오버라이딩 | 
|---|---|
| 같은 클래스에서 | 상속 관계에서 | 
| 매개변수의 타입과 개수를 달리해서 정의된 구현부가 동일한 형태 | 매개변수 타입과 개수가 동일하고, 구현부가 다른 형태의 메서드 | 
public class A {
    public String f() {
        System.out.println("A");
    }
}
public class B extends A {
    @Override
    public String f() {
        System.out.println("B");
    }
}
A B = new B();
B.f()
// B 출력😮 부모 타입인데 자식 메소드에 저렇게 접근이 가능해?
→ Yes. 오버라이딩하면 부모 타입으로 자식에 대한 접근이 다운 캐스팅 없이도 가능하다 (바텀업 방식으로 읽어내기 때문)
매개변수의 다형성
public class ArgPolyApp {
    private static PersonDTO [] personArr;
    private static int idx;
    public static void main(String[] args) {
        // ...
      setTeacherArr(student); // ❌ 
      setStudentArr(teacher); // ❌
    }
    public static void setTeacherArr(TeacherDTO element) {
        personArr[idx++] = element;
    }
    public static void setStudentArr(StudentDTO element) {
        personArr[idx++] = element;
    }
두 메소드는 구현부가 동일한데, 아래와 같이 메소드명을 setPersonArr로 변경해서 메소드 오버로딩을 할 수 있다. 이 경우 오류가 발생하지 않음.
    public static void setPersonArr(TeacherDTO element) {
        personArr[idx++] = element;
    }
    public static void setPersonArr(StudentDTO element) {
        personArr[idx++] = element;
    }
    setPersonArr(student);
    setPersonArr(teacher);
또는 매개변수의 다형성을 활용하여 하나의 메소드로 두 가지(TeacherDTO, StudentDTO) 타입을 받을 수 있다.
public class ArgPolyApp {
// ...
    public static void main(String[] args) {
        // ...
      setPersonArr(student); 
      setPersonArr(teacher);
    }
    public static void setPersonArr(PersonDTO element) {
        personArr[idx++] = element;
    }
final
final을 메서드에 붙이면 그 메서드는 더 이상 오버라이딩할 수 없음
class Parent {
    final void greet() {
         System.out.println("Hello from Parent");
    }
}
class Child extends Parent {
    @Override
    void greet() { // ❌
        System.out.println("Hello from Child");
    }
}자식 클래스에서 재정의하려고 하면 오류가 발생한다.
'백엔드 > JAVA' 카테고리의 다른 글
| [JAVA] 자바 OOP의 특징 3 - 추상화 (추상 클래스와 인터페이스) (2) | 2025.08.13 | 
|---|---|
| [JAVA] Java Package 선언의 의미와 역할 (3) | 2025.08.11 |