안녕하세요. 자바 프로그래밍 입문 여덟번째 이야기입니다.

오늘 포스팅에서는 할 말이 많기 때문에 각설하고 이전 포스팅 내용 복습하겠습니다!
긴 이야기는 복습 이후에...!

이전 포스팅 복습


우선 이전 포스팅에서는 객체지향 프로그래밍이 무엇인지 그리고 constructor 즉 생성자가 무엇인지, 객체지향 프로그래밍의 특징 중에 은닉성(캡슐화)이 무슨 말인지, DTO, DAO, VO의 개념에 대해 살펴보고 프로그램까지 작성을 해봤습니다.

자 각각에 대해 간단하게만 살펴보도록 하겠습니다.

객체 지향 프로그래밍(OOP)

객체 지향에 반대되는 개념이 무엇이었나요? 바로 절차 지향입니다.

우리가 텔레비전에 예를 들어서 살펴봤는데, 텔레비전이 여러 대 있을 때 텔레비전의 현재 채널, 음량, 제조사 등을 절차 지향 프로그래밍으로 구현하려면 TV 각각마다 코드를 작성해주어야 했습니다.

그렇지만 객체 지향 프로그래밍의 도입으로 공통적인 특성을 객체라는 틀에 묶어서 하나로 만들어주고 필요할 때마다 새로 생성(복제의 느낌)해서 부수적인것만 바꿔주면 되는 것이었죠!

객체는 우선 클래스라고 하는 선언자로 선언을 해주고 객체 이름을 붙여준 뒤 중괄호를 넣어줬습니다. 그 안에는 기본적으로 멤버 변수라고 불리는 객체 내부의 변수와 멤버 메서드라고 불리는 객체 내부의 메서드가 있었죠.

class MyClass {
  // 멤버변수
  String name;
  int age;
  
  // 멤버 메서드
  public void method() {
    System.out.println("MyClass method()");
  }
}

또한 객체지향 프로그래밍의 특징 4가지로는 추상화, 다형성, 은닉(캡슐화), 상속이 있었습니다.

생성자(Constructor)

생성자는 객체를 새로 만들고 초기화 해주는 역할을 합니다.

public class MyClass {
  
  MyClass() {
    System.out.println("MyClass의 기본 생성자");
  }

}

위와 같이 클래스명과 동일하게 선언되어 ()와 { }의 형태가 뒤에 붙어있는 녀석이 있는데 이것이 바로 생성자입니다.

메인 함수에서 객체를 만들어주면 바로 호출됩니다. 모양은 멤버 메서드와 비슷하게 생겼지만 멤버 메서드는 public void처럼 접근 제어자와 반환할 자료형 등을 명시해준다는 특징이 있습니다.

또한 생성자는 ()안에 들어갈 가인수를 다르게 해서 오버로드 시킬 수 있습니다.

this 바인딩

public class MyClass {

  int number;
  String name;
  
  // 오버로드 가능
  MyClass() {
    System.out.println("MyClass의 기본 생성자");
  }
  
  MyClass(int num) {
    System.out.println("MyClass(int num) 생성자");
  }
  
  // This 바인딩
  MyClass(int num, String na) {
    this.number = num;
    this.name = na;
    System.out.println("MyClass(int num, String na) 생성자");
  }
  MyClass getThis () {
    return this;
  }

}

여기에서 this가 가리키는 것은 무엇일까요? 바로 클래스 안에 있는 자료를 의미합니다. 여기에서는 MyClass안에 있는 멤버변수 즉 int number로 선언된 변수와 String name으로 선언된 변수를 의미합니다.

다시 말해서 this는 매개변수(int num, String na)가 멤버변수에 접근할 때 자기 객체의 heap 메모리 주소를 담아줍니다.

결론적으로 this가 붙어있는건 클래스의 멤버변수이고 그렇지 않은 것은 로컬변수나 parameter임을 알수가 있죠.

캡슐화(은닉)

우리가 객체지향 프로그래밍의 특징 중에 은닉이라는 것도 살펴보았는데(요즘은 트렌드가 아니라고 하지만), 가장 먼저 접근 제어자라는 개념을 정리했었습니다.

