JAVA 기초 (26) 인터페이스

코린이서현이·2023년 8월 3일
0

Java

목록 보기
26/46

😆들어가면서😆

자바의 인터페이스에 대해서 알아보자.
인터페이스는 어떤 기능의 메소드를 구현해야하는지 미리 알려주면서 이 메소드를 구현하자고 약속하는 명세서의 역할을 한다.
클래스는 imolement라는 예약어를 통해 인터페이스를 구현하고
실제 사용시에는 클래스로 생성, 인터페이스 참조변수에 대입해서 다형성을 구현한다.

즉 "밥먹는 방법"이라는 인터페이스에 "밥먹기" 추상메소드를 구현,
강아지 클래스, 고양이 클래스, 인간 클래스에서 "밥먹기"메소드를 구현
각 클래스 생성 후 인터페이스 참조변수에 대입후 "밥먹기"코드를 실행하면
겉보기에는 다 같은 코드지만 다 다른 구현을 한다는 다형성을 구현할 수 있는 것이다.

인터페이스에서도 defult메소드, static메소드를 가질 수 있는데 각 예약어의 뜻을 생각해서 어떤 역할을 할지 생각해보자.

default 예약어

왠지 default예약어가 굉장히 낯설게 느껴질텐데 그런 이유가 있다!!
바로 접근제어자를 별도로 설정하지 않았을 때 자동으로 적용되는 예약어로 직접 쓸 일이 지금까지는 없었기 때문이다.

하지만 이번 인터페이스에서 default예약어를 쓰게 되는데, 이는 동일한 패키지안에서 접근이 가능하도록 하는 접근제어자임을 알고 가자. 다른 패키지에서는 사용할 수 없다.

접근 제어자같은 클래스의 멤버같은 패키지의 멤버자식 클래스의 멤버그 외의 영역
public
protectedX
defaultXX
privateXXX

📕 인터페이스

  • 객체의 사용방법을 가이드라인한 명세서
  • interface예약어를 사용한다.
  • 추상메서드와 상수로만 이루어져있다. (따로 abstract 예약어나 static예약어를 쓰지 않아도 컴파일러가 자동으로 변환해준다.)

✍️ 사용방법

  • 클래스에서 implements예약어를 통해 추상메소드를 각각 구현한다.

📖 인터페이스 선언방법

interface Calc {
	//상수 
    //public  static  final예약어가 자동 컴파일 (인스턴스를 생성하지 않고도 접근할 수 있다.)
    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);
}
  • 인터페이스는 추상메소드로 이루어져있음으로 클래스에서 추상메소드를 오버라이딩을 통해 구현해서 사용해야함!!
  • 추상메소드만 존재하기 때문에 인터페이스는 인스턴스를 생성할 수 없다. 인스턴스는 추상메소드를 구현한 클래스로 생성해 사용해야한다.
    (💡그런데, 상수와 static메소드는 인터페이스명으로 접근이 가능함.)

➕ 인터페이스만으로 상수 접근

