이끌든지 따르든지 비키든지

Software Development/Tech Interview

[기술 면접 단골 질문] Java

SeongHo5 2024. 2. 28. 21:38

Java는? Java의 장 · 단점


 

Java는 플랫폼 독립적인 언어로, 자바 가상 머신(JVM) 위에서 동작하는 객체 지향 프로그래밍 언어입니다.

 

  • 장점
    • JVM 위에서 동작하기 때문에 운영체제에 독립적이다.
    • 가비지 컬렉터가 메모리를 관리해주기 때문에 편리하다.
  • 단점
    • JVM 위에서 동작하기 때문에 실행 속도가 상대적으로 느리다.
    • 다중 상속이나 타입에 업격하는 등 제약이 있는 것이 많다.
꼬리질문: Java가 다중 상속을 지원하지 않는 이유

다중 상속을 지원하면, 다이아몬드 문제가 발생할 수 있기 때문입니다.

Java가 다중 상속을 지원하지 않는 이유는 '다이아몬드 문제'를 방지하기 위해서입니다.
다중 상속을 허용하면 두 개 이상의 상위 클래스에서 상속받은 하위 클래스에서 같은 메서드를 가지게 될 경우, 하위 클래스에서는 어떤 상위 클래스의 메서드를 호출해야 할지 명확하지 않게 되는 문제가 발생할 수 있습니다.

 

JVM의 구조와 Java의 실행방식을 설명해 주세요.


 

JVM은 크게 클래스 로더 / 런타임 데이터 영역 / 실행 엔진 세 가지 영역으로 나뉩니다.

 

클래스 로더는 Java 클래스들을 로드하고, 링크, 초기화하는 역할을 합니다. 동적 로드를 지원하기 때문에, 실행 중에 적절한 클래스를 찾아 메모리에 로드할 수 있습니다.

 

런타임 데이터 영역은 프로그램 실행 중에 필요한 다양한 데이터를 저장하는 공간입니다.

 

실행 엔진은 바이트 코드를 명령어 단위로 읽어 실행합니다. 인터프리터, JIT 컴파일러, GC를 포함하고 있습니다.

 

 

💡 JVM의 메모리 구조

힙(Heap): Java에서 생성된 모든 객체의 인스턴스가 저장되는 영역입니다. GC의 관리를 받습니다.

스택(Stack): 각 스레드마다 생성되며, 메서드 호출과 관련된 데이터를 저장하는 영역입니다. 메서드 호출마다, 해당 메서드의 로컬 변수, 파라미터 등을 저장합니다.

메서드 영역(Method Area): 메서드 영역은 클래스 정보, 정적 변수(static) 등을 저장하는 공간입니다. 클래스 로더에 의해 로드된 클래스들의 정보가 저장되고, 프로그램 실행부터 종료까지 유지됩니다.

 

 

실행 과정

  • 자바 컴파일러(javac)가 소스코드(.java)를 읽어 바이트코드(.class)로 변환합니다.
  • 클래스 로더를 통해 class 파일들을 JVM으로 로딩합니다.
  • 로딩된 class파일들은 실행 엔진을 통해 해석됩니다.
  • 해석된 바이트코드는 런타임 데이터 영역에 배치되어 실질적인 수행이 이루어집니다.

 

 

GC가 무엇인지, 필요한 이유는 무엇인지, 동작방식에 대해 설명해 주세요.


 

가비지 컬렉터(Garbage Collector, GC)란, 사용되지 않는 객체를 식별하고, 해제하는 작업을 자동으로 수행하는 프로세스를 말합니다.

 

Java는 개발자가 직접 메모리를 해제하는 기능을 지원하지 않기 때문에, 가비지 컬렉터가 이를 대신 관리하며 메모리 누수를 방지하고, 프로그램의 안정성을 높입니다.

 

가비지 컬렉터는 여러 가비지 컬렉션 알고리즘을 활용해 더 이상 참조되고 있지 않은 객체를 식별하고, 이 객체들에 할당된 메모리를 해제해 메모리를 관리합니다.

 

 

 