접근 제어자는 멤버(멤버변수, 멤버 메서드 등)에 대한 외부 접근을 제어하는 역할을 하며 차단, 읽기전용, 허용여부 결정 등의 역할을 수행합니다.

접근 지정자의미
public아무 곳에서나 접근할 수 있습니다.
private클래스 내부에서만 접근할 수 있습니다.
protected상속에 따른 보호

그러나 public 이외의 값들을 외부에서 조회하거나 수정하는 방법이 있었는데 바로 gettersetter를 사용하는 것이었죠!

setter 같은 경우에는 parameter로 값을 받아와서 지정해줘야 하기 때문에 parameter가 존재하지만 getter의 경우에는 단순히 값을 전달만 하면 되기 때문에 parameter가 없습니다.

DTO(Data Transfer Object)

DTO는 계층간 데이터의 교환을 위한 객체 즉 Java Beans를 말합니다.

로직을 가지지 않는 데이터 객체이며 getter나 setter메서드만 가지고 있는 클래스입니다. 주로 비동기 처리를 할 때 사용하지요.

DAO(Data Access Object)

데이터베이스의 데이터에 접근하기 위한 객체입니다. 이 객체로 직접 데이터베이스에 접근하여 데이터를 삭제, 조회, 수정할 수 있습니다.

데이터베이스에 접근을 하기 위한 logic이나 비즈니스 로직을 분리하기 위해서 사용합니다.

VO(Value Object)

VO는 불변 클래스, 즉 Read-Only 속성을 위한 객체입니다.

자바에서는 단순하게 값을 표현하기 위해 불변 클래스를 만들어서 사용하는데, 예를 들어서 보라색을 만든다고 하면 Color.PURPLE처럼 단순한 값이 이에 해당합니다.

값을 가져오기만 하면 되기 때문에 getter 메서드 기능만 존재합니다.


상속, Inheritance


기본 개념

상속은 Inheritance, 부모 클래스가 자식 클래스에게 물려주는 행위를 말합니다. 그래서 자식은 부모가 물려준 특성들을 자유롭게 활용할 수 있습니다.

예를 들어서 부모 클래스에 어떤 요소들을 자식 클래스가 상속받게 되면 부모클래스에 원래 있던 것들은 자식 클래스에서 자유롭게 사용할 수가 있습니다.

클래스의 상속

자 그러면 상속이 어떻게 일어나는지 형태를 보겠습니다.

우선 부모 클래스에 들어갈 내용들입니다.

public class Parent {

    private int number;
    protected String name;      // 외부접근은 불가하지만 상속받은 클래스는 접근 가능

    public void parentMethod () {
        System.out.println("ParentClass parentMethod()");
    }
}

자 부모 클래스에 들어있는건 멤버변수 2개(number, name)입니다. 이들은 각각 privateprotected라는 접근 제어자가 붙어있습니다.

앞서 살펴봤지만 private이라고 하는것은 해당 클래스 내에서만 마음대로 사용할 수 있고 외부 객체에서 조회 및 수정하기 위해서는 gettersetter를 사용해야 했죠.

그리고 이를 상속받는 자식 클래스를 만들어보겠습니다.

public class Child extends Parents {
  
  private double height;		// 자식 클래스만의 변수
  
  // 이 안에서는 우리가 부모 클래스로부터 받아온 것들은 자유롭게 사용할 수 있습니다.
  
  public void childMethod () {
  
    number = 1;
    name = "홍길동";
    
    System.out.println("childMethod()");
  
  }

}

이렇게 자식 클래스를 작성하면 에러가 발생합니다.

왜냐하면 부모 클래스에서 number에 대한 접근 제어자는 private이었기 때문에 gettersetter 메서드를 사용해 주어야 합니다.

그래서, 부모 클래스에 gettersetter를 지정해 주어야 하죠!

public class Parent {

    private int number;
    protected String name;      // 외부접근은 불가하지만 상속받은 객체는 접근 가능

    public void parentMethod () {
        System.out.println("ParentClass parentMethod()");
    }
    
    protected void setNumber(int number) {
        this.number = number;
    }
}

