[면접공부] 자바 - 면접질문(1) (feat.gpt)

반응형
반응형

오늘 국비에서 모의 면접을 진행하였다.
나는 6개중 제대로 알고있는건 한개라고 하셨고 나머지는 개념정도는 알고있는 수준이라고 하셨다.
그리고 성능적인 측면도 고려해서 공부를 해보라고 하셨다.
다음은 내가 질문을 받은 내용들이다.

  1. try with reouces (이건 pass 나중에 정리예정임)
  2. String vs StringBuffer vs StringBuilder
  3. volatile
  4. 제네릭 > 타입이레이저
  5. 인티저캐시
  6. auto boxing, unBoxing
 

이렇게 질문을 하셨고 4,5번은 애초에 틀리라고 낸 문제라고 하셨다.
1번은 내가 알고 있기때문에 패스하고 
2번부터 6번까지 다시 공부 예정이다.
일단 2번은 알고는 있었지만 너무 장황하다는 평가를 받았다.
애초에 초기화 방식을 여쭤본것이 아닌데 나는 초기화 방식도 말했다. 그러면 안되는데 아는거라고 이렇게 말했던거 같다.
암튼 다시 말해보면 String은 불변객체로 연산시 메모리에 영향을 주는 반면, StringBuffer와 StringBuilder는 가변객체로 연산시 메모리에 영향을 주지 않는다는걸로 알고 있습니다. 또, StringBuffer와 StringBuilder의 차이점은 StringBuffer가 멀티 쓰레드 환경에서 동작을 하며, StringBuilder가 싱글 스레드 환경에서는 성능이 더 좋은 걸로 알고 있습니다.라고 말했으면 어땠을까? 이걸 gpt에 돌려보자.
그 결과 String은 불변 객체라서 문자열 연산 시 새로운 객체가 생성되기 때문에 메모리에 부담을 줄 수 있습니다. 반면에 StringBuffer와 StringBuilder는 가변 객체이기 때문에 문자열을 반복적으로 조작할 때 메모리 사용 측면에서 훨씬 효율적입니다.
두 클래스의 차이는 동기화 여부인데요, StringBuffer는 synchronized 키워드로 메서드가 동기화되어 있어서 멀티 스레드 환경에서 안전하게 사용할 수 있는 반면, StringBuilder는 동기화를 제공하지 않기 때문에 싱글 스레드 환경에서 성능이 더 좋습니다.
그래서 저는 상황에 따라 적절하게 선택해서 사용하려고 합니다.

이 처럼 다른 질문들도 이렇게 대답을 해보자.
3번째 volatile는 자바에서 동시성을 처리하는 방식으로 가시성을 보장하는 방식으로 알고 있습니다. 찾아보니 동기화를 처리하는 방식은 아닌듯 하다. 자바에서 volatile 키워드는 멀티 스레드 환경에서 변수의 가시성(visibility) 을 보장해주는 역할을 합니다.
일반적으로 스레드들은 각자의 CPU 캐시를 사용하기 때문에, 하나의 스레드가 변수 값을 바꿔도 다른 스레드가 그 변경을 바로 인지하지 못할 수 있는데요, volatile을 사용하면 해당 변수는 메인 메모리(main memory) 에 바로 반영되고, 다른 스레드들도 항상 최신 값을 읽을 수 있게 됩니다. 


더 깊게 들어가면 synchronized와의 차이점이나 volatile이 사용되는 대표적인 예로 double-checked locking 같은 패턴도 있다고 한다.
어디 한번 살펴보자 (추가적으로 atomic도 찾아보자.)

volatile

  • 가시성(visibility) 을 보장함.
  • 한 스레드가 변경한 값을 다른 스레드가 즉시 인식할 수 있도록 함.
  • 하지만 원자성(atomicity) 은 보장하지 않음.
    (즉, 복잡한 연산이 중간에 끊길 수 있음)
  • 간단한 플래그 값 변경 같은 상황에 적합.

 synchronized

  • 가시성도 보장하고, 원자성도 보장함.
  • 한 번에 하나의 스레드만 코드 블록을 실행하도록 락(lock)을 걸어줌.
  • 성능 부담은 있지만 정확한 결과를 보장해야 할 때 사용.

