요구사항을 받았을 때, 우리는 아키텍처를 기반으로 어떤 기능을 만들어야 할지부터 고민한다. 즉, 만들어야 할 기능들이 선언만 되어있고, 관련된 것들끼리 묶은 후 이름을 지어주는데, 이를 인터페이스라고 한다.
인터페이스 작성 문법
- 인터페이스의 모든 필드는 public static final이어야 하며, 모든 메서드는 public abstract이어야 한다. 단, public, final, abstract 는 생략하면 자동으로 붙는다.
- Java 8부터는 default 메서드와 static 메서드도 선언이 가능하다.
/**
* 1. 1 ~ 45 까지 써져있는 Ball을 로또 기계에 넣는다.
* 2. 로또 기계에 있는 Ball들을 섞는다.
* 3. 섞인 Ball 중에서 6개를 꺼낸다.
*/
public interface LottoMachine {
public static fianl int MAX_BALL_COUNT = 45; // 메모리에 인스턴스를 생성하지 않아도 올라간다.
public static fianl int RETURN_BALL_COUNT = 6;
public abstract void setBalls(Ball[] balls); // Ball을 45개 받는다.
void shuffle(); // 자기가 가지고있는 Ball들을 섞는다.
Ball[] getBalls(); // 6개의 Ball을 반환한다.
}
그리고 interface는 추상 클래스와 마찬가지로 new 키워드를 이용해 인스턴스를 생성할 수 없다. 즉, 기능을 나열해놓은 껍데기일 뿐이다.
그렇다면 interface의 장점은 무엇일까?
- 마치 TODO 리스트와 같이 구현해야할 기능 목록을 정의해놓고, 하나하나씩 구현해나가는 것이다.
그리고 이 interface를 구현하게 되면, 반드시 인터페이스가 가지고 있는 메서드를 Overriding 해줘야 한다.
package theory.lotto;
public class LottoMachineImpl implements LottoMachine{
@Override
public void setBalls(Ball[] balls) {
}
@Override
public void shuffle() {
}
@Override
public Ball[] getBalls() {
return new Ball[0];
}
}
이를 main 메서드에 사용하려고 한다면, LottoMachine이라는 interface는 참조 타입이 될 수 있다.
public class LottoMachineMain {
public static void main(String[] args) {
// 로또 머신 인스턴스 생성
LottoMachine lottoMachine = new LottoMachineImpl();
}
}
또한 Ball 은 각각 number를 갖는 불변 객체가 되어야 한다.
public class Ball {
private int number;
public Ball(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
}
그리고 이 Ball을 45개 만들어서 setBalls()를 해줘야하기 때문에, Ball 인스턴스를 45개 참조할 수 있는 배열 생성한다. 하지만 배열을 생성한다고 해서 Ball 인스턴스 45개를 참조할 수 있는 참조 변수가 만들어진 것이고, 인스턴스가 생성된 것은 아니다. 따라서 배열의 각 index에 인스턴스를 생성해줘야 하는 것이다.
Ball[] balls = new Ball[45];
for (int i = 0; i < 45; i++) {
balls[i] = new Ball(i + 1);
}
즉, Ball[] 도 객체이고, 각 index가 Ball을 참조하는 것이다.
이에 대한 로또 머신 구현체를 만들면 다음과 같고,
package theory.lotto;
public class LottoMachineImpl implements LottoMachine{
private Ball[] balls;
@Override
public void setBalls(Ball[] balls) {
this.balls = balls;
}
@Override
public void shuffle() {
for (int i = 0; i < 10000; i++) {
// 0.0 <= x < 45.0
int x1 = (int) (Math.random() * LottoMachine.MAX_BALL_COUNT);
int x2 = (int) (Math.random() * LottoMachine.MAX_BALL_COUNT);
if (x1 != x2) {
Ball tmp = balls[x1]; // 값을 치환할 때는 같은 type의 임시변수가 필요하다.
balls[x1] = balls[x2];
balls[x2] = tmp;
}
}
}
@Override
public Ball[] getBalls() {
Ball[] result = new Ball[LottoMachine.RETURN_BALL_COUNT]; // Ball 6개를 참조할 수 있는 배열
for (int i = 0; i < result.length; i++) {
result[i] = balls[i];
}
return result;
}
}
main 메서드에 상수를 사용하도록 변경한 코드는 다음과 같이 나온다.
package theory.lotto;
public class LottoMachineMain {
public static void main(String[] args) {
// Ball 인스턴스를 45개 참조할 수 있는 배열 생성
// 즉, Ball 인스턴스 45개를 참조할 수 있는 참조 변수가 만들어진 것이고, 인스턴스가 생성된 것은 아니다.
Ball[] balls = new Ball[LottoMachine.MAX_BALL_COUNT];
for (int i = 0; i < LottoMachine.MAX_BALL_COUNT; i++) {
balls[i] = new Ball(i + 1);
}
// 로또 머신 인스턴스 생성
LottoMachine lottoMachine = new LottoMachineImpl();
lottoMachine.setBalls(balls);
lottoMachine.shuffle();
Ball[] result = lottoMachine.getBalls();
for (Ball ball : result) {
System.out.println("ball.getNumber() = " + ball.getNumber());
}
}
}
interface의 default 메서드가 생긴 이유
interface에 메서드를 선언 후 version 1.0으로 오픈소스로 외부 공개했는데, 그러면 version 2.0으로 업데이트시 기존에 사용했던 사용자들의 클래스에서 컴파일 에러가 발생한다. 이를 방지하기 위해, 미리 구현된 default method가 필요한 것이다. 즉, 필요하다면 나중에 사용자가 Overriding 하면 된다.
interface의 static 메서드
인터페이스.메서드() 처럼 인터페이스를 구현한 클래스가 없어도 사용이 가능한 static method이다.
인용
'Language > Java' 카테고리의 다른 글
익명 클래스 (Anonymous Class) (0) | 2023.09.15 |
---|---|
팩토리 메서드 패턴과 Java Reflection (0) | 2023.09.15 |
부모가 될 수 없는 클래스 - String (0) | 2023.09.15 |
추상클래스 (0) | 2023.09.15 |
super() 생성자와 불변 객체 (0) | 2023.09.14 |