본문 바로가기

Development/JAVA

(Java) Chapter8. 생성자와 상속

1. 생성자

  - 개념 : 어떤 동작을 실행하기 전에 준비하는 초기화의 기능

class Calculator4 {
    int left, right;

    // 생성자(constructor) : 클래스가 실행되면 자동으로 생성자를 최우선으로 실행, 없으면 자동으로 만듦
    public Calculator4(int left, int right) {    
        this.left = left;
        this.right = right;
    }

    public void sum() {
        System.out.println(this.left + this.right);
    }

    public void avg() {                                     // 평균 메소드 정의
        System.out.println((this.left + this.right) / 2);
    }
}
public class _12_Constructor {
    public static void main(String[] args) {
        // new 뒤의 Calculator4는 생성자를 의미
        Calculator4 c1 = new Calculator4(10, 20);
        c1.sum();   // 30
        c1.avg();   // 15

        Calculator4 c2 = new Calculator4(20, 40);
        c2.sum();   // 60
        c2.avg();   // 30
    }
}

  ※ 생성자는 직접 명시하지 않으면 자동으로 생성하며 직접 명시하면 자동으로 최우선으로 작동함

  ※ 반드시 해야할 일을 놓치지 않도록 할 경우 사용

 

2. 상속 (Inheritance)

  1) 개념

  - 재활용성 : 객체의 필드(변수)와 메소드를 다른 객체가 물려받을 수 있는 기능

  - 예제

class Calculator5 {                                          // Calculator 클래스 정의, 설계
    int left, right;

    public void setOprends(int left, int right) {
        this.left = left;                                   // this. 객체 속성값 입력 시 사용
        this.right = right;
    }

    public void sum() {                                     // 덧셈 메소드 정의, static 이 없음 > 객체 선언이 필요함
        System.out.println(this.left + this.right);         // static 은 각 정의들이 같은 메모리를 공유하여 객체 선언 필요 없음
    }

    public void avg() {                                     // 평균 메소드 정의
        System.out.println((this.left + this.right) / 2);
    }
}

// 새로운 하위 클래스를 정의하고 기존의 클래스를 확장(또는 상속)한다
class SubstractionableCalculator extends Calculator5 {
    public void substract() {
        System.out.println(this.left - this.right);
    }
}

public class _01_Inheritance1 {               // public class 는 파일명과 같아야 함. 즉, 대표 클래스
    public static void main(String[] args) {
        // 인스턴스 생성은 하위 클래스를 통해서 하지만 부모 클래스 기능은 그대로 상속함
        SubstractionableCalculator c1 = new SubstractionableCalculator();
        c1.setOprends(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

  - Calculator5 : 부모 클래스(상위 클래스)

  - SubstractionableCalculator : 자식 클래스(하위 클래스)

  - extends를 통해 선언

  - 객체에 메소드를 추가하기 어려운 경우

    객체를 직접 만들지 않아서 원소스를 업데이트하면 추가한 메소드가 사라지기도 함

    객체가 다양한 곳에서 활용되는데 추가할 기능이 필요가 없을 경우 등

  2) 다중 상속

  - 기존 클래스를 확장 후 그 하위 클래스를 다시 클래스로 확장

class MultiplicationableCalculator extends Calculator5 {
    public void multiplication () {
        System.out.println(this.left * this.right);
    }
}
class DivisionableCalculator extends MultiplicationableCalculator {
    public void division () {
        System.out.println(this.left / this.right);
    }
}

public class _02_MultipleInheritance {
    public static void main(String[] args) {
        DivisionableCalculator c1 = new DivisionableCalculator();
        c1.setOprends(10, 20);
        c1.sum();
        c1.avg();
        c1.multiplication();
        c1.division();
    }
}

 

3. 기본 생성자 (Default Constructor)

  1) 기본 생성자의 성질

  - 매개변수가 있는 생성자를 선언하고 메인영역에서 매개변수가 없는 인스턴스를 생성하면 에러가 발생함

  - 클래스를 선언하면 자동으로 매개변수가 없는 '기본 생성자'를 생성하고, 인스턴스는 그 '기본 생성자'를 호출하기 때문

  - 매개변수가 있는 생성자를 선언하면 기본 생성자를 생성하지 않음

  - 해결책 : 매개변수가 없는 생성자를 먼저 선언 후 매개변수 생성자를 만들면 해결 가능

 

  2) 기본 생성자 문제 해결

  - 문제상황 : 하위 클래스의 생성자도 없고, 상위 클래스에서도 매개변수를 받는 생성자로 '기본 생성자' 생성을 막는 경우
class Calculator6 {
    int left, right;
//    public Calculator6() {}                         // 부모클래스 기본생성자를 먼저 설정해줘야함
    public Calculator6(int left, int right) {
        this.left = left;
        this.right = right;
    }
    public void setOprends(int left, int right) {
        this.left = left;
        this.right = right;
    }

