본문 바로가기
자바/Java의 정석

[Chapter 07] 객체지향 프로그래밍 II

by 코딩diary 2024. 11. 19.

상속

"기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것"

장점 : 적은 양의 코드로 새로운 클래스를 작성할 수 있고, 코드를 공통적으로 관리할 수 있기 때문에 추가 및 변경에 용이.

코드의 재사용성을 높이고 중복을 제거하여 생산성과 유지보수에 기여함.

class Child extends Parent {
	...
}

 

Child(자식 클래스) -> Parent(부모 클래스)

class Parent { }		// 부모 클래스
class Child extends Parent { }	// Child 클래스가 Parent 클래스를 상속 받음

 

Parent 클래스에 age라는 정수형 변수를 멤버변수로 추가하면, Child 클래스는 모두 상속받기 때문에 Child 클래스에도 자동으로 age 멤버변수가 추가된다.

 

 

 

 

클래스 클래스의 멤버
Parent age
Child age

 

 

반대로 자식 클래스 Child에 멤버변수를 추가하면 부모 클래스에 아무런 영향을 주지 못한다.

 

- 멤버변수만 상송된다. 생성자와 초기화 블럭 X

- 자식 클래스의 멤버 개수는 부모 클래스보다 항상 같거나 많다.

 

부모 클래스로부터 상속받는 자식 클래스가 여러개인 경우 자식 클래스간에는 서로 아무런 관계도 성립하지 않는다.

class Parent { }
class Child1 extends Parent { }
class Child2 extends Parent { }

Child1 -> Parent <- Child2

 

자식 클래스로부터 상속받는 GrandChild라는 새로운 클래스를 추가하면

class Parent { }
class Child1 extends Parent { }
class Child2 extends Parent { }
class GrandChild extends Child1 { }

GrandChild -> Child1 -> Parent <- Child2

 

 

 

 

클래스 클래스의 멤버
Parent age
Child1 age
Child2 age
GrandChild age

 

Parent 클래스에 추가된 멤버변수 age는 Parent 클래스의 자손인 Child1, Child2, GrandChild에서도 추가되며 Parent 클래스에서 삭제되면 모두 삭제된다.

 

단일 상속

자바는 오직 단일 상속만을 허용한다. 두 개 이상의 클래스로부터 상속 받을 수 없다.

class TVCR extends TV, VCR {	// 에러. 부모 클래스는 하나만 허용
	...
}

 

Object 클래스

모든 클래스의 조상 클래스. 마지막 최상위 조상은 Object 클래스.

 

 

오버라이딩

"부모 클래스로부터 상속받은 메서드의 내용을 변경하는 것"

class Point {
	int x;
    int y;
    
    String getLocation() {
    	return "x : " + x + ", y : " + y;
    }
}

class Point3D extends Point {
	int z;
    
    String getLocation() {	// 오버라이딩
    	return "x : " + x + ", y : " + y + ", z : " + z;
    }
}

 

조건

자손 클래스와 오버라이딩하는 메서드는 조상 클래스의 메서드와

- 이름이 같아야 함

- 매개변수가 같아야 함

- 반환타입이 같아야 함

 

 

오버라이딩과 오버로딩

오버로딩 = 기존에 없는 새로운 메서드는 정의

오버라이딩 = 상속받은 메서드의 내용을 변경하는 것

class Parent {
	void parentMethod() { }
}

class Child extends Parent {
	void parentMethod() { }		// 오버라이딩
    void parentMethod(int i) { }	// 오버로딩
    
    void childMethod() { }		
    void childMethod(int i) { }		// 오버로딩
    void childMethod() { }		// <--- 에러. 중복정의

 

 

super

"자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조 변수"

 

thissuper 차이

class Parent {
    String name = "Parent";

    void display() {
        System.out.println("부모 클래스 메서드");
    }
}

class Child extends Parent {
    String name = "Child";

    void display() {
        System.out.println("자식 클래스 메서드");
    }

    void test() {
        System.out.println(this.name);  // Child
        System.out.println(super.name); // Parent
        this.display();                 // 자식 클래스 메서드
        super.display();                // 부모 클래스 메서드
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child();
        c.test();
    }
}

/* 출력 결과
Child
Parent
자식 클래스 메서드
부모 클래스 메서드
*/

 

 

package와 import

패키지 : 클래스의 묶음

package 패키지명;

 

import : 다른 패키지의 클래스를 사용할 때 사용

import 패키지명.클래스명;
	또는
import 패키지명.*;

 

 

제어자

클래스, 변수 또는 메서드의 선언부에 사용되어 부가적인 의미를 부여

 

접근 제어자 : public, protected, default, private

그            외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

 

 

final

변수에 사용되면 값을 변경할 수 없는 상수가 됨. 오버라이딩 불가능.

final class FinalTest {
	final int MAX_SIZE = 10;
    
