[KOSTA JAVA] #Day 8 (Interface)

0f1c04·2021년 2월 24일
0

KOSTA JAVA TIL

목록 보기
7/11
post-thumbnail

신용권님의 ''이것이 자바다'' 공부 기록

책을 보면서 내용을 정리했습니다. 이것이 자바다 커뮤니티 : https://cafe.naver.com/thisisjava

1. 인터페이스의 역할

인터페이스란 개발 코드와 객체가 서로 통신하는 접점이다. 개발 코드는 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출시킨다. 그래서 개발 코드는 객체의 내부 구조를 알 필요없이 인터페이스의 메소드만 알고 있으면 된다. 인터페이스의 역할로 객체를 교체할 수 있도록 개발 코드가 객체에 종속되지 않게 한다. 또 개발 코드 변경 없이 리턴값 또는 실행 내용이 다양해지는 다형성을 지원한다.


2. 인터페이스 선언

2.1 인터페이스 선언

  • 인터페이스 선언은 class 키워드 대신 interface 키워드를 사용한다.

    [public] interface interfaceName {...}

  • 인터페이스 이름은 자바 식별자 작성 규칙에 따라 작성한다.

    interface interfaceName {
      //상수
      타입 상수명 =;
      //추상 메소드
      타입 메소드명(매개변수, ...);
      //디폴트 메소드
      default 타입 메소드명(매개변수, ...) {...}
      //정적 메소드
      static 타입 메소드명(매개변수) {...}
    }

2.2 상수 필드 선언

  • 인터페이스는 데이터를 저장하지 않는 상수 필드만 선언 가능하다.
  • 인터페이스에 선언된 필드는 모두 public static final 문을 사용하지만 자동적으로 컴파일 과정에 붙으므로 생략해도 된다.
  • 상수명은 대문자로 작성하는 것이 관례이다. 서로 다른 단어로 구성되어 있을 경우 언더 바(_)로 연결한다.
  • 선언과 동시에 초기값을 지정한다. static { } 블록은 작성할 수 없으며 초기화도 불가능하다.

2.3 추상 메소드 선언

  • 인터페이스를 통해 호출된 메소드는 최종적으로 객체에서 실행한다.

    • 인터페이스의 메소드는 기본적으로 실행 블록이 없는 추상 메소드로 선언된다.

    • public abstract 를 생략하더라도 자동적으로 컴파일 과정에서 붙게 된다.

      [public abstract] 리턴타입 메소드명(매개변수, ...);

2.4 디폴트 메소드 선언

  • JAVA8에서 추가된 인터페이스의 새로운 멤버이다.

    [public] default 리턴타입 메소드명(매개변수, ...) {...}

  • 실행 블록을 가지고 있는 메소드이다.

  • default 키워드를 반드시 붙여야 한다.

  • 기본적으로 public 접근 제한이다. 생략하더라도 컴파일 과정에서 자동으로 붙는다.

2.5 정적 메소드

  • 정적 메소드 또한 디폴트 메소드처럼 JAVA8에서 추가된 새로운 멤버이다.

  • 디폴트 메소드와 달리 객체가 없어도 인터페이스만으로 호출이 가능하다.

    [public] static 리턴타입 메소드면(매개변수, ...) { ... }

    public interface RemoteControl {
      static void changeBattery() {
        System.out.println("건전지를 교환합니다.");
      }
    }

2.6 인터페이스 선언 예제 (상수, 추상, 디폴트, 정적)

package com.kosta.day08;

public interface RemoteControl {
    //1. 상수
    public static final int MAX_VALUE = 10;    //public static final 생략가능
    int MIN_VALUE = 0;

    //2. 추상 메소드
    public abstract void turnOn();             //public abstract 생략가능
    void turnOff();
    void setVolume(int volume);

    //3. 디폴트 메소드(default method: 구현 class들이 공통적으로 사용하는 기능 구현
    public default void setMute(boolean mute) { //public 생략가능
        if(mute) {
            System.out.println("무음 처리합니다.");
        } else {
            System.out.println("무음 해제합니다.");
        }
    }

    //4. 정적 메소드(static method: interface의 메소드)
    public static void changeBattery() {        //public 생략가능
        System.out.println("건전지를 교환합니다.");
    }
}

3. 인터페이스 구현

