Search

20년차 여기어때 출신 백엔드 개발자가 알려주는 CS질문 정답과 해설

안녕하세요, 커널아카데미입니다! 수강생 여러분들에게 최초로 여기어때, 위메프 등 다양한 서비스기업을 거친 20년차 시니어 백엔드 개발자가 전수해주는 기술면접 적중 CS질문의 정답과 해설을 여러분들에게만 무료로 공개합니다. 지금 개발자 이직을 준비하고 있다면 지금 바로 확인해보세요!

여기서 잠깐!

신입도 아니고 이직하는데 CS질문에 대해 왜 대비해야 하나요?

C.S.(Computer Science)

경력직 지원 시에는 CS 질문이 신입 때처럼 비중 있게 다뤄지지 않을 수 있습니다. 하지만 이는 경력자라면 이력서와 포트폴리오에 녹아 있는 경험을 통해 이미 답변할 수 있다는 전제가 있기 때문입니다. 즉, 별도의 키워드로 묻지 않을 뿐, 실무에서 CS 개념을 얼마나 잘 적용했는지를 평가합니다. 이 때문에 단순히 개념을 아는 것만으로는 충분하지 않습니다. 실제 프로젝트에서 CS 개념을 어떻게 활용했는지 설명하는 능력이 중요합니다. 예를 들어, 데이터베이스 최적화나 네트워크 구조를 이해하고 그 지식을 어떻게 적용했는지 말할 수 있어야 하죠. CS 개념은 이론에 그치는 것이 아니라, 실무에서의 문제 해결에 직결됩니다. 그리고 면접에서는 이 이해도가 드러납니다. 그래서 경력이 있는 주니어 개발자라도 기본 CS 질문을 놓치지 않고 철저히 준비해야 합니다. 면접에서 예상치 못한 CS 질문에 당황하지 않도록, 기본 개념부터 실무 적용 사례까지 설명하는 연습을 꼭 하시길 바랍니다.

CS질문 정답과 해설

1. 자바 코드라인에 대한 boolean 값과 이유에 대한 설명

System.out.println(new Object() == new Object()); System.out.println(new String("a") == new String("a")); System.out.println("a" == new String("a")); System.out.println("a".equals(new String("a"))); System.out.println("a" == "a");
Java
복사
1.
System.out.println(new Object() == new Object());
이 라인은 두 개의 새로운 Object 인스턴스를 생성하고, 이들의 참조를 비교합니다. 이들은 각각 다른 메모리 위치에 생성되기 때문에 참조가 다르고 결과는 **false*입니다.
2.
System.out.println(new String("a") == new String("a"));
이 라인은 두 개의 새로운 String 인스턴스를 생성하고, 이들의 참조를 비교합니다. 이들은 각각 다른 메모리 위치에 생성되기 때문에 참조가 다르고 결과는 **false*입니다.
3.
System.out.println("a" == new String("a"));
이 라인은 문자열 리터럴 "a"와 새로운 String 인스턴스를 생성하고, 이들의 참조를 비교합니다. 문자열 리터럴은 문자열 풀에 저장되어 재사용되지만, **new String("a")*는 새로운 객체를 생성합니다. 따라서 참조가 다르고 결과는 **false*입니다.
4.
System.out.println("a".equals(new String("a")));
이 라인은 문자열 리터럴 "a"와 새로운 String 인스턴스를 생성하고, 이들의 내용을 비교합니다. .equals() 메소드는 객체의 내용을 비교하므로, 이 경우 문자열의 내용이 동일하기 때문에 결과는 **true*입니다.
5.
System.out.println("a" == "a");
이 라인은 두 개의 문자열 리터럴을 비교합니다. 문자열 리터럴은 컴파일 시점에 문자열 풀에 저장되어 재사용됩니다. 따라서 두 문자열 리터럴은 같은 참조를 가지고 결과는 **true*입니다.

2. JVM이란?

Java Virtual Machine (JVM)은 자바 프로그램을 실행하는 가상 머신입니다. 컴파일된 자바 코드는 JVM에서 실행됩니다. JVM은 명령어를 읽고 해석하며, 실행 중에 메모리 할당과 해제, 가비지 수집 등을 수행합니다. 이렇게 함으로써, 자바 프로그램은 운영체제나 하드웨어에 의존하지 않고, 어디서든 실행될 수 있습니다.
JVM (Java Virtual Machine)은 Java 프로그램을 실행하는 데 사용되는 가상 머신입니다. Java의 핵심 철학 중 하나는 "한 번 작성하고, 어디에서나 실행하라(Write Once, Run Anywhere)"이며, 이를 가능하게 하는 기술이 바로 JVM입니다. JVM은 Java 프로그램을 실행하기 위해 필요한 바이트코드 인터프리터와 런타임 환경을 제공합니다.
JVM의 주요 기능은 다음과 같습니다:
1.
바이트코드 로딩: JVM은 Java 컴파일러가 생성한 .class 파일의 바이트코드를 로드합니다. 바이트코드는 기계어가 아닌, 플랫폼에 독립적인 중간 형식입니다.
2.
바이트코드 검증: 로드된 바이트코드가 올바른지 확인하고 안전한지 검증합니다. 검증 과정에서 문제가 발견되면, JVM은 프로그램 실행을 중지합니다.
3.
바이트코드 인터프리팅: JVM은 바이트코드를 기계어로 변환하고, 실제 시스템에서 실행합니다. 이 과정에서 JIT(Just-In-Time) 컴파일러를 사용해 자주 사용되는 코드를 최적화하여 빠르게 실행할 수 있도록 합니다.
4.
메모리 관리: JVM은 Java 프로그램의 메모리 관리를 담당합니다. JVM은 메모리 할당 및 가비지 컬렉션을 수행하여 사용되지 않는 메모리를 회수하고, 메모리 누수를 방지합니다.
5.
런타임 환경: JVM은 Java 프로그램이 실행되는 동안 필요한 라이브러리, 클래스 로더, 스레드 관리, 보안 기능 등을 제공합니다.
JVM은 여러 플랫폼에서 동일한 바이트코드를 실행할 수 있도록 설계되어 있습니다. 이는 Java 프로그램이 플랫폼 독립적으로 작동하게 하며, 개발자가 다양한 환경에서 동일한 코드를 실행할 수 있도록 지원합니다. 이러한 이유로 Java는 웹 서버, 모바일 애플리케이션, 기업용 소프트웨어 등 다양한 분야에서 널리 사용되고 있습니다.

