신입 개발자 기술면접 질문 정리 - 자바

August 15, 2024

Java의 특징

  • Java는 객체지향 프로그래밍 언어임
  • 기본 자료형을 제외한 모든 요소들이 객체로 표현되고, 객체 지향 개념의 특징인 캡슐화, 상속, 다형성이 잘 적용됨
  • 장점
    • JVM 위에서 동작하기 때문에 운영체제에 독립적임
    • 가비지컬렉터를 통한 자동적인 메모리 관리가 가능함
  • 단점
    • JVM 위에서 동작하기 때문에 실행 속도가 상대적으로 느림
    • 다중 상속이나 타입에 엄격하며, 제약이 많음

JVM의 역할

  • JVM은 스택 기반으로 동작하며, Java Byte Code를 OS에 맞게 해석해주는 역할을 하고 가비지컬렉션을 통해 자동적인 메모리 관리를 해줌

Java의 컴파일 과정

  1. 개발자가 .java 파일을 생성
  2. build
  3. java compiler의 javac 명령어를 통해 바이트코드(.class)를 생성
  4. Class Loader를 통해 JVM 메모리 내로 로드
  5. 실행엔진을 통해 컴퓨터가 읽을 수 있는 기계어로 해석됨(각 운영체제에 맞는 기계어)

Java에서 제공하는 원시 타입

  • 정수형
    • byte: 1
    • short: 2
    • int: 4
    • long: 8
  • 실수형
    • float: 4
    • double: 8
  • 문자형
    • char: 2
  • 논리형
    • boolean: 1

오버라이딩(Overriding), 오버로딩(Overloading)

  • 오버라이딩은 상위 클래스에 있는 메서드들을 하위 클래스에서 재정의함
  • 오버로딩은 매개변수의 개수나 타입을 다르게 하여 같은 이름의 메서드를 여러 개 정의함

객체지향 프로그래밍(OOP)

  • 우리가 실생활에서 쓰는 모든 것을 객체라 하며, 객체지향 프로그래밍은 프로그램 구현에 필요한 객체를 파악하고, 상태와 행위를 가진 객체를 만들고 각각의 객체들의 역할이 무엇인지를 정의하여 객체들 간의 상호작용을 통해 프로그램을 만드는 것을 말함
  • 기능이 아닌 객체가 중심이며 ‘누가 어떤 일을 할 것인가’가 핵심
  • 특징으로는 캡슐화, 상속, 다형성, 추상화 등이 있고, 모듈 재사용으로 확장 및 유지보수가 용이

try-with-resources

  • try-with-resources는 try-catch-finally의 문제점을 보완하기 위해 나온 개념
  • try(...) 안에 자원 객체를 전달하면, try 블록이 끝나고 자동으로 자원을 해제해줌
  • 따로 finally 구문이나 모든 catch 구문에 종료 처리를 하지 않아도 되는 장점이 있음

불변 객체

  • 불변 객체는 객체 생성 이후 내부의 상태가 변하지 않는 객체
  • Java에서는 필드가 원시 타입인 경우 final 키워드를 사용해 불변 객체를 만들 수 있음
  • Java에서는 참조 타입일 경우 추가적인 작업이 필요함. 참조 타입은 대표적으로 객체, 배열, List 등을 참조할 수 있음
    • 참조 변수가 일반 객체인 경우 객체를 사용하는 필드의 참조 변수도 불변 객체로 변경해야 함
    • 배열일 경우 배열을 받아 copy해서 저장하고, getter를 clone으로 반환하도록 하면 됨
    • List인 경우에도 배열과 마찬가지로 생성 시 새로운 List를 만들어 값을 복사하도록 해야함

불변 객체나 final을 굳이 사용해야 하는 이유

  • Thread-Safe하여 병렬 프로그래밍에 유용하며, 동기화를 고려하지 않아도 됨 (공유 자원이 불변이기 때문에 항상 동일한 값을 반환)
  • 예외가 발생되더라도 메서드 호출 전의 상태를 유지할 수 있음
  • 메서드 호출 시 파라미터 값이 변하지 않는다는 것을 보장할 수 있음
  • 가비지 컬렉션 성능을 높일 수 있음 (가비지 컬렉터가 스캔하는 객체의 수가 줄어듬)

