BUILD_SSO

[Java] 자바 테크 인터뷰 part2 본문

Tech Interview

[Java] 자바 테크 인터뷰 part2

sohyeonnn 2023. 5. 22. 16:23

◼ 클래스와 객체에 대해 설명해주세요.

  • 클래스
    객체를 생성하기 위한 설계도나 틀이라고 할 수 있습니다.
  • 객체
    클래스를 기반으로 생성되며 자신의 고유한 이름과 상태, 행동을 가집니다.
  • 여기서 상태는 필드(field), 행동은 메서드(method)라고 하며, 객체가 메모리에 할당되어 실제로 활용되는 실체를 인스턴스라고 합니다.

클래스 멤버 변수 초기화 순서

  1. static 변수 선언부
    클래스가 로드될 때 가장 먼저 초기화된다.
  2. field(필드) 변수 선언부
    객체가 생성될 때 생성자 block보다 앞서 초기화된다.
  3. constructor(생성자)
    객체가 생성될 때 JVM이 내부적으로 locking (thread-safe영역)

생성자(Constructor)에 대해 설명해주세요.

  • 생성자는 클래스와 이름이 같은 매서드로, 객체가 생성될 때 호출되는 매서드입니다.
  • 명시적으로 생성자를 만들지 않아도 default로 만들어지며, 생성자는 파라미터를 다르게 하여 오버로딩할 수 있습니다.

👉🏻리플렉션(Reflection)

리플렉션(Reflection)이란 무엇인지 설명해주세요.

  • Java에서 이미 로딩이 완료된 클래스에서 또 다른 클래스를 동적으로 로딩하여 생성자, 멤버 필드, 멤버 메서드 등을 사용할 수 있는 기법입니다.
  • 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 Java API 입니다.

리플렉션은 어떤 경우에 사용되는지 설명해주세요.

코드를 작성할 시점에는 어떤 타입의 클래스를 사용할지 모르지만, 런타임 시점에 지금 실행되고 있는 클래스를 가져와서 실행해야 하는 경우 사용됩니다.

  • 클래스 정보 얻기
    Class 객체를 통해 클래스 이름, 부모 클래스, 구현한 인터페이스, 사용된 어노테이션 등의 정보를 얻을 수 있습니다.
  • 메소드와 필드 정보 얻기
    Method, Field, Constructor 등의 클래스를 사용하여 메소드, 필드, 생성자의 정보를 얻을 수 있습니다. 이들을 통해 메소드나 생성자를 호출하거나, 필드의 값을 읽거나 변경할 수도 있습니다.
  • 동적 클래스 로딩
    Class.forName() 메소드를 사용하여 문자열로 된 클래스 이름을 가지고 해당 클래스를 동적으로 로딩할 수 있습니다.

프레임워크나 IDE에서 이런 동적인 바인딩을 이용한 기능을 제공합니다. intelliJ의 자동완성 기능, 스프링의 어노테이션이 리플렉션을 이용한 기능이라 할 수 있습니다.

의미만 들어보면 리플렉션은 보안적인 문제가 있을 가능성이 있어보이는데,
    실제로 그렇게 생각하시나요?
    만약 그렇다면, 어떻게 방지할 수 있을까요?

맞습니다. 리플렉션은 매우 강력한 도구로, 제한 없이 사용하면 보안 문제를 일으킬 수 있습니다. 예를 들어, 리플렉션을 통해 보호된(ex: private) 필드나 메소드에 접근하거나, 보안 매커니즘을 우회하는 데 사용될 수 있습니다.

 

이러한 문제 방지를 위해 아래와 같은 조치를 취할 수 있습니다.

  • 최소한의 권한 부여
  • 코드 검사와 테스트
    리플렉션을 사용하는 부분에서 예기치 않은 동작이 발생하지 않도록 해야 합니다.
  • 보안 매니저 사용
    Java는 SecurityManager라는 시스템을 제공합니다. 이를 사용하면 애플리케이션의 보안 정책을 더 세밀하게 제어할 수 있습니다. 예를 들어, 특정 작업(예: 리플렉션을 사용한 접근)을 허용하거나 금지하는 등의 설정을 할 수 있습니다.
  • 의존하는 라이브러리 검사
    외부 라이브러리가 리플렉션을 어떻게 사용하는지 알아보고, 필요하다면 그 라이브러리의 사용을 재고해야 합니다.

◼ Java Collections Framework

