상속에 대해 다뤄보자.
상속 : 부모 클래스가 가지는 멤버 (필드, 메소드)를 자식 클래스가 물려 받아 자신의 멤버인 것처럼 사용할 수 있도록하는 것
자식 - 부모 - 조부모 관계와 같이 가능은 하다.
상속은 extends 키워드를 사용한다.
public class Academy extends Company {
}
Academy 가 자식 클래스, Company 가 부모 클래스이다.
-> Academy is a Company -> 아카데미는 하나의 회사이다. 라는 말이 어울리므로 자식이다.
자식 클래스는 부모 클래스의 필드와 메소드를 포함하고, 확장한 클래스이다.
부모 클래스는 여러 자식 클래스를 가질 수 있으니 가능하면 여러 자식 클래스마다 공통적으로 포함되는 메소드들은 부모 클래스에 정의하는게 좋다.
인스턴스 생성 시 부모 생성자를 호출해 부모 클래스의 인스턴스도 함께 생성하게 된다.
이 때 생성한 부모 인스턴스의 주소를 보관하는 레퍼런스 변수로 자식 클래스는 모든 생성자, 메소드에서 사용할 수 있다.
super() : 부모 생성자를 호출하는 구문, 인자와 매개변수 타입, 개수, 순서가 일치하는 생성자를 호출하게 된다.
사실 super는 안 적어도 기본생성자처럼 실행된다.
computer is a product 라는 문장을 이용해서 computer와 product를 상속 관계로 해보자.
Product
public class Product {
private String code;
private String brand;
private String name;
private int price;
private java.util.Date manufactureDate;
public Product() {
}
public Product(String code, String brand, String name, int price, Date manufactureDate) {
this.code = code;
this.brand = brand;
this.name = name;
this.price = price;
this.manufactureDate = manufactureDate;
}
}
Computer
public class Computer extends Product {
private String cpu;
private int hdd;
private int ram;
private String operatingSystem;
}
이 때, Computer에도 기본생성자를 만들려고 하면

이렇게 나오는데, 위의 코드의 생성자에 대해서 말하는 것이다.
아래와 같이 만들 수 있다. (Product의 기본 생성자는 super()를 생략할 수 있어서 빈 칸으로 보이지만 추가해줬다.)
import java.util.Date;
public class Computer extends Product {
private String cpu;
private int hdd;
private int ram;
private String operatingSystem;
public Computer() {
super();
}
public Computer(String cpu, int hdd, int ram, String operatingSystem) {
super();
this.cpu = cpu;
this.hdd = hdd;
this.ram = ram;
this.operatingSystem = operatingSystem;
}
public Computer(String code, String brand, String name, int price, Date manufactureDate, String cpu, int hdd, int ram, String operatingSystem) {
super(code, brand, name, price, manufactureDate);
this.cpu = cpu;
this.hdd = hdd;
this.ram = ram;
this.operatingSystem = operatingSystem;
}
}
Product 와 Computer 둘다 toString()도 추가해주고 실행해보면?
Application
import java.util.Date;
public class Application {
public static void main(String[] args) {
Product product = new Product();
System.out.println(product);
Product product2 = new Product("p01", "예시", "자바", 1000, new Date());
System.out.println(product2);
// System.out.println(new Date().toString());
Computer computer = new Computer();
System.out.println(computer);
Computer computer1 = new Computer("snap dragon", 512, 16, "Android");
System.out.println(computer1);
Computer computer2 = new Computer("s-1234", "Google", "픽셀", 1000000, new Date(), "snap dragon", 1024, 32, "Android");
System.out.println(computer2);
}
}

computer2 는 부모의 생성자들도 받아온 객체여서 code, brand, name, price, manufatureDate가 더 필요한데 toString()에서 더 나와야 하니 수정해준다.

원래는 이렇게 생성됐지만

super. 을 활용해서 더 추가해줬다.
부모 클래스의 toString()을 활용

