자바 - 상속, super, override, dispatch, abstract, object

불순분자들·2022년 9월 24일

자바

목록 보기
7/16

상속( inheritance )

상속이라는 개념은 캡슐화, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나이다.

상속을 이용하면 기존에 정의되어 있는 클래스의 모든 필드와 메소드를 물려받아 새로운 클래스를 생성할 수 있다.

  • 기존에 정의되어 있는 클래스를 부모 클래스, 상속을 통해 새롭게 작성되는 클래스를 자식 클래스라고 한다.
  • 상속을 선언할 때는 extends 키워드를 사용한다.

상속의 필요성

  1. 기존 클래스의 변수와 코드를 재사용할 수 있어 개발 시간이 단축된다.
  2. 먼저 작성된 검증된 프로그램을 재사용하는 이유로 신뢰성 있는 프로그램을 개발할 수 있다.
  3. 클래스간 계층적 분류 및 관리가 가능하여 유지보수가 용이하다.

상속의 특징

  • 자바는 클래스의 다중상속을 지원하지 않는다.
  • 자바에서는 상속의 횟수에 제한을 두지 않는다.
  • 자바의 계층구조 최상위는 Object이다. 그러므로 toString(), equals()를 바로 사용할 수 있는 것 !

클래스의 상속

class 자식클래스명 extends 부모클래스명 { ... }


위 말을 그림으로 나타내어보면 알 수 있듯 자식 클래스는 부모클래스를 싸고 있는 모습을 볼 수 있다.
따라서 부모 클래스에 새로운 필드를 추가하면, 자식클래스에서도 자동으로 해당 필드가 추가되는 것처럼 동작한다.
*! 필드와 메소드만 상속되며, 생성자와 초기화 블록은 상속되지 않는다 !

서브 클래스 객체

클래스를 객체로 만들어서 사용할수도 있다. 아래 코드는 그 예를 보여준다.

public class MotionBook {
    public void read() {
        System.out.println("읽기");
    }
    public void close() {
        System.out.println("닫기");
    }
}
public class Book {
    public static void main(String[] args) {
        MotionBook m1 = new MotionBook();

        m1.read();
        m1.close();
    }
}

super 와 super()

super 키워드

super 키워드는 부모 클래스로부터 상속받은 필드나 메소드를 자식 클래스에서 참조하는 데 사용하는 참조 "변수"이다.
결과적으로 우리는 부모클래스의 멤버와 자식 클래스의 멤버 이름이 같을 경우 super 키워드를 사용하여 구별할 수 있다.

class Parent { int a = 10; }

class Child extends Parent {
    int a = 20;
    void display() {
        System.out.println(a);
        System.out.println(this.a);
        System.out.println(super.a);
    }
}

public class Inheritance {
    public static void main(String[] args) {
        Child ch = new Child();
        ch.display();
    }
}

위 코드를 참조하면 알 수 있듯이, 첫 a와 두 번째 a는 20을 출력하였고, 마지막 a는 10을 출력하였음을 참고해보면 super는 부모클래스의 a에 접근했다는 것을 알 수 있다.

super()

super 와 super()는 다르다.
this() 메서드가 같은 클래스의 다른 생성자를 호출할 때 사용된다면, super() 메서드는 부모 클래스의 생성자를 호출할 때 사용하게 된다.
만약, 부모클래스의 생성자가 없다면 에러가 발생한다.

class Parent {
    int a;
    Parent() {
        a = 10;
    }
    Parent(int n) {
        a = n;
    }
}

class Child extends Parent {
    int b;
    Child() {
        super(40);
        b = 20;
    }
    void display() {
        System.out.println(a);
        System.out.println(b);
    }
}

public class Inheritance {
    public static void main(String[] args) {
        Child ch = new Child();
        ch.display();
    }
}


super()를 이용하여 Parent 클래스의 두 번째 생성자에 접근해 초기화되어 a는 40으로 출력되는 모습을 볼 수 있을 것이다.

