Language/Java

[Java] 상속 : 재사용+확장 (생성자,오버로딩, 오버라이딩, IS-A관계 등)

KAispread 2022. 5. 17. 23:43
728x90
반응형

상속이란 친족간의 어떠한 권리나 의무를 계승, 물려주는 것을 말한다.
자바에서도 상속을 통해 상위 클래스의 속성과 기능들을 하위 클래스에게 물려줄 수 있다.
이를 통해 프로그램의 생산성을 크게 증가 시킬 수 있다.


상속 방법

클래스 상속 방법
class 자식클래스 extends 부모클래스 {

}
//ex
class Doctor extends Job {

}


자식 클래스를 선언할때 extends 예약어를 이용하여 부모 클래스를 뒤에 붙여주면
부모 클래스에 있는 모든 메소드와 변수를 자식 클래스에 존재하는 것 처럼 사용할 수 있다.

예제 코드를 통해 살펴보자

class Job {
  public int type;
	
  public void setType(int type) {
    this.type = type;
    System.out.println(type);
  }
}

class Developer extends Job{
  public void greetingDev() {
      System.out.println("hello developer");
  }
}

public class Sample {

  public static void main(String[] args) {
    Developer dev = new Developer();
    dev.setType(1);   
    // 1 출력
    dev.greetingDev();
    // hello developer 출력
  }
}

Developer(자식 클래스)에 extends 예약어를 사용하여 Job(부모클래스)에 있는 메소드, 변수를 물려받았다.
실제로 Developer에 선언하지 않은 메소드와 변수를 Developer의 객체에서 사용할 수 있다는 것을 알 수 있다.
물론, 자식 클래스에만 선언되어 있는 메소드도 사용할 수 있다.
이것처럼 직업에 해당하는 Developer 클래스 뿐만 아니라, Teacher, Doctor등 '직업'이라는 범주 안에서 공통적으로 필요한 변수와 메소드가 있을 때, 간편하게 Job이라는 클래스를 상속받는 것만으로도 그 기능들을 물려받을 수 있다.
그렇다면 자식클래스 부모클래스에 선언된 변수와 메소드를 전부 다 사용할 수 있을까?

상속의 제약

자바에서는 '캡슐화'라는 아주 중요한 개념이 있다.
이를 정보 은닉이라고도 하는데, 이를 통해 다른 클래스에서 변수나 메소드의 접근을 제한할 수 있다.
이러한 개념을 실현하는 것이 public, protected, private과 같은 '접근 제어자'이다.
자바에는 4가지의 접근 제어자가 존재한다.

  • public - 누구나 접근 가능
  • protected - 상속받거나 같은 패키지에 있을때만 접근 가능
  • package-private - 같은 패키지 내에 있을 때만 접근 가능
  • private - 선언된 클래스에서만 접근 가능

따라서 자식 클래스에서는 부모 클래스에서 publicprotected로 선언된 변수와 메소드만 접근 가능하다.

또한, 자바에서는 다중 상속을 지원하지 않는다.
즉, extends 예약어 뒤에 여러개의 클래스를 쓸 수 없고 하나의 클래스만 존재해야한다.
여러 클래스를 상속받을때 메소드 충돌이 일어날 수 있기 때문이다.
(이와 같은 문제를 다이아몬드 문제라고 한다.)

 

생성자

생성자 호출 순서

자식 객체가 생성될 때, 호출되는 생성자는 다음과 같다.
부모 객체 생성자 -> 자식 객체 생성자

class Job{
   Job(){
      System.out.println("Job Parent constructor");
   }
}

class Developer extends Job {
   Developer(){
      System.out.println("Dev constructor");
   }
}

public class Sample {
   public static void main(String[] args) {
      Developer dev = new Developer();
   }
}

실행 결과

자식 객체를 생성하였더니, 자식 클래스의 생성자가 호출되기 전에 부모의 생성자가 먼저 호출되는 모습이다.
클래스에 생성자를 따로 선언하지 않으면 객체가 생성될때 컴파일러가 자동으로 기본 생성자(매개변수가 없는 생성자 "Job(){ }")를 생성한다.
자식 객체를 생성할때도 마찬가지이다. 자식 클래스의 생성자에 부모 클래스의 생성자를 명시적으로 지정하지 않으면, 부모 클래스의 기본 생성자를 컴파일러가 실행한다.
그래서 Developer 클래스의 생성자에 부모 클래스 생성자를 지정하지 않았음에도, 자동으로 "Job(){ }" 코드가 실행된 것이다.

 

생성자 호출 시 유의점

만약, 부모 클래스에 생성자가 선언되어있지 않다면?
이 경우는 문제가 발생하지 않는다. 클래스에 생성자가 선언되어있지 않으면 컴파일러가 알아서 클래스에 기본 생성자를 추가하기 때문이다.

