중간고사 대비 java 8단원 정리

목차

혼자 공부하는 자바 8단원 인터페이스 정리

1. 인터페이스

인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 한다. 개발 코드가 인터페이스의 메소드를 호출하면 인터페이스가 객체의 메소드를 호출하는 것이다. 즉 개발 코드에선 인터페이스 메소드만 가지고 있으면 된다.

인터페이스를 쓰는 이유는 개발 코드를 수정하지 않고 사용하는 객체를 변경하게 할 수 있도록 하게 위해서이다. 인터페이스를 사용하면 개발 코드는 인터페이스에 정의된 메소드만 호출하면 되기 때문에 객체를 변경해도 개발 코드를 수정할 필요가 없다.

1.1 인터페이스 선언

인터페이스는 클래스와 물리적 형태가 동일하지만 class 대신 interface 키워드를 사용한다.

public interface [인터페이스명]{}
public interface [인터페이스명]{}
public interface [인터페이스명]{}
public interface [인터페이스명]{}

인터페이스는 클래스처럼 객체를 생성하는 것이 아니다. 객체의 사용 규칙을 정의하는 것이다. 따라서 인터페이스는 객체를 생성하지 않기 때문에 생성자가 없다. 그리고 인터페이스는 객체를 생성하지 않기 때문에 필드와 메소드의 구현부를 가질 수 없다. 또 인터페이스 객체 같은 건 없기 때문에 this와 super를 사용할 수 없다. 상수 필드와 추상 메소드만을 구성 요소로 가진다.

  • 상수 필드 선언하기

인터페이스는 인스턴스나 정적 필드를 선언할 수 없다. 상수 필드만 선언할 수 있는데 이런 상수는 앞서 보았듯 public static final을 붙여야 한다. 만약 이를 생략하더라도 컴파일 과정에서 자동으로 붙게 된다. 또한 상수는 선언과 동시에 초기화해야 한다.

  • 추상 메소드 선언하기

인터페이스를 통해 호출된 메소드는 결국 객체에서 실행된다. 따라서 인터페이스 메소드는 실행 블록 없이 추상 메소드로 선언된다. 이렇게 인터페이스의 추상 메소드는 public abstract로 선언되어야 한다. 만약 이를 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.

인터페이스는 개발 코드의 메소드 호출을 객체의 메소드 호출로 연결해주는 역할만 하면 되기 때문이다.

1.2 인터페이스 구현

개발 코드가 인터페이스 메소드를 호출시 인터페이스는 객체 메소드를 호출하게 된다. 따라서 인터페이스를 구현하는 객체는 인터페이스의 모든 메소드를 구현해야 한다. 이를 위해 인터페이스를 구현하는 클래스는 implements 키워드를 사용한다. 이렇게 인터페이스의 내용을 구현하는 클래스를 구현 클래스라고 한다.

public class MyClass implements MyInterface {
  //MyInterface에 정의된 추상 메소드의 실체 메소드를 구현해야 한다.
    @Override
    public void method1() {
        System.out.println("MyClass-method1() 실행");
    }

    @Override
    public void method2() {
        System.out.println("MyClass-method2() 실행");
    }
}
public class MyClass implements MyInterface {
  //MyInterface에 정의된 추상 메소드의 실체 메소드를 구현해야 한다.
    @Override
    public void method1() {
        System.out.println("MyClass-method1() 실행");
    }

    @Override
    public void method2() {
        System.out.println("MyClass-method2() 실행");
    }
}
public class MyClass implements MyInterface {
  //MyInterface에 정의된 추상 메소드의 실체 메소드를 구현해야 한다.
    @Override
    public void method1() {
        System.out.println("MyClass-method1() 실행");
    }

    @Override
    public void method2() {
        System.out.println("MyClass-method2() 실행");
    }
}
public class MyClass implements MyInterface {
  //MyInterface에 정의된 추상 메소드의 실체 메소드를 구현해야 한다.
    @Override
    public void method1() {
        System.out.println("MyClass-method1() 실행");
    }

    @Override
    public void method2() {
        System.out.println("MyClass-method2() 실행");
    }
}

이때 인터페이스로 구현 객체를 사용하려면 인터페이스 변수를 선언하고 구현 객체를 대입해야 한다. 그러면 인터페이스 변수는 구현 객체의 번지를 저장하게 된다.

MyInterface myInterface = new MyClass();
MyInterface myInterface = new MyClass();
MyInterface myInterface = new MyClass();
MyInterface myInterface = new MyClass();

1.3 다중 인터페이스 구현

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

public class MyClass implements interfaceA, interfaceB{}
public class MyClass implements interfaceA, interfaceB{}
public class MyClass implements interfaceA, interfaceB{}
public class MyClass implements interfaceA, interfaceB{}

