본문 바로가기

개발일지

인터페이스(Interface)와 추상화(abstract)

인터페이스(Interface)란 무엇인가

클래스를 이용하여 다중 상속을 할 때 여러 문제가 발생할 수 있기에 자바에서는 인터페이스를 통해 다중 상속을 지원한다.

  • 다른 클래스를 작성할 때 기본이 되는 틀을 제공
  • 구현된 것은 아무것도 없는 기본 설계도와 같음
  • 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스
  • 오로지 추상 메소드와 상수만을 포함 가능

 

인터페이스 선언하기

접근제어자 interface 인터페이스이름 {

    public static final 타입 상수이름 = 값;

    ...

    public abstract 메소드이름(매개변수목록);

    ...

}
  • 인터페이스의 모든 필드는 public static final
  • 인터페이스의 모든 메소드는 public abstract

 

인터페이스 구현하기

인터페이스는 추상 클래스와 마찬가지로, 자신이 직접 인스턴스 생성하는 것은 불가

따라서, 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성한다

class 클래스이름 implements 인터페이스이름 { ... }

만약 모든 추상 메소드를 구현하지 않으면 abstract 키워드를 이용해 추상 클래스로 선언해야 한다

 

인터페이스 활용 예제

interface Animal { public abstract void cry(); }

 

class Cat implements Animal {

    public void cry() {

        System.out.println("냐옹냐옹!");

    }

}

 

class Dog implements Animal {

    public void cry() {

        System.out.println("멍멍!");

    }

}

 

public class Polymorphism03 {

    public static void main(String[] args) {

        Cat c = new Cat();

        Dog d = new Dog();

 

        c.cry();

        d.cry();

    }

}
  • 실행 결과
  • 냐옹냐옹! 멍멍!
interface Animal { public abstract void cry(); }

interface Pet { public abstract void play(); }

 

class Cat implements Animal, Pet {

    public void cry() {

        System.out.println("냐옹냐옹!");

    }

    public void play() {

        System.out.println("쥐 잡기 놀이하자~!");

    }

}

 

class Dog implements Animal, Pet {

    public void cry() {

        System.out.println("멍멍!");

    }

    public void play() {

        System.out.println("산책가자~!");

    }

}

 

public class Polymorphism04 {

    public static void main(String[] args) {

        Cat c = new Cat();

        Dog d = new Dog();

 

        c.cry();

        c.play();

        d.cry();

        d.play();

    }

}
  • 실행 결과
  • 냐옹냐옹! 나비야~ 쥐 잡기 놀이하자~! 멍멍! 바둑아~ 산책가자~!
interface Pet {
	var category : String // 추상 프로퍼티
	fun feeding()  // 추상 메소드
	fun patting() { // 구현부를 포함할 수 있다. 구현부를 포함하면 일반 메소드
		println("Keep patting")
	}
}
class Cat(override var category : String) : Pet { // 주생성자를 이용
	override fun feeding() {
		println("Feeding 메소드가 구현되었습니다.")
	}
}

fun main() {
	val obj = Cat("Small")
	obj.feeding() // 구현된 메소드
		obj.patting()// 일반 메소드
}
package com.journaldev.retrofitintro;

import com.journaldev.retrofitintro.pojo.MultipleResource;
import com.journaldev.retrofitintro.pojo.User;
import com.journaldev.retrofitintro.pojo.UserList;

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;

interface APIInterface {

    @GET("/api/unknown")
    Call<MultipleResource> doGetListResources();

    @POST("/api/users")
    Call<User> createUser(@Body User user);

    @GET("/api/users?")
    Call<UserList> doGetUserList(@Query("page") String page);

    @FormUrlEncoded
    @POST("/api/users?")
    Call<UserList> doCreateUserWithField(@Field("name") String name, @Field("job") String job);
}
  • API 인터페이스 정의API 정의인터페이스 선언사용할 API 한개에 하나의 메소드를 추가GET 메소드 이므로 @GET을 추가한다.
    @Path ( "username" ) username: String
    
    username 매개 변수를 인수에 추가
    @Query ( "sort" ) sort: String
    
    Query임을 나타내는 @Query 추가
  • API 쿼리를 인수에 추가
  • Path의 일부인 것을 나타내는 @Path 추가
  • 그리고 API 경로를 설정한다
  • @GET ( "users/{username}/repos" )
  • @GET ( "users/{username}/repos" ) fun loadRepos( @Path ( "username" ) username: String , @Query ( "sort" ) sort: String ): Call<ResponseBody>
  • interface GitHubService { ︙ }
  • interface GitHubService {@GET ("users/{username}/repos" ) fun loadRepos( @Path ("username" ) username:String ,@Query ("sort" ) sort:String ): Call<ResponseBody> }

 