만약, 부모 클래스에 기본 생성자가 없고 매개 변수가 있는 생성자만 선언되어있다면?
이 경우에 문제가 발생한다.

class Job{
   Job(String name){
      System.out.println("Job name is" + name);
   }
}

class Developer extends Job {
   Developer(){
      System.out.println("Dev constructor");
   }
}

자식 생성자에 부모 생성자가 명시되어있지 않으면, 컴파일러가 자동으로 기본 생성자(Job() { })을 호출한다고 했다.
그리고 클래스에 생성자가 선언되어있다면 기본 생성자(Job() { })를 자동으로 만들어주지 않으므로, 컴파일 오류가 발생한다.
이럴때 두가지 방법이 있다.

  • 부모 클래스에 기본 생성자(Job() { })를 만들어준다.
  • 자식 생성자에 super 예약어로 부모 클래스의 생성자를 명시해준다.

 

부모 클래스를 호출하는 super

자식에서 부모 클래스의 객체를 super 예약어를 통해 접근할 수 있다.

class Job{
   Job(String name){
      System.out.println("Job name is" + name);
   }
}

class Developer extends Job {
   Developer(){
      super("Developer");
      System.out.println("Dev constructor");
   }
}


위 코드에서와 같이 super("Developer") 코드를 추가해, 명시적으로 String 자료형을 매개변수로 받는 부모 생성자를 호출할 수 있다. 자식보다 부모 생성자가 먼저 호출되어야 하므로 생성자 블록 최상단에 super 예약어를 사용해야 한다.
super 예약어는 생성자 뿐만 아니라, 부모 클래스의 메소드와 변수에도 접근할 수 있다.
super."변수명" / super."메소드()" 로 별도의 객체 생성 없이 부모 클래스의 기능을 사용할 수 있다.

 

오버라이딩 (Overriding)

상속은 부모 클래스의 변수와 메소드를 물려 받는다.
부모에게서 물려받은 메소드를 자식 클래스에서 재 정의하는 것을 오버라이딩(Overriding)이라고 한다.

class Job{
   public void methodOverriding() {
      System.out.println("Parent Method");
   }
}

class Developer extends Job {
   @Override
   public void methodOverriding() {
      System.out.println("Child Method");
   }
}

public class Sample {
   public static void main(String[] args) {
      Developer dev = new Developer();
      dev.methodOverriding();
   }
}

실행 결과

Job이라는 부모 메소드에서 메소드를 물려 받고 자식에서 메소드 내용을 바꿔주었다.
그 결과, 부모에서 선언된 메소드의 코드가 실행되는 것이 아니라 자식에서 재정의한 코드가 실행된다.
메소드를 Overriding 할 때 주의해야할 점은 다음과 같다.

  • 부모 클래스의 메소드와 동일한 리턴타입, 함수명, 매개변수를 갖는 자식 클래스의 메소드가 존재할 때 Overriding이 발생한다.
  • Overriding 하려는 메소드의 리턴타입, 함수명, 매개변수가 같아야 한다.
  • 접근 제어자는 접근 제한이 확대되는 경우만 가능하다.

접근 제어자의 접근 권한은 public > protected > package-private > private 이다.
예를 들어 부모 클래스에서 메소드가 protected로 선언되어 있다면 자식 클래스에서 Override 될 때, protected나 public으로 메소드를 선언하는 것은 가능하다는 것이다.
추가적으로 부모 클래스의 메소드를 그대로 실행하되, 몇가지 코드만 더 추가하고 싶다면 위에서 이야기한 super 예약어를 사용하면 된다.

class Job{
   public void methodOverriding() {
      System.out.println("Parent Method");
   }
}

class Developer extends Job {
   @Override
   public void methodOverriding() {
      super.methodOverriding();
      System.out.println("Child Method");
   }
}

public class Sample {
   public static void main(String[] args) {
      Developer dev = new Developer();
      dev.methodOverriding();
   }
}

실행 결과

 

오버로딩 (Overloading)

클래스 내의 같은 메소드를 매개 변수를 다르게 하여 중복해서 메소드를 선언하는 것을 오버로딩(Overloading)이라고 한다.

class Job{
   public void printParameter() {
      System.out.println("Overloading Parameter :: ");
   }
   public void printParameter(int a) {
      System.out.println("Overloading Parameter :: " + a);
   }
   public void printParameter(int a, String name) {
      System.out.println("Overloading Parameter :: " + a + " / " + name);
   }
   public void printParameter(String name, int a) {
      System.out.println("Overloading Parameter :: " + name + " / " + a);
   }
}

public class Sample {
   public static void main(String[] args) {
      Job sample = new Job();
      sample.printParameter();
      sample.printParameter(3);
      sample.printParameter(10, "kiw");
      sample.printParameter("Mu", 5);
   }
}