위와 같은 경우 MyClass는 A, B 인터페이스의 모든 메소드를 구현해야 한다. 모든 인터페이스의 추상 메소드에 대해 실체 메소드를 작성해야 하는 것이다.

그렇게 되면 interfaceA, interfaceB 변수 둘 모드에 MyClass 객체를 대입할 수 있다.

1.4 인터페이스의 사용

클래스를 선언할 때 인터페이스는 필드, 생성자, 메소드 매개변수, 생성자나 메소드의 로컬변수로 선언될 수 있다. 이렇게 인터페이스 타입으로 선언된 부분들에는 인터페이스의 구현 객체가 들어갈 수 있다. 어떤 구현 객체가 들어가느냐에 따라서 인터페이스에서 호출한 메소드가 어떤 클래스의 메소드로 연결되는지가 달라진다.

2. 타입 변환과 다형성

프로그램을 개발시 인터페이스를 통해 메소드를 호출하게 했다면 구현 객체만 변경하면 호출되는 메소드가 달라지게 할 수 있다. 따라서 프로그램의 실행 결과를 쉽게 다양하게 조절할 수 있다. 이것이 바로 다형성이다. 또한 구현 객체는 인터페이스 타입으로 자동 타입 변환될 수 있다. 하지만 인터페이스 타입 변수에서 메소드를 호출할 시 대입된 구현 객체 클래스의 메소드가 호출된다.

2.1 매개 변수 다형성

자동 타입 변환은 필드 값 대입보다는 메소드 호출시 매개변수를 통해서 많이 발생한다. 인터페이스를 통한 다형성의 경우, 매개 변수를 인터페이스 타입으로 선언하고 호출 시에는 구현 객체를 대입하는 것이다.

그러면 메소드를 호출할 때는 매개 변수에 인터페이스의 구현 객체를 넣어 줄 수 있다. 그 구현 객체가 어떤 클래스인지에 따라 메소드의 동작이 달라질 수 있다. 메소드 호출 시 어떤 구현 객체를 주느냐에 따라 메소드의 실행 결과가 달라지는 것이다.

2.2 인터페이스 강제 타입 변환

구현 객체를 인터페이스 타입으로 자동 타입 변환하면 인터페이스에 선언된 메소드만 호출할 수 있다. 만약 인터페이스에 3개의 메소드가 선언되어 있고 메소드 매개변수가 인터페이스 타입으로 선언되어 있다면, 그 메소드에 인터페이스의 어떤 구현 객체를 넘겨주는지에 상관없이(구현 객체에는 메소드가 5개 있더라도) 인터페이스에 선언된 3개의 메소드만 호출할 수 있다.

그러나 인터페이스 타입을 구현 객체 타입으로 강제 타입 변환하면 구현 객체에 선언된 메소드도 호출할 수 있다. 이렇게 인터페이스 타입을 구현 객체 타입으로 강제 타입 변환하는 것을 인터페이스 타입의 다운 캐스팅이라고 한다.

이때 강제 타입 변환은 구현 객체가 인터페이스 타입으로 자동 타입 변환되어 있는 상태에서만 가능하다. 그러나 어떤 구현 객체가 변환되어 있는지 모르는 상태에서 강제 타입 변환을 하면 컴파일 에러가 발생한다. 이런 경우에는 instanceof 연산자를 사용하여 구현 객체가 인터페이스 타입으로 자동 타입 변환되어 있는지 확인한 후에 강제 타입 변환을 해야 한다. 상속에서와 마찬가지이다.

if(vehicle instanceof Bus) {
    Bus bus = (Bus) vehicle;
}
if(vehicle instanceof Bus) {
    Bus bus = (Bus) vehicle;
}
if(vehicle instanceof Bus) {
    Bus bus = (Bus) vehicle;
}
if(vehicle instanceof Bus) {
    Bus bus = (Bus) vehicle;
}

3. 인터페이스 상속

인터페이스도 다른 인터페이스를 상속할 수 있다. 클래스 상속과 마찬가지로 extends키워드를 사용한다. 그리고 클래스 상속과 달리 다중 상속이 허용된다.

이때 자식 인터페이스를 구현하는 클래스는 자식뿐 아니리 부모(상위)인터페이스의 모든 추상 메소드에 대한 구현 메소드를 가지고 있어야 한다. 그래서 구현 클래스에서 인스턴스 생성 후 하위/상위 인터페이스 타입으로 형변환도 가능하다.

주의할 점은 상위 인터페이스로 타입 변환될 시, 상위 인터페이스에 선언된 메소드만 사용 가능하다는 것이다. 하위 인터페이스에 선언된 메소드는 사용할 수 없다.