Back-end/Java

다시 하자 기초! 추상,인터페이스

이안_ian 2019. 4. 20. 22:24
반응형

추상클래스

클래스를 설계도에 비유하자면 추상 클래스는 미완성 설계도에 비유할 수 있다. 미완성 뜻 그대로 완성되지 못한 채로 남겨진 설계도를 말한다. 미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로 인스턴스를 생성할 수 없다. 그래서 추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.

 

추상클래스 자체로는 클래스의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다. 새로운 클래스를 정의 할 때 아무것도 없는 상태에서 시작하는 것보다는 완전하지는 못하더라도 어느정도 틀을 갖춘 상태에서 시작하는 것이 나을 것이다.

 

추상클래스는 키워드 abstract를 붙이기만 하면 된다. ex) abstract class 클래스 이름{ ... }

이렇게 함으로써 이 클래스를 사용할 때, 선언부를 보고 추상 메서드가 있으니 상속을 통해 구현해 주어야 한다는 것을 쉽게 알 수 있을 것이다. 추상메서드를 포함한다는 것 빼고는 일반 클래스와 똑같다. 생성자와 변수, 메서드도 있다.

 

추상 메서드

메서드를 이와 같이 미완성 상태로 남겨 놓는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서 선언부만 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워두는 것이다. 그래서 추상클래스를 상속받는 자손 클래스는 조상의 추상 메서드를 상황에 맞게 적절히 구현해주어야 한다.

 

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

 

실제 작업내용인 구현부가 없는 메서드가 무슨 의미가 있을까 싶기도 하겠지만 메서드를 작성할 때 실제 작업내용보다 중요한것이 선언부이다. 메서드의 이름과 메서드 작업에 필요한 매개변수 그리고 어떤 타입으로 리턴할지 결정하는게 쉽지 않은 일이다. 선언부만 작성해도 절반이상이 완성된 것이기에 그 나름대로의 의미가 있다.

 

인터페이스

일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있다. 그 외의 어떠한 요소를 허용하지 않는다.

 

인터페이스의 작성

인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class 대신 interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스와 같이 접근 제어자로 public 또는 default를 사용할 수 있다.

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

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

public abstract는 생략가능하다. 단, static메서드와 디폴트 메서드는 예외(JDK 1.8부터)

 

인터페이스의 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다. (인터페이스는 클래스와 달리 Object클래스 같은 최고 조상이 없다)

interface Movable{
 //지정된 위치(x,y)로 이동하는 기능의 메서드
 void move(int x, int y);
}

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

interface Fightable extends Movable, Attackable { }

클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속 받는다. 그래서 Fightable 자체에는 정의된 멤버가 하나도 없지만 조상 인터페이스로부터 상속받은 두 개의 추상 메서드, move(int x, int y)와 attack(Unit u)을 멤버로 갖게 된다.

 

인터페이스의 구현

인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스를 정의 하는 것과 다르지 않다. 다만 클래스는 확장한다는 의미의 extends를 사용하지만 인터페이스는 implements를 사용할 뿐이다.

 

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

다형성을 학습할 때 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 것을 배웠다. 인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.

Fightable f = (Fightable) new Fighter();

or

Fightable f = new Fighter();

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

void attack(Fightable f) { ... }

인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 것이다. 그래서 attack 메서드를 호출할 때는 매개변수로 Fightable 인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다. 위와 같이 Fightable 인터페이스를 구현한 Fighter클래스가 있을 때, attack메서드의 매개변수로 Fighter인스턴스를 넘겨줄 수 있다. 즉 attack(new Fighter())와 같이 할 수 있다는 뜻이다.

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

Fightable method(){
 ...
 Fighter f = new Fighter();
 return f;
}

리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

위 문장은 외울 때까지 반복해야한다.

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

자바의 정석 3판 388page

 

인터페이스의 장점

1. 개발시간을 단축시킬 수 있다.

-메서드를 호출 하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다. 그리고 동시에 한쪽에서 인터페이스를 구현하는 클래스를 완성 시키면 세부 구현하는 사람은 기다리지 않고 양쪽에서 동시에 개발을 진행할 수 있다.

 

2. 표준화가 가능하다.

-기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램 개발이 가능하다.

 

3. 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.

-서로 관계없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어줄 수 있다.

 

4. 독립적인 프로그래밍이 가능하다.

-클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.

 

자바의 정석 391page를 보면 아주 자세한 예제를 볼 수 있다.

 

인터페이스의 이해

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

선언(설계)와 구현을 분리시키는 것이 가능하게 한다.

-클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.

-메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다. (내용은 몰라도 된다.)

 

왼쪽과 같이 A에서 바로 B를 호출해 사용하는 직접적인 관계에선 직접 연결되어 있기 때문에 둘중 하나만 변경 되더라도 영향을 바로 미치게 된다. 하지만 오른쪽과 같이 중간에 인터페이스를 둠으로써 간접적인 관계를 하게 되었을 경우 내용이 변경 되더라도 I와 B간의 변경을 있을 수 있지만 A와 B는 간접적인 관계이기에 아무런 변화가 없다. 또한 C라는 객체를 추가 하려고 할 때도 인터페이스 I 를 상속받아 구현부만 작성해주면 A에서 호출 할 때 인스턴스만 변경해 생성하면 똑같이 사용이 가능하다.

 

그리고 직접적인 관계에서는 B가 완성되어야 A도 마저 작성할 수 있지만 인터페이스를 중간에 끼게 되면 반환형과 매개변수를 알기에 A,B클래스의 작업이 동시에  가능하기 때문에 개발시간이 단축 될 수 있다는 것이다.

 

디폴트 메서드와 static메서드

원래는 인터페이스에 추상 메서드만 선언할 수 있는데 JDK 1.8부터 디폴트 메서드와 static메서드도 추가할 수 있게 되었다. static메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 예전부터 인터페이스에 추가하지 못할 이유가 없었다. 

디폴트 메서드를 사용하는 이유로는 기존의 조상 클래스에 새로운 메서드를 추가하는 일은 별거 아니지만 인터페이스에서는 아주아주 큰일이다. 왜냐하면 새로운 메서드를 추가하면 그 인터페이스를 상속받은 모든 클래스에 새로운 메서드가 추가되어야 하기 때문이다. 그렇기에 인터페이스에 메서드를 추가해도 상속받은 클래스들이 꼭 그 메서드를 구현하지 않아도 된다는 의미로 디폴트 메서드가 생겨났다.

void newMethod(); //추상 메서드 -> default void newMethod(){ }; //디폴트 메서드

새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우 아래의 규칙을 따른다.

 

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

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

 

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

-조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

 

 

반응형