컬렉션 프레임워크란 데이터 그룹을 저장하는 클래스들을 표준화한 설계를 의미합니다.
핵심 인터페이스로는 List, Set, Map 이 있으며 Stack, Queue도 이에 포함됩니다.

  • List
    순서가 있는 데이터의 집합이며 중복을 허용합니다.
    대표적인 구현체로는 ArrayList가 있습니다.
  • Set
    순서가 없는 데이터의 집합이며 중복을 허용하지 않습니다.
    대표적인 구현체로는 HashSet이 있고, 순서를 보장하기 위해 LinkedHashSet을 사용합니다.
  • Map
    key, value가 한 쌍으로 이루어져있고, 키를 기준으로 중복을 허용하지 않으며 순서가 없습니다.
    key의 순서를 보장하기 위해 LinkedHashMap을 사용합니다.

 

Stack 객체는 직접 new 키워드를 사용할 수 있으며, Queue 인터페이스는 LinkedList에 new 키워드를 적용해 사용할 수 있습니다.

  • Stack
    LIFO, 후입선출 구조의 자료구조, push(), pop()으로 입출력
  • Queue
    FIFO, 선입선출 구조, enQueue(), deQueue()를 통해 입출력

Call by Value 와 Call by Reference의 차이

  • call by value
    기본 데이터형을 사용. 주어진 값을 복사하여 처리하는 방식. 메서드 내의 처리 결과는 메서드 밖의 변수에 영향을 미치지 않는다.
  • call by reference
    매개변수의 원래 주소에 값을 저장하는 방식이다.

◼ 제네릭(Generic)이란?

  • 모든 데이터 타입을 다룰 수 있도록 하나로 데이터 타입을 지정하지 않고 사용시마다 범용적이고 포괄적으로 지정한다는 의미입니다.
  • 일반화된 타입 매개 변수(generic type)로 클래스나 메서드를 선언하는 기법입니다.
  • 제네릭 타입을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있어 에러를 사전에 방지할 수 있습니다.

◼ Wrapper class

  • 자바의 기본 자료형(Primitive Data Type)에 대한 객체 표현을 wrapper class 라고 합니다.
  • 기본 자료형으로 표현할 수 있는 간단한 데이터를 객체로 만들어야 할 경우가 있는데 그러한 기능을 지원합니다.

◼ Boxing/unboxing

  • 기본자료형(원시형)을 ➡ wrapper class로 변환하는 것이 박싱이고
    wrapper class를 ➡ 기본자료형(원시형)으로 변환하는 것이 언박싱입니다.

◼ String & StringBuffer & StringBuilder

공통점으로는 셋은 모두 Java의 문자열을 저장하고 관리하는 문자열 클래스입니다. 

차이점으로는 String은 불변(immutable)의 속성을 가지며, StringBuffer와 StringBuilder는 가변(mutable)의 속성을 가집니다.

  • String
    자바의 기본 데이터 타입인 int, float, char 등과 다르게 String은 데이터 타입이 아닌 클래스 객체입니다.
    ex. 한번 String name =”길동”; 라고 선언하게 되면 먼저 String 객체타입인 name이라는 인스턴스를 만들고 메모리에 “길동”을 올려버립니다. 그리고 name이 “길동”을 참조하는 레퍼런스가 되는 것입니다.
  • StringBuffer
    스트링버퍼는 동기화를 지원하여 멀티스레드 환경에서 주로 사용하고
  • StringBuilder
    스트링빌더는 동기화를 지원하지 않아 싱글스레드 환경에서 주로 사용합니다.

👉🏻불변객체와 final

◼ 불변 객체가 무엇인지 설명하고 대표적인 Java의 예시를 설명해주세요.

불변 객체(Immutable Object)는 생성된 후 그 상태가 변하지 않는 객체를 의미합니다.

즉, 객체가 한 번 생성되면 그 후로는 내부 상태를 변경할 수 없습니다. 이러한 불변성은 여러 스레드에서 동시에 객체를 사용할 때 발생할 수 있는 문제를 방지하므로, 병렬 처리 환경에서 매우 유용합니다.

  • String
    String 클래스는 가장 대표적인 불변 객체입니다. String 객체가 한 번 생성되면 그 값을 변경할 수 없습니다. String 클래스의 메소드 중 일부는 문자열을 변경하는 것처럼 보이지만, 실제로는 새로운 String 객체를 생성해서 반환합니다.
  • Wrapper 클래스들
    Integer, Long, Double, Character 등의 원시 타입을 감싸는 wrapper 클래스들은 모두 불변입니다.
  • BigInteger와 BigDecimal
    이들 클래스는 불변이며, 큰 정수나 정밀한 소수를 표현하는 데 사용됩니다.

