본문 바로가기
프로그래밍언어/Java

[자바의 정석] 07. 객체지향 프로그래밍Ⅱ(3)

by qkzkdo 2023. 8. 3.
728x90

6. 추상클래스(abstract class)

6.1 추상클래스란?

미완성(부족한)설계도, 완성되지 못한 채로 남겨진 설계도를 말한다.

클래스가 미완성이라는 것은 멤버의 개수에 관계된 것이 아니라,단지 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다.

추상 클래스로 인스턴스는 생성할 수 없다. 추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.(다른 클래스 작성에 도움을 주기 위한 것)

새로운 클래스를 작성하는데 있어서 바탕이 되는 조상 클래스로서 중요한 의미를 갖는다.

새로운 클래스를 작성할 때 아무 것도 없는 상태에서 시작하는 것보다는 완전하지는 못하더라도 어느 정도 틀을 갖춘 상태에서 시작하는 것이 나음

ex. Tv는 여러 모델이 있지만 이 들의 설계도는 90%가 동일할 것이다. 공통부분만을 그린 미완성 설계도를 만들어 놓고, 이 미완성 설계도를 이용해서 각각의 설계도를 완성하는 것이 훨씬 효율적일 것이다.

추상클래스는 키워드 abstract 붙이면 된다.

클래스 선언부의 abstract를 보고 이 클래스에는 추상메서드가 있으니 상속을 통해서 구현해주어야 한다는 것을 쉽게 알 수 있다.

 

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

 

추상클래스는 추상메서드를 포함하고 있다는 것을 제외하고는 일반 클래스와 전혀 다르지 않다.

추상클래스도 생성자가 있으며, 멤버변수와 메서드도 가질 수 있다. 

❗추상메서드를 포함하고 있지 않은 클래스에도 키워드’abstrack’을 붙여서 추상클래스로 지정할 수도 있다. 추상메서드가 없는 완성된 클래스라 할지라도 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없다.

 

6.2 추상메서드(abstrack method)

✏️{}구현부가 없는 메서드/미완성메서드

메서드는 선언부와 구현부(몸통)로 구성되어 있다고 했다. 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다.

✏️꼭 필요하지만 자손마다 다르게 구현될 것으로 예상되는 경우

메서드를 이와 같이 미완성 상태로 남겨 놓는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에

추상메서드 역시 키워드 ‘absctract’를 앞에 붙여주고, 괄호{}대신 문장의 끝을 알리는 ‘;’을 적어준다.

 

/*주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다.*/
abstrack 리턴타입 메서드이름(); //{}구현부없음  //abstract <-제어자

 

추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상클래스의 추상메서드를 모두 구현해주어야 한다. 만일 조상으로부터 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해 주어야 한다.

구현 : 추상메서드 몸통{} 만들어 주는 것

 

abstract class Player { //추상클래스
    abstrack void play(int pos);
    abstrack void stop();
}
class AudioPlayer extends Player{ //완전한 클래스
    void play(int pos) { /*내용생략*/ } //추상메서드 구현
    void stop() { /*내용생략*/ } //추상메서드 구현
    /*play로 써도 됨(다형성)*/AudioPlayer ap = new AudioPlayer(); //인스턴스 생성 ok
} //상속을 통해 추상메서드를 완성해야 인스턴스 생성가능
abstract class AbstractPlayer extends Player {
    void play(int pos) {/*내용생략*/} //추상메서드 구현
    //abstract void stop(); 이 숨어있음

메서드를 작성할 때 실제 작업내용인 구현부보다 더 중요한 부분이 선언부이다. 

메서드의 이름과 매개변수, 리턴타입, 즉 선언부만 알고 있으면 되므로 내용이 없을 지라도 추상메서드를 사용하는 코드를 작성하는 것이 가능, 실제로는 자손클래스에 구현된 메서드가 호출되도록 할 수 있다.

 

6.3 추상클래스의 작성

-추상화된 코드는 구체화된 코드보다 유연하다.변경에 유리

여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고, 기존의 클래스의 공통적인 부분을 뽑아서 추상클래스로 만들어 상속하도록 하는 경우도 있다.

상속계층도를 따라 내려갈수록 클래스는 점점 기능이 추가되어 구체화의 정도가 심해지며, 상속계층도를 따라 올라갈수록 클래스는 추상화의 정도가 심해진다고 할 수 있다. 즉, 상속계층도를 따라 내려 갈수록 세분화되며, 올라갈수록 공통요소만 남게 된다.

추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
구체화 : 상속을 통해 클래스를 구현, 확장하는 작업
abstract class Player {
    boolean pause; //일시정지 상태를 저장하기 위한 변수
    int currentPos; //현재 play되고 있는 위치를 저장하기 위한 변수

