객체지향 3대원칙
추상화와 캡슐화
상속
다형성
상속
부모클래스가 가지는 멤버(필드,메소드)를 자식클래스가 물려받아 자신의 멤버인 것처럼 사용할 수 있도록 만든 기술
- 멤버 외에 타입(부모클래스의 타입) 또한 상속이 된다. (다형성의 토대)
- 자바는 단일 상속(하나의 클래스만을 부모클래스로 가짐)만 지원 -> 모호성 때문
- 클래스 간의 상속 시에는 extends 키워드를 사용
public class Academy extends Company{ }
상속의 장단점
상속의 장점
- 새로운 클래스를 작성 시 기존에 작성 된 클래스의 재사용 가능
- 클래스간 계층 관계가 형성되어 다형성 문법의 토대가 됨
상속의 단점
- 부모클래스의 기능 변경 시 자식클래스의 정상적인 동작 유무 예측이 힘듦
- 자식클래스가 물려받아 사용하는 기능들을 부모클래스에서 변경 시 어려움이 따름
- 부모클래스로부터 물려받은 기능이 자식클래스에서는 무의미 할 수 있음
장단점을 고려해 볼 때 IS-A 관계로 구분되는 경우에만 사용해야 함
IS-A
“자식클래스는 (하나의)부모 클래스이다.”의 관계로, 부모클래스의 멤버들을 자식클래스가 상속 받음
자식클래스는 일종의/하나의 부모클래스이다.
-> 재사용성이 늘어나며 상속의 단점으로 여겨지는 부분이 상쇄될 수 있음
class Application
public class Application {
public static void main(String[] args) {
Car car = new Car();
car.soundHorn();
car.run();
car.soundHorn();
car.stop();
car.soundHorn();
// Car 클래스의 기본 생성자 호출됨...
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
// 자동차가 달립니다.
// 빵!빵!
// 자동차가 멈춥니다.
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
/* 소방차, 레이싱 카는 모두 자동차이다.
* 하지만 모든 자동차는 소방차 혹은 레이싱카가 아니다.
*
* FireCar is a Car(o) / Car is a FireCar(x)
* RacingCar is a Car(o) / Car is a RacingCar(x)
*
* 이러한 관계가 성립되는 것을 IS-A관계라고 한다.
* IS-A 관계가 성립이 되는 경우에 상속을 사용할 수 있다.
*
* */
FireCar fireCar = new FireCar();
/* 정말 car의 기능들을 쓸 수 있는 지 확인 */
fireCar.soundHorn();
fireCar.run();
fireCar.soundHorn();
fireCar.stop();
fireCar.soundHorn();
// Car 클래스의 기본 생성자 호출됨...
// FireCar 클래스의 기본 생성자 호출됨...
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
// 자동차가 달립니다.
// 빵!빵!
// 자동차가 멈춥니다.
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
/* FireCar 클래스에는 아무것도 작성하지 않았지만 Car 클래스가 가진 메소드를
* 전부 사용할 수 있다. 하지만 private 멤버는 접근이 불가능하다.
* 아래 예시는 private이라 접근 불가
* */
// fireCar.runningStatus;
// fireCar.isRunning();
// 오버라이딩 이후 콘솔 출력
// Car 클래스의 기본 생성자 호출됨...
// FireCar 클래스의 기본 생성자 호출됨...
// 소방차가 앞으로 갈 수 없습니다~ 비키세요~
// 자동차가 달립니다.
// 빠아아아앙ㅇ~~!!! 빠앙ㅇ아아아아앙~~~!!!!!!
// 자동차가 멈춥니다.
// 소방차가 앞으로 갈 수 없습니다~ 비키세요~
fireCar.sprayWater();
// car.sprayWater();
// fireCar에만 있기 때문에 부모클래스에서는 사용할 수 없는 기능이다.
/* 이렇게 상속은 부모가 가진 멤버를 사용하면서 확장까지 가능하도록 해 두었다.
* car인스턴스로는 fireCar 가 가진 기능을 사용할 수 없다.
* 자식은 부모 멤버에 접근해서 자신의 것처럼 사용 가능하지만, 반대의 경우는 허용하지 않는다.
* */
RacingCar racingCar = new RacingCar();
racingCar.soundHorn();
racingCar.run();
racingCar.soundHorn();
racingCar.stop();
racingCar.soundHorn();
// Car 클래스의 기본 생성자 호출됨... // 컴파일러가 묵시적으로 호출
// RacingCar 클래스 기본 생성자 호출됨...
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
// 자동차가 달립니다.
// 빵!빵!
// 자동차가 멈춥니다.
// 주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.
// 부모클래스의 기본 생성자는 자식 클래스의 메소드 생성시 가장먼저 호출이 된다.
// 명시적으로 쓰지않더라도 컴파일러가 묵시적/자동적으로 추가하기 때문에
// 자동으로 출력된다.
}
}
class Car
public class Car {
/* 자동차의 달리는 상태를 확인할 수 있는 필드 */
private boolean runningStatus;
/* 기본생성자 */
public Car() {
System.out.println("Car 클래스의 기본 생성자 호출됨...");
}
/* 자동차의 기본적인 달리는 기능 */
public void run() {
runningStatus = true;
System.out.println("자동차가 달립니다.");
}
/* 자동차의 기본적인 경적을 울리는 기능 */
public void soundHorn() {
/* 자동차는 주행 중인 상태일 때 기본적인 경적을 울리는 기능을 수행할 수 있다. */
if (isRunning()) {
System.out.println("빵!빵!");
} else {
System.out.println("주행중이 아닌 상태에서는 경적을 울릴 수 없습니다.");
}
}
//private boolean isRunning() {
protected boolean isRunning() {
/*자동차의 주행중 상태를 반환하는 기능을 수행*/
return runningStatus;
}
/* 자동차의 기본적인 멈추는 기능 */
public void stop() {
runningStatus = false;
System.out.println("자동차가 멈춥니다.");
}
}
class FireCar extends Car
public class FireCar extends Car{
/* Car 클래스를 부모클래스로 FireCar 클래스를 자식 클래스로 사용할 것이다.
* 클래스 선언부에 extends Car 를 추가해서 Car 클래스를 상속 받을 것이다.
* public class FireCar extends Car
*
* 1. 그러면 FireCar 클래스는 부모클래스인 Car가 가지고 있는 모든 멤버를 상속 받는다.
* 2. 하지만 부모클래스의 생성자는 상속받지 못한다.
* 3. 또한 부모가 가지고있는 private 멤버는 접근이 불가능하다.
*
* 자바에서 상속은 기본적으로 부모가 가진 멤버를 자식이 사용할 수 있는 것이지만
* 더 나아가서 부모클래스의 확장(extend)이라는 개념을 가진다.
* 추가적인 자신의 멤버 작성이 가능하며 메소드 재정의(overriding)라는 기술을 이용해서
* 부모가 가진 메소드를 재정의 하는 것도 가능하다.
*
* */
/* 기본 생성자 */
public FireCar() {
/* 모든 생성자에는 맨 첫줄에 super() 클래스를 컴파일러가 자동추가한다.
* 부모의 기본 생성자를 호출하는 구문이다.
* 해당 생성자가 호출 될 시 가장 먼저 Car 클래스 호출 내용이 출력 될 것이다.
* 명시적, 묵시적(안써도 컴파일러가 자동추가) 전부 가능하다. */
super();
System.out.println("FireCar 클래스의 기본 생성자 호출됨...");
}
/* 오버라이딩 */
/* 소방차의 경적 소리가 너무 작으므로 조금 더 크게 키워보자
* 경적 소리를 내는 용도의 메소드 선언부는 그대로 두되 소방차 쪽에서 재정의 해 볼 것이다. */
/* @Override 어노테이션
* JDK 1.5 부터 추가된 문법으로 오버라이딩 성립 요건을 체크하여 성립되지않는 경우
* 컴파일 에러를 발생시킨다. (오버라이딩이 정상적으로 작성된 것인지를 체크해주는 기능)
* 에러 : 철자가 틀리는 등
* */
@Override //부모가 가진 기능을 자식이 고쳐서 사용하고 싶을때 재정의하는 것
public void soundHorn() {
/* 부모가 가지고 있는 멤버인 isRunning() 메소드를 사용해야
* 주행중인 상태를 확인할 수 있으므로 메소드 접근 제한자를
* private -> protected 로 변경*/
if(isRunning()) {
System.out.println("빠아아아앙ㅇ~~!!! 빠앙ㅇ아아아아앙~~~!!!!!!");
} else {
System.out.println("소방차가 앞으로 갈 수 없습니다~ 비키세요~");
}
}
/* 부모클래스에는 없지만 자식클래스에는 있는 기능 추가
* 물을 뿌리는 기능을 제공 */
public void sprayWater() {
System.out.println("불난 곳을 발견했습니다. 물을 뿌립니다 =======>>>");
}
}
class RacingCar extends Car
public class RacingCar extends Car {
/* 기본 생성자 */
public RacingCar() {
System.out.println("RacingCar 클래스 기본 생성자 호출됨...");
}
/* 부모로부터 한꺼번에 상속받은 기능이지만
* 레이싱카의 적합하지않은 기능인 경적소리, 달리는 기능등을 수정하고싶다. */
@Override
public void soundHorn() {
/* 레이싱카는 경적을 울리지 않는다. */
System.out.println("레이싱 카는 경적을 울리지 않습니다. (조용...)");
}
@Override
public void run() {
System.out.println("레이싱카가 전속력으로 질주합니다!!!!!!!!!!");
}
// 오버라이딩 후 결과값 출력
// Car 클래스의 기본 생성자 호출됨...
// RacingCar 클래스 기본 생성자 호출됨...
// 레이싱 카는 경적을 울리지 않습니다. (조용...)
// 레이싱카가 전속력으로 질주합니다!!!!!!!!!!
// 레이싱 카는 경적을 울리지 않습니다. (조용...)
// 자동차가 멈춥니다.
// 레이싱 카는 경적을 울리지 않습니다. (조용...)
}
super와 super()
super
- super( )와 소괄호가 안붙었다는 차이점이있으므로 구분해서 기억해야한다.
- 부모클래스의 인스턴스 주소를 보관하는 레퍼런스 변수
- 자식클래스 내의 모든 생성자와 메소드 내에서 부모클래스의 레퍼런스 변수를 사용할 수 있음
super( )
- 부모 생성자를 호출하는 구문으로 매개변수의 타입, 개수, 순서가 일치하는 부모의 생성자를 호출하게 됨
- this( )는 해당 클래스의 생성자를 호출하는 구문이다.
- super( )는 부모클래스가 가지는 private 생성자를 제외한 나머지 생성자를 호출하는 구문이다.
class Application
public class Application {
public static void main(String[] args) {
/* ProductDTO 기본 생성자로 인스턴스 생성 후 정보 출력 */
ProductDTO product1 = new ProductDTO();
System.out.println(product1.getInformation());
/* ProductDTO의 모든 필드를 초기화하는 생성자로 인스턴스 생성 후 정보 출력 */
ProductDTO product2 = new ProductDTO("S-01234", "삼성", "갤럭시Z폴드1", 2398000, new java.util.Date());
System.out.println(product2.getInformation());
/* ComputerDTO 기본 생성자로 인스턴스 생성 후 정보 출력 */
ComputerDTO computer1 = new ComputerDTO();
System.out.println(computer1.getInformation());
/* ComputerDTO의 모든 필드를 초기화하는 생성자로 인스턴스 생성 후 정보 출력 */
ComputerDTO computer2 = new ComputerDTO("퀼컴 스냅드래곤", 512, 12, "안드로이드");
System.out.println(computer2.getInformation());
/* ComputerDTO의 부모 필드로 함께 초기화하는 생성자로 인스턴스 생성 후 정보 출력 */
ComputerDTO computer3 = new ComputerDTO("S-01234", "삼성", "갤럭시Z폴드1", 2398000, new java.util.Date(),
"퀼컴 스냅드래곤", 512, 12, "안드로이드");
System.out.println(computer3.getInformation());
/* 항상 super()로 부모생성자를 먼저 호출해야 한다. */
}
}
class ProductDTO
import java.util.Date;
public class ProductDTO {
/* 관리하려는 상품의 속성을 추상화해서 DTO 클래스를 만든다. */
private String code; // 상품코드
private String brand; // 제조사
private String name; // 상품명
private int price; // 가격
private java.util.Date manufacturingDate; // 제조일자
/* 기본 생성자 */
public ProductDTO() {
System.out.println("ProductDTO 클래스의 기본 생성자 호출함...");
}
/* 모든 필드를 초기화 하는 생성자 */
public ProductDTO(String code, String brand, String name, int price, Date manufacturingDate) {
/* 아무 클래스도 상속받지 않았는데 super() 가 호출되는 이유
* java.lang.Object 클래스의 생성자가 호출 된 것이다.
* 모든 클래스는 Object 클래스의 후손이기 때문이다. */
super();
// extends 를 선언하지않았는데 생겼다. 무엇을 의미하는 것일까?
// Every class has {@code Object} as a superclass.
// ProductDTO extends Object 라 작성하지않더라도 컨파일러가 자동으로 입력해준다.
// 모든 클래스는 오브젝트 클래스의 자식/후손 이다.
this.code = code;
this.brand = brand;
this.name = name;
this.price = price;
this.manufacturingDate = manufacturingDate;
System.out.println("ProductDTO 클래스의 매개변수 있는 생성자 호출함...");
}
/* getters/setters 작성하기 */
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public java.util.Date getManufacturingDate() {
return manufacturingDate;
}
public void setManufacturingDate(java.util.Date manufacturingDate) {
this.manufacturingDate = manufacturingDate;
}
/* 모든 필드 값을 문자열로 반환하는 메소드 */
public String getInformation() {
return "ProductDTO [code=" +code + ", brand=" + brand + ", name=" + name
+ ", price=" + price + ", manufacturingDate=" + manufacturingDate + "]";
}
}
class ComputerDTO extends ProductDTO
import java.util.Date;
public class ComputerDTO extends ProductDTO{
/* ComputerDTO 클래스는 하나의 상품이다. (IS-A)
* ProductDTO클래스를 상속받아 작성할 것이다.
* (단, 생성자는 상속받지않으며 private에는 직접접근 불가)
* */
/* ComputerDTO가 가지는 추가적인 속성을 필드로 작성한다. */
private String cpu;
private int hdd;
private int ram;
private String operationSystem;
/* 기본 생성자 */
public ComputerDTO() {
System.out.println("ComputerDTO클래스의 기본 생성자 호출함...");
}
/* 자식클래스의 기본 생성자를 생략하면 부모 클래스의 기본생성자 호출에 오류를 만든다.
* 자식클래스의 기본생성자는 필수로 작성한다. */
/* 모든 필드를 초기화 하는 생성자 */
public ComputerDTO(String cpu, int hdd, int ram, String operationSystem) {
/* 부모 클래스의 기본 생성자 호출 */
super();
this.cpu = cpu;
this.hdd = hdd;
this.ram = ram;
this.operationSystem = operationSystem;
System.out.println("ComputerDTO클래스의 모든 필드를 초기화 하는 생성자 호출함...");
}
// 컴퓨터와 관련된 값들은 초기화 하지만, 부모클래스 쪽의 생성자들은 초기화되지않은
// 기본값만을 가지고 있을 것이다.
/* 부모의 필드도 모두 초기화하는 생성자 */
/* 우클릭 - source - Generate Constructor from Super class */
public ComputerDTO(String code, String brand, String name, int price, Date manufacturingDate,
String cpu, int hdd, int ram, String operationSystem) {
/* 부모의 모든 필드를 초기화하는 생성자로 ProductDTO 클래스가 가진 필드를 초기화 할 값 전달
* 반드시 첫 줄에 생성자를 호출해야 오류가 나지않는다. */
super(code, brand, name, price, manufacturingDate);
// this(cpu, hdd, ram, operationSystem);
// 생성자 안에서 다른 생성자를 호출할때는 무조건 윗줄에 작성해야 하기에 컴파일 에러가 생긴다.
/* 위와 같이 작성하면 this()로 다시한번 생성자를 호출하게 되기 때문에
* 생성자 두 번 호출은 허용되지 않는다.
* */
this.cpu = cpu;
this.hdd = hdd;
this.ram = ram;
this.operationSystem = operationSystem;
System.out.println("ComputerDTO 클래스의 부모필드도 초기화하는 생성자 호출함...");
}
/* getters/setters 작성하기 */
/* 부모 필드의 메소드에 대해서는 자신의 것처럼 사용 가능하므로 따로 작성할 필요가 없다.
* 자식 클래스에 추가된 필드에 대해서만 작성한다. */
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getHdd() {
return hdd;
}
public void setHdd(int hdd) {
this.hdd = hdd;
}
public int getRam() {
return ram;
}
public void setRam(int ram) {
this.ram = ram;
}
public String getOperationSystem() {
return operationSystem;
}
public void setOperationSystem(String operationSystem) {
this.operationSystem = operationSystem;
}
/* 모든 필드값을 문자열로 반환하는 메소드 */
@Override
public String getInformation() {
/* 부모 클래스에서 작성한 getter를 이용해서 부모 필드가
* 가지고 있는 값도 한번에 문자열 합치기를 한다.
* 부모가 가진 멤버는 super.와 this. 둘다 사용이 가능하다.
* 하지만 코드의 의미를 명확히 하기 위해 super. 를 사용하는 것이 좋다.
* */
// return "ComputerDTO ["
// + "code=" + super.getCode()
// + ", brand=" + super.getBrand()
// + ", name=" + super.getName()
// + ", price=" + super.getPrice()
// + ", manufacturingDate=" + super.getManufacturingDate()
//super.getInformation() 한줄로 단축할 수도 있다.
//
// + ", cpu=" + this.cpu
// + ", hdd=" + this.hdd
// + ", ram=" + this.ram
// + ", operationSystem=" + this.operationSystem
// + "]";
//
/* super.getInformation() : 정상적으로 부모의 메소드 호출
* this.getInformation() : 재귀 호출이 일어나며 StackOverflowError 발생
* getInformation() : this. 이 자동 추가되어 재귀호출이 일어남
* java.lang.StackOverflowError
* */
/* 부모의 getInformation 을 호출하여 코드 단축하기 */
return super.getInformation() //컴퓨터의 getInformation과 구분해야 한다.
+ "ComputerDTO ["
+ ", cpu=" + this.cpu
+ ", hdd=" + this.hdd
+ ", ram=" + this.ram
+ ", operationSystem=" + this.operationSystem
+ "]";
// this.code로는 작성하지 못한다. 왜냐? private이기 떄문
}
}
상속에서 부모생성자가 먼저 호출이 되어야지만 자식 생성자가 호출될 수 있다.
오버라이딩
오버라이딩(Overriding)이란?
- 부모클래스에서 상속받은 메소드를 자식클래스가 재정의하여 사용하는 것
성립조건
- 1. 메소드 이름 동일
- 2. 메소드 리턴 타입 동일
- 3. 매개변수의 타입, 개수, 순서가 동일
- 4. private 메소드는 오버라이딩 불가능
- 5. final 키워드가 사용된 메소드는 오버라이딩 불가능
- 6. 접근제한자는 부모 메소드와 같거나 더 넓은 범위여야 함
- 7. 예외처리는 같은 예외이거나 더 구체적(하위)인 예외를 처리해야 함
오버라이딩과 오버로딩
오버라이딩 테스트 : class SuperClass
/* 부모클래스를 의미 */
public class SuperClass {
/* 오버라이딩 성립 요건에 대해서 알아보자
* 오버라이딩이란?
* 부모클래스(SuperClass)에서 상속받은 메소드를 자식클래스(SubClass)
* 에서 재정의하여 사용하는 것이다. */
/* 오버라이팅 테스트를 위한 기준이 되는 메소드 */
public void method(int num) {}
private void privateMethod() {}
public final void finalMethod() {}
protected void protectedMethod() {}
}
오버라이딩 테스트 : class SubClass
/* 자식클래스를 의미 */
public class SubClass extends SuperClass {
/* extends SuperClass를 추가해야 오버라이딩 가능하다. */
/* 부모 클래스에서 작성한 메소드를 그대로 가져오면 정상적으로 오버라이딩 되었다.
* 1. 메소드 이름 변경 하면 에러발생
* 2. 메소드 리턴 타입 변경 하면 에러발생
* 3. 매개변수 갯수나 타입 변경시 에러 발생
*
* */
@Override //어노테이션
public void method(int num) {}
// 1. The method method1(int) of type SubClass must
// override or implement a supertype method
// 2. The return type is incompatible with SuperClass.method(int)
/* 4. private 메소드는 오버라이딩 불가 */
// @Override
// private void privateMethod() {}
// The method privateMethod() of type
// SubClass must override or implement a supertype method
/* 5. final 메소드는 오버라이딩 불가 */
// @Override
// public final void finalMethod() {}
// Cannot override the final method from SuperClass
// 파이널 메소드는 재정의할 수 없다.
// final은 어디에 붙을 수 있을까?
// : 지역변수, 필드, 메소드(오버라이딩불가의미), 클래스(상속 불가를 의미)
// The type SubClass cannot subclass the final class SuperClass
/* 6. 부모메소드 접근 제한자와 같거나 더 넓은 범위로만 오버라이딩 가능
* 더 좁은 범위의 접근제한자로 변경 불가능하다. */
// @Override
// void protectedMethod() {}
// 넓이 큰 순위대로 public > protected > default > private
// @ Override
// protected void protectedMethod() {} // 같은 범위로 가능
@ Override
public void protectedMethod() {} // 더 넒은 범위로 가능
/* 클래스에도 final 키워드를 넣을 수 있는데
* final 키워드를 추가하면 상속 불가의 의미를 가진다.
* */
}
학습점검
더보기
✅ 클래스의 상속에 대해 이해할 수 있다.
✅ 상속의 목적에 대해 이해할 수 있다.
✅ 상속의 장단점에 대해 이해할 수 있다.
✅ 상속의 특징에 대해 이해할 수 있다.
✅ super에 대해 이해할 수 있다.
✅ 오버라이딩에 대해 이해할 수 있다.
✅ 오버로딩과 오버라이딩의 차이에 대해 이해할 수 있다.
'Programming > JAVA' 카테고리의 다른 글
자바 API (0) | 2022.01.06 |
---|---|
다형성 (0) | 2022.01.05 |
객체 배열 (0) | 2022.01.03 |
클래스와 객체(3) (0) | 2021.12.30 |
클래스와 객체(2) (0) | 2021.12.29 |