개발자 기술면접 대비 - JAVA

문제지 스타일 질문 리스트

질문 리스트

Java의 특징에 대해 설명해주세요
자바는 객체지향 언어로 아래와 같은 특징을 가지고 있습니다.

1) 객체 지향 프로그래밍 지원
캡슐화, 상속, 다형성, 추상화 같은 객체지향 개념을 기반으로 설계되어 재사용성과 유지보수성이 뛰어납니다.

2) 플랫폼 독립성
자바는 코드를 컴파일하면 바이트코드(.class)가 생성되고 JVM이 있는 환경이면 어디서든 실행 가능합니다.

3) 자동메모리 관리 (Garbage Collection)
자바는 개발자가 직접 메모리를 건드릴 필요없이 JVM이 사용하지 않는 객체를 자동으로 탐지하고 메모리를 정리합니다.

4) 풍부한 표준라이브러리

5) 멀티스레드 지원
동시에 여러 작업을 처리할 수 있는 멀티스레드를 지원합니다.

6) 강타입 언어 (Strong type)
변수 선언 시 타입을 명확히 해야하므로, 오류를 사전에 차단할 수 있습니다.
객체 지향 프로그래밍이 무엇인가요?
객체 지향 프로그래밍 (Object-Oriented Programming, OOP)은 프로그래밍에서 필요한 데이터를 추상화 시켜 상태와 행위를 가진 객체로 만들고,
객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법입니다.
객체 지향 특징은 크게 4가지로 추상화, 캡슐화, 상속, 다형성이 있습니다.
객체 지향 프로그래밍의 4가지 특징이 무엇인가요?
  1. 캡슐화 (Encapsulation)
    데이터와 메서드를 하나의 객체로 묶고, 외부에 필요한 부분만 공개(public)하고,
    내부 구현은 숨깁니다(private)
    ⇒ 데이터 보호, 유지보수 용이

  2. 상속 (Inheritance)
    기존 클래스를 재사용하여 새로운 클래스를 만들 수 있습니다.
    공통 기능을 부모 클래스에 작성하고, 자식 클래스가 이를 물려받아 코드 중복을 줄입니다.

  3. 다형성 (Polymorphism)
    같은 인터페이스나 부모 클래스를 상속받은 객체들이 서로 다른 방식으로 동작할 수 있습니다.
    오버라이딩과 오버로딩이 있습니다.
    예를 들어, Animal 클래스를 상속받은 Dog와 Cat 클래스가 각각 다르게 짖거나 야옹거리는 것

  4. 추상화 (Abstraction)
    복잡한 내부 로직은 숨기고, 핵심적인 기능만 외부에 제공합니다.
    필요 없는 세부사항을 숨겨 사용자는 단순한 인터페이스만 알면 됩니다.
객체 지향 프로그래밍의 장점과 단점에 대해 설명해주세요

장점으로는 코드 재사용성과 유지보수성이 뛰어나고, 확장성과 가독성이 좋아서, 대규모 프로젝트 적합합니다.

단점으로는 초기 설계가 복잡하고 시간이 오래 걸리며, 객체 간 통신으로 성능이 약간 떨어질 수 있고, 구조가 지나치게 복잡해질 위험이 있습니다.

결론적으로, OOP는 큰 프로젝트에 강하지만, 작은 프로그램에선 오히려 과한 설계가 될 수도 있습니다.


오버라이딩과 오버로딩에 대해 설명해주세요.

1) 오버라이딩

상위 클래스에 있는 메소드를 하위 클래스에서 재정의 하는 것을 말합니다.

 

2) 오버로딩

매개변수의 개수나 타입을 다르게 하여 같은 이름의 메소드를 여러개 정의하는 것을 말합니다.

SOLID 5원칙에 대해 설명해주세요.
SOLID 원칙은 객체지향 설계에서 유지보수성과 확장성을 높이기 위한 5가지 원칙입니다.
 
SRP ( SRP: Single Responsibility Principle , 단일 책임의 원칙 )
클래스는 하나의 책임만 가진다.

OCP ( OCP: Open/Closed Principle, 개방-폐쇄의 원칙 )
기능 추가는 열려 있고, 수정은 닫혀 있어야 한다.

LSP ( LSP: Liskov Substitution Principle, 리스코프 치환의 원칙)
자식 클래스는 부모를 대체할 수 있어야 한다.

ISP ( ISP: Interface Segregation Principle, 인터페이스 분리 원칙)
클라이언트에 맞게 인터페이스를 나눈다.

DIP ( DIP: Dependency Inversion Principle, 의존 역전 원칙)
상위 모듈과 하위 모듈은 추상화에 의존해야 한다.
즉, 서비스가 직접 Repository를 만들지 않고 인터페이스에 의존해야함
JAVA는 Call by Value인지 Call by reference인지 설명해주세요.
자바는 무조건 Call by Value(값에 의한 호출)입니다.

자바에서는 모든 메서드 호출 시, 실제 값이 복사되어 전달됩니다.

기본 타입(int, double, boolean 등)은 값 그 자체가 복사되어 넘어갑니다.

참조 타입(객체, 배열 등)은 참조(주소)값 자체를 복사해서 넘어갑니다.
즉, 객체를 넘겨줄 때도 "참조값(주소)"를 값으로 복사해서 전달하는 것입니다.
그래서 메서드 안에서 객체의 내부 상태(필드 값)는 변경할 수 있지만,
참조 자체를 새로운 객체로 바꿔도 호출자에게는 영향을 주지 않습니다.
위 질문연계) 그럼 메서드 안에서 객체를 새로 만들어서 리턴하면 어떻게 되나요?
메서드 안에서 객체를 새로 만들고 리턴하면, 새로 생성한 객체의 참조값이 반환됩니다.
그리고 메서드를 호출한 쪽에서 그 반환값을 받아서 사용해야 합니다.

즉, 기존 객체는 바뀌지 않고 메모리에 남아는 있습니다.

메서드 안에서 새로 만든 객체를 리턴받아서 저장해야 외부에서도 접근할 수 있습니다.
위 질문연계) 그럼 기존 객체는 메모리에 남아있다가 가비지 컬렉션 되나요?
기존 객체는 메모리에 남아 있다가, 가비지 컬렉션(Garbage Collection, GC) 대상이 됩니다.

위 예시에서, 원래 p 변수가 가리키고 있던 기존 객체(Original)는
p = createPerson(); 하면서 새 객체(NewPerson)를 가리키게 됩니다.

그래서 기존 객체를 가리키는 참조(reference)가 하나도 없게 됩니다.

참조가 없는 객체는 자바의 가비지 컬렉터(GC)가 '더 이상 쓸모없는 객체'로 판단하고,
GC가 작동할 때 기존 객체를 메모리에서 자동으로 삭제합니다.
Garbage Collection(GC)에 대해서 설명해주세요.
GC는 더 이상 사용되지 않는 객체를 자동으로 탐지하고 메모리에서 정리하는 자바의 메모리 관리 기능입니다.

개발자가 직접 메모리 해제할 필요 없이, JVM이 주기적으로 참조 끊긴 객체를 삭제합니다.

기본적으로 Mark & Sweep 방식을 사용하며,
최근에는 Generational GC나 G1 GC 같은 최적화된 알고리즘도 사용됩니다.
GC가 어떻게 참조를 판별하나요?
GC는 객체가 garbage인지를 판별하기 위해 Reachability Analysis (도달 가능성 분석) 라는 방법을 사용합니다.

순서
1. 루트(Root) 객체를 기준으로 탐색을 시작합니다.
   1) 스택에 있는 지역 변수들 (메서드 안에서 선언된 변수들)
   2) 메서드 호출 스택에 있는 활성 객체들
   3) 클래스(static) 변수들
   4) JNI (자바 네이티브 인터페이스)를 통해 참조하는 객체들

2. 루트에서 연결된 객체를 따라가며 살아있는 객체를 표시(Mark)합니다.

3. 루트에서 연결되지 않은 객체는 Garbage로 판단하고 제거합니다.

결론적으로, GC는 참조 자체만 보는 게 아니라 루트에서 도달 가능한지를 기준으로 객체를 판별합니다.
GC가 동작하면 성능이 느려지지 않나요?
Stop-the-World 현상이 발생해서, 애플리케이션 전체가 일시적으로 멈추고 성능이 저하될 수 있습니다.

