목표
학습할 것(필수)
- 인터페이스 정의하는 방법
- 인터페이스 구현하는 방법
- 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
- 인터페이스 상속
- 인터페이스의 기본 메소드 (Default Method), 자바 8
- 인터페이스의 static 메소드, 자바 8
- 인터페이스의 private 메소드, 자바 9
인터페이스
- 클래스의 계약 또는 청사진으로 작용하는 참조 유형
- 구현을 갖지 않는 추상 메소드의 집합을 정의하며 해당 인터페이스를 구현하는 클래스가 제공해야 하는 메소드를 정의함
- 다른 클래스를 작성할 때 기본이 되는 틀을 제공하며 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스
- 다중상속
- 자식 클래스가 여러 부모 클래스를 상속받으면 다양한 동작을 수행할 수 있지만 클래스를 이용해 다중 상속을 하는 경우 메소드 출처의 모호성 등 여러 가지 문제가 발생할 수 있어 클래스를 통한 다중상속은 자바에서 지원하지 않음
- 그렇기 때문에 인터페이스를 통해 다중상속을 지원함
- 인터페이스는 추상 메소드와 상수만을 포함할 수 있음
- 자바 버전이 올라가며 다양한 접근 제어자 메소드들도 포함할 수 있게 변경됨
- 추상클래스는 추상 메소드뿐만 아니라 생서자, 필드, 일반 메소드도 포함할 수 있음
인터페이스의 장점
- 추상화와 계약 선언 = 클래스가 구현해야 하는 메소드를 정의하여 일관성과 유지보수성 향상
- 다중 상속과 유연성 = 다중 상속의 효과를 얻어 여러 클래스의 기능을 조합하여 유연하고 재사용 가능한 코드 작성
- 다형성 구현 = 다양한 구현체를 동일한 인터페이스로 다루어 코드의 유연성과 확장성 증가
- 코드의 모듈화와 재사용성 = 코드 모듈화 및 구현 세부 정보 감추기로 코드 재사용성 및 유지보수 비용 감소
- 표준화와 협업 = 표준화된 계약 제공으로 협업과 의사소통 간소화, 모듈 간 상호 작용 정의 및 개발자 역할 분리 가능
인터페이스 정의하는 방법
// 접근제어자 interface 인터페이스 이름
public interface MyInterface {
// 상수 필드
int MAX_VALUE = 10;
// public static final int MAX_VALUE = 10;
// 추상 메소드 선언
void method1();
int method2(String str);
void method3(int num);
// public abstract void method3(int num);
}
- 기능에 대한 구현보다 선언에 초점을 맞춰 사용
- 인터페이스 내에 존재하는 메소드는 public abstract로 선언되며 생략가능
- 인터페이스 내에 존재하는 필드는 public static final로 선언되며 생략가능
- 컴파일 시 자바 컴파일러가 자동으로 생략된 제어자를 추가해 줌
인터페이스 구현하는 방법
// 접근제어자 class 클래스이름 implements 인터페이스이름
public class MyClass implements MyInterface {
// 인터페이스 메소드 구현
public void method1() {
// 메소드 구현
System.out.println("method1");
}
public int method2(String str) {
// 메소드 구현
System.out.println("method2 : " + str);
return 0;
}
public void method3(int num) {
// 메소드 구현
System.out.println("method3");
}
}
- implements를 사용하여 새로운 클래스를 작성해 구현해야 함
- 인터페이스는 추상클래스와 마찬가지로 자신이 직접 인스턴스를 생성할 수 없음
- 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성해야만 함
인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
public class MyClass implements MyInterface {
//생략
...
public static void main(String[] args) {
MyInterface myInterface = new MyClass();
myInterface.method1();
}
}
- 인터페이스를 선언하고 필요한 메소드를 정의
- 인터페이스를 구현하는 클래스 작성
- MyInterface myInterface = new MyClass(); -> 인터페이스의 레퍼런스를 선언하고 구현체의 객체를 할당
- myinterface.method1() -> myinterface를 사용하여 구현체의 메소드를 호출
인터페이스 상속
public interface ParentInterface {
void parentMethod();
}
public interface ChildInterface extends ParentInterface {
void childMethod();
}
- extends 키워드를 사용하여 인터페이스에서 인터페이스를 상속할 수 있음
- 인터페이스의 상속은 자바에서 인터페이스 간의 관계를 나타내는 개념임
- 클래스와 다르게 다중상속을 지원하며 인터페이스가 인터페이스를 상속할 수도 있음
- 인터페이스 간의 계층 구조를 형성하고 코드의 재사용성을 높일 수 있음
- ChildInterface를 구현하는 구현체는 parentMethod와 childMethod를 모두 구현해야 함
public interface InterfaceA {
void methodA();
}
public interface InterfaceB {
void methodB();
}
public interface InterfaceC extends InterfaceA, InterfaceB {
void methodC();
}
- 인터페이스는 다중 상속을 지원함
- InterfaceC를 구현하는 구현체는 methodA, methodB, methodC를 모두 구현해야 함
- 인터페이스의 상속을 통해 계층적인 구조를 형성할 수 있으며 코드의 구조화와 재사용성을 증가시킬 수 있음
- 인터페이스의 상속은 다형성을 강화하고 유연성을 제공하여 다양한 인터페이스 타입으로 객체를 다룰 수 있음
인터페이스의 기본 메소드 (Default Method), 자바 8
public interface JoinMember {
void preJoin();
void afterJoin();
}
- Java8 이전에는 인터페이스에서는 나는 preJoin 만 구현하고 싶었는데 DefaultJoinMember 자체를 implements 하게 되면 preJoin, afterJoin 둘 다 구현해야 했음
- 그래서 interface 자체를 implements 하는 게 아니라 interface를 implements 한 adapter 클래스를 extends 하여 사용하게 됨
public class JoinMemberAdapter implements JoinMember {
// 원하는 메소드만 사용하기 위한 adapter 클래스
// 이게 adapter 패턴은 아니고 편의성으로 사용하는 거임
// HandlerInterceptor 클래스, XXXConfigurer, XXXConfiguration, XXXConfigurerAdapter
@Override
public void preJoin() {
System.out.println("반갑습니다");
}
@Override
public void afterJoin() {
System.out.println("가입감사합니다");
}
}
public class HelloJoinMember extends JoinMemberAdapter {
@Override
public void preJoin() {
System.out.println("반갑습니다.");
}
}
- JoinMemberAdapter를 extends 하여 원하는 메소드만 구현
- 이 경우 상속은 한 번밖에 되지 않기 때문에 추가 상속을 사용하기가 어려움 -> interface로 바뀌면 상속을 추가로 가능하게 됨
public interface DefaultJoinMember extends StaticJoinMember {
default void preJoin(){
System.out.println("멤버 반갑습니다");
}
default void afterJoin(){
System.out.println("멤버 가입감사합니다");
}
}
public class DefaultHelloJoinMember implements DefaultJoinMember {
@Override
public void preJoin() {
System.out.println("반갑습니다.");
}
}
- Java8 이후에는 default를 사용할 수 있게 되어 추가로 adapter class를 두지 않아도 됨
- 인터페이스에서 기본적인 구현을 제공하는 데 사용되며 default 메소드는 인터페이스에서 메소드를 정의하고 해당 인터페이스를 구현한 클래스에서 선택적으로 오버라이딩할 수 있음
인터페이스의 static 메소드, 자바 8
public interface StaticJoinMember {
static void preJoin() {
System.out.println("static pre join member");
}
static void afterJoin() {
System.out.println("static after join member");
}
}
public class StaticJoinMemberImpl implements StaticJoinMember {
public static void main(String[] args) {
StaticJoinMemberImpl member = new StaticJoinMemberImpl();
// member.preJoin(); // 불가능
// StaticJoinMemberImpl.preJoin(); // 불가능
StaticJoinMember.preJoin(); // 이렇게만 사용 가능
}
}
- 이전에는 인터페이스에서는 추상 메소드만 정의할 수 있었지만, static 메소드를 추가함으로써 인터페이스에 유틸리티 메소드나 기본 구현을 제공할 수 있게 됨
default 메소드, static 메소드 백기선님 팁
- 기본 메소드와 static 메소드는 하위 호환성을 위해 등장함 -> 예를 들어 내가 어떤 오픈소스를 사용하고 있는데 그 오픈소스에서 기능을 추가하기 위해 interface에 메소드를 추가할 시 기존에 사용하던 사용자들 그 메소드를 구현을 하지 않아 갑자기 에러가 발생할 것 -> 이를 방지하기 위해 등장
인터페이스의 private 메소드, 자바 9
public interface DefaultJoinMember {
default void preJoin(){
printMessage("멤버 반갑습니다");
}
default void afterJoin(){
printMessage("멤버 가입감사합니다");
}
private void printMessage(String message) {
System.out.println(message);
}
}
- pirvate 메소드는 인터페이스 내부에서만 호출할 수 있음
- default 메소드와 함께 사용하여 default 메소드의 구현을 지원하고 코드 중복을 방지하며 코드의 가독성을 높이는 데 사용함