1. 인터페이스
인터페이스는 일종의 추상클래스이다. 추상클래스와 같이 추상메서드를 가지고 있지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 구현부를 가진 일반 메서드는 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있다.
추상클래스가 부분적으로 미완성 된 추상메서드를 가지고 있는 미완성 설계도라면, 인터페이스는 아무런 구현이 되어있지 않은 기본 설계도라고 할 수 있다. 아무 로직을 가지고 있지 않은 상태이기 때문에 그 자체로는 클래스의 역할을 할 수 없으나 다른 클래스를 작성하는데 밑바탕이 되는 설계 역할을 할 수 있다.
2. 인터페이스 작성
인터페이스는 class가 아닌 'interface' 키워드를 통해 선언한다. 그리고 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.
interface InterfaceName {
// 멤버변수로 상수를 정의한다.
public static final int ConstantName = 10;
// 선언부만 가진 추상메서드를 선언한다.
public abstract void methodName(int param1, String param2);
}
인터페이스의 멤버들은 다음과 같은 제약사항을 가진다.
- 모든 멤버변수는 public static final 이어야 하며 이를 생략할 수 있다.
- 모든 메서드는 public abstract 형태로 선언되어야 하며 이를 생략할 수 있다. 단 static 메서드와 디폴트 메서드는 예외이다.
public static final이나 public abstract 는 인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있으며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.
원래 인터페이스의 모든 메서드는 추상메서드이어야 하는데, jdk1.8 에서부터는 인터페이스에 static 메서드와 디폴트 메서드의 추가를 허용하는 방향으로 변경되었다. 이를 통해서 jdk1.8 이후부터는 구현부가 존재하는 메서드도 작성할 수 있다.
3. 인터페이스의 상속
인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속을 허용한다.
interface If1 { ... }
interface If2 { ... }
interface ChildInterface extends If1, If2 { ... }
클래스와 동일하게 조상 인터페이스로부터 멤버들을 상속받는다.
※ 인터페이스는 클래스와 달리 Object 클래스와 같은 최고 조상은 없다.
4. 인터페이스의 구현
인터페이스도 추상클래스와 같이 그 자체로는 인스턴스를 생성할 수 없다. 클래스와 같이 다른 클래스가 인터페이스를 상속받아서 구현부를 작성해야 한다. 상속의 방법으로는 추상클래스는 확장의 의미로 extends 키워드를 사용하는 것과 같이 인터페이스는 구현을 의미하는 'implements' 를 키워드로 사용하여 상속한다.
// 인터페이스를 정의한다.
interface IF {
public abstract void method1();
public abstract void method2();
}
// 인터페이스 IF를 구현한다.
class Impl implements IF {
public void method1() {
System.out.println("method1");
}
public void method2() {
System.out.println("method2");
}
}
// 만약 클래스에서 인터페이스의 멤버를 모두 구현하지 않는 경우에는 abstract 키워드를 통해 추상 클래스임을 알려준다.
// 해당 클래스는 method1 메서드는 구현하였지만 method2 메서드는 추상메서드 그대로 상속받은 추상클래스 상태이다.
abstract class Abs implements IF {
public void method1() {
System.out.println("method1");
}
}
// 구현은 상속과 동시에 일어날 수 있다.
class Child extends Parent implements IF {
public void method1() {
System.out.println("1");
}
public void method2() {
System.out.println("2");
}
}
인터페이스의 메서드를 구현시에 주의할 점은 접근제어자가 public이라는 것이다. 오버라이딩을 할때는 항상 상속받은 조상 클래스의 메서드의 접근제어자와 같거다 보다 넓은 영역을 허용해주는 접근제어자를 사용해야 한다. 인터페이스의 경우 항상 public 이기 때문에 상속받아 구현시에도 public으로 선언해주어야 한다.
5. 인터페이스를 이용한 다중상속
자바에서는 기본적으로 다중상속을 지원하지 않는다. 하지만 실제로 잘 사용되지는 않으나 인터페이스를 사용하는 경우 자바에서도 다중상속이 가능하다.
인터페이스를 이용한 다중상속은 상속받아야 할 클래스 중 가장 비중이 큰 클래스만을 상속받고 나머지 클래스들에 대해서는 이에 대응하는 인터페이스를 작성후 이를 구현하는 형식으로 다중상속한다. 상속받지 않는 나머지 클래스들은 인스턴스를 생성하고 이 인스턴스를 사용해 인터페이스를 구현하여 해당 클래스를 상속받은 것과 같은 결과를 얻도록 한다.
// 다음의 두개의 클래스 TV, VCR을 상속받으려고 한다.
// 둘 중 TV 클래스의 비중이 더 크기 때문에 TV를 상속받고 VCR 클래스는 인스턴스를 생성하여 사용할 것이다.
public class TV {
protected boolean power;
protected int channel;
protected int volume;
public void power() { ... }
public void channelUp() { ... }
public void channelDown() { ... }
public void volumeUp() { ... }
public void volumeDown() { ... }
}
public class VCR {
protected int counter;
public void play() { ... }
public void stop() { ... }
public void reset() { ... }
public int getCounter() { ... }
public void setCounter(int counter) { ... }
}
// VCR 인스턴스를 통해 구현할 인터페이스를 작성한다.
public interface IVCR {
public void play();
public void stop();
public void reset();
public int getCounter();
public void setCounter(int counter);
}
// TV 클래스를 상속받고 VCR 클래스는 인스턴스를 생성하여 IVCR 인터페이스를 구현한다.
public class TVCR extends TV implements IVCR {
VCR vcr = new VCR();
public void play() {
vcr.play();
}
public void stop() {
vcr.stop();
}
public void reset() {
vcr.reset();
}
public int getCounter() {
return vcr.getCounter();
}
public void setCounter(int counter) {
vcr.setCounter(counter);
}
}
위와 같이 인터페이스를 사용하여 다중상속을 구현할 수 있다. 물론 IVCR과 같이 인터페이스를 새로 작성하여 상속을 구현하지 않고 단순히 vcr 인스턴스를 생성하는 것만으로도 구현할 수 있다. 하지만 인터페이스를 이용하면 다형적 특성을 이용할 수 있다는 장점이 있다.
6. 인터페이스를 이용한 다형성
자바에서는 다형성을 통해서 자손 클래스의 인스턴스를 조상 타입의 참조변수로 참조 가능하다. 인터페이스도 이와 같이 다형성을 통해 인터페이스를 구현한 클래스를 해당 인터페이스 타입의 참조변수로 참조할 수 있으며 인터페이스 타입으로의 형변환도 가능하다.
// 인터페이스 If와 이를 구현한 클래스 Impl이 있는 경우 다음과 같이 사용이 가능하다.
// Impl 인스턴스를 If 타입으로 형변환하여 참조가 가능하다.
If var = (If) new Impl();
// 매개변수의 타입으로도 사용가능하다.
// 인터페이스 타입을 매개변수로 사용하는 경우에는 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다.
// 해당 인터페이스를 구현한 클래스라면 어떤 클래스라도 인스턴스를 생성하여 매개변수로 사용할 수 있다.
void method(If argument) { ... }
// If 인터페이스를 구현한 Impl 클래스의 인스턴스를 매개변수로 제공
method(new Impl());
// 메서드의 반환 타입으로도 인터페이스를 사용할 수 있다.
// 이 경우도 매개변수와 같이 인터페이스를 구현한 클래스의 타입을 반환한다는 의미가 된다.
If method() { ... }
이러한 인터페이스의 다형성을 이용하게되면 보다 유연하게 프로그래밍할 수 있기 때문에 유지보수에 유리하다. 반환타입을 인터페이스로 하는 경우에 새로운 클래스 타입의 인스턴스를 추가하기 쉽다. 클래스가 해당 인터페이스를 구현하도록 하기만 한다면 메서드에 특별한 로직을 추가하지 않더라도 새로운 클래스의 인스턴스를 반환하도록 할 수 있다.
7. 인터페이스의 장점
- 개발시간 단축
인터페이스가 작성되면 이를 통해서 인터페이스를 사용하는 쪽과 인터페이스의 구현부를 구현하는 쪽이 동시에 개발을 진행할 수 있다. 인터페이스의 메서드를 참고하여 이를 사용하는 쪽이 개발을 진행하고, 구현부를 작성하는 곳에서는 메서드의 선언부를 가져와서 로직을 개발하면 된다. 이렇게 동시에 개발을 진행함으로 개발 시간이 단축된다.
- 프로그램 개발 표준화
프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 후에 개발을 진행하면 보다 일관되고 정형화된 프로그램의 개발이 가능하다.
- 관계없는 클래스들 간의 관계 생성
서로 상속관계에 있거나, 같은 조상 클래스를 공유하지도 않은 클래스들 간에 관계를 생성할 수 있다. 하나의 인터페이스를 공통적으로 구현하도록 하여 관계를 생성할 수 있다.
- 독립적인 프로그래밍
인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제 구현에 독립적인 프로그램을 작성하는 것이 가능하다. 인터페이스를 이용하여 클래스 간에 간접적인 관계를 맺어준다면 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.
8. default 메서드와 static 메서드
jdk1.8 부터는 인터페이스에 추상메서드가 아닌 구현부를 가진 메서드를 포함시킬 수 있는데, 바로 default 메서드와 static 메서드이다.
- default 메서드
기존에 존재하는 인터페이스에 새로운 메서드를 추가하는 작업은 굉장히 큰 작업이다. 해당 인터페이스를 구현하는 모든 클래스에 새로운 메서드를 구현해주어야 하기 때문이다. 이러한 문제를 해결하기 위해서 고안한 것이 디폴트 메서드이다.
디폴트 메서드는 추상메서드가 아닌 기본적인 구현이 되어있는 메서드로, 추상메서드가 아니기 때문에 인터페이스를 구현한 클래스에서 구현하지 않아도 된다.
디폴트 메서드는 default 키워드를 메서드 앞에 붙여서 작성하며, 추상메서드와 달리 메서드의 몸통이 존재한다. 디폴트 메서드 역시 접근 제어자가 public 이며, 이를 생략할 수 있다.
디폴트 메서드를 인터페이스에 추가하는 경우 기존의 메서드와 이름이 중복되는 문제가 발생할 수 있다. 그러한 경우 다음의 규칙을 따라 해결한다.
1) 여러 인터페이스의 디폴트 메서드 간의 충돌: 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야한다.
2) 디폴트 메서드와 조상 클래스의 메서드 간의 충돌: 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
'프로그래밍언어 > JAVA' 카테고리의 다른 글
[JAVA] 예외처리 (Exception handling) (0) | 2021.12.07 |
---|---|
[JAVA] 내부 클래스 (Inner class) (0) | 2021.11.29 |
[JAVA] 추상클래스 (abstract class) (0) | 2021.11.20 |
[JAVA] 다형성 (Polymorphism) (0) | 2021.11.19 |
[JAVA] 제어자 (Modifier) (0) | 2021.11.16 |