[JAVA] 인터페이스

용용학생·2024년 4월 24일

자바

목록 보기
21/32

인터페이스

  • 일종의 추상 클래스
  • 추상 클래스보다 추상화 정도가 높아서 일반 메소드나 멤버변수를 가질 수 없음
  • 오직 추상메소드상수만을 멤버로 가짐
  • 추상 클래스가 미완성 설계도라면,
    인터페이스는 구현된 것이 아무 것도 없는 기본 설계도

인터페이스의 작성

클래스를 작성하는 것과 같다. 그러나 class 키워드 대신 interface 키워드를 사용한다. 접근제어자로 public이나 default를 사용할 수 있다

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

일반적인 클래스와는 다르게 인터페이스에는 몇 가지 제약사항이 있다.

  1. 모든 멤버변수는 public static final이어야 하며, 생략할 수 있다.
  2. 모든 메소드는 public abstract이어야 하며, 생략할 수 있다.
    • 단, static 메소드와 디폴트 메소드는 예외( JDK 1.8부터 )

인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있고, 생략된 제어자는 컴파일러가 자동적으로 추가해준다.

인터페이스의 상속

  • 인터페이스는 인터페이스끼리만 상속받을 수 있다.
  • 클래스와는 달리 다중상속을 허용한다.
    • 메소드의 구현부가 없어서 충돌 가능성이 없다.
  • 클래스와 달리 Object 같은 최고 조상이 없다.
interface Movable {
	void move(int x, int y);
}

interface Attackable {
	void attack(Unit u);
}

interface Fightable extends Movable, Attackable { }

클래스의 상속과 마찬가지로 상위 인터페이스에 정의된 멤버들을 모두 상속받는다.

인터페이스의 구현

인터페이스는 추상 클래스처럼 그 자체로는 인스턴스를 생성할 수 없다.
인터페이스에 정의된 메소드를 구현해주는 클래스를 작성해서 인스턴스를 생성해야 한다.

추상 클래스를 상속할 때는 extends라는 키워드를 사용했지만,
인터페이스는 구현한다는 의미에서 implements를 사용한다.