3. Interface와 Abstract Class 차이

interfaceabstract class는 Java에서 추상화를 구현하는 두 가지 방법입니다. 이 두 방법은 서로 다른 목적과 특징을 가지고 있습니다.
Interface
1.
인터페이스는 완전히 추상화된 메소드 집합을 정의합니다. 이들 메소드는 기본적으로 public abstract입니다.
2.
인터페이스는 멤버 변수를 가질 수 없습니다. 하지만, 상수(final 변수)는 가질 수 있습니다.
3.
인터페이스는 다중 상속을 지원합니다. 즉, 하나의 클래스는 여러 인터페이스를 구현할 수 있습니다.
4.
인터페이스는 구현된 메소드를 가질 수 없었으나, Java 8부터 default 메소드와 static 메소드를 허용하게 되었습니다.
5.
인터페이스는 주로 다양한 구현을 강제하고 서로 다른 클래스에 공통된 행동을 정의하는 데 사용됩니다.
Abstract Class
1.
추상 클래스는 완전히 추상화된 메소드와 구현된 메소드를 모두 가질 수 있습니다.
2.
추상 클래스는 멤버 변수를 가질 수 있으며, 접근 제어자를 사용하여 변수의 접근 범위를 조절할 수 있습니다.
3.
추상 클래스는 다중 상속을 지원하지 않습니다. 클래스는 오직 하나의 추상 클래스만 상속받을 수 있습니다.
4.
추상 클래스는 구현된 메소드를 가질 수 있어, 일부 공통 기능을 구현하고 상속받는 클래스에서 나머지 기능을 구현할 수 있습니다.
5.
추상 클래스는 공통된 상태(멤버 변수)와 메소드를 상속받는 클래스에 제공하려는 경우에 사용됩니다.
결론적으로, 인터페이스는 여러 클래스에 공통된 행동을 정의할 때 사용되며, 다중 상속을 지원합니다. 반면에 추상 클래스는 공통된 상태와 메소드를 제공하며, 다중 상속을 지원하지 않습니다. 상황과 요구 사항에 따라 적절한 방법을 선택하여 추상화를 구현할 수 있습니다.

4. Maker Interface란?

Marker 인터페이스는 Java에서 특별한 기능이나 메소드를 추가하지 않고, 클래스에 특정 속성이나 행동을 나타내기 위해 사용되는 인터페이스입니다. 이러한 인터페이스는 메소드가 없는 빈 인터페이스로 정의되며, 클래스에 메타데이터를 제공하는 데 사용됩니다.
Marker 인터페이스의 주요 목적은 클래스의 유형을 식별하고, 클래스의 동작을 영향을 주는 것입니다. 클래스가 marker 인터페이스를 구현하면, JVM 또는 다른 코드가 해당 클래스의 유형을 확인하고 특정 동작을 수행할 수 있습니다.
Java 표준 라이브러리에서 사용되는 몇 가지 대표적인 marker 인터페이스는 다음과 같습니다:
1.
java.io.Serializable: 이 인터페이스를 구현하는 클래스의 객체는 직렬화가 가능합니다. 즉, 객체의 상태를 바이트 스트림으로 변환하여 파일에 저장하거나 네트워크로 전송할 수 있습니다.
2.
java.lang.Cloneable: 이 인터페이스를 구현하는 클래스의 객체는 clone() 메소드를 사용하여 복제할 수 있습니다. 객체를 복제할 때, 객체의 새로운 인스턴스를 생성하고 원본 객체와 동일한 값을 가지도록 합니다.
Marker 인터페이스는 특별한 기능을 추가하지 않지만, 클래스에 대한 정보를 전달하는 역할을 합니다. Java 1.5 이후, 애너테이션(annotation)이 도입되면서, marker 인터페이스의 사용이 점차 줄어들고 있습니다. 애너테이션은 코드에 메타데이터를 추가하는 더 강력하고 유연한 방법을 제공합니다.

5. java.lang.Object 존재하는 이유와 제공하는 method에 대한 설명