Java에서는 필드가 원시 타입인 경우 final 키워드를 사용해 불변 객체를 만들 수 있고, 참조 타입일 경우엔 추가적인 작업이 필요합니다.

◼ 참조 타입일 경우 추가적인 작업은 어떤게 있는지 설명해주세요

  • 참조 변수가 일반 객체인 경우 객체를 사용하는 필드의 참조 변수도 불변 객체로 변경해야 합니다.
  • 배열일 경우 배열을 받아 copy해서 저장하고, getter를 clone으로 반환하도록 하면 됩니다.
    ➡ 배열을 그대로 참조하거나, 반환할 경우 외부에서 내부 값을 변경할 수 있기 때문에 clone을 반환해 외부에서 값 변경하지 못하게 하는것 입니다.
  • 리스트인 경우에도 배열과 마찬가지로 생성시 새로운 List를 만들어 값을 복사하도록 해야 합니다.

◼ 불변 객체나 final을 굳이 사용해야 하는 이유가 있을까요?

성능, 동기화, 캐싱, 보안등의 이유로 불변객체와 final을 사용해야합니다. → 메모리 절약, 보안, thread safe 보장

  • 스레드 세이프 보장(Thread Safety)
    불변 객체는 여러 스레드에서 동시에 접근하더라도 그 상태가 변경되지 않으므로, 동기화 없이도 안전하게 사용할 수 있습니다. 따라서 병렬 처리 환경에서의 버그를 예방하고, 성능을 향상시킬 수 있습니다.
  • 가독성과 유지 관리성
    불변 객체는 상태가 변경되지 않으므로 코드를 이해하고 디버깅하는 데 더 쉽습니다. 또한, 버그가 발생할 가능성을 줄여 유지 관리성을 향상시킵니다.
  • 보안
  • 캐싱 기능에 의한 메모리 절약과 속도향상

◼ final 키워드를 사용하면, 어떤 이점이 있나요?

  • 불변성 보장
    final 키워드를 사용하여 변수를 선언하면 그 변수는 한 번 초기화된 후에는 변경할 수 없습니다. 이는 특정 값을 변경하지 못하도록 보장하고, 해당 변수가 참조하는 객체가 변하지 않음을 보장합니다.
  • 디버깅 용이
    변수가 final로 선언되면, 그 값은 프로그램 실행 도중 변경되지 않습니다. 따라서 값이 어디서 바뀌는지 추적할 필요가 없어져 디버깅이 용이해집니다.
  • 스레드 안전성/Thread Safety
    final 필드는 스레드 간에 안전하게 공유될 수 있습니다. 특히 멀티스레드 환경에서 유용합니다.
  • 클래스 불변성
    final 키워드는 클래스 또는 메소드에도 적용할 수 있습니다. 클래스에 final을 붙이면 그 클래스는 상속할 수 없습니다. 메소드에 final을 붙이면 그 메소드는 오버라이딩할 수 없습니다. 이를 통해 클래스의 불변성을 보장할 수 있습니다.
  • 효율성 증대
    컴파일러는 final 변수를 통해 최적화를 수행할 수 있습니다. final 키워드가 붙은 변수의 값은 변하지 않으므로, 컴파일러는 이를 이용해 더 효율적인 코드를 생성할 수 있습니다.

하지만, final 키워드가 참조 타입 변수에 적용될 때, 그 변수가 참조하는 객체의 내부 상태는 final이 적용되지 않습니다.

따라서 final이 참조하는 객체 자체가 불변임을 보장하지는 않기 때문에 이점에 유의하여 사용해야 합니다.

◼ final / finally / finalize 를 각각 설명해주세요.

  • final
    클래스, 메소드, 변수, 인자를 선언할 때 사용할 수 있으며, 해당 요소가 더 이상 변경되거나 확장되거나 오버라이드되지 않음을 나타냅니다.
    • 변수: final로 선언된 변수는 한 번 초기화하면 변경할 수 없습니다.
    • 메소드: final로 선언된 메소드는 다른 클래스가 이 클래스를 상속할 때 메소드 오버라이딩을 금지합니다.
    • 클래스: final로 선언된 클래스는 다른 클래스가 상속받을 수 없습니다.
  • finally
    try-catch 구조에서 사용되며, 예외가 발생하든 발생하지 않든 항상 실행되는 코드 블록을 정의합니다.
    이는 주로 사용했던 자원을 반환하거나, 열려 있는 파일 또는 데이터베이스 연결을 닫는 등의 정리 작업에 사용됩니다.
  • finalize
    Object 클래스에 정의되어 있는 메소드이며, 객체가 가비지 컬렉션에 의해 회수되기 전에 실행되는 코드를 정의합니다. 따라서, GC에 의해 호출되는 메소드로 절대 호출해서는 안되는 메소드입니다.
    GC가 발생하는 시점이 불분명하기 때문에 해당 메소드가 실행된다는 보장이 없고, finalize() 메소드가 오버라이딩 되어 있으면 GC가 이루어질 때 바로 Garbage Collection 되지 않습니다.
    GC가 지연되면서 OOME(Out of Memory Exception)이 발생할 수 있기 때문에 finalize() 메소드를 오버라이딩하여 구현하는 것을 권장하지 않고 있습니다.