* Stop-the-World ?
GC가 메모리를 정리하는 동안, 애플리케이션의 모든 스레드가 일시 중지되고, GC 작업이 끝날 때까지 아무 작업도 진행되지 않는 현상

이를 줄이기 위해
CMS GC, G1 GC, ZGC 같은 최적화된 GC 방식이 개발되었습니다.
Generational GC의 Young/Old 세대 차이점은 무엇인가요?
Generational GC는 객체의 생존 기간에 따라 메모리를 나누어 관리하는 방식입니다.

Young Generation
새로 생성된 객체들이 저장되고, Minor GC를 통해 빠르고 자주 수거됩니다.

Old Generation
오래 살아남은 객체들이 저장되고, Major GC가 발생하는데, 비용이 크고 멈춤 시간도 깁니다.

결론적으로, Young 세대는 짧은 생명 객체를 빠르게 수거하고, Old 세대는 장수 객체를 효율적으로 관리합니다.

 

구분 Young Generation Old Generation
저장 대상 새로 생성된 객체 오래 살아남은 객체
GC 종류 Minor GC Major GC / Full GC
특징 자주, 빠르게 GC 발생 드물게, 무겁게 GC 발생
최적화 목표 빠른 수거 최소한의 중단시간
Java 8과 Java 11의 변화를 알려주세요.
Java 8은 람다식, 스트림 API, Optional, LocalDateTime 같은 함수형 프로그래밍을 도입한 대형 변화 버전입니다.

Java 11은 var 키워드, HTTP Client 표준화, String 개선처럼 코드를 현대화하고 성능을 개선한 안정적인 LTS(Long Term Support) 버전입니다.

결론적으로,
Java 8은 큰 변화를 가져온 버전이고, Java 11은 그 변화를 다듬고 정리한 장기 지원(LTS) 버전입니다.
Java 8과 Java 11의 GC 차이를 설명해주세요.
Java 8은 Parallel GC가 기본이었고, 빠르지만 Stop-the-World 시간이 길어서 서버 지연이 생길 수 있었습니다.

Java 11은 G1 GC가 기본이 되었고, 메모리를 Region 단위로 나눠서 관리하기 때문에 멈춤 시간을 최소화하고, 대규모 메모리에도 효율적입니다.

추가로, Java 11은 ZGC, Shenandoah GC 같은 초저지연 GC도 지원합니다.

결론적으로, Java 11은 GC 측면에서 성능과 응답성이 크게 향상되었습니다.
JVM이 무엇인가요?
JVM(Java Virtual Machine)은 자바 바이트코드(.class 파일)를 읽고 실행하는 가상 머신입니다.
JVM 덕분에 자바는 운영체제에 상관없이 실행할 수 있고, 
메모리 관리(Garbage Collection)와 보안(바이트코드 검증)까지 담당합니다.
JVM 구성요소(전체 구조)를 설명해보세요.
JVM은 크게 4가지 구성요소로 이루어져 있습니다.

1) Class Loader
.class 파일(바이트코드)을 읽어와서 메모리에 적재하는 역할
  • Bootstrap Class Loader (JDK 기본 클래스 로딩)
  • Extension Class Loader (JDK 확장 라이브러리 로딩)
  • Application Class Loader (사용자 애플리케이션 클래스 로딩)

2) Runtime Data Area
JVM이 실행 중일 때 사용하는 메모리 영역입니다.
JVM의 메모리 구조 (Heap, Stack 등)

3) Execution Engine
메모리에 로딩된 바이트코드를 실제로 실행하는 엔진입니다.
  • Interpreter (인터프리터)
    → 바이트코드를 한 줄씩 읽고 바로 실행하는 방식 (빠른 시작 가능, 하지만 느림)
  • JIT Compiler (Just-In-Time Compiler)
    → 자주 실행되는 코드를 기계어로 변환해서 캐싱하는 방식
  • Garbage Collector (GC)
    → 사용하지 않는 객체를 메모리에서 제거하는 역할.
4) Native Interface
OS의 네이티브 코드 호출 지원
JVM 메모리 구조를 설명해보세요.
JVM 메모리는 크게 5가지로 나뉩니다.

1) Method Area 
클래스 정보와 static 데이터를 저장하는 영역으로 모든 스레드가 공유합니다.

2) Heap
객체 인스턴스들이 저장되는 영역으로 모든 스레드가 공유합니다. GC가 주로 여기서 작동합니다.

3) Stack
각 스레드별로 따로 존재하여 메서드 호출, 지역 변수를 관리합니다.

4) Program Counter Register
각 스레드별로 따로 존재하며 현재 실행 중인 명령어 주소를 저장합니다.
JVM은 이 값을 기준으로 명령어를 실행할지 안할지 결정합니다.

5) Native Method Stack
자바가 아닌 네이티브 메서드를 실행할 때 사용합니다. (C+, C++ 등)
JVM 작동의 전체 흐름에 대해 설명하세요.
1) Class Loader가 .class 파일을 메모리에 적재합니다.

2) Runtime Data Area에 필요한 데이터 구조를 준비합니다.

3) Execution Engine이 바이트코드를 해석하거나 컴파일해서 실행합니다.

4) 필요하면 Native Interface를 통해 OS 기능 호출합니다.
JVM 메모리 상수풀 (Constant Pool)에 대해 설명하세요.
메모리 상수풀은 JVM이 상수나 심볼릭 레퍼런스를 저장하는 특별한 메모리 공간입니다.
(Method Area 안에 존재합니다)

클래스와 관련된 상수 데이터를 저장합니다.
Ex) 리터럴 값, 클래스/메서드/필드의 이름 정보, 타입 정보 등

상수풀은 메모리 절약과 성능 향상을 위해 존재합니다.

항목 설명
클래스 파일 상수풀 .class 파일 안에 있는 컴파일된 상수 테이블
런타임 상수풀 JVM이 실행 중에 관리하는 상수 저장소
문자열 상수풀 문자열 리터럴을 공유 저장하는 별도 공간

JIT 컴파일러가 왜 필요한가요?
JVM은 원래 바이트코드를 한 줄씩 해석하는 인터프리터 방식이라 실행 속도가 느립니다.

그래서 JIT (Just-In-Time) 컴파일러가 자주 실행되는 코드를 기계어로 변환해서 캐싱해두고,
다음부터는 빠르게 실행할 수 있도록 도와줍니다.
JIT 컴파일이랑 AOT 컴파일 차이는 뭔가요?

1. JIT(Just-In-Time) 컴파일
프로그램 실행 도중에 바이트코드를 기계어로 변환합니다.
처음에는 인터프리터처럼 한 줄씩 실행하다가, 자주 실행되는 코드(핫스팟)를 발견하면 그 부분만 실시간(런타임)으로 컴파일합니다.
이후에는 기계어로 변환된 코드를 캐시해두고 바로 실행하므로 점점 빠른 속도를 얻을 수 있습니다.


특징

  • 초기 실행 속도는 느릴 수 있지만, 점점 최적화되어 빨라진다.
  • 런타임 상황을 분석해서 최적화할 수 있다. (프로파일링 기반 최적화)

2. AOT(Ahead-Of-Time) 컴파일
프로그램 실행 전에 바이트코드를 미리 기계어로 변환합니다.
즉, 프로그램을 배포하거나 설치할 때 이미 기계어 파일로 변환된 상태입니다.

 

특징

  • 시작이 빠르다. 실행 시 인터프리팅이나 JIT 컴파일 과정이 필요 없다.
  • 대신 런타임 정보를 반영하지 못해서 최적화 수준은 제한적일 수 있다.
  • 주로 네이티브 이미지 생성(GraalVM의 native-image 등)에서 사용된다.
JVM 클래스로더의 작동 과정에 대해 설명해주세요.
1) 로딩
클래스의 .class 파일을 읽어서 JVM 메모리에 적재합니다.

2) 링크
  • 검증(Verification)
    .class 파일의 바이트코드가 JVM 규칙에 맞는지 검사합니다.
  • 준비(Preparation)
    static 변수들을 메모리에 할당하고, 기본값(default value)을 설정합니다. (ex. int는 0)
  • 해결(Resolution)
    다른 클래스, 메서드, 필드 참조를 실제 메모리 주소로 연결합니다.

3) 초기화
static 블록(static initializer)과 static 변수들이 실제 값으로 초기화됩니다.