추상 클래스 VS 인터페이스

추상 클래스와 인터페이스는 엄밀히 다르다!

대략적인 설계 명세를 구현하고 인터페이스를 상속하는 하위클래스에서 이를 구체화하는 것까지는 같다.

하지만 인터페이스에서는 프로퍼티의 상태 정보를 저장할 수 없다.

즉, 인터페이스에서는 프로퍼티의 초기화가 불가능하다

 

추상 클래스

구체화되지 않은 클래스로, 일반적인 객체를 생성하는 방법으로 인스턴스화 될 수 없다

 

수퍼클래스에서 구현이 끝난 함수를 override 로 재구현하기

fun main() {
	var t = Tiger()
	t.eat()
}

open class Animal {
	open fun eat() {
		println("음식을 먹습니다")
	}
}

class Tiger : Animal() {
	override fun eat() {
		println("고기를 먹습니다")
	}
}

[음식을 먹습니다] 대신 override를 사용하여 [고기를 먹습니다]로 출력한다

 

 

💡 슈퍼클래스에서는 함수의 구체적인 구현은 없고 서브클래스에서 eat()이라는 함수가 반드시 있어야 한다는 점만 명시한다!

 

각 서브클래스가 필요에 따라 함수의 내용을 구현하도록 하기 위해 추상화(abstraction) 를 사용한다!

 

추상화

선언부만 있고 기능이 구현되지 않은 추상함수

추상함수를 포함하는 추상클래스로 구성된다

fun main() {
}

abstract class Animal {
	abstract fun eat()
}

추상 함수 eat() : 함수 내용은 적지 않는다! 비어있는 껍데기

추상 클래스 Animal : 미완성 클래스이므로 단독으로 인스턴스를 만들 수 없다

→ 반드시 서브 클래스에서 상속을 받아 abstract 표시가 된 함수들을 구현해주어야 한다!

fun main() {
	var r = Rabbit()
	r.eat()
	r.sniff()
}

abstract class Animal {
	abstract fun eat()
	fun sniff() {
		println("킁킁")
	}
}

class Rabbit : Animal() {
	override fun eat() {
		println("당근을 먹습니다")
	}
}

당근을 먹습니다

킁킁

추상화를 하는 또 다른 방법 : 인터페이스

코틀린에서의 인터페이스는 속성, 추상함수, 일반함수를 모두 가질 수 있다

추상함수는 생성자를 가질 수 있는 반면 인터페이스는 생성자를 가질 수는 없다

 

구현부가 있는 함수 → open 함수로 간주

구현부가 없는 함수 → abstract 함수로 간주

별도의 키워드가 없어도 포함된 모든 함수를 서브클래스에서 구현 및 재정의가 가능하다

 

또한, 한번에 여러 인터페이스를 상속받을 수 있어 좀 더 유연한 설계가 가능하다

 

인터페이스 2개를 동시에 상속받는 코드

fun main() {
	var d = Dog()

	d.run()
	d.eat()
}

interface Runner {
	fun run()
}

interface Eater {
	fun eat() {
		println("음식을 먹습니다")
	}
}

class Dog : Runner, Eater {
	override fun run() {
		println("우다다다 뜁니다")
	}

	override fun eat() {
		println("허겁지겁 먹습니다")
	}
}

주의! 여러 개의 인터페이스나 클래스에서 같은 이름과 형태를 가진 함수를 구현하고 있다면 서브 클래스에서는 혼선이 일어나지 않도록 오버라이딩하기

 

오버라이딩 : 이미 구현된 함수를 서브클래스에서 변경해야 할 때

추상화 : 형식만 선언하고 실제 구현은 서브클래스에 일임할 때

인터페이스 : 서로 다른 기능들을 여러 개 물려주어야 할 때