추상클래스와 인터페이스

  • 추상클래스는 클래스 내 추상메서드가 하나 이상 포함되거나 abstract로 정의되어 있음
  • 인터페이스는 모든 메서드가 추상메서드로만 이루어져 있음
  • 공통점
    • new 연산자로 인스턴스 생성 불가능
    • 사용하기 위해서는 하위 클래스에서 확장/구현해야 함
  • 차이점
    • 인터페이스는 그 인터페이스를 구현하는 모든 클래스에 대해 특정 메서드가 반드시 존재하도록 강제함
    • 추상클래스는 상속받는 클래스들의 공통적인 로직을 추상화시키고, 기능확장을 위해 사용함
    • 추상클래스는 다중 상속이 불가능하지만, 인터페이스는 가능

싱글톤 패턴

  • 단 하나의 인스턴스를 생성해 사용함
  • 인스턴스가 1개만 존재해야한다는 것을 보장하고 싶을 때
  • 동일한 인스턴스를 자주 생성해야할 때 (메모리 낭비 방지)
  • 스프링의
    javascript
    @Bean
    등록 방식은 싱글톤 스코프이고, 스프링 컨테니어는 모든 빈들을 싱글톤으로 관리함

가비지 컬렉션

  • JVM의 메모리 관리 기법 중 하나로, 시스템에서 동적으로 할당됐던 메모리 영역 중에서 필요없어진 메모리 영역을 회수하여 메모리를 관리해주는 기법
  • JVM이 애플리케이션의 실행을 잠시 멈춤 -> GC를 실행하는 쓰레드를 제외한 모든 쓰레드들의 작업을 중단 -> 사용하지 않는 메모리 제거

객체지향의 설계원칙

  • SRP(단일 책임): 한 클래스는 하나의 책임만 가져야 함
  • OCP(개방-폐쇄): 확장에는 열려있고, 수정에는 닫혀있어야 함
  • LSP(리스코프 치환): 하위 타입은 항상 상위 타입을 대체할 수 있어야 함
  • ISP(인터페이스 분리): 인터페이스 내의 메서드는 최소한일수록 좋음
  • DIP(의존관계 역전): 구체적인 클래스보다 상위클래스, 인터페이스, 추상클래스와 같이 변하지 않을 가능성이 높은 클래스와 관계를 맺는게 좋음

Java의 메모리 영역

  • 크게 Method, Stack, Heap으로 구분됨
  • Method – 전역변수와 static변수를 저장, 프로그램의 시작부터 종료까지 메모리에 남아있음 / JVM이 동작해서 클래스가 로딩될 때 생성
  • Stack – 지역변수와 매개변수 데이터 값이 저장되는 공간, 메서드가 호출될 때 메모리에 할당되고 메서드가 종료되면 해제됨 / 메서드 호출시
  • Heap – new 키워드로 생성되는 객체(인스턴스), 배열 등, 가비지 컬렉션에 의해 메모리가 관리됨 / 런타임시

클래스, 객체

  • 클래스는 객체를 만들어내기 위한 설계도or틀
  • 객체를 생성하는데 사용함
  • 객체는 설계도(클래스)를 기반으로 생성되며, 자신의 고유 이름, 상태, 행동을 가짐
  • 상태는 필드, 행동은 메서드라고 표현함
  • 객체에 메모리가 할당되어 실제로 활용되는 실체를 인스턴스라고 함

생성자(Constructor)

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

Wrapper Class

  • 기본자료형(Primitive Data Type)에 대한 객체 표현
  • Boxing: 기본자료형 -> Wrapper Class
  • UnBoxing: Wrapper Class -> 기본자료형
  • int -> Integer

동기화(Synchronized)

  • 여러 개의 쓰레드가 한 개의 자원을 사용하고자 할 때, 현재 데이터를 사용하고 있는 쓰레드를 제외하고 나머지 쓰레드들은 데이터에 접근할 수 없게 막음
  • 데이터의 thread-safe를 하기 위해 Java에서 Synchronized 키워드를 제공해 멀티 쓰레드 환경에서 쓰레드간 동기화를 시켜 데이터의 thread-safe를 보장
  • Synchronized는 변수와 메서드에 사용해서 동기화할 수 있으며, Synchronized 키워드를 남발하게 되면 오히려 프로그램의 성능저하를 일으킬 수 있음

new String(), 리터럴(“”)

  • new String()은 new 키워드로 새로운 객체를 생성하기 때문에 Heap 메모리 영역에 저장됨
  • 리터럴(“”)은 Heap 안에 있는 String Constant Pool 영역에 저장됨