JVM 클래스로더의 계층 구조에 대해 설명해주세요.

클래스 로더는 부트스트랩 → 확장 → 애플리케이션 순서로 계층적으로 동작합니다.
이를 부모 위임 모델이라고 부릅니다.

  • Bootstrap Class Loader
    JDK 내부 기본 클래스(java.lang.String 등) 로딩.
  • Extension Class Loader
    JDK 확장 기능 라이브러리 로딩.
  • Application Class Loader
    사용자 애플리케이션 클래스(.class 파일) 로딩.

클래스를 로드하려 할 때, 부모 로더에 요청(delegate) 먼저 보냅니다.

부모가 해당 클래스를 못 찾으면 자식 로더가 직접 로드합니다.

이를 통해 클래스 중복로딩이 방지되고, 보안이 강화(시스템 클래스를 임의로 덮는 것 방지)됩니다.
심볼릭 레퍼런스와 다이렉트 레퍼런스의 차이는 무엇인가요?
심볼릭 레퍼런스는 클래스 이름, 필드 이름 같은 문자열 기반 참조이고, 메모리에 적재되기 전에는 주소를 모릅니다.

다이렉트 레퍼런스는 클래스를 로딩하고 해석한 후 생성되는 메모리 주소 기반 참조입니다.

결론적으로, 심볼릭은 이름, 다이렉트는 메모리 주소라고 보면 됩니다.

클래스 로딩 → 검증(Verification) → 준비(Preparation) → 해결(Resolution) → 초기화(Initialization)
이 중 해결(Resolution) 단계에서 심볼릭 레퍼런스가 다이렉트 레퍼런스로 변환됩니다.
Java의 ThreadLocal이 무엇인가요?
ThreadLocal은 각 스레드마다 독립적인 변수를 저장할 수 있게 해주는 클래스입니다.

같은 ThreadLocal 인스턴스를 공유해도, 스레드마다 자기만의 값을 가질 수 있습니다.
이로인해 멀티스레드 환경에서도 동기화 없이 안전하게 스레드별 데이터를 관리할 수 있습니다.
일반 변수와 ThreadLocal의 차이점은 무엇인가요?

1. 일반 변수 (인스턴스 변수, static 변수)
일반 변수는 모든 스레드가 공유하는 데이터입니다.
멀티스레드 환경에서는 여러 스레드가 동시에 같은 변수에 접근할 수 있습니다.
여러 스레드가 setValue()를 동시에 호출하면 서로 값이 덮어씌워질 수 있습니다.
따라서 데이터 충돌이나 예상치 못한 결과를 방지하려면 synchronized 같은 동기화 작업이 필요합니다.

2. ThreadLocal 변수
ThreadLocal은 스레드마다 따로 따로 값을 저장합니다.
각 스레드 전용 저장소를 가지고 있어, 다른 스레드와 절대 간섭하지 않습니다.
별도의 동기화 없이 멀티스레드 환경에서도 안전하게 사용할 수 있습니다.

쓰레드(Thread)란 무엇인가요?
Thread는 프로세스(Process) 안에서 실제 작업을 수행하는 최소 실행 단위입니다.

여러 쓰레드가 하나의 프로세스 안에서 메모리를 공유하며 독립적으로 실행됩니다.
쓰레드는 가볍고, 병렬 처리와 비동기 작업을 가능하게 만들어 프로그램 성능을 높일 수 있습니다.
멀티스레드 환경에서는 어떤 문제가 발생할 수 있나요?
1) 경쟁 조건(Race Condition)
두 개 이상의 스레드가 동시에 같은 데이터를 읽거나 쓰면서 예상치 못한 결과가 나오는 현상입니다.
Ex. 은행 계좌 잔액 업데이트 시 두 스레드가 동시에 출금 요청을 보내서 잔액이 틀어지는 경우.

2) 데드락(Deadlock)
서로 자원이 잠긴 상태로 스레드들이 영원히 대기하는 상황입니다.
Ex. 스레드1이 자물쇠1을 잡고 자물쇠2를 기다리고, 스레드2는 자물쇠2를 잡고 자물쇠1을 기다릴 때

3) 라이브락(Livelock)
데드락처럼 멈추지는 않지만, 서로 양보만 하다가 아무것도 진행되지 않는 상황입니다.
상태는 바뀌지만 결과적으로 작업이 진행되지 않습니다.

4) 기아 상태(Starvation)
특정 스레드가 자원이나 CPU 사용 기회를 거의 얻지 못하고 계속 기다리기만 하는 상황입니다.
높은 우선순위 스레드에 밀려서 낮은 우선순위 스레드가 실행되지 않는 경우입니다.

5) 데이터 불일치(Data Inconsistency)
적절한 동기화 없이 여러 스레드가 데이터를 수정하면서, 일관성 없는 잘못된 데이터가 저장될 수 있습니다.
데드락을 방지하려면 어떻게 해야 하나요?

* 데드락이란?
여러 스레드가 서로 상대방의 자원을 점유하고 해제를 기다리면서 영원히 대기 상태에 빠지는 문제입니다.

데드락 발생 4가지 조건 ( 이 중 하나라도 깨면 데드락을 방지할 수 있습니다)

  1. 상호 배제(Mutual Exclusion): 자원은 한 번에 하나의 스레드만 사용할 수 있다.
  2. 점유 대기(Hold and Wait): 자원을 점유한 채로 다른 자원을 기다린다.
  3. 비선점(No Preemption): 다른 스레드가 점유한 자원을 강제로 뺏을 수 없다.
  4. 순환 대기(Circular Wait): 스레드들이 자원을 순환적으로 기다린다.

데드락 방지 방법
1. 자원 획득 순서를 일관되게 정하기
항상 동일한 순서로 자원을 획득하도록 강제합니다.
예를 들어, 항상 Lock A → Lock B 순서로만 잠그도록 규칙을 정하면 순환 대기를 방지할 수 있습니다.

2. 타임아웃 설정
일정 시간 안에 락을 획득하지 못하면 포기합니다.
Java에서는 tryLock(long timeout, TimeUnit unit) 같은 메서드를 사용할 수 있습니다.

3. 락을 최소화하고 필요한 만큼만 사용
락을 오래 잡고 있으면 데드락 가능성이 높아집니다.
꼭 필요한 범위 안에서만 락을 걸고, 빨리 해제하는 것이 중요합니다.


4. 데드락 탐지와 회복 (Deadlock Detection)
데드락이 발생할 수도 있다고 가정하고, 주기적으로 상태를 검사합니다.
데드락이 감지되면, 강제로 일부 스레드를 종료하거나 자원을 회수해서 풀어버리는 방법입니다.


Synchronized 키워드가 무엇이고 어떤 문제를 해결해줄 수 있나요?

synchronized는 한 번에 하나의 스레드만 특정 코드에 접근할 수 있도록 락을 거는 키워드입니다.

경쟁 조건이나 데이터 불일치 같은 문제를 방지할 수 있습니다.
메서드나 코드 블록에 적용할 수 있고,
너무 넓은 범위에 synchronized를 걸면 병목(Bottleneck) 생길 수 있는 단점이 있습니다.

결론적으로, synchronized는 멀티스레드 환경에서 데이터 충돌을 막고 일관성을 보장하는 역할을 합니다.

Synchronized 대신 ReentrantLock을 써야 할 때는 언제인가요?
synchronized는 자바 내장 키워드고,
ReentrantLock은 java.util.concurrent.locks 패키지에 있는 클래스입니다.
둘 다 락(lock)을 걸지만, ReentrantLock이 더 세밀한 제어를 제공합니다.

1. 락을 걸거나 풀 때 더 세밀한 제어가 필요할 때
synchronized는 락을 얻으면 블록이 끝날 때 자동으로 풀린다. (자동 unlock)
ReentrantLock은 원하는 위치에서 직접 unlock() 할 수 있다.
-> 단, unlock을 빠뜨릴 경우, 무한대기가 발생할 수 있으므로 주의해야합니다.

2. 락 획득 시 대기 시간 설정이 필요할 때
synchronized는 락을 무조건 기다리기때문에, 대기하다가 블로킹합니다.
ReentrantLock은 tryLock()을 사용해서 락을 얻을 수 있으면 진행하고, 못 얻으면 바로 다른 로직을 처리할 수 있다.
데드락 방지나 타임아웃 전략에 유리합니다.