이렇게 protected 접근 제어자를 주어서 상속관계에서만 접근이 자유롭게 설정해주면 private으로 지정된 자료를 바꿔줄 수 있죠.

public class Child extends Parents {
  
  private double height;		// 자식 클래스만의 변수
  
  // 이 안에서는 우리가 부모 클래스로부터 받아온 것들은 자유롭게 사용할 수 있습니다.
  
  public void childMethod () {
  
    setNumber(1);		// 숫자가 1로 바뀝니다.
    name = "홍길동";
    
    System.out.println("childMethod()");
  
  }

}

자 이렇게 자식 클래스에서 내용을 수정한다고 해서 부모 클래스에 있는 자료까지 바뀌진 않습니다. 주의하세요!

부모 생성자 호출

모든 객체의 클래스는 생성자를 호출해야 객체가 만들어집니다. 부모객체도 마찬가지고요.

메인함수에서 자식 클래스를 불러올 때 자식 클래스만 불러오게 되는 것이 아니라 자식 클래스 내부에서 부모 클래스를 부른 뒤에 자식 클래스가 메인함수에 생성됩니다.

부모 클래스의 생성자가 자식 클래스의 생성자의 맨 첫번째로 호출이 되고 이를 통해 객체가 생성되는 것이죠.

쉽게 말해서 메인 함수에서 자식 클래스를 불러다 만드는데 부모의 허락이 필요해서 부모 생성자로 부모를 먼저 깨우고 그다음 부모 동행하에 자식 클래스가 만들어지는 것입니다.

// 부모 클래스

public class People {

    private String name;
    private String ssn;

    public People(String name, String ssn) {
      this.name = name;
      this.ssn = ssn;
    }

}

예를 들어서 부모 클래스에 멤버변수 두개와 생성자 한개가 있습니다. 이 생성자는 매개변수를 갖습니다.

그러면 자식클래스를 한번 보겠습니다.

public class Student extends People {

    private int studentNo;

    public Student(String name, ssn, int studentNo) {
      super(name, ssn);			// 부모 생성자를 호출
      this.studentNo = studentNo;
    }

}

자식 클래스에는 하나의 멤버변수가 있습니다. 생성자를 보면 세개의 파라미터를 받고 있는데 이 중에 두개가 부모객체에 있던 것이기 때문에 부모 객체의 생성자를 super() 메서드로 때려넣습니다.

그러면 부모 클래스의 생성자를 깨워서 자식클래스로 가져오고 이를 그대로 파라미터를 받아 사용할 수 있는 것이죠.

studentNo라는 변수는 자식클래스에만 있기 때문에 별도로 선언을 해주는 것입니다.

또 다른 예시를 보겠습니다.

// Parent Class

public class ParentClass {

    private int number;
    private String name;

    public ParentClass() {
        System.out.println("ParentClass ParentClass()");
    }

    public ParentClass(int number, String name) {
        this.number = number;
        this.name = name;
        System.out.println("ParentClass ParentClass()");
    }

}
// Child Class

public class ChildClass extends ParentClass {

    private double height;

    public ChildClass() {
        super(123, "hello");
        System.out.println("ChildClass ChildClass()");
    }

    public ChildClass(double height) {
        super();
        this.height = height;
    }

    public ChildClass(int number, String name, double height) {
        super(number, name);
        this.height = height;
    }

}

우선 자식 클래스를 보면 생성자가 세종류 있습니다. 자식객체의 첫번째 생성자는 super() 메서드로 부모 클래스의 두번째 생성자에 해당하는 것을 가져옵니다.

그리고 자식 클래스의 두번째 생성자는 super()메서드에 아무것도 넣어주지 않았기 때문에 부모 클래스의 맨 첫번째 생성자를 가져옵니다. 거기에 자식객체에만 있는 변수 height을 넣어준 것이죠.

마지막으로 자식 클래스의 세번째 생성자를 보면 세개의 변수가 paremeter로 들어가있고, super() 메서드에는 numbername이라고 하는 변수가 들어가 있는 것으로 보아, 부모 클래스의 두번째 생성자를 깨워서 height만 넣어준 형태로 보시면 되겠습니다.


