[이것이 자바다 - 8] 인터페이스

Uicheon·2022년 6월 8일
0

이것이 자바다

목록 보기
6/8
post-thumbnail

8. 인터페이스


아닙니다


8.1 인터페이스의 역할

인터페이스는 객체의 사용 방법을 정의한 타입이다.
개발 코드와 객체가 서로 통신하는 접점 역할을 한다.
개발 코드가 인터페이스의 메소드를 호출하면, 인터페이스는 객체의 메소드를 호출시킨다.
그렇기 때문에 개발 코드는 객체의 내부 구조를 알 필요가 없고 인터페이스의 메소드만 알고 있으면 된다.

그렇다면 인터페이스를 둬야 하는가?
그 이유는 개발 코드를 수정하지 않고, 사용 객체를 변경하기 위함이다.
인터페이스는 하나의 객체가 아니라 여러 객체들과 사용이 가능하므로, 코드 변경 없이 실행 내용과 리턴값을 다양화 시킬 수 있다.

8.2 인터페이스 선언

(인터페이스도 "*.java" 형태의 소스파일로 작성되고, 컴파일도 "*.class 형태로 컴파일 된다. 선언방식만 다르다)

8.2.1 인터페이스 선언

[public] interface 인터페이스명 {...}
  • public 접근 제한은 다른 패키지에서도 인터페이스를 사용할 수 있도록 해준다.
  • 클래스는 필드,생성자,메소드를 구성 멤버로 가진다.
  • 인터페이스는 상수, 메소드만을 구성 멤버로 가진다.
  • 인터페이스는 객체로 생성할 수 없기에 생성자가 없다.
  • java 8부터 디폴트 메소드와 정적 메소드도 선언이 가능하다.
interface 인터페이스 명{
	//상수
    타입 상수명 =;
    //추상 메소드
    타입 메소드명(매개변수, ...);
    //디폴트 메소드
    default 타입 메소드명(매개변수,...){...}
    //정적 메소드
    static 타입 메소드명(매개변수){...}
}

상수 필드(Constant Field)

인터페이스는 객체 사용 설명서이므로 런타임 시 데이터를 저장할 수 있는 필드를 선언할 수 없다. 그러나 상수 필드는 가능하다. 반드시 초기값을 대입해야 한다.

추상 메소드(Abstract Method)

추상 메소드는 객체가 가지고 있는 메소드를 설명한 것으로 호출 시 어떤 매개값이 필요하고 리턴 타입이 무엇인지만 알려준다. 실제 실행은 객체가 가진다.

디폴트 메소드(Default Method)

디폴트 메소드는 인터페이스에 선언되지만 사실은 객체(구현 객체)가 가지고 있는 인스턴스 메소드라고 생각해야한다.

자바 8에서 디폴트 메소드를 허용한 이유는 기존 인터페이스 확장을 위해서이다.

정적 메소드(Static Method)

정적 메소드도 자바 8부터 지원된다. 객체가 없이 인터페이스만으로 호출 가능하다.

8.2.2 상수 필드 선언

인터페이스는 데이터를 저장할 수 없기 때문에 데이터를 저장할 인스턴스 또는 정적 필드를 선언할 수 없다. 대신 상수 필드만 선언 할 수 있다.
상수는 public static final로 선언하는데, 인터페이스의 필드는 모두 public static final이기에 안붙여도 자동적으로 붙게 된다.

그러나 명시하는 것이 좋다고 생각한다.
코드 작업을 하다보면 분명 헷갈릴 것이고 보일 때 템플릿 처럼 보여야 인터페이스의 상수 필드임을 알 것이다.

public static final 타입 상수명 =;

상수명은 대문자로, 서로 다른 단어로 구성되어 있을 경우는 언더바(_)로 연결하는 것이 관례이다.

인터페이스 상수는 static {} 블록으로 초기화 할 수 없기 때문에
반드시 선언과 동시에 초기값을 지정해야 한다.

public interface RemoteControl{
    // static final을 안붙여도 인터페이스의 필드는 상수이다.
	public static final int MAX_VOLUME = 10;
    public int MIN_VOLUME = 0;
}

8.2.3 추상 메소드 선언

인터페이스를 통해 호출된 메소드는 최종적으로 객체에서 실행된다.
그러므로 인터페이스의 메소드에는 실행블록이 없다.

추상메소드는 아래 3가지만 기술되고, 중괄호를 붙이지 않는다.

  • 리턴타입
  • 메소드명
  • 매개변수

인터페이스에 선언된 추상 메소드는 모두 public abstract의 성격을 갖기에 생략하더라도 자동적으로 컴파일 과정에서 붙게 된다.

public interface RemoteControl{
    // 상수
	public static final int MAX_VOLUME = 10;
    public int MIN_VOLUME = 0;
    
    // 추상 메소드
    public abstract void turnOff();
    public void turnOn();
    void setVolume(int volume);
}

8.2.4 디폴트 메소드 선언

디폴트 메소드는 자바 8에서 추가된 인터페이스의 새로운 멤버다.
형태는 클래스의 인스턴스 메소드와 비슷하다
선언에는 default 키워드가 리턴 타입 앞에 붙는다.
public을 갖기에 생략해도 컴파일 과정에 붙게된다.

public interface RemoteControl{
    // 상수
	int MAX_VOLUME = 10;
    int MIN_VOLUME = 0;
    
    // 추상 메소드
    void turnOff();
    void turnOn();
    void setVolume(int volume);
    
    //디폴트 메소드
    default void setMute(boolean mute){
    	if(mute){
        	sout("무음처리");
        }
        else{
        	soutv("무음 해제");
        }
    }
}

8.2.5 정적 메소드 선언

클래스의 정적 메소드 선언과 완전 동일하다.
public 특성을 갖기에 생략하더라도 자동적으로 붙게 된다.

    // 상수
	int MAX_VOLUME = 10;
    int MIN_VOLUME = 0;
    
    // 추상 메소드
    void turnOff();
    void turnOn();
    void setVolume(int volume);
    
    //디폴트 메소드
    default void setMute(boolean mute){
    	if(mute){
        	sout("무음처리");
        }
        else{
        	soutv("무음 해제");
        }
    }
	static void changeBattery(){
    	sout("건전지를 교체합니다.");
	}
}    