클래스, 객체, 인스턴스?


 

클래스는 객체를 생성하기 위한 템플릿, 설계도입니다.

객체는 클래스를 기반으로 선언된 대상을 말하고, 클래스의 인스턴스라고도 합니다.

 

인스턴스는 객체에 메모리가 할당되어 실제로 활용되는 실체를 말합니다.

 

 

오버라이딩과 오버로딩이 무엇이며 어떤 차이가 있을까요?


 

오버라이딩은 상위 클래스에서 상속받은 메서드를 재정의하는 것을 말합니다.

오버라이딩된 메서드는 상위 클래스의 메서드와 동일한 시그니처(이름, 파라미터)를 가져야 합니다.

 

+ @Override 어노테이션을 꼭 사용해야 하는 이유?

이 어노테이션을 통해 해당 메서드가 상위 클래스에서 상속받은 메서드를 재정의하고 있음을 명시할 수 있습니다. 이를 통해 컴파일 타임에 이 메서드를 한 번 더 체크해 오타 등 오류를 방지할 수 있습니다.

 


오버로딩은 같은 이름을 가진 메서드를 여러 개 정의하지만, 파라미터의 타입이나 개수를 다르게 하는 것을 말합니다.

오버로딩을 통해 같은 동작을 하는 메서드가 여러 입력 형태에 대응하도록 구성할 수 있습니다.

 

 

인터페이스와 추상클래스의 차이점에 대해 설명해 주세요.


 

추상클래스는 클래스가 공통적으로 가져야 할 구조와 기능을 정의할 때 사용합니다. - '이것은 무엇이다.'

  • 단일 상속만 지원합니다. 추상 클래스를 상속하는 클래스는 서로 밀접한 연관 관계가 있습니다.
  • 모든 접근 제어자를 사용할 수 있습니다.
  • 추상 메서드와 일반 메서드를 모두 선언할 수 있고, 변수와 상수를 선언할 수 있습니다.

 

인터페이스는 다양한 클래스가 동일한 동작을 한다는 것을 보장하는데 사용됩니다. - '이것을 할 수 있다'

  • 다중 구현이 가능합니다. 인터페이스를 구현하는 클래스 간에는 서로 연관 관계가 없을 수도 있습니다.
  • public 접근 제어자만 사용할 수 있습니다.
  • 추상 메서드만 선언할 수 있습니다.

 

원시 타입과 참조 타입?


 

원시 타입(Primitive Types):

원시 타입은 Java에서 기본적으로 제공하는 데이터 타입으로, int, long, char, boolean 등이 있습니다.

JVM의 스택 영역에 생성되며, 실제 값을 저장하기 때문에 null을 담을 수 없습니다.

참조 타입(Reference Types):

참조 타입은 기본 타입을 제외한, 객체의 주소를 저장하는 타입입니다.

참조 타입은 실제 객체는 힙 영역에 생성되며, 스택 영역에는 해당 객체의 메모리 주소를 저장합니다.




정적(static)이란 무엇인가요?


 

static은 클래스 멤버라고도 하며, 메서드, 변수 등에 주로 사용합니다. 클래스의 인스턴스가 아닌 클래스 자체에 속하게 되며, 인스턴스 생성 없이도 클래스 이름을 통해 참조 · 호출할 수 있습니다.

 

static 멤버는 클래스가 로드될 때 한 번 초기화되며, 프로그램 실행 중에 변경되지 않습니다. 

 

 

로깅에 sout를 쓰지 않는 이유?


 

TRACE / DEBUG / INFO / WARN / ERROR 와 같은 로깅 레벨을 사용할 수 없습니다.

 

로깅 프레임워크를 사용하지 않기 때문에, 로그가 파일로 저장되지 않고 & 애플리케이션 종료 시 휘발된다.

 

sout는 내부적으로 synchronized 블럭 내에서 실행되기 때문에, 성능 저하의 우려가 있습니다.

 

synchronized 키워드는?


 

이 키워드가 사용된 메서드나 블럭은 한 번에 하나의 스레드만 접근할 수 있다.

