
상속 먼저 얘기해보자.

상속은 부모클래스로부터 메서드와 필드를 내려받는다.

class 부모클래스 {
public int wheatAmount;
부모클래스(int w) {
this.wheatAmount = w;
}
public void sayHello() {
System.out.println("안녕! 나는 부모클래스 밀가루호빵이야");
}
public void showAmounts() {
System.out.println("wheatAmount: " + wheatAmount);
}
또한 부모클래스의 메서드를 @overriding(메서드 재정의) 하여 메서드를 다른 로직으로 변경할 수 있다.
메서드재정의를 하면 부모객체의 메서드는 숨겨지기 때문에 자식객체에서 매서드를 호출하면 오버라이딩된 자식 메서드가 호출된다.
🪬 주의 !
class 자식클래스 extends 부모클래스 {
public int pizzaAmount
public int tomatoAmount
자식클래스(int pa, int twa, int w) {
super(w); //부모클래스를 호출!
this.pizzaAmount= pa;
this.tomatoAmount= twa;
}
@Override
public void sayHello() {
System.out.println("안녕! 나는 자식클래스 피자호빵이야");
}
public void showAmounts() {
super.showAmounts(); //부모클래스의 메서드 호출하기
System.out.println("Pizza Amount: " + pizzaAmount);
System.out.println("Tomato Amount: " + tomatoAmount);
}
}
public class Main {
public static void main(String[] args) {
자식클래스 child = new 자식클래스(10, 20, 5);
//새로운 생성자 생성하면서 파라미터 입력
child.sayHello();
//안녕! 나는 자식클래스 피자호빵이야
child.showAmounts();
//Pizza Amount: 10, Tomato Amount: 20, wheat Amount: 5
}
🪬 메서드를 호출할 때 자동으로 타입캐스팅이 일어난다.
Parent parent = new Parent();
Child child = new Child();
parent.sayHello(child);
//안녕! 나는 자식클래스 피자호빵이야
//parent 에서 파라미터에서 받는 객체로 자동으로 타입캐스팅
//즉, Parent parent = child;
아래와 같이 강제로 타입을 캐스팅하여 다양한 결과를 도출할 수도 있다.
이것을매개 변수의 다형성이라고 한다.
강제로 타입캐스팅을 하는 경우
Parent parent = new Child();
// 다형성에 의한 상위 클래스 타입 변수에 하위 클래스 객체 할당
// parent는 Parent 타입이므로 Parent 클래스에 정의된 메서드만 호출 가능
parent.someParentMethod();
// Child 타입으로 다시 캐스팅하여 Child 클래스의 메서드 호출 가능
if (parent instanceof Child) {
Child child = (Child) parent;
child.someChildMethod();
}
자식클래스의 메서드에 부모클래스의 필드와 메서드를 받아오면 main에서는 자식클래스를 실행하는 것만으로도 부모도 함께 생성자를 생성하고, 메서드를 실행할 수 있다. 이 기능으로 추상화와 인터페이스를 활용할 수 있다.
추상클래스는 메서드만 선언해놓고, 자식클래스에서 그 메서드를 오버라이딩 하여 메소드 코드를 조작할 수 있다.
public abstract class Animal {
String name;
// 생성자
public Animal(String animalName) {
this.name = animalName;
}
// 추상 메서드
public abstract void makeSound();
// 구현된 메서드
public void sleep() {
System.out.println(name + " 자는중...");
}
}
class Dog extends Animal {
// 생성자
public Dog(String animalName) {
super(animalName); // Animal 클래스의 생성자를 호출
}
// 메서드
public void lifeCycle() {
System.out.println("강아지는 보통 주행성 동물이에요.");
}
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
public class Execute {
public static void main(String[] args) {
// 추상 클래스의 객체는 직접 생성할 수 없음
// Animal animal = new Animal(); // 오류: 추상 클래스는 인스턴스화할 수 없음
// 대신 하위(자식)클래스의 객체를 생성하여 메서드 호출
Dog dog = new Dog("진도리");
dog.sleep(); // 진도리 자는중...
dog.makeSound(); // Woof!
dog.lifeCycle(); // 강아지는 보통 주행성 동물이에요.
}
}
객체를 생성할 수 없고 자식클래스를 통해서 구현하며 단일상속이다.
이 특성을 아직 어디에 활용해야 하는지 감은 잘 오지 않지만 분명 메서드가 적용되는 좁은 폭의특정한 조건이 있을 거다.
그래서 기능을 만들었고 아직까지 있는거겠지 -_-
메서드 시그니쳐만 모아놓은 클래스, 인터페이스. (거의 설계자 직급)
추상화클래스에서는 필드와 일반메서드를 작성할 수 있어서 인터페이스보다 적용되는 폭이 좁다.
interface Animal {
// 추상 메서드
void makeSound();
// 디폴트 메서드
default void sleep() {
System.out.println("자는중...");
}
// 정적 메서드
static void staticMethod() {
System.out.println("인터페이스의 static 메서드 이옵니다");
}
// 상수 필드
static final String DEFAULT_NAME = "동물";
}
자식클래스에서 인터페이스에서 확장(implements)받아 뚝딱뚝딱 설계도의 기초에서 더 자세한 코드와 기능을 추가한다.
class Dog implements Animal {
String name;
public Dog(String name) {
this.name = name;
}
@Override
public void makeSound() {
System.out.println("Woof!");
}
// sleep() 메서드는 디폴트 메서드를 그대로 사용할 수 있다.
@Override
public void sleep() {
System.out.println("Dog is sleeping...");
//영문번역으로 오버라이딩 해버리기 ;;
}
}
메인클래스에서 자식클래스에서 뚝딱뚝딱 만든 새로운 메서드 로직으로 값을 도출할 수 있다.
public class Execute {
public static void main(String[] args) {
Dog dog = new Dog("진도리");
dog.makeSound(); // Woof!
dog.sleep(); // Dog is sleeping...
// 오버라이딩 하기 전의 디폴트 메서드 호출
Animal.originalSleep(); // 자는중 ...
// 인터페이스의 정적 메서드 호출
Animal.staticMethod(); // 인터페이스의 static 메서드 이옵니다
// 인터페이스의 상수 필드 사용
System.out.println(Animal.DEFAULT_NAME); // 동물
}
}
인터페이스를 활용하여 어떤 메서드를 어떤 파라미터를 받아서 활용할 건지 메서드 시그니쳐만 작성해 둔 뒤, 자식클래스에서 상세히 구현할 수 있다.
이는 초반 프로젝트 설계 기간을 줄여줄 수 있을 것으로 기대된다.