8.3 인터페이스 구현

개발 코드가 인터페이스를 호출하면, 인터페이스는 객체의 메소드를 호출한다.
객체는 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야한다.
구현 객체를 생성하는 클래스를 구현 클래스라고 한다.

8.3.1 구현 클래스

구현 클래스는 보통의 클래스와 동일한데, 클래스 선언부에 implements 키워드를 추가하고 인터페이스명을 명시해야 한다.

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

그리고 인터페이스의 추상 메소드의 실체 메소드를 선언해야한다.

public class Television implements RemoteControl{
	//필드
    private int volume;
    
    //turnOn() 추상메소드의 실체 메소드
    public void turnOn(){
    	sout("티비를 켭니다");
    }
    //Keep implements...
}

구현 클래스에서 인터페이스의 추상 메소드들에 대한 실체 메소드를 작성할 때 주의할 점은 인터페이스의 모든 메소드는 기본적으로 public 접근 제한을 갖기 때문에 public보다 더 낮은 접근 제한으로 작성할 수 없다.

만약 인터페이스에 선언된 추상 메소드에 대응하는 실체 메소드를 구현 클래스가 작성하지 않으면 구현 클래스는 자동적으로 추상 클래스가 된다.
그러므로 abstract키워드를 추가해야 한다.

RemoteControl 인터페이스로 구현 객체인 Television과 Audio를 사용하려면 다음과 같이 인터페이스 타입 변수를 선언하고, 구현 객체를 대입해야 한다.

public class RemoteControlExample{
	public static void main(String[] args){
    	RemoteControl rc;
        rc = new Television();
        rc = new Audio();
    }
}

구현 객체를 인터페이스 변수에 대입해서 사용한다.

8.3.2 익명 구현 객체

구현 클래스를 만들어 사용하는 것이 일반적이고, 클래스 재사용성 때문에 편리하지만, 일회성 구현 객체를 만들기 위해 소스파일과 클래스를 선언하는 것은 비효율적이다.
이를 위해 익명 구현 객체가 존재한다.
자바 8에서 지원하는 람다식은 인터페이스의 익명 구현 객체를 만들기 때문에 익명 구현 객체의 코드 패턴을 잘 익혀두길 바란다.

❗ 하나의 실행문이므로 끝에는 세미콜론(;)을 붙여야 한다.

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

new연산 뒤에는 클래스 이름이 와야하는데, 이름이 없다.
즉, 인터페이스(){}는 클래스를 선언하라는 뜻이고, new연산자는 이렇게 선언된 클래스를 객체로 생성한다.
물론, 모든 추상 메소드들의 실체 메소드를 작성해야한다. (컴파일 에러 생긴다.)
추가적인 필드, 메소드를 선언할 수 있지만, 익명 객체 안에서만 사용가능하고 인터페이스 변수로 접근 할 수 없다.

어.. 이러면 굳이 실무에서 익명 구현 객체를 사용할 까? 싶기도 하다.

모든 객체는 클래스로부터 생성된다.
익명 구현 객체도 예외는 아니다.
자바 컴파일러에 의해 클래스 파일이 만들어진다.
(만들어지는 것은 클래스명$번호식이다.)

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

