2011년 5월 7일 토요일

자바의 추상클래스(abstract class)와 인터페이스(interface)


자바의 상속 개념을 공부할때 빠지지 않고 등장하는 것이 바로 오늘 배울 추상클래스(abstract class)와 인터페이스(interface)다. 무슨 공통점이 있는지 무슨 차이점이 있는지 말도 애매모호하고 서로 비슷비슷하므로 이 부분만 나오면 한숨이 나오는 이들이 많을 것이다. 오늘 강좌를 통해서 언제나 그랬듯이 여러분의 막힌 속을 뻥 뚫리도록 상세한 설명과 함께 본 내용을 진행하겠다.*

추상클래스를 설명하자면 없거나 하나 이상의 추상메소드를 가지고 있는 것이 추상클래스다. 그렇다면 추상메소드는 무엇인가? 추상메소드는 안이 아직 구현되어 있지 않은 abstract로 정의된 메소드를 말한다. 클래스안의 메소드가 단 한개라도 추상메소드가 있다면 그 클래스 앞에는 반드시 abstract 클래스명으로 표기되어야 하며 abstract와 final 키워드를 동시에 표기할수 없다. 추상클래스는 일반적인 메소드도 있을수 있고 추상메소드가 있을수도 있다. 추상클래스는 일반변수들을 가질수 있다. 추상클래스도 인터페이스처럼 추상클래스가 아닌 클래스에서 상속을 받는다면 추상메소드가 있을경우 모두 구현해주어야 한다. 물론 추상클래스에서 추상클래스를 상속받는다면 모두 구현하지 않아도 된다. 추상클래스에서도 인터페이스를 구현할수 있는데 이때는 구현하지 않고 그냥 놔둘수 있다. 추상클래스는 생성자를 가질수 있다. 추상클래스는 인스턴스를 만들수 없지만 추상클래스를 상속받은 클래스를 통하면 인스턴스화가 가능하다. 예를 들자면 "추상클래스명 ab = new 클래스명( )" 이런 형태가 될것이다. 추상클래스의 접근지정자는 어떤 것이나 가능하다. 추상클래스의 궁극적인 목적은 상속하기 위함이다. 어떤 클래스가 추상클래스를 상속받을때는 extends를 쓴다.^^

인터페이스는 안이 비어있는 메소드들의 형태들만 써놓은 것이며 상속하는 클래스들에서 해당 메소드들의 내용을 구현해서 가져야하는 메소드들의 집합이라 할수있다. 따라서 인터페이스에 새로운 메소드를 추가한다고 하고 그 아래에 인터페이스로부터 상속되는 클래스가 있다면 새로운 메소드에 대한 내용을 반드시 구현해야한다. 인터페이스안의 모든 메소드들은 추상메소드이다. 인터페이스는 final을 붙일수 없고 인터페이스 변수들은 static이어야만 한다. 한마디로 인터페이스는 일반변수들을 가질수 없다. 인터페이스는 하나 이상의 인터페이스들을 상속할수 있는데 여러개일때는 콤마(,)를 사용하며 이때는 클래스에서 상속받는게 아니라 인터페이스에서 상속받으니 즉 내용이 없는 메소드들을 그대로 두는것이니 구현이 아니라 인터페이스간의 상속이므로 일반 클래스끼리 상속할때처럼 extends를 쓴다. 인터페이스는 생성자를 가질수 없다. 인터페이스는 인스턴스를 만들수 없지만 인터페이스를 구현한 클래스를 통하면 인스턴스화가 가능하다. 예를 들자면 "인터페이스명 ab = new 클래스명( )" 이런 형태가 될것이다. 인터페이스의 접근지정자는 아예 없거나 public이거나 아님 abstract만 가능하다. 어떤 클래스가 인터페이스를 상속받을때는(구현할때는) implements를 쓴다. 물론 클래스가 다른 클래스를 상속하면서 인터페이스를 구현하는 것도 가능하다. 그럴때는 extends 쓰고 implements를 순서대로 쓰면 된다.^^