3.1 구현 객체와 구현 클래스

  • 인터페이스의 추상 메소드에 대한 실체 메소드를 가진 객체를 구현 객체라 한다.
  • 구현 객체를 생성하는 클래스를 구현 클래스한다.

3.2 구현 클래스 선언

  • 자신의 객체가 인터페이스 타입으로 사용할 수 있다.

    • implements 키워드로 명시

      public class 구현클래스명 implements 인터페이스명 {
        //인터페이스에 선언된 추상 메소드의 실체 메소드 선언
      }

3.3 추상 메소드의 실체 메소드를 작성하는 방법

  • 메소드의 선언부가 정확히 일치해야 한다.
  • 인터페이스의 모든 추상 메소드를 재정의하는 실체 메소드를 작성해야한다.
    • 일부만 재정의할 경우, 추상 클래스로 선언하고 abstract 키워드를 붙인다.

3.4 익명 구현 객체

명시적인 구현 클래스 작성을 생략하고 바로 구현 객체를 얻는 방법이다. 이름 없는 구현 클래스 선언과 동시에 객체를 생성한다. 인터페이스의 추상 메소드들을 모두 재정의하는 실체 메소드가 있어야 하며 추가적으로 필드와 메소드 선언이 가능하나 익명 객체 안에서만 사용한다. 인터페이스 변수로는 접근이 불가능하다.

인터페이스 변수 = new 인터페이스() {
  //인터페이스에 선언된 추상 메소드의 실체 메소드 선언
}

3.5 다중 인터페이스 구현 클래스

객체는 다수의 인터페이스 타입으로 사용할 수 있다. 다수의 인터페이스 객체의 메소드를 호출할 수 있으려면 객체는 다수 인터페이스를 모두 구현해야 한다. 다중 인터페이스를 구현할 경우, 구현 클래스는 모든 인터페이스의 추상 메소드에 대해 실체 메소드를 작성해야 한다. 만약 하나라도 없으면 추상 클래스로 선언해야 한다.

public class 구현클래스명 implements 인터페이스A, 인터페이스B {
  //인터페이스 A에 선언된 추상 메소드의 실체 메소드 선언
  //인터페이스 B에 선언된 추상 메소드의 실체 메소드 선언
}

4. 인터페이스 사용

인터페이스로 구현 객체를 사용하려면 인터페이스 변수를 선언하고 구현 객체를 대입해야 한다. 인터페이스 변수는 참조 타입이기 때문에 구현 객체가 대입될 경우 구현 객체의 번지를 저장한다.

인터페이스 변수;
변수 = 구현객체;

or
  
인터페이스 변수 = 구현객체;
  • 개발 코드에서 인터페이스는 클래스의 필드, 생성자 또는 메소드의 매개변수, 생성자 또는 메소드의 로컬 변수로 선언될 수 있다.

4.1 추상 메소드 사용

  • 구현 객체가 인터페이스 타입에 대입되면 인터페이스에 선언된 추상 메소드를 개발 코드에서 변수를 통해 호출할 수 있게 된다.
RC rc = new TV();
rc.turnOn();	//TV의 실체 메소드 실행

4.2 디폴트 메소드 사용

  • 디폴트 메소드는 인터페이스에 선언되나, 인터페이스에서 바로 사용할 수 없다. 디폴트 메소드는 추상 메소드가 아닌 인스턴스 메소드이므로 구현 객체가 있어야 사용이 가능하다.

    RC rc = new tv();
    rc.turnOff();
  • 디폴트 메소드는 인터페이스의 모든 구현 객체가 가지고 있는 기본 메소드라고 생각할 수 있다.

  • 어떤 객체의 디폴트 메소드 내용의 수정이 필요할 때는, 이를 재정의(오버라이딩)하면 된다. 그러면 디폴트 메소드를 호출할 때 재정의한 메소드가 호출된다.

4.3 정적 메소드 사용

  • 인터페이스의 정적 메소드는 인터페이스로 바로 호출이 가능하다.

    public class ex {
        public static void main(String[]args){
            RC.go(); //인터페이스로 바로 호출
        }
    }

5. 타입변환과 다형성

요즘은 상속보다도 인터페이스를 통해 다형성을 구현한다. 상속에서 부모 타입에 어떤 자식 객체를 대입하느냐에 따라 실행 결과가 달라지는 것처럼, 인터페이스 타입에 어떤 구현 객체를 대입하느냐에 따라 실행 결과가 달라진다.