    Player(){ //추상클래스도 생성자가 있어야 한다.
        pause = false;
        currentPos = 0;
    }
    /* *지정된 위치(pos)에서 재생을 시작하는 기능이 수행하도록 작성되어야 한다. */
    abstract void play(int pos);
    /* *재생을 즉시 멈추는 기능을 수행하도록 작성되어야 한다 */
    abstract void stop();

    void play() {
        play(currentPos);
    }
    void pause() {
        if(pause) { //pause가 true일 때(정지상태)에서 pause가 호출되면,
            pause = false; //pause의 상태를 false로 바꾸고,
            play(currentPos); //현재의 위치에서 play를 한다.
        }else {	//pause가 false일 때(play상태)에서 pause가 호출되면,
            pause = true; //pause의 상태를 true로 바꾸고
            stop(); //play를 멈춘다.
        }
    }
}
class CDPlayer extends Player {
    void play(int currentPos) {
        /*조상의 추상메서드를 구현. 내용생략*/
    }
    void stop() {
        /*조상의 추상메서드를 구현. 내용생략*/
    }
    //CDPlayer클래스에 추가로 정의된 멤버
    int currentTrack; //현재 재생 중인 트랙

    void nextTrack() {
        currentTrack++;
    }
    void preTrack() {
        if(currentTrack > 1) {
            currentTrack--;
        }
    }
}

 

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

 

 

7. 인터페이스(interface)

7.1 인터페이스란?

인터페이스는 일종의 추상클래스이다. 일반메서드,멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있다.

추상클래스 : 부분적으로만 완성된 미완성 설계도

인터페이스 : 구현된 것은 아무것도 없고 밑그림만 그려져 있는 기본설계도

 

7.2 인터페이스의 장점

키워드로 class 대신 interface를 사용한다.

interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.

 

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

인터페이스 멤버들의 제약