객체는 다음과 같이 다수의 인터페이스 타입으로 사용할 수 있다.

인터페이스A와 인터페이스B를 모두 호출할 수 있으려면, 객체는 이 두 인터페이스를 모두 구현해야 한다. 따라서 구현 클래스는 다음과 같이 작성되어야 한다.

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

구현 클래스는 모든 인터페이의 추상 메소드에 대해 실체 메소드를 작성해야 한다.
만약 없다면 추상 클래스로 선언해야 한다.

8.4 인터페이스 사용

  • 인터페이스로 구현 객체를 사용하려면, 인터페이스 변수에 구현 객체를 대입한다.
  • 인터페이스 변수는 참조 타입이기에, 구현 객체의 번지를 저장한다.
  • 개발 코드에서 인터페이스는 클래스의 필드, 생성자 또는 메소드의 매개 변수, 생성자 또는 메소드의 로컬 변수로 선언될 수 있다.
public class MyClass{
	//필드
	RemoteControl rc = new Television();
    
    //생성자
    MyClass(RemoteControl rc){
    	this.rc = rc;
	}
    
    //메소드 
    void methodA(){
    	RemoteControl rc = new Audio();
	}
    
    void methodB(RemoteControl rc){...}
    //...
    
    mc.methodB(new Audio());
    
}

8.4.1 추상 메소드 사용

구현 객체가 인터페이스 타입에 대입되면, 인터페이스에 선언된 추상 메소드를 개발 코드에서 선언 할 수 있게 된다.

RemoteControl rc = new Television();
rc.turnOn();
rc.turnOff();

8.4.2 디폴트 메소드 사용

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

디폴트 메소드)
인터페이스의 모든 구현 객체가 가지고 있는 기본 메소드

그러나 어떤 구현 객체는 디폴트 메소드의 내용이 맞지 않아 오버라이딩 할 수도 있다.

디폴트 메소드의 존재 이유가 뭘까? 검색해보았다.

당신이 자율 주행 자동차 회사 사장이다.
그리고 다른 회사들과 함께 자율 주행 자동차의 업계 표준 인터페이스를 만들었다.
모든 자율 주행 자동차는 업계의 표준(인터페이스)을 따른다고 하자.

자율 주행 자동차에 "비행" 기능을 인터페이스에 추가해야 한다.
만약 기존 인터페이스에 (추상 메소드로)추가한다면, "비행" 기능을 구현하지 않은 타 회사들은 구현 클래스들 다시 작성해야 한다.
정적(static) 멤버로 추가한다면, 프로그래머들은 필수/코어 기능이 아니라 그것을 유틸 메소드로 생각할 것이다.

즉, 디폴트 메소드는 인터페이스 라이브러리에 새로운 기능 추가를 가능하게 하고, 인터페이스들의 예전 버전과 이진-적합성(컴파일 인듯?)을 보장한다.

8.4.3 정적 메소드 사용

일반 클래스의 정적 메소드 사용과 같다.
객체가 없이도, 인터페이스로 직접 호출한다.

psvm(){
	RemoteControl.changeBattery();
}

8.5 타입 변환과 다형성

상속에서 타입 변환과 다형성에 대하여 살펴보았다.
인터페이스도 다형성을 구현하는 기술이 사용된다.
(오히려 인터페이스로 다형성을 구현하는 경우가 더 많다.)
인터페이스 타입에 어떤 구현 객체를 대입하느냐에 따라 실행 결과가 달라진다.

상속은 같은 종류의 하위 클래스를 만드는 기술이다.
인터페이스사용 방법이 동일한 클래스를 만드는 기술이다.
둘의 개념적 차이가 존재하지만, 둘 다 다형성을 구현하는 기술이다.

인터페이스의 다형성이란?
프로그램 소스는 변함이 없는데, 구현 객체를 교체함으로써 프로그램의 실행 결과가 다양해 진다.

유지보수에도 좋다.
A 클래스를 만들었는데 이상현상이 일어났다고 하자.
이상현상을 고친 B 클래스로 바꾸려면, A 클래스의 메소드가 사용된 곳을 찾아 B클래스의 메소드로 바꿔야 한다.
만약, A클래스와 B클래스의 메소드 선언부가 같다면?
메소드 호출 코드는 수정할 필요 없이 객체 생성 부분만 A클래스에서 B클래스로 바꾸기만 하면ㄷ ㅚㄴ다.
즉, 인터페이스를 작성하고 A, B 클래스는 구현 클래스로 작성하면 된다.

인터페이스는 메소드의 매개 변수로 많이 등장한다.
인터페이스 타입으로 매개 변수를 선언하면, 메소드 호출 시 매개값으로 여러 가지 종류의 구현 객체를 전달 할 수 있기 때문에, 실행 결과가 다양하게 나온다.
이것이 인터페이스의 매개변수의 다형성이다.