상속은 같은 종류의 하위 클래스를 만드는 기술이고, 인터페이스는 사용 방법이 동일한 클래스를 만드는 기술이다.

프로그램 소스 코드는 변함이 없는데, 구현 객체를 교체함으로써 프로그램의 실행 결과가 다양해지는 것을 인터페이스의 다형성이라고 부른다.

  • 클래스에 문제가 있어 다른 클래스를 만들 때는 같은 메소드를 사용한다면 메소드 선언부가 동일해야 한다. 인터페이스를 추상 메소드를 작성하고 구현 클래스로 해당 메소드를 작성할 시에 이러한 문제를 해결할 수 있다. 처음부터 메소드 선언부가 동일하기 때문이다.
  • 인터페이스는 메소드의 매개 변수로 많이 등장하는데, 해당 매개 값으로 여러 종류의 구현 객체를 줄 수 있으므로 메소드 실행 결과가 다양하게 나온다. 이것이 인터페이스 매개 변수의 다형성이다.

5.1 자동 타입 변환(Promotion)

구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환(promotion)에 해당한다. 자동 타입 변환은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.

인터페이스 변수 = 구현객체; //구현객체가 자동 타입 변환됨
  • 구현 클래스를 상속하여 자식 클래스를 만들었다면 자식 객체 역시 인터페이스 타입으로 자동 타입 변환시킬 수 있다. 이를 통해 필드의 다형성과 매개 변수의 다형성을 구현할 수 있다.

5.2 필드의 다형성

  • 같은 인터페이스의 구현 객체는 교체될 수 있다. 따라서 개발 코드의 수정 없이도 다양한 결과(개발 코드에서의 인터페이스 메소드 실행 등)를 얻을 수 있다.

5.3 인터페이스 배열로 구현 객체 관리

  • 인터페이스 배열을 통해 여러 구현 객체들을 인덱스로 표현하여 관리할 수 있다. 이렇게 하면 제어문에서 가장 큰 이점이 있다.
Tire[] tires ={
    new HankookTire();
    new HankookTire();
    new HankookTire();
    new HankookTire();
}
void run(){
    for(Tire tire : tires){
        tire.roll();
    }
}

5.4 매개 변수의 다형성

  • 자동 타입 변환은 주로 메소드를 호출할 때 많이 발생한다. 매개 변수를 인터페이스 타입으로 선언하고 호출 시 구현 객체를 대입한다.
//Driver 클래스
public class Driver{
    public void drice(Vehicle vehicle){
        vehicle.run();
    }
}

//Vehicle 인터페이스 타입
public interface Vehicle{
    public void run();
}

//구현 객체 Bus
public class Bus implements Vehicle{
    @Override
    public void run(){
        System.out.println("버스가 달립니다.");
    }
}

//매개 변수의 다형성 테스트
public class DriverEx{
    public static void main(String[] args){
        Driver driver = new Driver()
            
        Bus bus = new Bus();
        
        driver.drive(bus); //bus -> 자동 타입 변환됨
    }
}
  • 인터페이스가 매개변수 타입으로 제공될 경우, 어떠한 구현 객체도 매개값으로 사용할 수 있고, 이를 통해 메소드의 실행결과가 다양해질 수 있게 된다.(매개 변수의 다형성)

5.5 강제 타입 변환

  • 구현 객체가 인터페이스 타입으로 자동 변화하면, 인터페이스에 선언된 메소드만 사용이 가능하다. 그러나 경우에 따라 구현 클래스에 선언된 필드와 메소드를 사용해야 할 수 있는데, 이 때 강제 타입 변환을 사용하여 구현 클래스의 필드와 메소드를 사용할 수 있다.
구현 클래스 변수 = (구현 클래스) 인터페이스 변수; //강제 타입 변환

5.6 객체 타입 확인

  • 강제 타입변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능하다. 이 때 상속에서 객체 타입 확인을 한 것처럼, 인터페이스 타입도 객체의 타입 확인이 필요하다. 어떤 구현 객체가 변환되어 있는지 알지 못하고 변환할 경우 ClassCastException이 발생할 수 있기 때문이다.
  • 객체 타입 확인을 위하여 instanceof 연산자를 사용할 수 있다.
