인터페이스
■ 인터페이스란?
객체의 사용 방법을 정의
코드와 객체의 중간 다리 역할이며, 코드에서 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출하는 구조
사용자는 객체의 내부 구조를 몰라도 인터페이스의 메소드만 알면 사용 가능
■ 인터페이스를 왜 사용할까?
- 설계시 미리 선언해두면 개발할 때 기능 구현에 초점을 맞출 수 있다.
- 프로젝트 시 개개인의 능력과 관계없이 메소드의 이름과 매개변수의 선언 등의 규칙이 유지된다.
- 공통 인터페이스와 추상 클래스를 선언해 놓으면, 선언과 구현을 구분할 수 있다.
■ 인터페이스와 추상 클래스의 차이?
가장 큰 차이점은
인터페이스는 모든 메소드가 추상 메소드이고, 추상 클래스는 클래스 내 추상 메소드가 하나 이상 포함되는 경우이다.
조금 더 자세한 차이는 아래의 표를 참고
|
인터페이스 |
추상 클래스 |
선언 |
interface |
abstract class |
추상 메소드 포함 |
가능(필수) |
가능 |
구현된 메소드 포함 |
불가능 |
가능 |
static 메소드 선언 |
불가능 |
가능 |
final 메소드 선언 |
불가능 |
가능 |
상속(extends) |
불가능 |
가능 |
구현(implements) |
가능 |
불가능 |
인터페이스 정의하는 방법
- 인터페이스의 모든 메소드는 추상 메소드(public abstract)로 작성
- 모든 필드는 public static final
> 만약, 제어자를 생략한다면, 컴파일 시 컴파일러가 자동으로 추가
- 인터페이스는 공용 API를 정의하기 때문에 암묵적으로 public
- 인스턴스화 할 수 없으므로 생성자가 필요없음
- 인터페이스는 중첩 가능
// 인터페이스 정의 예시
public interface InterfaceTest {
int a = 1; // 컴파일러가 자동으로 public static final을 붙임
public static final int b = 1;
void testMethod(); // 컴파일러가 자동으로 public abstract를 붙임
public abstract void testMethod();
인터페이스 구현하는 방법
implements 키워드를 사용
반드시! 인터페이스에 명시되어 있는 모든 추상 메소드를 구현해야 함
// 구현 예시
public interface Animal {
public abstract void cry();
}
public class Dog implements Animal {
@Override
public void cry() {
Sysyem.out.println("멍멍");
}
}
public class Cat implements Animal {
@Override
public void cry() {
Sysyem.out.println("야옹");
}
}
위의 인터페이스 및 클래스를 이용해보자
// 실행시켜보자
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.cry();
cat.cry();
}
}
// 결과
멍멍
야옹
인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
상속을 학습할 때 자식 클래스의 인스턴스를 부모 클래스의 참조 변수로 사용할 수 있었다.
인터페이스도 이와 동일하게 인터페이스 타입의 참조변수로 클래스의 인스턴스를 참조할 수 있다.
위의 예시를 그대로 가져와서 이용해보자
// 구현 예시
public interface Animal {
public abstract void cry();
}
public class Dog implements Animal {
@Override
public void cry() {
Sysyem.out.println("멍멍");
}
// 추가 메소드
public void name() {
System.out.println("복돌이");
}
}
public class Cat implements Animal {
@Override
public void cry() {
Sysyem.out.println("야옹");
}
// 추가 메소드
public void name() {
System.out.println("나비");
}
위의 인터페이스 및 클래스를 이용해보자
// 실행시켜보자
public class Main {
public static void main(String[] args) {
// 인터페이스 변수를 선언하고 클래스 객체 생성
Animal dog = new Dog();
Animal cat = new Cat();
dog.cry();
cat.cry();
dog.name(); // 에러 발생, dog이 바라보는 것은 Animal 인터페이스이기 때문
cat.name(); // 에러 발생, cat이 바라보는 것은 Animal 인터페이스이기 때문
// 위의 문제를 캐스팅으로 해결
((Dog)dog).name();
((Cat)cat).name();
}
}
// 결과
멍멍
야옹
복돌이
나비
인터페이스 상속
앞전에 상속을 할 때, 상속은 다중 상속이 불가능 하다고 했었다.
그러나, 인터페이스는 구현을 할 때 다중으로 가능하다.
추가적으로 인터페이스는 인터페이스만 상속을 받을 수 있다.
// 예시
public interface Animal {
public abstract void cry();
}
public interface Human {
public abstract void name();
}
// 인터페이스에 인터페이스를 상속
public interface life extends Animal, Human {
}
// 구현
public class Test implements life {
@Override
public void cry() {
Sysyem.out.println("안녕하세요");
}
@Override
public void name() {
Sysyem.out.println("재희");
}
}
위의 인터페이스 및 클래스를 이용해보자
// 실행시켜보자
public class Main {
public static void main(String[] args) {
// 인터페이스 변수를 선언하고 클래스 객체 생성
Test t = new Test();
t.cry();
t.name()
}
}
// 결과
안녕하세요
재희
물론, 위와 같이 할 수도 있지만 아래의 방법으로 사용 가능하다.
// 예시
public interface Animal {
public abstract void cry();
}
public interface Human {
public abstract void name();
}
// 구현
public class Test implements Animal, Human {
@Override
public void cry() {
Sysyem.out.println("안녕하세요");
}
@Override
public void name() {
Sysyem.out.println("재희");
}
}
인터페이스의 기본 메소드 (Default Method), 자바 8
기존에는 인터페이스에서 메소드를 구현할 수 없었지만, Java 8에서부터 인터페이스에 대한 정의가 몇 가지 변경이 되었다.
그것은 바로, default 키워드로 선언하면 메소드를 구현할 수 있다는 것이다.
또한, 이를 구현하는 클래스는 default 메소드를 오버라이딩 할 수 있다.
■ 특징
- default 메소드는 암시적으로 public 이므로 public을 지정할 필요가 없다.
- 추상 클래스의 입지가 줄어듬 (사실 인터페이스에서 구현까지 할 수 있으면 추상 클래스와 동일한 기능이지 않은가...)
- static 메소드가 아니기 때문에 반드시 구현 클래스를 생성 후 사용이 가능
// default 메소드 예시
public interface Test {
public int division(int a, int b);
public int minus(int a, int b);
// default 메소드을 통한 인터페이스 내부에서 구현
default int plus(int a, int b) {
return a + b;
}
}
인터페이스의 static 메소드, 자바 8
static 메소드는 위의 default 메소드와 같이 인터페이스에서 구현이 가능한 메소드이다.
그러나!!
static 메소드는 호출할 때, 반드시 인터페이스 명으로 호출해야 하고 재정의가 불가능하다.
위의 예제를 끌어와보자
// 예시
public interface Test {
public int division(int a, int b);
public int minus(int a, int b);
// default 메소드을 통한 인터페이스 내부에서 구현
default int plus(int a, int b) {
return a + b;
}
// static 메소드를 통한 인터페이스 내부에서 구현
public static int multiple(int a, int b) {
return a * b;
}
}
public class ImpleTest implements Test {
// default 메소드는 재정의 가능
@Override
pubilc int plus(int a, int b) {
return a + b;
}
// static 메소드는 재정의 불가능
}
위의 인터페이스 및 클래스를 이용해보자
// 실행시켜보자
public class Main {
public static void main(String[] args) {
// 인터페이스 변수를 선언하고 클래스 객체 생성
Test t = new ImpleTest();
int a = t.plus(1, 3); // 정상출력
// int b = t.multiple(2, 4); << 에러
int b = Test.multiple(2, 4)
System.out.println(a, b);
}
}
// 결과
4 8
인터페이스의 private 메소드, 자바 9
자바 8에서 default와 static 메소드가 추가되면서 새로운 기능을 제공하였다.
그러나, 내부 메소드일 경우에도 외부로 공개되는 public 으로 선언을 했어야 했는데, 자바 9에서 private 메소드라는 새로운 기능을 제공하여 이러한 문제를 해결하였다.
■ 특징
- private 메소드는 추상화가 불가능
- 인터페이스 내에서만 사용 가능
- static 메소드도 private이 가능
마지막이기도 하고, 전체적으로 정리해보자
// private 메소드 예시
public interface Test {
// 일반적인 추상 메소드
public abstract void method1();
// default 메소드
public default void method2() {
method4(); // private 메소드는 인터페이스 내부에서만 사용
method5(); // private 메소드는 인터페이스 내부에서만 사용
System.out.println("method2");
}
// static 메소드
public static void method3() {
method5(); // private 메소드는 인터페이스 내부에서만 사용
System.out.println("method3");
}
// private 메소드
private void method4() {
System.out.println("method4");
}
// private + static 메소드
private static void method5() {
System.out.println("method5");
}
}
public class Sample implements Test {
@Override
public void method1() {
System.out.println("method1");
}
}
위의 인터페이스 및 클래스를 이용해보자
// 실행시켜보자
public class Main {
public static void main(String[] args) {
// 인터페이스 변수를 선언하고 클래스 객체 생성
Test t = new Sample();
t.method1();
t.method2();
t.method3();
}
}
// 결과
method1
method4
method5
method2
method5
method3