오버 라이드, Over-Ride


앞전에 비슷한 단어로 오버로드라고 하는 개념에 대해 살펴봤었습니다. 오버로드는 매개변수만 다른 같은 이름의 메서드를 칭하는 말이었고, 여기에서 살펴볼 것은 어감만 약간 다른 오버 라이드입니다.

기본 개념

오버 라이드는 부모 클래스에서 상속받은 메서드를 고쳐서 기입, 사용하는 행위를 말합니다.

자 그러면 오버라이드는 왜 하는 걸까요?

  1. 상속 받은 함수(또는 메서드)를 고쳐 사용하기 위해
  2. class 관리를 위해

그리고 오버라이드에도 규칙이 존재합니다.

  1. 상속받은 클래스의 오버라이딩 할 멤버 메서드와 이름이 동일해야 함.
  2. 반환할 자료형이 같아야함.

참고적으로 오버라이딩 해준 메서드는 오버라이딩된게 우선 호출되고, 만약에 고치기 전의 메서드를 호출하고 싶으면 super() 메서드를 붙여주면 됩니다!

그리고 오버라이딩 하지 않은 메서드는 그냥 호출해도 사용할 수가 있습니다.

부모 클래스의 메서드 호출하기

부모 클래스를 아래와 같이 작성한다고 가정할 때,

public class ParentClass {

	// 오버라이딩할 멤버 메서드
    public void pMethod() {
        System.out.println("ParentClass pMethod()");
    }

	// 오버라이딩 하지 않을 멤버 메서드
    public void method() {
        System.out.println("OverRide가 되지 않을 멤버 메서드");
    }

}

두개의 경우를 주고 하나는 오버라이딩을 할것이고 다른 하나는 하지 않을 것입니다.

자 그러면 자식 클래스에서 오버라이딩을 해봅시다.

package cls;

public class ChildClass extends ParentClass{

    @Override
    public void pMethod() {
        System.out.println("ChildClass pMethod()");
    }

    public void func() {
        //pMethod();      // 이거는 기존의 ParentClass에서 오버라이딩 된 ChildClass에 있는 pMethod()가 호출되는것
        super.pMethod();    // 이거는 ParentClass로 올라가서 거기있는 pMethod()가 호출됨.
    }

}

자식 클래스의 첫번째 멤버 변수를 보시면 @Override라는 주석이 달려있는데 이것이 바로 오버라이딩한 멤버변수입니다. @Override는 작성하지 않아도 됩니다.

오버라이딩한 멤버 메서드를 출력하기 위해 메인함수에서 ChildClass를 생성해주고 이를 호출하면 오버라이딩된 메서드의 출력구문이 나옵니다.

그리고 그 아래에 있는 멤버 메서드 func를 보시면 내부에 두개의 메서드가 있고, 만약 첫번째 메서드처럼 pMethod()만 사용하게 되면 부모 클래스에서 오버라이딩된 자식 클래스 내부의 pMethod()가 호출되는 것입니다.

반대로 super.pMethod()를 호출하게 되면 이는 기존의 부모 클래스로 거슬러 올라가서 부모 클래스에 있는 pMethod()를 호출해주게 됩니다.

그래서 아래와 같이 메인 함수에서 객체를 생성해주고 각각 호출해보면 결과를 알 수가 있죠.

import cls.ChildClass;

public class Main {

    public static void main(String[] args) {

        ChildClass cc = new ChildClass();
        cc.pMethod();	// "ChildClass pMethod()"
        cc.func();		// "ParentClass pMethod()"

    }
}

다형성, Polymorphism


기본 개념

다형성이란 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말합니다. 즉, 우리가 사용하는 자바에서는 하나의 타입에 여러가지 객체를 넣어서 다양한 기능을 이용하게 해줍니다.

이를 위해서 우리는 강제 자료형 변환(Casting)의 개념을 상기시킬 필요가 있습니다.

또한 배열의 특성에 대해서도 다시 짚어보아야 할 것입니다.

타입 변환 Promotion