3. 공정성(Fairness)이 필요한 경우
synchronized는 공정성 보장이 없습니다.
ReentrantLock은 생성할 때 공정성(fairness) 설정이 가능하다. 먼저 기다린 스레드가 먼저 락을 얻도록합니다. (FIFO 순서)
-> 단, 이로 인해 락 획득, 반납 과정에 추가 비용이 발생하고, 시스템 처리량이 줄어들어 성능은 저하될 수 있습니다.
바이트코드(Bytecode)란 무엇인가요?
바이트코드는 자바 컴파일러(javac)가 .java 소스 파일을 JVM이 이해할 수 있는 중간 언어로 변환한 파일입니다.
확장자 .class 파일로 저장됩니다.
자바는 왜 상속을 하나만 가능하게 했나요?
다중 상속의 문제점 때문입니다.
다중 상속(Multiple Inheritance) 은 하나의 클래스가 여러 부모 클래스를 상속받을 수 있게 하는 개념인데,
이건 복잡성과 모호성 문제를 일으킵니다.
Diamond Problem  : 두 부모 클래스가 같은 이름의 메서드를 가지고 있을 때, 자식 클래스가 어떤 부모의 메서드를 상속받을지 모호해집니다.

결론적으로, 자바는 단일 상속만 가능하지만, 인터페이스는 여러 개 구현할 수 있도록하여 구조는 단순하게하고 유연성은 확보하게 되었습니다.
Ex) class MyClass implements InterfaceA, InterfaceB { ... }
인터페이스는 구현(implementation)이 아니라 설계(specification)만 제공하므로,
다중 인터페이스를 구현해도 모호성 문제가 발생하지 않습니다.
Java 8의 default 메서드는 다중 상속 문제를 해결할 수 있나요?
Java 8의 default 메서드는 인터페이스 다중 구현에서 메서드 충돌 문제를 명시적으로 해결할 수 있게 해줬습니다.

클래스 상속은 여전히 하나만 가능하지만 (다중상속은 여전히 비허용),
인터페이스끼리 default 메서드가 충돌할 때는 인터페이스명.super.메서드명() 방식으로 해결할 수 있습니다.

interface A {
    default void hello() { System.out.println("Hello from A"); }
}
interface B {
    default void hello() { System.out.println("Hello from B"); }
}

class C implements A, B {
    @Override
    public void hello() {
        A.super.hello(); // 명시적으로 선택
    }
}​

상속, 구현, 설계의 차이점에 대해 설명해주세요.
1. 상속(Inheritance)
상속은 기존 클래스를 확장해서 새로운 클래스를 만드는 것입니다.
부모 클래스(슈퍼클래스)의 속성(필드)과 행동(메서드) 을 자식 클래스(서브클래스)가 물려받습니다.
중복 코드를 줄이고, 코드 재사용성을 높이는 데 목적이 있습니다.
class Animal {
    void eat() { System.out.println("eating..."); }
}

class Dog extends Animal {
    void bark() { System.out.println("barking..."); }
}
// Dog는 Animal의 eat()메소드를 물려받습니다.
상속과 구현 중 언제 무엇을 써야 하나요?
1. 상속(extends)을 사용하는 경우
구체적인 기능(코드)을 재사용하고 싶을 때 상속을 사용합니다.
부모 클래스의 속성, 메서드를 그대로 또는 조금 수정해서 사용하고 싶을 때 적합합니다.

2. 구현(implements)을 사용하는 경우
구현은 없는 '기능 명세' 만 제공하고, 구체적인 로직을 각자 다르게 작성해야 할 때 사용합니다.
1) 다른 종류 객체들이 같은 동작을 제공하지만, 방법이 다를 때
2) API 설계할 때 다형성을 활용하고 싶을 때
상속을 너무 많이 쓰면 어떤 문제가 생기나요?
1. 부모 클래스에 지나치게 의존하게 된다
부모 클래스를 변경하면, 모든 자식 클래스에도 영향이 퍼지게되고, 전체 시스템이 흔들릴 수 있습니다.

2. 코드 유연성이 떨어집니다.
상속은 컴파일 타임에 구조가 고정되기 때문에, 동적으로 행동을 바꿀 여지가 줄어듭니다.

3. 다중 상속 문제
자바는 다중 클래스 상속 자체를 막아두었지만, 상속의 상속의 상속을 반복하다보면 코드가 복잡해지고 충돌하는 문제가 생깁니다.

4. 테스트의 어려움
테스트하려는 자식의 구조가 부모 클래스에 깊게 의존하고 있는 경우, 독립적인 단위의 Unit Test가 어려워집니다.

5. 억지상속(is-a 구조)가 발생할 수 있습니다.
조합(Composition)은 상속에 비해 어떤 장점이 있나요?
조합은 '필요한 기능을 직접 상속받는 대신, 다른 객체를 필드로 가지고 위임(delegate)해서 사용하는 것' 
(has-a 관계)

장점
1. 다른 객체를 참조만 할 뿐이므로, 결합도(Coupling)가 낮습니다.
2. 런타임에 객체를 교체하거나 기능을 자유롭게 추가, 제거할 수 있어 유연성이 뛰어납니다.
3. 상속은 모든 부모요소를 가져와야하는 반면, 조합은 필요한 기능만 가져올 수 있습니다.
4. 객체간 의존관계가 느슨해져 Mock 객체를 끼워넣어 쉽게 Unit Test 할 수 있습니다.
5. 상속은 한 부모만 가질 수 있지만, 조합은 여러 객체를 참조할 수 있습니다.

추상 클래스와 인터페이스의 차이에 대해서 설명해주세요.
1. 추상 클래스(Abstract Class)
공통적인 속성과 기능을 가진 클래스입니다.
클래스 안에 추상 메소드가 하나 이상 포함되거나, abstract으로 정의된 경우입니다.

abstract class Animal {
    String name;

    void eat() {
        System.out.println("Eating...");
    }

    abstract void sound();
}
// eat메소드는 이미 구현되어있고
// sound 메소드는 자식 클래스가 꼭 구현해야합니다.


2. 인터페이스(Interface)
동작 규칙(명세) 만 정의하는 완전 추상적인 타입입니다.
모든 메서드는 기본적으로 abstract (구현 없음)으로 간주합니다.

interface Animal {
    void sound(); // 구현 없이 동작만 명세
}
// 오직 "이런 기능이 있어야 한다"만 정하고, 세부 구현은 클래스가 담당해야합니다



Java 8 이후 인터페이스에 default 메서드가 추가되면서 추상 클래스와 차이가 줄어든 거 아닌가요?
기존 인터페이스를 깨지 않고 기능을 확장하기 위해 Java 8에서 인터페이스에 default 메서드 추가되었습니다.

인터페이스를 수정하면 원래는 그걸 구현한 모든 클래스가 오류가 나는데,
default 메서드가 있으면 기존 클래스를 깨지 않고 새 기능을 추가할 수 있게 되었습니다.

하지만, 추상 클래스는 공통 속성과 상태를 관리할 수 있고,
인터페이스는 여전히 동작 규칙만 정의합니다.

결론적으로, 둘의 본질적인 역할은 여전히 다릅니다.
추상 클래스와 인터페이스를 같이 사용할 수 있나요?
네, 가능합니다.

자바에서는 한 클래스가 하나의 추상 클래스를 상속하면서 동시에 여러 인터페이스를 구현할 수 있습니다.
추상 클래스는 공통 속성과 기본 동작을 제공하고, 인터페이스는 동작 규칙만 강제합니다.
두 개를 역할에 맞게 조합해서 더 유연한 설계를 할 수 있습니다.

예시 코드)

// Bird는 Animal 추상 클래스를 상속(extends) 하고,
// 동시에 Flyable, Walkable 인터페이스를 구현(implements) 한다.

interface Flyable {
    void fly();
}

interface Walkable {
    void walk();
}

abstract class Animal {
    String name;

    void eat() {
        System.out.println(name + " is eating");
    }

    abstract void sound();
}

class Bird extends Animal implements Flyable, Walkable {
    @Override
    public void sound() {
        System.out.println(name + " chirps");
    }

    @Override
    public void fly() {
        System.out.println(name + " flies");
    }

    @Override
    public void walk() {
        System.out.println(name + " walks");
    }
}​
함수형 인터페이스가 무엇인가요?
함수형 인터페이스는 '추상 메서드가 딱 하나만 존재하는 인터페이스' 입니다.