public class CalculatorTest {
    public static void main(String[] args) {
    
        //3같은 패키지에 있으므로 인터페이스명.상수로 접근 가능, 다른 패키지면 import해야함.
        System.out.println(Calc.PI);		//3.14

📖 클래스에서 인터페이스 구현하기

  • 상속의 개념이 아니라 구현의 개념이기 때문에 '구현하다'의 뜻을 가진 implement예약어를 사용한다.
  • 인터페이스의 추상메소드를 모두 구현한다.
public class CompleteCalc implements Calc{
	
    //인터페이스의 추상메소드 모두 구현
    @Override
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    @Override
    public int substract(int num1, int num2) {
        return num1 - num2;
    }
    
    @Override
    public int times(int num1, int num2) {
        return num1 * num2;
    }

    @Override
    public int divide(int num1, int num2) {
        if (num2 != 0) {
            return num1 / num2;
        } else {
            return Calc.ERROR;
        }
    }

}

📒 사용하기

  1. 클래스 생성자로 인스턴스를 만들어 사용할 수 있다.
  2. 클래스 생성자를 인터페이스 참조 변수에도 대입할 수 있다.
  • 클래스에서 추가로 구현한 메소드는 사용할 수 없다.

✍️ 예시코드
클래스에서 showInfo()라는 메소드 추가 구현

public class CompleteCalc implements Calc{
	
    ...
	
    public void showInfo(){
        System.out.println("CompleteCalc 구현");
    }

}
public class CalculatorTest {
    public static void main(String[] args) {
        int num1 = 10;
        int num2 = 5;

        Calc calc1 = new CompleteCalc();
        CompleteCalc calc2 = new CompleteCalc();

//  	calc1.showInfo();			// 실행오류
        calc2.showInfo();			// 실행가능		CompleteCalc 구현
		
        //상수는 인터페이스로 접근 가능.
		System.out.println(Calc.PI);
        
        System.out.println(calc1.add(num1,num2));		
        System.out.println(calc1.substract(num1,num2));	
        System.out.println(calc1.times(num1,num2));
        System.out.println(calc1.divide(num1,num2));

	}
}

👉 실행화면

CompleteCalc 구현
3.14
15
5
50
2

🤔 추상메소드를 모두 구현하지 않으면 어떻게 될까?

  • 추상클래스가 된다.
public abstract class Calculator implements Calc {
	//전부 구현하지 않았기 때문에 추상메소드로 존재
    @Override
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    @Override
    public int substract(int num1, int num2) {
        return num1 - num2;
    }
}

📖 인터페이스의 private, default, static

🤔 디폴트 메소드 생각해보기

한 인터페이스에 A라는 메소드가 있다고 생각해보자.
이 A라는 메소드는 모든 클래스에서 동일하게 작용한다. 그러면 클래스마다 같은 메소드를 여러번 작성해야해서 생산성이 떨어진다. 
이때 디폴트 메소드를 사용하면 클래스마다 공통적인 메소드를 인터페이스에서 구현할 수 있다.

📒 default 메소드

  • 인터페이스에서 default예약어를 통해 구현한 메소드
  • 인터페이스의 인스턴스를 생성할 수 없듯이 default메소드또한 구현되어있다고 사용할 수 없고, 클래스를 생성하고 나서 사용할 수 있다.
  • 접근제어자가 public 이며 생략 가능하다. (인터페이스의 추상메소드의 접근제어자가 public이므로 public예약어만 사용가능하다.)
interface interfaceEx {
    default void defaultMethod(){
        System.out.println("defaultMethod 실행");
    }
}

class classImplement implements interfaceEx{

}

public class InterfaceEX1 {
    public static void main(String[] args) {
        classImplement classImplement = new classImplement();

        classImplement.defaultMethod();             //defaultMethod 실행
    }
}

➕ default 메소드 오버라이딩하기

상위클래스를 상속하고 상위메소드를 오버라이딩하여 재정의하였을 때, 상위 메소드를 호출할 일이 생긴다면 super키워드를 통해 부모 메소드를 호출 할 수 있었는데,
인터페이스의 디폴트 메소드 오버라이딩 역시 super예약어를 사용할 수 있다.
다만 인터페이스명.super.메소드명으로 문법이 다르다.

인터페이스의 디폴트 메소드 오버라이딩

interface interfaceEx {
    default void defaultMethod(){
        System.out.println("인터페이스 디폴트 메소드 실행");
    }

}

class classImplement implements interfaceEx{
    @Override
    public void defaultMethod() {
        System.out.println("이 디폴트 메소드는 구현클래스에서 재정의되었습니다.");
        interfaceEx.super.defaultMethod();			//인터페이스명.super.메소드명
    }

}

public class InterfaceEX1 {
    public static void main(String[] args) {
        classImplement classImplement = new classImplement();

        classImplement.defaultMethod();            
    }
}

👉 실행화면

이 디폴트 메소드는 구현클래스에서 재정의되었습니다.
인터페이스 디폴트 메소드 실행

일반 클래스에서 메소드 오버라이딩

class superClass{
    void methodTest () {
        System.out.println("상위클래스메소드 실행");
    }
}

class subclass extends superClass{
    @Override
    void methodTest() {
        System.out.println("이 메소드는 하위클래스에서 재정의 되었습니다.");
        super.methodTest();
    }
}

public class superTest {
    public static void main(String[] args) {
        subclass subclass = new subclass();

        subclass.methodTest();
    }
}

👉 실행화면

이 메소드는 하위클래스에서 재정의되었습니다.
상위클래스메소드 실행

🤔 정적메소드 생각해보기

앞에서 인터페이스의 상수는 static 상수로 인터페이스명으로 접근할 수 있는 것을 배웠다.
그렇다면 메소드에 static을 붙여 클래스를 생성하지 않고도 인터페이스로 접근가능한 static메소드를 만들어보자. 

📒 static 메소드

  • static를 사용해 정적 메소드를 만든다.
  • 인스턴스를 생성하지 않고도 인터페이스명으로 접근이 가능하다.
  • 오버라이딩할 수 없다. (컴파일러,JVM이 static메소드 호출 시 실제 객체를 찾는 작업을 실행하지 않기 때문에 객체마다 다르게 오버라이딩할 수 없는 것이다.더 알아보기)
interface interfaceEx {
    static void staticMethod() {
        System.out.println("staticMethod 실행");
    }
}

class classImplement implements interfaceEx{

}

public class InterfaceEX1 {
    public static void main(String[] args) {
    	//인스턴스를 생성하지 않고 인터페이스명으로 메소드호출
        interfaceEx.staticMethod();		//staticMethod 실행

    }
}

🤔 private 메소드 생각해보기

private 메소드는 해당 클래스에서만 사용이 가능한 메소드이다.
즉 인터페이스내에서만 사용이 가능하도록 하는 메소드이다. 
또한 접근이 불가능하기 때문에 구현클래스에서 재정의할 수 없다.

📒 private 메소드

  • 인터페이스에서만 사용하고 접근할 수 있다. 구현 클래스에서 재정의할 수 없다.
  • 인터페이스에서만 접근이 가능하기 때문에 추상메소드에서 호출 할 수 없고,
    private 메소드default 메소드 내부에서 사용 가능
    private static메소드static 메소드에서만 사용 가능하다.
interface interfaceEx {
    default void defaultMethod(){
        System.out.println("인터페이스 디폴트 메소드 실행");
        prevateMethod();
    }

    private void prevateMethod(){
        System.out.println("private 메소드");
    }
    static void staticMethod() {
        System.out.println("인터페이스 명으로 사용가능한 static메소드 실행");
        privateStaticMethod();
    }

    private static void privateStaticMethod(){
        System.out.println("static메소드에서 사용가능한 privateStatic 실행");
    }

}

class classImplement implements interfaceEx{

}

public class InterfaceEX1 {
    public static void main(String[] args) {
        classImplement classImplement = new classImplement();

        classImplement.defaultMethod();             
		//인터페이스 디폴트 메소드 실행
		//private 메소드
        
        interfaceEx.staticMethod();
		//인터페이스 명으로 사용가능한 static메소드 실행
		//static메소드에서 사용가능한 privateStatic 실행

    }
}

📖 인터페이스로 다형성 구현하기

  • 어떤 클래스로 생성되었는지와 관련없이 인터페이스가 제공하는 메소드를 사용할 수 있다.
import java.util.Scanner;

interface Animal {
    void cry();
}

class Cat implements Animal{
    @Override
    public void cry() {
        System.out.println("냐옹");
    }
}

class Dog implements Animal{
    @Override
    public void cry() {
        System.out.println("멍멍");
    }
}

public class interfacePolymorphism {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Animal animal = null;

        System.out.println("멍멍이(1) 고양이(2)");
        switch (sc.nextInt()){
            case 1 : {
                animal = new Dog();
                break;
            }
            case 2 : {
                animal = new Cat();
                break;
            }
            default: break;
        }

        animal.cry();					//사용자의 입력에 따라 냥냥인지 멍멍인지 결정된다. 
    }
}

📖 인터페이스의 상속

