인터페이스는 일종의 추상클래스와 비슷한 개념으로, 추상 메서드만을 가질 수 있는 구조다. 하지만 추상 클래스보다 추상화 정도가 높다.
즉, 인터페이스는 일반 메서드나 멤버 변수를 가질 수 없으며, 오직 추상 메서드(자바 8 이후부터는 예외적으로 default, static, private 메서드 포함)를 가질 수 있다.
public abstract이다.public static final이다.interface 인터페이스 {
public static final 타입 상수이름 = 값;
public abstract 메소드이름(매개변수);
}
public interface Animal {
void makeSound(); // 추상 메서드 (자동으로 public abstract가 붙음)
int MAX_AGE = 100;
}
인터페이스의 메서드는 무조건 추상 메서드이므로 abstract 키워드를 생략해도 되고, 모든 필드는 자동으로 public static final 속성을 가지므로 static final을 명시하지 않아도 상수로 선언된다.
인터페이스를 구현(implement) 하려면 implements 키워드를 사용한다.
public class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("멍멍!");
}
public void sleep(){
System.out.println("새근새근 잡니다.");
}
}
⚠️ 인터페이스를 구현하는 클래스는 반드시 모든 추상 메서드를 구현해야하기 때문에, 만약 구현하지 않았다면 해당 클래스도 abstract 클래스가 되어야한다.
abstract class AbstractDog implements Animal {
// makeSound()를 구현하지 않았으므로 abstract class여야 함
}
⚠️Dog 클래스에서 메소드를 구현할 때 접근제어자를 public으로 했다. 즉, 오버하리딩할 때는 부모의 메서드보다 넓은 범위의 접근 제어자를 지정해야한다.
인터페이스는 구현체의 객체를 담을 수 있는 레퍼런스 타입으로 사용할 수 있다.
아래처럼 인터페이스를 활용하면 구현체를 변경하더라도 코드 수정 없이 사용 가능하므로 유연성이 증가한다. (Animal myDog를 바꾸지 않고 = new Cat()만 바꾸면 되기 때문)
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog(); // 인터페이스 타입으로 객체 참조
myDog.makeSound(); // "멍멍!" 출력
}
}
myDog가 바라보고 있는 것은 Animal 인터페이스기 때문에 main클래스에서 dog.sleep()을 호출할 수 없다. 그래서 ((Dog)myDog).sleep()으로 캐스팅하여 사용해야한다. 반대로 Dog dog = new Animal(); 는 선언하지 못하고 컴파일 에러가 발생한다.
인터페이스도 다른 인터페이스를 상속할 수 있으며, 다중 상속이 가능하다.
public interface Pet {
void beFriendly();
}
public interface DogInterface extends Animal, Pet {
void guardHouse();
}
위와 같이 DogInterface는 Animal과 Pet 두 개의 인터페이스를 동시에 상속할 수 있다.
구현하는 클래스는 모든 인터페이스의 메서드를 구현해야한다.
public class GoldenRetriever implements DogInterface {
@Override
public void makeSound() {
System.out.println("왈왈!");
}
@Override
public void beFriendly() {
System.out.println("꼬리를 흔들어요!");
}
@Override
public void guardHouse() {
System.out.println("집을 지켜요!");
}
}
// 인터페이스 (메서드 구현 불가능)
public interface Minji {
void sayHello(); // 반드시 구현해야 함
void introduce(); // 반드시 구현해야 함
}
// 첫 번째 구현 클래스
public class SeoMinji implements Minji {
@Override
public void sayHello() {
System.out.println("안녕하세요! 저는 민지입니다.");
}
@Override
public void introduce() {
System.out.println("저는 백엔드 일짱입니다.");
}
}
// 두 번째 구현 클래스
public class ParkMinji implements Minji {
@Override
public void sayHello() {
System.out.println("안녕하세요! 저는 민지입니다."); //⚠️중복 코드 발생!
}
@Override
public void introduce() {
System.out.println("저는 백엔드 일짱입니다."); //⚠️중복 코드 발생!
}
}
🚨 문제점
SeoMinji와 ParkMinji 클래스에서 같은 내용의 sayHello()와 introduce()메서드를 각각 구현해야 한다. 즉, 같은 기능을 하는 코드가 여러 번 반복되고 있다.
-> 중복 문제
default라는 키워드를 명시해야함❓왜 사용할까❓
바로 "하위 호환성"때문이다. 예를 들어 설명하자면, 여러분들이 만약 오픈 소스코드를 만들었다고 가정하자. 그 오픈소스가 엄청 유명해져서 전 세계 사람들이 다 사용하고 있는데, 인터페이스에 새로운 메소드를 만들어야 하는 상황이 발생했다. 자칫 잘못하면 내가 만든 오픈소스를 사용한 사람들은 전부 오류가 발생하고 수정을 해야 하는 일이 발생할 수도 있다. 이럴 때 사용하는 것이 바로 default 메소드다.
기존에 존재하던 인터페이스를 이용하여서 구현된 클래스를 만들고 사용하고 있는데,
인터페이스를 보완하는 과정에서 추가적으로 구현해야 할, 혹은 필수적으로 존재해야 할 메소드가 있다면, 이미 이 인터페이스를 구현한 클래스와의 호환성이 떨어지게 된다. 이러한 경우 default 메소드를 추가하게 된다면 하위 호환성은 유지되고 인터페이스의 보완을 진행 할 수 있다.
// 인터페이스 (Default Method 사용)
public interface Minji {
//인터페이스에서 기본 동작 제공 -> 구현 클래스에서 굳이 구현할 필요 없음
default void sayHello() {
System.out.println("안녕하세요! 저는 민지입니다.");
}
default void introduce() {
System.out.println("저는 백엔드 일짱입니다.");
}
}
// 첫 번째 구현 클래스
public class SeoMinji implements Minji {
//아무것도 오버라이딩하지 않아도 기본 메서드를 그대로 사용 가능!
}
// 두 번째 구현 클래스
public class ParkMinji implements Minji {
//필요하면 메서드만 오버라이딩 가능
@Override
public void introduce() {
System.out.println("저는 인프런을 공부하는 민지입니다!");
}
}
// 실행 코드
public class Main {
public static void main(String[] args) {
SeoMinji minji1 = new SeoMinji();
ParkMinji minji2 = new ParkMinji();
minji1.sayHello(); //"안녕하세요! 저는 민지입니다."
minji1.introduce(); //"저는 백엔드 일짱입니다."
minji2.sayHello(); //"안녕하세요! 저는 민지입니다."
minji2.introduce(); //"저는 인프런을 공부하는 민지입니다!"
}
}
🔨개선된 점
introduce() 메서드는 인터페이스에서 기본 구현을 제공하므로, 구현 클래스에서 직접 구현할 필요 없음🚨 여전히 문제점
System.out.println("좋아하는 것은 여행입니다 !");과 System.out.println("저는 백엔드 일짱입니다.");를 모두 포함하는 detailedIntroduce()라는 새로운 메서드를 추가해야 한다면? introduce()와 동일한 코드 System.out.println("저는 백엔드 일짱입니다.");가 중복됨!introduce()와 detailedIntroduce() 둘 다 수정해야하고 //인터페이스 (Java 9부터 지원하는 private method)
public interface Minji {
void sayHello(); //여전히 직접 구현해야 함
default void introduce() {
printBasicInfo(); // private method 호출 (중복 제거)
}
default void detailedIntroduce() {
printBasicInfo(); // private method 호출 (중복 제거)
System.out.println("좋아하는 것은 여행입니다 !");
}
private void printBasicInfo() { // 인터페이스 내부에서만 사용 가능 (캡슐화)
System.out.println("저는 백엔드 일짱입니다.");
}
}
🔨개선된 점
printBasicInfo()라는 private method를 만들어 중복되는 부분을 한 곳에서 관리introduce()와 detailedIntroduce()에서 중복 없이 같은 기능을 재사용할 수 있음printBasicInfo()만 수정하면 모든 메서드에 자동 반영됨private method이므로 인터페이스 내부에서만 사용할 수 있고, 구현 클래스에서는 접근 불가Java 8부터 인터페이스에서도 static 메서드를 선언할 수 있다. 인스턴스 생성과 상관 없이 인터페이스 타입으로 호출하는 메소드이다. 이 메소드는 인터페이스 이름을 통해 직접 호출할 수 있다.
static 예약어를 사용하고 접근제어자는 항상 public이며 생략할 수 있다.public interface Animal {
static void info() {
System.out.println("동물 인터페이스입니다.");
}
}
public class Main {
public static void main(String[] args) {
Animal.info(); // "동물 인터페이스입니다."
}
}
위 코드처럼 static 메서드는 구현체를 만들지 않아도 interface이름.메소드로 호출할 수 있다.
Java 9부터 인터페이스 내에서 private 메서드를 정의할 수 있도록 했다.
이는 default 메서드와 static 메서드 내에서만 사용할 수 있는 헬퍼(보조) 메서드 역할을 한다.
private 메서드는 인터페이스 내부에서만 호출할 수 있으며, 외부에서는 호출할 수 없다.
👉 자바9 이전
interface SeoMinji {
void sayHello(); // 직접 구현해야 함
default void introduce() {
printBasicInfo(); // private method 호출 (중복 제거)
}
default void detailedIntroduce() {
printBasicInfo(); // private method 호출 (중복 제거)
System.out.println("좋아하는 것은 여행입니다 !");
}
private void printBasicInfo() { // 인터페이스 내부에서만 사용 가능 (캡슐화)
System.out.println("저는 백엔드 일짱입니다.");
}
// 자바8 이전
public static void weight() {
System.out.println("제 몸무게는 xx입니다 ! 비밀입니다 비밀!!!");
}
}
class 민지님베프 {
public void 민지정보출력() {
SeoMinji.weight(); // 민지님 : ?????????????????????????너가왜????????
}
}
👉 자바9 이후
private 키워드 사용이 가능해지면서 인터페이스 내에서만 사용하는 메서드의 경우 private static을 통해 외부 접근을 막으면서 캡슐화가 가능해졌다!
interface SeoMinji {
// 기존코드 생략...
// 자바9 이후
private static void weight() {
System.out.println("제 몸무게는 xx입니다 ! 비밀입니다 비밀!!!");
}
}
class 민지님베프 {
public void 민지정보출력() {
SeoMinji.weight(); // 컴파일에러 발생 ! 민지님 : 휴..
}
}
(추가) 인스턴스 즉 객체에 의존하는 메서드면 static을 사용하지 않는게 좋고
의존하지 않는다면 private static으로 만들어서 재활용 하는게 좋다.
왼쪽의 그림을 보면 (강한 결합)
오른쪽 그림을 보면 (느슨한 결합)