본문 바로가기

스터디/JAVA

[JAVA] 상속

[상속의 개념]

상속이란 부모클래스가 자식클래스에게 필드와 메서드를 물려주는 것을 말한다.

우선 코드로 먼저 확인해 보자.

[상속을 사용하지 않았을 때]

public class Child {
    public static void main(String[] args) {
        int result = valueAdd(10, 20); // Parent 클래스 내부에서 valueAdd 메서드 호출
        System.out.println("a + b = " + result);
    }

    public static int valueAdd(int a, int b) {
        int result = a + b;
        return result;
    }
}


public class Kid {
    public static void main(String[] args) {
        int result = valueAdd(50, 70); // Child 클래스 내부에서 valueAdd 메서드 호출
        System.out.println("a + b = " + result);
    }

    public static int valueAdd(int a, int b) {
        int result = a + b;
        return result;
    }
}

 

[상속을 사용했을 때]

public class Parent {	// 먼저 생성된 부모 클래스
	public static int valueAdd(int a, int b) {
		int result = a + b;
		return result;
	}
}

// 부모 클래스를 상속받는 자식 클래스
public class Child extends Parent {	

	public static void main(String[] args) {
		int result = valueAdd(50, 70);
		System.out.println("a + b = " + result);
	}
}

 

해당 코드들을 얼핏 봐도 코드의 의미를 몰라도 상속을 사용했을 때와 사용하지 않았을 때 코드 양이 다르다는 것을 알 수 있다.

상속이란 이미 잘 만들어진 클래스를 재사용해서 새로운 클래스를 만들어주기 때문에 중복되는 코드를 줄여주면서 개발 시간을 단축시킨다.

상속을 받는 방법은 상속을 받을 클래스에 부모클래스명을 extends라는 명령어를 사용하여 상속을 받아오면 된다.

이때 주의사항으로는 다른 언어들과는 달리 자바는 다중 상속을 허용하지 않는다.

즉, 여러 개의 부모 클래스를 상속할 수 없다. 따라서 extends 뒤에는 단 하나의 부모 클래스만 가능하다는 것이다.


[부모 생성자 호출]

현실에서 자식이 부모보다 먼저 태어날 수 없듯이 자바에서도 자식 객체를 생성하기 전에 부모 객체가 먼저 생성돼야 한다. 

위의 코드에서는 자식 클래스인 Child클래스에서 연산이 이루어지기 때문에 자식 클래스에서만 로직이 실행되는 거 같지만

실제로는 부모 클래스 -> 자식 클래스 순으로 실행된다.

public class GrandFather {
	
    int handsomeScore = 10;
	void handsome() {
		System.out.println("잘 생겼다~");
	}
}
public class Father extends GrandFather {

	long money = 1000_000_000_000_000L;
	void wealth() {
		System.out.println("돈을 많이 벌었다.");
	}
}
public class Child extends Father {
	float day = 365 + 1.0f / 4;
	
	void play() {
		System.out.println("인생이 즐겁다!!");
	}
}
public class ExtendsMain {

	public static void main(String[] args) {
		Child child = new Child();
		child.handsome();
		child.wealth();
		child.play();
		
		System.out.println("잘생김 점수 : " + child.handsomeScore);
		System.out.println("돈 : " + child.money);
		System.out.println("즐거운 날 : " + child.day);
	}
}

실행결과

해당 자식 간의 관계를 보여주는 클래스들이 존재할 때 결괏값을 보면 알 수 있듯이 Child클래스가 우선적으로 인스턴스화가 되는 것 같지만 실제로는 제일 높은 GrandFather 클래스부터 실행되는 것을 확인할 수 있다. 즉 상속 관계에서 자식 클래스를 인스턴스화를 하면 부모 클래스의 객체가 먼저 인스턴스화가 되고, 그다음으로 자식 클래스가 인스턴스화되는 것을 알 수 있다.


[super 키워드]

상속을 다룰 때 super 키워드는 필수이다.

모든 객체는 생성자를 호출해야만 생성된다. 부모 객체도 예외는 아니다.

부모 객체의 생성자는 자식 생성자에 숨어있게 된다. 부모 생성자는 자식 생성자의 맨 첫 줄에 숨겨져 있는 super()라는 메서드를 통해 호출된다.

public class GrandFather {
    private int handsomeScore;    
    
    GrandFather() {
        System.out.println("GrandFather 생성자");
    }
    
    GrandFather(int score) {
        this.handsomeScore = score;
        System.out.println("GrandFather 생성자");
    }
}

public class Father extends GrandFather {
    private long money;
    
    Father() {    
        System.out.println("Father 생성자");
    }
    
