09_다형성(Polymorphism)
Sports 클래스 (== 부모 클래스)
package com.kh.chap02.abstractAndInterface.part01.basic.model.vo;
// public class Sports {
public abstract class Sports { // 추상 클래스(abstract 키워드)
// 필드부
private int people; // 스포츠에 참여하는 선수 명 수
// 생성자부
public Sports() {
}
public Sports(int people) {
this.people = people;
}
// 메소드부
// getter/setter, toString() 오버라이딩, 규칙을 출력하는 public void rule 메소드
public int getPeople() {
return people;
}
public void setPeople(int people) {
this.people = people;
}
@Override
public String toString() {
return "people: " + people;
}
// 규칙을 출력하는 public void rule() 메소드
/*
public void rule() {
System.out.println("규칙을 잘 지켜서 플레이하세요"); // Sports 라고 포괄적으로 표현했을 때 정확한 룰이 없음
}
*/
// 아니 어차피 자식 클래스가 지 입맛대로 메소드 바꿀 것 같으면 부모 클래스에서는 그냥
// public void rule(); // 이렇게 쓰면 안 되나?
// This method requires a body instead of a semicolon 오류 뜸
public abstract void rule();
// The abstract method rule in type Sports can only be defined by an abstract class: abstract 클래스 내에서만 정의될 수 있다는 내용으로 오류 메시지 바뀜
}
몸통부가 존재하지 않는 미완성된 메소드 == 추상 메소드
단, 추상 메소드를 정의하고자 하다면 표현상 abstract 라는 예약어를 추가해 줘야 함
미완성된 추상 메소드가 하나라도 포함되는 순간 해당 클래스 또한 미완성된 클래스 (추상 클래스)가 되어 버림
추상 클래스 또한 예약어로 abstract를 붙이면 됨!
public class Sports { => public abstract class Sports { 수정해 주면 오류 없어짐!
Basketball 클래스 (== 자식 클래스)
package com.kh.chap02.abstractAndInterface.part01.basic.model.vo;
// Sports 상속받기
public class Basketball extends Sports{
// 미완성된 클래스인 추상 클래스를 상속받게 되면
// 부모 클래스에 있는 추상 메소드를 강제로 오버라이딩을 통해 완성시켜 줘야 함
// 필드부
// 따로 만들 필드 없습니다~
// 생성자부
public Basketball() {
}
public Basketball(int people) {
super(people);
}
// 메소드부
@Override
public void rule() {
System.out.println("손으로 공을 던져 링에 넣어야 함");
}
}
Football 클래스(== 자식 클래스)
package com.kh.chap02.abstractAndInterface.part01.basic.model.vo;
// Sports 상속받기
public class Football extends Sports {
// 필드부
// 따로 만들 필드 없습니다~
// 생성자부
public Football () {
}
public Football(int people) {
super(people);
}
// 메소드부
@Override
public void rule() {
System.out.println("손이 아닌 발로 공을 차야 함");
}
}
BasicRun 클래스
package com.kh.chap02.abstractAndInterface.part01.basic.run;
import com.kh.chap02.abstractAndInterface.part01.basic.model.vo.Basketball;
import com.kh.chap02.abstractAndInterface.part01.basic.model.vo.Football;
import com.kh.chap02.abstractAndInterface.part01.basic.model.vo.Sports;
public class BasicRun {
public static void main(String[] args) {
/*
// Sports s = new Sports();
// 추상 클래스로 만들면 절대 객체 생성 불가
// => 미완성된 클래스이기 때문
Sports s; // 변수를 선언할 뿐이기 때문에 가능(참조자료형 변수로는 활용 가능)
s = new Football();
// 아무리 추상클래스 타입이어도 "다형성"에 의해 자식 타입의 객체를 생성 후 담을 수 있음
*/
// 선언과 동시에 생성하고 싶다면?
Sports s = /* (Sports) */ new Football();
// 객체 배열
Sports[] arr = new Sports[2];
arr[0] = new Basketball();
arr[1] = new Football();
for(int i = 0; i < arr.length; i++) {
arr[i].rule();
}
* 추상 클래스(미완성된 클래스) => abstract calss
- 해당 클래스 내부에 추상 메소드가 하나라도 존재하는 순간 반드시 추상 클래스로 정의해야 함
- 단, 추상 메소드가 굳이 없어도 abstract class로 정의하면 추상 클래스로 정의함
(일반 필드 + 일반 메소드 + 추상 메소드(생략 가능))
=> 개념적 이유: 클래스가 아직 구체적이지 않은 덜 왈성된 상태인 것 같을 때
=> 기술적 이유: 이 클래스를 객체 불가능하게 막고 싶을 때
- 객체 생성이 불가하나 다형성적을 적용하여 참조변수로는 활용 가능!
*추상 메소드(미완성된 메소드) => abstract 반환형
- 미완성된 메소드로 몸통부 { }가 구현되어 있지 않은 메소드
- 자식 클래스에서 오버라이딩을 통해 완성됨 (즉, 강제로 오버라이딩을 해야 함)
Person 클래스
package com.kh.chap02.abstractAndInterface.part02.family.model.vo;
public abstract class Person {
// 필드부
private String name; // 이름
private double weight; // 몸무게
private int health; // 건강도
// 생성자부
public Person() {
super();
}
public Person(String name, double weight, int health) {
super();
this.name = name;
this.weight = weight;
this.health = health;
}
// 메소드부
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
@Override
public String toString() {
return "name: " + name + ", weight: " + weight + ", health: " + health;
}
// 사람의 행위를 메소드로 표현
// 일반 메소드 버전
/*
public void eat() {
System.out.println("밥을 먹는다.");
}
public void sleep() {
System.out.println("잠을 잔다.");
}
*/
// 추상 메소드 버전 (abstract, 몸통부 x) => 미완성된 상태 = > 상속받는 측에서 완성시켜 줘야 함(오버라이딩 활용)
public abstract void eat();
public abstract void sleep();
}
Mother 클래스
package com.kh.chap02.abstractAndInterface.part02.family.model.vo;
public class Mother extends Person {
// 필드부
private String babyBirth; // 아기의 탄생 여부
// 생성자부
public Mother() {
super();
}
public Mother(String name, double weight, int health, String babyBirth) {
super(name, weight, health); // 부모 클래스에 토스
this.babyBirth = babyBirth;
}
// 메소드부
public String getBabyBirth() {
return babyBirth;
}
public void setBabyBirth(String babyBirth) {
this.babyBirth = babyBirth;
}
@Override
public String toString() {
return super.toString() + ", babyBirth: " + babyBirth;
}
// 잠을 자는 행위, 밥을 먹는 행위
@Override
public void eat() { // 엄마가 밥을 먹으면?
// 몸무게가 기존의 몸무게에 10kg 정도 증가
// super.weight = super.getWeight() + 10;
// 직접 접근 안 되니까 getter 메소드를 이용해 접근
super.setWeight(super.getWeight() + 10);
// 직접 접근 안 되니까 setter 메소드를 이용해 값 입력
// 수정할 몸무게 == 기존 몸무게 + 10
// 건강도는 기존의 건강도에 10 감소
super.setHealth(super.getHealth() - 10);
// 수정할 건강도 == 기존 건강도 - 10
}
@Override
public void sleep() { // 엄마가 잠을 잔다면?
// 건강도를 10 증가
super.setHealth(super.getHealth() + 10);
}
}
Baby 클래스
package com.kh.chap02.abstractAndInterface.part02.family.model.vo;
public class Baby extends Person {
// 필드부
// 추가 필드 없음
// 생성자부
public Baby() {
}
public Baby(String name, double weight, int health) {
super(name, weight, health);
}
// 메소드부
// 추가 필드 없으므로 getter, setter 필요하지 않음
@Override
public String toString() {
return super.toString();
}
@Override
public void eat() { // 아기가 밥을 먹으면?
// 몸무게 3 증가
super.setWeight(super.getWeight() + 3);
// 건강도 1 증가
super.setHealth(super.getHealth() + 1);
}
@Override
public void sleep() { // 아기가 잠을 자면?
// 건강도 3 증가
super.setHealth(super.getHealth() + 3);
}
}
FamilyRun 클래스
package com.kh.chap02.abstractAndInterface.part02.family.run;
import com.kh.chap02.abstractAndInterface.part02.family.model.vo.Baby;
import com.kh.chap02.abstractAndInterface.part02.family.model.vo.Mother;
import com.kh.chap02.abstractAndInterface.part02.family.model.vo.Person;
public class FamilyRun {
public static void main(String[] args) {
/*
// 일반 클래스: 객체 생성이 가능
Person p = new Person();
// 일반 클래스: 다형성 적용 가능
Person p1 = new Mother();
Person p2 = new Baby();
*/
// Person 클래스에 abstract(추상)을 추가하여 추상 클래스로 만들면?
// 추상 클래스: 객체 생성이 불가능
// Person p = new Person(); // Cannot instantiate the type Person
Person p1 = new Mother("김엄마", 50, 70, "출산");
Person p2 = new Baby("박응애", 3.5, 70);
System.out.println(p1); // 엄마: 몸무게 50, 건강도 70
System.out.println(p2); // 애기: 몸무게 3.5, 건강도 70
p1.eat(); // 엄마: 몸무게 + 10, 건강도 - 10
p2.eat(); // 아기: 몸무게 + 3, 건강도 + 1
p1.sleep(); // 엄마: 건강도 + 10
p2.sleep(); // 아기: 건강도 + 3
System.out.println("=== 다음날 ===");
System.out.println(p1);
System.out.println(p2);
}
}
// 출력물
name: 김엄마, weight: 50.0, health: 70, babyBirth: 출산
name: 박응애, weight: 3.5, health: 70
=== 다음날 ===
name: 김엄마, weight: 60.0, health: 70, babyBirth: 출산
name: 박응애, weight: 6.5, health: 74
*인터페이스
모든 필드가 상수 필드이고, 모든 메소드가 추상 메소드인 일종의 추상 클래스
[ 표현법 ]
public interface 인터페이스명 {
}
// 필드부: 상수 필드만 정의되어야 함
// 생성자부: 추상 클래스는 어차피 객체 생성이 불가하므로 생성자부 생략
// 메소드부: 추상 메소드들만 있어야 함
- 인퍼테이스에서 모든 필드는 무조건 상수 필드
- 인터페이스에서 모든 메소드는 무조건 추상 메소드
- 무조건 구현해야 될 것들이 있을 때 인터페이스를 만들어서 상속하게 됨
- 추상 클래스와 다르게 좀 더 강한 규칙성, 강제성을 제공하는 게 인터페이스
*추상 클래스와 인터페이스의 비교
1. 공통점
- 객체 생성은 안 되나 다형성은 적용 가능함
- 상속(구현) 하는 클래스에 추상 메소드를 오버라이딩 하도록 강제함
2. 차이점
- 추상클래스는 클래스 내에 자유롭게 필드, 메소드를 생성 가능하고
추상 메소드가 포함되었거나 포함되지 않더라도 abstract class로 생성하면 추상 클래스로 정의됨
인터페이스는 인터페이스 내에 상수 필드, 추상 메소드로만 구성되어야 함
- 존재하는 목적이 다름
추상 클래스: 공통적인 코드를 모아두고 더 가져다가 기능을 "확장"시켜서 쓰겠다 (클래스명 extends 부모클래스명)
인터페이스: 클래스의 기능 구현을 강제하기 위해서 가져다 쓰겠다 == 적어도 해당 메소드들은 다 가지고 있어야 함
(클래스명 implements 인터페이스명)
=> 즉, 구현을 강제함으로써 구현 객체들의 같은 동작을 보장할 수 있음
3. extends 와 implements
- 클래스 간의 상속관계일 경우: 자식클래스명 extends 부모클래스명 (다중상속 불가)
- 클래스와 인터페이스 간의 구현관계일 경우: 클래스명 implements 인터페이스명, 인터페이스명2, ... (다중구현 가능)
- 인터페이스와 인터페이스 간의 상속관계일 경우: 자식인터페이스명 extends 부모인터페이스명, 부모인터페이스명2, ... (다중구현 가능)
Basic 클래스
package com.kh.chap02.abstractAndInterface.part02.family.model.vo;
// interface: 추상 클래스인데 종특인 애
public interface Basic {
// 필드부: 상수 필드만 선언 가능
/* public static */ final int NUM = 10;
// 인터페이스에는 상수 필드만 정의할 수 있기 때문에
// public static final은 생략 가능함 (묵시적으로 쓰지 않음)
// private int a; // 일반 필드 선언이 불가
// 메소드부: 추상 메소드만 선언 가능
/* public static */ void sleep();
/* public static */ void eat();
// 인터페이스에는 추상 메소드만 정의할 수 있기 때문에
// 앞의 public abstract 은 생략 가능함 (묵시적으로 쓰지 않음)
}
Mother, Baby, FamilyRun 클래스
// Mother 클래스
public class Mother extends Person implements Basic {
// Baby 클래스
// 확장한다 // 구현한다. 라는 뜻
public class Baby extends Person implements Basic {
// FamilyRun 클래스
// 인터페이스 적용 후
// Basic b = new Basic(); // 객체생성이 불가능
Basic b1 = new Mother("김엄마", 50, 70, "출산");
Basic b2 = new Baby("박응애", 3.5, 70);
System.out.println(b1);
System.out.println(b2);
b1.eat(); // 엄마: 몸무게 + 10, 건강도 - 10
b2.eat(); // 아기: 몸무게 + 3, 건강도 + 1
b1.sleep(); // 엄마: 건강도 + 10
b2.sleep(); // 아기: 건강도 + 3
System.out.println("=== 다음날 ===");
System.out.println(b1);
System.out.println(b2);
// 출력물
name: 김엄마, weight: 50.0, health: 70, babyBirth: 출산
name: 박응애, weight: 3.5, health: 70
=== 다음날 ===
name: 김엄마, weight: 60.0, health: 70, babyBirth: 출산
name: 박응애, weight: 6.5, health: 74