Atomic 클래스 (java.util.concurrent.atomic)

  • 예: AtomicInteger, AtomicBoolean, AtomicLong 등
  • 가시성 + 원자성 보장
  • CAS(Compare And Swap) 기반으로 동작 → 락 없이 동기화
  • 락 없이도 동시성 처리 가능 → 성능 우수
  • 단점: 복잡한 로직에는 부적합 (단일 변수 연산에 적합)

다음은 volatile이 사용되는곳 이다.

1. 스레드 종료 플래그 (Thread Stop Flag)

멀티 스레드 환경에서 스레드를 안전하게 종료할 때 자주 쓰이는 방식

왜 volatile?

  • volatile이 없으면, running이 스레드의 캐시에 저장되어 변경을 감지하지 못할 수도 있다.
  • volatile을 쓰면 다른 스레드가 값을 바꾸면 즉시 반영됨 → 가시성 보장.

2. Double-Checked Locking 패턴 (싱글톤 패턴 구현)

volatile이 없으면 잘못된 싱글톤 객체가 생성될 위험이 있어.

왜 volatile?

  • new Singleton()은 실제로 여러 단계로 나뉘는 작업이라서,
    메모리에 할당되고 레퍼런스가 instance에 저장된 후, 초기화가 나중에 일어날 수도 있다.
  • volatile을 쓰면 이런 재정렬(reordering) 을 막을 수 있어서 안전하게 싱글톤 객체를 생성할 수 있다.

면접 대답으로는 volatile은 대표적으로 스레드 종료 플래그와 Double-Checked Locking 패턴에서 사용됩니다.
이 키워드는 변수의 가시성을 보장하고, 일부 경우에는 재정렬을 방지해서 동시성 문제를 예방하는 데 중요한 역할을 합니다.

대충 아는 정도는 요정도가 다인거 같구 4번 부터는 내가 뭘 말했는지 어떻게 말할지 잘모르겠다. 고로 gpt의 대답정도만 있을예정이다.
4번 제네릭 > 타입이레이저 얘는 .. 타입을 소거하는 거 같은데 잘모르겠다.
자바의 제네릭은 컴파일 타임에만 존재하고, 런타임에는 타입 정보가 제거되는 타입 이레이저 방식을 따릅니다.
덕분에 타입 안전성을 확보하면서도, 이전 버전과 하위 호환성을 유지할 수 있었지만,
그에 따라 new T[], instanceof T 같은 제한이 존재합니다.

도식화로도 그려준다니 한번 그려보자고 해보자.

요렇게 그려줬다. 한글로 그려줬으면 좋겠는데..
다음은 와일드카드와 제네릭메서드, pesc원칙에대해 깊게 더 공부할 수 있다고 한다.

 1. 와일드카드 (?)

 개념:

  • 타입이 정확하지 않거나, 어떤 타입이든 가능하게 만들고 싶을 때 사용
  • ?는 "모든 타입"을 의미해
    • List<?> : 어떤 타입이든 OK

List<Object>는 오직 Object 타입만 받을 수 있지만, List<?>는 어떤 타입의 리스트든 받을 수 있습니다.

2. 제네릭 메서드 개념:

  • 메서드 선언부에 타입 파라미터를 지정해서, 호출될 때 타입이 결정되도록 하는 메서드
  • 여기서 <T>는 이 메서드 전용 타입 파라미터 (클래스의 T와는 별개!)

제네릭 메서드는 클래스가 제네릭이 아니어도 사용할 수 있습니다.
메서드 앞에 <T>를 붙이면, 메서드 내부에서 T를 타입처럼 쓸 수 있어요.

3. PECS 원칙 (Producer Extends, Consumer Super)

이건 와일드카드 사용할 때 외우기 쉬운 원칙이다.

데이터를 꺼내 쓸 때 ? extends T
데이터를 넣을 때 ? super T

