클래스 - 3

Bummy·2023년 8월 10일
0

JAVA

목록 보기
7/11
post-thumbnail

7. 상속

7.1 상속 개념

  • 상속이란 부모가 자식에게 물려주는 행위를 의미
  • 부모 클래스의 멤버를 자식 클래스에게 물려주는 것을 의미
  • 이미 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 코드의 중복을 줄여준다.
  • 상속을 해도 부모 클래스의 모든 필드와 메소드들을 물려받는 것은 아니고 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다.
  • 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면 default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외된다.

7.2 클래스 상속

  • 상속은 다음과 같이 extends를 붙여 기술하여 클래스를 선언한다.
class 자식클래스 extends 부모클래스{
	//필드
	//생성자
	//메소드
}
  • 자바는 다중 상속을 허용하지 않기에 extends 뒤에는 단 하나의 부모 클래스만 와야한다.
class 자식클래스 extends 부모클래스1, 부모클래스2(X){
}

7.3 부모 생성자 호출

  • 자바에서 자식 객체를 호출하면 부모 객체가 먼저 생성되고 자식 객체가 그 다음에 생성된다.
  • 자식 객체만 생성하는 것으로 보이지만, 사실은 내부적으로 부모 객체가 먼저 생성되고 그 다음 자식 객체가 생성된다.
💡 Q. 모든 객체는 클래스의 생성자를 호출해야만 생성되는데 부모 객체는 어디서 호출할까? A. 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다.
public 자식클래스() {
	super();
}
  • 다음과 같이 super()를 통해 부모의 기본 생성자를 호출한다.
  • 소스코드에서 부모 생성자가 선언되지 않아도 컴파일러는 자동으로 부모의 기본 생성자를 만들어준다.
자식클래스(매개변수선언, ...){
	super(매개값, ...);
}
  • 다음과 같이 직접 자식 생성자를 선언하고 명시적으로 부모 생성자를 호출할 수도 있다.
  • 부모 클래스에 기본 생성자가 없고 매개 변수가 있는 생성자만 있다면 자식 생성자에서 반드시 부모 생성자 호출을 위해 super(매개값, …)를 명시적으로 호출해주어야한다.
//부모 클래스
public class People{
	public String name;
	public String ssn;

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

//자식 클래스
public class Student extends People{
	public int studentNo;

	public Student(String name, String ssn, int studentNo){
		super(name, ssn); //부모 생성자 호출
		this.studentNo = studentNo;
	}
}
  • super(name, ssn); 부분을 주석처리 한다면 “Implicit super constructor People() is undefined. Must explicitly another construcotr”라는 컴파일 오류가 발생하게 된다. 부모의 기본 생성자가 없으니 다른 생성자를 명시적으로 호출하라는 의미이다.

7.4 메소드 재정의

  • 부모 클래스를 자식 클래스가 사용하기에 적합하지 않다면 이 경우 상속된 일부 메소드는 자식 클래스에서 다시 수정해서 사용해야 한다.
  • 자바는 이런 경우를 위해 메소드 오버라이딩(Overriding) 기능을 제공한다.

7.4.1 메소드 재정의(@Override)

  • 메소드 오버라이딩은 상속된 메소드의 내용이 자식 클래스에 맞지 않을 경우, 자식 클래스에서 동일한 메소드를 재정의하는 것을 말한다.
  • 메소드가 오버라이딩되었다면 부모 객체의 메소드는 숨겨지기 때문에, 자식 객체에서 메소드를 호출하면 오버라이딩된 자식 메소드가 호출된다.
💡 메소드 오버라이딩 할 때 주의할 점 - 부모의 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 리스트)를 가져야 한다. - 접근 제한을 더 강하게 오버라이딩할 수 없다. - 새로운 예외(Exception)를 throws할 수 없다.
public class Calculator{
	double areaCircle(double r){
		System.out.println("Calculator 객체의 areaCircle() 실행");
		return 3.14159 * r * r;
}

public class extends Calculator{
	@Override
	double areaCircle(double r){
		System.out.println("Computer 객체의 areaCircle() 실행");
		return Math.PI * r * r;
}
  • @Override를 생략할 수도 있지만 이것을 붙여주게 되면 해당 메소드가 정확히 오버라이딩된 것인지 컴파일러가 체크하기 때문에 개발자의 실수를 줄여줄 수 있다.
  • 만약 오버라이딩 하려는 메소드의 이름이 잘못되었다면 컴파일 에러가 발생하게 된다.

오버로딩과 오버라이딩
1. 오버로딩

