[Java의 정석] 7장 객체지향 프로그래밍 2

이정규·2022년 3월 21일
0

Java의 정석

목록 보기
6/7

상속

위 그림과 같이 부모 클래스의 멤버는 자식 클래스가 그대로 물려받게 된다.
자식 클래스의 멤버는 부모 클래스에 영향을 끼치지 못한다.

  • 모든 객체의 조상은 Object 클래스이다.
  • Java는 다중상속을 허용하지 않는다. 단일상속만 가능하다.

다중상속을 허용하지 않는 이유

부모 클래스가 2개인데, 각 클래스에 메소드의 이름이 겹치는 상황을 생각해보자.
그러면 누구의 메소드를 상속받을 것인지 정해야 한다.
이러한 다중상속의 문제점으로 단일 상속만을 허용한다. (그런데 C++에서는 다중상속을 허용한다.)

상속 구현 방법

위 그림과 같이 부모 클래스를 확장시킨다. 라는 뜻에서 자식 클래스에 extends를 붙이면 된다.

class Child extends Parent {
	...
}

포함관계, 상속관계

포함관계

"has a" 를 넣어서 문장이 성립하면 포함관계이다.
ex) Circle has a point. (원은 점을 가지고 있다.)

class Point {
	int x;
    int y;
}
class Circle {
	Point c = new Point(); // 원점
    int r; // 반지름
}

상속 관계

"is a" 를 넣어서 문장이 성립하면 상속관계이다.
ex) Circle is a shape. (원은 도형이다.)

class Shape {
	String color = "black";
}
class Circle extends Shape {
	...
}

Overriding

"~위에 덮어쓰다." 라는 뜻이다.
말 그대로 조상 클래스로부터 상속받은 기존의 메소드의 내용을 변경하는 것을 말한다.

조건

조상 클래스의 메소드와

  1. 이름이 같아야 한다.
  2. 매개변수가 같아야 한다.
  3. 반환타입이 같아야 한다.

=> 즉 선언부가 서로 일치해야 한다.

접근 제어자와 예외

접근 제어자와 예외는 다음과 같은 조건에서는 변경이 가능하다.

  1. 접근 제어자는 조상 클래스의 메소드보다 좁은 범위로 변경할 수 없다.
  2. 조상 클래스의 메소드보다 많은 수의 예외를 선언할 수 없다.
  3. 인스턴스 메소드를 static 메소드로 또는 그 반대로 변경할 수 없다.
  • 접근 제어자
public > protected > default(생략 가능) > private

static 메소드를 자손 클래스에서 똑같은 이름의 static메서드로 정의하는 건 오버라이딩인가요?

  • 아니다. 별개의 static 메소드를 정의한 것일 뿐, 오버라이딩이 아니다.

Overloading

기존에 없는 새로운 메소드를 추가하는 것이다.

같은 메소드이름이여도 매개변수가 다르거나 반환타입이 다른 경우를 말한다.


super

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

class Parent {
	int x = 10;
}

class Child extends Parent{
	int x = 20;
    void method() {
    	System.out.println(super.x);  // 10
        System.out.println(this.x);   // 20
    }
}

만약 super() 이렇게 사용한다면 조상 클래스의 생성자를 부른다는 뜻이다.

Object 클래스를 제외한 클래스의 생성자 첫 줄에는 super();가 붙어있다. 안 붙이면 컴파일러가 알아서 붙여준다.

그런데 만일 부모 클래스에 기본 생성자가 아닌 생성자가 들어가있다면 자식 클래스에 부모 클래스의 생성자를 붙여줘야 한다.

class Parent {
    int x = 10;

    public Parent(int x) {
        this.x = x;
    }
}

class Child extends Parent {
    int x = 20;

    public Child(int x) {
        // super(10); 없으면 컴파일 에러!
        this.x = x;
    }
    public void method() {
        System.out.println(this.x);
        System.out.println(super.x);
    }
}
public class Test {
    public static void main(String[] args) {
        Child c = new Child(20);
        c.method();
    }
}

조상 클래스의 멤버변수는 이처럼 조상의 생성자에 의해 초기화되도록 해야 하는 것이다.


package

패키지란 클래스 또는 인터페이스의 묶음이다.
클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리인 것이다.

import문

다른 패키지의 클래스를 사용할 때 사용한다.

  • import문은 프로그램 성능에 전혀 영향을 끼치지 않는다. 컴파일시 시간이 걸릴 뿐이다.

static import 문

static member를 호출할 때 클래스 이름을 생략할 수 있게 해준다.

import static java.lang.System.out;
out.println("print");
// System.out.println("print");

제어자

접근 제어자

  • public
    접근 제한이 전혀 없다.
  • protected
    같은 패키지 내에서, 그리고 다른 패키지의 자식 클래스에서 접근이 가능하다.
  • default (생략 가능)
    같은 패키지 내에서만 접근이 가능하다.
  • private
    같은 클래스 내에서만 접근이 가능하다.