Method Overlode & Override

Overlode

메서드 오버로딩이란 같은 이름의 메서드를 중복하여 정의하는 것을 의미한다.
자바에서는 원래 한 클래스 내에 같은 이름의 메서드를 둘 이상 가질 수 없다.

메서드 오버로딩은 객체 지향 프로그래밍의 특징 중 하나인 다형성을 구현하는 방법 중 하나로 println() 메서드는 그 예로 적합하다.

메서드 오버로딩의 조건은 두 가지가 있는데

  • 메서드의 이름이 같아야한다.
  • 메서드의 매개변수의 개수 또는 타입이 달라야한다.

아래는 오버로딩의 예제를 보여준다.

class Parent {
    int a;
    Parent() {
        a = 10;
    }
    Parent(int n) {
        a = n;
    }
}

class Child extends Parent {
    int b;
    Child() {
        super(40);
        b = 20;
    }
    void display() {
        System.out.println(a);
        System.out.println(b);
    }
}

public class Inheritance {
    public static void main(String[] args) {
        Child ch = new Child();
        ch.display();
    }
}

override

오버로딩은 새로운 메서드를 정의하는 것이다.
오버라이딩은 상속받은 기존의 메서드를 "재정의"하는 것이다.

class Parent {
    void display() {
        System.out.println("부모 클래스의 display 메서드임.");
    }
}

class Child extends Parent {
    // display() 오버라이딩
    @Override
    void display() {
        super.display();
    }

    void display(String str) {
        System.out.println(str); // display() 오버로딩
    }
}

public class ExOver {
    public static void main(String[] args) {
        Child ch = new Child();
        ch.display();
        ch.display("오버로딩된 display()임");
    }
}

Method Dispatch

메서드 디스패치란 어떤 메서드를 호출할지 결정하여 실제로 실행시키는 과정이다.
자바는 런타임 시 객체를 생성하고, 컴파일 시에는 생성할 객체 타입에 대한 정보만 보유한다.
이 과정은 static( 정적 ), dynamic( 동적 )으로 분류되게 된다.

static Dispatch

컴파일 할 때, 컴파일러가 main과 같은 어떤 메소드를 호출할지 명확하게 알고 있는 경우이다.

class SubClass {
    void print() {
        System.out.println("print 호출됨");
    }
}

public class StaticDynamic {
    public static void main(String[] args) {
        new SubClass().print();
    }
}

Dynamic Dispatch

컴파일 할 때, 컴파일러가 어떤 메소드를 호출할지 모르는 경우로 이 경우에는 런타임 시점에서 컴파일러가 알게 된다.

class SubClass {
    void print() {
        System.out.println("print 호출됨");
    }
}

class SubClass2 extends SubClass {
    void print() {
        System.out.println("SubClass2의 print 호출됨");
    }
}

class SubClass3 extends SubClass {
    void print() {
        System.out.println("SubClass3의 print 호출됨");
    }
}

public class StaticDynamic {
    public static void main(String[] args) {
        SubClass s1 = new SubClass();
        SubClass s2 = new SubClass2();
        SubClass s3 = new SubClass3();

        s1.print();
        s2.print();
        s3.print();
    }
}

abstract

추상 클래스( abstract class )

자바에서는 하나 이상의 추상 메소드를 포함하는 클래스를 추상 클래스라고 한다.

  • 추상 클래스는 객체 지향 프로그래밍에서 중요한 다형성의 특징을 가지는 메소드의 집합을 정의할 수 있게 해준다.

추상 메소드( abstract method )

추상 메소드는 자식 클래스에서 반드시 오바라이딩해야만 사용할 수 있는 메소드를 의미한다.
추상 메소드를 선언하여 사용하는 목적은 추상 메소드가 포함된 클래스를 상속받는 자식 클래스가 반드시 추상 메소드를 구현하도록 하기 위함이다.

  • 추상메소드는 선언부만이 존재하며, 구현부는 작성되어 있지 않는다.
  • 작성되어 있지 않은 구현부를 오버라이딩하여 사용하는 것이다.