java.lang.Object는 Java의 모든 클래스의 기본 클래스입니다. Java에서 모든 클래스는 명시적으로 또는 암시적으로 Object 클래스를 상속받습니다. 다른 클래스에서 상속을 명시하지 않으면, 컴파일러는 자동으로 Object 클래스를 상속하게 합니다. 이 때문에 Object 클래스는 Java 객체의 기본적인 동작을 정의하는 역할을 하며, 모든 클래스에 공통적인 메소드를 제공합니다.
java.lang.Object 클래스는 여러 가지 메소드를 제공합니다. 주요 메소드들은 다음과 같습니다:
1.
public final Class<?> getClass(): 객체의 런타임 클래스를 반환합니다. 이를 사용하여 클래스에 대한 메타데이터 정보를 얻을 수 있습니다.
2.
public boolean equals(Object obj): 객체의 동등성을 검사합니다. 기본적으로 객체의 참조를 비교합니다. 하지만, 서브클래스에서 이 메소드를 재정의하여 객체의 상태를 비교하도록 변경할 수 있습니다.
3.
public int hashCode(): 객체의 해시 코드 값을 반환합니다. 이 메소드는 equals() 메소드와 함께 사용되며, 같은 상태를 가진 객체는 동일한 해시 코드를 반환해야 합니다. 서브클래스에서 equals() 메소드를 재정의할 경우, hashCode() 메소드도 함께 재정의해야 합니다.
4.
public String toString(): 객체의 문자열 표현을 반환합니다. 이 메소드는 기본적으로 객체의 클래스 이름과 해시 코드를 포함한 문자열을 반환합니다. 하지만, 서브클래스에서 이 메소드를 재정의하여 객체의 상태를 포함한 문자열을 반환하도록 변경할 수 있습니다.
5.
protected Object clone() throws CloneNotSupportedException: 객체의 복사본을 생성합니다. 이 메소드를 사용하려면 클래스가 java.lang.Cloneable 인터페이스를 구현해야 합니다. 그렇지 않으면, **CloneNotSupportedException*이 발생합니다.
6.
public final void notify()public final void notifyAll()public final void wait()public final void wait(long timeout)public final void wait(long timeout, int nanos): 객체의 동기화와 관련된 메소드들입니다. notify()notifyAll() 메소드는 객체의 대기 중인 스레드를 깨우는 데 사용되며, wait() 메소드는 스레드를 일시 중지하고 객체의 모니터 잠금을 해제하는 데 사용됩니다.

6. Design Pattern 에 정의와 알고있는 패턴에 대한 설명

디자인 패턴(Design Pattern)은 소프트웨어 설계에서 반복적으로 발생하는 문제들에 대한 일반적이고 재사용 가능한 해결책입니다. 이것은 소프트웨어 설계에서 겪는 일반적인 문제에 대한 최선의 연습으로 간주되며, 신속하고 효율적인 솔루션을 구현할 수 있는 템플릿 역할을 합니다. 디자인 패턴은 코드를 재사용하는 것이 아니라, 설계를 재사용하는 아이디어를 제공합니다.
디자인 패턴은 크게 3가지 범주로 분류됩니다.
1.
Creational Patterns (생성 패턴): 객체 생성과 관련된 로직을 캡슐화하여 객체 생성 방식과 클래스 구현을 독립적으로 유지하는 데 도움이 됩니다. 대표적인 생성 패턴에는 Singleton, Factory Method, Abstract Factory, Builder, Prototype 등이 있습니다.
2.
Structural Patterns (구조 패턴): 클래스와 객체를 더 큰 구조로 구성하여 서로간의 관계와 기능을 확장시키는 데 도움이 됩니다. 대표적인 구조 패턴에는 Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy 등이 있습니다.
3.
Behavioral Patterns (행동 패턴): 객체 간의 상호 작용과 책임을 분산하는 방법을 정의하여 객체 간의 통신을 최적화하는 데 도움이 됩니다. 대표적인 행동 패턴에는 Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor 등이 있습니다.

7. Synchronized 에 대한 설명과 함수 정의에 있는 Synchronized 함수안에 사용하는경우의 차이를 설명해주세요