*그러나 finalize는 Java 9 이후로 이 메소드는 deprecated 상태이며, 더 이상 사용되지 않습니다.
대신 java.lang.ref.Cleanerjava.lang.ref.PhantomReference를 사용하는 것이 권장됩니다.


 Inner Class(내부 클래스)의 장점에 대해 설명해주세요.

내부클래스란 클래스 내에 선언되는 클래스이다. 이점을 제외하고는 일반적인 클래스와 다르지않다.

  • 내부 클래스에서 외부 클래스의 멤버에 손쉽게 접근할 수 있다.
  • 서로 관련 있는 클래스를 논리적으로 묶어서 표현함으로써, 캡슐화를 증가시키고, 코드의 복잡성을 낮출 수 있다.
  • 외부에서는 내부 클래스에 접근할 수 없으므로, 코드의 보안성을 높일 수 있다.

👉🏻 접근 제한자(Access Modifier)

◼ 접근제한자(access modifier)에 대해 설명해주세요.

변수 또는 메모리 접근 범위를 설정해주기 위해 사용하는 Java의 예약어를 의미합니다. 총 4가지 종류가 있습니다.

  • public
    접근 제한이 없으므로, 같은 프로젝트 내 어디서든 사용 가능합니다.
  • protected
    해당 패키지 내, 다른 패키지에서 상속받아 자손 클래스에서 사용이 가능합니다.
  • default
    해당 패키지 내에서만 접근이 가능합니다.
  • private
    해당 클래스 내에서만 접근이 가능합니다.

👉🏻Static

◼ static 을 사용하면 어떤 이점을 얻을 수 있나요? 어떤 제약이 걸릴까요?

Java에서 static 키워드는 클래스 레벨에서 사용되는 메소드나 변수, 내부 클래스에 사용될 수 있습니다. static 키워드는 해당 요소가 인스턴스 수준이 아닌 클래스 수준에서 작동하도록 지시합니다.

하지만, 적절하지 않게 사용된 static 요소는 객체 지향 프로그래밍의 원칙을 위반할 수 있으며, 코드의 유연성과 재사용성을 저해할 수 있습니다.

 

장점

  • 메모리 효율성
    static 변수는 클래스당 하나만 생성되며, 해당 클래스의 모든 인스턴스에서 공유됩니다. 이는 메모리 사용량을 줄이는 데 도움이 될 수 있습니다.
  • 객체 생성 없이 사용 가능
    static 메소드나 변수는 객체를 생성하지 않고도 사용할 수 있습니다. 이는 유틸리티 메소드를 작성하거나, 상수를 정의하는 데 유용할 수 있습니다.
  • 글로벌 접근
    static 변수는 해당 클래스의 모든 인스턴스에서 접근할 수 있습니다. 이는 일종의 전역 변수로 작동하여, 특정 정보를 공유해야 하는 상황에서 유용할 수 있습니다.

단점

  • 인스턴스 수준의 접근 제한
    static 메소드는 클래스의 인스턴스 변수나 메소드에 직접적으로 접근할 수 없습니다. static 메소드에서 인스턴스 변수를 사용하려면 객체의 인스턴스를 통해 접근해야 합니다.
  • 오버라이딩 불가
    static 메소드는 오버라이딩할 수 없습니다. 하위 클래스에서 동일한 시그니처를 가진 static 메소드를 정의할 수는 있지만, 이는 상위 클래스의 메소드를 오버라이딩하는 것이 아니라, 하위 클래스에서 새로운 static 메소드를 정의하는 것입니다.
  • 정적 바인딩
    static 메소드는 정적 바인딩을 사용합니다. 즉, 메소드 호출이 프로그램 실행 중에 결정되는 것이 아니라, 컴파일 시점에 결정됩니다. 이는 다형성을 제한할 수 있습니다.