상반되는 내용을 많이 비교하였으므로 이 정도면 충분히 무엇이 다른지 알았을거라 본다.ㅎㅎ 조금더 덧붙이자면 추상클래스는 인터페이스보다 속도가 빠르다. 왜냐하면 인터페이스는 관련 메소드들을 찾기 위해 부가적인 일들을 더 처리하기 때문에 그러하다. 추상클래스는 "일반변수(가능)+일반메소드(가능)+추상메소드" 형태이고 인터페이스는 "상수+추상메소드"만 가능하고 일반변수나 일반메소드를 쓰는 것은 불가능하다. 추상메소드가 뭐냐면 아직 구현이 되지않은 다시 말해서 아직 "구"체적으로 표"현"이 되지않은 메소드를 말한다. 이걸 코드상으로 설명하자면 메소드의 제목(리턴타입,메소드명,매개변수)은 있는데 { }안에 내용이 없는 형태이고 물론 { } 이런 괄호도 없는 형태를 말한다. 끝에 세미콜론 붙이는거 까먹지 않아야하고 말이다. 그럼 어떻게 코드로 작성하는지 알아보기로 하자.^^

abstract class Shape
{
   abstract void draw( );   //메소드 앞에 abstract가 있으면 추상메소드다.
   void hit(int x)
   {
      x=3;
   }
}

interface Shape
{
   void draw( );   //인터페이스에서는 abstract 생략가능하다.
   abstract void move(int y);
}

추상클래스는 메소드를 하나라도 abstract로 만들었다면 반드시 클래스명 앞에다 abstract로 정의해야된다. 인터페이스의 모든 변수는 상수(static final)로 모든 메소드는 추상메소드(abstract)로 정의해야된다는 점이 추상클래스와 다르다. 따라서 인터페이스의 경우 변수 앞에 static final이나 메소드앞에 abstract라는 명령어를 안써도 상수나 추상메소드로 자동인식하므로 안써도 상관없으나 빠른 가독성을 위해 생략가능한 부호는 언제나 쓰는 습관을 기르기 바란다. 추상메소드를 코드로 쓸때는 몸통이 없으니 당연히{ } 괄호가 없고 끝에 세미콜론(;)을 붙여야한다. 더불어 인터페이스는 클래스처럼 일반메소드는 쓰지 못하고 모든 메소드가 몸통없이 껍데기만 있는 다른 형태이므로 class 대신에 interface라고 처음에 표기하고 인터페이스 이름을 만든다. 추상클래스와 인터페이스는 (new를 통한) 객체 생성을 하지 못하게 되어있는데 객체생성후에 메소드를 호출하려고 해도 내용이 없어 이용할수 없으므로 생성이 불가능하도록 되어있다. 물론 추상클래스나 인터페이스를 상속받은 자식 클래스에서 해당멤버들이 구현이 되었다면 객체생성이 가능하다. 키워드가 각각 abstract와 interface인데 당연하지만 전부 소문자로 써야 작동되니 클래스나 메소드 앞에 붙일때 첫글자를 대문자로 쓰는 일은 없길 바란다.-;

예제처럼 도형을 그리는 프로그램을 만든다고 치자. 삼각형을 만들때도 그려야하고 사각형을 만들때도 그려야하니 예제와 같이 draw( ) 메소드가 필요하겠지만 도형종류가 다르니 그리는 방식도 다를 것이다. 그리고 상위 클래스인 도형은 그릴수가 없다. 도형이 무슨 모양인지 결정이 안되었으니까 말이다. 이런 상황이 추상화(그림아님--)의 대표적인 예가 아닐까 싶다. 도형을 그리는 프로그램이니 위처럼 draw( ) 메소드를 만든 것이나 도형 종류마다 그리는 방법이 다르니 추상메소드로 선언만 해놓고 안의 내용은 상황에 맞게 자식 클래스에서 알아서 다시 구현해 쓰라고 선언만 해놓고 구현을 기다리는 방식(오버라이드해서 재정의)이 바로 추상클래스와 인터페이스다.