  • 클래스끼리 상속을 통해 확장을 하듯이 인터페이스자체를 상속이 가능하다.
    extends예약어 사용

📖 인터페이스의 다중 상속

단일 상속의 경우 꼭 써야하나🤔 라는 생각이 들 것 같다. 하지만 여러 인터페이스를 다중상속할 수 있다면?!
자바는 클래스 상속에 대해서 다중상속을 허용하지 않지만 인터페이스에 한해서는 다중상속을 허용하고 있다.

인터페이스상속은 인터페이스가 받을 수 있다. 클래스가 인터페이스를 상속받을 수는 없다.

<인터페이스 다중 상속>
→ 인터페이스를 인터페이스가 entends예약어를 통해 다중 상속
interface interface3 extends interface1,interface2 {...}
→ 상속받은 인터페이스를 클래스로 구현
class class2 implements interface3{..}

😆 인터페이스의 다중 상속의 장점

  • 메소드 호출의 모호성을 방지할 수 있다.
interface interface1 {
    void in1();
    void in3();
}

interface  interface2 {
    void in2 ();
    void in3();
}

interface interface3 extends interface1,interface2 { }

class class1 implements interface3{
    @Override
    public void in1() {
        System.out.println("in1 실행");
    }

    @Override
    public void in2() {
        System.out.println("in2 실행");
    }

