본문 바로가기

Java SE Tutorials/Interfaces

Java Interfaces

Java 프로그래밍, 인터페이스 ( Interfaces )


인터페이스 (Interfaces)의 개념

소프트웨어를 개발할 때는 다수의 개발자나 다수의 그룹으로 나뉘어 개발해야 하는 상황이 많다. 이런 상황에서 한 팀과 다른 팀은 소프트웨어의 상의 서로 관련이 있는 부분을 개발해야 할 때도 있는데, 팀이 다르더라도 다수의 팀들은 한개의 소프트웨어를 작성하는 것이므로 한 팀에서 작성한 소프트웨어 모듈은 다른 팀에서 작성한 모듈과 서로 잘 연결되어야 한다.

인터페이스(Interfaces)란 용어의 정의부터 알아보면, '장치와 장치를 서로 잇는 부분' 이라는 뜻이다. 그 의미가 확대되어 사람과 컴퓨터 간의 인터페이스도 생각할 수 있는데, 키보드나 마우스, 모니터가 해당될 것이다. 소프트웨어 개발에 적용해보면, 소프트웨어 각 모듈간의 연결을 담당하는 부분을 인터페이스라고 할 수 있을 것이다.

좀더 실제적으로 Java 언어에서 자주 사용되는 인터페이스와 그 역할에 대해서 알아보면 이해가 쉬울 것이다. Java 언어에서는 컴퓨터 하드웨어에서 발생하는 저수준의 다양한 이벤트를 처리하는 로직이 내장되어 있기 때문에 개발자의 이벤트 처리 로직과 시스템의 이벤트가 서로 잘 연결되어야 하는데, 이 부분에서 개발자는 Java 언어에서 제공하는 다양한 인터페이스를 구현하여 시스템의 저수준 이벤트 처리 로직과 연결해야 한다.

개발자와 개발자간의 코드가 무리 없이 연결되어야 하고, 두개의 소프트웨어가 연결되어야 하고, 팀간의 모듈이 연결되어야 하고, 시스템과 시스템이 연결되어야 하고, 개발자의 코드와 시스템에 내장된 코드가 서로 연결되어야 하는 부분에서 Java는 인터페이스(Interfaces)라는 것을 사용할 수가 있다. 좀더 원리적으로 자세한 설명은 상속(Inheritance), 다형성(Polymorphism)을 학습한 후에 보다 잘 이해할 수 있을 것이다


인터페이스의 선언 형식

  • 접근 제한자 : public, default 중 하나이어야 한다
  • 다른 인터페이스를 중첩하여 상속할 수 있다
  • 메소드 : 묵시적으로 public 이므로 생략해도 된다. 반드시 public 이어야 한다
  • 메소드 형태 : abstract, default, static ( default, static은 추상 메소드가 아님)
  • 추상 메소드 : abstract 메소드는 메소드의 바디를 정의하면 안된다
  • 변수 : 묵시적으로 public static final 이므로 생략해도 된다. 반드시 public static final 이어야 한다


인터페이스 선언 및 구현 클래스의 예

public class Main
{
	public static void main(String[] args)
	{
		Item item = new Item("프리미엄 이어셋", 5, 35000);
		if(item instanceof Printable)
		{
			item.printInfo();
		}
	}
}

interface Printable
{
	void printInfo();
	int getQty();
	int getPrice();
}

class Item implements Printable
{
	private String itemName;
	private int price;
	private int qty;
	
	public Item(String itemName, int qty, int price)
	{
		this.itemName = itemName;
		this.qty = qty;
		this.price = price;
	}
	
	@Override
	public void printInfo() {
		System.out.printf("이름:%s, 수량:%d, 가격:%d %n", itemName, qty, price);
	}

	@Override
	public int getQty() {
		return qty;
	}

	@Override
	public int getPrice() {
		return price;
	}
}


JDK 1.8 부터 인터페이스에 추가된 default 메소드

