[Java] Day08 - 인터페이스
2021-02-15 # Java

이 스터디는 백기선님의 유튜브로 진행되는 live study입니다.


Day08. 인터페이스


Review Day07


Day08. 인터페이스



What is Interface



인터페이스 하면 남자들은 가장 많이 떠오르는 것이 게임하면서 본 인터페이스다(?). 반가운 단어지만 이제부터 다뤄볼 인터페이스는 다른얘기이다.

인터페이스란 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화정도가 높아서 추상클래와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.

오직 추상메서드상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 가질 수 없다.

추상클래스를 미완성 설계도라고 한다면 인터페이스는 기본 설계도라고 할 수 있다.

또한, 추상클래스는 ==”is-a : ~는 ~이다”==의 개념이고, 인터페이스는 ==”has-a: ~는 ~를 할 수 있다.”==의 개념이다.

ex. SSON은 사람 Person 이면서 코딩(Developable)을 할 수 있다.

  • class SSON extends Person implements Developable

이제부터 인터페이스의 사용법과 사용이유에 대해서 다뤄보도록 하겠다.


How to define Interface



인터페이스를 정의 하는 것은 클래스와 동일하다. 키워드로 class 대신 interface를 사용한다는 것만 다르다. interface의 접근제어자는 public 또는 default를 사용할 수 있다.

예시는 다음과 같다.

1
2
3
4
interface howToMakeInterface {
public static final 상수이름 = 값;
public abstract 메서드이름(매개변수);
}

인터페이스는 다음과 같은 제약조건이 있다.

  1. 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
  2. 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. (단, static메서드와 default메서드는 예외)

static과 default메서드는 뒤에서 다뤄보도록 하겠다.

인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이다. 인텔리제이를 사용하다보면 접근제어자를 생략하는 것을 자주 경험하게 되는데 클래스에서 대부분 public을 사용하기 때문에 생략하면 public으로 implicit하게 선언되는 것이기 때문이다.

이는 컴파일 시에 컴파일러가 자동적으로 추가해준다.


Inherit Interface



1
2
3
4
5
6
7
8
9
interface Movable{
void move(int x, int y); // public abstract가 생략되었다.
}

interface Attackable{
void attack(Unit u); // public abstract가 생략되었다.
}

interface Fightable extends Movable, Attackable{ }

클래스의 상속과 마찬가지로 자손 인터페이스(Fightable)는 조상 인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받는다.

위에 Fightable 인터페이스는 정의된 멤버가 한개도 없지만 조상 인터페이스에서 정의된 move와 attack을 멤버로 갖게되는 것이다.

또한, 인터페이스는 인터페이스로부터만 상속을 받을 수 있다. 당연한 것일수도 있는데 자손이 추상클래스만 선언이 가능한데 부모 클래스가 추상클래스가 아닐경우 상속을 받으면 이에 위반되는 경우가 생성될 수 있으므로 당연한 것이다.

인터페이스의 상속 구조에서는

→ 서브 인터페이스는 수퍼 인터페이스의 메서드까지 모두 구현해야 한다.

인터페이스 레퍼런스는 인터페이스를 구현한 클래스의 인스턴스를 가리킬 수 있고, 해당 인터페이스에 선언된 메서드(수퍼 인스턴스 메소드 포함)만 호출 할 수 있다.

단, 인터페이스는 클래스와 달리 Object와 같은 최상위조상이 존재하지 않는다.


How to implement Interface



인터페이스도 추상클래스이기 때문에 당연히 그 자체로는 인스턴스를 생성하는 것이 불가능하다.

추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 한다.

그 방법은 다음과 같다.

1
2
3
4
5
6
7
8
class 클래스이름 implements 인터페이스이름{
//추상메서드를 구현.
}

class Fighter implements Fightable{
pulic void move(int x, int y) { implement }
pulic void attack(Unit u) { implement }
}

인터페이스는 implements를 사용하여 구현을 하는데 말 그대로 구현을 해야하기 때문이다. 모든 멤버를 구현하지 않을 경우는 abstract를 붙여서 완성되지 않은 클래스임을 명시해줘야한다.

위처럼 Fightable이라는 interface에 구성되어야할 move, attack과 같은 메서드를 모두 작성하지 않을 경우 abstract를 붙여야 한다.

1
2
3
abstract class Fighter implements Fightable{
pulic void move(int x, int y) { implement }
}

인터페이스의 이름에는 주로 ~able의 접미사가 붙는 경우가 많은데, 그 이유는 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 직관적으로 알게하기 위함이다. 반드시 ‘able’로 끝나야하는 것은 아니지만 가독성을 높이기위해서는 따르는 것이 좋을 것이다.


Multiple Inheritance of Interface



다중상속을 할 때, 멤버변수의 이름이 같거나 메서드의 선언부가 일치하고 구현 내용이 다르다면 어떤 조상의 것인지 불분명하기 때문에 자바에서는 다중상속을 허용하지 않는다.

또 다른 객체지향언어인 C++에서는 다중상속을 허용하기 때문에 다중상속의 단점이 부각되지만 인터페이스로 다중상속을 구현할 수 있다.

다중상속을 위해서 인터페이스를 사용하는 것은 아니므로 헷갈리지 말자.

상위 인터페이스에 있는 메서드 중에서 메서드 명과 파라미터 형식은 같지만 리턴 타입이 다른 메서드가 있다면, 둘중 어떤 것을 상속받느냐에 따라 규칙이 달라지기 때문에 다중 상속이 불가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Tv{
protected boolean power;
protected int channel;
protected int volume;

public void power() { power = ! power;};
public void channelUp() { channel++;};
public void channelDown() { channel--;};

}

