본문 바로가기
[Naver Cloud Camp 7] 교육 정리

네이버 클라우드 캠프 36일차 230615

by 우기37 2023. 6. 16.

#1 교육정리

1) 인터페이스

 

 

 

#2 인터페이스

인터페이스는 클래스 혹은 프로그램이 제공하는 기능을 명시적으로 선언하는 역할을 합니다. 인터페이스는 추상 메서드와 상수로만 이루어져 있습니다. 구현된 코드가 없기 때문에 당연히 인터페이스로 인스턴스를 생성할 수도 없습니다. 

 

자식 클래스가 여러 부모 클래스를 상속받을 수 있다면, 다양한 동작을 수행할 수 있다는 장점을 가지게 될 것입니다.

하지만 클래스를 이용하여 다중 상속을 할 경우 메소드 출처의 모호성 등 여러 가지 문제가 발생할 수 있어 자바에서는 클래스를 통한 다중 상속은 지원하지 않습니다.

 

하지만 다중 상속의 이점을 버릴 수는 없기에 자바에서는 인터페이스라는 것을 통해 다중 상속을 지원하고 있습니다.

 

즉, 인터페이스(interface)란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미합니다.

 

자바에서 추상 클래스는 추상 메소드뿐만 아니라 생성자, 필드, 일반 메소드도 포함할 수 있습니다.

하지만 인터페이스(interface)는 오로지 추상 메소드와 상수만을 포함할 수 있습니다.

 

이들 메서드는 public abstract 예약어를 명시적으로 쓰지 않아도 컴파일 과정에서 자동으로 추상 메서드로 변환됩니다. 그리고 인터페이스에 선언한 변수는 모두 컴파일 과정에서 값이 변하지 않는 상수로 자동 변환됩니다. public statc final 예약어를 쓰지 않아도 무조건 상수로 인식하는 것입니다.

 

선언한 인터페이스를 클래스가 사용하는 것을 '클래스에서 인터페이스를 구현한다(implements)'라고 표현 합니다.

 

상속에서는 상위 클래스에 구현한 기능을 하위 클래스에서 확장한다는 의미로 extends 예약어를 사용하고, 인터페이스에서는 인터페이스에 선언한 기능을 클래스가 구현한다는 의미로 implements 예약어를 사용합니다.

 

- 문법

접근제어자 interface 인터페이스이름 {
    public static final 타입 상수이름 = 값;
    ...
    public abstract 메소드이름(매개변수목록);
    ...
}

 

단, 클래스와는 달리 인터페이스의 모든 필드는 public static final이어야 하며, 모든 메소드는 public abstract이어야 합니다.

이 부분은 모든 인터페이스에 공통으로 적용되는 부분이므로 이 제어자는 생략할 수 있습니다.

이렇게 생략된 제어자는 컴파일 시 자바 컴파일러가 자동으로 추가해 줍니다.

 

 

- 역할

인터페이스의 역할은 인터페이스를 구현한 클래스가 어떤 기능의 메서드를 제공하는지 명시하는 것입니다. 그리고 클라이언트 프로그램은 인터페이스에서 약속한 명세대로 구현한 클래스를 생성해서 사용하면 됩니다.

 

 

인터페이스의 구현)

인터페이스는 추상 클래스와 마찬가지로 자신이 직접 인스턴스를 생성할 수는 없습니다.

따라서 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성해야만 합니다.

 

자바에서 인터페이스는 다음과 같은 문법을 통해 구현합니다.

class 클래스이름 implements 인터페이스이름 { ... }

 

 

인터페이스의 장점)

인터페이스를 사용하면 다중 상속이 가능할 뿐만 아니라 다음과 같은 장점을 가질 수 있습니다.

 

1. 대규모 프로젝트 개발 시 일관되고 정형화된 개발을 위한 표준화가 가능합니다.

2. 클래스의 작성과 인터페이스의 구현을 동시에 진행할 수 있으므로, 개발 시간을 단축할 수 있습니다.

3. 클래스와 클래스 간의 관계를 인터페이스로 연결하면, 클래스마다 독립적인 프로그래밍이 가능합니다.

 

 

상속과 구현)

 

- 문법

class 클래스이름 extend 상위클래스이름 implements 인터페이스이름 { ... }

 

- 다중 상속의 예제

interface Animal { public abstract void cry(); }
interface Pet { public abstract void play(); }
 
class Cat implements Animal, Pet {
    public void cry() {
        System.out.println("냐옹냐옹!");
    }
    public void play() {
        System.out.println("쥐 잡기 놀이하자~!");
    }
}
 
