[북스터디]자바 기술 면접 빈출 질문(2) 6가지 인터페이스 & 메서드 하이딩

Wang_Seok_Hyeon·2023년 9월 24일
0
post-thumbnail

Intro.

이번은 2 번째 질문 유형에 관한 시간으로 다음으로 디자인 패턴에 관한 질문을 끝으로!! 빅오에 관한 내용의 정리로 넘어갈 예정이다. 다만, 해당 내용들이 방대하지만 한 주에 하나씩 챕터를 끝내고자! 생략되는 부분이 많을 수 있는데, 이 부분은 내가 열심히... 생략 없이 풀로 채우는 방식도 고려해볼까 싶기도 하다.

어쨋든! 인터페이스와 추상 클래스의 주요 차이, 오버라이딩과 유사하지만 다른! 메서드 하이딩에 관한 질문을 정리해 보자!

1. 자바 인터페이스 안에 abstarct가 아닌 메서드를 포함할 수 있는가?

자바8전까지 자바 인터페이스 안에 abstract가 아닌 메서드를 사용할 수 없었습니다. 인터페이스의 모든 메서드는 암묵적으로 public 및 abstract 메서드였습니다. 그러나 자바 8부터는 인터페이스에 추가할 수 있는 새로운 유형의 메서드가 있습니다.
실제로 자바8부터 구현된 메서드를 인터페이스에 직접 추가할 수 있습니다. default 및 static 키워드를 사용해서 이 작업을 수행할 수 있습니다. default 키워드는 기본(default), 방어(defender), 확장(extension) 메서드라고 불리는 메서드를 인터페이스에 포함하는 키워드로서 자바 8에 도입되었습니다. 이 기능의 주요 목표는 이전 버전과의 호환성을 보장하면서 기존의 인터페이스를 발전시키는 것입니다. JDK 자체는 default 메서드를 사용해 기존 코드를 망가뜨리지 않으면서 새로운 기능을 추가하여 자바를 발전시킵니다. 반면 인터페이스의 static 메서드는 default 메서드와 매우 유사하지만, 이러한 인터페이스를 구현하는 클래스에서 static 메서드는 오버라이드 할 수 없다는 차이점이 있습니다. static 메서드는 객체에 묶인 것이 아니기 때문에 인터페이스 이름과 메서드 이름 사이에 점을 찍어서 호출할 수 있ㅅ브니다. 또한 static 메서드는 다른 default 또는 static 메서드 내에서 호출할 수 있습니다.

아래와 같은 예시로 이야기 할 수 있습니다.

증기 자동차와 같은 이동 수단을 표현하기 위한 인터페이스가 있다고 가정하겠습니다. 여기서 증기 자동차는 오래된 자동차인 만큼 오래된 코드를 대표하는 예제입니다.

    public interface Vehicle {
        public void speedUp();
        public void slowDown();
    }

