인터페이스?
클래스 혹은 프로그램이 제공하는 기능을 명시적으로 선언하는 역할
구현된 코드가 없고 추상 메서드와 상수로만 이루어져있다.
- 두 객체(A, B)를 연결해주는 connector 역할
- 인터페이스를 통해 A가 B를 활용할 수 있음
- 인터페이스는 A가 쓸 메서드 목록
- B에서 인터페이스의 메서드를 구현함
- 객체 B가 객체 C로 대체될 수 있음
- A는 인터페이스에 있는 메서드만 사용 가능
- A가 인터페이스의 메서드를 사용 → 실행은 B/C에서 일어남
// 인터페이스 선언은 interface 키워드를 사용
interface Calc {
// 인터페이스에서 선언한 변수는 컴파일 과정에서 상수로 변환
double PI = 3.14;
// 인터페이스에서 선언한 메서드는 컴파일 과정에서 public 추상 메서드로 변환됨
int add(int num1, int num2);
}
- 위와 같이 인터페이스의 선언은 interface 키워드를 사용한다.
- 인터페이스 내에서 변수를 선언하면 이는 컴파일 과정에서 public 정적 상수로 변환된다. (public + static + final)
- 상수 이름은 주로 대문자와 밑줄로 표현
- 인터페이스 내에서 메서드를 선언하면 이는 public(공개 범위) 추상 메서드로 변환된다.
- 메서드 선언 시에 앞에 public, abstract 키워드를 붙일 수 있기는 하지만 생략해도 동일한 기능을 한다.
// 클래스처럼 인터페이스도 접근제어자 public/default를 지닐 수 있음
public interface Calc {
}
public interface Calc {
static int ZERO = 0; // 가능
double PI = 3.14; // 가능
int add(int num1, int num2); // 가능
// 불가능(실제 구현을 포함할 수 없음)
int times(int num1, int num2) {
int result = num1 * num2;
return result;
}
}
- 인터페이스 내에서 클래스처럼 실제 구현을 바로 할 수 없음
- 선언된 모든 메서드는 abstract이자 public
인터페이스 내부에 올 수 있는 것
- public 상수
- public 추상 메서드
- public default 메서드
- public 정적 메서드(static)
- private 메서드
- private 정적 메서드
* 아래의 게시물 참고
[Java] interface 인터페이스 메서드 종류(default, static, private)
swlin23.tistory.com
메서드/상수에 접근 제어자를 쓰지 않으면 컴파일 과정에서 자동으로 public 취급
- A가 인터페이스의 추상 메서드를 부르면 인터페이스가 B가 구현한 메서드를 실행
- B는 인터페이스의 메서드를 override한 것임
즉, B에서 인터페이스를 구현하게 된다!
이 때, implements 키워드를 통해 해당 클래스가 인터페이스를 override할 수 있다.
// B가 interface1을 override
public class B implements interface1 {...}
인터페이스 구현 예시
public interface Calc {
int PI = 3.14;
int add(int num1, int num2);
int sub(int num1, int num2);
}
위와 같이 인터페이스가 존재할 때, 이를 클래스를 통해 구현할 수 있다.
public class Calculator implements Calc {
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int sub(int num1, int num2) {
return num1 - num2;
}
}
이 때, 만약 인터페이스에 존재하는 메서드를 하나라도 구현하지 않는다면 오류가 발생한다.
그리고 인터페이스의 메서드 구현시 무조건 public을 붙여주어야 한다.
또한 인터페이스를 구현할 때에는 인터페이스의 모든 메서드를 구현해야만 한다.
public class Calculator implements Calc {
@Override
public int add(int num1, int num2) {
return num1 + num2;
}
@Override
public int sub(int num1, int num2) {
return num1 - num2;
}
// 인터페이스에는 없는 메서드
public int new(int num) {
return num;
}
}
참고로, 인터페이스를 구현하는 클래스에서 인터페이스에는 존재하지 않는 새로운 메서드를 추가할 수 있다.
Calc c1;
Calc c2 = null;
Calc c3 = new Calculator();
c3.add(a, b)
// 인터페이스를 구현한 클래스를 사용할 수도
Calcultor c4 = new Calculator();
위와 같이 객체를 생성할 수 있다.
이 때 인터페이스는 변수의 type 처럼 사용된다.
new 키워드와 구현 클래스를 통해 인터페이스를 통한 객체를 생성한다.
인터페이스는 reference type이기 때문에 null 값이 들어갈 수 있다.
이와 같이 관계를 포현할 수 있다.
한 클래스가 여러 인터페이스를 구현하는 경우
인터페이스의 특이한 점은 구현 클래스가 여러 인터페이스를 함께 구현할 수 있다는 점에 있다.
public class ImplementClass implements interface1, interface2 {...}
이 때, 구현 클래스는 각 인터페이스에 있는 모든 abstract method를 구현해야 한다.
인터페이스 상속
인터페이스 간에도 상속이 가능하다.
다만 구현 코드를 통해 기능을 상속하는 것이 아닌 형 상속이다.
또한, 클래스와는 달리 한 인터페이스가 여러 인터페이스를 상속 받을 수 있다.
클래스와 마찬가지로 extends 키워드를 사용하여 상속받는다.
public interface ChildInterface extends Interface1, Interface2 {...}
이 때, 구현 클래스는 interface1, interface2, ChildInterface의 추상 메서드들을 모두 구현해야 한다.
class ImplementClass implements ChildInterface {...}
위와 같이 ImplementClass에서 자식 클래스를 구현하였을 때,
ChildInterface v1 = new ImplementClass();
Interface1 v2 = new ImplementClass();
Interface2 v3 = new ImplementClass();
// 구현 클래스 객체 생성
ImplementClass impl = new ImplementClass();
ChildInterface v4 = impl;
Interface1 v5 = impl;
Interface2 v6 = impl;
위 처럼 해당 클래스를 자식 인터페이스와 부모 인터페이스에 모두 할당할 수 있다.
(클래스의 자식 부모 관계를 생각하면 쉽다. 자식 클래스 객체를 부모 클래스 객체에 할당할 수 있듯이)
부모 인터페이스에 구현 객체를 할당하면, 부모 인터페이스에 존재하는 메서드만 사용할 수 있다.
자식 인터페이스에 구현 객체를 할당하면, 자식 인터페이스에 존재하는 메서드와 부모 인터페이스에 존재하는 메서드를 모두 사용할 수 있다.
인터페이스와 구현 객체 사이의 형 변환
위 그림과 같은 관계일 때, 인터페이스 객체와 구현 클래스(A, B) 사이의 형 변환이 가능하다.
A, B를 각각 상속받은 C, D 또한 인터페이스 객체로 형 변환이 가능하다.
Casting
인터페이스 유형의 객체를 클래스 유형의 객체로 변환
명시적 형변환과 유사하다.
InterfaceA a = new ImplClass();
인터페이스를 interfaceA, 구현 클래스를 ImplClass 라고 할 때 위와 같이 객체를 생성할 수 있다.
a는 현재 인터페이스 객체라고 생각할 수 있다.
이 때 a는 인터페이스에 존재하는 메서드만을 사용할 수 있다.
이 객체를 구현 클래스 객체로 만들고 싶다면?
ImplClass i = (ImplClass) a;
인터페이스 객체였던 a를 위와 같이 구현 클래스 ImplClass로 형변환 시킬 수 있다.
이 때 i는 인터페이스와 구현 클래스에 존재하는 메서드를 모두 사용할 수 있다.
Casting 예시
// Food 인터페이스
interface Food {
void eat();
}
// 구현 클래스 Cake
class Cake implements Food {
@Override
public void eat() {
System.out.println("Eating cake");
}
public void taste() {
System.out.println("Delicious");
}
}
// main
class Main {
public static void main(String[] args) {
Food food = new Cake();
food.eat(); // Eating cake 출력
food.taste(); // 인터페이스에 존재하지 않는 메서드기에 오류
Cake cake = (Cake) food; // casting
cake.eat(); // Eating cake 출력
cake.taste(); // Delicious 출력
}
}
Polymorphism 다형성
1. Field 다형성
// Food 인터페이스
interface Food {
void eat();
}
// 구현 클래스 Cake
class Cake implements Food {
@Override
public void eat() {
System.out.println("Eating cake");
}
}
// 구현 클래스 Noodle
class Noodle implements Food {
@Override
public void eat() {
System.out.println("Eating noodle");
}
}
위와 같이 Food 인터페이스와 이를 구현한 클래스 Cake, Noodle이 있을 때
// Person 클래스
class Person {
Food food = new Cake();
void eating(){
food.eat();
}
}
// Main 클래스
class Main {
public static void main(String[] args) {
Person person = new Person();
person.eating(); // Eating Cake 출력
person.food = new Noodle;
person.eating(); // Eating Noodle 출력
}
}
이 처럼 필드에 담긴 값에 따라 어떤 메서드를 실행할 지가 결정된다.
2. parameter 다형성
// Food 인터페이스
interface Food {
void eat();
}
// 구현 클래스 Cake
class Cake implements Food {
@Override
public void eat() {
System.out.println("Eating cake");
}
}
// 구현 클래스 Noodle
class Noodle implements Food {
@Override
public void eat() {
System.out.println("Eating noodle");
}
}
// Person 클래스
class Person {
void eating(Food food){
food.eat();
}
}
// Main 클래스
class Main {
public static void main(String[] args) {
Person person = new Person();
Cake cake = new Cake();
Noodle noodle = new Noodle();
person.eating(cake); // Eating Cake 출력
person.eating(noodle); // Eating Noodle 출력
}
}
파라미터에 담긴 값에 따라 메서드 실행이 달라진다.