◼ static class와 static method를 비교해 주세요.

  • Static Class
    Java에서 static 클래스는 static 내부 클래스로만 존재할 수 있습니다. 즉, 최상위 수준의 클래스는 static일 수 없습니다.
    static 내부 클래스는 자신을 포함하는 외부 클래스의 인스턴스에 의존하지 않으므로, 외부 클래스의 인스턴스 없이도 생성할 수 있습니다.
    static 내부 클래스는 일반적으로 외부 클래스와 관련이 있지만, 외부 클래스의 인스턴스와 독립적으로 작동하는 클래스를 정의할 때 사용됩니다.
  • Static Method
    static 메소드는 클래스 수준에서 작동하는 메소드입니다. 즉, 클래스의 인스턴스를 생성하지 않고도 호출할 수 있습니다. static 메소드는 인스턴스 변수에 접근할 수 없으며, 오직 static 변수나 다른 static 메소드에만 접근할 수 있습니다. 주로 유틸리티나 헬퍼 메소드를 정의할 때 사용됩니다. 예를 들어, Math 클래스의 Math.abs()나 Math.sqrt() 메소드는 static 메소드입니다.

두 가지 요소는 모두 클래스 수준에서 작동하지만, 그들이 사용되는 방식과 목적은 다릅니다.

static 내부 클래스는 외부 클래스와 독립적으로 작동하는 객체를 생성하는 데 사용되며,
static 메소드는 객체의 인스턴스 없이도 작동하는 동작을 정의하는 데 사용됩니다.

◼ non-static 멤버와 static 멤버의 차이

non-static 멤버(인스턴스 멤버)

  • 멤버할당: 멤버는 객체마다 별도로 존재한다
  • 생성시기: 객체 생성시에 멤버가 생성된
  • 공유여부: 공유되지않는다

static 멤버(클래스 멤버)

  • 멤버할당: 멤버는 클래스당 하나가 생성된다
  • 생성시기: 클래스 로딩시에 멤버가 생성된다
  • 공유여부: 동일한 클래스의 모든 객체들에 의해 공유된다

◼ equals()와 hashcode()에 대해 설명해 주세요.

Java에서 equals()와 hashCode() 메소드는 Object 클래스에서 정의된 메소드이며, 객체의 동일성 및 동등성을 비교하는 데 사용됩니다.

  • equals() 메소드
    이 메소드는 두 객체가 동등한지를 판단하는 데 사용됩니다.
    기본적으로 equals() 메소드는 두 객체의 참조가 같은지를 비교합니다. 즉, 두 객체가 같은 메모리 주소를 가리키는 경우에만 true를 반환합니다. 하지만 equals() 메소드는 오버라이드하여 사용자 정의 동등성 비교를 수행할 수 있습니다. 예를 들어, 두 개의 String 객체가 다른 메모리 주소에 위치하더라도 동일한 문자열을 가지고 있다면, equals() 메소드는 true를 반환합니다.
  • hashCode() 메소드
    이 메소드는 객체의 해시 코드 값을 생성하는 데 사용됩니다.
    해시 코드는 객체를 유일하게 식별하는 정수값을 반환합니다. hashCode() 메소드는 주로 해시 기반의 컬렉션, 예를 들어 HashMap, HashSet 등에서 객체를 빠르게 검색하는 데 사용됩니다.

 

  • 두 객체가 equals()를 통해 동등하다면, 두 객체의 hashCode()는 반드시 같아야 합니다. 즉, a.equals(b)가 true를 반환한다면 a.hashCode()와 b.hashCode()는 같은 값을 반환해야 합니다.
  • 그러나 두 객체의 hashCode()가 같다고 해서 두 객체가 equals()를 통해 동등하다는 것은 아닙니다. 즉, 해시 충돌(hash collision)이 발생할 수 있습니다.

 

* equals와 hashCode의 관계 
동일한 객체는 동일한 메모리 주소를 갖는다는 것을 의미하므로, 동일한 객체는 동일한 해시코드를 가져야 한다. 그렇기 때문에 우리가 equals() 메소드를 오버라이드 한다면, hashCode() 메소드도 함께 오버라이드 되어야 한다.

◼ new String()과 리터럴("")의 차이에 대해 설명해주세요.

  • new String()
    new 키워드로 새로운 객체를 생성하기 때문에 Heap 메모리 영역에 저장되고,
  • 리터럴("")
    Heap 안에 있는 String Constant Pool 영역에 저장됩니다.
Comments