본문 바로가기

국비필기노트/Java

자바(java)_추상화,추상 메서드,인터페이스,implements

▶추상화를 사용해야하는 이유

 

클래스다이어그램

 

Unit이라고 하는 클래스에 attck()이라고 하는 메서드와 move()라고 하는 메서드를 추가했다고 가정을 해보자 

 

그럼 자식클래가 AirForce, Navy, Army가 attck()과 move()라는 기능을 상속받을 것인데 이 친구들이 move()는 정상적으로 상속받았으나 attck()은 모두 메서드명을 작성하는 과정에서 오타로 인해 오류가 났다. 

 

오버라이드를 사용할 수는 있지만 @Override는 선택적으로 하는것이지 필수는 아니다. 직접 개발자가 수기로 @Override라고 작성을 해줘야하는 부분이라 만약 바쁘거나 신입이거나 대규모프로젝트를 할 경우 개발자가 단순히 깜박한다면 위와 같은 오류의 가능성이 분명히 있을 것이다. 즉, '@Override' 키워드를 사용하지 않고 메서드를 재정의 하는 과정에서 메서드 이름에 실수가 발생하더라도 에러가 아닌 새로운 메서드의 정의로 인식되므로 의도하지 않는 실행결과를 가져올 수 있다는 것이다. 

 

게다가 상속성은 객체간의 공통적인 기능을 관리하기 위한 기법이긴 자식클래스에서 사용을 하지 않는 부모의 메서드가 분명히 있을 것이다. 

 

이러한 불편함을 보완하기 위해 사람들은 필요한 메서드만 부분적으로 공통의 부모로 만들고 그 메서드만 빼와서 사용을 하는 개념을 만들었고 그것이 추상화라는 개념이다.

 

▶추상클래스의 특징: 오버라이드

 

추상 클래스는 '미완성된' 설계도이다.

그래서 추상화 기법은 특정 클래스를 상속받은 경우, 부모의 특정 메서드들을 상속받은 자식 메서드가 무조건 재정의하도록 강제한다.

이는 부모클래스에는 하나의 껍데기(소스코드)만 정의해두고 그 메소드의 실제 동작 방법은 이 메소드를 상속받은 하위 클래스의 책임이다. 

 

즉, 껍데기를 가져와서 양식, 이름만 동일하게 하고 기능은 완전히 자식클래스에서 사용을 위해 재정의 하는 것이 추상클래스의 가장 큰 특징이며 이것이 추상클래스의 오버라이드이다. 

 

▶추상클래스의 장점

 

실체 클래스들의 공통된 필드와 메소드 이름을 통일할 목적이다.

혼자 개발할 때는 상관없지만 여러사람과 협업을 통해서 개발을 할 때에는 이런 통일된 양식이 매우매우 중요하다. 그래서 추상클래스, 인터페이스와 같은 구현부가 없는 추상화된 부분을 통일화된 하나의 양식으로 삼는다.

 

또한 실체 클래스를 작성할 때 시간을 절약해준다.

공통적인 부분만 추상클래스에 작성하고 차이점이 존재하는 부분은 실체클래스에 따로 작성하여 겨로가적으로 다른 것만 작성하면 되기에 효율적이다.

 

▶추상메서드 만들기

 

추상메서드를 정의하기 위해서는 'abstract' 키워드를 사용하여 메서드를 정의한다. 

 

추상 메서드는 자식 클래스가 구현해야 하는 메서드의 가이드라인만 제시하기 위한 목적으로 사용되기 때문에, 선언만 가능하고 구현부가 없다. 

 

 public void attck();{
            //구현부
    }

 

보통의 메서드들은 기능을 구현해야할 "구현부"가 존재하는데 구현을 위한 블록이 존재하지 않아서 자식클래스에서 구현부를 재정의해주어야한다.

 

 public abstract void attck();

 

 추상화 메서드를 선언 할 땐 abstract라는 키워드를 사용하여 attck()가 추상화 메서드라고 정의해준다.

 

 

 