  • 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
  • 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
     단, static메서드와 디폴트 메서드는 예외(JDK1.8부터)

*컴파일러가 제어자 자동 추가

 

7.3 인터페이스의 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러개의 인터페이스로부터 상속을 받는 것이 가능하다.(추상메서드는 충돌해도 문제없음)

❗인터페이스는 클래스와 달리 Object클래스와 같은 최고 조상이 없다.

 

7.4 인터페이스의 구현

-인터페이스에 정의된 추상메서드를 완성하는 것

그 자체로는 인스턴스를 생성할 수 없으며, 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 한다.

구현한다는 의미의 키워드 implements를 사용

 

class 클래스이름 implements 인터페이스이름 {
    //인터페이스에 정의된 추상메서드를 구현해야함
}
abstract class Fighter implements Fightable{
    //메서드구현
} 일부만 구현하면
class Fighter extents Unit implements Fightable {
    //메서드 구현
} //상속과 구현 동시

 

❗"'클래스이름'클래스는 '인터페이스이름'인터페이스를 구현한다" 라고 한다.

인스턴스 메서드 중 일부만 구현한다면 abstract를 붙여서 추상클래스로 선언해야한다.

상속과 구현을 동시에 할 수도 있다.

인터페이스의 이름은 주로 Fightable과 같이 ‘~을 할 수 있는’의 의미은 ‘able’로 끝나는 것들이 많음

오버라이딩 할 때는 조상메서드보다 넓은 범위의 접근제어자를 지정해야함

 

7.5 인터페이스를 이용한 다중상속

자바에서 인터페이스로 다중상속을 구현하는 경우는 거의 없다.

 

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

-인터페이스도 구현 클래스의 부모? yes

-인터페이스타입 매개변수는 인터페이스 구현한 클래스의 객체만 가능

인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.

 

fightable f = (fightable)new Fighter();
Fightable f = new Fighter();

 

Fighter인스턴스를 Fightable타입 참조변수로 참조하는 것이 가능하다.

 

❗Fightable타입의 참조변수로는 인터페이스 Fightable에 정의된 멤버들만 호출이 가능하다.

따라서 인터페이스는 다음과 같이 매서드의 매개변수의 타입으로 사용될 수 있다.

 

void attack(Fightable f) {
	//...
}

 

인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 것이다.

그래서 attack메서드를 호출할 때는 매개변수로 Fightable인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다.

메서드의 리턴타입으로 인터페이스의 타입을 지정하는 것 역시 가능하다.

 

Fightable method() {
    ...
    Fighter f = new Fighter();
    return f;  // 이 두 문장을 한 문장으로 바꾸면 다음과 같다. return new Fighter();
}

 

위의 코드에서는 method()의 리턴타입이 Fightable인터페이스이기 때문에 메서드의 return문에서 Fightable인터페이스를 구현한 Fighter클래스의 인스턴스를 반환한다.

 

7.7 인터페이스의 장점

-두 대상(객체)간의 ‘연결, 대화, 소통’을 돕는 ‘중간역할’을 한다.

-선언(설계)와 구현을 분리시킬 수 있게 한다.

 

인터페이스의 장점

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

 

7.8 인터페이스의 이해

인터페이스를 이해하기 위해 다음 두 가지의 사항을 받드시 염두에 두고 있어야 한다.

-클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
-메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.(내용은 몰라도 된다.)
class A {
    public void methodA(B b) {
        b.methodb();
    }
}
class B {
    public void methodB() {
        System.out.println("methodB()");
    }
}
calss InterfaceTest {
    public static void main(String[]) args) {
        A a = new A();
        a.methodA(new B());
    }
}

 

클래스 A와 B가 있다고 하자. 클래스 A(User)는 클래스 B(Provider)의 인스턴스를 생성하고 메서드를 호출한다.

인터페이스 I를 정의한다.

 

interface I {
    public abstract void methodB();
}

 

B가 인터페이스 I를 구현하도록 한다.

 

class B implemetns I {
    public void methodB() {
        System.out.println("methodB in B class"); 
	}
}

 

이제 클래스 A는 클래스 B대신 인터페이스 I를 사용해서 작성할 수 있다.

 

class A {
    public void methodA(B b) {
        b.methodB();
    }
}
class A {
    public void methodA(I i) {
        i.methodB();
    }
}

 

클래스 A는 인터페이스 I 하고만 직접적인 관계가 있기 때문에 클래스 B의 변경에 영향을 받지 않는다. 클래스 A는 오직 집적적인 관계에 있는 인터페이스 I 의 영향만 받는다.

 

7.9 디폴트 메서드와 static 메서드

-인터페이스에 새로운 메서드(추상메서드)를 추가하기 어려움 해결책=>디폴트메서드

-디폴트메서드는 인스턴스메서드(인터페이스 원칙 위반)

 

디폴트 메서드

인터페이스에 메서드를 추가한다는 것은, 추상 메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현 해야하기 때문이다. → 디폴트 메서드

디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

디폴트메서드는 앞에 키워드 default를 붙이며, 추상메서드와 달리 메서드 몸통{}이 있어야 한다. 디폴트 메서드 역시 접근 제어자가 public이며, 생략가능하다.

디폴트메서드를 추가하면 조상클래스에 새로운 메서드를 추가한 것과 동일해 지는 것이다.

 