    Father(int score, long money) {
        super(score); // GrandFather의 매개변수 생성자 호출
        this.money = money;
        System.out.println("Father 매개변수 생성자");
    }
}

public class Child extends Father {
    private float day;
    
    Child() {
        System.out.println("Child 생성자"); 
    }
    
    Child(int score, long money, float day) {
        super(score, money); // 부모 클래스의 매개변수 생성자 호출
        this.day = day;
        System.out.println("Child 매개변수 생성자");
    }
}

 

결과를 보면 알 수 있듯이 조상 클래스 -> 자식 클래스 순서로 실행되는 것을 알 수 있고 자식 클래스에서 부모 클래스에게 생성자의 값을 넘겨주기 위해서 super()라는 키워드를 사용하는 것을 알 수 있다. 

super()는 컴파일 과정에서 자동 추가되는데. 이것은 부모의 기본 생성자를 호출한다.

우리는 그동안 상속 클래스를 정의할 때 super()를 호출하지 않아도 자동적으로 부모 생성자가 호출되어서 사용되었다.

원래는 자식 클래스의 생성자에서 꼭 첫 줄에 super()를 호출해줘야 했지만 개발의 편의성을 위해 생략이 되어도 자동으로 인식되었다.

즉, 부모 클래스의 필드 값들을 초기화하기 위해서는 자식 클래스의 생성자에서 부모 클래스를 직접 호출해줘야 한다.

만약 부모 클래스에 기본 생성자가 없다면 자식 생성자 선언에서 컴파일 에러가 발생하게 된다.

super() 안에는 매개변수의 타입개수가 일치해야 한다.


[메서드 오버라이딩]

메서드 오버라이딩은 상속된 메서드를 자식 클래스에서 재정의를 하여 사용하는 것을 말한다.

또한 메서드 오버로딩과 같이 객체 지향 프로그래밍에서의 다형성을 대표하는 핵심적인 기능이다.

메서드가 오버라이딩이 되었다면 해당 부모 메서드가 아닌 자식 메서드가 우선적으로 사용된다.

 

[메서드 오버라이딩 주의사항]

  1. 부모 메서드의 선언부(리턴 타입, 메서드 이름, 매개변수)와 동일해야 한다.
  2. 접근 제한을 더 강하게 할 수 없다. (public -> private로 변경 불가)
  3. 새로운 예외를 throws 할 수 없다.(throws는 라이브러리와 모듈에서 알아보자)
// 아빠 클래스
class Dad {
    void speak() {
        System.out.println("아빠는 자는중입니다.");
    }
}

// 자식 클래스 - Dad 클래스를 상속받음
class Child extends Dad {
    // speak 메서드를 오버라이딩하여 자식의 말소리로 재정의
    @Override
    void speak() {
        System.out.println("아들은 놀고 있습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Dad 객체 생성
        Dad dad = new Dad();
        // Dad의 speak 메서드 호출
        dad.speak(); //아빠는 자는중입니다.
        
        // Child 객체 생성
        Child child = new Child();
        // Child의 speak 메서드 호출
        child.speak(); // 아들은 놀고 있습니다.
    }
}

 

위의 코드를 보면 알 수 있듯이 메서드 오버라이딩을 하게 된다면 부모 클래스에서 선언되어 있던 메서드를 재정의하여

사용하는 것을 말한다.

이때 메서드 오버로딩과 메서드 오버라이딩이 같은 것이라고 생각할 수 있다. 

  메서드 오버로딩 메서드 오버라이딩
메소드 이름 동일 동일
매개변수 개수 다름 동일
매개변수 타입 다름  동일

정리하자면

  • 메서드 오버로딩 :  기존에 없던 새로운 메서드를 생성
  • 메서드 오버라이딩 : 기존에 존재하던 메서드를 새로 덮어 씌어서 사용

[핵심 요약]

  • 상속을 사용함으로써 코드의 중복성을 줄여주고 코드의 효율성을 극대화할 수 있다.
  • 자식클래스가 부모 클래스를 호출할 때 부모 클래스의 인스턴스화가 먼저 이루어진다.
  • 자식클래스에서 부모클래스로 생성자의 값을 넘겨줄 때는 super()라는 메서드를 필수적으로 사용해줘야 한다.
  • 메서드 오버라이딩을 통해 코드의 유연성을 높여줄 수 있고 각 클래스에서의 유지보수성이 향상된다.

'스터디 > JAVA' 카테고리의 다른 글

[JAVA] 컬렉션  (1) 2024.02.18
[JAVA] Thread  (0) 2024.02.15
[JAVA] 배열  (1) 2024.02.07
[JAVA] 클래스  (1) 2024.02.03
[JAVA] 변수와 타입  (0) 2024.01.29