class Dog implements Animal, Pet {
    public void cry() {
        System.out.println("멍멍!");
    }
    public void play() {
        System.out.println("산책가자~!");
    }
}
 
public class Polymorphism04 {
    public static void main(String[] args) {
        Cat c = new Cat();
        Dog d = new Dog();
 
        c.cry();
        c.play();
        d.cry();
        d.play();
    }
}

냐옹냐옹!
나비야~ 쥐 잡기 놀이하자~!
멍멍!
바둑아~ 산책가자~!

 

 

클래스를 이용한 다중 상속의 문제점)

클래스를 이용하여 다중 상속을 하면 다음 예제와 같은 메소드 출처의 모호성 등의 문제가 발생할 수 있습니다.

class Animal { 
    public void cry() {
        System.out.println("짖기!");
    }
}
 
class Cat extends Animal {
    public void cry() {
        System.out.println("냐옹냐옹!");
    }
}
 
class Dog extends Animal {
    public void cry() {
        System.out.println("멍멍!");
    }
}
 
① class MyPet extends Cat, Dog {}
 
public class Polymorphism {
    public static void main(String[] args) {
        MyPet p = new MyPet();
②      p.cry();
    }
}

위의 예제에서 Cat 클래스와 Dog 클래스는 각각 Animal 클래스를 상속받아 cry() 메소드를 오버라이딩하고 있습니다.

여기까지는 문제가 없지만, ①번 라인에서 MyPet 클래스가 Cat 클래스와 Dog 클래스를 동시에 상속받게 되면 문제가 발생합니다.

 

②번 라인에서 MyPet 인스턴스인 p가 cry() 메소드를 호출하면, 이 메소드가 Cat 클래스에서 상속받은 cry() 메소드인지 Dog 클래스에서 상속받은 cry() 메소드인지를 구분할 수 없는 모호성을 지니게 됩니다.

이와 같은 이유로 자바에서는 클래스를 이용한 다중 상속을 지원하지 않는 것입니다.

 

하지만 다음 예제처럼 인터페이스를 이용하여 다중 상속을 하게되면, 위와 같은 메소드 호출의 모호성을 방지할 수 있습니다.

interface Animal { public abstract void cry(); }
 
interface Cat extends Animal { public abstract void cry(); }
interface Dog extends Animal { public abstract void cry(); }
 
class MyPet implements Cat, Dog {
    public void cry() {
        System.out.println("멍멍! 냐옹냐옹!");
    }
}
 
public class Polymorphism05 {
    public static void main(String[] args) {
        MyPet p = new MyPet();
        p.cry();
    }
}

멍멍! 냐옹냐옹!

 

 

인터페이스 상수)

인터페이스는 추상 메서드로 이루어지므로 인스턴스를 생성할 수 없으며 멤버 변수도 사여ㅛㅇ할 수 없습니다.

그런데 인터페이스에 아래 코드와 같이 변수를 선언핸도 오류가 발생하지 않습니다. 그 이유는 인터페이스에 선언한 변수를 컴파일하면 상수로 변환되기 때문입니다.

 

public interface Calc {
	double PI = 3/14;
    int ERROR = -999999999;
    ...
   }

Calc 인터페이스에 선언한 변수 PI를 컴파일 하면 public static final double PI = 3.14,

즉 상수 3.14로 변환 됩니다. 그리고 int형 변수 ERROR 역시 public static final int ERROR = -999999999 로 변환되어 상수로 취급됩니다.

 

 

디폴트 메서드와 정적 메서드)

자바 7까지는 기능이 같다고 해도 인터페이스에서 코드를 구현할 수 없으므로 추상 메서드를 선언하고 각 클래스마다 똑같이 그 기능을 반복해 구현해야 해서 굉장히 번거로웠습니다. 또한, 클래스를 생성하지 않아도 사용할 수 있는 메서드가 필요한 경우도 있는데, 인터페이스만으로는 메서드를 호출할 수가 없어 불편했습니다.

 

이러한 분편한 이유로 인해 인터페이스 활용성을 높이기 위해 디폴트 메서드와 정적 메서드가 기능을 제공합니다.

 

디폴트 메서드)

: 말 그대로 기본으로 제공되는 메서드입니다. 디폴트 메서드는 인터페이스에서 구현하지만, 이후 인터페이스를 구현한 클래스가 생성되면 그 클래스에서 사용할 기본 기능입니다.

디폴트 메서드를 선언할 때는 default 예약어를 사용합니다.

 

- 문법