타입변환이라는 것은 데이터 타입을 다른 데이터 타입으로 바꿔주는 것이죠. 이것은 우리가 자료형에 대해 다룰 때 자동 자료형 변환과 강제 자료형 변환에 대해 다룰 때 살펴봤던 개념입니다.

객체에서도 똑같은 개념을 적용할 수 있는데, 이러한 타입 변환은 상속된 객체끼리만 가능합니다.

그래서 다음과 같은 관계가 성립할 수 있습니다.

ParentClass variable = ChildClass

이렇게 부모 클래스 그리고 자식 클래스가 상속 관계에 있다면 자식은 부모의 일부로 볼 수 있기 때문에 이러한 관계가 성립할 수 있고, 이를 자동 타입변환이라고 합니다.

자식은 부모의 특징, 기능 등을 상속받기 때문에 부모와 동일하게 취급하는 것이죠.

가령 다음과 같은 부모 클래스와 자식 클래스가 있다고 가정해 보겠습니다.

// Parent class

public class ParentClass {

    public void Method() {
        System.out.println("ParentClass Method()");
    }

    public void func() {
        System.out.println("ParentClass func()");
    }
}
// Child class

public class ChildOneClass extends ParentClass{

    @Override
    public void Method() {  // OverRide
        System.out.println("ChildOneClass Method()");
    }

    public void function() {
        System.out.println("ChildOneClass function()");
    }

}

이 때, 부모 클래스의 Method라는 멤버 메서드가 자식 클래스에서 Overriding 되어 재정의 되었습니다.

그래서 메인 함수에서 다음과 같이 작성하고 run해보면 주석과 같은 결과를 얻을 수가 있습니다.

import cls.ChildOneClass;
import cls.ParentClass;

public class Main {

    public static void main(String[] args) {

        ParentClass pc = new ChildOneClass();
        pc.Method();	// "ChildOneClass Method()"


        pc.func();
        pc.function();   // 이렇게는 작동하지 않습니다.
    }
}

자 우선 자식 클래스를 부모 클래스의 인스턴스로 만들어줍니다. 이제부터 원래 부모의 것이었던 것은 그대로 부모것이고, 원래 자식 것이었던 것은 부모의 것과 별개가 됩니다.

그래서 pc.Method()를 출력했을 때 오버라이딩된 자식 클래스의 메서드가 호출이 되는 것을 알 수가 있죠. 이것은 오버라이딩 즉 메서드 자체가 자식 클래스에서 재정의 되었기 때문에 메인에서 호출하면 재정의된 메서드로 호출이 됩니다.

다시 말해서 부모 클래스에 들어가면서 덮어 씌이는 느낌이라는거죠.

그리고 pc.func()를 호출하면 원래 부모 클래스의 것이었기 때문에 잘 나옵니다.

그러나 원래 자식 클래스에만 있던 pc.function() 메서드는 호출할 수 없습니다. 왜냐하면 부모 클래스를 바탕으로 자식 클래스를 만들어서 넣어준 것이기 때문에 부모 클래스에 없던 것은 사용할 수가 없습니다.

그래서 캐스터 변환 즉, 강제적으로 "이거는 원래 자식 클래스에 있던거야!"라고 알려줌으로써 사용할 수 있게 됩니다.

import cls.ChildOneClass;
import cls.ParentClass;

public class Main {

    public static void main(String[] args) {

        ParentClass pc = new ChildOneClass();
        
        ChildClass cc = (ChildClass) pc;
        
        cc.function();	// "ChildOneClass function()"
    }
}

배열에 응용과 instanceof


예를 들어서 두개의 자식 클래스가 있습니다. 이 클래스의 객체를 10개 만들어준다고 가정했을 때 아주 단순한 방법으로는 두 개의 클래스를 각각 10개씩해서 배열을 잡아주고 조건식으로 조건 만들어주고 각각의 배열에 무작위로 숫자가 들어가게 해주어야 합니다.

그런데 이렇게하면 너무나 수고스러운 일을 해야하기 때문에 별로 권장하지 않는 방법이죠.

public class Main {
  