String, StringBuffer, StringBuilder

  • String은 불변의 속성을 가지며, StringBuffer와 StringBuilder는 가변의 속성을 가짐
  • StringBuffer는 동기화를 지원하며 주로 멀티쓰레드 환경에서 사용함
  • StringBuilder는 동기화를 지원하지 않아 주로 싱글쓰레드 환경에서 사용함

String 객체 불변

  • 캐싱 기능에 의한 메모리 절약과 속도 향상
    • String 객체들은 Heap의 String Pool에 저장되는데, 참조하려는 문자열이 String Pool에 존재하는 경우 새로 생성하지 않고 Pool에 있는 객체를 사용함
  • thread-safe
    • String 객체는 불변이기 때문에 여러 쓰레드에서 동시에 특정 String 객체를 참조하더라도 안전함
  • 보안기능
    • 중요한 데이터를 문자열로 다루는 경우 강제로 해당 참조에 대한 문자열 값을 바꾸는 것이 불가능함

접근 제한자(Access Modifier)

  • 변수 또는 메서드의 접근 범위를 설정해주기 위해서 사용하는 Java의 예약어
  • public: 접근 제한이 없음
  • protected: 해당 패키지 내, 다른 패키지에서 상속받아 자손 클래스에서 접근 가능함
  • default: 해당 패키지 내에서만 접근 가능
  • private: 해당 클래스에서만 접근 가능

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

  1. static 변수 선언부: 클래스가 로드될 때 변수가 제일 먼저 초기화
  2. 필드 변수 선언부: 객체가 생성될 때 생성자 block보다 앞서 초기화
  3. 생성자 block: 객체가 생성될 때 JVM이 내부적으로 locking

static

  • static 키워드를 사용한 변수나 메서드는 클래스가 메모리에 올라갈 때 자동으로 생성되며 클래스 로딩이 끝나면 바로 사용할 수 있음
  • 모든 객체가 메모리를 공유함, GC 관리 영역 밖에 있기 때문에 프로그램이 종료될 때까지 메모리에 값이 유지된 채로 존재하게 됨

static 사용 이유

  • 자주 변하지 않는 값이나 공통으로 사용되는 값같은 공용자원에 대한 접근에 있어서 매번 메모리에 로딩하거나 값을 읽어들이는 것보다 일종의 '전역변수'와 같은 개념을 통해 접근하는 것이 비용효율적으로 좋음
  • 인스턴스 생성없이 바로 사용할 수 있기 때문에 프로그램 내에서 공통으로 사용되는 데이터들을 관리할 때 좋음

Inner Class(내부 클래스)

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

리플렉션(Reflection)

  • 구체적인 클래스 타입을 알지 못해도 해당 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 Java API

Error, Exception

  • Error는 실행 중 일어날 수 있는 치명적인 오류
  • 컴파일 시점에 체크할 수 없고, 오류가 발생하면 프로그램은 비정상 종료되며 예측 불가능한 UncheckedException에 속함
  • Exception은 Error보다 비교적 경미한 오류이며,
    javascript
    try-catch
    를 이용해 프로그램의 비정상 종료를 막을 수 있음

CheckedException, UnCheckedException

  • CheckedException은 실행하기 전에 예측 가능한 예외를 말하며, 반드시 예외처리를 해야함
    • IOException, ClassNotFoundException 등
  • UnCheckedException은 실행하고 난 후에 알 수 없는 예외를 말하며, 따로 예외처리를 안 해도 됨
    • NullPointerException, ArrayIndexOutOfBoundException 등

Optional API

  • 개발할 때 가장 많이 발생하는 예외 중 하나가 NPE(NullPointerException)임
  • NPE를 피하려면 null 여부 검사를 필연적으로 하게되는데 null 검사를 해야하는 변수가 많은 경우 코드가 복잡해짐
  • javascript
    Optional<T>
    를 이용해 null로 인한 예외가 발생하지 않도록 null을 컨트롤할 수 있음