기존메서드와 이름 충돌 해결 규칙

1. 여러 인터페이스의 디폴트 메서드 간의 충돌

-인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 한다.

2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌

-우선 조상클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다. (그냥 직접 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 하면 해결)

 

 

8. 내부 클래스(inner class)

내부 클래스는 클래스 내에 선언된다는 점을 제외하고는 일반적인 클래스와 다르지 않다.

사용빈도가 높지는 않음

 

8.1 내부 클래스란?

내부 클래스는 클래스 내에 선언된 클래스이다. 클래스에 다른 클래스를 선언하는 이유는 두 클래스가 서로 긴밀한 관계가 있기 때문이다.

 

내부클래스의 장점

  1. 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
  2. 코드의 복잡성을 주일 수 있다(캡슐화). (외부에는 불필요한 클래스를 감춤으로써)
class A {
    ...
}
class B {
    //A의 객체를 생성해야 사용가능
}
class A { //(B의)외부클래스
    ...
        class B { //(A의)내부클래스
            //객체 생성 없이 사용 가능(접근 가능)
        }
    ...
}

 

이때 내부 클래스는 외부 클래스를 제외하고는 다른 클래스에서 잘 사용되지 않는 것이여야 한다.

 

8.2 내부 클래스의 종류와 특징

-내부 클래스의 종류와 유효범위(scope)는 변수와 동일

내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다.

[내부 클래스의 종류와 특징] - 선언 위치에 따른 종류

내부 클래스 특징
인스턴스 클래스 외부 클래스의 멤버번수 선언위치에 선언하며, 외부 클래스의 인스턴스멤버처럼 다루어진다. 주로 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언된다.
스태틱 클래스 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static멤버처럼 다루어진다. 주로 외부 클래스의 static멤버, 특히 static메서드에서 사용될 목적으로 선언된다.
지역 클래스 외부 클래스의 메서드나 초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다.
익명 클래스 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)

 

8.3 내부클래스의 선언

classs outer {
    ⑴int iv = 0;
    ⑵static int cv = 0;

    void myMethod() {
        ⑶int lv = 0;
    }
}
class Outer {
    ⑴class InstanceInner {}	//인스턴스내부클래스
    ⑵static class StaticInner {}	//스태틱내부클래스

    void myMethod() {
        ⑶class LocalInner {}	//지역내부클래스
    }
}

 

각 내부클래스의 선언위치에 따라 같은 선언위치의 변수와 동일한 유효범위(scope)와 접근성(accessibility)를 갖는다.

 

8.4 내부 클래스의 제어자와 접근성

-내부 클래스의 제어자는 변수에 사용 가능한 제어자와 동일

제어자 4개 모두 사용 가능, abstrack, final도 사용가능(원래는 클래스 앞에 public,defualt만 가능)

 

class Outer {
    private int iv = 0;
    protected static int cv = 0;

    void myMethod() {
        int lv = 0;
    }
}
class Outer {
    private class InstanceInner{}
    protected static class StaticInner {}

    void myMethod() {
        class LocalInner {}
    }
}

 

내부 클래스 중 staticInner만 static멤버를 가질 수 있다. 다만 final과 static이 동시에 붙은 변수는 상수(constant)이므로 모든 내부 클래스에서 정의가 가능하다.

 

8.5 익명 클래스(anonymous class)

-이름이 없는 일회용 클래스. 정의와 생성을 동시에

클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.

 

new 조상클래스이름() {
    //멤버 선언
}

또는

new 구현인터페이스이름 () {
    멤버 선언
}

 

이름이 없기 때문에 생성자도 가질 수 없으며, 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다. 오로지 단 하나의 클래스를 상속받거나 단 하나의 인터페이스만을 구현할 수 있다.

인스턴스 클래스를 익명 클래스로 바꾸는 연습을 몇 번만 해보면 익숙해 질 것이다.

 

 

 

출처 : 남궁성. 「자바의 정석」. 도우출판. 2016

728x90