  public static void main (String[] args) {
    ChildOneClass[] arrOne = new ChildOneClass[10];     // 각각의 최대개수를 잡는다
    ChildTwoClass[] arrTwo = new ChildTwoClass[10];
    
    String name = "";
    
    if (name.equals("one")) {
      ....
    } else if (name.euqals("two")) {
      ....
    } else {
      ....
    }
    
    arrOne[0] = new ChildOneClass();
    arrTwo[0] = new ChildTwoClass();
    arrTwo[1] = new ChildTwoClass();
    arrOne[1] = new ChildOneClass();
    arrOne[2] = new ChildOneClass();
    arrOne[3] = new ChildOneClass();
    arrTwo[2] = new ChildTwoClass();
    arrOne[4] = new ChildOneClass();
    
    //		:
    //		:
    //		:
    //		:
        

  }
}

이렇게 처리해주면 식이 굉장히 길어지게 됩니다..

이보다 훨씬 나은 방법이 있다면, 자식 클래스를 부모 클래스의 인스턴스로 넣어서 해결하는 방법입니다.

그보다 너 나은 방법은 부모 클래스 자체를 배열화 해줍니다.

그 전에 부모 클래스와 자식 클래스를 생성해주겠습니다.

// Parent Class

public class ParentClass {
    public void Method() {
        System.out.println("ParentClass Method()");
    }
}

// ChildOneClass

public class ChildOneClass extends ParentClass {

    @Override
    public void Method() {  // override
        System.out.println("ParentClass Method()");
    }

    public void func() {
        System.out.println("ChildOneClass func()");
    }

}
// ChildTwoClass

public class ChildTwoClass extends ParentClass{

    @Override
    public void Method() {  // override
        System.out.println("ParentClass Method()");
    }

    public void proc() {
        System.out.println("ChildTwoClass proc()");
    }

}

체크해야할 사항으로 각각의 자식 클래스에는 부모클래스의 Method()가 오버라이딩 되어 있습니다.

public class Main {
  
  public static void main (String[] args) {
  
    ParentClass[] arrParent = new ParentClass[10];
    
  }
}

그 다음으로 부모요소의 각 인덱스에 자식 요소를 넣어주는 겁니다. 그러면 부모요소 하나만 만들어줘도 자식요소가 몇개이든 하나의 배열에 다 때려넣으면 되기 때문에 배열 관리 차원에서 매우 쉬워지겠죠.

public class Main {
  
  public static void main (String[] args) {
  
    ParentClass[] arrParent = new ParentClass[10];
    
    arrParent[0] = new ChildOneClass();
    arrParent[1] = new ChildTwoClass();
    arrParent[2] = new ChildTwoClass();
    arrParent[3] = new ChildOneClass();
    arrParent[4] = new ChildOneClass();
    arrParent[5] = new ChildOneClass();
    arrParent[6] = new ChildTwoClass();
    arrParent[7] = new ChildOneClass();
    arrParent[8] = new ChildTwoClass();
    arrParent[9] = new ChildOneClass();
    
  }
}

굳이 10개짜리 인덱스의 배열을 두개나 만들 필요 없이 10개만 만들어서 그 안에 자식 클래스를 넣어주는겁니다.

그 다음으로 결과를 보기 위해서 각 배열들의 내용을 체크해줄겁니다.

public class Main {
  
  public static void main (String[] args) {
  
    ParentClass[] arrParent = new ParentClass[10];
    
    arrParent[0] = new ChildOneClass();
    arrParent[1] = new ChildTwoClass();
    arrParent[2] = new ChildTwoClass();
    arrParent[3] = new ChildOneClass();
    arrParent[4] = new ChildOneClass();
    arrParent[5] = new ChildOneClass();
    arrParent[6] = new ChildTwoClass();
    arrParent[7] = new ChildOneClass();
    arrParent[8] = new ChildTwoClass();
    arrParent[9] = new ChildOneClass();
   
   
    for (int i = 0; i < arrParent.length; i++) {
      arrParent[i].Method();

      if(arrParent[i] instanceof ChildOneClass) {
        ChildOneClass one = (ChildOneClass) arrParent[i];
        one.func();
      } else if (arrParent[i] instanceof ChildTwoClass) {
        ChildTwoClass two = (ChildTwoClass) arrParent[i];
        two.proc();
      }
    }
    
  }
}