JDK 1.7까지는 인터페이스를 정의할 때 메소드의 바디가 없는 추상(abstract) 메소드만 선언할 수 있었다. JDK 1.8에서 부터는 인터페이스에도 바디를 가진 완전한 메소드를 정의할 수 있게 되었다. 추상 메소드(abstract methods)만 선언할 수 있었던 제약이 약간 변경되면서 디폴트 메소드(default methods)를 선언할 수 있게 되었는데, 이렇게 되면 추상 클래스(abstract class)와 인터페이스(interface)의 차이는 그만큼 줄어들게 된다. 그러나 자바에서 추상 클래스의 자리를 인터페이스로 대체하려는 의도는 아니고 인터페이스가 가진 단점을  약간 보완하여 개발자에게 유지보수의 편의성을 제공하려는 의도라고 생각할 수 있다.


default 메소드

  • 인터페이스 안에만 선언할 수 있다
  • 추상 메소드가 아니라는 뜻으로 default 키워드를 사용한다
  • default 메소드는 반드시 완전한 바디 블럭을 가져야 한다
  • 구현하는 클래스에는 디폴트 메소드가 그대로 상속되어 사용되므로 반드시 오버라이드할 필요는 없다
  • 구현 클래스에서는 디폴트를 메소드를 필요에 따라 오버라이드할 수도 있다


default 메소드 도입의 장점 ( 인터페이스의 기능을 업그레이드할 때 유용할 수도 있음 )

JDK 1.8부터 인터페이스에 추가된 디폴트 메소드는 기존 인터페이스가 가진 단점을 극복하기 위한 것이다. 그렇다고 인터페이스를 정의하고 사용할 때 무조건 디폴트 메소드를 포함해야 한다고 생각하면 오산이고 인터페이스를 그렇게 사용할 바엔 차라리 추상 클래스를 사용하는 편이 나을 것이다. 프로그램을 구성하는 수많은 클래스에서 특정 인터페이스를 구현했다고 할 때 인터페이스의 기능을 개선하기 위해 새로운 추상 메소드를 추가하면, 그 인터페이스를 구현한 모든 클래스에서는 구현하지 않는 추상 메소드가 있다는 오류가 발생하게 된다. 이런 경우에는 오류를 발생한 모든 클래스를 찾아서 추가된 추상 메소드를 구현해야 하는 부담이 따른다. 그러나 상황이 허용하지 않는다면 일단 프로그램을 그대로 유지하면서 인터페이스에 디폴트 메소드나 static 메소드를 추가하여 급한대로 기능이 정상적으로 작동하도록 할 수는 있다. 인터페이스에 디폴트 메소드나 static 메소드를 선언하면 이를 구현한 클래스에서는 별도로 해당 메소드를 오버라이드 할 필요가 없이 디폴트 메소드를 그대로 사용할 수가 있어서 오류를 피하면서 기능을 추가할 수 있게 된다


디폴트 메소드를 선언하고 사용하는 예

public class Main
{
	public static void main(String[] args)
	{
		Item item = new Item("프리미엄 이어셋", 5, 35000);
		if(item instanceof Printable)
		{
			item.printInfo();
			
			// 인터페이스에 디폴트(default)메소드를 추가하고 구현 클래스를 수정하지 않아도  추가된 기능이 실행된다
			item.printTotal();
		}
	}
}

interface Printable
{
	void printInfo();
	int getQty();
	int getPrice();
	
	// 디폴트 메소드를 추가해도 구현 클래스에서 오류가 발생하거나 수정할 필요가 없다
	default void printTotal()   
	{
		System.out.printf("총 금액=%d 원 %n", getQty()*getPrice());
	}
}

// 인터페이스를 구현하는 클래스에서는 인터페이스에서 디폴트 메소드를 추가해도 아무런 수정도 필요없다
class Item implements Printable
{
	private String itemName;
	private int price;
	private int qty;
	
	public Item(String itemName, int qty, int price)
	{
		this.itemName = itemName;
		this.qty = qty;
		this.price = price;
	}
	
	@Override
	public void printInfo() {
		System.out.printf("이름:%s, 수량:%d, 가격:%d %n", itemName, qty, price);
	}

	@Override
	public int getQty() {
		return qty;
	}

	@Override
	public int getPrice() {
		return price;
	}
}