public interdace Calc {
...
	default void description() {		// 메서드 자료형 앞에 default 예약어만 써 주면 됩니다.
    	System.out.println("정수 계산기를 구현합니다");
        }
      }

 

- 장점

다음과 같이 default method가 등장하면서 위에서 다뤘던 문제점을 해결할 수 있습니다. 

 

문제점 : 인터페이스에 추상메서드를 추가하게 되면 모든 구현체에 구현을 해야한다.

해결 방안 : 인터페이스에 default method를 사용하면 추가 변경을 막을 수 있다.

이로써 OCP(Open-Close-Principle : 개방 폐쇄 원칙) 에서 확장에 개방(Open)되어 있고, 변경에닫혀(Close)있는 코드를 설계할 수 있게 됩니다.

또한 default method가 등장하면서 객체지향 설계를 더욱 유연하게 할 수 있게 된 것 같습니다

 

 

여기서 OCP란?

개방-폐쇄 원칙 (Open/closed principle)이란?

“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”

소프트웨어 구성 요소(컴포넌트, 클래스, 모듈, 함수)는 확장에 대해서는 개방(OPEN)되어야 하지만 변경에대해서는 폐쇄(CLOSE)되어야 한다는 의미라고 볼 수 있습니다. 

근데 왜 갑자기 개방 폐쇄 원칙을 언급하는걸까요?

위와 같은 경우, 확장은 할 수 있지만(OPEN) 변경에 대한 폐쇄(CLOSE)를 위반한 케이스라고 볼 수 있습니다.

그 이유는 methodA InterfaceA에 추가된다는 이유로 methodA를 사용하지 않는 모든 구현체에 methodA에 대한 구현을 해야 하기 때문입니다. 

즉, 기존의 코드가 변경되지 않고 기능 확장을 해야 위 원칙을 지킬 수 있을 것입니다.

글출처 : https://velog.io/@heoseungyeon/디폴트-메서드Default-Method

 

- 디폴트 메서드 재정의 하기

만약 이미 인터페이스에 구현되어 있는 디폴트 메서드가 새로 생성한 클래스에서 원하는 기능과 맞지 않는다면 하위 클래스에서 디폴트 메서드를 재정의 할 수 있습니다.

 

public class CompleteCalc extends Calculator {
...
	@Override
    public void description() {	// 디폴트 메서드 description()을 
    	//TODO Auto-generated method stub	// CompleteCalc 클래스에서 원하는 기능으로 재정의
        super.description();
        }
      }

 

Calc 인터페이스를 구현하는 Calculator 클래스에서 재정의 할 수도 있고 Calculator 클래스를 상속받은 CompleteCalc 클래스에서 재정의 할 수도 있습니다.

 

위의 코드와 같이 super.description()은 인터페이스에 선언한 메서드를 의미합니다. 이 코드를 사용하지 않을거라면 지우고 새 코드를 작성하면 됩니다. 이제 CompleteCalc 클래스로 인스턴스를 생성하여 호출하면 재정의된 메서드가 호출됩니다.

 

 

정적 메서드)

정적 메서드는 static 예약어를 사영하여 선언하며 클래스 생성과 무관하게 사용 할 수 있습니다. 정적 메서드를 사영할 때는 인터페이스 이름으로 직접 참조하여 사용합니다. 그러면 Calc 인터페이스에 매개변수로 전달된 배열의 모든 요소 값을 더하는 정적 메서드 total()을 추가해 보겠습니다.

 

public interface Calc {
...
static int total(int[] arr) {	// 인터페이스에 정적 메서드 total() 구현
	int total = 0;
    
    for (int i : arr) {
    	total += i;
        }
        return total;
      }
    }

 

이렇게 항상 값이 변하지 않는 경우라면 static 사용시 메모리의 이점을 얻을 수 있습니다.

 

static 으로 설정하면 같은 곳의 메모리 주소만을 바라보기 때문에 static 변수의 값을 공유하게 되는 것입니다.

 

 

private 메서드)

private 메서드는 인터페이스를 구현한 클래스에서 사용하거나 재정의할 수 없습니다.따라서 기존에 구현된 코드를 변경하지 않고 인터페이스를 구현한 클래스에서 곹옹으로 사용하는 경우에 private 메서드로 구현하면 코드 재사용성을 높일 수 있습니다. 또한 클라이언트 프로그램에 제공할 기본 기능을 private 메서드로 구현하기도 합니다.

 

private 메서드는 코드를 모두 구현해야 하므로 추상 메서드에 private 예약어를 사용할 수는 없지만, static 예약어는 함께 사용할 수 있습니다. private static 메서드는 정적 메서드에서 호출하여 사용합니다.