Interface와 abstract class의 차이를 알아보자.
Java 8부터 interface에는 default method가 생겼다. 이로 인해 interface와 abstract class의 차이가 하나 줄었다.
Interface와 abstract class의 차이는 크게 두 가지다.
- Abstract class는 interface와 달리 다중 상속의 대상이 될 수 없다.
- Abstract class는 state를 가질 수 있지만 interface는 state를 가질 수 없다.
이외에도 lambda expression과 관련한 차이도 있다. 하지만 여기서는 다루지 않는다. 위에 명시한 두 가지 차이에 대해서 알아보자.
다중 상속
Effective Java를 보면 abstract class보다는 interface를 사용하라고 한다. 여러 이유가 있는데 그 중 위에서 언급한 첫 번째 이유가 있다. Abstract class 다중 상속의 대상이 될 수 없다. 결국, abstract class는 hierarchy와 관련된다. 하지만 hierarchy가 부적절한 경우가 많다. 예를 들어 interface Husband
와 interface Son
을 조합해 class MarriedMan
을 정의할 수 있다. MarriedMan은 Husband이면서 Son이다. Hierarchy로 이 셋의 관계를 정의하는 건 부적절하다.
class MarriedMan() : Husband, Son {
override fun actAsHusband() {}
override fun actAsSon() {}
}
interface Husband {
fun actAsHusband()
}
interface Son {
fun actAsSon()
}
만약 Husband
와 Son
이 interface가 아닌 abstract class였다면 위처럼 MarriedMan
을 정의할 수 없다.
State
Interface: stateless
Interface는 state를 가질 수 없기 때문에 mutable instance variables를 사용할 수 없다. 즉, instance fields가 없거나 final variables만 가질 수 있다. 아래 예시는 final variables만 가진 경우다.
public interface Son {
String nameOfFather = "John";
String nameOfMother = "Joan";
}
실제로 아래 코드는 컴파일 에러를 낸다.
public class Demo {
public static void main(String[] args) {
Son.nameOfMother = "Anne";
}
}
java: cannot assign a value to final variable nameOfMother
Abstract class: stateful
Abstract class는 여느 클래스와 마찬가지로 state를 가질 수 있다. 즉, mutable instance variables를 사용할 수 있다.
abstract class AbstractSon {
String nameOfMother = "Joan";
}
public class ConcreteSon extends AbstractSon {
}
그렇기 때문에 아래 코드는 컴파일이 된다.
public class Demo {
public static void main(String[] args) {
AbstractSon son = new ConcreteSon();
son.nameOfMother = "Anne";
}
}
Skeletal implementation = abstract class + interface
위에서 interface의 특징에 대해 언급했다. Interface에는 또 다른 제약 사항이 있다. Interface는 Object class의 메서드인 hashCode()
, toString()
equals()
등에 대해 default method를 정의할 수 없다. 또한 interface는 public이 아닌 static members를 가질 수 없다 (private static 메서드 제외).
Skeletal implementation은 interface의 특징과 abstract class의 특징을 조합하기 위한 방법으로, interface를 상속하는 abstract class를 정의한다. 예를 들어, java.util.AbstractList
, java.util.AbstractMap
등이 있다.
AbstractList
코드 일부를 보자.
package java.util;
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
// 생략
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
// 생략
}
}
위 코드에서 equals()
가 정의돼 있다. 위에서 언급했듯이, interface인 List
는 equals()
이름의 default method를 만들 수 없다. Skeletal implementation은 이를 극복했다.
이 Skeletal implementation은 어떻게 사용될까? 예시로 AbstractList
를 상속하는 ArrayList
를 살펴보자.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 생략
}
ArrayList
의 equals()
메서드는 AbstractList
의 것이다. ArrayList
class는 AbstractList
를 상속하기 때문이다. 추가로 위 코드에서 알 수 있듯이 ArrayList
는 다른 여러 interfaces를 다중 상속한다.
덕분에 interface에 default method equals()
가 없지만 아래와 같이 코드가 잘 작동한다.
public class Demo {
public static void main(String[] args) {
List a = new ArrayList<Integer>(1);
List b = new ArrayList<Integer>(1);
System.out.println(a.equals(b));
}
}
true
의문점: Kotlin interface
서두에서 정의한 son interface를 보자.
public interface Son {
String nameOfFather = "John";
String nameOfMother = "Joan";
}
Kotlin으로 표현하면 아래와 같이 표현할 수 있다 (IntelliJ의 java -> kotlin 자동 변환 사용).
interface Son {
companion object {
const val NAME_OF_FATHER = "John"
const val NAME_OF_MOTHER = "Joan"
}
}
참고로 kotlin의 성질상 아래와 같이 interface를 정의하면 아래와 같은 컴파일 오류가 발생한다.
interface Son {
val NAME_OF_FATHER = "John"
val NAME_OF_MOTHER = "Joan"
}
Property initializers are not allowed in interfaces
Kotlin에서 const val
대신에 var
를 사용하면 interface가 state를 가질 수 있는 것으로 보인다. 아래와 같이 말이다.
interface Son {
companion object {
var NAME_OF_FATHER = "John"
var NAME_OF_MOTHER = "Joan"
}
}
진짜 그런가? 그렇다면 이렇게 코딩하는 건 바람직할까?
답은 No다. IntelliJ에서 위 코드를 java로 decompile해보면 아래와 같이 나온다.
public interface Son {
// 생략
public static final class Companion {
// 생략
static {
$$INSTANCE = new Companion();
NAME_OF_FATHER = "John";
NAME_OF_MOTHER = "Joan";
}
}
}
즉, 애초에 java interface fields에 속하지 않는다.
Reference
- Abstract classes vs. interfaces in Java, Rafael del Nero - InfoWorld
- Joshua Bloch. (2018) Effective Java (3rd ed.). Addison-Wesley
- What is Stateless Object in Java? - Stackoverflow
- Companion Objects in Kotlin Interfaces - Stackoverflow