다음과 같은 SteamCar 클래스를 통해 다양한 종류의 증기 자동차가 제작되었다고 하겠습니다.

    public class SteamCar implements Vehicle {
        private String name;
    
        public SteamCar(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        @Override
        public void speedUp() {
            System.out.println("Speed up the steam car ...");
        }
    
        @Override
        public void slowDown() {
            System.out.println("Slow down the steam car ...");
        }
    }

SteamCar 클래스가 Vehicle 인터페이스를 구현하므로 이 클래스는 speedUp 메서드와 slowDown 메서드를 오버라이드합니다. 얼마 후 휘발유 자동차가 발명되고 사람들은 마력과 연료 소비에 관심을 가지기 시작합니다. 따라서 이 예제 코드는 휘발유 자동차도 지원할 수 있도록 진화해야 합니다. 연비를 계산하기 위해 다음과 같은 default 메서드인 computeCousumption을 추가하여 Vehicle 인터페이스를 발전시킬 수 있습니다.

    public interface Vehicle {
        public void speedUp();
        public void slowDown();
    		default double computeConsumption(int fuel, int distance, int horsePower) {
            // 연비 계산을 가장한 임의의 계산식입니다.
            return Math.random() * 10d;
        }
    }

이렇게 Vehicle 인터페이스를 발전시켜도 StreamCar 클래스와의 호환성은 깨지지 않습니다.

시간이 더 지나고 전기 자동차가 발명 되었습니다. 전기 자동차의 연료 소비량은 휘발유 자동차와는 다르게 계산됩니다. 하지만 계산 공식은 연료, 거리, 그리고 마력 등 같은 조건을 사용합니다. 이에 따라 ElectricCar 클래스는 computeConsumption 메서드를 다음과 같이 오버라이드 합니다.

    public class ElectricCar implements Vehicle {
        private String name;
        private int horsePower;
    
        public ElectricCar(String name, int horsePower) {
            this.name = name;
            this.horsePower = horsePower;
        }
    
        public String getName() {
            return name;
        }
    
        public int getHorsePower() {
            return horsePower;
        }
    
        @Override
        public void speedUp() {
            System.out.println("Speed up the electric car ...");
        }
    
        @Override
        public void slowDown() {
            System.out.println("Slow down the electric car ...");
        }
    
        @Override
        public double computeConsumption(int fuel, int distance, int horsePower) {
            return Math.random() * 60d / Math.pow(Math.random(), 3);
        }
    }

예제에서 보듯이 default 메서드를 오버라이드 하거나 암묵적 구현을 사용할 수 잇습니다. 마지막으로 인터페이스가 증기, 휘발유, 그리고 전기 자동차를 지원하므로 인터페이스가 어떤 종류의 자동차를 지원하는지 설명하는 기능을 추가하겠습니다. Vehicle 인터페이스에 static 메서드인 description을 다음과 같이 추가합니다.

    public interface Vehicle {
        public void speedUp();
        public void slowDown();
    		default double computeConsumption(int fuel, int distance, int horsePower) {
            // 연비 계산을 가장한 임의의 계산식입니다.
            return Math.random() * 10d;
        }
    		static void description() {
            System.out.println("This interface control steam,and electric cars");
        }
    }

이 static 메서드는 특정 자동차 유형에 종속된 것이 아니기 때문에 Vehice.description 을 통해 직접 호출할 수 있습니다. 아래는 이를 출력하기 위한 Main과 결과입니다.

    public class Main {
        public static void main(String[] args) {
            Vehicle.description();
    
            ElectricCar ec = new ElectricCar("Audi", 150);
            System.out.println("Electric car consume: "
              + String.format("%.1f", ec.computeConsumption(250, 50, ec.getHorsePower())) + " kW/h");
        }
    }
    This interface control steam, and electric cars
    Electric car consume: 9.4 kW/h
    

2. default 메서드를 가지는 인터페이스와 추상 클래스의 주요 차이점은 무엇인가?

자바8 인터페이스와 추상 클래스의 주요 차이점으로는 인터페이스가 생성자를 지원하지 않는 반면 추상 클래스는 생성자를 가질 수 있다는 점을 꼽을 수 있습니다. 즉, 추상 클래스는 상태를 가질 수 있지만, 이터페이스는 상태를 가질 수 없습니다. 또한 인터페이스의 주요 목적은 완전히 추상화된 상태를 구현하는 것이지만, 추상 클래스는 부분적인 추상화를 위한 것입니다.
인터페이스는 스스로 어떤 것도 하지 않는 완전한 추상화를 목표로 하지만, 구현할 때 어떻게 동작할 것인지를 명시하도록 설계되어 있습니다. default 메서드는 클라이언트 코드에 영향을 미치지 않고 상태를 변경하지 않으면서 인터페이스에 추가 기능을 더하는 방법을 나타냅니다. default 메서드를 다른 목적으로 사용해서는 안됩니다.
또 다른 차이점은 추상 클래스는 추상 메서드가 없어도 아무런 문제가 없지만, 인터페이스가 default 메서드만 가지는 것은 안티패턴이라는 사실입니다. 이것은 유틸리티 클래스(기능만이 담긴 클래스)를 대체하려고 인터페이스를 만든 것이나 다름 없으며 이렇게 하면 구현이라는 인터페이스의 주요 목적을 무력화시킵니다.

3. 추상 클래스와 인터페이스의 주요 차이점은 무엇인가?

자바8 이전까지 추상 클래스와 인터페으스의 주요 차이점은 추상 클래스는 abstract가 아닌 메서드를 포함할 수 있는 반면에 인터페이스는 그러한 메서드를 포함할 숭 벗다는 사실입니다. 자바 8부터는 추상 클래스는 생성자와 상태를 가질 수 있는 반면에 인터페이스는 이들 중 하나를 가질 수 없다는 주요 차이점이 있습니다.

4. abstarct 메서드가 없는 추상 클래스를 만들 수 있는가?

네 만들 수 있습니다. abstract 키워드를 클래스에 추가하면 추상 클래스가 됩니다. 추상 클래스는 인스턴스화할 수는 없지만 생성자와 abstract가 아닌 메서드만 있어도 괜찮습니다.

5. 추상이면서 동시에 final인 클래스를 만들 수 있는가?

final 클래스는 서브클래스화 되거나 상속할 수 없습니다. 추상 클래스는 확장을 통해서만 사용할 수 있습니다. 그러므로 final과 추상은 반대되는 개념입니다. 이것은 두 가지를 ㄷ동시에 같은 클래스에 적용할 수 없다는 것을 의미합니다. 컴파일러에서 오류가 발생합니다.

6. 자바에서 메서드 하이딩이란 무엇인가?

> 메서드 하이딩(method hiding)은 정적 메서드에 한정됩니다. 더 정확하게 말해서 슈퍼클래스와 서브클래스에서 같은 시그니처와 이름을 가진 2개의 static 메서드를 선언한다면 두 메서드는 서로를 숨길 것(실제 참조하는 클래스를 찾는 ㅇ것이 아니라 컴파일에 결정된 클래스의 해당 메서드를 호출)입니다.
슈퍼클래스에서 메서드를 호출하면 슈퍼클래스의 static 메서드가 호출되고, 같은 메서드를 서브클래스에서 호출하면 서브클래스의 static 메서드가 호출됩니다. static 메서드가 다형이 될 수 없기 때문에 하이딩은 오버라이딩과 다릅니다.
> 

세부적인 내용을 위해 아래와 같은 예제 코드를 작성할 수 있습니다.
static 메서드 move를 갖는 Vehicle 슈퍼클래스가 있다고 가정하겠습니다.

```java
public class Vehicle {
    public static void move() {
        System.out.println("Moving a vehicle");
    }
}
```

이제 동일한 static 메서드를 가지는 서브클래스 Car를 살펴보겠습니다.

```java
public class Car extends Vehicle {
    // 이 메서드는 Vehicle의 move() 메서드를 숨깁니다.
    public static void move() {
        System.out.println("Moving a car");
    }
}
```

다음으로 main 메서드에서 두 가지의 static 메서드를 호출해보겠습니다.

```java
public class Main {
    public static void main(String[] args) {
        Vehicle.move(); // Vehicle의 move() 호출
        Car.move();     // Car의 move() 호출
    }
}
```

출력 결과는 이 두 가지의 static 메서드가 서로를 하이딩한다는 것을 나타냅니다.

```
Moving a vehicle
Moving a car
```

static 메서드를 호출할 때 클래스 이름을 통해 호출한다는 점에 주목하세요. static 메서드를 인스턴스에서 호출하는 것은 `매우 나쁜 관행`이므로 인터뷰 중에는 이러한 방식을 사용하면 안 됩니다.

Outro.

책의 한 챕터를 다루는 데 오랜 시간을 들이고 있지만 덕분에 책을 여러 번 보게 되고 개념들 중 내가 지금 적고 있는 단어에 대해서 내가 얼마나 알고 있는지를 확인하며 발전해 가는 걸 느낀다. 이를 통해 더 성장하는 개발자가 되자!

profile
하루 하루 즐겁게

0개의 댓글