Java 8부터 람다 표현식을 사용하기 위해 도입되었고,
@FunctionalInterface를 붙여서 컴파일 타임에 체크할 수 있습니다.

default 메서드나 static 메서드가 있어도 상관없습니다. 추상메소드가 오직 한개일 것!
//예시 코드
@FunctionalInterface
interface MyFunction {
    void run();
}​

Java 기본 제공 함수형 인터페이스 종류는?

이름 역할 메서드  이름설명
Function<T, R> T를 입력받아 R을 반환 R apply(T t) 입력 → 변환 → 출력
Consumer<T> T를 입력받아 소비 void accept(T t) 입력만 받고 반환 없음
Supplier<T> T를 반환 (입력 없음) T get() 값을 공급
Predicate<T> T를 입력받아 boolean 반환 boolean test(T t) 조건 검사
UnaryOperator<T> T를 입력받아 T를 반환 T apply(T t) Function의 특수형 (입출력 타입 동일)
BinaryOperator<T> T를 두 개 입력받아 T를 반환 T apply(T t1, T t2) Function의 특수형 (2입력 1출력, 타입 동일)

//예시 코드
Function<Integer, String> intToString = i -> "Number: " + i;
System.out.println(intToString.apply(5)); // Number: 5

Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello!");

Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get());

Predicate<String> isEmpty = s -> s.isEmpty();
System.out.println(isEmpty.test("")); // true​

람다식(Lambda Expression)이 무엇인가요?
람다식은 '메서드를 하나의 식(expression)처럼 간결하게 표현하는 방법' 이다.

// 문법
(매개변수) -> { 실행문 }​

 

// 예시코드
// 매개변수 x를 받아서 x를 2배해서 반환하는 람다식
Function<Integer, Integer> doubler = x -> x * 2;
System.out.println(doubler.apply(5)); // 10

// x -> x * 2 이 부분이 바로 람다식!

람다식과 익명 내부 클래스 차이점은?
익명 내부 클래스 (Anonymous Inner Class)
클래스를 따로 정의하지 않고, 이름 없는 클래스를 바로 만들어서 객체를 생성하는 방식.
어떠한 인터페이스와 클래스를 대상으로 해도되며, this는 익명 클래스 자기 자신을 나타냅니다.

// 익명내부클래스 예시
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running with anonymous class");
    }
};
r.run();
람다식 내부에서 지역 변수를 사용할 때 제약이 있나요?" (Effectively final)
람다식 내부에서 참조하는 지역 변수는 반드시 'Effectively final'이어야 합니다.
그 이유는 지역 변수는 기본적으로 메소드 호출이 끝나면 스택에서 사라지는데, 만약 람다가 지역 변수를 참조하면서
변경하면 메모리 일관성 문제가 생길 수 있습니다.
그래서 컴파일 시점에 람다가 참조하는 지역 변수는 절대 변하지 않는 값임을 보장하기 위해 Effectively final로 제약을 걸어둔 것입니다.

* Effectively final은 명시적으로 final 키워드를 붙이지 않더라도, 값이 한 번만 할당되고 이후에 변경되지 않는 변수를 의미합니다.

이러한 제약은 익명 내부 클래스도 동일하게 작용합니다.
// 성공 예시
String name = "junki";

Runnable r = () -> System.out.println(name); // OK​

// 실패 예시

String name = "junki";

Runnable r = () -> {
    System.out.println(name);
    // name = "changed";  // ❌ 컴파일 에러: 변경 시도 금지
};​


람다식에서 외부 변수를 변경하려면 어떻게 해야 하나요?
* 기본 전제
람다식은 참조하는 외부 지역 변수를 '읽기'만 허용하고, '직접 변경'은 금지합니다.

배열, AtomicInteger 같은 객체, Wrapper 객체를 사용하면 객체 내부 값을 수정하는 방식으로 변경할 수 있습니다.

// 배열을 이용하는 방법
int[] counter = {0}; // 배열로 감싸기

Runnable r = () -> counter[0]++; // 배열의 원소는 변경 가능하기 때문

r.run();
System.out.println(counter[0]); // 1​

 

// Atomic 타입을 이용하는 방법
AtomicInteger counter = new AtomicInteger(0);

Runnable r = () -> counter.incrementAndGet();

r.run();
System.out.println(counter.get()); // 1

 

// 직접 객체를 감싸는 방법(Wrapper 클래스 이용)
class Wrapper {
    int value;
}

Wrapper wrapper = new Wrapper();
wrapper.value = 0;

Runnable r = () -> wrapper.value++;

r.run();
System.out.println(wrapper.value); // 1

람다식은 내부적으로 어떻게 컴파일되고 동작하나요?
  1. 컴파일 시점
    자바 컴파일러는 람다식을 보고, 이를 invokedynamic 바이트코드를 넣어 처리한다.

  2. 런타임 시점
    JVM이 invokedynamic을 만나면,  LambdaMetafactory라는 시스템 클래스가 동작해서
    필요한 익명 클래스를 메모리상에 즉시 생성한다.

  3. 메모리상에서 람다 객체 생성
    실체는 함수형 인터페이스를 구현한 인스턴스다.
    단, 컴파일할 때 미리 고정된 .class 파일을 만들지 않고, 필요할 때 동적으로 메모리에 올라가는 방식이다.

Java의 main문이 public static void인 이유에 대해서 설명해주세요 

1. public
JVM은 main 메서드를 실행해야 하는데, main이 private이나 default라면 접근할 수 없다.
public이어야 JVM이 클래스 외부에서 main()을 호출할 수 있다.


2. static
자바는 클래스를 메모리에 올린 직후 바로 main()을 호출한다.
객체를 먼저 생성해야 한다면, 생성자 호출해야 하고, 객체 초기화 코드가 필요하고, 복잡해진다.

그래서 객체 없이 바로 호출할 수 있게 static으로 선언하는 것.


3. void
JVM은 main() 메서드의 리턴값을 사용하지 않는다.
단지 프로그램을 시작할 뿐이다. 따라서 반환값이 필요 없기 때문에 void로 선언한다.


4. main
이름은 고정 규칙이다. 다른 이름이면 JVM이 진입점을 찾지 못한다.


5. String[] args
프로그램 실행할 때 명령줄 인자(command line arguments) 를 받을 수 있어야 한다.
그래서 String 배열 형태로 매개변수를 받는다.



요약하면
Java 프로그램은 JVM이 클래스를 메모리에 올린 뒤,
객체 생성 없이(static)
어디서든(public)
반환 없이(void)
main() 메서드로부터 프로그램 실행을 시작합니다.
정적 타입 언어와 동적 타입 언어의 차이점은 무엇인가요?

정적 타입 언어
컴파일할 때 타입을 결정하고, 컴파일러가 코드를 검증하므로 런타임에 타입 에러를 잡아내기 쉽고,

안정성과 예측 가능성이 높습니다.

또한, 코드 작성시 변수형을 결정해줘야 하는 불편함이 있지만, 변수의 형식을 지정함으로써 타입이 맞지 않는 에러를 줄일 수 있습니다.

int num = 5; // Java에서는 num은 무조건 int
num = "hello"; // ❌ 컴파일 에러
// 변수를 선언할 때 타입이 딱 정해짐



동적 타입 언어
런타임 시 타입을 결정하고, 변수에 대한 타입 검사를 실행합니다.

이러한 특성 때문에 유연성과 개발 속도가 빠르며, 코드 작성시 타입에 대한 고민을 하지 않아도 되어 개발자가 자유롭게 코드를 작성할 수 있습니다.

하지만, 런타임에 타입 에러가 발생할 가능성이 높아집니다.

num = 5
num = "hello" # ✅ 문제없음​



java가 파이썬 보다 속도가 빠른 이유
Java는 정적 타입 언어로 컴파일 시점에 타입이 고정되고, JVM과 JIT 컴파일러를 통해 바이트코드를 최적화해서 빠르게 실행합니다.

Python은 동적 타입 언어이고, 인터프리터 방식을 취하며, 런타임 타입 검사 때문에 실행 속도가 느립니다.

결론적으로, Java는 정적 타입과 JIT 최적화 덕분에 Python보다 훨씬 빠릅니다.
제네릭(Generic)이 무엇인가요?
제네릭(Generic)은 데이터 타입을 일반화(Generalize)해서, 나중에 구체적인 타입을 지정하는 문법이다.