    public void sum() {
        System.out.println(this.left + this.right);
    }

    public void avg() {                                     // 평균 메소드 정의
        System.out.println((this.left + this.right) / 2);
    }
}

// 새로운 하위 클래스를 정의
class SubstractionableCalculator2 extends Calculator6 {     // 상위 클래스의 기본생성자가 없으면 에러 발생
//    public SubstractionableCalculator2(int left, int right) { // 상위와 하위 클래스모두 생성자가 같다면?
//        // this.left = left;                                  // 상위 클래스 생성자와 같음, 너무 길다면?
//        // this. right = right;
//        super(left, right);                                   // 위와 같을 때 부모클래스 생성자를 참조함
//        // 하위클래스의 초기화 코드는 super 에 선행할 수 없음
//    }
    public void substract() {
        System.out.println(this.left - this.right);
    }
}

public class _03_ConstructorAndInheritance {
    public static void main(String[] args) {
        SubstractionableCalculator2 c1 = new SubstractionableCalculator2();
        c1.setOprends(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

  - 해결방법 : 상위 클래스의 '기본 생성자'를 설정

class Calculator6 {
    int left, right;
    public Calculator6() {}                         // 부모클래스 기본생성자를 먼저 설정해줘야함
    public Calculator6(int left, int right) {
        this.left = left;
        this.right = right;
    }
    public void setOprends(int left, int right) {
        this.left = left;
        this.right = right;
    }

    public void sum() {
        System.out.println(this.left + this.right);
    }

    public void avg() {                                     // 평균 메소드 정의
        System.out.println((this.left + this.right) / 2);
    }
}

// 새로운 하위 클래스를 정의
class SubstractionableCalculator2 extends Calculator6 {     // 상위 클래스의 기본생성자가 없으면 에러 발생
//    public SubstractionableCalculator2(int left, int right) { // 상위와 하위 클래스모두 생성자가 같다면?
//        // this.left = left;                                  // 상위 클래스 생성자와 같음, 너무 길다면?
//        // this. right = right;
//        super(left, right);                                   // 위와 같을 때 부모클래스 생성자를 참조함
//        // 하위클래스의 초기화 코드는 super 에 선행할 수 없음
//    }
    public void substract() {
        System.out.println(this.left - this.right);
    }
}

  - 상위 클래스와 하위 클래스에서 정의된 생성자가 같은 경우 -> super 사용

class SubstractionableCalculator2 extends Calculator6 {     // 상위 클래스의 기본생성자가 없으면 에러 발생
    public SubstractionableCalculator2(int left, int right) { // 상위와 하위 클래스모두 생성자가 같다면?
        // this.left = left;                                  // 상위 클래스 생성자와 같음, 너무 길다면?
        // this. right = right;
        super(left, right);                                   // 위와 같을 때 부모클래스 생성자를 참조함
        // 하위클래스의 초기화 코드는 super 에 선행할 수 없음
    }
    public void substract() {
        System.out.println(this.left - this.right);
    }
}

  ※ 문제 이유 : 상위 클래스의 매개변수가 있는 생성자를 선언하고 매개변수가 없는 하위 클래스 인스턴스를 생성하면 문제 발생

    > 하위 클래스 생성자 호출 이전에 상위 클래스의 '기본 생성자'를 호출하도록 되어있기 때문

    > 그러나 매개변수가 정의된 생성자가 있으면 '기본 생성자' 자동 생성을 막음

  ※ 하위 클래스의 생성자가 상위 클래스의 '기본 생성자'를 호출하면 인스턴스의 매개변수가 없어도 실행 가능한가? 

    > 불가능, 이미 하위 클래스는 매개변수를 받겠다는 생성자를 선언 했기 때문

    > 해결 방법 : 하위 클래스의 '기본 생성자'를 선언하면 가능

class SubstractionableCalculator2 extends Calculator6 {     // 상위 클래스의 기본생성자가 없으면 에러 발생
    public SubstractionableCalculator2() {}                 // 기본 생성자 선언
    public SubstractionableCalculator2(int left, int right) { // 상위와 하위 클래스모두 생성자가 같다면?
        // this.left = left;                                  // 상위 클래스 생성자와 같음, 너무 길다면?
        // this. right = right;
        super(left, right);                                   // 위와 같을 때 부모클래스 생성자를 참조함
        // 하위클래스의 초기화 코드는 super 에 선행할 수 없음
    }
    public void substract() {
        System.out.println(this.left - this.right);
    }
}

public class _03_ConstructorAndInheritance {
    public static void main(String[] args) {
        SubstractionableCalculator2 c1 = new SubstractionableCalculator2();
        c1.setOprends(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

4. Overrriding

  1) 개념

  - 상위 클래스가 가진 메소드를 하위 클래스에서 재정의하여 기능을 변경하는 방법의 명칭

  - 상위 클래스의 메소드는 무시되고 하위 클래스의 재정의 된 메소드가 우선순위로 동작

class Calculator7 {
    int left, right;

    public void setOprends(int left, int right) {
        this.left = left;
        this.right = right;
    }

    public void sum() {              // 오버라이딩 할 상위 클래스 메소드
        System.out.println(this.left + this.right);
    }

    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}

// 새로운 하위 클래스를 정의
class SubstractionableCalculator3 extends Calculator7 {
    public void sum() {                         // 오버라이딩 된 메소드
        System.out.println("실행 결과는 " + (this.left + this.right) + "입니다.");
    }

    public void substract() {
        System.out.println(this.left - this.right);
    }
}

public class _04_Overriding {
    public static void main(String[] args) {
        SubstractionableCalculator3 c1 = new SubstractionableCalculator3();
        c1.setOprends(10, 20);
        c1.sum();                   // 실행 결과는 30입니다.
        c1.avg();                   // 15
        c1.substract();             // -10
    }
}

  2) 오버라이딩의 제약사항

  - 메소드의 이름 

  - 메소드의 매개변수의 숫자와 데이터 타입 그리고 순서

  - 메소드의 리턴 타입

  - 예시(이름은 같으나 데이터타입(int), 리턴타입 불일치)

public void avg() {             // 상위 클래스 메소드
    System.out.println((this.left + this.right) / 2);
}
// 새로운 하위 클래스를 정의
class SubstractionableCalculator3 extends Calculator7 {
    public void sum() { 
        System.out.println("실행 결과는 " + (this.left + this.right) + "입니다.");
    }

    public int avg() {                          // 오류
        return(this.left + this.right) / 2;
    }
    public void substract() {
        System.out.println(this.left - this.right);
    }
}

 

  ※ 메소드의 형태를 정의하는 사항들을 메소드의 '서명(Signature)'이라고 하며 상기 예제는 서명이 달라서 발생한 문제

  ※ 해결 방법은 상의 클래스의 코드를 변경하여 문제를 우회

  ※ 상위 클래스의 메소드가 이미 가지고 있는 로직을 하위에서 다시한번 정의하지 않기 위해서는 'super' 키워드 사용

public int avg() {                          // 타입과 리턴타입 오류, 상위 클래스 변경 필요
    return(this.left + this.right) / 2;
    // return super.avg();                   // super 사용하면 메소드를 똑같이 다시 쓸 필요 없음, 여기에 다른 기능을 더 추가도 가능
}

 

5. Overloading

  - 오버로딩을 통한 3개의 값 계산기 예제

    기존

int left, right;

public void setOprands(int left, int right) {
    this.left = left;
    this.right = right;
}

    수정

int left, right;
int third = 0;

public void setOprands(int left, int right) {
    // System.out.println("setOprands(int left, int right)");     // 중복 피하기 1
    this.left = left;
    this.right = right;
}
public void setOprands(int left, int right, int third) {
    // this.setOprands(left, right);                             // 중복 피하기 2
    // System.out.println("setOprands(int left, int right, int third)"); // 중복 피하기3
    this.left = left;
    this.right = right;
    this.third = third;
}

public void sum() {              // 오버라이딩 할 상위 클래스 메소드
    System.out.println(this.left + this.right + this.third);
}

 

  2) 제약사항

  - 리턴타입이 같아야함

  - 매개변수의 형태는 상관 없음

  - 매개변수의 이름은 문제 없음

public class _05_Overloading {
    void A() {
        System.out.println("void A()");
    }
    void A(int arg1) {                      // 문제가 없는 이유 : 매개변수
        System.out.println("void A(int arg1)");
    }
    void A(String arg1) {
        System.out.println("void A(String arg1)");
    }
    // int A() {                            // 첫 메소드와 문제 발생, 이미 정의됨, 리턴값으로 반환해야 함
    //     System.out.println("void A()");
    // }

    public static void main(String[] args) {
        _05_Overloading ol = new _05_Overloading();
        ol.A();
        ol.A(1);
        ol.A("coding everybody");
    }
}

  3) Overriding vs. Overloading

  - Overriding : 상위 클래스의 서명이 같은 경우 새롭게 정의(동작방법 변경)

  - Overloading : 메소드의 이름이 같지만 상위 클래스와 다른 매개변수를 가지는 메소드를 정의

public class Overloding2 extends _05_Overloading {
    void A(String arg1, String arg2) {              // Overloading
        System.out.println("sub class : void A()");
    }

    void A() {                                      // Overriding
        System.out.println("sub class : void A()");
    }
}

 

'Development > JAVA' 카테고리의 다른 글

(Java) Chapter13. Object  (0) 2023.01.23
(Java) Chapter12. 예외  (0) 2023.01.23
(Java) Chapter11. 접근 제어자  (0) 2023.01.17
(Java) Chapter10. API  (0) 2023.01.10
(Java) Chapter7. 객체 지향 프로그래밍  (0) 2023.01.08