멀티 스레드 환경에서 여러 스레드가 안전하게 자원에 접근할 수 있도록 하지만, 성능 저하 우려가 있다.

 

 

어노테이션에 대해서 설명해 주세요.


 

어노테이션은 Java 코드에 추가할 수 있는 메타데이터 형태로,

컴파일러에게 코드 작성 방법을 알려주거나, - @Override, @SuppressWarning

런타임 시 특정 기능을 실행하게 하는 등 - @Transactional, @Test 의 용도로 사용됩니다.

 

 

컬렉션 프레임워크에 대해서 설명해 주세요.


 

다양한 데이터 구조를 구현하는 클래스와 인터페이스의 집합입니다. 

이 프레임워크는 List, Set, Map 등의 인터페이스와 이를 구현하는 ArrayList, HashSet, HashMap 등의 클래스를 제공합니다.

  • List
    • 데이터를 순차적으로 저장
    • 데이터 중복 & null 허용
  • Set
    • 순서 없이 Key로만 데이터를 저장
    • Key는 중복되거나 null일 수 없음
  • Map
    • 순서 없이 Key, Value로 데이터를 저장
    • Key는 중복되거나 null일 수 없음 / Value는 중복될 수 있음

 

 

제네릭에 대해서 설명해 주세요.


 

제네릭은 클래스, 인터페이스, 메서드를 정의할 때 타입을 파라미터로 사용할 수 있게 해주는 기능입니다.

 다양한 타입의 객체를 다루는 코드를 보다 안전하고, 읽기 쉽게 작성할 수 있습니다.

 

 

동일성(identity)과 동등성(equality)에 대해 설명해 주세요.


 

동일성 (Identity)
동일성은 두 객체가 메모리 상에서 정확히 같은 주소(위치)를 가리키는 지를 나타냅니다.  '==' 연산자를 사용하여 두 객체의 동일성을 비교합니다. 

 

동등성 (Equality)
동등성은 두 객체의 상태나 값이 같은지를 나타냅니다. equals() 메소드를 사용하여 두 객체의 동등성을 비교합니다. 

 


hashCode()와 equals()가 무엇인가요? Map에서 객체를 키로 사용하면 어떻게 되나요?


 

hashCode() 메소드는 객체의 해시 코드를 반환하는데 사용되며, 

equals() 메소드는 두 객체가 동등한지 비교하는 데 사용됩니다. 일반적으로 객체의 내용이 같으면, 동등하다고 간주합니다.

 

해시 코드는 객체를 HashMap, HashSet 등과 같은 자료 구조에 저장할 때 사용된다.
동일한 객체는 항상 같은 해시 코드를 반환해야 합니다.

 

Map에서 객체를 키로 사용할 경우, 객체의 동등성을 기반으로 키의 유일성을 결정하게 됩니다. 

이 때 만약, hashCode()와 equals() 메소드가 적절히 재정의(오버라이딩)되지 않았다면, 동등한 두 객체가 서로 다른 키로 인식되는 등, 예기치 못한 문제가 발생할 수 있습니다.

 

예방하는 방법

일관된 해시 코드를 생성하고, 동등성을 올바르게 확인할 수 있도록 hashCode()와 equals()를 적절하게 재정의해야 합니다.

그리고, Map의 키로는 가능한 불변 객체를 사용하는 것이 좋습니다.
(Integer, Long, String, BigDecimal 등)

 

 

Checked Exception과 Unchecked Exception에 대해 설명해 주세요. 스프링 트랜잭션 추상화에서 rollback 대상은 무엇일까요?


 

두 예외의 가장 큰 차이점은, 컴파일러가 예외 처리를 강제하는지 여부 / RuntimeException을 상속하는지 여부입니다.

 

Checked Exception은 주로 외부 리소스에 대한 접근이나 입. 출력 작업 등에서 발생하는 예외로, 발생할 수 있는 코드에서 예외를 처리하지 않으면 컴파일 오류가 발생합니다. 

 

