java는 다중 상속을 지원하지 않기에 다중 구현을 하기 위함
sub class의 공통점을 추출해서
또 다른 super class를 선언하고 싶은 경우
형을 대상으로 참조변수 선언 가능
추상 메소드와 이를 구현하는 메소드 사이에 @Override
선언이 가능
interface Printable{
public void print(String doc);
}
class Printer implements Printable {
@Override
public void print(String doc) {
System.out.println("doc = " + doc);
}
}
public class 인터페이스특징예제 {
public static void main(String[] args)
{
Printable prn = new Printer();
prn.print("Printer print 호출");
}
}
Printable prn = new Printer();
Printable형 참조변수 선언 가능
Printable 인터페이스를 직접 혹은 간접적으로 구현하는
모든 클래스의 인스턴스 참조 가능
prn.print("Printer print 호출");
이 참조변수
를 대상으로
Printable 인터페이스에 정의된 추상 메소드를 호출할 수 있음.
( 이 모든 특성이 상속과 동일 )
인터페이스의 상속 기능 지원
interface Printable{
void print( String doc );
// + 신규 기능 추가
void printCMYK( String doc );
}
만약 위에 소스 처럼 Printable 인터페이스에서
기능이 하나 더 추가된다면 어떻게 될까?
위와 같이 수정한다면 기존 Printable 인터페이스를 기반으로 개발된
모든 드라이버를 수정해야 하는 불상사가 발생된다.
( interface default 메소드는 jdk1.8부터 사용 가능 )
따라서 이럴 경우
상속
으로 이 문제를 해결한다.
interface Printable{
void print( String doc );
}
// Printable을 상속하는 ColorPrintable 인터페이스
interface ColorPrintable extends Printable{
void printCMYK( String doc );
}
class SamsumColorPrintDriver implements ColorPrintable
{
// 흑백 출력
@Override
public void print( String doc ){
System.out.println( "From BLACK ver");
System.out.println( doc );
}
// 컬러 출력( 신규 기능 )
@Override
public void printCMYK( String doc ){
System.out.println( "From CMYK ver" );
System.out.println( doc );
}
}
public class Main
{
public static void main(String[] args)
{
String trgStr = "report ===== \n";
ColorPrintable prn = new SamsumColorPrintDriver();
prn.print( trgStr );
// 추가한 신규 기능 출력
prn.printCMYK( trgStr );
}
}
위 코드로 상속을 통해 이 문제를 해결할 수 있었다.
extends를 사용하는데 이에 대한 내용을 정리하자면
extends
로 명시extends
로 명시implements
로 명시자바에서는 클래스의 다중 상속을 허용하지 않음,그런데 때로는 이미 다른 클래스를 상속 받은 서브 클래스의 공통점을 추출해서 또 다른 슈퍼 클래를 선언하고 싶은 경우 인터페이스를 사용한다.
"단행본 클래스" 와 "부록 CD 클래스"는 대출에 관련된 많은 공통점을 갖고 있다. 하지만 그 공통점을 추출해서 슈퍼 클래스를 만들 수 없다.
왜냐하면 부록 CD 클래스는 이미 다른 클래스와 상속 관계를 맺고 있기 때문 따라서 이럴때는 클래스들의 공통점을 추출해서 인터페이스로 만들면 된다. java 에서는 Class의 상속 관계에 상관없이 인터페이스와 또 다른 관계를 맺을 수 있도록 허용하고 있기 때문이다.
그런데? 클래스들의 공통점을 추출해서 인터페이스를 만들 때는 제약이 있다.
인터페이스는 클래스들의 공통 기능만 표현할 수 있고, 공통 데이터는 표현할 수 없다는 제약때문이다.
그렇다면 위 그림의 "단행본 클래스" 와 "부록 CD 클래스"의 공통 기능을 뽑아서 인터페이스로 정의해보자
이렇게 공통 기능을 추출하고 인터페이스를 정의하는 것으로 끝나는 것이 아닌 인터페이스도 클래스와 마찬가지로 선언을 해줘야 한다.
클래스와 인터페이스의 관계는 클래스 쪽에서 implements라는 자바 키워드와 인터페이스 이름을 쓰는 것
즉, 상속이라는 용어 대신 구현( implementation ) 이라는 용어를 사용한다.
인터페이스에 속하는 메소드는 무조건 추상 메소드이다.
선언 하는 방법은 클래스와 비슷하지만 class 대신interface키워드를 사용한다.
인터페이스의 메소드는 무조건 추상 메소드이기 때문에 메소드 선언에 abstract 키워드를 쓰지 않아도 된다. 그 이유는 Java 컴파일러가 컴파일할 때 자동으로 추가 하기 때문이다.
implements 절을 써서 선언한 클래스는 그 인터페이스에 선언되어 있는 추상 메소드를 모두 상속받기 때문에 클래스 안에서 그 메소드들을 구현해야 한다.
인터페이스 선언은 클래스 선언과 비슷해 보이지만 인터페이스를 이용하여 객체를 만들 수는 없다. 그래서 위에서 선언한 Lendable 인터페이스를 다음과 같은 방법으로 사용하는 것은 잘못이다.
// 잘못된 예
object = new Lendable();
인터페이스는 본체가 없는 추상 메소드만 가질 수 있기 때문에 인터페이스를 구현하는 클래스에게 메소드의 로직을 상속해 줄 수는 없다.
하지만 클래스의 경우와 마찬가지로 추상 메소드를 이용하여 그 인터페이스를 구현하는 클래스의 선언 방법을 제한할 수는 있다.
예를 들어볼까? Lendable 인터페이스를 구현하는 다음과 같은 클래스가 있다고 해보자
1 2 3 4 5 6 7 8 9 | // Lendable 인터페이스를 구현하는 클래스의 잘못된 예 class Dictionary implements Lendable { String title; Dictionary(String title) { this.title = title; } } | cs |
위의 Lendable 인터페이스로부터 상속받은 추상 메소드 checkOut과 checkIn을 구현하지도 않고, 추상 메소드로 선언되지도 않았기 때문에 컴파일할 때 다음과 같은 메러가 발생된다.
인터페이스는 이 밖에도 인터페이스 변수의 선언에도 사용된다.
**대출가능 인터페이스**
1 2 3 4 5 | public interface Lendable { abstract void checkOut( String borrower, String data ); abstract void checkIn(); } | cs |
대출가능 인터페이스를 구현하는 단행본 클래스
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class SeparateVolume implements Lendable { String requestNo; // 청구번호 String bookTitle; // 제목 String writer; // 저자 String borrower; // 대출인 String checkOutDate; // 대출일 byte state; // 대출상태 public SeparateVolume(String requestNo, String bookTitle, String writer) { this.requestNo = requestNo; this.bookTitle = bookTitle; this.writer = writer; } // public은 인터페이스의 메소드를 구현할 때 반드시 써야 하는 키워드 @Override public void checkOut(String borrower, String data) { if ( state != 0 ) return; this.borrower = borrower; this.checkOutDate = data; this.state = 1; System.out.println( "*" + this.bookTitle + "이(가) 대출되었습니다." ); System.out.println( "대출인 : " + borrower ); System.out.println( "대출일 : " + data + "\n" ); } @Override public void checkIn() { this.borrower = null; this.checkOutDate = null; this.state = 0; System.out.println( "*" + this.bookTitle + "이(가) 반납되었습니다. \n" ); } } | cs |
SeparateVolume 클래스의 checkOut() 메소드와 checkIn() 메소드에는 public 키워드가 붙어 있는데, 인터페이스의 메소드를 구현하는 메소드는 반드시public 키워드가붙어야 한다.public 키워드가 붙어야 패키지 외부에서 사용할 수 있기 때문이다.
대출가능 관련번호 및 타이틀 정의 클래스
1 2 3 4 5 6 7 8 9 10 | class CDInfo { String registerNo; // 관련번호 String title; // 타이틀 public CDInfo(String registerNo, String title) { this.registerNo = registerNo; this.title = title; } } | cs |
대출가능 인터페이스를 구현하는 부록 CD 클래스
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class AppCDInfo extends CDInfo implements Lendable { String borrower; String checkOutDate; byte state; AppCDInfo ( String registerNo , String title ) { super( registerNo , title ); } @Override public void checkOut(String borrower, String date) { if ( state != 0 ) return; this.borrower = borrower; this.checkOutDate = date; this.state = 1; System.out.println("*" + title + "CD가 대출되었습니다."); System.out.println("대출인 : " + borrower); System.out.println("대출일 : " + date + "\n"); } @Override public void checkIn() { this.borrower = null; this.checkOutDate = null; this.state = 0; System.out.println( "*" + title + "CD가 반납되었다." ); } } | cs |
단행본 클래스와 부록 CD클래스를 사용하는 프로그램
1 2 3 4 5 6 7 8 9 10 11 12 | public class ExampleInterface { public static void main(String[] args) { SeparateVolume svObj = new SeparateVolume( "863?774개" , "개미" , "베르베르" ); AppCDInfo appCdObj = new AppCDInfo( "2005-7001" , "Redhat Fedora" ); svObj.checkOut( "hs" , "20210104" ); appCdObj.checkOut( "my" , "20201231" ); svObj.checkIn(); appCdObj.checkIn(); } } | cs |
대출가능 인터페이스 실행 결과
1 2 3 4 5 6 7 8 9 10 11 12 | *개미이(가) 대출되었습니다. 대출인 : hs 대출일 : 20210104 *Redhat FedoraCD가 대출되었습니다. 대출인 : my 대출일 : 20201231 *개미이(가) 반납되었습니다. *Redhat FedoraCD가 반납되었다. | cs |
인터페이스는 변수의 선언에 사용될 수 있다.
예를들어 위에 선언했던 Lendable 인터페이스 타입의 변수를 선언하면
Lendable obj;
인터페이스 변수에는 그 인터페이스를 구현하는 클래스의 객체라면 어떤 객체든지 다 대입할 수 있다.
obj = new SeparateVolume( "100001" , "엔트맨", "베르베르" );
obj = new AppCDInfo( "2006-7001" , "Redhat Fedora" );
이렇게 한 변수에 여러 종류의 데이터를 대입할 수 있는 성질을 변수의 다형성이라고 한다.
따라서 인터페이스 변수는 다형성을 갖는다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package com.basic.study.step8; public class ExampleInterface2 { static void checkOutAll( Lendable arr[] , String borrower, String date ) { for ( int i = 0; i < arr.length; i++ ) { arr[i].checkOut( borrower , date ); } } public static void main(String[] args) { // 인터페이스 타입 배열 Lendable lendableArry[] = new Lendable[3]; lendableArry[0] = new SeparateVolume( "100001" , "푸코의 진자" , "에코" ); lendableArry[1] = new SeparateVolume( "100002" , "서양미술사" , "곰브리치" ); lendableArry[2] = new AppCDInfo( "2021-1720" , "Java 프로그래밍"); // 배열의 모든 항목에 대해 checkOut 메소드 호출 checkOutAll( lendableArry , "hs" , "20210109" ); } } | cs |
1 2 3 4 5 6 7 8 9 10 11 | *푸코의 진자이(가) 대출되었습니다. 대출인 : hs 대출일 : 20210109 *서양미술사이(가) 대출되었습니다. 대출인 : hs 대출일 : 20210109 *Java 프로그래밍CD가 대출되었습니다. 대출인 : hs 대출일 : 20210109 | cs |
위 예제에서 Lendable 타입의 배열에 SeparateVolume 객체와 AppCDInfo 객체를 함께 담아서 checkOutAll 메소드에 넘겨주고 있다. 그리고 그 메소드에서는 배열에 담겨 있는 모든 객체에 대해 타입에 상관없이 무조건 checkOut 메소드를 호출하고 있다. 하지만 이때 호출되는 메소드는 각각의 객체가 속하는 클래스의 checkOut메소드이다. 그렇기 때문에 객체의 타입에 따라 다른 메소드가 호출되는 셈이다.
자바 컴파일러는 변수의 타입만 보고 메소드나 필드의 존재 여부를 체크하기 때문에 위 예제와 같이 인터페이스 변수를 가지고 메소드를 호출하기 위해서는 그 인터페이스에 해당 메소드가 있어야 한다.
인터페이스에는 인스턴스 필드는 선언할 수 없지만, 상수 필드 만큼은 선언할 수 있다. 인터페이스에 상수 필드를 선언하는 방법은 클래스에 상수 필드를 선언하는 방법과 같다.
// 상수 필드 선언
1 | final static int MAXIMUM = 100; | cs |
인터페이스를 구현하는 클래스들이 주로 사용하는 상수는 인터페이스 안에 선언하는 것이 좋다.
1 2 3 4 5 6 7 | interface Lendable { final static byte STATE_BORROWED = 1; // 대출 중 final static byte STATE_NORMAL = 0; // 대출되지 않은 상태 void checkOut( String borrower, String date ); void checkIn(); } | cs |
인터페이스에는 정적 초기화 블록을 사용할 수 없다. 그렇기 때문에 반드시 선언문에서 초기값을 대입해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | package com.basic.study.step8; class SeparateVolume implements Lendable { String requestNo; // 청구번호 String bookTitle; // 제목 String writer; // 저자 String borrower; // 대출인 String checkOutDate; // 대출일 byte state; // 대출상태 public SeparateVolume(String requestNo, String bookTitle, String writer) { this.requestNo = requestNo; this.bookTitle = bookTitle; this.writer = writer; } @Override public void checkOut(String borrower, String data) { // Lendable 인터페이스의 상수 필드를 사용 if ( state != STATE_NORMAL ) return; this.borrower = borrower; this.checkOutDate = data; this.state = STATE_BORROWED; // Lendable 인터페이스의 상수 필드를 사용 System.out.println( "*" + this.bookTitle + "이(가) 대출되었습니다." ); System.out.println( "대출인 : " + borrower ); System.out.println( "대출일 : " + data + "\n" ); } @Override public void checkIn() { this.borrower = null; this.checkOutDate = null; this.state = STATE_NORMAL; // Lendable 인터페이스의 상수 필드를 사용 System.out.println( "*" + this.bookTitle + "이(가) 반납되었습니다. \n" ); } } | cs |
위 클래스는 Lendable 인터페이스로부터 상속받은 상수 필드를 마치 클래스 안에 선언된 상수 필드처럼 자유롭게 사용하고 있다. 그렇지만 Lendable 인터페이스를 구현하지 않는 클래스에서 이 상수 필드를 사용하기 위해서는 필드 이름 앞에 인터페이스 이름과 마침표(.)를 써줘야 한다.
일반 클래스는 다중 상속이 불가능하다, 따라서 java에서는 인터페이스를 통해 다중 상속을 지원한다.
implements 키워드 뒤에 선언한다.
1 | class AppCDInfo extends CDInfo implements Lendable | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package me.java.study.java8.step8; interface pallet { public void fill(); } interface drawing { public void line(); public void circle(); public void rectangle(); } class Character implements pallet { @Override public void fill() { System.out.println( "색을 칠한다" ); } } public class MultiImplementsInterface { public static void main(String[] args) { Character character = new Character(); character.fill(); } } | cs |
[##Image|kage@b3WtBj/btqSV9klDsC/djHRmgVR0OkXJKqQHpudjk/img.png|alignLeft|data-origin-width="0" data-origin-height="0" data-ke-mobilestyle="widthContent"|한개의 인터페이스 선언 예제||##]
implements 키워드 뒤에콤마( , )로 구분하여 다중 상속이 가능하다.
1 | class AppCDInfo extends CDInfo implements Lendable , Resizable | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | package me.java.study.java8.step8; interface pallet { public void fill(); } interface drawing { public void line(); public void circle(); public void rectangle(); } class Character implements pallet , drawing { @Override public void fill() { System.out.println( "paint to color" ); } @Override public void line() { System.out.println( "draw a line" ); } @Override public void circle() { System.out.println( "draw a circle" ); } @Override public void rectangle() { System.out.println( "draw a rectangle" ); } } public class MultiImplementsInterface { public static void main(String[] args) { Character character = new Character(); character.fill(); character.line(); character.circle(); character.rectangle(); } } | cs |
[##Image|kage@q0TW9/btqS1VeoLPQ/A5kSPbMUYEwF9lOQHOhHrK/img.png|alignLeft|data-origin-width="0" data-origin-height="0" data-ke-mobilestyle="widthContent"|다중 인터페이스 선언 실행 결과||##]
implements가 먼저 선언되고 extends 가 올 수 없다.
기본 구현을 가지는 메서드 앞에 default예약어를 붙인다.
추상 메소드와 달리 일반 메소드처럼 몸통 ( {} ) 이 있어야 한다.
접근 제어자가 public이다 ( 생략 가능 )
기본 메소드는 인터페이스에 이미 구현되어 있으므로 인터페이스를 구현한 클래스에서 코드를 구현할 필요가
없다.
( 단, 구현하는 클래스에서 재정의 가능 )
인터페이스를 구현한 클래스에서 디폴트 메소드를 오버라이딩 해야 한다.
상위 클래스의 메소드가 상속되고, 디폴트 메소드는 무시된다.
인터페이스가 default키워드로 선언되면 메소드가 구현될 수 있다. 또한 이를 구현하는 클래스는 default메소드를 오버라이딩 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package com.basic.study.step8; interface Calculator { public int plus(int i, int j); public int multiple(int i, int j); default int exec(int i, int j){ //default로 선언함으로 메소드를 구현할 수 있다. return i + j; } } //Calculator인터페이스를 구현한 MyCalculator클래스 class MyCalculator implements Calculator { @Override public int plus(int i, int j) { return i + j; } @Override public int multiple(int i, int j) { return i * j; } } public class MyCalculatorExam { public static void main(String[] args){ Calculator cal = new MyCalculator(); int value = cal.exec(5, 10); System.out.println(value); } } | cs |
1 2 3 4 | 15 Process finished with exit code 0 | cs |
인터페이스가 변경이 되면, 인터페이스를 구현하는 모든 클래스들이 해당 메소드를 구현해야 하는 문제가 있다. 이런 문제를 해결하기 위하여 인터페이스에 메소드를 구현해 놓을 수 있도록 하였다.
인스턴스 생성과 상관없이 인터페이스 타입으로 호출하는 메서드.
static 예약어를 사용하며, 접근 제어자는 항상 public 이다 ( 생략 가능 )
정적 메소드를 사용할 떄는 인터페이스를 직접 참조하여 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package com.basic.study.step8; interface Calculator { public int plus(int i, int j); public int multiple(int i, int j); default int exec(int i, int j){ return i + j; } public static int exec2(int i, int j){ //static 메소드 return i * j; } } class MyCalculator implements Calculator { @Override public int plus(int i, int j) { return i + j; } @Override public int multiple(int i, int j) { return i * j; } } //인터페이스에서 정의한 static메소드는 반드시 인터페이스명.메소드 형식으로 호출해야한다. public class MyCalculatorExam { public static void main(String[] args){ Calculator cal = new MyCalculator(); int value = cal.exec(5, 10); System.out.println(value); int value2 = Calculator.exec2(5, 10); //static메소드 호출 System.out.println(value2); } } | cs |
인터페이스에 static 메소드를 선언함으로써, 인터페이스를 이용하여 간단한 기능을 가지는 유틸리티성 인터페이스를 만들 수 있게 되었다
출처 제공
뇌를 자극하는 Java 프로그래밍