6 minute read

Updated:

추상클래스(abstract class)

추상클래스란?

  • 클래스가 설계도라면 추상클래스는 미완성 설계도(미완성 메서드를 포함하고 있다는 의미)
  • 완성된 설계도가 아니므로 인스턴스 생성은 불가하고, 새로운 클래스를 작성하는데 도움을 줄 목적으로 작성한다.
  • 예를 들어 TV 제품을 구현할 때 서로 다른 설계도를 따로 그리는 것보다, 공통부분만 그린 미완성 설계도를 그려놓고 각각의 제품을 완성하는 것이 효율적이다.
//추상클래스의 선언
abstract class 클래스이름 {
    ...
}

추상메서드(abstract method)

  • 선언부만 작성하고 구현부는 없는 메서드
/*주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다.*/
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) { }      // 추상메서드를 구현
}
  • 메서드를 작성할 때 구현부보다 더 중요한 부분이 선언부이다.
  • 메서드의 이름과 작업에 필요한 매개변수, 리턴타입을 결정하는 것만으로도 메서드의 절반 이상이 완성된 것이다.

추상클래스의 작성

  • 여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나, 기존 클래스의 공통 부분을 뽑아서 추상클래스를 만든다.
  • 아래는 공통부분인 move 메서드를 추상메서드로 구현했다. 모든 유닛은 이동할 수 있어야하므로 move 메서드가 반드시 필요하다. 따라서 자손 클래스에서 추상메서드를 반드시 구현해야하므로 자신의 클래스에 알맞게 구현이 될 것이다.
/*기존 클래스 작성*/
class Marine {
    int x, y;
    void move(int x, int y) {/*지정된 위치로 이동*/}
    void stop() {/*현재 위치에 정지*/}
    void stimPark() {/*스팀팩을 사용한다.*/}
}

class Tank {
    int x, y;
    void move(int x, int y) {/*지정된 위치로 이동*/}
    void stop() {/*현재 위치에 정지*/}
    void changeMode() {/*공격모드를 변환한다.*/}
}

class DripShip {
    int x, y;
    void move(int x, int y) {/*지정된 위치로 이동*/}
    void stop() {/*현재 위치에 정지*/}
    void unload() {/*선택된 대상을 내린다.*/}
}
/*추상클래스로 구현*/
abstract class Unit {
    abstract void move(int x, int y);
    void stop() {/*현재 위치에 정지*/}
}

class Marine extends Unit {
    void move(int x, int y) {/*지정된 위치로 이동*/}
    void stimPark() {/*스팀팩을 사용한다.*/}
}

class Tank extends Unit{
    void move(int x, int y) {/*지정된 위치로 이동*/}
    void changeMode() {/*공격모드를 변환한다.*/}
}

class DripShip extends Unit{
    void move(int x, int y) {/*지정된 위치로 이동*/}
    void unload() {/*선택된 대상을 내린다.*/}
}
  • 클래스들 간 공통조상이 있으면 아래처럼 조상 클래스타입 배열에 자손 인스턴스를 담을 수 있다.
  • 메서드는 실제 인스턴스에 구현된 것이 호출되므로, Unit 클래스의 move 메서드를 호출하면 각 자손클래스의 인스턴스 메서드가 호출된다.
Unit[] group = new Unit[4];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new Marine();
group[3] = new DripShip();

for(int i = 0; i <group.length; i++)
    group[i].move(100, 200);    //Unit 배열의 모든 유닛을 좌표(100, 200)의 위치로 이동한다.

인터페이스

인터페이스란?

  • 추상 메서드의 집합.
  • 구현된 것은 아무것도 없고 밑그림만 그려져 있는 ‘기본설계도’, 껍데기이다.(모든 멤버가 public)
  • 미리 정해진 규칙에 맞게 구현되도록 표준을 제시하는 데 사용된다.

인터페이스 vs 추상메서드 차이

  • 추상메서드는 일반클래스로서 생성자와 iv(멤버변수)를 가질 수 있지만, 인터페이스는 구현된 것이 전혀 없고 추상 메서드만 나열된 것

인터페이스의 작성

  • ‘class’ 대신 ‘interface’를 사용한다는 것 외에 클래스 작성과 동일하다.
  • 구성요소(멤버)는 추상메서드와 상수만 가능하다.

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

interface PlayingCard {
    public static final int SPACE = 4;
    final int DIAMOND = 3;      // public static final int DIAMOND = 3;  
    static int HEART = 2;       // public static final int HEART = 2;  
    int CLOVER = 1;             // public static final int CLOVER = 1; 
    
    public abstract String getCardNumber ();
    String getCardKind();       // public abstract String getCardKind();
}

인터페이스의 상속

  • 인터페이스도 클래스처럼 상속이 가능하다. (클래스와 달리 다중상속 허용)