그 외

static, final, abstract, native, transient, synchronized, volatile, strictfp

생성자의 접근 제어자

class Singleton {
	private static Singleton s = new Singleton();
    
    private Singleton() {
    	...
    }
    
    public static Singleton getInstance() { // 직접 인스턴스를 생성 못 하니 static을 붙여야함.
    	return s
    }
}

private를 붙이게 되면 같은 클래스내에서밖에 쓰지 못 하니 직접 인스턴스를 생성할 수 없다.
또한, 다른 클래스의 조상이 될 수 없다.
자식 클래스의 인스턴스를 생성할 때 조상 클래스의 생성자가 실행되어야 하는데 접근 제어자가 private이기 때문이다.

public 메소드를 통해 접근하게 만듬으로써 인스턴스의 개수를 제한할 수 있다.

static

인스턴스 멤버를 사용하지 않는 메소드는 static 메소드로 만들자.
그러면 인스턴스를 생성하지 않아도 되어 편리하고 속도도 더 빠르다.

final

  • 변수에 사용 : 변경될 수 없는 값
  • 메소드에 사용 : 오버라이딩 불가
  • 클래스에 사용 : 해당 클래스는 확장 불가 (자식 클래스에 정의하지 못한다.)

주의 사항

  1. 메소드에 static과 abstract를 함께 사용할 수 없다.

    static 메소드는 몸통이 있는 메소드에만 사용할 수 있기 때문이다.

  2. 클래스에 abstract와 final을 동시에 사용할 수 없다.

    abstract는 상속을 통해서 완성이 되기 때문이다.

  3. abstract메소드의 접근 제어자가 private일 수 없다.

    위와 동일한 이유이다.

  4. 메소드의 private와 final을 같이 사용할 필요는 없다.

    private자체로 오버라이딩이 될 수 없다는 전제이기 때문이다.


다형성

조상클래스 타입의 참조변수자식클래스의 인스턴스를 참조할 수 있도록 한 것이다.

class Parent {
	...
}

class Child extends Parent{
	...
}
class Test {
	public static void main(String[] args) {
    	Parent p = new Child();
    }
}
  • 주의할 점
    참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.
    즉, Parent p = new Child(); 는 parent의 멤버에만 참조가 가능하다. child의 메소드, 멤버 변수를 참조할 수 없다는 뜻이다.

다운 캐스팅, 업 캐스팅

  • 업캐스팅 : 자식타입 -> 조상타입 (형변환 필요 X)
  • 다운캐스팅 : 조상타입 -> 자식타입 (형변환 필요)
Child c = new Parent();

이와 같이 반대로 하면 컴파일 에러가 발생한다.
인스턴스인 Parent보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다.

즉, 참조변수가 사용할 수 있는 멤버의 개수 <= 인스턴스 멤버의 개수 를 만족해야 한다.

Child c = new Child();
Parent p = c;
c = (Child) p;

이렇게 쓰면 가능하긴 하다.
바로 다운캐스팅을 하게되면 컴파일은 괜찮지만, 실행시 에러가 뜬다. 업캐스팅 -> 다운캐스팅을 해야한다.

컴파일 시에는 참조변수간의 타입만 체크하기 때문이다.
실행 시에는 인스턴스의 타입까지 고려하기 때문에 실행시에는 에러가 발생된다.

참조변수와 인스턴스의 연결

  • 오버라이딩 된 경우
    인스턴스 타입의 메소드가 불러와진다.
  • 오버라이딩 X
    참조변수 타입의 메소드가 불러와진다.

멤버변수는 참조변수 타입에 따른다.


추상클래스

추상클래스는 미완성 설계도에 비유할 수 있다.

추상클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상 클래스로서 중요한 의미를 갖는다.

그러니깐 "자식클래스들은 이 메소드를 써야해!" 하고 틀을 만들어주는 것이다.

추상클래스에도 생성자가 있고, 멤버변수와 메소드를 가질 수 있다.

기존 클래스의 공통부분을 뽑아 조상클래스로 만드는 개념이다.

미완성 상태로 남겨놓는 이유

상속받는 클래스에 따라 메소드의 내용이 달라질 수 있다.
그렇기 때문에 선언부만을 작성해서 해당 기능의 목적을 알려주는 것이다.

자식클래스들은 해당 메소드를 받아 적절히 구현하는 것이다.

구현방법

abstract class Player {
	abstract void play(int post);
    void stop();
}

메소드에 abstract 를 붙이면 자식 클래스에서 해당 메소드를 구현하도록 강제할 수 있다.

인터페이스

  • 인터페이스는 일종의 추상클래스이다.
  • 추상클래스처럼 추상메소드를 갖지만 추상화 정도가 더 높다.
  • 추상메소드와 상수만을 멤버로 가질 수 있다.
  • 일반 메소드, 멤버변수를 가질 수 없다.