  • 오버로딩은 같은 이름의 메소드를 여러개 만들고 매개변수의 타입이나 수를 다르게 해서 같은 이름으로 메소드를 여러개 사용할 수 있는 것
  • 오버로딩을 하더라도 기존에 있던 메소드는 없어지지 않고 모두 호출해서 사용할 수 있음
  1. 오버라이딩
  • 부모 클래스를 상속한 자식 클래스에서 입맛에 맛게 메소드를 바꿔서 사용하는 것
  • 메소드를 오버라이딩 해서 사용하게 되면 기존에 있는(부모 클래스의 기존 메소드는) 숨겨지게 되어 super가 아니라면 호출할 수가 없음

7.4.2 부모 메소드 호출(super)

  • 자식 클래스 내부에서 오버라이딩 된 부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면 명시적으로 super 키워드를 붙여서 부모 메소드를 호출할 수 있다.
  • super는 부모 객체를 참조하고 있기 때문에 부모 메소드에 직접 접근할 수 있다.

7.5 final 클래스와 final 메소드

  • final 키워드는 해당 선언이 최종 상태이고, 결코 수정될 수 없음을 뜻한다.

7.5.1 상속할 수 없는 final 클래스

  • final 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.

7.5.2 오버라이딩할 수 없는 final 메소드

  • 메소드를 선언할 때 final을 붙이게 되면 이 메소드는 최종적인 메소드이므로 오버라이딩 할 수 없는 메소드가 된다.
  • 즉, 부모 클래스를 상속해서 자식 클래스를 선언할 때 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의할 수 없다는 것이다.

7.6 protected 접근 제한자

  • 접근 제한자는 public, protected, default, private와 같이 네 가지 종류가 있다.
  • 이 중에서 protected는 상속과 관련이 있다.
  • protected는 같은 패키지에서는 default와 같이 접근 제한이 없지만 다른 패키지에서는 자식 클래스만 접근을 허용한다.

7.7 타입 변한과 다형성

  • 다형성 : 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질
  • 객체 프로그래밍에서는 각 객체가 하나의 부품처럼 서로 연결되어 있는데 이 객체들은 다른 객체로 교체될 수 있어야 한다.
  • 예를 들어 자동차 클래스를 처음 설계할 때 타이어 클래스는 기존 타이어 객체와 사용 방법은 동일하지만, 실행 결과는 더 좋게 나올 수 있도록 교체가 가능해야한다.
  • 이것을 프로그램으로 구현하기 위해 상속, 오버라이딩, 타입 변환을 이용한다.

7.7.1 자동 타입 변환(Promotion)

  • 자동 타입 변환은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.
  • 타입 변환 : 자식 타입을 부모 타입으로 변환할 수 있는 것을 의미 (다형성)
부모클래스 변수 = 자식 클래스 타입;
//자동 타입 변환

→ 다음과 같이 자동 타입 변환의 개념은 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다는 것을 의미

→ 바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다. 만약 상속 관계가 아니라면 타입 변환이 될 수 없음

7.7.2 필드의 다형성

class Car {
	//필드
	Tire frontLeftTire = new Tire();
	Tire frontRightTire = new Trie();
	Tire backLeftTire = new Tire();
	Tire backRigthTire = new Tire();

	//메소드
	void run() { ... }
}
  • 4개의 Tire 필드에 각각 하나씩 Tire 객체가 들어가게 된다.
Car myCar = new Car();
myCar.frontRightTire = new HankookTire();
myCar.backLeftTire = new KumhoTire();
myCar.run();
  • frontRightTire와 backLeftTire는 원래 Tire 객체가 저장되어야 하지만, Tire의 자식 객체가 저장되어도 문제가 없다. 왜냐하면 자식 타입은 부모 타입으로 자동 타입 변환이 되기 때문

7.7.3 하나의 배열로 객체 관리

class Car {
	Tire[] tires = {
		new Tire("앞왼쪽", 6),
		new Tire("앞오른쪽", 2),
		new Tire("뒤왼쪽", 3),
		new Tire("뒤오른쪽", 4)
	};
}
  • 위에서 각각 저장하던 필드를 다음과 같이 배열로 만들어 관리를 할 수 있다.

7.7.4 매개 변수의 다형성

  • 자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.
  • 메소드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만 매개값을 다향화하기 위해 매개 변수에 자식 타입 객체를 지정할 수도 있다.

7.7.5 강제 타입 변환(Casting)

