1만 줄 짜리 클래스 A가 있다. A 클래스에 딱 1줄만 추가하여 새로운 B 클래스를 만들려 한다. 1만 줄을 복붙하려니 뭔가 억울하다.. 좋은 방법 없을까?
상속이란
상속 적용 예
상속의 장점
Inheritance
inheritance >> 상속이란, 기존 클래스를 확장하여 새 클래스를 만드는 것이다.
Inheritance example
RPG 게임에서 사용할 두 클래스. Novice와 Wizard가 있다. Novice가 전직하여 Wizard가 되는 시나리오다. Wizard가 되면 마나가 생기고, 파어어볼을 사용할 수 있다.
막상 코드로 작성하고 보니 중복되는 코드가 많다.
extends키워드를 사용하여, Wizard가 Novice를 상속받게 했다. 이를 통해 중복 코드를 줄일 수 있다.
Inheritance advantage
상속을 사용하면 중복 코드를 제거할 수 있고, 또 프로그램을 더 손쉽게 확장할 수 있다.
inheritance >> 상속은 기존 클래스(A)를 확장하여 새로운 클래스(B)를 만드는 것입니다. 이를 위해 extends 키워드를 사용합니다. 이렇게 확장된 클래스는, 기존 클래스의 모든 속성을 물려받게 됩니다.
// 기존 클래스 class A { ... } // 확장된 새로운 클래스 class B extends A { ... }
example code >> 아래는 상속 코드의 예입니다.
위 코드의 MotorBicycle은 Bicycle의 기본 속성(이름, 페달 이동)을 기반으로, 추가 속성(배터리, 모터 이동)을 가집니다. 따라서 아래와 같은 코드 사용이 가능합니다.// 객체 생성 MotorBicycle mb = new MotorBicycle(); // 필드 초기화 mb.name = "씽씽이"; mb.battery = 500; // 메소드 호출 mb.pedal(); // 페달로 이동~ mb.motor(); // 모터로 이동!!
CODE
public class RPGTest { public static void main(String[] args) { // 객체 생성 및 초기화 Novice novice = new Novice(); novice.name = "프로도"; novice.hp = 100; Wizard wizard = new Wizard(); wizard.name = "해리포터"; wizard.hp = 120; wizard.mp = 100; // 두 객체의 펀치 novice.punch(); wizard.punch(); // 위자드 객체의 파이어볼 wizard.fireball(); } } class Novice { String name; int hp; public void punch() { System.out.printf("[%s]의 펀치!!\n", name); } } /* 1. Wizard 클래스를 Novice 클래스로부터 확장하세요. */ class Wizard extends Novice{ /* 2. mp 필드를 추가하세요. */ int mp; /* 3. fireball() 메소드를 만드세요. */ public void fireball(){ System.out.printf("[%s]의 파이어볼@@", name); } }
advatage of inheritance >> 서로 다른 두 클래스에 중복 속성을 상속을 통해 제거할 수 있습니다.
example >> 아래는 중복 코드가 존재하는 두 클래스(Product, Pants)의 예입니다.// 상품 클래스 class Product { String name; // 이름 int price; // 가격 // 가격 출력 void printPrice() { System.out.printf("%d원 입니다.\n", price); } } // 바지 클래스 class Pants { String name; // 이름 int price; // 가격 int waistSize; // 허리둘레 // 가격 출력 void printPrice() { System.out.printf("%d원 입니다.\n", price); } // 허리둘레 출력 void printWaistSize() { System.out.printf("허리둘레: %d인치\n", waistSize); } }
위의 클래스를 상속을 통해 새롭게 정의하면 다음과 같습니다. 먼저 근간이 되는 상품 클래스를 그대로 둡니다.
// 상품 클래스 class Product { String name; // 이름 int price; // 가격 // 가격 출력 void printPrice() { System.out.printf("%d원 입니다.\n", price); } }
다음으로 Pants 클래스를 정의하는데, 이때 Product 클래스를 기반으로 확장합니다. 다음 코드를 보시면, 중복 코드가 extends를 통해 제거되었음을 알 수 있습니다.
// 바지 클래스 class Pants extends Product { int waistSize; // 허리둘레 // 허리둘레 출력 void printWaistSize() { System.out.printf("허리둘레: %d인치\n", waistSize); } }
parents class and child class >> 이러한 상속 관계에서, 근간이 되는 클래스를 부모(Parent) 클래스라 부릅니다. 또, 부모 클래스를 통해 확장된 클래스는 자식(Child) 클래스라 합니다.
// 부모 클래스 class Parent { ... } // 자식 클래스 - 부모 클래스로부터 파생 class Child extends Parent { ... }
CODE
public class InheritanceTest { public static void main(String[] args) { // 객체 생성 Pet dog = new Pet(); /* 4. 객체를 초기화하세요. */ dog.name = "차우차우"; dog.age = 3; dog.price = 2200000; // 정보 출력 System.out.printf("Pet { name: %s, age: %d세, price: %d원 }", dog.name, dog.age, dog.price); } } class Animal { String name; // 이름 public void cry() { System.out.println("동물이 웁니다!"); } } /* 1. Pet 클래스를 Animal로부터 확장 선언 하세요. */ class Pet extends Animal{ /* 2. 부모 클래스 Animal과 중복되는 필드를 제거하세요. */ int age; // 나이 int price; // 가격 /* 3. 부모 클래스 Animal과 중복되는 메소드를 지우세요. */ public void play() { System.out.println("애완 동물이 장난을 칩니다~"); } }
upcasting >> 업 캐스팅이란, 자식 객체를 부모의 타입으로 해석하는 것입니다. 예를 들어 아래와 같은 상속 관계가 있을 때,
class Animal { ... } class Cat extends Animal { ... } class Dog extends Animal { ... } class Horse extends Animal { ... }
Cat의 인스턴스(객체)는, Animal로 해석 될 수 있습니다.
// 고양이 객체 생성 Cat c = new Cat(); // 고양이는 동물이다(O) Animal a = c; // 고양이 객체를 동물로 해석
하지만 위 내용의 역은 성립하지 않음을 주의하세요.
// 동물 객체 생성 Animal aaa = new Animal(); // 동물은 고양이다(X) Cat ccc = aaa; // ERROR!
grouping from upcasting >> 이러한 업 캐스팅은, 다양한 객체들을 부모의 타입으로 관리할 수 있게 합니다.
Animal c = new Cat(); // 고양이는 동물이다 Animal d = new Dog(); // 개는 동물이다 Animal h = new Horse(); // 말은 동물이다 // 동물 배열 - 고양이, 개, 말 Animal[] animals = { c, d, h };
CODE
public class UpCasting { public static void main(String[] args) { // 기사 객체 생성 및 초기화 Knight knight = new Knight(); knight.name = "아서스"; knight.hp = 180; knight.strength = 50; // 도적 객체 생성 및 초기화 Thief thief = new Thief(); thief.name = "발리라"; thief.hp = 120; thief.agility = 40; // 업 캐스팅 - 부모 타입으로 객체를 해석 Adventurer adv0 = knight; Adventurer adv1 = thief; // 모험가들의 배열 생성 Adventurer[] advs = { adv0, adv1 }; // 모든 모험가의 정보 출력 for (int i = 0; i < advs.length; i++) { System.out.println(advs[i].toString()); } } } /* 1. 부모 클래스 Adventurer를 만드세요. */ class Adventurer { /* 1.1 공통 필드를 선언하세요. */ String name; int hp; /* 1.2 공통 메소드를 작성하세요. */ public String toString() { return String.format("[%s] HP: %d", name, hp); } public void punch(){ System.out.printf("[%s]의 펀치!!\n", name); } } /* 2. Knight를 Adventurer의 자식 클래스로 정의하세요. */ class Knight extends Adventurer{ /* 2.1 부모와 중복된 필드를 제거하세요. */ int strength; // 힘 /* 2.2 부모와 중복된 메소드를 제거하세요. */ public void berserker() { System.out.println("체력과 공격력이 증가합니다."); } } \ /* 3. Thief를 Adventurer의 자식 클래스로 정의하세요. */ class Thief extends Adventurer{ /* 3.1 부모와 중복된 필드를 제거하세요. */ int agility; // 민첩 /* 3.2 부모와 중복된 메소드를 제거하세요. */ public void sharpen() { System.out.println("크리티컬 확률이 증가합니다."); } }
method overriding >> 메소드 오버라이딩(overriding)이란, 부모의 메소드를 자식 클래스에서 재정의하는 것입니다.
example >> 가령 부모 클래스 Wizard의 파이어볼은 데미지가 10일 때, 이를 물려받은 자식 클래스 GreatWizard에서는 30 데미지를 주고 싶다면 어떻게 해야 할까요?class Wizard { public void fireball() { System.out.println("10 데미지를 줍니다"); } }
이때 사용하는 개념이 메소드 오버라이딩입니다. 아래의 GreateWizard 클래스는 부모 Wizard의 fireball() 메소드를 새롭게 재정의합니다.
class GreatWizard extends Wizard { // 메소드 오버라이딩(재정의) public void fireball() { System.out.println("30 데미지를 줍니다"); } }
메소드를 재정의함으로써, GreatWizard 객체의 파이어볼 데미지가 상승하였습니다
Wizard w = new Wizard(); w.fireball(); // 10 데미지를 줍니다 GreatWizard gw = new GreatWizard(); gw.fireball(); // 30 데미지를 줍니다
CODE
public class Overriding { public static void main(String[] args) { // 객체 생성 및 초기화 - 정사각형 Square s = new Square(); s.name = "정사각형"; s.length = 5; // 객체 생성 및 초기화 - 삼각형 Triangle t = new Triangle(); t.name = "삼각형"; t.base = 4; t.height = 3; // 객체 생성 및 초기화 - 원 Circle c = new Circle(); c.name = "원"; c.radius = 4; // 업 캐스팅 - 도형 배열에 정사각형, 삼각형, 원 담기 Shape[] shapes = { s, t, c }; // 모든 도형의 넓이 계산 및 출력 for (int i = 0; i < shapes.length; i++) { Shape tmp = shapes[i]; System.out.printf("%s의 넓이 -> %.2f\n", tmp.name, tmp.area()); } } } // 도형 class Shape { String name; // 도형의 넓이를 반환 public double area() { return 0; } } // 정사각형 class Square extends Shape { int length; // 한 변의 길이 /* 1. 정사각형 넓이를 구하도록 area()를 재정의하세요. */ public double area(){ return length * length; } } // 삼각형 class Triangle extends Shape { int base; // 밑변 int height; // 높이 /* 2. 삼각형 넓이를 구하도록 area()를 재정의하세요. */ public double area(){ return base * height / 2; } } // 원 class Circle extends Shape { int radius; // 반지름 /* 3. 원의 넓이를 구하도록 area()를 재정의하세요. */ public double area(){ return radius * radius * Math.PI; } }
protected >> 우리는 이전시간 접근 제한자를 배웠었습니다.
이들 중 protected는 상속 관계의 클래스까지 접근을 허용합니다. 따라서 아래 코드의 필드 name은 protected 선언되었으므로, B에서 직접 사용할 수 있습니다.class A { protected String name; } class B extends A { public void printName() { // 부모클래스 A의 필드 name을 출력 System.out.println(name); } }
CODE
public class ProtectedFields { public static void main(String[] args) { // 객체 생성 Basketball b = new Basketball(); Soccer s = new Soccer(); PingPong p = new PingPong(); // 객체 필드 초기화 b.name = "농구"; s.name = "축구"; p.name = "탁구"; // 부모 타입(업 캐스팅)으로 배열 생성 Sports[] sportsArr = { b, s, p }; // 모든 운동 설명 출력 for (int i = 0; i < sportsArr.length; i++) { Sports tmp = sportsArr[i]; tmp.description(); } } } // 운동 class Sports { /* 1. 상속 관계 및 동일 패키지내에서 해당 필드를 자유로이 쓰게 하세요. */ protected String name; public void description() { System.out.printf("[%s]는 여가/경기/체력 단련 등을 위한 신체 운동입니다.\n", name); } } // 농구 class Basketball extends Sports { /* 2. 메소드 오버라이딩(재정의)을 통해 농구을 설명해주세요. */ public void description() { System.out.printf("[%s]는 손으로 공을 던져 골을 넣는 운동이다.\n", name); } } // 축구 class Soccer extends Sports { /* 3. 메소드 오버라이딩(재정의)을 통해 축구을 설명해주세요. */ public void description() { System.out.printf("[%s]는 주로 발로 공을 차 넣는 운동이다.\n", name); } } // 탁구 class PingPong extends Sports { /* 4. 메소드 오버라이딩(재정의)을 통해 탁구을 설명해주세요. */ public void description() { System.out.printf("[%s]는 공을 번갈아가며 주고 받는 운동이다.\n", name); } }
1st parent class and 2nd chil class >> 자식 객체를 생성과 동시에 초기화하려면, 먼저 부모의 생성자가 호출돼야만 합니다. 이를 위한 키워드! 바로 super 되겠습니다.
// 생성자 호출 영역 Wizard w = new Wizard("프로도", 100, 80); // 생성자 정의 영역 class Novice { protected String name; protected int hp; public Novice(String name, int hp) { this.name = name; this.hp = hp; } } class Wizard extends Novice { protected int mp public Wizard(String name, int hp, int mp) { super(name, hp); // 부모 클래스 생성자 호출 this.mp = mp; } }
CODE
public class SuperTest { public static void main(String[] args) { /* 1. Orc 객체를 만들고 정보를 출력하시오. */ Orc a = new Orc("오크", 80); System.out.println(a.toString()); /* 2. OrcWarrior 객체를 만들고 정보를 출력하시오. */ OrcWarrior b = new OrcWarrior("오크전사", 120, 3); System.out.println(b.toString()); } } class Orc { protected String name; protected int hp; public Orc(String name, int hp) { this.name = name; this.hp = hp; } public String toString() { return String.format("Orc { name: %s, hp: %d }", this.name, this.hp); } } class OrcWarrior extends Orc { protected int amor; public OrcWarrior(String name, int hp, int amor) { super(name, hp); this.amor = amor; } // 메소드 오버라이딩! public String toString() { return String.format("OrcWarrior { name: %s, hp: %d, amor: %d }", super.name, super.hp, this.amor); } }
CODE
import java.util.ArrayList; public class ElvesTest { public static void main(String[] args) { // 객체 생성 -> up casting Elf a = new Elf("티란데", 100); Elf b = new HighElf("말퓨리온", 160, 100); Elf c = new ElfLord("마이에브", 230, 140, 100); // 객체 배열 생성 //Elf[] elves = { a,b,c }; // arraylist ArrayList<Elf> list = new ArrayList<Elf>(); list.add(a); list.add(b); list.add(c); // 모든 객체 정보 출력 for (int i = 0; i < list.size(); i++) { //System.out.println(elves[i].toString()); System.out.println(list.get(i).toString()); } } } class Elf { /* 1. 상속을 위한 접근 제한자를 사용하세요. */ protected String name; protected int hp; public Elf(String name, int hp) { this.name = name; this.hp = hp; } public String toString() { return String.format("[엘프] Name: %s, HP: %d", name, hp); } } class HighElf extends Elf { protected int mp; public HighElf(String name, int hp, int mp) { super(name, hp); this.mp = mp; } public String toString() { return String.format("[하이엘프] Name: %s, HP: %d, MP: %d", super.name, super.hp, this.mp); } } class ElfLord extends HighElf { protected int shield; public ElfLord(String name, int hp, int mp, int shield) { /* 2. 부모의 생성자를 호출하세요. */ super(name, hp, mp); this.shield = shield; } /* 3. toString() 메소드를 오버라이딩(재정의) 하세요. */ public String toString() { return String.format("[엘프로드] Name: %s, HP: %d, MP: %d, SH: %d", super.name, super.hp, super.mp, this.shield); } }