[8] Java - Interface

harold·2021년 3월 4일
0

java

목록 보기
5/13

인터페이스를 사용하는 이유

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로 명시

1. 인터페이스 정의하는 방법

인터페이스를 사용하는 이유

자바에서는 클래스의 다중 상속을 허용하지 않음,그런데 때로는 이미 다른 클래스를 상속 받은 서브 클래스의 공통점을 추출해서 또 다른 슈퍼 클래를 선언하고 싶은 경우 인터페이스를 사용한다.

인터페이스를 사용하는 이유 예제

"단행본 클래스" 와 "부록 CD 클래스"는 대출에 관련된 많은 공통점을 갖고 있다. 하지만 그 공통점을 추출해서 슈퍼 클래스를 만들 수 없다.

왜냐하면 부록 CD 클래스는 이미 다른 클래스와 상속 관계를 맺고 있기 때문 따라서 이럴때는 클래스들의 공통점을 추출해서 인터페이스로 만들면 된다. java 에서는 Class의 상속 관계에 상관없이 인터페이스와 또 다른 관계를 맺을 수 있도록 허용하고 있기 때문이다.

그런데? 클래스들의 공통점을 추출해서 인터페이스를 만들 때는 제약이 있다. 

인터페이스는 클래스들의 공통 기능만 표현할 수 있고, 공통 데이터는 표현할 수 없다는 제약때문이다.

그렇다면 위 그림의 "단행본 클래스" 와 "부록 CD 클래스"의 공통 기능을 뽑아서 인터페이스로 정의해보자

이렇게 공통 기능을 추출하고 인터페이스를 정의하는 것으로 끝나는 것이 아닌 인터페이스도 클래스와 마찬가지로 선언을 해줘야 한다.

2. 인터페이스 구현하는 방법

클래스와 인터페이스의 관계는 클래스 쪽에서 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

3. 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

인터페이스 변수의 다형성

인터페이스는 변수의 선언에 사용될 수 있다.

예를들어 위에 선언했던 Lendable 인터페이스 타입의 변수를 선언하면

  Lendable obj;

인터페이스 변수에는 그 인터페이스를 구현하는 클래스의 객체라면 어떤 객체든지 다 대입할 수 있다.

  obj = new SeparateVolume( "100001" , "엔트맨", "베르베르" );

  obj = new AppCDInfo( "2006-7001" , "Redhat Fedora" );

이렇게 한 변수에 여러 종류의 데이터를 대입할 수 있는 성질을 변수의 다형성이라고 한다.

따라서 인터페이스 변수는 다형성을 갖는다

인터페이스 변수의 다형성을 이용한 단행본과 부록 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
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 인터페이스를 구현하지 않는 클래스에서 이 상수 필드를 사용하기 위해서는 필드 이름 앞에 인터페이스 이름과 마침표(.)를 써줘야 한다.

4. 인터페이스 상속

일반 클래스는 다중 상속이 불가능하다, 따라서 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 가 올 수 없다.

5. 인터페이스의 기본 메소드 (Default Method), 자바 8

기본 구현을 가지는 메서드 앞에 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(510);
        System.out.println(value);
    }
}
 
cs
1
2
3
4
15
 
Process finished with exit code 0
 
 
cs

인터페이스가 변경이 되면, 인터페이스를 구현하는 모든 클래스들이 해당 메소드를 구현해야 하는 문제가 있다. 이런 문제를 해결하기 위하여 인터페이스에 메소드를 구현해 놓을 수 있도록 하였다.

6. 인터페이스의 static 메소드, 자바 8

인스턴스 생성과 상관없이 인터페이스 타입으로 호출하는 메서드.

static 예약어를 사용하며, 접근 제어자는 항상 public 이다 ( 생략 가능 )

정적 메소드를 사용할 떄는 인터페이스를 직접 참조하여 사용한다.

static 메소드 예제

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(510);
        System.out.println(value);
 
        int value2 = Calculator.exec2(510);  //static메소드 호출
        System.out.println(value2);
    }
}
 
cs

인터페이스에 static 메소드를 선언함으로써, 인터페이스를 이용하여 간단한 기능을 가지는 유틸리티성 인터페이스를 만들 수 있게 되었다

출처 제공

뇌를 자극하는 Java 프로그래밍

https://velog.io/@ednadev

https://programmers.co.kr/

0개의 댓글