Synchronized는 Java에서 동시성 문제를 해결하기 위해 사용되는 키워드입니다. 여러 스레드가 동시에 공유 자원에 액세스하려고 할 때 발생하는 문제를 방지하기 위해 사용됩니다. synchronized를 사용하면 한 번에 하나의 스레드만 해당 코드 블록 또는 메소드에 액세스할 수 있습니다. 이렇게 하면 데이터 무결성과 일관성을 유지할 수 있습니다.
synchronized는 두 가지 방식으로 사용할 수 있습니다.
Synchronized method (동기화된 메소드): 메소드 선언에 synchronized 키워드를 추가하면 해당 메소드는 동기화됩니다. 이 경우, 해당 메소드에 동시에 액세스하려는 스레드는 해당 객체의 모니터 잠금을 획득해야 합니다. 이미 다른 스레드가 잠금을 보유하고 있다면, 요청한 스레드는 대기 상태에 들어갑니다.
public synchronized void synchronizedMethod() { // Synchronized code }
Plain Text
복사
Synchronized block (동기화된 블록)synchronized 키워드를 사용하여 특정 코드 블록을 동기화할 수 있습니다. 이 경우, 동기화된 블록에 대한 모니터 잠금을 얻으려면 특정 객체를 지정해야 합니다. 동기화된 블록을 사용하면 필요한 부분만 동기화할 수 있어 성능에 더 유리할 수 있습니다.
public void someMethod() { // Non-synchronized code synchronized (this) { // Synchronized code block } // Non-synchronized code }
Plain Text
복사
차이점:
동기화된 메소드의 경우, 메소드 전체가 동기화되며 해당 객체의 모니터 잠금을 사용합니다. 이 방식은 메소드 전체에 동기화를 적용하기 때문에 성능에 영향을 줄 수 있습니다.
동기화된 블록의 경우, 특정 코드 블록만 동기화되고 지정된 객체의 모니터 잠금을 사용합니다. 이 방식은 필요한 부분만 동기화하기 때문에 성능에 더 유리할 수 있습니다. 또한, 동기화된 블록은 다른 잠금 객체를 사용할 수 있어 더 유연한 동기화 전략을 구현할 수 있습니다.

8. volatile 에 대해서 설명해주세요

volatile은 Java에서 변수의 가시성과 동시성 문제를 해결하기 위해 사용되는 키워드입니다. volatile 키워드는 변수에 대한 읽기와 쓰기 작업이 원자적(atomic)으로 수행되도록 보장합니다. 또한, 변수의 값이 캐시되지 않고 항상 메인 메모리에서 읽고 쓰여집니다.
여러 스레드가 동시에 공유 변수에 액세스할 때 발생할 수 있는 문제를 방지하려면, 해당 변수를 volatile로 선언합니다. 이렇게 하면 한 스레드에서 변수의 값을 변경하면 변경된 값이 즉시 다른 스레드에게 보이게 됩니다. 이는 일관성과 가시성 문제를 해결하는 데 도움이 됩니다.
예를 들어, 다음과 같이 volatile 키워드를 사용하여 변수를 선언할 수 있습니다.
javaCopy code private volatile boolean flag;
Java
복사
그러나 volatile 변수를 사용할 때 주의해야 할 몇 가지 사항이 있습니다:
1.
volatile은 원자적인 읽기와 쓰기 작업만 보장하므로, 복잡한 연산이나 여러 변수 간의 동시성을 처리하는 경우에는 적합하지 않습니다. 이러한 상황에서는 synchronized 블록이나 자바의 고급 동시성 라이브러리를 사용해야 합니다.
2.
volatile 변수는 성능에 영향을 줄 수 있습니다. 변수 값이 항상 메인 메모리에서 읽고 쓰여지기 때문에, 캐시를 활용할 수 없게 되어 애플리케이션의 성능에 영향을 줄 수 있습니다.
따라서 volatile은 원자적인 단순한 연산이 필요한 경우에만 사용하고, 복잡한 동시성 문제를 해결하기 위해서는 다른 동시성 도구를 사용하는 것이 좋습니다.

9. ThreadLocal 에 대해서 설명해주세요

ThreadLocal은 Java에서 스레드 지역 변수를 사용하기 위한 클래스입니다. ThreadLocal 객체는 각 스레드에 고유한 값을 저장하고 제공합니다. 다시 말해, 각 스레드가 자체 사본을 가지는 변수를 생성할 수 있습니다. 이로 인해 여러 스레드 간에 데이터가 겹치거나 동시성 문제가 발생하지 않습니다.
ThreadLocal의 주요 사용 사례는 스레드별 리소스 관리와 상태를 관리하는 데 있습니다. 예를 들어, 스레드별 난수 발생기, 스레드별 날짜 형식 변환기 등을 구현할 때 사용할 수 있습니다.
ThreadLocal을 사용하려면 ThreadLocal 객체를 생성하고 set() 및 get() 메소드를 사용하여 스레드별 값을 설정하고 검색합니다.
ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<>(); // 값을 설정하기 위해 set() 메소드를 사용합니다. threadLocalCounter.set(10); // 값을 검색하기 위해 get() 메소드를 사용합니다. Integer value = threadLocalCounter.get();
Java
복사
ThreadLocal에 초기 값을 제공하려면 ThreadLocal의 서브클래스를 만들고 initialValue() 메소드를 오버라이드합니다.
ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } };
Java
복사
ThreadLocal을 사용할 때 주의할 점은 메모리 누수 문제가 발생할 수 있다는 것입니다. 스레드가 더 이상 사용되지 않을 때 ThreadLocal의 값도 제거되지 않으면 메모리 누수가 발생할 수 있습니다. 이 문제를 해결하려면 ThreadLocalremove()메소드를 호출하여 스레드별 값을 제거해야 합니다.
threadLocalCounter.remove();
Plain Text
복사
요약하면, ThreadLocal은 스레드 지역 변수를 저장하고 관리하는 데 사용되는 유용한 클래스로, 각 스레드에 고유한 값을 저장할 수 있습니다. 이를 통해 동시성 문제를 피하고, 스레드별 리소스와 상태를 관리할 수 있습니다. 그러나 메모리 누수 문제를 방지하기 위해 적절하게 사용해야 합니다.

10. inner class 에서 static 이 있는 경우와 없는 경우에 차이는 무엇인가요?