public class VCR{
protected int counter;

public int getCounter(){return counter;}
}

public interface IVCR{
public int getCounter();
}

public class TVCR extends Tv implements IVCR{
VCR vcr = new VCR();

public int getCounter(){
return vcr.getCounter();
}
}

위와같이 다중상속을 허용할 수 있다.


How to use the implementation using reference



1
2
3
4
public interface Animal {
String getName();
int getLegs();
}

위와 같이 Animal이라는 interface가 있다고 하자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Bird implements Animal {

@Override
public String getName() {
return "새";
}

@Override
public int getLegs() {
return 2;
}

}

public class Cat implements Animal {

@Override
public String getName() {
return "고양이";
}

@Override
public int getLegs() {
return 4;
}

}

BirdCat이라는 Animal interface를 구현한 class가 있을 때, 두 클래스를 인스턴스화 하여 메서드를 호출해보자.


1
2
3
4
5
6
7
8
9
Cat cat = new Cat();
Bird bird = new Bird();

System.out.println(cat.getName()); // 고양이
System.out.println(cat.getLegs()); // 4

System.out.println(bird.getName()); // 새
System.out.println(bird.getLegs()); // 2


또한, 인스턴스들은 interface타입으로 생성이 가능하다.


1
2
Animal cat = new Cat();
Animal bird = new Bird();

Animal 인터페이스를 파라미터로 받는 메소드가 있다면 Cat, Bird 타입의 인스턴스를 파라미터로 사용할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
public void printAnimal(Animal animal) {
System.out.println("이름 : " + animal.getName());
System.out.println("다리 개수 : " + animal.getLegs());
}

Cat cat = new Cat();
Bird bird = new Bird();

printAnimal(cat);
printAnimal(bird);

Default Method of Interface, java8



java8부터는 Default Method와 Static Method를 선언할 수 있다. static method는 인스턴스를 생성하지 않아도 가능하므로 없을 이유가 없었다.

static method는 나중에 다뤄보고 우선 default method부터 다뤄보겠다.

들어가기에 앞서 default method가 필요한 이유에 대해서 얘기해보겠다.

Q. 만약 당신이 유명한 RPG게임의 제작자라고 하자. 직업군이 총 256가지인 게임이라고 할 때, 인터페이스에 아이템 강화시스템을 도입한다고 한다. 인터페이스에 강화시스템을 도입한다면 추상메서드이기 때문에 구현한 모든 직업군에 따로 정의를 해야할 것이다. 구현 X 256을 한다면 총 얼마의 시간이 걸리겠는가?

정말 최악이다. 구현을 간단하게 한다 쳐도 256번을 반복하라니 정말 최악이다. 하지만 interface에 default method 즉, 기본으로 생성할 수 있는 method가 없을 경우의 얘기이다.

디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

이 때, 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생할 수 있다. 이럴 경우는 다음의 규칙을 따른다.

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

    • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야한다.
  2. 디폴트 메서드와 조상 클래스의 메서드간의 충돌

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

위의 경우를 코드로 살펴보겠다.

위와같이 1.같은 경우에는 디폴트 메서드간의 충돌이 일어나서 컴파일시 오류가 나는 경우이다.

위와같이 2.같은 경우에는 조상 클래스의 printMethod가 실행됨을 알 수 있다.


Static Method of Interface, java8



인스턴스 생성과 상관없이 인터페이스 타입으로 호출하는 메소드이다.

static 예약어를 사용하고, 접근제어자는 항상 public이며 생략 할 수 있다.

static method는 일반적으로 우리가 정의하는 메소드와는 다르다.

  1. code body가 있어야 한다.

  2. implements 한 곳에서 override가 불가능하다.

1
2
3
4
5
6
7
8
9
10
11
interface StaticInterface{
static void make() {
System.out.println("static make");
}
}

interface newInterface extends StaticInterface {
default void make(){
System.out.println("it isn't overriding");
}
}

위의 StaticInterfacemake를 호출하기 위해서는 StaticInterface.make()를 써야지만 호출이 가능하다.

Private Method of Interface, java9



java8 에서는 default method와 static method가 추가 되었고,

java9 에서는 private method와 private static method가 추가 되었다.

java8의 default method와 static method는 여전히 불편하게 만든다.

단지 특정 기능을 처리하는 내부 method일 뿐인데도, 외부에 공개되는 public method로 만들어야하기 때문이다.

interface를 구현하는 다른 interface 혹은 class가 해당 method에 엑세스 하거나 상속할 수 있는 것을 원하지 않아도,

그렇게 될 수 있는 것이다.

java9 에서는 위와 같은 사항으로 인해 private method와 private static method라는 새로운 기능을 제공해준다.

→ 코드의 중복을 피하고 interface에 대한 캡슐화를 유지 할 수 있게 되었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Car { 

void carMethod();
default void defaultCarMethod() {
System.out.println("Default Car Method");
privateCarMethod(); privateStaticCarMethod();
}
private void privateCarMethod() {
System.out.println("private car method");
}
private static void privateStaticCarMethod() {
System.out.println("private static car method");
}
}

1
2
3
4
5
public class DefaultCar implements Car{ 
@Override public void carMethod() {
System.out.println("car method by DefaultCar");
}
}

Reference




  1. https://www.notion.so/4b0cf3f6ff7549adb2951e27519fc0e6
  2. https://blog.baesangwoo.dev/posts/java-livestudy-8week/
  3. https://dev-coco.tistory.com/13