매개변수에 따라 다른 메소드가 실행됨

이와 같이, 같은 메소드를 매개변수만 다르게 하여 여러개 선언할 수 있다.
이 때 매개 변수의 순서가 달라도 다른 메소드로 취급하며, 메소드 호출 시 자동으로 매개 변수에 맞는 메소드가 호출된다.

 

IS-A관계

앞서 예시로 사용한 코드를 다시 보자.

class Job {
  public int type;
	
  public void setType(int type) {
    this.type = type;
    System.out.println(type);
  }
}

class Developer extends Job{
  public void greetingDev() {
  }
}


Devloper 클래스는 Job 이라는 클래스를 상속했다. 즉, Job이라는 큰 범주 안에 Developer이라는 명칭이 포함되어 있는 것이다. 따라서, "Developer은 Job이다"라고 할 수 있다.
이와 같은 관계를 IS-A 관계라고 한다. Developer is a Job 이기 때문이다.
자바에서는 이러한 IS-A 관계로 인해 자식클래스의 객체를 생성할 때 부모클래스의 자료형으로 선언할 수도 있고, 자식클래스 자신의 자료형으로도 선언할 수 있다.
이 두 클래스의 관계를 UML의 클래스 다이어그램으로 살펴보면 다음과 같다.

UML

예시 코드를 통해 자세히 알아보자.

class Job {
   public int type;

   public void setType(int type) {
      this.type = type;
      System.out.println(type);
   }
}

class Teacher extends Job {

}

class Developer extends Job {
   public void greetingDev() {
      System.out.println("hello developer");
   }
}

public class Sample {

   public static void main(String[] args) {
      Job devParent = new Developer();
      Developer dev = new Developer();
      
      devParent.setType(3);
      dev.setType(3);
      devParent.greetingDev(); //컴파일 에러 발생
      dev.greetingDev();
   }
}

Sample 클래스에서 Developer 객체를 부모클래스(Job)의 자료형으로 생성하고 (devParent) 자기 자신의 클래스(Developer)의 자료형으로도 생성했다. (dev)
부모클래스를 자료형으로 한 자식의 객체는 부모클래스에서 상속받은 메소드(자식에서 Overriding된 메소드 포함)와
변수만 사용할 수 있다.
따라서, 자식클래스에서만 정의되어 있는 greetingDev()메소드를 사용하려 할 때 에러가 발생하는 것이다.
자식 자료형으로 자식 객체를 생성했을 경우에는 자식 클래스의 모든 변수와 메소드를 사용할 수 있다.

이러한 특징은 배열을 생성할 때 유용하게 사용될 수 있다.

class Job {
   public int type;

   public void setType(int type) {
      this.type = type;
      System.out.println(type);
   }
}

class Teacher extends Job {

}

class Developer extends Job {
}

public class Sample {
   public static void main(String[] args) {
      Job[] jobArray = new Job[3];
      jobArray[0] = new Developer();
      jobArray[1] = new Teacher();
      jobArray[2] = new Job();
   }
}

다음과 같이 부모 클래스의 자료형으로 배열을 선언하면,
해당 배열 안에 자신의 객체 뿐만 아니라, 자식 객체도 넣을 수 있다.

 

결론

상속을 이용하면 부모 클래스의 기능을 자식 클래스가 물려받을 수 있다.
이후, Overriding으로 부모 클래스의 메소드를 덮어 쓸수도 있고, super 라는 예약어를 통해서 부모 클래스의 변수와 메소드에 쉽게 접근할 수 있다.
이와 같은 상속의 여러 기능들을 통해 개발 시간을 단축하고, 코드가 간결해지며, 확장성이 용이해지는 등 다양한 장점이 있다.

여기서 유의 해야할 점은 자식 객체를 생성할 때 부모 클래스의 생성자가 호출된다는 점과 클래스의 다중 상속이 불가능하다는 점. (인터페이스에서는 가능하다) 부모의 자료형으로 자식 객체를 생성할 수 있다는 점. public, protected로 선언되지 않은 변수와 메소드는 상속받을 수 없다는 점이다.

자바에서 상속은 매우 중요한 개념이기에 한 번에 이해가 되지 않는다면 여러번 반복하여 개념을 잡는 것이 중요하다.

 

 

참고
ㆍ"자바의 신 vol.1", 이상민 저자
https://scshim.tistory.com/210

 

[Java] 자바 상속의 특징 - extends, super, 오버라이딩, instanceof, 추상 클래스와 메소드, final

상속이란? 상속은 부모가 자식에게 물려주는 행위다. 객체 지향 프로그램에서도 부모(상위) 클래스의 멤버를 자식(하위) 클래스에 물려주어 자식 클래스가 갖고 있는 것처럼 사용할 수 있다. 상

scshim.tistory.com

https://wikidocs.net/280

728x90
반응형