구현된 것은 아무것도 없고 밑그림만 그려져 있는 기본 설계도와 같다.

구현방법

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

주의 사항

  • 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
  • 모든 메소드는 public abstract이어야 하며, 이를 생략할 수 있다.

생략하면 컴파일러가 자동으로 추가해준다.

JDK1.8부터는 static메소드와, default 메소드를 허용해준다.

인터페이스의 상속

interface Movable {
	void move(int x, int y);
}
interface Attackable {
	void attack(Unit u);
}
interface Fightable extends Movable, Attackable {}

클래스와 달리 다중상속이 가능하다.

interface는 Object클래스와 같은 최고 조상이 없다.

인터페이스의 구현

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

extends가 아닌 implements를 사용한다.

  • 만일 구현하는 인터페이스 메소드 중에 일부만 구현한다면 abstract를 붙여 추상클래스로 선언해야한다.
  • 구현하는 클래스에서는 public을 꼭 붙여야 한다. 접근 제어자의 범위를 같거나 좁게해야하기 때문이다. 오버라이딩의 주의사항을 읽어보자.

인터페이스를 이용한 다형성

  1. 인터페이스의 타입의 참조로 구현한 클래스의 인스턴스를 참조할 수 있다.
class Fighter extends Unit implements Fightable {
	...
}

Fightable f = new Fighter();
  1. 인터페이스는 다음과 같이 메소드의 매개변수의 타입으로 사용될 수 있다.
void attack(Fightable f) {
	...
}

그러면 1번에 의해서 다음과 같이 할 수 있다.
attack(new Fighter())

  1. 메소드의 리턴타입으로 인터페이스의 타입을 지정할 수 있다.
Fightable method() {
	return new Fighter();
}	
  • 리턴타입이 인터페이스라는 것은 메소드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환하다는 것을 의미한다.
    이 문장은 극도로 중요하다고 나와있다. 위의 코드를 보면 알 수 있다.

인터페이스의 장점

  • 개발시간을 단축할 수 있다.
  • 표준화가 가능하다.
  • 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
  • 독립적인 프로그래밍이 가능하다.

인터페이스의 이해

  • 클래스를 사용하는 쪽과 클래스를 제공하는 쪽이 있다.
  • 메소드를 사용하는 쪽에서는 사용하려는 메소드의 선언부만 알면 된다. (내부는 몰라도 된다.)

default, static 메소드

static 메소드

static메소드는 인스턴스와 관계가 없기 때문에 딱히 큰 상관이 없다.

default 메소드

인터페이스가 변경될 일이 생길 수도 있다.
이 때 메소드를 추가하면 이를 구현한 클래스들을 모두 변경해야 할 것이다.

이 때문에 default method라는 것을 고안해냈다.

interface MyInterface {
	void method();
	default void newMethod();
}

다음과 같이 default를 추가하면 굳이 구현하지 않아도 된다.

이름이 중복되어 충돌이 나는 경우

  1. 여러 인터페이스의 디폴트 메소드간의 충돌
    • 인터페이스를 구현한 클래스에서 디폴트 메소드를 오버라이딩해야 한다.
  2. 디폴트 메소드와 조상 클래스의 메소드간의 충돌
    • 조상 클래스의 메소드가 상속되고, 디폴트 메소드는 무시된다.

내부 클래스

클래스 내부에 선언되는 클래스이다.

내부 클래스의 장점

  • 내부 클래스에서 외부 클래스 멤버들을 쉽게 접근할 수 있다.
  • 코드의 복잡성을 줄일 수 있다.(캡슐화)

종류와 특징

인스턴스 클래스

외부 클래스의 멤버변수 선언위치에 선언한다. 외부 클래스의 instance멤버처럼 다루어진다.

스태틱 클래스

외부 클래스의 멤버변수 선언위치에 선언한다. 외부 클래스의 static멤버처럼 다루어진다.

지역 클래스

외부 클래스의 메소드나 초기화블럭 안에 선언한다. 선언된 내부에서만 사용가능하다.

익명 클래스

클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스이다.(일회용)

class Outer{
	void method() {
    	int lv = 0;
        final int LV = 0;
        
        class LocalInner {
        	int liv1 = lv;  // JDK1.8부터는 에러가 안난다.
            int liv2 = LV;
        }
    }
}

다음과 같이 지역 클래스일 경우 생각해야한다.
지역 클래스상수만 접근이 가능하다.
하지만 JDK1.8부터는 final을 생략할 수 있다. 컴파일러가 알아서 붙여주기 때문이다.

상수만 접근이 가능한 이유

  • 메소드가 수행을 마쳐서 지역변수가 소멸되었는데도 지역 클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생할 수도 있기 때문이다.

참고
http://www.tcpschool.com/java/java_inheritance_concept

profile
강한 백엔드 개발자가 되기 위한 여정

0개의 댓글