와일드카드는 타입을 명확히 알 수 없을 때 유용하고, PECS 원칙에 따라
데이터를 꺼낼 땐 extends, 넣을 땐 super를 사용합니다.
또, 제네릭 메서드는 메서드 호출 시점에 타입이 정해져서, 클래스가 제네릭이 아니어도 사용할 수 있습니다.

이것도 도식화로 그려줬다.

 

5번째 인티저캐시로 얘는 처음들어본 용어였다. 면접 끝나고 찾아보니 Integer은 객체인데 int처럼 사용하려고 그 범위까지 캐싱해서 저장한걸 말하는거 같다. 자바에서는 Integer 같은 래퍼 객체를 자주 사용되는 숫자 범위인 -128~127에 대해 미리 캐싱해둡니다.
이 덕분에 불필요한 객체 생성을 줄이고, 메모리와 성능을 절약할 수 있죠.
단, 이 범위를 벗어나면 new Integer()가 호출되어 다른 객체가 생성되므로 == 비교가 false가 될 수 있습니다.

근데 얘가 깊이 들어가면 오토박싱이 나오네.. 고로 마지막건 패스

Integer는 객체인데 왜 int처럼 쓰이죠?", "== 비교에서 false 나오는 이유는요라고 질문이 들어올수도 있다고 한다.

오토박싱 (AutoBoxing)

기본 타입 → 래퍼 클래스 객체로 자동 변환

  • 자바 컴파일러가 자동으로 int → Integer 변환해줘.
  • 내부적으로는 Integer.valueOf()를 호출함 → 이게 바로 Integer 캐시와 연결됨!

언박싱 (Unboxing)

래퍼 클래스 → 기본 타입으로 자동 변환

  • Integer를 int처럼 쓰면 자동으로 .intValue() 호출됨.

 Integer 캐시와의 연결 흐름

구분작동 방식결과
Integer a = 100; 오토박싱 → Integer.valueOf(100) 캐시에서 재사용됨
Integer b = 100; 오토박싱 → 같은 캐시 객체 a == b → true
Integer x = 200; 오토박싱 → new Integer(200) 생성 x == y → false (다른 객체)

 면접에서 자주 나오는 질문 포인트

Q. == 비교는 왜 결과가 다르게 나오나요?

"100은 캐시 범위에 포함되므로 같은 객체를 참조해서 true가 되고,
200은 범위를 벗어나 새 객체가 생성되므로 false가 됩니다."

 Q. 오토박싱/언박싱을 잘못 쓰면 어떤 문제가 생기나요?

  • NullPointerException 발생 가능:

"언박싱은 내부적으로 intValue()를 호출하므로, null을 언박싱하면 NPE가 납니다."

 

도식화도 시켜봤다.

 

 오토박싱 / 언박싱 / Integer 캐시 체크 카드

1 == 비교 주의 Integer는 참조 타입 → ==은 객체 비교 (주소 비교) Integer a = 100; Integer b = 100; a == b // true (캐시) 값 비교 시 .equals() 사용
2 null 언박싱 시 NPE Integer가 null이면 int로 언박싱 시 예외 발생 Integer val = null; int v = val; // NPE 발생 null 체크 필수
3 루프 내 오토박싱 성능 저하 박싱/언박싱 반복은 GC 부담 → 성능 저하 Long sum = 0L; for (...) sum += i; 기본형 우선 사용 권장
4 valueOf() vs new valueOf()는 캐시 사용, new는 새 객체 생성 Integer a = Integer.valueOf(100); vs new Integer(100); valueOf() 우선 사용
5 제네릭에서 기본형 불가 List<int>는 컴파일 에러, 반드시 래퍼 클래스 사용 List<Integer> list = new ArrayList<>(); 모든 제네릭에 해당
6 오토박싱 객체는 불변 아님 a++ 시 내부적으로 새로운 Integer 객체 생성 Integer a = 10; a++; // 새로운 객체 생성됨 값 자체는 바뀌지만 새 객체

 

반응형

댓글

Designed by JB FACTORY