컬렉션 프레임워크

  • 다수의 데이터를 쉽고 효과적으로 관리할 수 있는 표준화된 방법을 제공하는 클래스의 집합
  • List, Set, Map 인터페이스를 기준으로 여러 구현체가 존재함
  • Stack, Queue 인터페이스도 존재함
  • List
    • 순서가 있는 데이터의 집합이며, 데이터의 중복을 허용함
    • 대표적인 구현체로는 ArrayList가 있고, 이는 Vector를 개선한 것
    • 이외에도 LinkedList 등의 구현체가 있음
    • Vector, ArrayList, LinkedList, Stack, Queue
  • Set
    • 순서가 없는 데이터의 집합이며, 데이터의 중복을 허용하지 않음
    • 대표적인 구현체로는 HashSet이 있고, 순서를 보장하기 위해서는 LinkedHashSet을 사용함 (Map의 key-value 구조에서 key 대신 value가 들어가 value를 key로 하는 자료구조)
    • HashSet, LinkedHashSet, TreeSet
  • Map
    • 키와 값이 한 쌍으로 이뤄져 있고, 키를 기준으로 중복을 허용하지 않으며, 순서가 없음
    • key의 순서를 보장하기 위해서는 LinkedHashMap을 사용함
    • HashMap, TreeMap, HashTable, Properties
  • Stack
    • 직접 new 키워드로 사용할 수 있음
  • Queue
    • LinkedList에 new 키워드를 적용해 사용할 수 있음

Vector, List

  • Vector는 데이터 삽입 시 원소를 밀어내지만 List는 노드를 연결만 하기 때문에, 삽입/삭제 부분에서 List가 시간복잡도의 우위를 가짐
  • Vector는 랜덤부분접근이 가능해지지만 List는 DoubleLinkedList(노드가 양쪽으로 연결)로 되어있기 때문에 랜덤접근이 되지 않음. 조회는 Vector가 좋음
  • Vector는 List와 달리 동기화가 됨. 멀티쓰레드 환경에서 안전하게 객체를 추가하고 삭제할 수 있지만, 단일쓰레드 환경에서도 동기화를 하기 때문에 List보다 성능이 떨어짐

제네릭

  • 데이터의 타입을 하나로 지정하지 않고 사용할 때마다 범용적이고 포괄적으로 지정함
  • 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있어 에러를 사전에 방지할 수 있음

final, finally, finalize

  • final은 클래스, 메소드, 변수, 인자를 선언할 때 사용할 수 있으며, 한 번만 할당하고 싶을 때 사용함
    • final 변수는 한 번 초기화되면 그 이후에 변경할 수 없습음
    • final 메서드는 다른 클래스가 이 클래스를 상속할 때 메소드 오버라이딩을 금지함
    • final 클래스는 다른 클래스에서 이 클래스를 상속할 수 없음
  • finally는
    javascript
    try-catch
    와 함께 사용되며,
    javascript
    try-catch
    가 종료될 때 finally block이 항상 수행되기 때문에 마무리 해줘야 하는 작업이 존재하는 경우에 해당하는 코드를 작성해주는 코드 블록임
  • finalize는 Object 클래스에 정의되어 있는 메서드이며, GC에 의해 호출되는 메서드로 절대 호출해서는 안되는 메서드임
    • GC가 발생하는 시점이 불분명하기 때문에 해당 메소드가 실행된다는 보장이 없고,
      javascript
      finalize()
      메서드가 오버라이딩 되어 있으면 GC가 이루어질 때 바로 Garbage Collectiong 되지 않음
    • GC가 지연되면서 OOME(Out of Memory Exception)이 발생할 수 있기 때문에
      javascript
      finalize()
      메서드를 오버라이딩하여 구현하는 것을 권장하지 않고 있음

직렬화(Serialize)

  • 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 시스템에서도 사용할 수 있도록 바이트 형태로 데이터를 변환하는 기술
  • JVM의 메모리(Heap or Stack)에 상주하는 객체 데이터를 바이트 형태로 변환

SerialVersionUID

  • JVM은 직렬화와 역직렬화를 하는 시점의 클래스에 대한 버전 번호를 부여하는데, 만약 그 시점에 클래스의 정의가 바뀌어 있다면 새로운 버전 번호를 할당하게 됨
  • 따라서 직렬화할 때의 버전 번호와 역직렬화를 할 때의 버전 번호가 다르면 역직렬화가 불가능하게 될 수 있기 때문에 SerialVersionUID를 사용함
  • 만약 직렬화할 때 사용한 SerialVersionUID와 역직렬화를 할 때 사용한 SerialVersionUID가 다르면 InvalidClassException이 발생할 수 있음

댓글

댓글을 불러오는 중...