추상메소드만 선언할꺼면 인터페이스를, 다른 일반 메소드나 필드도 필요하면 추상클래스를 쓰면 된다. 어렵게 생각하지 말자. 인터페이스는 한눈에 보면 다 빈껍데기 뿐이기 때문에 어떤 것을 구현해야되는지 한눈에 몽땅 들어오므로 쓰는 빈도가 높고 추상클래스는 필요에 의해서 일반 메소드와 더불어서 추상화 기능을 가미할때 쓴다는 정도로만 이해하면 될듯 싶다.

추상클래스나 인터페이스가 하위 클래스에서 구현을 하려면 상속을 받아야하는데 방식이 다르다. 위의 예제에서 쓰인 Shape 추상클래스(abstract class Shape)와 Shape 인터페이스(interface Shape)가 각각 어떤 방법으로 상속되는지 살펴보기 바란다. 노파심(?)에 한마디 하자면 둘다 같은 추상(껍데기) 메소드를 넘겨받아 안에 내용을 채우는 것이므로 당연히 오버라이드(리턴값,메소드명,입력값 동일한 메소드)해야 된다

class Triangle extends Shape   //abstract클래스는 상속처럼 extends를 쓴다.
{
   void draw( )   //이렇게 일반 메소드 형태로 구현해줘야 한다.
   {
      System.out.println("삼각형을 그린다");
   }
...   //hit( )는 필요시 재정의하고 아니면 상속이니 있는걸로 간주하면 된다.
}

class Triangle implements Shape   //interface는 implements로 상속을 받는다.
{
   public void draw( )   //이렇게 일반 메소드 형태로 전부 구현해줘야 한다.
   {
      System.out.println("삼각형을 그린다");
   }
   public void move(int y)   //접근 지정자를 완화시켜 public으로 구현해준다.
   {
      System.out.println("삼각형을 이동시킨다");      
   }
}

추상클래스던 인터페이스던간에 상속받기로 하였으면 안에 있는 추상메소드는 예제처럼 전부 구현해 주어야한다. 추상클래스는 인터페이스와 달리 클래스이므로 extends로 상속하고 다중상속은 안되지만 추상클래스(abstract인 자식클래스)가 추상클래스(abstract인 부모클래스)를 상속하는 것은 가능하다. 이 경우에는 자식 클래스도 역시 추상클래스라고 알려주어야 하므로 abstract를 class 앞에 표기해야되고 추상 자식클래스이니 상속받았다 할지라도 추상메소드를 꼭 구현할 필요는 없고 다른 추상메소드를 만들어도 상관없다.

abstract class Triangle extends Shape
//추상 abstract와 상속 extends가 함께 있으면 추상 자식클래스를 뜻한다.

인터페이스는 추상클래스와는 달리 클래스가 아니므로 자식 클래스가 상속을 받을때 implements라는 키워드를 쓴다. 예제에서 인터페이스 자식 클래스에서 추상메소드를 구현할때 접근지정자를 public 모드로 썼는데 이는 implements라는 키워드에서 느낄수 있듯이 어떤 상황에서든 해당 멤버들을 반드시 구현해야하므로 관련 메소드에 아무런 제약없이 접근할수 있어야하기 때문이다. 또한 인터페이스는 추상클래스나 일반클래스와는 달리 다중상속이 가능하다. 여러개의 인터페이스를 아래처럼 한꺼번에 상속받을수 있는데 콤마(,)찍고 계속해서 원하는만큼 상속받을 인터페이스 이름을 적어주고 상속받아서 해당멤버들을 인터페이스 자식클래스에서 구현하면 된다.

class Triangle implements Shape, 인터페이스이름, 인터페이스이름