interface Moveable {
    /* 지정된 위치(x,y)로 이동하는 기능의 메서드*/
    void move (int x, int y);
}

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

interface Fightable extends Movable, Attackabe { }
  • 인터페이스는 Object클래스와 같은 최고 조상이 없다.

인터페이스의 구현

  • 인터페이스를 구현하는 것은 클래스를 상속받는 것과 같다.
  • 다만, ‘extends’ 대신 ‘implements’를 사용한다.
class 클래스이름 implements 인터페이스이름 {
    //인터페이스에 정의된 추상메서드를 구현해야한다.
}
  • 인터페이스에 정의된 추상메서드를 완성해야 한다.
class Fighter implements Fightable {
    public void move() { /*내용 생략*/ }
    public void attack() { /*내용 생략*/ }
}

interface Fightable {
    void move(int x, int y);
    void attack(Unit u);
}
  • 상속과 구현이 동시에 가능하다.
class Fighter extends Unit implements Fightable {
    public void move(int x, int y) { /*내용 생략*/ }
    public void attack(Unit u) { /*내용 생략*/ }
}

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

  • 인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있다.
class Fighter extends Unit implements Fightable {
    public void move(int x, int y) { /*내용 생략*/}
    public void attack(Fightable f) { /*내용 생략*/}
}
Fighter f = new Fighter();
Fightable f = new Fighter();
  • 인터페이스를 메서드의 매개변수 타입으로 지정할 수 있다.
void attack(Fightable f) {  //fightable인터페이스를 구현한 클래스의 인스턴스를 매개변수로 받는 메서드
    //...    
        }
  • 인터페이스를 메서드의 리턴 타입으로 지정할 수 있다.
Fightable method(){  // Fightable인터페이스를 구현한 클래스의 인스턴스를 반환
        //...
        return new Fighter();
}

인터페이스의 장점

  1. 개발시간을 단축시킬 수 있다.
    • 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다.
    • 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하도록 하여, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 할 수 있다.
  2. 표준화가 가능하다.
    • 프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.
  3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
    • 서로 상속관계에 있지도 않고 같은 조상클래스를 가지고 있지 않은 서로 아무 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 하여 관계를 맺어줄 수 있다.
  4. 독립적인 프로그래밍이 가능하다.
    • 인터페이스를 사용하면 클래스의 선언과 구현을 분리시킬 수 있으므로 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다.
    • 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.

인터페이스의 이해

  • 두 대상(객체) 간의 ‘연결, 대화, 소통’을 돕는 ‘중간 역할’을 한다.
  • 선언(설계)와 구현을 분리시키는 것을 가능하게 한다.
    class B {
      public void method() {
          System.out.println("methodInB");
      }
    }
    
인터페이스로 구현
interface I {
    public void method();
}

class B implements I {
    public void method() {
        System.out.println("methodInB");
    }
}

디폴트 메서드와 static메서드

  • 인터페이스에 디폴트 메서드, static메서드 추가 가능(JDK1.8부터)

디폴트 메서드

  • 인터페이스에 메서드를 추가하면, 이 인터페이스를 구현한 모든 클래스들이 새로 추가된 메서드를 구현해야한느 번거로움이 있었다.
  • 디폴트 메서드를 사용하면 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.
  • 디폴트 메서드는 앞에 키워드 default를 붙이며, 일반 메서드처럼 몸통{}이 필요하다.
interface MyInterface {
    void method();
    void newMethod();   // 추상 메서드
}
/*디폴트 메서드로 메서드 추가*/
interface MyInterface {
    void method();
    default void newMethod() {}
}
  • 새로 추가된 디폴드 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우, 규칙은 다음과 같다.
    1. 여러 인터페이스의 디폴트 메서드 간의 충돌
    • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
      1. 디폴트 메서드와 조상 클래스 메서드 간의 충돌
    • 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
  • 간단하게, 필요한 쪽의 메서드와 같은 내용으로 오버라이딩하면 된다.

내부 클래스

  • 내부 클래스란 클래스 내에 선언된 클래스이다.
  • 클래스에 다른 클래스를 선언하는 이유는 두 클래스가 서로 긴밀한 관계가 있기 때문이다.
  • 내부 클래스로 선언하면 1. 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있고, 2. 코드의 복잡성을 줄일 수 있다.(캡슐화)
class A {   // 외부 클래스
   ...
   class B {    // 내부 클래스
    ...
   }
   ...
}

내부 클래스의 종류와 특징

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

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

  • 내부 클래스도 클래스이므로 abstact나 final과 같읕 제어자를 사용할 수 있고, 멤버변수처럼 private, protected 접근제어자도 사용 가능하다.

Tags:

Categories:

Updated: