인터페이스

파이 ఇ·2023년 6월 16일
1
post-thumbnail

💡 목표 : 자바의 인터페이스에 대해 학습해보자.

⚡️ 목차

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9

인터페이스란

자바에서 인터페이스는 클래스들이 필수로 구현해야 하는 추상 자료형이다. 쉽게 말하자면 객체의 사용 방법을 가이드라인 하는 것이라고 생각하면 이해가 쉽다. 자바에서 인터페이스는 추상 메서드와 상수로만 이루어져있다. 구현된 코드가 없기 때문에 당연히 인스턴스로는 사용이 불가능하다.

인터페이스 특징

  • 다중 상속 가능
    • 인터페이스는 껍데기만 존재하여 클래스 상속 시 발생했던 모호함이 없다. 고로 다중 상속이 가능하다.
  • 추상 메서드와 상수만 사용 가능
    • 인터페이스에는 구현 소스를 생성할 수 없다. 고로 상수와 추상 메서드만 가질 수 있다.
  • 생성자 사용 불가
    • 인터페이스는 객체가 아니므로 생성자를 사용할 수 없다.
  • 메서드 오버라이딩 필수
    • 자식 클래스는 부모 인터페이스의 추상 메서드를 모두 오버라이딩 해야 한다.

인터페이스 정의하는 방법

  • 필드
    • public static final 필드로 구성
    • public static final 생략 시 컴파일러가 자동으로 추가해준다.
  • 메서드
    • public abstract 메서드로 구성
    • public abstract 생략 시 컴파일러가 자동으로 추가해준다.
public interface 인터페이스명 {
	void setAnimalName(String name);
    
    public abstract void bark();
}
public class Dog implements Animal {
	private String name;
    
	@Override
	public void setAnimalName(String name) {
		this.name = name;
	}
    
    @Override
    public void bark() {
    	System.out.println("멍멍~");
	}
}
public class Cat implements Animal {
	private String name;
    
    @Override
    public void setAnimalName(String name) {
    	this.name = name;
	}
    
    @Override
    public void bark() {
    	System.out.println("야옹~");
	}
    
    public void move() {
    	System.out.println("사뿐사뿐");
	}
}
public class Main {
	public static void main(String[]args) {
    	Dog dog = new Dog();
        Cat cat = new Cat();
        
        dog.bark();
        cat.bark();
	}
}

Output
멍멍~
야옹~

인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

Animal 인터페이스 타입으로 Cat구현체를 사용한다면 Animal 인터페이스에 선언된 setAnimalName() 메서드만 사용할 수 있다.

public void Main {
	public static void main(String[]args) {
    	//래퍼런스로 사용하는 방법
        Animal animal = new Cat();
        animal.setAnimalName("먼지");
	}
}

하지만 Animal 인터페이스를 구현한 Cat 타입으로 Cat을 구현체를 사용한다면 오버라이딩 한 setAnimalName()뿐만 아니라 move()메서드까지 사용 가능하다.

public void Main {
	public static void main(String[]args) {
    	//래퍼런스로 사용하는 방법
        Animal animal = new Cat();
        String name = "먼지";
        animal.setAnimalName(name);
        
        Cat cat = new Cat();
        cat.setAnimalName(name);
        cat.move();
	}
}

인터페이스 래퍼런스 타입으로 선언된 구현체에서 인터페이스에 선언된 메서드만 사용됨을 확인했다. 따라서, 인터페이스 래퍼런스 타입으로 선언된 구현체에서도 구현체에서 확장한 메서드를 사용하기 위해선 구현체 타입으로 캐스팅이 필요하다.

인터페이스 상속

다른 예제 코드로 예시를 들어보자

  • Person
public class Person {
	String species = "영장류";
    
    protected void 국적() {
    	System.out.println("국적을 오버라이딩 하세요.");
	}
}
  • Person객체를 상속받은 Father
public class Father extends Person {
	@Override
    protected void 국적() {
    	System.out.println("한국");
	}
}
  • Person객체를 상속받은 Mother
public class Mother extends Person {
	@Override
	protected void 국적() {
		System.out.println("미국");
	}
}
  • Mother객체를 상속받은 Child
public class Child extends Mother {
	public static void main(String[] args) {
		Child child = new Child();
		System.out.println("나의 생물학적 분류는 "+child.species+"입니다.");
		child.국적();
	}
}

Output
나의 생물학적 분류는 영장류 입니다.
미국

위와 같은 결과를 볼 수 있다. 하지만 Father 클래스Mother 클래스를 둘다 상속 받는다고 가정했을 때 child.국적()은 어떤 결과를 낼 수 있을까? 굉장히 모호한 결과가 생긴다. 이러한 현상을 "다이아몬드 문제" 라고 한다.
Person의 국적을 아빠와 엄마 모두 오버라이딩 했다면 자식은 어떤 국적 메서드를 받아올 지 알 수 없는 것이다. 실제로 이 애플리케이션을 실행해야 하는 JVM에서는 Father에도 선언되어 있고, Mother에도 선언되어 있는 국적()중 어떤걸 child가 상속 받아야 하는지를 모르기 때문에 컴파일 에러가 발생한다. 이러한 현상을 해결해 주는 것이 바로 인터페이스의 다중 상속이다.

  • Interface Person
public interface Person {
	String species = "영장류";

	public abstract void 국적();
}
  • Person인터페이스를 상속받은 Father
public interface Father extends Person {
	@Override
	void 국적();
}
  • Person인터페이스를 상속받은 Mother
public interface Mother extends Person {
	@Override
	void 국적();
}
  • Mother, Father 인터페이스를 구현하는 구현체 Child
public class Child implements Mother, Father {

	@Override
	public void 국적() {
		System.out.println("대한민국");
	}

	public static void main(String[] args) {
		Child child = new Child();
		System.out.println("나의 생물학적 분류는 "+child.species+"입니다.");
		child.국적();
	}
}

위 코드들을 보면 왜 인터페이스의 다중 상속이 가능한가 알 수 있다. 어차피 인터페이스에는 추상 메서드로 구성되어 중간 과정에 구현체가 없기 때문에 Child가 오버라이딩 해야하는 즉, 구현해야 하는 메서드가 Father로 부터 받은건지, Mother로 부터 받은건지 알 필요가 없다는 것이다.

인터페이스의 기본 메서드 (default method), 자바 8

자바 8버전 이후부터 인터페이스에 기본 메서드(default method)와 정적 메서드(static)를 사용할 수 있다. 여러 구현체들이 한 인터페이스를 구현하고 있을 때, 인터페이스에 어떤 기능을 추가하기 위해서는 그것을 구현한 구현체들 모두 에서 기능에 대해 정의해주어야 한다. 이런 불편함 때문에 자바 8버전 이후부터는 인터페이스 내부에 default method를 구현할 수 있게 되었다.

//메서드를 정의할 경우
public interface Example {
	void printSt(String st) {
    	System.out.println(st);
}


자바 8 이전에는 인터페이스 내부의 추상 메서드를 재정의 하기 위해 인터페이스를 상속받는 클래스에서 추상 메서드를 재정(Overriding)해야만 했다. 하지만 자바 8부터 도입된 default 키워드를 사용하면 인터페이스에서 메서드 본문을 구현 할 수 있으며 추상 메서드가 아니기때문에 구현이 강제되지 않는다.

public interface Example {
	default void printSt(String st) {
		System.out.println(st);
	}
}

public class ClassTest implements Example {}

public class Main {
	public static void main(String[] args) {
		ClassTest obj = new ClassTest();
		obj.printSt("Hi");
	}
}

Output
Hi

그리고 인터페이스에 정의된 기본 메서드는 구현 클래스에서 재정의 할 수 있다.

public interface Example {
	default void printSt(String st) {
		System.out.println(st);
	}
}

public class ClassTest implements Example {
	@Override
	public void printSt(String st) {
		System.out.println("Override");
		System.out.println(st);
	}
}

public class Main {
	public static void main(String[] args) {
		ClassTest obj = new ClassTest();
		obj.printSt("Hi");
	}
}

Output
Override
Hi

인터페이스의 정적 메서드 (static method), 자바 8

오라클 공식 문서에는 static 메서드를 인터페이스에서 helper 메서드로 사용하는 예시를 들고있다. 여기서 말하는 helper 메서드는 주목적으로 사용되는 것이 아닌, 다른 객체를 돕기 위해서 특정 목적으로 만들어진 메서드이다. 클래스의 static 메서드처럼 사용하기 보단, 인터페이스 내부적으로 필요한 것을 정의해두고 사용하는데 목적이 있다고 추측된다. 마찬가지로 추상 메서드가 아니기 때문에 구현 클래스에서 강제로 구현할 필요는 없다.

public interface InterfaceTest {
	static String myIpAddress() {
		return "127.0.0.1";
	}
}

public class Main {
	public static void main(String[] args) {
		System.out.println(Interface.myIpAddress);
	}
}

Output
127.0.0.1

인터페이스 private 메서드 , 자바 9

인터페이스의 private 메서드는 인터페이스 내에서만 사용 가능한 메서드이고, default 메서드나 static 메서드에 사용하기 위한 메서드이다. 인터페이스를 구현하는 클래스 쪽에서 재정의 하거나 사용할 수 없고, 디폴트나 정적 메서드를 통해서만 사용 가능하다

public interface Calc {
	double PI = 3.14; // 나중에 상수가 됨.
    int ERROR = -9999999;
    