▶추상메서드를 포함한 클래스

 

abstract 즉, 추상메서드를 하나라도 포함하고 있는 클래스는 반드시 '추상클래스'로 정의되어야한다. 

 

public abstract class Hello{
          public abstract void attck();
}

 

추상메서드를 하나라도 포함하는 클래스는 public class Hello 라는 일반 클래스가 아니라 abstract 키워드를 사용하여 추상클래스로 정의 해준다는 의미이다. 

 

 

▶공통기능과 설계 제시를 모두 처리하기

 

 

추상클래스는 생성자, 멤버변수, 일반 메서드등을 포함할 수 있지만 스스로 객체를 생성할 수 없다.

이는 이 밑의 예시와 같은 의미이다.

 

public abstract class Hello{

            //멤버변수
            private String msg;

           //생성자
            pulbic Hello(String msg){
                      this.msg = msg;
                    }

          //일반메서드(getter,setter)
            public String getMsg(){
                     return this.msg;
                    }

             public String setMsg(String msg){
                     this.msg = msg;
                    }

         //선언만 되고 구현부를 위한 블록이 존재하지 않는 추상메서드
              public abstract void sayHello();
}

 

▶추상클래스의 상속화

 

추상클래스는 스스로 객체를 생성할 수 없다는 말을 좀 더 자세히 알아보자.

추상 클래스는 공통기능과 가이드라인을 모두 정의하여 다른 클래스에게 상속되는 것인데 정확히 무엇을 상속하는것일까?

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class Unit {

     private String Unit;
 
 
    public String getUnit() {
        return this.Unit;
    }
 
    public void setUnit(String Unit) {
        this.Unit = Unit;
    }
 
    public abstract void attack();
 
    public abstract void move();
}
 
public class AbstractTest{
public static void main(String[] args) {
    Unit u = new Unit();
}
}
 
cs

 

Unit이라는 추상메서드가 있는데 이 Unit 추상메서드를 바로 Main 메서드에서 호출을 한다고 해보자. 그럼 Unit u = new Unit()이라는 부분에서 바로 오류가 난다. 추상클래스는 어떤 자식클래스에 상속을 시키고 그 자식클래스를 호출해야지만 호출이 가능하기 때문이다.

 

그래 그럼 추상 메서드는 상속을 받아서 사용을 해야한다고 했으니 상속을 했다.

