인터페이스와 추상클래스의 차이는 무엇일까?
물론 기능적인 차이는 누구라도 이야기할 수 있을 것이다.
1. 인터페이스는 완성된 메서드를 정의할 수 없다.
2. final 키워드는 추상 클래스는 사용할 수 없고 인터페이스만 사용할 수 있다.
3. 추상 클래스는 생성자를 가질 수 있다.
4. 인터페이스는 다중 구현할 수 있다.
기타 등등..
그런데, 그래서 왜 인터페이스랑 추상 클래스가 따로 존재해야하는 건데?
인터페이스에도 디폴트 메서드를 줄 수 있고, 솔직히 둘을 비슷하게 쓸 수 있는 방법이 너무 많은데?
심지어 다중 상속 얘기도 자바에 국한된 거잖아? 그러면 C의 Abstracts는?
라고 누군가가 묻는다면 속시원하게 대답해줄 수 있는 사람은 많지 않을 것 같다.
실제로 위처럼 면접관에게 대답한 사람의 후기이다.
(무려 2013년에 작성된 글이다.)
However, the interviewer was not satisfied, and told me that this description represented "bookish knowledge"
그러나, 면접관은 만족스러워하지 않았고, 저의 설명에 대해 답했습니다. " 교과서적인 답변이군요. "
출처 :
쩝.. 해당 글에 붙은 무수한 답변들, 즐겨찾기의 개수와 추천수만 보아도 얼마나 많은 이들이 추상클래스와 인터페이스로 인해 고통 받는지 간접적으로 알 수 있을 것이다.
해당 글이 아니더라도 Interface Abstract difference로 검색하면 엄청나게 많은 질문 글과 매번 빽빽하게 달려있는 답변들을 확인할 수 있으며, 한국 블로그 글들도 인터페이스와 추상 클래스에 대해서 단정지어 설명하기는 부담스러운지 '나름대로 정리했다'는 표현을 방패막이처럼(..) 앞세우며 포스팅한 경우를 심심치 않게 찾아볼 수 있다
그리고 abstract는 좀 더 구체적인 것, interface는 더 유연하게 쓰기 위한 것에 쓴다는데( 이것 외에도 많은 이야기들이 있다. ) 정작 실무 예제에 abstract는 찾아보기가 힘들다. 아마 이 부분이 학습하는 사람들을 가장 고통스럽게 만드는 원인 중 하나일 것이다.
그나마 제일 와닿는 것은 아래의 답변이였는데, 혹시나 내 글을 읽을 사람들이 우리의 영원한 친구 구글 번역기를 사용할 수 있게 해주기 위해 그대로 복사해서 긁어왔다. 위의 스택오버플로우에서 가져온 글이다.
Many junior developers make the mistake of thinking of interfaces, abstract and concrete classes as slight variations of the same thing, and choose one of them purely on technical grounds: Do I need multiple inheritance? Do I need some place to put common methods? Do I need to bother with something other than just a concrete class? This is wrong, and hidden in these questions is the main problem: "I". When you write code for yourself, by yourself, you rarely think of other present or future developers working on or with your code.
Interfaces and abstract classes, although apparently similar from a technical point of view, have completely different meanings and purposes.
Summary
- An interface defines a contract that some implementation will fulfill for you.
- An abstract class provides a default behavior that your implementation can reuse.
These two points above is what I'm looking for when interviewing, and is a compact enough summary. Read on for more details.
Alternative summary
- An interface is for defining public APIs
- An abstract class is for internal use, and for defining SPIs
By example
To put it differently: A concrete class does the actual work, in a very specific way. For example, an ArrayList uses a contiguous area of memory to store a list of objects in a compact manner which offers fast random access, iteration, and in-place changes, but is terrible at insertions, deletions, and occasionally even additions; meanwhile, a LinkedList uses double-linked nodes to store a list of objects, which instead offers fast iteration, in-place changes, and insertion/deletion/addition, but is terrible at random access. These two types of lists are optimized for different use cases, and it matters a lot how you're going to use them. When you're trying to squeeze performance out of a list that you're heavily interacting with, and when picking the type of list is up to you, you should carefully pick which one you're instantiating.
On the other hand, high level users of a list don't really care how it is actually implemented, and they should be insulated from these details. Let's imagine that Java didn't expose the List interface, but only had a concrete List class that's actually what LinkedList is right now. All Java developers would have tailored their code to fit the implementation details: avoid random access, add a cache to speed up access, or just reimplement ArrayList on their own, although it would be incompatible with all the other code that actually works with List only. That would be terrible... But now imagine that the Java masters actually realize that a linked list is terrible for most actual use cases, and decided to switch over to an array list for their only List class available. This would affect the performance of every Java program in the world, and people wouldn't be happy about it. And the main culprit is that implementation details were available, and the developers assumed that those details are a permanent contract that they can rely on. This is why it's important to hide implementation details, and only define an abstract contract. This is the purpose of an interface: define what kind of input a method accepts, and what kind of output is expected, without exposing all the guts that would tempt programmers to tweak their code to fit the internal details that might change with any future update.
An abstract class is in the middle between interfaces and concrete classes. It is supposed to help implementations share common or boring code. For example, AbstractCollection provides basic implementations for isEmpty based on size is 0, contains as iterate and compare, addAll as repeated add, and so on. This lets implementations focus on the crucial parts that differentiate between them: how to actually store and retrieve data.
Another perspective: APIs versus SPIs
Interfaces are low-cohesion gateways between different parts of code. They allow libraries to exist and evolve without breaking every library user when something changes internally. It's called Application Programming Interface, not Application Programming Classes. On a smaller scale, they also allow multiple developers to collaborate successfully on large scale projects, by separating different modules through well documented interfaces.
Abstract classes are high-cohesion helpers to be used when implementing an interface, assuming some level of implementation details. Alternatively, abstract classes are used for defining SPIs, Service Provider Interfaces.
The difference between an API and an SPI is subtle, but important: for an API, the focus is on who uses it, and for an SPI the focus is on who implements it.
Adding methods to an API is easy, all existing users of the API will still compile. Adding methods to an SPI is hard, since every service provider (concrete implementation) will have to implement the new methods. If interfaces are used to define an SPI, a provider will have to release a new version whenever the SPI contract changes. If abstract classes are used instead, new methods could either be defined in terms of existing abstract methods, or as empty throw not implemented exception stubs, which will at least allow an older version of a service implementation to still compile and run.
A note on Java 8 and default methods
Although Java 8 introduced default methods for interfaces, which makes the line between interfaces and abstract classes even blurrier, this wasn't so that implementations can reuse code, but to make it easier to change interfaces that serve both as an API and as an SPI (or are wrongly used for defining SPIs instead of abstract classes).
"Book knowledge"
The technical details provided in the OP's answer are considered "book knowledge" because this is usually the approach used in school and in most technology books about a language: what a thing is, not how to use it in practice, especially in large scale applications.
Here's an analogy: supposed the question was:
What is better to rent for prom night, a car or a hotel room?
The technical answer sounds like:
Well, in a car you can do it sooner, but in a hotel room you can do it more comfortably. On the other hand, the hotel room is in only one place, while in the car you can do it in more places, like, let's say you can go to the vista point for a nice view, or in a drive-in theater, or many other places, or even in more than one place. Also, the hotel room has a shower.
That is all true, but completely misses the points that they are two completely different things, and both can be used at the same time for different purposes, and the "doing it" aspect is not the most important thing about either of the two options. The answer lacks perspective, it shows an immature way of thinking, while correctly presenting true "facts".
최대한 쉽고 간결하게정리하자면 다음과 같다.
1. 다중 상속, 메서드 여부 등 기능적인 부분으로 차이점을 이야기하는 것들은 잘못된 것이다. 인터페이스와 추상클래스는 기술적인 관점에서는 분명히 유사하지만 타인과 코드를 공유할 때는 그 의미와 목적히 전혀 다르다.
2. 인터페이스는 구현체가 이행해야할 계약을 명시하는 것이며, 추상 클래스는 구현체의 재사용에 필요한 기본 동작을 제공하는 것이다.
3. 다른 예시를 들자면, 인터페이스는 공개적으로 배포하기 위한 목적의 API를 정의할 때 사용하기 적합하고 추상클래스는 내부적인 로직(SPI)에 사용하는 것이 적합하다.
4. 더 자세하게 말하자면 만약 JAVA가 인터페이스 없이 오직 구체 클래스인 List만 가지고 있었다면 어떻게 되었을까? 사용자들이 이것을 본인들의 세부 구현 사항에 맞춰 마개조하면서 끔찍한 괴물들이 탄생하고 호환성 문제가 발생했을 것이다. Interface는 이런 공개적인 코드에서 반드시 반환되어야할 타입과 작성해야할 메서드를 명시함으로써 해당 인터페이스를 구현한 클래스가 어떤 기능을 가지고 있을 것이라고 신뢰할 수 있게 만들어준다.
5. 추상클래스는 보다 보편적이고 반복적으로 작성되는 코드의 작성을 줄이는데 도움을 준다. 예를 들자면 isEmpty인 배열의 사이즈는 0이라던가 addAll은 add를 반복한다던가.. 이것의 공통점은 사용자에게 영향을 주는 부분이 아니라 구현하는 사람에게 영향을 주는 부분이라는 것이다.
6. 면접 답변에 대한 조언은.. 직접 읽어보자.
사실 실무를 겪어보지 못한 취준생들에게 모든 기술에 대한 지식은 결국 "아니.. 그래서 이 기능이 어디 쓰이는 거냐니까???"가 가장 핵심적인 궁금증인데, 이런 식으로 구체적인 차이점을 알려주는 것이 제일 좋은 것 같다.
반대로 최악의 답변이 "is a"면 abstract고 "has a"라고 읽히면 interface라고 가르치는 건데 ^^.. parents와 child로 의미 없는 코드만 주구장창 따라치게 하면서 상속에 대해 가르치는 것보다 더 좋지 않다고 생각한다.. 지금까지 다들 그렇게 가르치고 배워왔으니 그런 거겠지만.. 좀 그래..
-- 조영호님께서 집필하신 "객체지향의 오해와 사실"이라는 책에서 왜 이렇게 나누어서 쓰는 것인지에 대한 추가적인 설명을 얻을 수 있었다.
우리는 일반적으로 상속으로써 일반화를 구현하지만 사실 상속 관계가 모두 일반화를 구현하는 것이 아니다.
일반화의 원칙은 "한 타입이 다른 타입의 Subtype이 되기 위해서는 Supertype에 순응(conformance)해야 한다는 것"이다.
순응에는 구조적인 순응(structural conformance)와 행위적인 순응(behavioral conformance)가 있는데, 구조적인 순응은 서브타입이 슈퍼타입이 가지고 있는 속성과 연관관계에서 100% 일치하는 것을 의미한다.
즉, 서브타입이 슈퍼타입을 완전히 대체하더라도 구조에 대해 동일한 집합을 가질 것임을 기대할 수 있는 것이다.
( 예 : Animal이라는 클래스가 name을 가진다면 상속받은 Dog라는 클래스도 name을 가지고 있어야 구조적 순응 )
행위적인 순응은 타입의 행위를 대체할 수 있어야함을 의미한다.
( 예 : Animal이라는 클래스가 bite()라는 메세지에 대한 응답으로 String인 물다를 반환한다면, 상속받은 Dog도 bite()라는 메세지에 대한 응답으로 String인 물다를 반환해야 한다. (값이 중요한 것이 아니라 같은 타입을 가지고 대체 가능할 것 ))
이처럼 상속이 코드 재사용을 줄여주는 것은 우리가 해당 순응을 만족해야하며, 상속이라는 기능 자체가 코드의 재사용과 중복을 줄여주는 것이 아니다.
이 때 상속은 서브타이핑(subtyping)과 서브클래싱(subclassing)의 용도로 사용될 수 있는데, 서브클래스가 슈퍼클래스를 대체할 수 있는 경우를 서브타이핑이라고 하며, 대체할 수 없는 경우에는 서브클래싱이라고 부른다.
서브타이핑의 목적은 설계의 유연성이 목표인 반면 서브 클래싱은 코드의 중복 제거와 재사용이 목적이다.
흔히 서브타이핑을 인터페이스 상속(interface inheritance)라고 하고, 서브 클래싱을 구현 상속(implementation inheritance)라고 한다.
중요한 것은 어떤 것이 서브클래싱인지, 서브타이핑인지를 결정하는 것은 단순히 상속의 형태가 아니라 대체 가능성이기 때문에 서브타이핑인지 여부를 확인하려면 클라이언트 관점에서 실제 사용되고 있는 모습을 관측해야 한다.
-- 객체지향의 사실과 오해 _ 부록 < 추상화 기법 > 중 일부 내용 요약
객체지향 언어의 거듭된 발전과 자바 자체의 기능 변경으로 인해 비록 자바의 인터페이스와 추상 클래스가 비슷한 기능을 할 수 있지만 관례적으로 인터페이스는 서브타이핑 용도로 사용되며 추상클래스가 서브클래싱 용도로 사용되고 있는 것 같다.
예제들을 찾아보면 인터페이스는 말 그대로 접촉 지점으로써, 그 위치에 인터페이스를 구현한 어떤 클래스가 역할을 대체해도 애플리케이션에 아무런 영향을 주지 않도록 설계하고, 추상 클래스의 경우 기능을 상속함으로써 비슷한 기능을 상속 받았으나 서로 다른 역할을 수행하는 것들을 많이 볼 수 있다. (혹은 상태값을 가진 엔티티 옵션 객체로 주로 사용된다.)
'Language & Framework > Java' 카테고리의 다른 글
이펙티브 자바 (1) 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2022.11.01 |
---|---|
대혼돈의 질서 파괴범 Reflection API에 대해 알아보자 (Java) (2) | 2022.09.11 |
자바 예외 (2) java.io.InvalidClassException: InputAndOutput.Child; no valid constructor (0) | 2022.06.22 |
자바 예외 (1) java.io.NotSerializableException (0) | 2022.06.21 |
자바 ArrayList와 LinkedList는 무엇인가? 성능 비교 (+ Vector..?) (0) | 2022.06.12 |