이 글에서는 java에서 사용하는 abstract class와 interace, 그리고 둘의 차이를 알아볼 것이다.
Abstraction
이전 글에서 abstract를 다음과 같이 설명했다.
일반적으로 컴퓨터 과학에서 추상화라 함은 불필요한 정보(세부 구현 등)는 숨기고 중요한 정보만을 보여주는 것이다.
객체지향에서 추상화는 객체를 만들 때 사용하는 개념으로, 일반화를 통해 공통된 속성과 행위를 추출하는 것이다. 불필요한 정보(객체마다 다른 특이한 정보)는 숨기고 중요한 정보(공통된 속성과 행위)를 상위 class로 추출하는 것이다.
추상 클래스 Abstract Class
abstract class는 abstract로 선언된 class이거나 abstract method가 1개 이상 포함된 class를 말한다.
- abstract class로 선언된 class는 abstract class이기에 abstract method를 포함하지 않아도 된다.
- abstract method가 1개 이상 포함된 class는 abstract class로 선언되어야 한다. 그렇지 않으면 compile error가 난다.
abstract class에서 method 이름만 통일하고 세부 구현은 derived class에게 맡김으로써 polymorphism을 구현할 수 있으며, 해당 method를 비워둘 경우에는 compiler가 오류를 잡아주기 때문에 좀 더 쉽게 polymorphism을 구현할 수 있다.
이외에도
- 일반 method, attribute를 포함할 수 있다.
- 생성자를 가질 수 있다.
- instantiation이 불가하다.
- abstract class를 상속받은 class는 abstract method를 반드시 구현해야 한다.
- 다중상속이 불가능하다 (Java의 상속은 다중상속을 지원하지 않음)
생성자를 가질 수도 있다. 아래 예시를 보자.
public abstract class AbstractClass {
abstract void function1(){}; // compile error : Abstract methods do not specify a body
void function2(){}; // OK : can have normal method
abstract void function3(); // OK
// can have normal attribute
private String attr;
// can have constructor
public AbstractClass(){};
public AbstractClass(String str){
this.attr = str;
}
}
class AbstractClass1{ // compile error : The type AbstractClass1 must be an abstract class to define abstract methods
abstract void function1();
}
abstract class는 constructor를 가질 수 있지만 instantiation이 불가하다. 그러면 contructor를 왜 사용하느냐? derived class에서 super()로 해당 constructor를 불러올 수 있기 때문이다.
// App.java
public class App {
public static void main(String[] args) throws Exception {
AbstractClass abstractClass = new AbstractClass();
// compile error : Cannot instantiate the type AbstractClass
}
}
abstract class를 상속받은 class는 abstract method를 구현해야만 한다.
// AbstractClass.java
public abstract class AbstractClass {
abstract void function();
}
// AbstractChildClass.java
public class AbstractChildClass extends AbstractClass{
void function1(){};
// if not implement abstract method, then compile error
void function(){};
}
abstract class의 사용 목적
inheritance는 class hierarchy를 통한 공통 기능의 code reuse, 중복 제거를 위해 사용한다. abstract class는 이러한 inheritance의 목적에 몇 가지를 더 추가한다.
derived class에서 해당 method의 로직이 달라져야할 때, derived class에서 특정 method의 구현을 강제할 때 사용한다. 굳이 abstract class를 사용하지 않아도 derived class에서 해당 method를 overriding해서 해당 method의 로직을 다르게 짤 수 있다. 그러나 이 경우, 유지보수를 하다 overriding을 빼먹는 경우가 생길 수 있고 경우에 따라 application에 critical한 영향을 미칠 수 있다. 해당 method의 구현을 강제함으로써 이런 경우를 막을 수 있다.
인터페이스 Interface
interface는 abstract class와 유사하지만 abstraction level이 좀 더 높다. abstract class의 경우에는 abstract method 이외에도 일반 method, attribute를 가질 수 있었지만 interface는 abstract method와 static final로 이루어진 상수만 포함할 수 있다.
- interface의 모든 method는 abstract method이며, public static final로 이루어진 상수 정도만 추가로 선언할 수 있다.
- public static final과 public abstract는 생략이 가능하다
- contructor를 가질 수 없으며 instantiation이 불가능하다.
- implement 객체는 모든 method를 구현해야 한다.
- implement 객체는 extends와 implements를 동시에 사용할 수 있다.
- multiple inheritance와 multiple interface가 가능하다.
- 상속 시 public static final로 선언한 상수는 static과 동일하게 행동한다.
- java8부터 default method와 static method를 이용할 수 있다.
- default method를 사용하고 싶을 경우 해당 interface로 upcasting을 해야 한다.
아래 예시를 보자.
// Interface.java
interface Interface {
public static final int constant = 1;
private static final int constant1 = 1; // compile error : only public, static & final are permitted
void function1(){} // compile error : Abstract methods do not specify a body
void function2();
}
multiple inheritance가 가능하다. interface를 여러 개 extend하든(multiple inheritance), interface 여러 개를 implement하든(multiple interface), 둘 다 가능하다.
// Interface.java
interface Interface1{
public abstract void function1();
}
interface Interface2{
public abstract void function2();
}
public interface Interface extends Interface1, Interface2{
public abstract void function3();
}
class implement implements Interface1, Interface2{
@Override
public void function2() {
// ...
}
@Override
public void function1() {
// ...
}
}
// InterfaceExample.java
public class InterfaceExample implements Interface{
// if do nothing, then compile error : must implement the inherited abstract method
@Override
public void function1() {
// ...
}
@Override
public void function2() {
// ...
}
@Override
public void function3() {
// ...
}
}
extends와 implements를 동시에 사용할 수 있다.
// Example.java
class Example extends AbstractClass implements Interface{
// function 1~3 : interface
// function : AbstractClass
@Override
public void function1() {
// ...
}
@Override
public void function2() {
// ...
}
@Override
public void function3() {
// ...
}
@Override
public void abstractFunction() {
// ...
}
}
interface의 default method
java 8부터 interface에서 default method와 static method를 정의할 수 있다. default method와 static method는 implement object에서 구현하지 않아도 사용할 수 있으며, interface를 수정하지 않아도 새로운 method를 추가할 수 있다.
// Interface.java
interface Interface{
public abstract void function();
// default method
default int print(){
System.out.println("interface default method");
return 1;
}
// static method
static int staticPrint(){
System.out.println("interface static method");
return 2;
}
}
// Example.java
class Example implements Interface{
@Override
public void function() {
System.out.println("class function");
}
}
// App.java
public class App {
public static void main(String[] args) throws Exception {
Example example = new Example();
example.function(); // class function
System.out.println(example.print()); // interface default method
}
}
문제점 1 : multiple inheritance
다만, default method를 사용함으로써 multiple inheritance 문제가 생겼다. 같은 default method를 가진 interface를 하나의 class에 구현하는 경우 해당 default method를 overriding해야 한다.
// Interface.java
interface I1{
abstract public void function();
default public void sameFunction(){
System.out.println("I1 default function");
}
}
interface I2{
abstract public void function();
default public void sameFunction(){
System.out.println("I2 default function");
}
}
class MultipleInheritance implements I1, I2{
// if nothing, then
// compile error : Duplicate default methods named sameFunction
@Override
public void function(){
System.out.println("class function");
}
@Override
public void sameFunction() {
System.out.println("class default function");
}
}
// App.java
public class App {
public static void main(String[] args) throws Exception {
MultipleInheritance example = new MultipleInheritance();
example.function(); // class function
example.sameFunction(); // class default function
}
}
문제점 2 : interface default method와 parent class method 충돌
충돌된다면 parent class method가 상속된다.
interface의 polymorphism
interface를 implement한 implement 객체는 overriding을 했다. dynamic polymorphism처럼 interface type variable에 implement object를 넣을 수 있다. (upcasting)
마찬가지로 parameter에도 upcasting이 가능하며, downcasting, instanceof 모두 사용 가능하다. dynamic polymorphism과 완전히 동일하다.
// Interface.java
interface Interface{
abstract void print();
}
class I1 implements Interface{
@Override
public void print() {
System.out.println("i1 object print");
}
}
class I2 implements Interface{
@Override
public void print() {
System.out.println("i2 object print");
}
}
// App.java
public class App {
public static void main(String[] args) throws Exception {
Interface ex1 = new I1();
Interface ex2 = new I2();
ex1.print(); // i1 object print
ex2.print(); // i2 object print
}
}
interface의 사용 목적
inheritance의 사용 목적은 abstract class와 유사하다. interface를 implement하는 class는 abstract method를 모두 구현해야 한다는 점에서 해당 method의 구현을 강제함으로써 object behavior를 명세한다.
abstract class가 class hierarchy 관계에 있을 때 method의 구현을 강제한다면, interface는 hierarchy에 관계없이 class들의 공통 행위를 지정하고 구현을 강제하고 싶을 때 사용한다.
multiple inheritance를 허용하지 않는 java에서 multiple inheritance를 사용하고 싶을 때 쓸 수 있다.
decoupling : 의존성 제거
객체지향 프로그래밍은 객체들의 상호작용으로 프로그래밍하는 것이라 했다. 그러나 모든 프로그래밍을 object의 상호작용으로 작성해 버리면 object들 간에 의존성이 너무 높아지며, 유지보수하기 힘들어진다. 따라서 coupling을 낮추기 위해 class extend가 아닌 implement로 구현하여 object 사이의 dependency를 줄일 수 있다.
해당 내용은 SOLID 원칙 중 Dependency Inversion Principle에 따른다.
abstract class vs interface
abstract class를 상속하거나, interface를 implement하는 class에게 특정한 method의 구현을 강제한다는 공통점이 있으며, abstraction에 사용되며, upcasting을 통해 polymorphism을 구현할 수 있다. 또한 선언부와 구현부를 분리하기 때문에 기능 구현에만 집중할 수 있다.
abstract class는 inheritance를 사용하는 만큼 class hierarchy를 가지며, is-a(~이다) 관계이며, extends keyword를 사용하는 것처럼 parent class를 확장하고 derived class에 따라 달라지는 기능을 강제하는 것이 주 목적이다. (사실 이건 abstract class의 특징이라기보단 inheritance의 특징이다. 또한 기능의 강제는 abstract method에 한정된다)
반면 interface는 상속 관계에 관계없이 class들의 공통 행위를 지정할 수 있기 때문에 has-a (~특성을 가진) 관계이며(~able로 많이 naming한다), implements keyword를 사용하는 것처럼 특정 기능 구현을 강제하는 것이 주 목적이다.
다음 예시를 보자.
Bird abstract class와 Human abstract class는 둘 모두 breed라는 특징이 있다. 이를 hierarchy에 넣고 싶기 때문에 Animal abstract class를 abstract했다. Animal abstract class를 상속받는 Bird abstract class에는 fly method가 추가되었고, Human abstract class에는 walk method가 추가되었다. 이렇듯 Animal class에서 더 확장되었다.
반면 singable, computable, assignable을 보자.
- Singer class와는 관계가 없는 Bird abstract class에는 singable이라는 공통점이 있다.
- Programmer class와 Engineer는 assignable(과제하는) 공통점이 있다.
- Programmer과 Gamer는 computable(컴퓨터를 하는) 공통점이 있다.
이런 공통점을 abstraction해서 class hierarchy로 만들기는 너무 어렵고 복잡하다. 이럴 때 interface를 사용한다. 상속 관계에 상관없이 공통 행위를 지정하고 구현을 강제하는 interface의 역할이 이것이다.
또한 Programmer는 computable, assignable 2개의 특징을 가진다. multiple inheritance가 불가능한 java에서 2개 이상의 interface를 extend해 multiple interface implements로 사용하면 코드의 유지보수성을 올릴 수 있다.
정리
abstract class, interface 둘 다 abstract method의 구현을 강제하고, upcasting을 통해 polymorphism을 사용할 수 있다.
abstract class를 사용하는 경우
- derived class의 공통 member가 많아서 class hierarchy로 abstraction할 때
- abstraction 시 static, final 이외의 field를 선언하고 싶을 때
- abstraction 시 public 이외의 access modifier를 사용하고 싶을 때
interface를 사용하는 경우
- class의 공통 기능을 정의하고 싶은데 그 class들이 딱히 hierarchy 관계가 없을 때
- multiple inheritance를 써야 할 때
'Development > Java' 카테고리의 다른 글
[Java] Java Virtual Machine (0) | 2023.03.01 |
---|---|
[Java] Exception Handling (0) | 2023.03.01 |
[Java] Polymorphism - static polymorphism, dynamic polymorphism, casting (0) | 2023.02.23 |
[Java] Inheritance - access modifier, super, overriding (0) | 2023.02.23 |
[Java] static, final (0) | 2023.02.23 |