  • 강제 타입 변환(Casting)은 부모 타입을 자식 타입으로 변환하는 것을 말한다.
  • 자식 타입이 부모 타입으로 자동 변환한 후, 다시 자식 타입으로 변환할 때 사용할 수 있다.

7.7.6 객체 타입 확인(instanceof)

  • 강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 때문에 부모 타입의 변수가 부모 객체를 참조할 경우 자식 타입으로 변환할 수 없다.
  • 부모 변후가 참조하는 객체가 부모 객체인지 자식 객체인지 확인하는 방법은 instanceof 연산자를 사용하여 확인할 수 있다.
boolean result = 좌항(객체) instanceof 우항(타입)

7.8 추상 클래스

7.8.1 추상 클래스의 개념

  • 추상은 실체 간에 공통되는 특성을 추출한 것을 의미
  • 객체를 직접 생성할 수 있는 클래스를 실체 클래스, 실체 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스
  • 추상 클래스와 실체 클래스는 상속의 관계를 가지고 있다. 추상 클래스가 부모이고 실체 클래스가 자식으로 구현된다.
Animal animal = new Animal(); (x)
  • 추상 클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만들었기 때문에 객체를 직접 생성해서 사용할 수 없다. new 연산자를 사용해서 인스턴스를 생성시키지 못한다.
class Ant extends Animal {...} (o)
  • 추상 클래스는 extends 뒤에만 올 수 있는 클래스이다.

7.8.2 추상 클래스의 용도

  1. 실체 클래스들의 공통된 필드와 메소드의 이름을 통일할 목적
    1. 공통 되는 필드와 메소드들을 추상 클래스들을 통해 통일시킬 수 있다.
  2. 실체 클래스를 작성할 때 시간을 절약
    1. 공통적인 필드와 메소드는 추상 클래스에 모두 선언해 두고, 실체 클래스마다 다른 점만 선언하게 되면 실체 클래스를 작성하는데 시간을 절약할 수 있다.
💡 공통적인 내용들을 추려내어 추상 클래스로 설계 규격을 만들고, 해당 추상 클래스를 상속해서 구체적인 실체 클래스를 만든다.

7.8.3 추상 클래스 선언

  • 추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙여야한다.
  • abstract를 붙이게 되면 new 연산자를 이용해서 객체를 만들지 못하고 상속을 통해 자식 클래스만 만들 수 있다.
public abstract class 클래스명 {
	//필드
	//생성자
	//메소드
}
  • new 연산자로 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때 super(…)를 호출해서 추상 클래스 객체를 호출하므로 추상 클래스도 생성자가 반드시 있어야 한다.

7.8.4 추상 메소드와 오버라이딩

  • 추상 클래스는 실체 클래스가 공통적으로 가져야할 필드와 메소드를 정의해 놓은 추상적인 클래스이므로 실체 클래스의 멤버를 통일화하는데 목적이 있다.
  • 메소드의 선언만 통일화하고, 실행 내용은 실체 클래스마다 달라야하는 경우가 있을 경우 추상 클래스는 추상 메소드를 선언할 수 있다.
  • 추상 메소드는 메소드의 선언부만 있고 메소드 실행 내용인 중괄호 {}가 없는 메소드를 말한다.
  • 하위 클래스가 반드시 실행 내용을 채우도록 강요하고 싶은 메소드가 있을 경우, 해당 메소드를 추상 메소드로 선언하면 된다. 자식 클래스는 반드시 추상 메소드를 재정의(오버라이딩)해서 실행 내용을 작성해야하며 작성하지 않을경우 컴파일 에러가 발생한다.

추상 메소드 선언 방법

[public || protected] abstract 리턴타입 메소드명(매개변수, ...);
  • 추상 메소드 선언은 abstract 키워드가 붙어 있고 메소드 중괄호 {}가 없다.

추상 메소드 사용 방법

//추상 메소드 선언
public abstract class Animal {
	public String kind;

	public void breathe(){
		System.out.println("숨을 쉽니다.");
	}

	public abstract void sound();
}

//추상 메소드 오버라이딩
public class Dog extends Animal {
	public Dog(){
		this.kind = "포유류";
	}

	@Override //추상 메소드 재정의
	public void sound(){
		System.out.println("멍멍");
	}
}

1개의 댓글

comment-user-thumbnail
2023년 8월 10일

정보에 감사드립니다.

답글 달기