✔ 인터페이스의 기본과 그 의미
인터페이스란?
- 기본 골격은 클래스와 동일하나 class 대신 interface라는 선언이 붙어 있음
- 인터페이스의 메소드는 몸체 없이 세미콜론으로 마무리됨
- 이렇게 메소드의 몸체가 비어 있는 메소드를 가리켜 추상 메소드라고 함
interface Printable {
public void print(String doc);
}
- 인터페이스를 대상으로는 인스턴스 생성이 불가능함
- 다만 이러한 인터페이스는 다른 클래스에 의해 상속이 됨
- 이렇게 클래스가 인터페이스를 상속하는 행위를 구현이라고 함
- 구현할 인터페이스를 명시할 때는 키워드 implements를 사용
- 한 클래스는 둘 이상의 인터페이스를 동시에 구현할 수 있음
- 상속과 구현은 동시에 가능함
class Printer implements Printable {
public void print(String doc) {
System.out.println(doc);
}
}
- 인터페이스는 인터페이스의 형을 대상으로 참조변수의 선언이 가능함
- 이는 인터페이스를 직접 혹은 직간접적으로 구현하는 모든 클래스의 인터페이스를 참조할 수 있음
- 그리고 참조변수를 대상으로 인터페이스에 정의된 추상 메소드를 호출할 수 있음
- 또한 인터페이스의 추상 메소드와 이를 구현하는 메소드 사이에 오버라이딩 관계가 성립하므로
@Override 어노테이션 선언이 가능하여 실수의 확률을 줄일 수 있음
interface Printable {
public void print(String doc);
}
class Printer implements Printable {
@Override
public void print(String doc) {
System.out.println(doc);
}
}
class PrintableInterface {
public static void main(String[] args) {
Printable prn = new Printer();
prn.print("Hello Java");
}
}
인터페이스의 장점
- 예) 마이크로소프트의 윈도우는 삼성과 LG의 프린터를 대상으로 출력을 진행할 수 있음
이 외에도 프린터를 제작하는 업체가 한 둘이 아니기 때문에,
마이크로소프트는 인터페이스를 하나 만들어서 모든 프린트 업체에 제공해야 함
- 이후 각각의 프린터 회사는 자사의 프린터 사용에 필요한 클래스인 드라이버를 각각 정의하여 제공하게 됨
- 이로 인해 마이크로소프트는 클래스 이름만 알면 될 뿐 내부적으로 구현이 어떻게 이뤄지는지 알 필요가 없어지게 됨
interface Printable {
public void print(String doc);
}
class SPrinterDriver implements Printable {
@Override
public void print(String doc) {
System.out.println("From Samsung printer");
System.out.println(doc);
}
}
class LPrinterDriver implements Printable {
@Override
public void print(String doc) {
System.out.println("From LG printer");
System.out.println(doc);
}
}
class PrinterDriver {
public static void main(String[] args) {
String myDoc = "This is a report about...";
Printable prn = new SPrinterDriver();
prn.print(myDoc);
prn = new LPrinterDriver();
prn.print(myDoc);
}
}
✔ 인터페이스의 문법 구성과 추상 클래스
인터페이스에 선언되는 메소드와 변수
- 인터페이스 내에 위치하는 모든 메소드는 public이 선언된 것으로 간주하므로 별도의 선언을 하지 않아도 됨
- 인터페이스 내에 선언되는 변수는 반드시 선언과 동시에 값으로 초기화해야 함
- 이러한 모든 변수는 public, static, final이 선언된 것으로 간주함
- 즉 인터페이스 내에 선언된 변수는 상수가 되는 것
- 그러므로 상수는 대문자로 이름을 짓는 관례를 따라 인터페이스 내에 위치한 변수의 이름은 대문자로 작성해야 함
interface Printable {
int PAPER_WIDTH = 70;
int PAPTER_HEIGHT = 120;
// public void print(String doc);
void print(String doc);
}
class Printer implements Printable {
@Override
public void print(String doc) {
System.out.println(Printable.PAPER_WIDTH);
System.out.println(doc);
}
}
인터페이스 간 상속
- 인터페이스를 구현하는 클래스는 인터페이스에 존재하는 모든 추상 메소드를 구현해야 함
- 하나라도 구현하지 않으면, 해당 클래스를 대상으로는 인스턴스 생성이 불가능
- 이때 만약 인터페이스를 수정할 경우, 기존에 이 인터페이스를 기반으로 개발된 드라이버를 모두 수정해야 함
- 이러한 상황을 고려하여 자바에서는 인터페이스의 상속을 지원함
- 두 인터페이스 사이의 상속을 명시할 때는 키워드 extends를 사용
- 이로써 기존에 제작 및 배포가 되어 사용 중인 드라이버를 수정할 필요가 없게 됨
interface Printable {
void print(String doc);
}
interface ColorPrintable extends Printable {
void printCMYK(String doc);
}
class Prn909Drv implements ColorPrintable {
@Override
public void print(String doc) { // 흑백 출력
System.out.println("From MD-909 black & white ver");
System.out.println(doc);
}
@Override
public void printCMYK(String doc) { // 컬러 출력
System.out.println("From MD-909 CMYK ver");
System.out.println(doc);
}
}
class PrinterDriver3 {
public static void main(String[] args) {
String myDoc = "This is a report about...";
ColorPrinable prn = new Prin909Drv();
prn.print(myDoc);
prn.printCMYK(myDoc);
}
}
인터페이스의 디폴트 메소드
- 디폴트 메소드를 사용하면 자체로 완전한 메소드이기 때문에 이를 구현하는 클래스가 오버라이딩 하지 않아도 됨
- 앞서 인터페이스 간 상속을 통해 해결하게 될 경우, 인터페이스의 수는 두 배로 늘어나게 되지만
디폴트 메소드를 통해 해결할 경우 인터페이스의 증가 없이 이전에 구현된 드라이버에 영향을 주지 않음
- 디폴트 메소드는 처음에는 없었지만, 이후 필요에 따라 메소드가 추가되었음으로 이해할 수 있음
- 그러므로 디폴트 메소드는 이전에 개발해 놓은 코드에 영향을 미치지 않기 위해 등장한 문법이기 때문에
처음 인터페이스를 설계하는 과정에서 디폴트 메소드를 정의해 넣는다면 디폴트 메소드를 잘못 이해하고 사용하는 것
interface Printable {
void print(String doc);
default void printCMYK(String doc) { }
}
class Prn731Drv implements Printable {
@Override
public void print(String doc) {
System.out.println("From MD-731 printer");
System.out.println(doc);
}
}
class Prn909Drv implements Printable {
@Override
public void print(String doc) { // 흑백 출력
System.out.println("From MD-909 black & white ver");
System.out.println(doc);
}
@Override
public void printCMYK(String doc) { // 컬러 출력
System.out.println("From MD-909 CMYK ver");
System.out.println(doc);
}
}
인터페이스의 static 메소드
- 인터페이스에도 static 메소드를 둘 수 있음
- 인터페이스의 static 메소드도 인터페이스의 다른 메소드들과 마찬가지로 public이 선언된 것으로 간주함
- 이러한 인터페이스의 static 메소드를 호출하는 방법은 클래스의 static 메소드 호출 방법과 동일
interface Printable {
static void printLine(String str) {
System.out.println(str);
}
default void print(String doc) {
printLine(doc);
}
}
class Printer implements Printable { }
class PrintableInterface {
public static void main(String[] args) {
Printable prn = new Printer();
Printable.printLine("end of line");
}
}
인터페이스 대상의 instanceof 연산
- 참조변수와 클래스의 이름을 피연산자로 하는 instanceof 연산자와 유사
- 참조변수가 참조하는 클래스가 직접 혹은 간접적으로 인터페이스를 구현한 클래스의 인스턴스인 경우 true
그렇지 않다면 false를 반환
인터페이스의 또 다른 사용 용도
- 클래스에 특별한 표식을 다는 용도인 마커 인터페이스로도 사용됨
- 마커 인터페이스에는 아무런 메소드도 존재하지 않는 경우가 많음
interface Upper { }
interface Lower { }
interface Printable {
String getContents();
}
class Report implements Printable, Upper {
String cons;
Report(String cons) {
this.cons = cons;
}
pubic String getContents() {
return cons;
}
}
class Printer {
public void printContents(Printable doc) {
// Upper 인터페이스를 구현하면 대문자로 출력
if(doc instanceof Upper) {
System.out.println((doc.getContents()).toUpperCase());
}
// Lower 인터페이스를 구현하면 소문자로 출력
else if(doc instanceof Lower) {
System.out.println((doc.getContents()).toLowerCase());
}
else
System.out.println(doc.getContents());
}
}
추상 클래스
- 하나 이상의 추상 메소드를 갖는 클래스를 추상 클래스라고 함
- 이러한 추상 클래스는 클래스의 선언부에 abstract 선언을 추가해야 함
public abstract class House {
public void methodOne() {
System.out.println("method one");
}
public abstract void methodTwo();
}
- 추상 클래스는 성격이 인터페이스와 유사함
- 추상 클래스를 대상으로 인스턴스 생성도 불가능하며 다른 클래스에 의해서 추상 메소드가 구현이 되어야 함
- 그럼에도 불구하고 이는 클래스이므로 상속의 형태를 띄어 키워드 extends를 사용함
- 즉, 인스턴스 변수와 인스턴스 메소드를 갖지만,
이를 상속하는 하위 클래스에 의하여 구현되어야 할 메소드가 하나 이상 있는 경우를 뜻함
public class MyHouse extends House {
@Override
public void methodTwo() {
System.out.println("method two);
}
}