if(vehicle instanceof Bus){
    Bus bus = (Bus) vehicle;
}

6. 인터페이스 상속

  • 인터페이스도 다른 인터페이스를 상속할 수 있다. 인터페이스는 클래스와 달리 다중 상속을 허용한다.
public interface 하위인터페이스 extends 상위인터페이스1, 하위인터페이스2 {...}
  • 하위 인터페이스의 구현 클래스는 하위, 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야 한다. 따라서 해당 구현 클래스로부터 객체를 생성하고 나서 하위 및 상위 인터페이스 타입으로 변환이 가능하다.
하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);
  • 하위 인터페이스로 타입 변환이 되면 상하위 인터페이스에 선언된 모든 메소드를 사용할 수 있으나, 상위 인터페이스로 타입 변환시 하위 인터페이스에 선언된 메소드는 사용이 불가하다.

7. 디폴트 메소드와 인터페이스 확장

  • 디폴트 메소드는 인터페이스에서 선언된 인스턴스 메소드이므로 구현 객체가 있어야 사용가능하다. 왜 선언은 인터페이스에서 하고, 사용은 구현 객체를 통해 하는 것일까?

7.1 디폴트 메소드의 필요성

인터페이스에서 디폴트 메소드를 허용한 이유는 기존 인터페이스를 확장해서 새로운 기능을 추가하기 위해서이다. 기존 인터페이스의 이름과 추상 메소드의 변경 없이 디폴트 메소드만 추가할 수 있기 때문에 이전에 개발한 구현 클래스를 그대로 사용할 수 있으면서 새롭게 개발하는 클래스는 디폴트 메소드를 활용할 수 있다.

  • 추상 클래스는 구현 클래스에서 실행 내용을 채워야 하지만, 디폴트 메소드는 인터페이스에 정의된 것을 그냥 사용해도 되고, 필요에 따라 재정의해서 사용할 수도 있다.

7.2 디폴트 메소드가 있는 인터페이스 상속

  • 부모 인터페이스에 디폴트 메소드가 정의되어 있을 경우, 자식 인터페이스에서 디폴트 메소드를 활용하는 방법은 다음 세 가지가 있다.
  1. 디폴트 메소드를 단순히 상속만 받는다.
//부모 인터페이스
public interface ParentInterface{
    public void method1();
    public default void method2(){/*실행문*/}
}
//자식 인터페이스1
public interface ChildInterface1 extends ParentInterface {
    public void method3;
}
//자식 인터페이스1을 구현하는 클래스는 method1()과 method3()의 실체 메소드를 가지고 있어야 하며 method2()를 호출할 수 있다.
ChildInterface1 ci1 = new ChildInterface1(){ //익명 구현 객체
    @Override
    public void method1() {/*실행문*/}
    @Override
    public void method3() {/*실행문*/}
};

ci1.method1();
ci1.method2();//ParentInterface의 method2() 호출
  1. 디폴트 메소드를 재정의(Override)해서 실행 내용을 변경한다.
//자식 인터페이스2
public interface ChildInterface2 extends ParentInterface {
    @Override
    public default void method2() {/*실행문*/} //재정의
    public void method3();
}
//자식 인터페이스1 구현 클래스와 유사함.
ChildInterface2 ci2 = new ChildInterface2(){ //익명 구현 객체
    @Override
    public void method1() {/*실행문*/}
    @Override
    public void method3() {/*실행문*/}
};

ci2.method1();
ci2.method2();//ParentInterface의 method2() 호출
  1. 디폴트 메소드를 추상 메소드로 재선언한다.
//자식 인터페이스3
public interface ChildInterface3 extends ParentInterface {
    @Override
    public void method2() //추상 메소드로 재선언
    public void method3();
}
//자식 인터페이스3을 구현하는 클래스는 method1(), method2(), method3()의 실체 메소드를 모두 가지고 있어야 한다.
ChildInterface2 ci3 = new ChildInterface3(){ //익명 구현 객체
    @Override
    public void method1() {/*실행문*/}
    @Override
    public void method2() {/*실행문*/}
    @Override
    public void method3() {/*실행문*/}
};

ci3.method1();
ci3.method2();//ChildInterface3 구현 객체의 method2() 호출
profile
라면 먹고 싶다. 두 개 끓여서 혼자 먹고 싶다. - 임덕배 (1997. 06 ~ )

0개의 댓글