프로그래밍안드로이드 개발자

코틀린에서 추상 클래스와 인터페이스의 차이점을 설명하세요. 어떤 접근 방식을 언제 적용해야 하며, 언어의 구현 세부사항과 상속의 뉘앙스는 무엇인가요?

Hintsage AI 어시스턴트로 면접 통과

답변.

코틀린에서 추상 클래스 (abstract class)와 인터페이스 (interface)는 추상적인 로직을 선언하는 데 사용되지만, 이들 사이에는 중요한 차이점이 있습니다:

  • 추상 클래스는 상태(필드), 메소드 구현 및 추상 메소드를 포함할 수 있습니다. 클래스는 오직 하나의 추상(또는 일반) 클래스만 상속할 수 있습니다. 추상 클래스는 생성자를 가질 수 있습니다.
  • 인터페이스는 상태를 소유하지 않는 속성 선언, 기본 메소드 구현 및 추상 메소드를 포함할 수 있습니다. 코틀린 1.1부터 인터페이스는 속성 구현을 포함할 수 있지만, 상태를 가질 수는 없습니다. 클래스는 여러 개의 인터페이스를 구현할 수 있습니다.
  • 인터페이스는 상태(백킹 필드)를 포함할 수 없지만, 추상 클래스는 가능합니다.

코드 예시

abstract class Animal(val name: String) { abstract fun makeSound() fun info() = println("저는 $name 입니다.") } interface Movable { fun move() } class Cat(name: String) : Animal(name), Movable { override fun makeSound() = println("야옹") override fun move() = println("고양이가 움직입니다") }

함정 질문.

코틀린에서 인터페이스가 상태(백킹 필드)를 가질 수 있습니까?

대부분의 사람들은 인터페이스에 속성을 선언할 수 있다고 대답하지만, 그것들은 여전히 백킹 필드를 가지지 않으며, 값 저장 없이 게터/세터만 존재합니다. get() = ...를 작성하더라도 값은 매번 계산됩니다.

예시:

interface MyInterface { val value: Int get() = 42 // 값 저장 없음, 실시간 계산 }

주제에 대한 미세한 차이를 모르는 것에서 발생한 실제 오류의 예.


이야기

대규모 프로젝트에서 선임 개발자가 인터페이스에 상태를 저장하려고 했습니다(예: 카운터), var count: Int 속성이 백킹 필드를 가질 것이라고 예상했습니다. 그 결과 인터페이스를 구현하는 모든 클래스에서 값을 저장하는 방법이 다르게 구현되어 불안정한 로직이 발생했습니다(세터를 재정의하는 것을 잊으면 데이터가 손실되었습니다).


이야기

한 개발자가 다양한 행동 전략(전략 패턴)을 구현해야 할 곳에서 추상 클래스를 사용했습니다. 결과적으로 발생한 문제는 한 기본 클래스만 상속할 수 있었지만, 다양한 행동 측면에 대해 조합이 필요하다는 점이었습니다. 인터페이스로 아키텍처를 변경해야 했습니다.


이야기

팀은 두 개의 추상 클래스를 상속하려고 했습니다(하나는 로직을 위한 것, 다른 하나는 공통 유틸리티를 위한 것), 이는 코틀린에서는 불가능합니다. 문제는 애플리케이션 확장 단계에서 발견되었습니다. 이로 인해 클래스를 급히 재조직해야 하여 상당한 기술적 부채가 발생했습니다.