Unchecked Exception는 주로 프로그램의 로직 오류나, 개발자의 실수로 인해 발생할 수 있는 예외로, 컴파일러가 예외 처리를 강제하지 않습니다.

 

스프링 트랜잭션은 기본적으로 Unchecked Exception에 대해서만 롤백을 수행합니다.

앞서 말씀드린 대로, Checked Exception은 예외 처리가 강제되기 때문에, 개발자에 의해 컴파일 단계에서 예외 처리가 이루어졌었을 것이라 기대하기 때문입니다.

 

ThreadPoolExecutor는 어떻게 동작하나요?


 

우선, 'ThreadPoolExcecutor'는 Java에서 제공하는 구현체 중 하나로, 스레드 풀을 관리하고 작업을 처리하는데 사용합니다.

 

 

1. 새로운 작업이 제출되면, 먼저 현재 실행 중인 Thread 수가 코어 풀 크기보다 작은지 확인합니다. 만약 작다면, 새로운 스레드를 생성하여 이 작업을 실행합니다. ( Current Thread Count  < Core Pool Size )

2.실행 중인 스레드의 수가 코어 풀 크기에 도달했지만, 최대 풀 크기에는 도달하지 않았다면, 작업은 Work Queue에 추가됩니다. (Current Thread Count > Core Pool Size && Current Thread Count < Maximum Pool Size)


(작업 큐가 가득 차 있고, 실행 중인 스레드의 수가 최대 풀 크기에 도달했다면, 새로운 작업에 대해 거부 정책을 실행)


3. 작업이 완료되면, 큐에서 다음 작업을 계속 가져와 실행하거나, Thread Pool로 반환됩니다.

 

Core Pool Size: 스레드 풀이 유지해야 하는 최소 스레드 수입니다. 작업이 추가되면, 이 수만큼의 스레드가 먼저 생성됩니다.
Maximum Pool Size: 풀이 허용하는 최대 스레드 수입니다. Core Pool Size를 초과하는 작업이 들어오면, 최대 이 수까지 스레드가 생성될 수 있습니다.
Keep Alive Time: 최대 크기를 초과한 스레드들이 작업 없이 대기할 수 있는 시간입니다. 이 시간이 지나면, 초과된 스레드는 종료됩니다.
Work Queue: 작업이 스레드에 할당되기 전에 저장되는 대기열입니다. 스레드 풀이 바쁠 때(모든 스레드가 작업 중일 때) 작업은 이 큐에 들어갑니다.



커스텀 불변 클래스를 어떻게 만드나요? 자바에서 불변 클래스의 예는 무엇인가요?


 

클래스를 final 선언합니다. 모든 필드를 private / final 선언합니다. Setter 등 변경자를 제공하지 않습니다.

 

 

자바에서 불변 클래스의 예시로는 String, Integer, BigDecimal 등의 일부 Wrapper 클래스가 있습니다.

 


깊은 복사와 얕은 복사가 무엇인가요?


 

얕은 복사(Shallow Copy)는 객체의 필드 값을 새 객체로 복사하지만, 필드가 참조하는 객체는 복사하지 않습니다. 이는 원본과 복사본이 내부 객체를 공유한다는 것을 의미합니다.

깊은 복사(Deep Copy)는 객체와 그 객체가 참조하는 모든 객체들을 재귀적으로 복사합니다. 이는 원본과 복사본이 완전히 독립적임을 의미합니다.

 



CompletableFuture가 무엇인가요?


 

Java 8에서 도입된 비동기 프로그래밍을 위한 클래스입니다. 여러 비동기 연산을 파이프라인으로 연결하고, 결과를 조합하고, 에러를 처리하고, 비동기 연산을 동기 연산처럼 처리할 수 있게 해줍니다.

 

 

 

Concurrent Collection이 무엇인가요?


 

멀티스레드 환경에서 여러 스레드가 안전하게 동시에 접근할 수 있는 컬렉션입니다.

동기화 메커니즘이 내부적으로 구현되어, 여러 스레드가 동시에 컬렉션을 수정하려 할 때, 의도치 않게 발생할 수 있는 문제를 예방하고, 안전하게 동시 접근을 가능하게 합니다.

 