사용하는 이유
1) 컴파일 시점에 타입 오류를 잡을 수 있다.
2) 타입에 관계없이 재사용 가능한 클래스/메서드를 만들 수 있다.
3) 어떤 타입을 다루는지 명확하게 알 수 있다.

//예시
List<String> list = new ArrayList<>();

// 여기서 List<String> 은
// String 타입만 넣을 수 있다는 걸 컴파일러가 미리 알게 해줌.​

제네릭 타입 소거(Type Erasure)란 무엇인가요?
Type Erasure(타입 소거) 는
'컴파일 후 제네릭 타입 정보를 제거하고, 일반 타입(Object 등)으로 변환하는 과정'을 의미합니다.
즉, 제네릭은 컴파일 타임까지만 존재하고, 런타임에는 사라진다.

제네릭 타입 소거하는 이유
Java는 하위 호환성을 매우 중요하게 여기는데, 제네릭이 도입된 Java 5 이전에도 이미 많은 라이브러리, 코드가 있었고, 새로 만든 제네릭 코드가 예전 JVM에서도 잘 돌아가게 하기 위함입니다.

// 1. 제네릭 타입
class Box<T> {
    private T value;
    void set(T value) { this.value = value; }
    T get() { return value; }
}​



// 2. 컴파일 후 타입 소거
class Box {
    private Object value;
    void set(Object value) { this.value = value; }
    Object get() { return value; }
}

와일드카드(?)와 타입 소거의 관계는 뭔가요?
와일드카드(?)
제네릭 타입 매개변수 자리에 어떤 타입이든 올 수 있다고 명시하는 것입니다.
컴파일 이후에는 타입 소거에 의해 구체적인 타입 정보가 사라지기 때문에, 와일드카드는 컴파일 타임에 타입 안정성을 보완하는 역할을 합니다.

결론적으로, 와일드카드는 타입 소거가 일어나는 걸 전제로 타입 안전성을 강화하는 장치입니다.


List<?> list;  // 어떤 타입의 List라도 받을 수 있다​

 

Array와 ArrayList의 차이점은 무엇인가요?
Array
크기가 고정된 기본 자료구조로, 메모리 효율이 좋고 빠르지만, 기능이 단순합니다.
오직 index를 통해서만 값을 읽을 수 있습니다.
기본타입(int[], double[]) 등도 저장이 가능합니다.


ArrayList
크기가 자동으로 늘어나는 컬렉션 클래스로, 요소의 추가와 삭제가 편리합니다.
객체타입만 저장이 가능합니다. (기본타입을 저장하려면 Wrapper 클래스를 사용해야함 : Integer, Double 등)
ArrayList와 LinkedList는 어떤 차이가 있나요?

1. ArrayList
내부적으로 연속된 배열을 사용해서 데이터를 저장한다.
특정 인덱스에 바로 접근할 수 있다 (O(1)).
하지만 중간에 삽입/삭제하면 뒤 요소들을 모두 이동시켜야 하므로 느리다 (O(N))

ArrayList<String> list = new ArrayList<>();
list.get(1000); // 아주 빠름 (O(1))
list.add(0, "new"); // 느림 (모든 요소 이동 필요)



2. LinkedList
내부적으로 이중 연결 리스트 구조를 가진다.
각 노드가 데이터와 이전/다음 포인터를 가지고 있어서 삽입/삭제는 빠르다 (포인터만 수정) → O(1).
하지만 인덱스로 접근은 느리다 (앞에서부터 순차적으로 찾아야 함) → O(N).
LinkedList<String> list = new LinkedList<>();
list.addFirst("new"); // 아주 빠름 (O(1))
list.get(1000); // 느림 (1000번 따라가야 함)​


ArrayList는 내부적으로 어떻게 동작하나요?
ArrayList는 내부적으로 Object 배열을 사용합니다.
요소를 추가할 때 배열이 꽉 차면 1.5배로 크기를 늘려서 새 배열로 복사합니다.
인덱스 접근 방식으로 검색은 빠르지만, 요소의 이동이 필요한 중간 삽입/삭제는 느립니다.

결론적으로, ArrayList는 읽기 중심 작업에 최적화된 자료구조입니다.

// 1. 생성
new ArrayList<>(); // 초기 배열은 용량 10으로 생성됩니다​


// 2. 요소의 추가
newCapacity = oldCapacity + (oldCapacity >> 1); // 즉, 1.5배 확장​

 

불변 객체가 무엇인지 설명하고 대표적인 Java의 예시를 설명해주세요.
불변 객체(Immutable Object)
한 번 생성되면 상태(필드 값)가 절대 변하지 않는 객체를 의미합니다.


클래스 설명
String 가장 대표적인 불변 객체. 한 번 생성되면 내용 수정 불가.
Integer, Long, Double (Wrapper 클래스) 기본 타입을 감싸는 불변 래퍼(wrapper) 클래스들.
LocalDate, LocalDateTime (Java 8 날짜 API) 날짜/시간 객체. 불변성으로 설계됨.
UUID 고유 ID를 표현하는 불변 객체.

* 불변객체를 만드는 법
1) final 클래스로 선언(상속 금지)
2) 모든 필드를 private final로 선언
3) setter 메서드 제공하지 않기
4) 생성자에서만 필드를 초기화
5) 객체 내부에서 변경 가능한 객체를 외부에 노출하지 않기

// 예시
public final class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
}​


String 객체가 불변인 이유에 대해 아는대로 설명해주세요
1. 보안적인 측면
String은 파일 경로, 네트워크 주소, DB 연결 문자열 등에 쓰인다. 만약 외부에서 수정 가능하면 보안 구멍이 생길 수 있다
Ex) DB 연결 URL을 외부에 공개하고 수정가능하게하면 심각한 보안 문제

2. 스레드 안전성
String이 불변이면 여러 스레드가 동시에 읽어도 안전하며 락(lock) 없이 공유 가능하다.

3. 성능 최적화 
같은 문자열은 메모리에 공유할 수 있게 되어 새로 생성되지 않고, 메모리 절약과 성능 향상에 기여합니다. (String Pool)
 
4. 해시코드 캐싱
String은 한 번 해시코드를 계산하면 값을 저장해두고, 이후에는 재계산하지 않아 성능이 향상됩니다.
StringBuilder와 StringBuffer 차이는 무엇인가요?
1. StringBuffer
멀티스레드를 고려해서 설계된 클래스입니다.
내부 모든 메서드가 synchronized 되어 있어서 여러 스레드가 동시에 접근해도 충돌이 발생하지 않습니다.
하지만 synchronized 때문에 오버헤드가 커서 단일 스레드에서는 느리다.
StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append(" World");​



2. StringBuilder
Java 5에 새로 추가된 클래스로, 단일 스레드 환경을 위한 최적화 버전입니다.
내부에 synchronized가 없기 때문에 StringBuffer보다 훨씬 빠르다.
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");​

StringBuilder를 멀티스레드 환경에서 쓰면 무슨 문제가 생기나요?
StringBuilder는 동기화 처리가 되어 있지 않기 때문에, 여러 스레드가 동시에 접근하면 데이터 손상이나 예외가 발생할 수 있습니다.

예상치 못한 결과 출력, ArrayIndexOutOfBoundsException 발생 가능성이 있습니다.

결론적으로, 멀티스레드 환경에서는 StringBuffer를 쓰거나 별도로 동기화 처리를 해야 안전합니다.
해시 함수를 많이 쓰는데, 해시로 데이터를 저장하는 방식과 해시 충돌이 나면 어떻게 처리하나요? 
해시를 사용해서 데이터를 저장하는 방식은,
먼저 키(key) 에 대해 해시 함수(hash function) 를 적용해서 해시값(hashcode) 를 구한 다음,
그 해시값을 이용해 내부 배열의 인덱스를 결정하는 방식입니다.

예를 들어, 키가 "apple"이면, 이를 해시 함수에 넣어 해시값을 얻고, 그 값을 배열의 크기로 나눈 나머지(Mod 연산)로 인덱스를 정해서 저장하는 거죠.

그런데 이렇게 저장할 때 서로 다른 키가 같은 인덱스로 매핑되는 경우가 생길 수 있는데, 이걸 해시 충돌(Hash Collision) 이라고 합니다.

