목차
06 상속
6.1 상속
- 기존에 존재하는 클래스로부터 필드와 메소드를 이어받고, 필요한 기능을 추가할 수 있는 기법
- 검증된 소프트웨어를 재사용할 수 있어서 신뢰성 있는 소프트웨어를 쉽게 개발, 유지 보수할 수 있게 해주는 기술
상속의 형식
- extends 키워드를 이용하여 상속을 나타낸다.
- 상속하는 클래스를 부모 클래스(Super Class)라고 하고 상속받는 클래스를 자식 클래스(Sub Class)라고 한다.
- 자식클래스명 extends 부모클래스명
무엇이 상속되는가?
- 자식 클래스는 부모 클래스가 가지고 있는 모든 멤버들을 전부 상속받고 자신이 필요한 멤버를 추가하기 때문에 항상 자식 클래스가 부모 클래스를 포함하게 된다.
- 자식 클래스는 부모 클래스의 필드와 메소드를 마치 자기 것처럼 사용할 수 있다.
왜 상속을 사용하는가?
- 중복되는 코드를 줄일 수 있다.
- 공통 부분은 하나로 정리되어서 관리하기 쉽고, 유지 보수와 변경도 쉬워짐
자바 상속의 특징
- 다중 상속을 지원하지 않는다. (다중 상속 : 여러 개의 클래스로부터 상속받는 것)
- 상속의 횟수에는 제한이 없다.
- 상속 계층 구조의 최상위에는 java.lang.Object 클래스가 있다.
6.2 상속과 접근 지정자
- 부모 클래스는 상속시킬 멤버(private 접근 제어)와 상속시키지 않을 멤버를 지정할 수 있다.
6.3 상속과 생성자
- 왜 자식클래스의 객체를 생성했는데 부모클래스 생성자까지 호출 되는가?
- 생성자의 호출 순서는 부모 클래스 생성자 → 자식 클래스 생성자
명시적인 호출
- super 키워드 사용 (this 처럼 쓰면됨)
묵시적인 호출
- 자식 클래스의 객체가 생성될 때 자동적으로 부모 클래스의 기본 생성자가 호출된다.
오류가 발생하는 경우
- 기본 생성자가 정의되어 있지 않으면 오류가 발생한다.
부모 클래스의 생성자 선택
- 생성자 오버로딩 되어 있는 경우 선택해서 호출 가능
6.4 메소드 오버라이딩
메소드 오버라이딩이란?
- 자식 클래스가 부모 클래스의 메소드를 자신의 필요에 맞추어서 재정의하는 것.
- 여러 오류를 방지하기 위해서 @Override 어노테이션을 붙이는 것이 좋다.
(부모 클래스에 그런 메소드가 없다면 컴파일 오류)
키워드 super를 사용하여 부모 클래스 멤버 접근
- super를 사용해서 부모 클래스의 메소드를 호출할 수 있다.
오버라이딩 vs 오버로딩
- 오버로딩은 컴파일 시점에서의 다형성을 지원
- 오버라이딩은 런타임 시점에서의 다형성을 지원
- 동적 바인딩
정적 메소드를 오버라이드하면 어떻게 될까?
- 자식 클래스가 부모 클래스의 정적 메소드와 동일한 정적 메소드를 정의하는 경우
자식 클래스의 메소드는 부모 클래스의 메소드를 숨긴다고 말한다.
package basic.practice;
class Animal{
public static void A() {
System.out.println("static method in Animal");
}
public void B() {
System.out.println("method in Animal");
}
}
public class Dog extends Animal {
public static void A() {
System.out.println("static method in Dog");
}
public void B() {
System.out.println("method in Dog");
}
public static void main(String[] args) {
Dog dog = new Dog();
Animal a = dog;
a.A();
dog.A();
a.B();
dog.B();
}
}
static method in Animal
static method in Dog
method in Dog
method in Dog
- 정적 메소드와 정적 메소드가 아닌 메소드의 차이를 확인 (이클립스에서도 정적 메소드는 오버라이딩 표시 되지않음)
6.5 다형성
: 하나의 식별자로 여러 개의 작업을 처리하는 것
: 객체 지향 프로그래밍에서는 객체들이 똑같은 메시지를 받더라도 각자의 실제 타입에 따라서 서로 다른 동작을 하는 것
- 메시지를 보내는 측에서는 객체가 어떤 타입인지 알 필요가 없다
업캐스팅
- 부모 클래스 변수로 자식 클래스 객체를 참조하는 것
- 어떤 멤버를 사용할 수 있느냐는 변수의 타입에 의하여 결정된다
(컴파일 시점에서는 자식 클래스에 뭐가 추가 되었는지 알 수가 없음)
업캐스팅 vs 다운캐스팅
- 업캐스팅과 다운캐스팅은 일종의 형변환
- 업캐스팅 : 자식 객체를 부모 참조 변수로 참조하는 것. 묵시적으로 수행될 수 있다. 부모 클래스의 멤버 접근 가능. 자식 클래스 멤버 접근 불가능
- 다운캐스팅 : 부모 객체를 자식 참조 변수로 참조하는 것. 묵시적으로 안되고 명시적으로 해야함
동적바인딩
- 부모 참조 변수를 가지고 자식 객체를 참조하는 것이 도대체 어디에 필요한가?
- 부모 클래스 배열로 변수를 선언하고 거기에 자식 객체를 참조하면 편리함
- 바인딩 : 메소드 호출을 실제 메소드의 몸체와 연결하는 것
- 동적 바인딩 : 실행 단계에서 변수가 참조하는 객체의 실제 타입을 보고 적절한 메소드를 호출
동적바인딩의 장점
- 시스템에 최소한의 영향을 미치면서 새로운 유형의 객체를 쉽게 추가하여 시스템을 확장할 수 있다.
업캐스팅의 활용
- 메소드의 매개 변수를 부모 타입으로 선언하면 넓은 범위의 객체를 받을 수 있다.
- Object obj 를 매개변수로 선언하면 모든 객체를 받을 수 있음
instanceof 연산자
- 변수가 가리키는 객체의 실제 타입을 알고 싶을 때 사용. true false 반환
종단 클래스와 종단 메소드
- 종단 클래스(final class) : 상속 시킬 수 없는 클래스
- 자바에서는 이론상 중요한 클래스의 서브 클래스를 만들어 서브 클래스로 하여금 시스템을 파괴하도록 할 수 있다.
- 대표적으로 String 클래스
- 일반 클래스에서 특정 메소드만 재정의될 수 없게 만드려면 final로 선언
- 대개 생성자에서 호출되는 메소드들은 일반적으로 final로 선언된다.
6.6 상속 vs 구성
- 구성(Composition) : 클래스가 다른 클래스의 인스턴스를 클래스의 필드로 가지는 디자인 기법 (생성자 포함)
- 상속 관계에서 부모 클래스를 변경하면 코드가 손상될 위험이 있다. (좀 더 밀접함)
- 구성은 좀 더 느슨함
- 상속 : 컴파일 시간에 결정됨, final로 선언된 클래스의 코드를 재사용할 수 없다. public, protected 메소드를 모두 노출
- 구성 : 런타임 시간에 결정됨, final로 선언된 클래스에서도 코드 재사용이 가능하다. 노출 X, 공개 인터페이스만 사용하여 상호작용
is-a 관계
- Car is a Vehicle, 사자는 동물이다.
- 자식 클래스는 부모 클래스의 특수 버전이다.
- 무분별하게 상속을 사용하는 것을 주의하자
has-a 관계
- Library has a book, 자동차는 엔진을 가지고 있다.
구성 vs 집합
- 구성 : House에는 하나 이상의 Room이 있다. Room은 House 없이는 존재하지 않으므로, Room의 수명은 House에 의해 제어됨
- 집합 : Block을 모아서 ToyHouse를 만들 수 있다. ToyHouse가 분해되더라도 Block들은 남는다.
잘못 사용하는 경우
import java.util.ArrayList;
public class Test extends ArrayList<Object> {
public static void main(String[] argv) {
Test obj = new Test();
obj.add("Kim");
obj.add("Park");
}
}
변경
import java.util.ArrayList;
public class Test {
static ArrayList<String> list = new ArrayList<String>();
public static void main(String[] argv) {
list.add("Kim");
list.add("Park");
}
}
상속 vs 구성
- 상속에서는 메소드 오버라이딩 기능을 사용하여 부모 클래스의 메소드를 수정할 수 있다.
- 상속은 내부 구조를 하위 클래스에 노출한다. 만약 구성을 사용하게 되면 객체는 캡슐화된 상태로 유지된다.
- 구성이 더 간단한 경우가 많다
- 구성은 구현 독립성을 허용한다.
Mini Project 황금 획득 게임
텍스트 기반 게임을 작성해보자
주인공을 움직여서 몬스터를 피하고 황금을 차지 하는 게임
화면에는 3가지 스프라이트가 나타남
주인공은 @ // 몬스터는 M // 황금은 G // WASD로 상하좌우
더보기
package practice.goldgame;
public abstract class Sprite {
final String UP = "w";
final String LEFT = "a";
final String RIGHT = "d";
final String DOWN = "s";
int x;
int y;
String image;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
abstract void move(String c);
}
package practice.goldgame;
public class Hero extends Sprite{
int hp;
public Hero() {
x = (int)(Math.random() * 4 + 1);
y = (int)(Math.random() * 4 + 1);
hp = 10;
image = "@ ";
}
boolean getGold(Gold gold) {
if (this.x == gold.getX() && this.y == gold.getY()) {
System.out.println("황금 획득!!");
return false;
} else {
return true;
}
}
boolean ifDie() {
if (hp == 0) {
return false;
} else {
return true;
}
}
void fightMonster(Monster monster) {
if (this.x == monster.getX() && this.y == monster.getY()) {
hp = 0;
}
}
@Override
void move(String c) {
switch (c) {
case UP :
if (y == 0) {
System.out.println("더이상 위로 갈수 없음");
break;
} else {
y--;
break;
}
case LEFT :
if (x == 0) {
System.out.println("더이상 왼쪽으로 갈수 없음");
break;
} else {
x--;
break;
}
case RIGHT :
if (x == 9) {
System.out.println("더이상 오른쪽으로 갈수 없음");
break;
} else {
x++;
break;
}
case DOWN :
if (y == 9) {
System.out.println("더이상 아래쪽으로 갈수 없음");
break;
} else {
y++;
break;
}
}
}
}
package practice.goldgame;
public class Monster extends Sprite{
public Monster() {
x = (int)(Math.random() * 4 + 5);
y = (int)(Math.random() * 4 + 5);
image = "M ";
}
public String direction() {
int direction = (int)(Math.random() * 4);
if (direction == 0) return UP;
else if (direction == 1) return LEFT;
else if (direction == 2) return RIGHT;
else return DOWN;
}
@Override
void move(String c) {
switch (c) {
case UP :
if (y <= 1) {
y++;
break;
} else {
y -= 2;
break;
}
case LEFT :
if (x <= 1) {
x++;
break;
} else {
x -= 2;
break;
}
case RIGHT :
if (x >= 8) {
x--;
break;
} else {
x += 2;
break;
}
case DOWN :
if (y >= 8) {
y--;
break;
} else {
y += 2;
break;
}
}
}
}
package practice.goldgame;
public class Gold extends Sprite{
public Gold() {
image = "G ";
x = (int)(Math.random() * 9);
y = (int)(Math.random() * 9);
}
@Override
void move(String c) {
System.out.println("움직이지 않음");
}
}
package practice.goldgame;
public class Field {
final int WIDTH = 10;
final int HEIGHT = 10;
String[][] field;
public Field() {
field = new String[WIDTH][HEIGHT];
}
public void showField() {
System.out.println("# # # # # # # # # # # #");
for (int i = 0; i < field.length; i++) {
System.out.print("# ");
for (int j = 0; j < field[i].length; j++) {
if (field[i][j] == null) {
field[i][j] = " ";
}
System.out.print(field[i][j]);
}
System.out.println("#");
}
System.out.println("# # # # # # # # # # # #");
}
}
package practice.goldgame;
import java.util.Scanner;
public class GoldGameTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Field field = new Field();
Hero hero = new Hero();
Monster monster = new Monster();
Gold gold = new Gold();
final String HERO = hero.image;
final String MONSTER = monster.image;
final String GOLD = gold.image;
boolean flag = true;
while (flag) {
field.field[hero.y][hero.x] = hero.image;
field.field[monster.y][monster.x] = monster.image;
field.field[gold.y][gold.x] = gold.image;
field.showField();
field.field[hero.y][hero.x] = null;
field.field[monster.y][monster.x] = null;
System.out.print("WASD입력으로 움직이세요 : ");
String choice = sc.nextLine();
hero.move(choice);
flag = hero.getGold(gold);
monster.move(monster.direction());
hero.fightMonster(monster);
hero.ifDie();
}
sc.close();
}
}
더보기
# # # # # # # # # # # #
# #
# #
# G #
# #
# @ #
# M #
# #
# #
# #
# #
# # # # # # # # # # # #
WASD입력으로 움직이세요 : w
# # # # # # # # # # # #
# #
# #
# G #
# @ #
# #
# M #
# #
# #
# #
# #
# # # # # # # # # # # #
WASD입력으로 움직이세요 : w
# # # # # # # # # # # #
# #
# #
# @ G #
# #
# #
# M #
# #
# #
# #
# #
# # # # # # # # # # # #
WASD입력으로 움직이세요 : d
# # # # # # # # # # # #
# #
# #
# @ G #
# #
# #
# M #
# #
# #
# #
# #
# # # # # # # # # # # #
WASD입력으로 움직이세요 : d
# # # # # # # # # # # #
# #
# #
# @ G #
# M #
# #
# #
# #
# #
# #
# #
# # # # # # # # # # # #
WASD입력으로 움직이세요 : d
# # # # # # # # # # # #
# #
# M #
# @ G #
# #
# #
# #
# #
# #
# #
# #
# # # # # # # # # # # #
WASD입력으로 움직이세요 : d
# # # # # # # # # # # #
# #
# M #
# @ G #
# #
# #
# #
# #
# #
# #
# #
# # # # # # # # # # # #
WASD입력으로 움직이세요 : d
황금 획득!!
Summary
- 상속(inheritance)은 기존에 존재하는 클래스로부터 필드와 메소드를 이어받고, 필요한 기능을 추가할 수 있는 기법이다.
- 상속을 이용하면 여러 클래스에 공통적인 코드들을 하나의 클래스로 모을 수 있어서 코드의 중복을 줄일 수 있다.
- 자바에서는 extends 키워드를 이용하여 상속을 나타낸다. 상속하는 클래스를 부모 클래스(슈퍼 클래스)라고 하고 상속받는 클래스를 자식 클래스(서브 클래스)라고 한다.
- 자식 클래스는 부모 클래스의 public 멤버, protected 멤버, 디폴트 멤버(부모 클래스와 자식 클래스가 같은 패키지에 있다면)를 상속받는다.
- (부모 클래스의 생성자) → (자식 클래스의 생성자) 순으로 호출된다.
- 메소드 오버라이딩은 자식 클래스가 부모 클래스의 메소드를 자신의 필요에 맞추어서 재정의하는 것이다.
- 오버로딩이란 같은 메소드명을 가진 여러 개의 메소드를 작성하는 것이다. 오버라이딩은 부모 클래스의 메소드를 자식 클래스가 다시 정의하는 것을 의미한다.
- 다형성은 "많은 + 모양" 이라는 의미로써 주로 프로그래밍 언어에서 하나의 식별자로 여러 개의 작업을 처리하는 것을 의미한다.
- 부모 클래스 변수로 자식 클래스 객체를 참조할 수 있다. 이것을 업캐스팅이라고 한다.
- 자바에서는 메소드 바인딩이 실행 시까지 연기된다. JVM은 실행 단계에서 객체의 실제 타입을 보고 적절한 메소드를 호출하게 된다. 동적 바인딩
- 추상 클래스는 완전하게 구현되어 있지 않은 메소드를 가지고 있는 클래스를 의미한다.
- 추상 메소드로만 이루어진 클래스를 인터페이스라고 한다. 인터페이스는 다른 클래스에 의하여 구현될 수 있다. 인터페이스를 구현한다는 말은 인터페이스에 정의된 추상 메소드의 몸체를 정의한다는 의미이다.
- 구성은 클래스가 다른 클래스의 인스턴스를 클래스의 필드로 가지는 디자인 기법이다. 반면에 상속은 한 객체가 클래스를 상속받아서 부모 객체의 속성과 동작을 획득할 수 있는 기법이다. 구성 및 상속은 모두 클래스를 연결하여 코드 재사용성을 제공한다.
'Java > 교재 정리' 카테고리의 다른 글
08 자바 API 패키지, 예외 처리, 모듈 (0) | 2024.04.27 |
---|---|
07 추상 클래스, 인터페이스, 중첩 클래스 (1) | 2024.04.26 |
05 클래스와 객체 II (0) | 2024.04.18 |
04 클래스와 객체 I (0) | 2024.04.15 |
03 조건문, 반복문, 배열 (0) | 2024.04.14 |