참고자료
추상적인 표현을 사용해 클래스의 행동을 정의해 놓는 녀석이다. 클래스의 청사진이라고 할 수 있다. 인터페이스는 abstract methods와 variables를 가지고 있을 수 있다. 그러나 메소드의 바디는 가질 수 없다.
인터페이스는 일종의 계약이다. 여러 개발자들이 작업을 수행할 때, 다른 개발자의 코드가 완성될 때까지 기다릴 수 없다. 또한, 다른 개발자가 어떻게 개발했는지 모르더라도 자신이 개발하는 것에 지장이 없어야 한다. 이를 위해 인터페이스를 사용한다.
인터페이스에 정의된 모든 변수들은 final, public and static 하다. 코드에 명시하지 않더라도, 컴파일러가 자동으로 위와 같이 만든다.
→ 인터페이스의 변수는 static 이므로, 동일한 인터페이스를 구현한 클래스들은 해당 변수를 공유하는가? 그렇다. 하지만 문제는 없다. 왜냐하면 final이기 때문에 수정이 불가능하기 때문이다.
추상화를 구현해내기 위해
자바에서는 클래스의 다중 상속을 지원하지 않기 때문에, 인터페이스를 통해 다중 상속이 가능하다.
느슨한 결합을 구현하기 위해
그렇다면 추상클래스가 있는데 왜 인터페이스를 사용할까?
추상 클래스는 non-final 변수를 가지고 있을 수 있기 때문이다.
→ 추상 클래스는 다중 상속이 불가능 한가? oo
→ 추가적으로, 인터페이스는 모든 메소드가 추상 메소드인 반해, 추상 클래스는 일부 메소드만 추상 메소드일 수 있다.
인터페이스를 정의하기 위해서 interface
키워드를 사용한다.
interface <interface_name>{
// declare constant fields
// declare methods that abstract
// by defalut
}
컴파일러는 자동으로 public, static, final 키워드를 부여한다. 또한, 메소드에는 public과 abstract 키워드를 부여한다.
// class version 52.0 (52)
// access flags 0x601
public abstract interface compiler/test/Printable {
// compiled from: Printable.java
// access flags 0x19
public final static I MIN = 5
// access flags 0x401
public abstract print()V
}
인터페이스의 래퍼런스는 인터페이스를 구현한 객체를 받을 수 있다. 아래의 경우 Animal 인터페이스를 구현한 Cat, Cow 클래스가 있다. Animal 인터페이스를 사용하여 각 구현체에서 구현한 메소드를 호출하는 것을 확인할 수 있다.
다만, Animal 래퍼런스에서는 Animal 인터페이스에서 정의한 abstract method만 사용할 수 있고, 구현체에서 추가로 정의한 메소드는 접근할 수 없다. 즉, runtime에 실행될 메소드가 결정되는 동적 바인딩이 이뤄지고 있다.
// Animal.java
public interface Animal {
String CODE = "ANIMAL";
void eat();
}
// Cat.java
public class Cat implements Animal{
@Override
public void eat() {
System.out.println("Eat fish!");
}
public void talk(){
System.out.println("I'm Cat!");
}
}
// Cow.java
public class Cow implements Animal{
@Override
public void eat() {
System.out.println("Eat grass!");
}
public void talk(){
System.out.println("I'm cow!");
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Animal animal1, animal2;
animal1 = new Cat();
animal2 = new Cow();
animal1.eat();
animal2.eat();
// animal1.talk();
// animal2.talk();
}
}
출력
Eat fish!
Eat grass!
extends
키워드를 사용한다.// ParentInterface1.java
public interface ParentInterface1 {
void parentInterfaceMethod();
}
// TestInterface.java
public interface TestInterface extends ParentInterface1{
void childInterfaceMethod();
}
// TestImplement.java
public class TestImplement implements TestInterface {
@Override
public void parentInterfaceMethod() {
System.out.println("Parent Interface Method");
}
@Override
public void childInterfaceMethod() {
System.out.println("Child Interface Method");
}
}
인터페이스를 구현한 TestImplement
클래스는 자식 인터페이스와 부모 인터페이스의 추상 메소드를 모두 오버라이딩해야 한다.
// main.java
public class Main {
public static void main(String[] args) {
TestInterface testInterface = new TestImplement();
testInterface.childInterfaceMethod();
testInterface.parentInterfaceMethod();
ParentInterface1 parentInterface1 = new TestImplement();
parentInterface1.parentInterfaceMethod();
// parentInterface1.childInterfaceMethod();
}
}
위 main 메소드에서는 자식 인터페이스 래퍼런스와 부모 인터페이스 래퍼런스에서 구현를 받아 메소드를 호출하고 있다.
자식 래퍼런스는 부모의 메소드를 호출할 수 있는 반면에, 부모 래퍼런스는 자식 래퍼런스의 메소드를 호출할 수 없다.
인터페이스가 인터페이스를 상속하는 경우는 다음과 같다.
interface TestInterface{
void print();
}
public class TestImplement implements TestInterface{
@Override
void print(){
System.out.println("print!")
}
}
시간이 흘러 위에 TestInterface
에 메소드를 추가하고 싶어졌다고 하자. 만약 위와 같이 하나의 클래스에서만 해당 인터페이스를 구현했다면 큰 문제가 없다.
하지만, 여러 클래스에서 해당 인터페이스를 구현했을 경우, 그리고 그 클래스들을 수정할 수 없는 경우, 추가된 메소드를 구현체에서 추가 구현해야되는 문제가 발생한다. 이를 해결하기 위해 인터페이스 상속을 사용한다.
interface TestInterface{
void print();
}
public class TestImplement implements TestInterface{
@Override
void print(){
System.out.println("print!");
}
}
interface ChildInterface extends TestInterface{
void move();
}
public class TestImplement2 implements ChildInterface{
@Override
void print(){
System.out.println("print!");
}
@Override
void move(){
System.out.println("move!");
}
}
위와 같이 인터페이스를 상속하므로서, 기존 인터페이스를 구현했던 구현체에 영향을 주지 않으면서, 인터페이스를 확장할 수 있게 된다.
기본 메소드 또한 위와 같이 인터페이스에 메소드를 추가할 때 유용하게 사용된다.
interface TestInterface{
void print();
default void move(){
System.out.println("move!");
}
}
public class TestImplement implements TestInterface{
@Override
void print(){
System.out.println("print!")
}
}
default
로 지정하므로서, 구현체는 해당 메소드를 구현할 필요없이 사용할 수 있다. 마치 abstract 클래스에서의 abstract가 아닌 기본 메소드와 동일한 효과를 준다.
default 메소드를 정의한 인터페이스를 상속받은 인터페이스는 해당 default 메소드를 abstract 메소드로 재정의할 수 있다. 유사하게, 부모 인터페이스의 abstract 메소드를 default 메소드로 재정의할 수도 있다.
static
키워드를 사용하여 static 메소드를 인터페이스 내에서 정의할 수 있다. 인터페이스를 구현하는 구현체에서 공통적으로 사용되는 유틸리티를 static 메소드로 정의할 수 있다.
interface TestInterface{
static void hello(){
System.out.println("hello!");
}
}
자바9부터
를 지원한다.
private method는 다음과 같은 규칙을 따라야 한다.