해시 충돌을 해결하는 방법은 대표적으로 두 가지가 있습니다.
첫 번째는 체이닝(Chaining) 방식입니다.
충돌이 발생하면, 해당 인덱스에 리스트나 연결리스트를 만들어 여러 데이터를 이어서 저장하는 방법입니다.
자바의 HashMap도 기본적으로 체이닝 방식을 사용합니다.

두 번째는 오픈 어드레싱(Open Addressing) 방식입니다.
이건 충돌이 발생하면, 빈 자리를 찾아서 데이터를 다른 위치에 저장하는 방법입니다.
이때는 선형 탐사(Linear Probing)나 이차 탐사(Quadratic Probing) 같은 전략을 씁니다.
해시를 사용할 때 equals()와 hashCode()를 왜 오버라이딩해야 하나요?
HashMap이나 HashSet 같은 해시 기반 자료구조는 객체를 저장하거나 찾을 때 hashCode()와 equals()를 모두 사용합니다.

먼저 저장할 때는, 객체의 hashCode()를 호출해서 배열의 어느 버킷(bucket) 에 저장할지 결정합니다.
그런데 해시코드는 충돌이 날 수 있기 때문에, 같은 해시코드를 가진 객체끼리는 equals()를 사용해서 진짜 같은 객체인지를 한 번 더 비교합니다.

즉, hashCode()는 버킷을 빠르게 찾기 위한 거고, equals()는 같은 버킷 안에서 객체를 정확히 구분하는 역할을 합니다.