Java에서 내부 클래스(inner class)는 클래스 내에 정의된 다른 클래스입니다. 내부 클래스는 여러 유형이 있으며, 그 중 일반 내부 클래스(non-static inner class)와 정적 내부 클래스(static inner class)가 있습니다.
1.
일반 내부 클래스(non-static inner class): 이러한 클래스의 인스턴스는 외부 클래스의 인스턴스와 연결되어 있습니다. 일반 내부 클래스의 인스턴스는 외부 클래스의 인스턴스가 없으면 만들 수 없습니다. 일반 내부 클래스는 외부 클래스의 멤버 변수와 메소드에 액세스할 수 있으며, 이를 통해 외부 클래스와의 상호 작용이 용이해집니다.
예를 들어, 다음과 같이 일반 내부 클래스를 정의할 수 있습니다.
class Outer { private int outerValue; class Inner { private int innerValue; void accessOuterMembers() { outerValue = 10; // 외부 클래스의 멤버 변수에 액세스 가능 } } }
JavaScript
복사
2.
정적 내부 클래스(static inner class): 이러한 클래스는 외부 클래스와 독립적으로 동작합니다. 정적 내부 클래스의 인스턴스는 외부 클래스의 인스턴스가 없어도 만들 수 있습니다. 정적 내부 클래스는 외부 클래스의 정적 멤버 변수와 메소드에만 액세스할 수 있습니다. 일반적으로 정적 내부 클래스는 외부 클래스와 느슨한 연관이 있는 경우 사용됩니다.
예를 들어, 다음과 같이 정적 내부 클래스를 정의할 수 있습니다.
class Outer { private static int outerStaticValue; static class Inner { private int innerValue; void accessOuterMembers() { outerStaticValue = 10; // 외부 클래스의 정적 멤버 변수에 액세스 가능 } } }
JavaScript
복사
차이점:
일반 내부 클래스는 외부 클래스의 인스턴스에 연결되어 있어 외부 클래스의 인스턴스가 없으면 생성할 수 없습니다. 반면, 정적 내부 클래스는 외부 클래스의 인스턴스와 독립적으로 동작하며 외부 클래스의 인스턴스가 없어도 생성할 수 있습니다.
일반 내부 클래스는 외부 클래스의 모든 멤버 변수와 메소드에 액세스할 수 있지만, 정적 내부 클래스는 외부 클래스의 정적 멤버 변수와 메소드에만 액세스할 수 있습니다.
일반 내부 클래스는 외부 클래스와의 강력한 연관성을 가지며 서로의 상태를 공유하고 관리하기 쉽습니다. 반면, 정적 내부 클래스는 외부 클래스와 느슨한 연관성을 가지며 독립적인 동작이 가능합니다.

11. generic 에 대해서 알려주세요

Java 제네릭(Generic)은 컴파일 시점에 타입 안전성을 제공하고, 클래스 또는 메소드에서 다양한 타입을 처리할 수 있는 기능입니다. 제네릭을 사용하면 하나의 클래스나 메소드를 여러 타입에 대해 동작하도록 만들 수 있습니다. 이를 통해 코드 중복을 줄이고, 코드의 가독성과 재사용성을 높일 수 있습니다.
제네릭은 다음과 같은 경우에 사용됩니다.
1.
클래스와 인터페이스: 제네릭 클래스와 인터페이스를 사용하여 다양한 타입의 객체를 처리할 수 있는 코드를 작성할 수 있습니다. 예를 들어, Java의 컬렉션 프레임워크에서는 제네릭을 사용하여 여러 타입의 객체를 저장하고 처리할 수 있는 ListSetMap 등의 컬렉션 클래스를 제공합니다.
예를 들어, 다음과 같이 제네릭 클래스를 정의할 수 있습니다.
public class GenericBox<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } }
JavaScript
복사
2.
메소드: 제네릭 메소드를 사용하여 다양한 타입의 매개 변수를 처리할 수 있는 코드를 작성할 수 있습니다. 이를 통해 하나의 메소드로 여러 타입에 대해 동작할 수 있는 유연한 코드를 만들 수 있습니다.
예를 들어, 다음과 같이 제네릭 메소드를 정의할 수 있습니다.
public class GenericUtility { public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); } }
JavaScript
복사
제네릭을 사용하면 컴파일 시점에 타입 검사를 수행하여 타입 안전성을 보장할 수 있습니다.
이를 통해 런타임 시점에 발생할 수 있는 ClassCastException 과 같은 오류를 미리 방지할 수 있습니다. 또한 제네릭을 사용하면 코드의 가독성이 높아지고, 여러 타입에 대해 동작하는 클래스나 메소드를 작성할 때 코드 중복을 줄일 수 있습니다.

12. exception hierarchy 주요 클래스에 대해서 설명해주세요

Java의 예외 계층 구조는 주요 클래스를 포함하여 몇 개의 계층으로 구성됩니다. 각 예외 클래스는 이 계층 구조에서 특정 위치를 차지하며, 이를 통해 예외를 효과적으로 처리할 수 있습니다. Java의 주요 예외 클래스는 다음과 같습니다.
1.
Throwable: Java 예외 계층 구조의 최상위 클래스입니다. 모든 오류와 예외 클래스는 Throwable 클래스를 직접 또는 간접적으로 상속받습니다. Throwable 클래스에는 메시지, 원인 및 스택 추적 정보와 같은 예외에 대한 세부 정보를 가져오는 메소드가 포함되어 있습니다.
2.
ErrorThrowable 클래스의 하위 클래스로, 시스템 오류를 나타냅니다. 이러한 오류는 일반적으로 JVM 또는 시스템 리소스와 관련된 심각한 문제로, 애플리케이션 코드에서 복구할 수 없는 경우가 대부분입니다. 예를 들어, OutOfMemoryErrorStackOverflowError 등이 있습니다.
3.
ExceptionThrowable 클래스의 하위 클래스로, 애플리케이션에서 발생할 수 있는 예외를 나타냅니다. Exception 클래스의 하위 클래스는 크게 체크 예외(checked exceptions)와 언체크 예외(unchecked exceptions)로 구분됩니다.
Checked Exceptions: 복구 가능한 예외로, 애플리케이션 코드에서 예외를 처리하거나 던지도록 강제하는 예외입니다. 이러한 예외는 컴파일 시점에 확인되며, try-catch 블록 또는 throws 절을 사용하여 처리해야 합니다. 예를 들어, IOExceptionFileNotFoundExceptionClassNotFoundException 등이 있습니다.
Unchecked Exceptions: 복구 가능하지 않은 예외로, 주로 프로그래밍 오류나 논리적 오류로 인해 발생합니다. 이러한 예외는 컴파일 시점에 확인되지 않으며, 명시적으로 처리할 필요가 없습니다. RuntimeException 클래스를 상속받는 예외가 이에 해당합니다. 예를 어, NullPointerExceptionIndexOutOfBoundsExceptionIllegalArgumentException 등이 있습니다.
Java의 예외 계층 구조를 이해하면 애플리케이션에서 발생할 수 있는 오류와 예외를 적절하게 처리할 수 있습니다. 이를 통해 더 안정적이고 견고한 애플리케이션을 구축할 수 있습니다.

13. immutable class 에 대해서 설명해주세요

불변 클래스(Immutable class)는 객체가 생성된 후에 상태가 변경되지 않는 클래스를 의미합니다. 객체의 상태는 생성 시점에 초기화되며, 이후에는 변경할 수 없습니다. 불변 클래스는 여러 스레드에서 동시에 사용되어도 동기화 문제가 없으며, 프로그램의 예측 가능성과 신뢰성을 높입니다.
불변 클래스를 만들기 위해서는 다음과 같은 원칙을 따라야 합니다:
1.
클래스를 final로 선언: 클래스를 final로 선언하면 다른 클래스가 해당 클래스를 상속받지 못하게 합니다. 이를 통해 하위 클래스가 상태 변경을 허용하는 메소드를 추가하는 것을 방지할 수 있습니다.
2.
모든 필드를 final로 선언: final로 선언된 필드는 한 번 초기화되면 변경할 수 없습니다. 이를 통해 객체의 상태가 변경되지 않도록 보장할 수 있습니다.
3.
모든 필드를 private로 선언: 필드를 private로 선언하면 외부에서 직접 접근할 수 없습니다. 이를 통해 외부에서 필드 값을 변경하는 것을 방지할 수 있습니다.
4.
필드에 대한 변경 가능성을 제거: 참조 타입의 필드가 변경 가능한 객체를 참조하는 경우, 불변성이 깨질 수 있습니다. 이를 방지하기 위해 생성자나 메소드에서 얕은 복사나 깊은 복사를 사용하여 필드 값을 설정하거나 반환할 수 있습니다.
5.
변경 가능한 객체를 반환하지 않는 getter 메소드 제공: 객체의 상태를 가져오기 위한 getter 메소드를 제공하되, 반환 값이 변경 가능한 객체인 경우 복사본을 반환하여 불변성을 보장합니다.
Java에서 대표적인 불변 클래스로는 StringIntegerLocalDateLocalTime 등이 있습니다. 이러한 불변 클래스는 객체의 상태가 변하지 않기 때문에, 프로그램의 예측 가능성이 높아지고 여러 스레드에서 동시에 사용할 때 동기화 문제가 발생하지 않습니다.

전반적인 스프링 질문정리

1. 필터와 인터셉터 설명

필터
인터셉트
역할
- 서블릿 컨테이너 레벨에서 동작 - HTTP 요청이 서블릿에 도달하기 전과 후에 작업을 수행 - 주로 인증, 인가, 로깅, 인코딩 설정과 같은 공통 관심사(cross-cutting concerns)를 처리하는 데 사용
- 스프링 MVC의 일부 - 컨트롤러에 요청이 전달되기 전, 후에 작업을 수행 - 스프링 컨텍스트와 상호작용할 수 있어 스프링 빈을 주입받거나, 보안 컨텍스트에 접근하는 등의 작업 가능
특징
- 스프링 애플리케이션 컨텍스트와는 분리되어 작동 - 스프링의 빈을 사용할 수 없음
- 스프링의 애플리케이션 컨텍스트에서 동작 - 스프링과의 통합이 필요한 작업에 적합
구현
- javax.servlet.Filter 인터페이스 구현 - doFilter 메서드를 통해 요청/응답을 처리
org.springframework.web.servlet.HandlerInterceptor 인터페이스 구현 - preHandle, postHandle, afterCompletion 메서드를 통해 요청/응답을 처리
차이점
- 서블릿 레벨에서 HTTP 요청과 응답을 처리 - 모든 요청에 대한 공통 작업(인증, 로깅 등)에 적합
- 컨트롤러 전후에 스프링 애플리케이션과 밀접하게 상호작용하는 작업에 적합
사용 예시
모든 HTTP 요청에 대한 인증 또는 로깅 작업을 처리
스프링의 보안 컨텍스트스프링 빈을 사용하여 특정 컨트롤러 전후에 작업 수행

2. @ControllerAdvice 설명

@ControllerAdvice는 스프링에서 여러 컨트롤러에 걸쳐 공통적으로 발생하는 문제를 중앙 집중적으로 처리하기 위한 어노테이션입니다. 이를 사용하면 코드 중복을 줄이고, 유지 보수성을 높이며, 애플리케이션 전반에 걸쳐 일관된 동작을 제공할 수 있습니다.

주요 기능

1. 전역 예외 처리
@ControllerAdvice@ExceptionHandler와 함께 사용하여 애플리케이션 전반에서 발생하는 특정 예외를 한 곳에서 처리할 수 있습니다.
각 컨트롤러마다 예외 처리 코드를 반복 작성할 필요가 없으며, 모든 예외 처리를 중앙에서 관리할 수 있습니다.
2. 데이터 바인딩 설정
@InitBinder와 함께 사용하여 모든 컨트롤러에서 공통 데이터 바인딩 설정을 정의할 수 있습니다.
예를 들어, 모든 컨트롤러에서 날짜 형식을 일관되게 적용하려는 경우에 유용합니다.
3. 모델 어트리뷰트 추가
@ModelAttribute와 함께 사용하여 모든 컨트롤러에서 공통으로 사용할 데이터를 정의할 수 있습니다.
모든 뷰에서 일관된 데이터에 접근할 수 있어 코드 중복을 줄이고, 유지 보수가 용이합니다.

주요 목적

코드 중복 최소화: 공통 로직을 한 곳에서 관리하여 불필요한 중복을 줄입니다.
일관성 유지: 애플리케이션 전체에서 동일한 동작을 보장합니다.
유지 보수성 및 확장성 향상: 코드 구조가 간결해지고 관리가 쉬워져 애플리케이션 확장이 용이해집니다.

3. AOP 핵심 포인트

AOP의 개념

AOP는 주 비즈니스 로직과는 별개로 **횡단 관심사(Cross-Cutting Concerns)**를 분리해 모듈화하는 방법입니다. 이를 통해 코드 중복을 줄이고 시스템의 유지 보수성가독성을 향상시킵니다.

횡단 관심사의 예시

횡단 관심사는 여러 클래스나 메서드에서 반복적으로 등장하는 코드 블록을 말합니다. 주로 로깅, 트랜잭션 관리, 보안, 캐싱 등이 이에 해당합니다.
예: 모든 서비스 호출 전후에 로깅을 하거나, 메서드 실행 시 트랜잭션을 관리하는 것.

AOP의 주요 개념

Aspect: 횡단 관심사를 모듈화한 코드 블록입니다.
Pointcut: Aspect가 적용될 **조인 포인트(Join Point)**를 정의하는 표현식입니다. 어떤 메서드나 클래스에 적용할지 지정합니다.
Advice: Pointcut에 정의된 시점에서 실제로 실행될 구체적인 동작입니다. 메서드 실행 전(@Before), 실행 후(@After), 예외 발생 시(@AfterThrowing) 등 다양한 시점에 적용할 수 있습니다.

스프링 AOP vs. AspectJ

스프링 AOP프록시 기반의 AOP로 주로 런타임에 동작하며, 단순한 AOP 작업에 적합합니다.
AspectJ는 컴파일 시점, 클래스 로딩 시점에도 동작할 수 있는 완전한 AOP 프레임워크입니다. 더 강력하고 정교한 AOP 기능을 제공합니다.

AOP의 사용 사례

면접에서는 AOP의 실제 적용 사례를 설명하는 것이 중요합니다. 예를 들어:
모든 메서드 호출 전후에 로깅을 수행하는 @Around Advice.
트랜잭션 관리를 위한 @Transactional 처리.
메서드 실행 시간 측정을 위한 Advice 작성.
이러한 사례를 통해 AOP가 실무에서 어떻게 적용되는지 설명할 수 있어야 합니다.

4. AOP와 필터(Filter)의 차이

필터
AOP
적용 범위
- 서블릿 컨테이너 레벨에서 동작 - HTTP 요청과 응답에 대한 전처리 및 후처리 작업을 처리 - 주로 웹 계층에서 사용되며, 인증, 인가, 로깅, 인코딩 설정 등의 작업에 적합합니다.
- 애플리케이션 전반에서 특정 로직을 모듈화해, 메서드 호출, 예외 처리 등 다양한 조인 포인트에서 실행됨 - 웹 계층뿐만 아니라 비즈니스 로직, 서비스, DAO 계층 등 모든 애플리케이션 부분에 적용 가능
적용 대상
- 웹 요청과 응답에만 적용. 즉, HTTP 요청/응답이 처리되는 순간에만 로직이 적용됨 - 서블릿 기반의 애플리케이션에서 주로 사용 - 웹 계층에 적합
웹 요청뿐만 아니라 비즈니스 로직, 서비스 메서드 호출, 예외 처리 등 다양한 지점에서 로직을 적용할 수 있습니다. 특정 메서드클래스에 대한 세밀한 로직 적용이 가능합니다.
유연성
- HTTP 요청이 처리되기 에만 로직을 적용 가능 - 전처리와 후처리 중심으로 동작하며, 그 외의 세부적인 제어는 어렵다.
- AOP는 훨씬 더 세밀한 제어가 가능 - 예를 들어, 메서드 호출 전, 후, 예외 발생 시 등 여러 시점에서 로직을 실행할 수 있 - 로직 적용 시점을 정밀하게 제어할 수 있어 다양한 상황에 유연하게 대응할 수 있습니다.

5. JPA를 사용한 이유

RDBMS 테이블, 행, 열 에 대한 것을 class method 객체지향적으로 가능하기 때문에 개발자 관점에서 처리할 수 있다는 장점이 있지만, 그만큼 JPQL 이나 RDBMS에 호출되어 나가는 Query가 어떻게 내부적으로 동작하는지에 대한 이해도가 있어야 많은 트래픽과 최적화를 구성할 수 있는 쿼리 튜닝 포인트를 해결해나갈 수 있을 것 같습니다.

5-1. 1억 개의 데이터 페이징 처리와 커버링 인덱스

5-2. 하이버네이트 1차 캐시와 2차 캐시

5-3. 더티체킹(Dirty Checking)

무분별한 더티 체킹 방지 필요 (update, insert 시 불필요한 오브젝트 변경 방지).
실시간 처리 시 효율적인 더티 체킹.
대량 데이터 처리 시 QueryDSL을 사용해 일괄 처리.
JPA+QueryDSL은 단순한 마법이 아니라, DB 쿼리의 실행 방식을 이해해야 함.
대량 데이터 처리 시에는 QueryDSL + DTO를 활용.

5-4. 벌크 Insert

대량의 데이터를 처리할 때는 JDBC batch를 사용한 벌크 Insert 처리가 권장됨.
QueryDSL을 사용할 경우, QueryDSL-JPAQueryDSL-SQL을 활용하여 벌크 Insert 작업을 수행할 수 있음.
JPA의 기본적인 벌크 작업은 성능에 제약이 있을 수 있으므로, 최적화를 위해 JDBC batch 처리나 SQL 직접 호출을 고려해야 함.

5-5. flyway

5-6. EntityQL

JPA Entity를 기반으로 QueryDSL을 사용하여 SQL 쿼리 클래스(QClass)를 생성.
Entity와 SQL 간의 매핑을 자동화하여 코드의 가독성 및 유지 보수성을 높임.

5-7. entity 할때 되도록 primitive type을 사용하기보다 wrapper class를 사용하는이유

퍼시스턴트 레이어에서 프록시가 복제될 때, Wrapper 클래스를 사용해야 효율적으로 처리할 수 있음.
Wrapper 클래스는 null을 허용하는 반면, Primitive 타입은 허용하지 않기 때문에 프록시 복제 시 리소스를 최소화할 수 있음.
이를 통해 객체 복제 과정에서 불필요한 리소스 사용을 줄일 수 있음.

6. Page객체 메모리릭

Spring JPA ORM spring data jpa -> Query DSL 복잡한 쿼리를 도와주는 동적쿼리 Where

7. 프로젝트에서 왜 JPA를 못썼나? 안쓴건가 못쓴건가

8. JPA 정리 여러가지 이슈 케이스

8-1. @OneToOne 관계에서 N+1문제

JPA에서 N+1 문제는 연관된 엔티티를 조회할 때 **지연 로딩(Lazy Loading)**으로 인해 발생하는 성능 문제입니다.
[예시]
UserProfile 엔티티가 @OneToOne 관계를 가지고 있다고 가정할 때, 모든 User와 그에 해당하는 Profile을 조회하는 쿼리를 실행하면 N+1 문제가 발생할 수 있습니다.
1.
첫 번째 쿼리: 먼저, 모든 User를 조회하는 쿼리가 실행됩니다.
예: SELECT * FROM User (1개의 쿼리)
2.
N개의 추가 쿼리: 각 User마다 관련된 Profile을 조회하는 추가 쿼리가 발생합니다.
예: SELECT * FROM Profile WHERE user_id = ? (N개의 쿼리)
따라서, 조회하려는 User의 수가 N이라면 총 N+1개의 쿼리가 실행됩니다.
[해결방법]
즉시 로딩(Eager Loading): 연관된 엔티티를 한 번의 쿼리로 함께 로딩하는 방법입니다.
장점: N+1 문제 해결 가능.
단점: 모든 연관 엔티티를 항상 로딩하므로 불필요한 데이터로 인해 성능 저하 가능.
조인 페치(Fetch Join): JPQL의 JOIN FETCH를 사용하여 연관된 엔티티를 함께 조회할 수 있습니다.
List<User> users = em.createQuery("SELECT u FROM User u JOIN FETCH u.profile", User.class).getResultList();
Java
복사

8-2. N+1 문제 일반

N+1 문제는 ORM에서 지연 로딩 전략을 사용할 때 발생하는 성능 문제로, 한 번의 쿼리로 가져올 수 있는 데이터를 여러 번의 쿼리로 가져오는 상황을 의미합니다.
[예시]
UserOrder@OneToMany 관계에 있을 때, 모든 사용자와 그들의 주문을 조회하면 다음과 같은 상황이 발생할 수 있습니다.
1.
첫 번째 쿼리: 모든 User를 조회합니다.
예: SELECT * FROM User (1개의 쿼리)
2.
N개의 추가 쿼리: 각 User의 주문을 조회하기 위한 추가 쿼리가 실행됩니다.
예: SELECT * FROM Order WHERE user_id = ? (N개의 쿼리)
이로 인해 총 N+1개의 쿼리가 실행됩니다.
[해결방법]
즉시 로딩(Eager Loading): 연관된 엔티티를 한 번의 쿼리로 가져옵니다.
단점: 모든 데이터를 불필요하게 로딩할 수 있어 성능 문제 발생 가능.
JPQL 조인 FETCH: JOIN FETCH로 연관 데이터를 함께 조회해 N+1 문제를 해결합니다.
List<User> users = em.createQuery("SELECT u FROM User u JOIN FETCH u.orders", User.class).getResultList();
Java
복사
@EntityGraph: 어노테이션을 사용하여 JPQL에서 명시적으로 FETCH JOIN을 지정하지 않고도 N+1 문제를 해결할 수 있습니다
@EntityGraph(attributePaths = { "orders" }) List<User> findAll();
Java
복사

결론

N+1 문제는 성능을 크게 저하할 수 있는 위험이 있지만, 즉시 로딩, FETCH JOIN, @EntityGraph 등을 통해 해결할 수 있습니다. 상황에 따라 적절한 조회 전략을 선택하는 것이 중요하며, 성능 테스트를 통해 최적의 방법을 찾는 것이 필요합니다.

9. 스프링 내부 핵심원리

Spring Core

10. 객체지향 개발을 도와주는 도구 원칙 SOILD

DI, IOC 컨테이너

11. Spring Web

HTTP 기반 설명

12. Spring MVC와 Servlet의 관계(다양한 측면에서 설명)

1편: MVC 패턴과 최신 MVC
2편: 뷰 템플릿, 국제화, 데이터 검증, 세션, 쿠키, 파일 업로드
3편: 관계형 데이터베이스와 스프링의 DB 접근
JDBC, 커넥션 풀, 데이터 소스, 트랜잭션, 예외 처리, DB 접근 기술
트랜잭션 관리와 QueryDSL

스프링 고급 주제

Spring Boot의 편의 기능
스프링 핵심 디자인 패턴
스프링의 로컬 기능
스프링 AOP와 프록시 패턴
실무에서의 모니터링 방법

13. 테스트 코드 작성 방법

정적분석
테스트코드
Lint코드 포맷팅