    @Override
    public void in3() {
        System.out.println("in3 실행");
    }
}
public class InterfaceInheritance {
    public static void main(String[] args) {
        class1 class1 = new class1();

        class1.in1();
        class1.in2();
        class1.in3();						//동일한 메소드명이 있어도 오류나지 않는다.

    }
}

🤔 클래스의 다중상속을 금지하는 이유

동일한 메서드 명이 겹칠 수 있고, 이는 다이아몬드 문제로 오류가 발생하기 때문에 
java에서는 다중 상속을 금지하고 있다.

🤔 인터페이스는 다중상속이 가능한 이유

어처피 구현은 클래스에서 하기 때문에 충돌 가능성이 없다. 
그런데 default메소드명이 동일할 경우 문제가 발생한다. 이에 대해서 더 자세하게 알아보자.

📒 다중인터페이스들간의 디폴트 메소드 출돌

  1. 똑같은 디폴트 메소드를 가진 인터페이스를 하나의 클래스에 구현하고 조치를 취하지 않으면 컴파일자체가 되지 않는다.
  2. 인터페이스를 구현한 클래스에서 디폴트 메소드를 오버라이딩해서 통합해야한다.
interface interface1 {
    default void in3(){
        System.out.println("interface1의 in3 실행");
    }
}

interface  interface2 {
    default void in3(){
        System.out.println("interface2의 in3 실행");
    }
}

interface interface3 extends interface1,interface2 {
	//오버라이딩을 해야지 컴파일이 가능함.
    @Override
    default void in3() {
        interface1.super.in3();
    }
}

class class1 implements interface3{
}

public class InterfaceInheritance {
    public static void main(String[] args) {
        class1 class1 = new class1();

        class1.in3();			//interface1의 in3 실행

    }
}

📖 인터페이스구현과 클래스상속을 같이 하기

  • 인터페이스 구현과 클래스 상속을 함께 쓸 수 있다.

🤔 인터페이스의 디폴트 메서드와 부모 클래스 메서드 간의 충돌

  • 부모 클래스의 메소드가 상속되고 디폴트 메소드는 무시된다.
  • 만일 인터페이스의 디폴트 메소드를 사용하고 싶다면, 오버라이딩해야한다.

case1. 부모클래스의 메소드 선택

interface interface1 {
    default void in3(){
        System.out.println("interface1의 in3 실행");
    }
}

class class1 {
    public void in3() {
        System.out.println("class1의 in3 실행");
    }
}

class class2 extends class1 implements interface1{
}

public class InterfaceInheritance {
    public static void main(String[] args) {
        class2 class2 = new class2();
        
        class2.in3();							//class1의 in3 실행

    }
}

case2. 인터페이스의 메소드 오버라이딩

interface interface1 {
    default void in3(){
        System.out.println("interface1의 in3 실행");
    }
}

class class1 {

    public void in3() {
        System.out.println("class1의 in3 실행");
    }
}

class class2 extends class1 implements interface1{
    @Override
    public void in3() {
        interface1.super.in3();
    }
}
public class InterfaceInheritance {
    public static void main(String[] args) {
        class2 class2 = new class2();

        class2.in3();                               //interface1의 in3 실행

    }
}
profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글