위 사진처럼 변경해주면 된다.
부모 클래스의 필드인
code,brand등 출력하기 위해 추가했던 필드들은 자식 클래스에서 출력할 때 내 필드라고 할 수 있기 때문에 꼭super.을 붙이지 않아도 된다.
this.getCode()getCode()
를 해줘도 괜찮다.하지만, 자식클래스에서도 똑같은
code라는 필드가 있다면? -> 그 때super.을 붙이거나this를 붙이거나 하는 것
실행결과
Product{code='null', brand='null', name='null', price=0, manufactureDate=null}
Product{code='p01', brand='예시', name='자바', price=1000, manufactureDate=Wed Jul 17 10:39:34 KST 2024}
Product{code='null', brand='null', name='null', price=0, manufactureDate=null}Computer{cpu='null', hdd=0, ram=0, operatingSystem='null'}
Product{code='null', brand='null', name='null', price=0, manufactureDate=null}Computer{cpu='snap dragon', hdd=512, ram=16, operatingSystem='Android'}
Product{code='s-1234', brand='Google', name='픽셀', price=1000000, manufactureDate=Wed Jul 17 10:39:34 KST 2024}Computer{cpu='snap dragon', hdd=1024, ram=32, operatingSystem='Android'}
오버라이딩 : 부모클래스에서 상속받은 메소드를 자식클래스가 재정의하여 사용하는 기술
오버로딩과 헷갈릴만 하지만 구별하기는 쉽다.
오버라이딩은 말그대로 매개변수, 메소드명 그대로 두고 자식클래스에서 다시 정의하는 것이고,
오버로딩은 같은 클래스에서 매개변수를 다르게 해서 정의하는 것이다.
스프링에서는 @Override 라고 하는 어노테이션이 있다.
@Override 어노테이션 사용 이유
진짜 간단하게 출력문을 이용해서 알아보자.
Car, FireCar, RacingCar 3개의 클래스를 만들어본다.

이런 상속 관계로 시작한다.
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 (runningStatus) {
System.out.println("뿌 뿌");
} else {
System.out.println("running 중이 아니면 경적 못 울림");
}
}
public boolean isRunningStatus() {
return runningStatus;
}
public void stop() {
runningStatus = false;
System.out.println("자동차가 멈춥니다.");
}
}
FireCar
public class FireCar extends Car {
public FireCar() {
super();
System.out.println("FireCar 클래스의 기본 생성자 호출됨.");
}
public void sprayWater() {
System.out.println("화재지 발견 쉭쉭 (물 뿌리는 소리)");
}
@Override
public void soundHorn() {
System.out.println("빠아아아아아아앙");
}
}
RacingCar
public class RacingCar extends Car {
@Override
public void run() {
System.out.println("레이싱 자동차가 신나게 달림 ㅋ 쓔우웅~~");
}
@Override
public void soundHorn() {
System.out.println("얘는 유명한 경적없는 차량임");
}
}
위와 같이 run(), soundHorn() 2개의 메소드를 부모 클래스 뿐만이 아닌 자식 클래스에서도 재정의가 가능하다.
Application
public class Application {
public static void main(String[] args) {
Car car = new Car();
car.soundHorn();
car.run();
car.soundHorn();
car.stop();
car.soundHorn();
System.out.println();
FireCar fireCar = new FireCar();
fireCar.soundHorn();
fireCar.run();
fireCar.soundHorn();
fireCar.stop();
fireCar.soundHorn();
fireCar.sprayWater();
System.out.println();
RacingCar racingCar = new RacingCar();
racingCar.soundHorn();
racingCar.run();
racingCar.soundHorn();
racingCar.stop();
racingCar.soundHorn();
}
}
실행결과
Car 클래스의 기본 생성자 호출됨.
running 중이 아니면 경적 못 울림
자동차가 달리기 시작합니다.
뿌 뿌
자동차가 멈춥니다.
running 중이 아니면 경적 못 울림
Car 클래스의 기본 생성자 호출됨.
FireCar 클래스의 기본 생성자 호출됨.
빠아아아아아아앙
자동차가 달리기 시작합니다.
빠아아아아아아앙
자동차가 멈춥니다.
빠아아아아아아앙
화재지 발견 쉭쉭 (물 뿌리는 소리)
Car 클래스의 기본 생성자 호출됨.
얘는 유명한 경적없는 차량임
레이싱 자동차가 신나게 달림 ㅋ 쓔우웅~~
얘는 유명한 경적없는 차량임
자동차가 멈춥니다.
얘는 유명한 경적없는 차량임
class에final키워드가 붙으면 부모 클래스가 될 수 없다.