그래서 hashCode()와 equals() 둘 다 제대로 오버라이딩하지 않으면, 데이터가 잘못 저장되거나 검색이 제대로 되지 않는 문제가 생길 수 있습니다.
Object.hashcode는 어떻게 정의되어 있나요?
public class Object { public native int hashCode();
// 인터페이스처럼 구현부가 없음​


Object.hashCode()는 public native int hashCode();처럼 선언만 되어있고, Java 코드 안에 구현 내용이 없습니다.

그 이유는, hashCode의 실제 구현이 native 메서드이기 때문입니다.

native 키워드는 '자바 바깥쪽, JVM 자체(C/C++ 같은 언어)로 구현돼 있다는 뜻'입니다.

이렇게 native로 처리한 이유는, JVM 내부 구조에 맞춰 최적화할 수 있고, 각 JVM(HotSpot, OpenJ9 등) 구현마다
운영체제, 메모리 관리 방식에 맞게 다르게 처리할 수 있도록 하기 위해서입니다.
Checked Exception과 Unchecked exception에 대해서 설명해주세요.
Checked Exception과 Unchecked Exception의 차이는 예외를 컴파일 시점에 강제로 처리해야 하느냐 아니냐에 있습니다.

Checked Exception
컴파일러가 강제하는 예외입니다.
예를 들어 IOException, SQLException 같은 경우, 코드를 작성할 때 반드시 try-catch로 처리하거나 throws로 선언해야 합니다.
주로 파일 입출력, 네트워크 통신처럼 외부 환경 요인 때문에 발생할 수 있는 예측 가능한 문제들을 다룹니다.

Unchecked Exception
RuntimeException을 상속한 예외들로, 컴파일러가 강제하지 않습니다.
NullPointerException, ArrayIndexOutOfBoundsException 같은 예외가 대표적인데, 이건 개발자의 실수로 발생하는 경우가 많습니다.
그래서 "알아서 고쳐야 하는 문제"라고 보고, 컴파일 단계에서 굳이 강제하지 않습니다.

정리하면, Checked는 외부 오류를 대비하기 위해 강제하고, Unchecked는 프로그래밍 오류를 개발자가 책임지는 구조입니다.
리플렉션에 대해서 설명해주세요.
리플렉션은 자바 프로그램이 실행 중에 클래스, 메서드, 필드 등의 정보를 읽고 조작할 수 있는 기능입니다.

즉, 컴파일 시점이 아닌 런타임에 클래스 구조를 분석, 동적으로 객체를 생성하고 메서드를 호출할 수 있습니다.

주로 프레임워크 개발(Spring, Hibernate), 라이브러리 동적 로딩, 테스트 자동화(JUnit) 등에 사용됩니다.

단점으로는 성능 저하, 타입 안정성 저하, 보안 이슈 같은 위험이 있습니다.
( 클래스 구조를 런타임에 분석해야 하고, 메서드, 필드 정보를 찾아야 하며, 접근 권한이 막혀 있으면 setAccessible(true)로 강제 열어야 하고, 실제 메서드 호출도 일반 호출보다 경로가 길어지기 성능이 저하됨 )
라이브러리와 프레임워크 차이점이 뭘까요? 
라이브러리와 프레임워크의 가장 큰 차이는 누가 프로그램의 흐름을 제어하느냐입니다.

라이브러리
필요할 때 내가 직접 호출해서 사용하는 도구입니다.
즉, 개발자가 흐름을 주도하고, 라이브러리는 특정 기능만 제공합니다.
예를 들면, Java의 Collections나 Apache Commons 같은 게 있습니다.

프레임워크
프레임워크가 전체 흐름을 제어하고, 내가 그 안에 코드를 끼워 넣는 구조입니다.
즉, 프로그램의 큰 틀을 프레임워크가 잡고,개발자는 필요한 부분만 구현합니다.
Spring, Django 같은 게 대표적입니다.
IoC(제어의 역전)와 DI(의존성 주입)의 차이는?
IoC는 '제어의 역전(Inversion of Control)'의 줄임말로, 프로그램의 흐름 제어 권한을 개발자가 아니라 외부 컨테이너가 가지는 것을 의미합니다.

전통적으로는 개발자가 직접 객체를 생성하고 연결했지만, IoC를 적용하면 컨테이너가 객체를 생성하고 주입하거나 관리합니다.

이 개념 안에 포함된 구체적인 구현 방법 중 하나가 바로 DI(Dependency Injection) 입니다.

DI는 IoC를 구현하는 방법 중 하나로, 객체가 직접 의존 대상을 생성하는 대신 외부에서 주입받는 것을 의미합니다.
즉, 필요한 의존 객체를 코드 내부에서 new로 만들지 않고, 컨테이너가 생성해서 알아서 넣어줍니다.

정리하면, IoC는 전체 설계 철학(흐름을 외부에 맡김)이고, DI는 그 철학을 실현하는 구체적인 방법(외부에서 의존성 주입)입니다.
직렬화와 역직렬화에 대해서 설명해주세요.
직렬화(Serialization)
객체를 바이트 형태로 변환해서 저장하거나 전송할 수 있게 만드는 과정입니다.
즉, 메모리에 존재하는 객체를 파일, 네트워크, 데이터베이스 등에 저장하거나 보내기 위해 바이트 스트림으로 변환하는 것입니다.

역직렬화(Deserialization)
이 바이트 데이터를 다시 원래의 객체로 복원하는 과정을 말합니다.

Java에서는 Serializable 인터페이스를 구현하면 직렬화가 가능하고,
ObjectOutputStream, ObjectInputStream 클래스를 이용해 객체를 직렬화하거나 역직렬화할 수 있습니다.

정리하면,직렬화는 객체를 저장/전송 가능하게 바꾸는 것, 역직렬화는 그걸 다시 복원하는 것입니다.
serialVersionUID를 선언해야하는 이유가 있을까요?
serialVersionUID는 직렬화된 객체와 역직렬화할 때 사용하는 고유 식별자입니다.

직렬화된 데이터에는 클래스의 serialVersionUID 값이 함께 저장되는데,
나중에 역직렬화할 때 현재 클래스의 serialVersionUID와 저장된 serialVersionUID를 비교해서
같은 클래스인지 확인합니다.

만약 serialVersionUID가 다르면, InvalidClassException이 발생해서 역직렬화를 막습니다.
(즉, 구조가 다른 클래스를 잘못 읽는 걸 방지.)

직접 serialVersionUID를 선언하지 않으면, JVM이 클래스 구조를 기반으로 자동 생성하는데,
클래스 구조가 조금만 바뀌어도 값이 달라질 수 있어서 직접 명시하는 것이 안정적입니다.

정리하면, serialVersionUID는 직렬화 호환성을 보장하기 위해 필요하며, 안정성을 위해 개발자가 명시하는 것이 좋습니다.
JDBC 가 무엇인가요
JDBC는 Java Database Connectivity의 약자로, 자바 프로그램과 데이터베이스를 연결해주는 표준 API입니다.

데이터베이스에 연결하고, SQL 쿼리를 실행하고, 결과를 받아오고, 트랜잭션을 관리할 수 있습니다.

JDBC는 추상화된 인터페이스를 제공하고, 각 DBMS(예: MySQL, Oracle, PostgreSQL) 업체가 JDBC 드라이버를 구현해서 실제 통신을 담당합니다.

개발자는 JDBC API만 사용하면, DB 종류에 관계없이 코드 수정 없이 데이터베이스 작업을 할 수 있습니다.

정리하면, JDBC는 자바와 데이터베이스 사이를 연결해주는 표준 통신 방법입니다.
생성자에 대해 설명해주세요
생성자는 객체가 생성될 때 호출되는 특수한 메서드입니다.

주로 객체의 초기 상태를 설정(초기화) 하기 위해 사용합니다.

생성자는 클래스 이름과 같아야 하고, 리턴 타입이 없습니다.

자바에서는 명시적으로 생성자를 만들지 않으면 기본 생성자(매개변수 없는 생성자)가 자동으로 제공되고,
생성자를 하나라도 정의하면 기본 생성자는 자동으로 만들어지지 않습니다.

또한, 오버로딩(Overloading) 을 통해 하나의 클래스에 여러 개의 생성자를 정의할 수 있습니다.
매개변수에 따라 다른 방식으로 객체를 초기화할 수 있죠.
List, Map, Set에 대해서 설명해주세요 
List, Set, Map은 모두 자바 컬렉션 프레임워크에 속하는 자료구조입니다.

List
순서가 보장되고, 중복을 허용하는 자료구조입니다.
배열처럼 인덱스 기반으로 요소를 관리할 수 있고, 대표적으로 ArrayList와 LinkedList가 있습니다.

Set
순서를 보장하지 않거나 특정 규칙에 따라 정렬되고, 중복을 허용하지 않는 자료구조입니다.
같은 값을 두 번 넣으면 하나만 저장됩니다. HashSet, TreeSet이 대표적입니다.

Map
키(key)와 값(value)의 쌍(pair)으로 데이터를 저장하는 자료구조입니다.
키는 중복될 수 없고, 하나의 키에 하나의 값만 매핑됩니다.
대표적으로 HashMap과 TreeMap이 있습니다.

정리하면, List는 순서와 중복 허용, Set은 중복 불가, Map은 키-값 쌍 관리로 이해할 수 있습니다.
static 과 non-static 에 대해서 설명해주세요 
static은 클래스 레벨에서 공유되는 영역을 의미합니다.

static 키워드가 붙은 변수나 메서드는 객체를 생성하지 않고 클래스 이름으로 바로 접근할 수 있고,
프로그램 전체에서 하나만 존재합니다.

static 변수는 모든 객체가 공유하는 공통 데이터를 저장할 때 사용하고,
static 메서드는 특정 인스턴스에 의존하지 않는 기능을 만들 때 사용합니다.

반대로 non-static은 각 객체(인스턴스)마다 별도로 존재하는 변수나 메서드입니다.
객체가 생성되어야만 사용할 수 있고, 각 객체마다 값이 다를 수 있습니다.

정리하면, static은 클래스 전체가 공유하고, Non-static은 각 객체마다 개별적으로 존재하는 것입니다.
inner class 의 장점에 대해 설명해주세요
Inner Class는 클래스 내부에 선언된 클래스를 의미합니다.

내부 클래스를 사용하면 외부 클래스와 밀접한 관계가 있는 클래스를 논리적으로 묶을 수 있어서 코드의 가독성과 응집도를 높일 수 있습니다.

또한, 외부 클래스의 private 멤버에도 직접 접근할 수 있기 때문에, 외부 클래스와 긴밀히 협력해야 하는 경우 코드가 훨씬 간결해집니다.

이벤트 핸들러, 콜백 객체, 일시적으로 사용되는 클래스 같은 경우, 굳이 별도 파일로 만들지 않고 외부 클래스 안에 정의해서 코드를 구조적으로 깔끔하게 유지할 수 있습니다.
박싱(Boxing)과 언박싱(Unboxing)에 대해 설명해주세요.
박싱(Boxing)
기본 타입(primitive type)을 대응하는 객체(wrapper class)로 변환하는 것을 의미합니다.
예를 들어, int를 Integer로, double을 Double 객체로 감싸는 것입니다.

언박싱(Unboxing)
객체(wrapper class)를 기본 타입으로 변환하는 것을 의미합니다.

자바 5부터는 오토박싱(Auto-Boxing) 과 오토언박싱(Auto-Unboxing) 기능이 도입되어서,
개발자가 명시적으로 변환하지 않아도 컴파일러가 자동으로 변환해줍니다.
new String()과 리터럴(””)의 차이에 대해 설명해주세요.
자바에서 new String()과 문자열 리터럴("")은 메모리에 저장되는 방식이 다릅니다.

문자열 리터럴
String Constant Pool(문자열 상수 풀) 에 저장됩니다.
이미 동일한 문자열이 풀에 있으면 새 객체를 만들지 않고 기존 객체를 재사용합니다. 그래서 메모리 사용이 효율적입니다.

new String()
Heap 메모리에 새로운 String 객체를 강제로 생성합니다.
같은 내용이라도 항상 새로운 인스턴스가 만들어지기 때문에, 메모리 낭비가 발생할 수 있습니다.

클래스와 객체에 대해 설명해주세요.
클래스(Class)
객체를 만들기 위한 설계도나 틀입니다.
속성(변수)과 동작(메서드)을 정의해서, 어떤 형태의 데이터를 다루고 어떻게 동작할지 미리 설계해놓은 구조입니다.

객체(Object)
클래스를 기반으로 실제 메모리에 생성된 실체(instance) 입니다.
예를 들어, '자동차'라는 클래스가 있다면, 실제로 생산된 각각의 자동차 한 대 한 대가 객체입니다.
원시타입과 참조타입에 대해서 설명해주세요.
자바에서 타입은 크게 원시 타입(Primitive Type) 과 참조 타입(Reference Type) 으로 나눌 수 있습니다.

원시 타입
int, double, boolean 같은 기본 자료형으로, 실제 값 자체를 메모리에 저장합니다.
가볍고 빠르며, Stack 메모리에 저장되는 경우가 많습니다.

참조 타입
객체의 주소(참조값)를 저장합니다.
String, 배열, 사용자 정의 클래스 등이 참조 타입에 해당하며, Heap 메모리에 객체가 실제로 생성되고, 변수에는 그 객체를 가리키는 참조값만 저장됩니다.

Getter, Setter를 사용하는 이유는 무엇인가요?
Getter와 Setter는 객체의 속성(필드)을 직접 외부에 노출하지 않고, 제어된 방식으로 접근하거나 수정하기 위해 사용하는 메서드입니다.

Getter는 필드 값을 외부에 읽어올 때 사용하고,
Setter는 필드 값을 외부에서 변경할 때 사용합니다.

이렇게 메서드를 통해 접근을 제한하면, 데이터 보호가 가능하고,
필요한 경우 Setter나 Getter 안에 검증 로직(Validation)을 추가할 수도 있습니다.

또한, 내부 구현이 바뀌더라도 외부 코드를 수정하지 않고 유지할 수 있어 유지보수성과 확장성이 좋아집니다.
final / finally / finalize 의 차이를 설명해주세요 
final, finally, finalize는 이름이 비슷하지만 각각 용도가 다릅니다.

final
변경을 막기 위한 키워드입니다. 
변수에 붙이면 값을 변경할 수 없고, 메서드에 붙이면 오버라이딩을 막고, 클래스에 붙이면 상속을 막습니다.

finally
예외 처리에서 try-catch-finally 블록 중 finally 블록을 의미합니다.
예외 발생 여부와 관계없이 무조건 실행되는 코드를 작성할 때 사용합니다.
(주로 리소스 정리, 연결 종료 등에 사용)

finalize
객체가 가비지 컬렉션되기 직전에 호출되는 메서드입니다.
Object 클래스에 있는 메서드고, 필요하면 오버라이딩해서 객체 소멸 직전에 정리 작업을 할 수 있습니다.
다만 현재는 거의 사용되지 않습니다.