class Fighter implements Fightable {
	public void move(int x, int y) { // 캐릭터 이동 }
    public void attack(Unit u) { // 기본 공격하기 }
}

인터페이스의 모든 메소드를 구현해야 인스턴스가 완전한 클래스가 된다.
만약, 메소드 중 일부만 구현한다면 추상클래스로 선언해야 한다.

abstract class Fighter implements Fightable {
	public void move(int x, int y) { // 캐릭터 이동 }
}

또한 상속과 구현을 동시에 할 수도 있다.

class Fighter extends Unit implements Fightable {
	public void move(int x, int y) { // 캐릭터 이동 }
    public void attack(Unit u) { // 기본 공격하기 }
}

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

자식 클래스의 인스턴스를 조상 클래스의 참조변수가 참조하는 다형성을 배웠다.

인터페이스 역시 이를 구현한 자식 클래스의 조상이기 때문에 다형성을 적용할 수 있다.


  • Fighter 클래스가 Fightable 인터페이스를 구현했을 때, 다음과 같이 쓸 수 있다.
Fightable f = new Fighter();

  • 메소드의 매개변수의 타입으로도 사용될 수 있다.
void attack(Fightable f) {
	// ...
}

인터페이스 타입의 매개변수가 갖는 의미는 메소드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다는 것이다.


  • 메소드의 리턴 타입으로도 사용할 수 있다.
Fightable method() {
	...
    Fighter f = new Fighter();
    return f;
}

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

인터페이스의 구현 메소드

원래 인터페이스의 메소드는 구현부가 없지만 Java 8 부터
default 메소드static 메소드를 통해 구현 메소드를 정의할 수 있게 되었다.

이러한 점 때문에 오히려 인터페이스와 추상 클래스의 차이가 흐려졌다고 말하기도 한다.

default 메소드

조상 클래스에 메소드를 새로 추가하는 것은 별 일이 아니지만,
인터페이스의 경우라면 이야기가 많이 달라진다.

인터페이스의 메소드는 무조건 추상메소드이기 때문에 이를 구현받은 모든 클래스에서 일일이 새로 정의된 메소드를 다 구현해야되기 때문이다.

이런 점 때문에 추상 메소드의 기본적인 구현을 제공하는 디폴트 메소드가 만들어졌다.

  • 메소드 앞에 default 키워드를 붙이며 일반 메소드처럼 구현부가 있어야 함
  • 접근제어자는 public이어야 하며 생략 가능
interface MyInterface {
	void method();
    void newMethod(); // 새로 추가한 메소드
}

newMethod()를 중간에 새롭게 추가하게 되면 이미 구현하고 있는 클래스들에서 직접 구현을 해주어야 한다.

interface MyInterface {
	void method();
    default void newMethod() { } // default 메소드
}

그러나 이렇게 default 메소드를 사용하면 구현하지 않아도 된다.

다중 상속 문제

클래스는 다중 상속을 허용하지 않는다고 했다.
그 이유가 상위 클래스들에 동일한 메소드가 있을 때 처리 과정에서 문제가 발생할 수 있기 때문이었다. (죽음의 다이아몬드 현상)

그런데 인터페이스에서도 default 메소드가 추가되면서 다중 구현할 때 위와 같은 문제가 발생해버린다.

  1. 다중 인터페이스들 간의 디폴트 메소드 충돌
interface A {
	public void methodA();
    default void methodSame() { }
}

interface B {
	public void methodB();
    default void methodSame() { }
}

class MyClass implements A, B {
	@Override
    public void methodA() { }
    
    @Override
    public void methodB() { }
    
    @Override
    public void methodSame() { } // 충돌된 디폴트 메소드 오버라이딩
}
  • 충돌이 발생한 디폴트 메소드가 있을 때 아무런 조치를 취하지 않으면
    컴파일 에러가 발생
  • 인터페이스를 구현한 클래스에서 디폴트 메소드를 무조건 오버라이딩하여 하나로 통합해야 함
  1. 인터페이스의 디폴트 메소드와 부모 클래스의 메소드 간의 충돌
interface A {
	public void methodA();
    default void methodSame() { }
}

abstract class B {
	abstract public void methodB();
    public void methodSame() { }
}

class MyClass extends B implements A {	
	@Override
    public void methodA() { }
    
    @Override
    public void methodB() { }
}

이 경우에는 2가지의 방법이 있다.

  • 기본적으로는 디폴트 메소드가 무시되고 부모 클래스의 메소드가 상속된다.
  • 인터페이스의 디폴트 메소드를 사용하고자 한다면, 디폴트 메소드의 내용을 그대로 오버라이딩해버린다.

default 메소드의 super

클래스의 상위 클래스를 가리키는 super 키워드를 인터페이스에서도 사용할 수 있다.

다만 문법이 약간 차이가 있다.
인터페이스를 호출하기 위해서는 인터페이스명.super.디폴트 메소드로 써야한다.

바로 위, 인터페이스의 디폴트 메소드와 부모 클래스의 메소드 간의 충돌 문제에서 인터페이스의 메소드를 구현하고 싶을 때 사용할 수 있다.

class MyClass extends B implements A {	
	@Override
    public void methodA() { }
    
    @Override
    public void methodB() { }
    
    @Override
    public void methodSame() {
    	A.super.methodSame();	// super를 이용해 인터페이스 메소드 호출
    }
}

static 메소드

static 메소드는 인스턴스와 관계가 없는 독립적인 메소드이기 때문에 인터페이스에 추가하지 못할 이유가 없었다.

그러나 인터페이스의 모든 메소드는 추상 메소드이어야 한다는 규칙에 예외를 두지 않기 위해서 만들지 않았다가 Java 8 에서 추가되었다.

대표적인 예시가 java.util.Collection 인터페이스가 있는데, 추상 메소드만 있어야 한다는 규칙 때문에 static 메소드를 따로 모아 놓은 java.util.Collections 클래스가 있다.

인터페이스 static 메소드라고 해서 특별한 것은 없다.
클래스의 static 메소드와 동일하게 취급한다.

profile
자바 스프링 공부하는 정리 블로그!

0개의 댓글