06 상속

목차

    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 목차로 돌아가기