    int add(int num1, int num2);
    int substract(int num1, int num2);
    int times(int num1, int num2);
    int divide(int num1, int num2);
    
    //디폴트 메서드
    default void description() {
    	System.out.println("정수 계산기를 구현합니다.");
        myMethod(); //private 메서드 사용
	}
    
    //static 메서드
    static int total(int arr[]) {
    	int total = 0;
        
        for(int i : arr) {
        	total += i;
		}
        
       myStaticMethod(); //private static 메서드 사용
       return total;
	}
    
    //private 메서드
    private void myMethod() {
    	System.out.println("private method");
	}
    
    //private static 메서드
    private static void myStaticMethod() {
    	System.out.println("private static method");
	}
}
  • default 메서드 description()은 구현문을 가진 메서드로 implements하는 클래스들에서도 기본적으로 적용이 되는 메서드
  • static 메서드 total()은 인스턴스의 생성 없이 사용할 수 있는 메서드로 인터페이스명을 타입으로 호출해 사용 가능.
  • private 메서드 myMethod()는 인터페이스 내에서만 사용 가능한 메서드로 description() 안에 사용
  • private static 메서드는 인터페이스 내에서만 사용 가능하고 static키워드가 붙어있어 static 메서드 안에서만 사용 가능 하기때문에 total()에서 사용
public class CalcTest {
	public static void main(String[] args) {

	Calc calc = new CompleteCalc();
	int n1 = 10;
	int n2 = 2;

	System.out.println(calc.add(n1, n2));
	System.out.println(calc.substract(n1, n2));
	System.out.println(calc.times(n1, n2));
	System.out.println(calc.divide(n1, n2));
	//default 메서드사용
	calc.description();

	int[] arr = {1,2,3,4,5};
	//static 메서드 사용
	int sum = Calc.total(arr);
	System.out.println(sum);
    
	}
}

Output
12
8
20
5
정수 계산기를 구현합니다.
private method
private static method
15

  • calc는 CompleteCalc의 인스턴스 이고 description()은 CompleteCalc에 구현되어 있지 않지만 default 메서드이기 때문에 사용 가능하다. default 메서드는 재정의가 가능하기 때문에 CompleteCalc에서 override한다면 다른 결과값이 나오게 된다.
  • static 메서드 total()은 따로 인스턴스 생성 없이 인터페이스명인 Calc.total()로 바로 사용할 수 있다. 마지막으로 private 메서드와 private static 메서드도 각각 default메서드와 static메서드 안에서 사용되고 있음을 결과값에서 확인 할 수 있다.

인터페이스의 요소

  • 상수 : 선언된 모든 변수는 상수로 처리된다.
  • 메서드 : 인터페이스에 모든 메서드는 추상 메서드이다.
  • 기본 메서드 : 메서드에 바디를 가질 수 있는 기본적인 구현 메서드이다. 기본 구현을 가지고 있다 해도 실제 구현하는 클래스에서 재정의 할 수 있다. 추상 메서드가 아니기 때문에 강제성은 없다.
  • 정적 메서드 : static 키워드가 붙는 메서드로 인스턴스 생성과 관계 없이 인터페이스 타입으로 호출하는 메서드이다. 인스턴스를 사용하기 위해 클래스를 만들고 인스턴스를 생성하는 과정을 생략하고 바로 사용할 수 있게 구현해놓은 메서드
  • private 메서드 : 인터페이스 내에서만 사용 가능한 메서드이다. 인터페이스의 외부에서 사용할 수 없고 디폴트 메서드나 정적 메서드에 사용하기 위해 작성되는 메서드이다. 인터페이스를 구현하는 클래스쪽에서 재정의하거나 사용할 수 없으며, 디폴트나 정적 메서드를 통해서만 사용 가능하다.

끝 !

내 기억력들아,, 힘내조라,,

[출처]
https://velog.io/@zayson/백기선님과-함께하는-Live-Study-8주차-인터페이스#-인터페이스-정의하는-방법
https://velog.io/@tsi0521/Java의-다중-상속-문제와-인터페이스
https://yeon-kr.tistory.com/187
https://developer-talk.tistory.com/463
https://velog.io/@foeverna/Java-인터페이스-인터페이스의-요소들

profile
⋆。゚★⋆⁺₊⋆ ゚☾ ゚。⋆ ☁︎。₊⋆

0개의 댓글