abstract class Animal { abstract void cry(); }

class Dog extends Animal {
    @Override
    void cry() {
        System.out.println("왕왕");
    }
}

class Cat extends Animal {
    @Override
    void cry() {
        System.out.println("애옹");
    }
}

public class Polymorphism {
    public static void main(String[] args) {
        Dog d = new Dog();
        Cat c = new Cat();
        
        d.cry();
        c.cry();
    }
}

final

final 변수는 한 번 값을 할당하게 되면 수정할 수 없다는 특징을 가지고 있다.
한 번 값을 할당한다는 것은 한번의 초기화가 가능하다는 것이다.
결과적으로 값을 할당하고 수정을 원치 않는 경우에 사용하면된다.
class, method, variable 등 접근제한자를 사용할 수 있는 경우라면 모든 곳에 사용할 수 있다.

Object

java.lang 패키지 중 가장 많이 사용되는 클래스는 Object 클래스이다.
Object 클래스는 자바의 모든 클래스 최상위에 위치하는 조상 클래스이다.
따라서 모든 클래스는 Object 클래스의 모든 메소드를 바로 사용할 수 있다.

  • toString() : 인스턴스에 대한 정보를 문자열로 반환함.
  • equals() : 인스턴스를 매개변수로 받는 참조 변수와 비교하여 그 결과를 boolean형으로 반환.
  • clone() : 해당 인스턴스를 복제하여 새로운 인스턴스를 생성해 반환함.
  • hashcode() : 해당 객체의 해시 코드 값을 반환함.

상속 VS Composition

상속의 사용 이유는 아래와 같았다.

  • 코드를 재사용함으로 중복을 줄인다.
  • 변화에 대한 유연성 및 확장성 증가.
  • 개발 시간이 단축됨.
    하지만, 상속의 장점들만 사용하였을 경우에만 위 이점들을 가져올 수 있었다. 그 의미는 상속을 잘못사용하면 변화에도 유연하지 못한데 오류까지 많이 나는 프로그램이 된다.

상속은 캡슐화를 깨뜨린다는 단점을 가지고 있다.
그로 인해 우리는 조합( composition )을 개발할 때 주로 사용하고 있다.

조합 : 기존 클래스가 새로운 클래스의 구성요소로 쓰인다. 새로운 클래스를 만들로 private 필드로 기존 클래스의 인스턴스를 참조하는 것이다.

public class Composition2 {
    private Composition composition;
    private Menu menu;
}

위 코드와 같은 식으로 조합을 사용하게 되는데, 우리는 알게 모르게 다들 조합을 쓰고 있었을 것이다. 나 또한 extends 잘 사용하지 않았다.
인터페이스를 상속하는 implement 또한 잘 사용하지 않았었다.

자바의 다중상속

자바에서는 다중상속을 허용하지 않고 있다. 그 이유는 다이아몬드 문제라고 한다.

위와 같은 상속관계 속에서 Child는 Father와 Mother를 상속받아 멤버 필드와 메소드를 받을 것이다.
그런데 Father과 Mother에 같은 메소드가 있고, Father와 Mother는 Person에서 member()을 서로 다르게 오버라이딩 했다고 하면 Child.member()는 어떤식으로 출력이 될지 의문이다.
우리도 의문인데 기계는 당연히 오류를 낸다.

그래서 해결방법으로 나온게 인터페이스를 통한 다중상속이다.

interface Person {
    String contry ="한국";
    void member();
}

interface Father extends Person {
    @Override
    void member();
}

interface Mother extends Person {
    @Override
    void member();
}

public class Child implements Father, Mother {
    @Override
    public void member() {
        System.out.println("======");
    }
}

위와 같은 식으로 다중 상속을 가능하게 할 수 있다.

profile
장래희망 : 침대 위 녹아든 치즈

0개의 댓글