[스터디할래 08] 인터페이스
이 글은 ‘백기선’ 개발자님과 함께하는 온라인 자바 스터디에 참여하여 준비/학습한 내용을 정리하는 글입니다.📚
- 8주차 : https://github.com/whiteship/live-study/issues/8
- 목표 : 자바의 인터페이스에 대해 학습
정리는 Java Tutorials - Interface를 기반으로 하였습니다.
인터페이스 정의하는 방법
- 자바에서 인터페이스는 클래스와 같은 reference type이다.
- 상수, 메소드 시그니쳐, 기본 메소드, 정적 메소드와 중첩 타입만을 포함하고 있다.
- default method, static method에만 몸체가 존재한다.
- instance화 할 수 없다. 클래스에 의해 구현되거나, 다른 인터페이스가 상속하는 경우만 있다.
- public 이외의 접근제한자 사용 가능
- 다른 interface를 상속할 수 있다. 클래스 상속과 달리 여러 개의 인터페이스를 상속할 수 있다.
- 필드에 접근 제한자를 사용하지 않는 기본으로
public
이다.
public interface SampleInterface {
// constants
static final int sampleCount = 0;
// abstract method
void doSth();
// default method
default void print() {
System.out.println("This is SampleInterface");
}
// static method
static int count() { // implicitly public 이다.
return sampleCount;
}
}
인터페이스 구현하는 방법
- 인스턴스화 할 수 있는 클래스가 인터페이스를
implements
할 경우, 메소드의 몸체를 반드시 구현한다. - 클래스 상속과는 달리 여러 개의 인터페이스를 구현할 수 있다.
public class SampleClass implements SampleInterface, OtherInterface {
@Override
void doSth() {
// 추상 메소드 바디를 반드시 구현
}
}
인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
- 영문으로는 Using an Interface as a Type 이라고도 표현한다.
- 인터페이스도 클래스를 정의하는 것 처럼, 타입을 정의하는 것이다.
- 인터페이스를 정의하는 것은 새로운 reference 타입을 정의하는 것이다. 즉, data type을 사용하는 곳에는 정의한 interface 를 어디든 사용할 수 있다.
- interface type의 변수를 할당할 때에는 해당 인터페이스를 구현한 클래스의 인스턴스여야 한다.
- interface 레퍼런스를 통해 구현체를 사용하면, 특정 구현에 한정하지 않고, 해당 인터페이스를 구현한 모든 클래스를 변수로 받을 수 있어 유연하게 코드 작성을 할 수 있다.
- (from Live class) 객체지향 관점으로 설명하면, 인터페이스를 타입으로 받으면 Loose Coupling(느슨한 결합), Polymorphism(다형성)를 가능하게 한다.
인터페이스 상속
- 기존에 존재하는 인터페이스에서 신규 메소드 추가가 필요할 때에, 기존 인터페이스에 추가하지 않고 새로운 인터페이스를 정의해서 기존 인터페이스를 상속시킨다.
public interface FirstInterface {
void doSthFirst();
// void soSthSecond();
}
// 인터페이스를 사용하는 기존 사용자는 FirstInterface를 그대로 사용할 수도 있고,
// 새로 구현된 SecondInterface를 사용한 후 추가된 메소드를 구현할 수도 있다.
public interface SecondInterface extends FirstInterface {
void doSthSecond();
}
인터페이스의 기본 메소드 (Default Method), 자바 8
- 인터페이스에 신규 메소드가 추가 되는 경우, 모든 클라이언트에서 해당 메소드를 추가로 구현해주어야 한다. 추가 구현된 메소드가 반드시 구현해야 하지 않을 수도 있다.
- 이런 경우 default method를 사용해서, 클라이언트가 다시 메소드를 구현하지 않아도 되는 편의를 줄 수 있다.
- 오픈소스 등 라이브러리를 개발하는 경우 굉장히 유용하다.
default
키워드로 메소드를 정의한다.- default method가 있는 인터페이스를 구현하는 클래스에서는
- 별도로 언급X -> 구현된 메소드를 그대로 상속
- 인터페이스의 입장에서는, redeclare한다. 기존 메소드는
abstract
메도스가 된다. - 클래스의 입장에서는, redefine해서 기존 메소드를 override 한다.
인터페이스의 static 메소드, 자바 8
- 인터페이스에 static 메소드를 정의할 수 있다. 정의된 static method는 인터페이스를 구현하는 클래스에 속하는 클래스 메소드이다.
- 인터페이스에 static 메소드를 추가해서, 인터페이스를 구현하는 클래스들의 공통된 helper method의 역할을 할 수 있다.
- default 메소드와 static 메소드 모두 인터페이스에 미리 구현해두는 메소드이기 때문에 비슷해 보이지만 아래의 차이점을 구분해서 적절한 경우에 사용해야 한다.
- default 메소드 : 인스턴스 멤버이다. 즉, 각 클래스의 인스턴스에 속한다.
- static 메소드 : 인스턴스 멤버가 아니다. 타입. 즉, 인터페이스에 속하는 변수이다.
SampleInterface.staticMethod()
와 같이 사용된다.
인터페이스의 private 메소드, 자바 9
- 인터페이스에 java7에서는 abstract method가, java8에서는 default method가 추가되었다.
- java9 에서는
Support for Private Interface Methods
Private interface methods are supported. This support allows nonabstract methods of an interface to share code between them.
(From https://docs.oracle.com/en/java/javase/15/language/java-language-changes.html#GUID-015392DB-F5C4-4A8E-B190-E797707E7BFB)
처럼, 인터페이스에서 몸체를 가진 private 메소드를 구현할 수 있도록 추가되었다. 이것이 의미하는 바가 무엇인가 ?
- abstract method에 이어 default method가 추가되면서, 인터페이스에도 몸체를 구현할 일이 많아졌다. 예를 들어, 서로 다른 defualt method가 내부에서만 사용되는 동일한 코드를 공유해야 하는 상황을 생각해보면,
- 공통 메서드를 선언하자니, 기존에는 public default 메소드로 외부에 노출되어야 했다.
- private method를 사용하면 선언한다면 외부에 노출하지 않고, interface 에만 몸체를 가진 메소드를 선언할 수 있게 된다. 즉, 더 높은 차원의 encapsulation이 가능하다.
public interface JavaNineInterface {
public abstract void method1();
// 이런 코드가 가능하다.
public default void method2() {
commonMethod();
}
public default void method3() {
commonMethod();
}
private void commonMethod(){
System.out.println("This is common private method");
}
}
라이브클래스 후 추가
- 항상 가장 헷갈렸던 부분이 추상클래스와 인터페이스의 차이이다. java8 이후에 인터페이스에도 default 메소드가 들어가면서, 추상클래스와의 경계가 모호해진 느낌이다. 하지만, 분명 차이는 있고, 둘 다 효용성이 있다.
- 인터페이스에서는 못 하는데, 추상클래스에서만 할 수 있는 것들이 있다. 이를 테면 아래와 같은 경우이다.
public class AbstractSampleClass { private String inputName; public void setInputName(String inputName) { this.inputName = inputName; } }
- 인터페이스는
static
변수만 선언할 수 있는데, 추상클래스에는 인스턴스 변수를 정의할 수 있다. 어떤 의미냐 하면, 추상 클래스는 좀 더 ‘상태’를 지향하는 경우에 사용되어야 한다. - 인터페이스는 어떤 ‘기능’을 위주로 정의할 때에 적합하다.