추상클래스와 인터페이스를 어떤 상황에 쓰는지 굳이 구별하자면,
추상클래스는 같은 종류나 행동들을 구현할게 많을때 쓰고 당연하지만 추상메소드를 상속받는 자식클래스에서 구현할때 쓰고 상속에 대한 계층구조를 명확히 표현할때 추상클래스를 이용하는 것만큼 효과적인 방법은 없다하겠다. 추상클래스는 일반변수들과 일반메소드들도 쓸수 있고 아직 구현하지 않아도될 메소드는 그냥 내버려둘수 있어 상황에 따라 편리하다.
인터페이스는 디자인을 구성하는 요소들이 자주 바뀔때 쓰면 유용하고 당연하지만 메소드 형태만 서로 공유해서 구현되는 상황일때 적합하고 클래스 전체가 아닌 메소드들만 쓰고 싶을때 인터페이스를 이용하면 효과적이다. 좀더 깊이 들어가자면 인터페이스의 사용빈도가 상당히 높은데 그 이유중 하나가 동시개발이 가능하므로 시간을 많이 단축시킬수 있다는 점이다. 인터페이스안의 메소드들은 내용이 없는 상태이나 메소드에 대한 결과값은 내용을 만들지 않아도 미리 알수 있으므로 인터페이스의 내용을 누군가가 구현하고 있는 동안에 다른 한사람은 그 메소드가 구현되고 나면 작동할 결과값으로 같은 시간에 다른 일을 할수 있으니 개발작업이 한층더 빨라질수 있는 것이다. 또한 여러사람이 인터페이스를 통해 그런 작업을 동시에 한다고 가정했을때 인터페이스안의 메소드 내용을 변경하더라도 그와 관련된 모든 클래스들을 변경할 필요없이 해당 메소드의 구현되는 내용만 변경하면 모든 처리가 가능해지므로 일거양득(一擧兩得)이라 할수 있겠다.^^

추상클래스와 인터페이스는 여러분이 자바를 배우는 이상 심심치않게 만나는 부분이다. 추상클래스와 인터페이스의 정의만 확실히 알아도 그리 어렵진 않을 것이다. 이제껏 함께 공부한 예제들을 추상클래스나 인터페이스로 만들어 보면서 여러분의 생각대로 프로그램이 잘 돌아가는지 연습해 보기 바란다. 수고많았다. ^^

댓글 17개:

  1. 예를 들어서 설명한 내용 잘 들었습니다.
    하지만 뭔가 부족하다는 느낌이..
    추상화는 어느 때 쓰는지 그리고 인터페이스는 어느때 쓰는지?
    그리고 왜 추상화를 써야 하는지 인터페이스는 왜 써야 하는지?

    답글삭제
  2. 추상클래스는 어느정도 작업을하다가 다름사람에게 넘길때 사용하는거 아닌가요? 오버로딩개념으로..
    인터페이스는 하나의 규약으로 보시면될것같습니다.

    답글삭제
  3. I really love your website.. Pleasant colors & theme.
    Did you create this website yourself? Please reply back as I'm hoping to create my own personal site and would like to learn where you got this from or exactly what the theme is named. Cheers!

    My blog: cellulite treatment cream

    답글삭제
  4. 많은 도움되었습니다. 감사합니다.

    답글삭제
  5. 정말 많은 도움 되었습니다!!!! 간단하게 정리해 주신부분
    이해 하기 정말 쉽네요!!

    답글삭제
  6. 작성자가 댓글을 삭제했습니다.

    답글삭제
  7. 작성자가 댓글을 삭제했습니다.

    답글삭제
  8. ". 인터페이스는 final을 붙일수 없고 인터페이스 변수들은 static이어야만 한다. 한마디로 인터페이스는 일반변수들을 가질수 없다. "
    라고 하셨는데 상수 선언을 할 수 없다 -> 일반변수만 사용가능하다 아닌가요..?

    답글삭제
    답글
    1. "한마디로 인터페이스는 일반변수들을 가질수 없다" => 상수선언만 가능하다 인데, 댓글 쓰신 분이 잘못 이해하고 계신거 같네요

      삭제
    2. 변수 = 변할수 있는 수
      상수 = 변할수 없는 수

      삭제
    3. 'static final'을 상수라고 상정하고 말씀하신듯. 저도 그 부분 읽을 때에( ...하고 바로 뒷문단에서... )에서 갸웃 했네요. 본문에서도 상수의 정의를 "상수(static final)"라고 표현하고 있는데 말이죠. 쓰다가 뭔가 혼란스러우셨나봐요.

      삭제
    4. 괄호 안에 본문 붙여넣은 부분이 날라갔네요 뭐지...

      삭제
  9. 익명 클래스, 익명 인터페이스 선언

    답글삭제