Arrays.sort()와 Collections.sort()에서 사용되는 알고리즘은 무엇인가요?


 

Arrays.sort()는 Primitive Type의 배열을 정렬하는 경우, DualPivotQuicksort 알고리즘을, 객체 배열을 정렬하는 경우 TimSort 알고리즘을 사용합니다.

 

Collections.sort()는 List 컬렉션을 정렬할 때 사용하며, 내부적으로는 Arrays.sort()를 호출합니다. 

리스트는 객체의 컬렉션이므로, 내부적으로 TimSort 알고리즘을 사용해 정렬을 수행한다고 할 수 있습니다.

 



자바에서 커스텀 어노테이션을 어떻게 만드나요?


 

@interface 키워드를 사용해 커스텀 어노테이션을 생성할 수 있습니다. Target, Retention을 통해 적용 대상과 어노테이션 정보 유지 정책을 지정하고, 필드를 작성해 필요한 속성을 정의할 수 있습니다.

 


String의 join() 메소드의 용도는 무엇인가요?


 

String.join() 메소드는 여러 문자열 요소를 하나의 문자열로 결합할 때 사용됩니다. 

이 메소드는 구분자를 첫 번째 인자로 받고, 이어지는 문자열 요소들을 이 구분자로 결합합니다.

 

<Java 8부터 추가된 기능>

default / static 키워드


 

static 메서드: 인터페이스 내에서 정적 메서드를 정의하고, 인터페이스 이름을 통해 호출할 수 있습니다.

 

default 메서드: 인터페이스 내의 메서드에 대해 기본 구현을 제공할 때 사용할 수 있습니다. 인터페이스를 구현하는 클래스에서는 변경 없이 이 메서드를 사용하거나, 재정의해 사용할 수도 있습니다.



람다 표현식


 

간결한 방식으로 익명 함수를 제공합니다.  주로 함수형 인터페이스의 인스턴스를 생성할 때 사용됩니다. 람다 표현식을 사용하면 코드의 양을 줄이고, 코드의 가독성을 향상시킬 수 있습니다.

 


Functional Interface


 

오직 하나의 추상 메소드를 가진 인터페이스입니다. 

@FunctionalInterface 어노테이션을 사용하여 함수형 인터페이스임을 명시할 수 있습니다. 람다 표현식과 함께 사용되며, 대표적인 예로 Runnable, Callable, Comparator 등이 있습니다.


Optional API


 

null이 될 수 있는(Nullable)한 T(Type) 타입 객체를 감싸는 래퍼 클래스입니다. NPE(NullPointerException)을 방지하고, 명시적으로 null을 핸들링할 수 있도록 합니다.

 

Stream API


 

선언적인 방식(어떤 작업을 수행할 지만 명시하고, 작업은 Stream API 처리)으로 컬렉션을 다룰 수 있도록 지원하는 API입니다. 필터링, 매핑, 정렬, 병렬 처리 등 다양한 기능을 지원합니다.

 

 

Method Reference


 

람다 표현식을 간단하게 대체하고 코드의 가독성을 높일 수 있다.

 

e.g. Integer::parseInt / SomeDto::of

 

<Java 16부터 추가된 기능>

Record


 

record는 DTO를 정의하기 위해 새로 도입된 타입입니다.

모든 필드에 대한 Getter, equals(), hashCode(), toString() 메서드를 생성합니다.

 

보일러플레이트 코드를 크게 줄이고, 간결하게 데이터 모델을 표현할 수 있습니다.

 

 

<Java 17부터 추가된 기능>

 

sealed 클래스


 

sealed 클래스는 permits 키워드를 통해 이 클래스를 상속할 수 있는 자식 클래스나 인터페이스를 명시적으로 지정해, 클래스 계층을 보다 더 엄격하게 제어할 수 있습니다.

 

 

 

'Software Development > Tech Interview' 카테고리의 다른 글

[기술 면접 단골 질문] Spring  (0) 2024.03.01