자 우선 부모 클래스로 만든 배열의 길이만큼 반복작업을 해줍니다.

반복을 돌릴 내용은 배열의 각 인덱스에 부여된 자식객체의 Method()를 호출해주는 것이네요.

그 안의 조건문을 보시면 배열의 i번째 인덱스가 ChildOneClass의 객체 타입이라면 one이라는 이름을 가진 ChildOneClass는 부모 객체로 만든 배열의 i번째 요소가 되고, 이것의 func() 메서드를 호출한다라는 작업과 그 이하는 반대의 경우입니다.

여기에서 instanceof는 부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인하기 위한 도구이고 이를 통해 어떤 객체가 어떤 클래스의 인스턴스인지 확인할 수 있습니다. 이것은 boolean 값으로 반환해줍니다.

그래서 반복문 조건에 대해 다시 말하자면 배열의 i번째 인덱스가 ChildOneClass를 참조하여 만들어진 것이라면 (부모 객체 배열로 만들어져 자동타입 변환된) 자식 객체로 강제로 타입변환시켜서 이 안에 있는 메서드를 호출하라는 얘기가 됩니다.

else if의 경우는 ChildTwoClass를 참조하여 만들어진 것이라면 자식 클래스 타입으로 강제 변환시켜서 이 안에 있는 메서드를 호출하라는 얘기가 됩니다.

즉 부모 클래스로 만들어진 객체 배열이기 때문에 자식 고유의 내용을 참조해서 쓸수가 없으므로 강제 타입 변환을 한번 거친 것입니다.


정리


상속이라는 것은 부모객체 그리고 자식객체의 성립 조건이며, 자식 객체는 부모 객체로부터 받아온 것을 가져다 쓸 수 있고, 커스텀할 수도 있습니다.

private 접근 제어자로 선언된 변수의 경우 우리는 gettersetter를 통해 조회, 수정할 수 있었고, protected의 경우에는 상속관계라면 자유롭게 가져다 쓸 수 있지만 다른 외부에서는 접근할 수 없었죠.

상속을 함에 있어서 자식 클래스가 부모 클래스 생성자의 매개변수에 접근하기 위해서는 super() 메서드를 사용했었고, 이는 부모 클래스의 메서드를 압축시켜 때려박아넣은 형태 정도로 이해하면 쉬울 것입니다.

오버라이딩을 하게 되면 자식 클래스에 입력된 메서드는 재정의 되는 형식입니다.
자식 클래스에 오버라이딩을 한 메서드를 사용하기 위해서는 그냥 그 메서드 이름을 입력해주면 되고, 초기에 부모 클래스에 있던 메서드를 사용하기 위해서는 super.method()로 사용을 했었죠.

그리고 타입변환은 상속된 객체끼리만 가능했습니다. 자식 클래스는 부모 클래스의 기능을 상속받기 때문에 부모와 동일하게 취급해줄 수 있습니다.

그래서 다음과 같은 관계가 성립했었죠

ParentClass variable = ChildClass

그래서 만약 자식 클래스에 오버라이딩 된 메서드가 있다면 부모 클래스를 인스턴스로 자식 클래스를 메인에서 만들었을 때 오버라이딩된 메서드로 재정의 되어 해당 메서드를 출력해보면 자식 클래스에 있던 메서드를 보여줍니다.

그리고 부모 클래스를 인스턴스로 자식 클래스를 만들었을 때 원래 자식 클래스에만 있던 변수나 메서드를 끄집어오려면 반드시 강제 타입 변환이 필요합니다.

마지막으로 instanceof는 객체의 타입을 확인해줍니다. 그래서 A라는 객체가 B라는 타입으로 이루어져 있는지 확인할 때 사용하고 이는 boolean 값을 반환합니다.

A instanceof B

오늘 내용 정리는 여기까지고 연습문제는 별도 포스팅에서 다루겠습니다.

profile
tried ? drinkCoffee : keepGoing;

0개의 댓글