Skip to content

Latest commit

 

History

History
285 lines (209 loc) · 23.3 KB

File metadata and controls

285 lines (209 loc) · 23.3 KB
  • ✅ Q: 객체지향의 5대 원칙인 SOLID에 대해서 설명해주세요. (depth 1)

    1. 단일 책임 원칙 (Single Responsibility Principle, SRP): 한 클래스는 하나의 책임만 가져야 합니다. 즉, 클래스는 변경되어야 하는 이유가 단 하나여야 합니다.

      • 예시: Report 클래스가 데이터를 가져오는 역할과 데이터를 PDF 형식으로 출력하는 두 가지 책임을 갖는 경우, 이를 DataFetcherPDFReportGenerator로 분리할 수 있습니다. DataFetcher는 데이터를 가져오는 역할만 수행하고, PDFReportGenerator는 데이터를 PDF 형식으로 출력하는 역할만 수행합니다.
    2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP): 소프트웨어의 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만, 수정에 대해서는 닫혀 있어야 합니다.

      • 예시: 만약 새로운 결제 방식을 추가해야 할 때, 기존의 PaymentProcessor 클래스를 수정하는 대신에, PaymentProcessor를 확장하는 새로운 클래스를 만들어서 사용합니다. 이렇게 하면 기존 코드에 영향을 주지 않으면서 새로운 기능을 추가할 수 있습니다.
    3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP): 하위 타입은 언제나 상위 타입으로 교체될 수 있어야 합니다 (즉, 하위 클래스는 그 상위 클래스의 행위를 완전히 대체할 수 있어야 합니다).

      • 예시: Bird 클래스에서 파생된 DuckOstrich 클래스가 있을 때, DuckOstrich는 모두 Bird로 취급될 수 있어야 합니다. 하지만 Ostrich는 날 수 없으므로 Bird 클래스의 fly 메소드를 Ostrich에서 사용하면 문제가 생길 수 있습니다. 이는 LSP를 위반하는 것입니다.
    4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP): 클라이언트는 자신이 이용하지 않는 메소드에 의존하도록 강요받아서는 안 됩니다.

      • 예시: IMultiFunctionPrinter 인터페이스에 print, scan, fax 등의 메소드가 모두 포함된 경우, SimplePrinter 클래스는 print 기능만 필요하지만 faxscan 메소드도 구현해야 합니다. 이 경우, 인터페이스를 IPrinter, IScanner, IFax 등으로 분리하여 각 클래스가 필요한 인터페이스만 구현하게 할 수 있습니다.

      • 디폴트 메소드 사용

        디폴트 메소드를 사용하면 인터페이스에 메소드의 기본 구현을 제공할 수 있으며, 이를 통해 클래스가 이 메소드를 오버라이드하지 않더라도 인터페이스를 구현할 수 있습니다. 이 방법은 인터페이스를 변경하면서 기존 구현을 깨지 않는 방법으로, 특히 API를 유지하면서 기능을 확장해야 할 때 유용합니다.

        예를 들어, IMultiFunctionPrinter 인터페이스에 print, scan, fax 메소드가 있다고 할 때, print 메소드는 모든 프린터에서 필수적이지만 scanfax는 옵셔널한 기능일 수 있습니다. 이 경우 scanfax에 대한 디폴트 구현을 제공함으로써 각 프린터 클래스가 필요한 기능만 구현하도록 할 수 있습니다.

      public interface IMultiFunctionPrinter {
          void print();
      
          default void scan() {
              // 기본 스캔 기능 구현 (또는 비어있는 구현)
          }
      
          default void fax() {
              // 기본 팩스 기능 구현 (또는 비어있는 구현)
          }
      }

      이렇게 하면 SimplePrinter 클래스는 print 메소드만 구현하고, scanfax는 디폴트 구현을 사용할 수 있습니다. 이 방식은 ISP를 적용하면서도 불필요한 구현 부담을 줄일 수 있는 좋은 방법입니다. 그러나 디폴트 메소드의 남용은 인터페이스의 명확성을 해칠 수 있으므로 주의해서 사용해야 합니다.

    5. 의존관계 역전 원칙 (Dependency Inversion Principle, DIP): 고수준 모듈은 저수준 모듈에 의존하면 안 되며, 둘 다 추상화에 의존해야 합니다. 추상화는 세부 사항에 의존해서는 안 되며, 세부 사항이 추상화에 의존해야 합니다.

      • 예시: BookStore 클래스가 MySQLDatabase 클래스에 직접 의존하는 경우, 데이터베이스를 변경하려면 BookStore 클래스도 수정해야 합니다. 대신 IDatabase 인터페이스를 만들고, BookStoreIDatabase에만 의존하도록 합니다. 그리고 MySQLDatabaseIDatabase를 구현합니다. 이렇게 하면 데이터베이스가 변경되어도 BookStore 클래스를 수정할 필요가 없습니다.
      • 고수준 모듈과 저수준 모듈
        • 고수준 모듈(High-level Module): 비즈니스 로직 또는 사용자의 요구를 반영하는 모듈로, 시스템의 전략적인 부분과 직접 관련이 있습니다. 이 모듈은 시스템의 핵심 기능을 정의하고, 어떤 작업을 수행할지에 대한 '정책(policy)'을 담고 있습니다. 고수준 모듈은 일반적으로 추상적이며, 구체적인 세부 사항에 대해서는 다루지 않습니다.
        • 저수준 모듈(Low-level Module): 시스템의 구체적인 세부 사항을 담당하는 모듈로, 데이터 관리, 기기의 입출력 처리, 데이터베이스 연결 등과 같은 기능을 수행합니다. 저수준 모듈은 실제로 데이터를 처리하는 방법, 데이터를 저장하거나 읽는 방법과 같은 '세부 사항(details)'에 초점을 맞춥니다.
        • 의존관계 역전 원칙에서는, 이 두 유형의 모듈 간의 전통적인 의존성(고수준 모듈이 저수준 모듈에 의존하는 형태)을 뒤집습니다. 즉, 고수준 모듈은 저수준 모듈의 구현에 직접적으로 의존하지 않고, 둘 모두 추상화(abstraction)에 의존하게 됩니다. 이를 통해 고수준 모듈은 저수준 모듈의 변경으로부터 보호받고, 시스템의 유연성과 유지보수성이 향상됩니다.
        • 예를 들어, 데이터베이스 액세스를 처리하는 '고수준'의 비즈니스 로직 모듈이 있을 때, 이 모듈이 직접 MySQL, PostgreSQL과 같은 특정 데이터베이스 기술('저수준' 모듈)에 의존하는 대신, '데이터베이스 액세스'라는 추상화 인터페이스에 의존하도록 합니다. 이렇게 하면 나중에 데이터베이스 시스템을 변경하더라도 고수준 모듈을 수정할 필요가 없습니다.
  •  Q: 자바의 지네릭에 대해서 설명해주세요. (depth 2)

    자바의 제네릭(Generic)은 코드의 재사용성을 높이고, 컴파일 시간에 타입 안정성을 제공하며, 캐스팅 문제를 줄이는 데 도움을 줍니다. 제네릭을 사용하면 클래스나 메서드를 정의할 때 타입(Type)을 파라미터로 전달할 수 있어, 다양한 타입에 유연하게 대응할 수 있습니다. 이를 통해 타입 안전성을 확보하고 런타임 오류의 가능성을 줄일 수 있습니다.

    지네릭을 사용하지 않는 경우:

    List list = new ArrayList();
    list.add("hello");
    list.add(1); // 이 부분은 런타임 에러를 일으킬 수 있습니다.
    
    String s = (String) list.get(0); // 캐스팅 필요
    Integer i = (Integer) list.get(1); // 캐스팅 필요, 런타임 에러 가능

    이 경우, 리스트에 어떤 타입의 객체도 추가할 수 있지만, 객체를 가져올 때 캐스팅이 필요하며, 잘못된 타입으로 캐스팅하면 런타임 에러가 발생할 수 있습니다.

    지네릭을 사용하는 경우:

    List<String> list = new ArrayList<>();
    list.add("hello");
    // list.add(1); // 컴파일 에러 발생
    
    String s = list.get(0); // 캐스팅 필요 없음

    제네릭을 사용하면, 컴파일 시점에 타입 체크를 할 수 있습니다. 따라서, 리스트에는 String 타입의 객체만 추가할 수 있고, 객체를 가져올 때 캐스팅이 필요 없으며, 타입 안전성이 보장됩니다.

    지네릭을 사용하는 커스텀 클래스 예제:

    public class Box<T> {
        private T t;
    
        public void set(T t) {
            this.t = t;
        }
    
        public T get() {
            return t;
        }
    
        public static void main(String[] args) {
            Box<String> stringBox = new Box<>();
            stringBox.set("hello world");
    
            Box<Integer> integerBox = new Box<>();
            integerBox.set(123);
    
            String str = stringBox.get(); // 캐스팅 필요 없음
            Integer intVal = integerBox.get(); // 캐스팅 필요 없음
        }
    }

    이 예제에서 Box 클래스는 제네릭 타입 T를 사용합니다. 이를 통해 Box 클래스의 인스턴스를 생성할 때, 저장할 객체의 타입을 지정할 수 있습니다. 이러한 방식으로 Box 클래스는 다양한 타입을 유연하게 처리할 수 있습니다.

    제네릭을 사용하면 코드의 타입 안전성을 높이고, 타입 캐스팅에 대한 오류 가능성을 줄이며, 코드의 가독성과 재사용성을 개선할 수 있습니다.

  •  Q: 자바의 GC 처리 과정에 대해서 설명해주세요. (depth 2)

    • java의 가비지 컬렉션(Garbage Collection) 처리 방법

      • GC 작업을 수행하는 가비지 콜렉터가 하는 일
        1. 메모리 할당
        2. 사용 중인 메모리 인식
        3. 미사용 메모리 인식
      • Stop the World
        • 자바 애플리케이션은 GC 실행 시 GC 실행 스레드를 제외한 모든 스레드를 멈추고, GC 완료 후 다시 스레드들을 실행 상태로 변경
        • Stop the World는 모든 애플리케이션 스레드의 작업이 멈추는 상태
        • 어떤 GC 알고리즘을 사용하더라도, Stop the World는 불가피하며 최소화하기 위해 GC 튜닝을 진행

      !https://github.com/WeareSoft/tech-interview/raw/master/contents/images/JVMHeap.png

      • 가비지 콜렉터가 들르는 메모리 영역은 Young 영역에 포함되는 Eden, Survivor1, Survivor2와 Old 영역 (Permanent 영역은 Java 1.8 부터 제거)
      • Young 영역에 있는 객체는 각 하위 영역이 가득 차면 Miner GC가 동작하여 더이상 참조되지 않는 객체 제거
      • Old 영역에 있는 객체는 영역이 가득 차면 Major GC(Full GC)가 동작하여 더이상 참조되지 않는 객체 제거
      • 동작 과정
        • 객체를 최초 생성하면 Young 영역 중 Eden 영역에 위치
        • Eden 영역에서 Miner GC 발생 시, 참조 중인 객체라면 Survivor1로 이동
        • Survivor1에서 Miner GC 발생 시, 참조 중인 객체라면 Survivor2로 이동
        • Survivor2에서 Miner GC 발생 시, 참조 중인 객체라면 다시 Survivor1 영역으로 이동 (Survivor1 <--> 2 반복)
        • Survivor 영역이 가득 차거나 Young 영역에서 오래 살아남은 객체는 Old 영역으로 이동
          • '오래'의 기준은 객체마다 age bit라는 것을 가지고 있는데 이는 Miner GC에서 살아남은 횟수를 기록하는 값
        • Old 영역에 있는 객체는 Major GC가 발생했을 때 참조 여부에 따라 공간이 유지되거나 제거

    java9의 default GC

    G1GC

    • Garbage First Garbage Collector
    • Java 9, 10의 default GC
    • Java 7, 8의 default GC는 ParallelGC
      • 활성화 옵션 XX:+UseG1GC
    • 큰 메모리를 가진 멀티 프로세스 시스템에서 사용하기 위해 개발된 GC
    • Stop the World 최소화 목적(실시간 GC는 불가능)
    • 통계를 계산하면서 GC 작업량 조절
    • G1을 사용하면 좋은 경우
      • Java heap의 50% 이상이 라이브 데이터일 때
      • GC가 너무 오래 걸릴 때(0.5초 ~ 1초)

    G1GC 특징

    • 다른 GC와 다르게 전체 힙 공간을 체스판처럼 여러 영역으로 나누어 관리
      • GC 발생 시, 전체 힙이 아닌 일부 영역에서만 GC 수행
      • 따라서 큰 힙 크기를 가질 경우 유리
    • 영역의 참조를 관리할 목적으로 remember set을 만들어 사용(set은 전체 힙의 5% 미만 크기)

    !https://github.com/WeareSoft/tech-interview/raw/master/contents/images/g1gc.png

    • 회색 : 빈 영역 / 빨간색 : Eden 영역 / 빨간색 S : Survivor 영역 / 파란색 : Old 영역(파란색 H는 크기가 커서 여러 영역 차지하는 객체)
    • Young과 Old 영역의 구분 없이 사용
    • 비어있는 영역에 새로 할당한 객체 위치
    • STW 시간 최소화를 위해 병렬 GC 작업 처리(각 스레드가 자신만의 영역을 잡고 작업)

    G1GC 동작 과정

    • Young 영역(Eden, Survivor)에서는 Young GC가 수행되며 Eden, Survivor 영역 이동
      • 옮기면서 비워진 영역은 사용 가능한 빈 영역으로 되돌림
    • Full GC 수행 단계
      • Initial Mark -> Root Region Scan -> Concurrent Mark -> Remark -> Cleanup -> Copy
      • Initial Mark
        • Old 지역에 존재하는 객체가 참조하는 Survivor 영역 탐색
        • StapTheWorld 발생
      • Root Region Scan
        • 이전 단계에서 찾은 영역에 대한 GC 대상 객체 스캔
      • Concurrent Mark
        • 전체 힙 영역 스캔
        • GC 대상 객체가 없는 영역은 이후 단계에서 제외
      • Remark
        • StapTheWorld가 발생하며 최종으로 GC 대상에서 제외할 객체 식별
      • Cleanup
        • StapTheWorld가 발생하며 제거할 객체가 가장 많은 지역에서 GC 수행
        • 완료 후, 완전히 비워진 영역을 재사용하기 위해 Freelist에 추가
      • Copy
        • GC 대상이었지만 Cleanup 단계에서 완전히 비워지지 않은 지역의 남은 객체를 새로운 영역에 복사하여 조각 모음(Compaction) 작업 수행

    G1GC vs ParallelGC

    • ParallelGC는 Old Generation 영역에서만 Full GC(공간 재확보 및 조각 모음) 수행
    • G1은 더 짧은 주기의 Full GC 작업을 수행하여 전체적인 처리량이 줄어드는 대신 일시 정지 시간을 크게 단축

    G1GC vs CMS

    • CMS는 Old Generation의 조각 모음(Compaction)을 하지 않으므로 Full GC 시간이 길어지는 문제 발생
    • CMS는 Old 영역 사용량이 특정 기준치 값 넘어가면 수행
    • G1GC는 Old 영역에서 GC 발생 시, 힙 사용량이 특정 기준치 값을 넘어가면 실행

    https://github.com/WeareSoft/tech-interview/blob/master/contents/java.md

  • ✅ JAVA 동시성 문제 (depth 1)

    synchronized 에 대해 아시나요?

    synchronized 키워드는 해당 메서드나 블록을 한번에 한 스레드씩 수행하도록 보장한다. 즉 한 스레드가 변경하는 중이라서 상태가 일관되지 않은 순간의 객체를 다른 스레드가 보지 못하게 막는 용도로만 생각한다.

    공유 중인 가변 데이터 동기화에 대하여

    동기화를 제대로 사용하지 못하면 어떤 메서드도 이 객체의 상태가 일관되지 않은 순간을 볼 수 없고 스레드가 만든 변화를 다른 스레드에서 확인하지 못할 수 있다.

    동기화는 배타적 실행뿐 아니라 스레드 사이의 안정적인 통신에 꼭 필요하다.

    공유 중인 가변 데이터를 비록 원자적으로 읽고 쓸 수 있을지라도 동기화에 실패하면 처참한 결과로 이어질 수 있다.

    가변 데이터는 단일 스레드에서만 쓰도록 하자.

    스레드가 데이터를 다 수정한 후 다른 스레드에 공유할 때는 해당 객체에서 공유하는 부분만 동기화해도 된다. 그러면 그 객체를 다시 수정할 일이 생기기 전까지 다른 스레드들은 동기화 없이 자유롭게 값을 읽어갈 수 있다.

    클래스 초기화 과정에서 객체를 정적 필드, volatile (Main Memory에 저장하겠다라는 것을 명시하는 것)필드, final 필드, 혹은 보통의 락을 통해 접근하는 필드에 저장해도 된다.

    여러 스레드가 가변 데이터를 공유한다면 그 데이터를 읽고 쓰는 동작은 반드시 동기화해야 한다. 동기화하지 않으면 한 스레드가 수행한 변경을 다른 스레드가 보지 못할 수도 있다. 공유되는 가변 데이터를 동기화하는 데 실패하면 응답 불가 상태에 빠지거나 안전 실패로 이어질 수 있다. 이는 디버깅 난이도가 가장 높은 문제에 속한다. 간헐적이거나 특정 타이밍에만 발생할 수도 있고, VM에 따라 현상이 달라지기도 한다. 배타적 실행은 필요 없고 스레드 끼리의 통신만 필요하다면 volatile 한정자만으로 동기화할 수 있다. 다만 올바로 사용하기가 까다롭다.

  • ✅ JDK와 JRE의 차이점은 무엇입니까? (depth 0)

    JDK(Java Development Kit)는 Java 개발을 위해 필요한 도구들의 집합입니다.컴파일러, 디버거, 개발 도구 등을 포함하고 있습니다. JRE(Java Runtime Environment)는 Java 애플리케이션을 실행하기 위한 런타임 환경입니다. JVM(Java Virtual Machine), 클래스 라이브러리, 실행환경 등을 포함하고 있습니다.JDK는 JRE를 포함하고 있으므로, JDK는 개발자용으로 JRE를 포함한 모든 도구를 제공합니다.
    
  •  Equals 메서드의 결과값으로 나온 두 개체가 동일한 hashCoded 값을 가지고 있어야 참입니까? (depth 2)

    아닙니다. hashCode()는 객체의 해시코드를 반환하는 메서드입니다.

    equals()는 객체들간의 동등성을 비교하는 메서드입니다.

    두 객체의 hasCode()값이 동일하다고 해서 equals()가 항상 true인것은 아닙니다.

    그러나 두 객체의 equals()로 비교했을 때 trure를 반환한다면 두 객체의 hashCode()값이 같아야합니다.

    링크: https://steady-coding.tistory.com/604

    두 객체가 equals()에 의해 동일하다면, 두 객체의 hashCode() 값도 일치해야 한다.두 객체가 equals()에 의해 동일하지 않다면, 두 객체의 hashCode() 값은 일치하지 않아도 된다.
    
  • ✅ 데몬 스레드가 정확히 무엇입니까? (depth 1)

    데몬 스레드는 백그라운드에서 실행되는 스레드로, 다른 모든 일반 스레드가 종료되면 자동으로 종료됩니다. 주로 보조적인 작업을 수행하며, 예를 들어 가비지 컬렉션 등을 처리하는 데 사용됩니다
    
  • ✅ 캡슐화가 무엇인지 예시(또는 프로젝트 경험)과 함께 말씀해주세요.

    • 캡슐화는 객체지향 프로그래밍의 핵심 개념 중 하나입니다.
    • 캡슐화는 객체 상태가 외부로부터 숨겨진 상황에서 이 상태에 접근하는 public 메서드만 노출하는 기법입니다.
    • 캡슐화는 각 객체가 클래스 내에서 객체의 상태를 비공개(private)으로 유지할 때 성립됩니다.
    • 캡슐화는 정보 은닉 메커니즘이라고도 합니다.
    • 캡슐화는 느슨한 결합, 재사용성, 보안 및 테스트하기 쉬운 코드와 같은 여러가지 즁요한 이점을 제공합니다.
    • 자바에서 캡슐화는 접근제어자로 구현 가능합니다.
  • ✅ 상속이 무엇인지 예시(또는 프로젝트 경험)과 함께 말씀해주세요.

    • 상속은 객체지향 프로그래밍의 핵심 개념 중 하나입니다.
    • 상속을 통해 다른 객체를 기반으로 하는 새로운 객체를 만들 수 있습니다.
    • 상속은 객체가 다른 객체의 코드를 재사용할 수 있도록 허용하여 코드의 재사용성을 유지합니다. 또한 각 객체만의 로직도 추가할 수 있습니다.
    • 상속은 IS-A 관계라고 하며 부모-자녀 관계라고도 합니다.
    • 자바에서 상속은 extends 키워드로 구현할 수 있습니다.
    • 상속된 객체는 슈퍼클래스(부모클래스)라고 하고, 슈퍼클래스를 상속받은 객체는 서브클래스(자식클래스)라고 합니다.
    • 자바에서는 여러 개의 클래스를 상속할 수 없습니다.
  • ✅ 추상화가 무엇인지 예시(또는 프로젝트 경험)과 함께 말씀해주세요.

    • 추상화는 객체지향 프로그래밍의 핵심 개념 중 하나입니다.
    • 추상화는 사용자와 관련 있는 내용만 노출하고 나머지 세부 내용은 숨기는 개념 입니다.
    • 추상화를 통해 사용자는 애플리케이션이 일을 수행하는 방법이 아니라 애플리케이션이 수행하는 일 자체에 집중할 수 있습니다.
      • 내용을 노출하는 복잡성을 줄이고 코드의 재사용성을 높이며 코드 중복을 방지하고 낮은 결합도와 높은 응집도를 유지합니다.
      • 또한 중요한 내용만 공개하여 애플리케이션의 보안과 재량권을 유지합니다.
  • ✅ 다형성이 무엇인지 예시(또는 프로젝트 경험)과 함께 말씀해주세요.

    • 다형성은 객체지향 프로그래밍의 핵심 개념 중 하나입니다.
    • 다형성을 뜻하는 ‘polymorphism’ 이라는 단어는 그리스어로 ’많은 형태‘를 의미합니다.
    • 다형성은 때에 따라 객체가 다르게 동작하거나 다른 방법으로 동작하도록 합니다.
    • 다형성은 메서드 오버로딩이나, IS-A 관계의 경우 메서드 오버라이딩을 통해 형성될 수 있습니다.

    메서드 오버로딩 ⇒ 컴파일 타임 다형성

    • 여러 개의 메서드가 동일한 이름을 가지고 있지만 매개변수가 다른 경우
    • 컴파일러가 오버로드 된 메서드 가운데 어떤 형식을 호출할지 컴파일 타임에 식별
      • 이 때 오버로드 된 메서드의 형태에 따라 객체는 다르게 동작

    메서드 오버라이딩 ⇒ 런타임 다형성, 동적 메서드 디스패치

    • IS-A 관계일 때 주로 사용
    • 인터페이스의 구현으로 각 클래스는 특화된 동작을 수행하기 위해 인터페이스의 메서드를 오버라이드
    • 보통 여러 가지 메서드를 포함하는 인터페이스 구현으로 시작하며, 각 클래스는 특화된 동작을 수행하기 위해 인터페이스에 있는 메서드를 오버라이드합니다.
      • 이때 다형성이 타입에 대한 혼란 없이 이 클래스들을 부모 인터페이스와 똑같이 사용할 수 있게 합니다.
      • 런타임에 자바가 이러한 클래스를 구별할 수 있고 어느 클래스가 사용 되는지 알고 있기 때문에 가능한 일입니다.