    final void getMaxSize() {
		final int LV = MAX_SIZE;
        return MAX_SIZE;
    }
}

 

 

abstract

추상 메서드. 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않음.

abstract class AbstractTest {	// 추상 클래스
	abstract void move();	// 추상 메서드
}

 

 

접근 제어자

제어자 같은 클래스 같은 패키지 자손 클래스 전체
public
protected  
(default)    
private      

 

캡슐화. 접근 제어자를 사용하는 이유는 클래스의 내부에 선언된 데이터를 보호하기 위함. 

 

 

다형성

"여러 가지 형태를 가질 수 있는 능력" 자바는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 한다.

조상 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조할 수 있도록 한다는 뜻.

class tv {
	boolean power;
    int channel;
    
    void power() { power = !power; }
    void channelUp() { ++channel; }
    void channelDown() { --channel; }
}

class CaptionTv extends Tv {
	String text;
    void caption() { ... }
}

Tv t = new CaptionTv();	// 조상 타입의 참조변수로 자손 인스턴스를 참조

 

둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

CaptionTv c = new CaptionTv();
TV t = new CaptionTv();

 

반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 불가능. 에러가 발생한다.

CaptionTv c = new Tv();	// 에러 발생

 

 

instanceof 연산자

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용한다.

참조변수 instanceof 타입(클래스명)
// 연산 결과로 true , false 중 하나 반환

// ex
if (c instanceof FireEngine) {
	...
}

instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하는 것을 뜻한다.

 

 

여러 종류의 객체를 배열로 다루기

Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();

// 위 코드를 배열로 처리

Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

 

 

 

추상클래스

클래스는 설계도, 추상클래스는 미완성 설계도.

미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로 인스턴스를 생성할 수 없다.

추상클래스는 상속을 통해서 자손 클래스에 의해서만 완성될 수 있다.

arstract class 클래스이름 {
	...
}

 

추상 메서드

메서드는 선언부와 구현부(몸통)로 구성되어 있다. 

선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다.

/* 주석을 통해 어떤 기능을 수행할 못적으로 작성하였는지 설명 */
abstract 리턴타입 메서드이름();

 

abstract class Player {				// 추상클래스
    abstract void play(int pos);    		// 추상 메서드
    abstract void stop();			// 추상 메서드
}

class AudioPlayer extends Player {
    void play(int pos) {	...	   }	// 추상메서드 구현
    void stop() {	...	  }		// 추상메서드 구현
}

abstract class AbstractPlayer extends Player {
    void play(int pos) {	...    }	//추상메서드 구현
}

 

굳이 abstract를 붙여서 추상메서드로 선언하는 이유는 자손 클래스에서 추상 메서드를 반드시 구현하도록 강요하기 위해서이다.

 

class Marine {
    int x, y;
    void move(int x, int y) {}
    void stop() {}
    void stimPack() {}
}

class Tank {
    int x, y;
    void move(int x, int y) {}
    void stop() {}
    void changeMode() {}
}

class Dropship {
    int x, y;
    void move(int x, int y) {}
    void stop() {}
    void load() {}
    void unload() {}
}

이 코드를 공통 부분을 뽑아내어 하나의 클래스로 만들고, 이 클래스로부터 상속받도록 코드 변경

abstract class Unit {
    int x, y;
    
    abstract void move(int x, int y);
    void stop() {}
}

class Marine extends Unit {
    void move(int x, int y) {}
    void stimPack() {}
}

class Tank extends Unit {
    void move(int x, int y) {}
    void changeMode() {}
}

class Dropship extends Unit {
    void move(int x, int y) {}
    void load() {}
    void unload() {}
}

 

 

 

인터페이스

일종의 추상클래스.추상화 정도가 높아서 몸통을 갖춘 일반 메서드 또는 멤버 변수를 구성원으로 가질 수 없다.

오직 추상메서드상수만을 멤버로 가질 수 있다.

 

추상클래스 = 미완성 설계도

인터페이스 = 기본 설계도

interface 인터페이스 이름 {
	public static final 타입 상수이름 = 값;
    public abstract 메서드이름(매개변수목록);
}

 

인터페이스의 제약사항

- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.

- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.

 

 

인터페이스의 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다.

interface Movable {
    // 지정된 위치(x, y)로 이동하는 기능의 메서드
    void move(int x, int y);
}

interface Attackable {
    // 지정된 대상을(u) 공격하는 기능의 메서드
    void attack(Unit u);
}

interface Fightable extends Movable, Attackable {}

 

 

인터페이스를 구현할 때는 extends 대신 implements를 사용한다

class 클래스이름 implements 인터페이스이름 {
	//
}

class Fighter implements Fightable {
	public void move(int x, int y) { }
    public void attack(Unit u) { }
}

 

구현하려는 인터페이스의 메서드 중 일부만 구현한다면 abstract를 붙인다

abstract class Fighter implements Fightable {
	public void move(int x, int y) {}
}

 

다음과 같이 상속과 구현을 동시에 할 수 있다

class Fighter extends Unit implements Fightable {
	public void move(int x, int y) { }
    public void attack(Unit u) { }
}

 

 

인터페이스의 장점

- 개발시간을 단축시킬 수 있다

- 표준화가 가능하다

- 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다

- 독립적인 프로그래밍이 가능하다