중간에 Unit을 상속받는 Army 메서드를 생성하고 Army 를 호출하였는데

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 
public abstract class Unit {
     private String name;

public Unit(String name) {
  this.name = name;
 
    public String getName() {
        return name ;
    }
 
    public void setUnit(String Unit) {
        this.name = name;
    }
 
    public abstract void attack();
 
    public abstract void move();
}
 
public class Army extends Unit{
(1)
}
 
public class AbstractTest{
public static void main(String[] args) {
    Unit u = new Army();
}
}
cs

 

여전히 Unit u = new Army() 부분에서 에러가 난다.

상속까지 시켜줬는데 이유가 뭘까?

 

attck()이라는 메서드는 구현부는 없고 메서드에 대한 형식만 가지는 추상적인 모습만 가지고 있는 추상메서드이다. 

 

저 추상메서드를 사용하기 위해서는 이 메서드를 반드시 오버라이드 해서 이 메서드에 대한 구체적인 로직을 attck()을 상속받는 Army라는 자식클래스(추상메서드를 사용하는쪽)에서 구체적인 로직을 정의를 해야만 하는 것이다.

 

그래서 (1)번에 public class Army extends Unit{

                       public void attck(){

                            Syso(this.game() + "육상공격");

                        }

 

이처럼 attck()에 없는 구현부를 Army 에서 구현을 해주어야지만 에러가 나지 않는다. 

추상메서드는 구체적인 구현 기능을 사용하는 쪽에 넘기는 것이라고 할 수 있다.

 

 

다형성이란 객체의 이름이 서로 다르더라도 객체의 메서드를 호출할 때 고민하지 않기 위한 기법이다. 

추상화는 다형성을 강제하는 것으로 객체를 사용하는 개발자의 편의를 위한 클래스 작성 규칙이다. 

 

▶추상클래스의 한계

 

 

우리가 게임을 개발한다고 예시를 들어보자. 게임 케릭터는 주인공이 있고 몬스터로 설정하면 주인공과 몬스터의 공통 기능이 있을 것이고 우린 그 공통적인 기능을 정의할 수 있다.

 

//캐릭터를 만들기 위한 부모클래스 

public abstract class Unit{

      public abstract void attck();        //공격

      public abstract void shield();      //방어

      public abstract void walk();       //공격

      public abstract void run();        //공격

      public abstract void jump();     //공격

      public abstract void pickup();  //아이템줍기

 

이렇게 만들었다. 그런데 몬스터가 아이템 줍기 기능이 있으면 될까?

몬스터는 아이템 줍기 기능이 있으면 안된다! 또한 쪼랩이라 간단한 공격과 방어는 가능하지만 아이템 획득은 불가능하다.

 

 

우리는 위의 사진처럼 기능(메서드)들을 가지고 오고싶은데 자바에는 클래스 상속은 무조건 하나만 가능하다.

Unit(싸움 클래스)의 공격, 방어, 아이템줍기 + 이동클래스의 걷기,뛰기,점프를 동시 사용은 불가능 하다는 것이다. 

 

추상클래스로 필요한 메서드만 따로 빼서 몬스터클래스에서 구현화하면 안될까 하지만 추상클래스도 일종의 "상속"이다. 추상클래스는 부분적으로 추상화가 되어있지만 반대로 말하면 일부는 구체화 되어있다. 그러면 일반 클래스에서 나타나는 문제점들이 분명 추상 클래스에도 동일하게 존재할 것이라는 결론이 나온다.  즉, 추상클래스의 다중상속은 무리가 있다는 것이다.

 

그럼 아예 모든 내용이 추상이라면 어떻게 될까?

 

▶인터페이스(interface)

 

인터페이스는 모든 메서드가 추상메서드로 이루어져있다. 

 

추상메서드처럼 실체가 존재하지 않는 것은 속성 역시 실체하지 않는 것으로 어떠한 상속의 경로에도 문제를 일으키지않는 것을 뜻한다.

 

즉, 다중상속이 가능하다는 의미로 추상메서드의 한계를 보완해준다. 

 

▶인터페이스와 추상메서드의 차이점

 

추상메서드 인터페이스
멤버변수, 생성자, 메서드, 추상메서드를 포함할 수 있다. 추상메서드만 포함할 수 있다.
이 클래스를 상속받는 자식 클래스는 다른 클래스를
상속받을 수 없다.
다중상속이 가능하다.
객체의 생성이 불가능하다.

 

 

▶인터페이스의 implements 키워드

 

implements 키워드는 인터페이스의 구현을 위한 키워드라고 볼 수 있다.

인터페이스가 여러개를 동시에 상속받을 수 있게 하는 방법은 콤마(,)로 연결하는 것인데 예시를 봐보자

public class Monter implements Fight, Move{

}

 

Monster는 클래스, Fight와 Move는 상속받고자하는 메서드로 메서드 바로 앞에 implements를 기재하고 Fight와 Move사이에 콤마(,)로 연결함으로 2가지의 메서드 즉, 여러개의 메서드를 가져와 Monster라는 클래스에서 구현시켰다.

 

▶인터페이스의 implements 사용방법

 

인터페이스는 완벽한 추상화를 구현하기 위한 java 클래스이다.

인터페이스 역시 추상화라는 의미이며 추상화의 특징도 그대로 가져온다. 즉, 인터페이스를 상속받는 클래스는 인터페이스내의 모든 메서드들을 반드시 재정의해야한다는 것 이다.

 

 

1
2
3
4
5
6
7
8
 
interface Fight{
        public void attck();
public void shield();
}
 
class Monster implements Fight{
        public void attck(){}
}
cs

 

Fight라고 하는 클래스 앞에는 interface 클래스라고 선언이 되어있다. 즉, 다중 상속이 가능하며 이는 추상클래스로 필요에 의해 용도별로 분할이 가능하다는 의미이다. Monster는 공격만 가능하고 방어는 할 수 없는 쪼랩몬스터로 우리는 attck만 가져올 것이다. 

 

그렇게 Monster는 implements를 통해 Fight라는 클래스를 가져오고 attck()이라는 메서드를 구현할 것이다. 그런데 implements는 인터페이스의 구현을 위해 사용하는 키워드이고 인터페이스는 추상메서드만 포함한다. 즉, attck()은 추상메서드라는 뜻으로 attck() 기능을 가져오고 싶다면 추상메서드 상속과 동일하게 상속을 받는 Monster에서 attck을 다시 재정의 시켜줘야한다는 것이다. 

 

우리가 특정 클래스를 어떤 interface와 결합을 시키면 우리가 클래스를 정의할 때 그 인터페이서 안에 정의되어있는 메서드를 반드시 구현하도록 강제하는 것이며, 이를 interface를 구현한다라고 이야기한다.

 

여기서 interface는 "상속"이 아닌 "구현"이라는 단어를 계속해서 사용함을 꼭 기억하고 있어야한다.

 

▶인터페이스의 규칙

 

-. 접근 한정자는 반드시 public이여야만한다.

 

인터페이스는 외부에서 조작을 하기 위한 조작장치라고 할 수 있다. 

의도적으로 보유하고 있는 기능을 외부로 노출시키고 다른 메서드의 기능을 제어하기 위해 반드시 public 으로 접근 한정자를 설정해야하며 이를 지키지 않을 경우엔 컴파일 에러가 난다.

 

-. 인터페이스도 상속이 가능하다.

 

interface Move{

         public void x();

}

 

interface Attck() extends Move{

         public void z();

}

 

class hero implement Attck(){

           public void x();

           public void z();

}

 

글을 작성하면서 "구현"과 "상속"라는 단어를 계속해서 구분하여 사용하였는데

위의 예시를 보면 Move는 X라는 메서드를 "구현"하도록 강제하고 

Attck()은 Move의 x기능을 "상속" 받았다. 

상속과 구현은 다른 의미임으로 구분하여 단어를 사용하는 것이 좋다.

 

다시 본론으로 들어가서 hero 는 Attck()이라는 메서드를 구현을 한다면 반드시 Attck()이 가지고 있는 메서드를 반드시 구현해야하며 Move가 가지고 있는 x역시 반드시 구현을 해야만 컴파일이 정상 작동된다. 

 

즉, implement는 재정의한다는 인터페이스만의 특징을 그대로 가지고 오면서 마치 클래스처럼 부모 인터페이스가 가지고 있는 기능들을 자식 인터페이스들이 가질 수 있다는 상속의 기능도 보유하고 있다. 

 

근데 만약 필요한경우 다른 클래스와 동시에 상속을 받을 수도 있다!

public class Monter extends Character implements Fight, Move{

}

 

이는 Monster라는 클래스에 Character라는 클래스를 상속받아 Monster는 Character의 자식 클래스가 되었고 동시에 Fight와 Move라는 기능(메서드)를 상속받았다. extends와 implements를 동시에 할 수 있다는 것을 기억해두자

 

 

▶인터페이스 실제 사용 예시

 

예를 들어보자면 A라는 회사에서 현재 가계부어플을 만들고 있는데 인력부족으로 차마 기능 하나하나를 하나하나 기능을 넣고 만들 시간이 없어 이 작업을 B라는 회사에서 외주를 주었다.

 

3개월간의 시간을 B회사에 주었고 B회사는 3개월 동안 열심히 기능들을 만들었을 것이다. 

근데 그 3개월동안 A라는 회사에서도 다른 코딩을 하려면 그 기능이 필요하고 3개월동안 잠깐동안 사용할 임시 기능 클래스를 간단하게 만들어 사용한다.

 

Class CalculatorDummy

   public void setOprands(int first, int second){}

   public int sum(){

         return 50;

   }

   public int avg(){

         return 30;

   }

 

Dummy를 붙혀 임시코드를 붙혔다고 지정하고 sum와 avg 기능에 대한 내용들을 간단하게 만들어 업무를 했다고 해보자.

근데 3개월 후 B라는 회사에서 결과물을 줬는데 커뮤니케이션 오류로 인해 B는 

 

Class CalculatorDummy

   public void setOprands(int first, int second,int third){}

   public int sum(){

         return 50;

   }

   public int avg(){

         return 30;

   }

 

받는 인수값을 원래는 2개인데 3개로 넣어버린 것이다. 지금 이 코드는 매우 간단한 코드이기에 오류내용을 알고 바로 변경이 가능하지만 실제로 이 로직이 매우 복잡하고 크고 많은 사람들이 참여했다면 수정작업은 정말 힘든일이 될 것이다.

 

이를 막기위해 사람과 사람간의 구두가 아닌 자바가 가지고있는 하나의 특성을 가지고 규칙을 정한다면 훨씬 안정적이고 정확한 업무를 할 수 있고 좀 더 엄격한 약속을 할 수 있다고 사람들은 생각했고 그 때 interface라는 키워드를 사용을 하자고 약속을 했다.

 

Class interface Calculatable

   public void setOprands(int first, int second,int third);

   public int sum();

   public int avg();

 

그래서 다음부턴 A와 B회사는 일을 하기전에 interface 먼저 서로 합의하여 로직을 짜둔다.

그래서 B개발자의 담당자가 바뀌어도 저기 interface만 봐도 Calculator는 setOprands라는 메서드를 가지고 있고 정수형식의 인자값 3개를 받는다. 그리고 sum과 avg라는 메서드가 있으며 이를 int값으로 받는다는 정보를 바로 캐치할 수 있다.

 

그리고 A라는 개발자는 임시 Calculator코드를 사용할 때, 

   

Class CalculatorDummy implements Calculatable

   public void setOprands(int first, int second,int third){}

   public int sum(){

         return 50;

   }

   public int avg(){

         return 30;

   }

 

public static void main(Stirng[] arg){

      CalculatrDummy c = new CalculatorDummy();

{

 

CalculatorDummy를 불러오더라도 이 클래스는아까 B회사 같이 정한 Calculatable을 implement하기에 Calculatable과 동일한 로직만 구현이 가능하다. 이는 impelment는 추상클래스로 동일한 로직으로 재정의해야한다라는 특성을 활용한 방법으로 잘못된 값을 입력을 하면 컴파일에서 오류가 나기 때문에 이는 자바의 기능으로 정한 강력한 약속이라고 볼 수 있다.

 

그리고 B회사 역시 

 

Class Calculator implements Calculatable{

 int first, second, third;  

 public void setOprands(int first, int second,int third){           

       this.first = first;

       this.second = second;

       this.third = third;

   }

 

로 Calculatable을 implements와 이어져서 사용을 하기에 서로 정한 약속 안에서 좀 더 편하게 코딩을 할 수 있다. 그래서 B가 작업을 모두 끝내고 A라는 회사에 코드를 넘긴다면 A라는 회사는  CalculatrDummy c = new CalculatorDummy(); 에서 Dummy를 뺀 CalculatorDummy c = newCalculator(); 로 생성을 하면 별다른 오류없이 바로 sum과 avg라는 기능을 정상적으로 사용할 수있다.