8.5.1 자동 타입 변환(Promotion)

구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환(Promotion)에 해당된다.
프로그램 실행 도중에 자동적으로 타입이 변환되는 것을 말한다.
인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었따면, 자식 객체 역시 인터페이스 타입으로 자동 타입 변환 시킬 수 있다.

8.5.2 필드의 다형성

같은 필드 타입으로 인터페이스를 선언하면, 필드 값으로 구현 객체를 대입할 수 있다. 자동 타입 변환 덕분이다.

public interface Attackable {
    public void attack();
}

public class Character {
    public Character() {}
    public void attack(Attackable weapon) {
        weapon.attack();
    }
}

@AllArgsConstructor
@Getter @Setter
public abstract class Weapon implements Attackable {
    private String name;
    private int damage;
}

public class Gun extends Weapon {
    public Gun(String name, int damage) {
        super(name, damage);
    }

    @Override
    public void attack() {
        System.out.println("BANG!");
    }
}
public class OneHandSword extends Weapon {
    public OneHandSword(String name, int damage) {
        super(name, damage);
    }

    @Override
    public void attack() {
        System.out.println("Swoosh!");
    }
}

public class main {
    public static void main(String[] args) {

        Character character = new Character();
        Attackable gun = new Gun("revolver", 10);
        Attackable oneHandSword = new OneHandSword("light saver", 5);
//      Weapon gun = new Gun("revolver", 10);
//      Weapon oneHandSword = new OneHandSword("light saver", 5);
//      Gun gun = new Gun("revolver", 10);
//      OneHandSword oneHandSword = new OneHandSword("light saver", 5);
        //총 사용
        character.attack(gun);

        //칼 사용
        character.attack(oneHandSword);

    }
}


인터페이스 - 필드의 다형성을 이용하였다.
여기서 IoC 컨테이너까지 이용한다면 DIP, OCP를 준수할 수 있다.

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

클래스를 배열로 관리했듯이, 인터페이스 배열로도 관리할 수 있다.

Tire[] tires = {
	new HankookTire(),
    new HankookTire(),
	new HankookTire(),
	new HankookTire()
};

for(Tire tire :tires){
	tire.roll();
}

8.5.4 매개변수의 다형성

위의 예제에서 확인 할 수 있다.

8.5.5 강제 타입 변환

구현 객체가 인터페이스 타입으로 자동 변환하면, 인터페이스 메소드만 사용 가능하다.
경우에 따라, 구현 클래스에 선언된 필드/메소드를 사용해야 할 경우도 있다.
이때 강제 타입 변환으로 다시 구현 클래스타입으로 변환 후, 구현 클래스의 필드/메소드를 사용할 수 있다.

Vehicle vehicle = new Bus();

vehicle.run();

Bus bus = (Bus) vehicle 	//강제 타입 변환

bus.run();					//인터페이스 메소드
bus.checkFare();			//버스 메소드

8.5.6 객체 타입 확인

강제 타입 변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능하다.
그러나 어떤 구현 객체가 변환되어 있는지 모르고 변환할 경우 ClassCastExcepton이 발생할 수도 있다.
아래와 같이 instanceof를 사용할 수 있다.

public void dirve(Vehicle vehicle){
  if(vehicle instanceof Bus){
		Bus bus = (Bus) vehicle;
  }
}

8.6 인터페이스 상속

  • 인터페이스는 인터페이스를 상속할 수 있다.
  • 인터페이스는 다중 상속이 가능하다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스1 {...}

하위 인터페이스를 구현하는 객체는 하위 메소드 뿐 아니라 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야 한다.

하위 인터페이스 타입은 상, 하위 인터페이스 메소드 사용이 가능하다.
그러나 상위 인터페이스는 상위 메소드만 사용 가능하다.

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

디폴트 메소드는 인스턴스 메소드이기에 구현 객체가 있어야 사용 가능하다.
선언은 인터페이스에서, 사용은 구현 객체를 통한다는게 이상해보인다.
그러나 기능 확장(추가)에 있어서 필요하다.

8.7.1 디폴트 메소드의 필요성

8.4.2의 인용을 참조하자.

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

부모 인터페이스에 디폴트 메소드가 정의 되어 있을 경우, 자식 인터페이스에서 디폴트 메소드를 활용하는 방법은 세가지다.

  • 디폴트 메소드를 단순히 상속만 받는다.
  • 디폴트 메소드를 재정의(Override)해서 실행 내용을 변경한다.
  • 디폴트 메소드를 추상 메소드로 재선언한다.
profile
컨셉입니다~

0개의 댓글