# Table of Contents

# Java 면접 정리

Java 면접 내용을 정리합니다.

# Compiler vs. Interpreter

  • Compiler: 컴파일러는 소스 코드를 기계어로 컴파일한다. 컴파일러 언어는 소스 코드를 컴파일하는 과정이 필요하다. 예를 들어 c언어는 .c 확장자의 소스코드는 gcc컴파일러로 컴파일하여 .o확장자의 오브젝트 파일이라는 실행 파일을 생성한다.
  • Interpreter: 인터프리터 언어 소스 코드를 컴파일하는 과정이 필요없으며, 인터프리터가 소스 코드를 한 줄씩 읽어서 실행한다. 예를 들어 JavaScript 언어는 크롬 V8 런타임이 소스코드를 한 줄씩 읽어 실행한다.
  • Java는 두 특성을 모두 가진 언어다. .java 확장자의 소스코드를 .class 확장자의 바이트 코드로 컴파일한다는 점에서 컴파일 언어다. 또한 JVM이 바이트 코드를 운영체제에 종속적인 기계어로 변환하여 실행한다는 점에서 인터프리터 언어의 특성도 가지고 있다.

# JVM, JRE, JDK

  • JVM: 자바 바이트코드를 운영체제에 종속적인 기계어로 변환한 후 실행한다.
  • JRE: JVM에 Java API
  • JDK: JRE + 개발과 관련된 도구

# JVM 메모리구조

  • 클래스 파일을 메모리에 로드하는 Class Loader
  • 클래스 파일이 로드되는 Runtime Data Area
  • Runtime Data Area를 실행하는 Execution Engine
  • 참조가 없는 인스턴스를 삭제하는 Garbage Collector
  • Runtime Data Area는 다섯 부분으로 나뉜다.
    • 메소드, 클래스, 인터페이스, Static 변수, 상수가 배치되는 Method영역. 모든 스레드가 공유한다.
    • 지역변수와 파라미터가 저장되는 Stack영역. 스레드마다 존재한다.
    • 동적 할당 영역인 Heap영역. 모든 스레드가 공유한다.
    • 멀티 스레드 환경에서 각 스레드가 실행할 명령어의 주소를 저장하는 PC Register 영역
    • C/C++ 같이 Java 외의 언어로 작성된 코드가 배치되는 Native Method영역

# final

키워드 final은 세 가지 용도로 사용된다.

  • 변수 앞에 붙이면 변경 불가능한 상수
  • 메소드 앞에 붙이면 오버라이드 불가능한 메소드
  • 클래스 앞에 붙이면 상속 불가능한 메소드

# Unchecked Exception vs. Checked Exception

RuntimeException을 상속하는 클래스를 Unchecked Exception라고 한다. 이 예외는 코드에서의 예외처리를 강제하지 않는다. 대표적으로 NullPointException, ClassCastException 등이 있다.

RuntimeException을 상속하지 않는 클래스를 Checked Exception라고 한다. 이 예외에서는 코드에서의 예외처리를 강제한다. 따라서 try-catch 문으로 감싸주거나 throw 구문으로 예외처리를 전가해야한다. 대표적으로 IOException, ClassNotFoundException 등이 있다.

# 제너릭

  • 클래스 내부에서 사용되는 변수의 타입을 클래스 외부에서 지정하는 것
  • 제너릭을 사용하면 세 가지 장점이 있다.
    • List, Set, Map 같은 Collection처럼 타입에 종속되지 않은 유연한 로직.
    • Object클래스와 비교했을 때 컴파일 타임에 에러를 찾아낼 수 있다는 점에서 타입 안정성.
    • Object클래스와 비교했을 때 자동 형변환을 제공한다.

# 어노테이션

  • 컴파일러에게 컴파일 타임이나 런타임에 특정 처리를 하도록 정보를 제공하는 것
  • 컴파일 과정에서 경고나 에러를 감지하도록 컴파일러에게 정보를 제공
  • 컴파일 과정에서 특정 코드를 생성하도록 컴파일러에게 정보를 제공
  • 런타임에서 특정 기능을 실행하도록 정보를 제공

# 직렬화

  • 객체를 스트림으로 입출력하기 위해서 바이트 배열로 변환하는 것
  • 바이트 배열을 객체로 변환하는 것을 역직렬화라고 한다.
  • 직렬화할 클래스는 Serializable 인터페이스를 구현해야한다.

# 얕은 복사 vs. 깊은 복사

  • 얕은 복사는 두 참조 변수가 같은 인스턴스를 참조하는 것이다.
  • 깊은 복사는 동일한 인스턴스를 새롭게 생성하는 것으로 Java에서는 Cloneable인터페이스를 구현하고 clone() 메소드를 오버라이드하면 된다.

# 객체 비교

# 동일성

동일성(Identity)는 두 객체의 주소값이 같다는 것을 의미한다. == 연산자로 두 객체가 동일한지 비교할 수 있다.

# 동등성

동등성(Equality)는 두 객체의 속성값이 같다는 것을 의마한다. Object 클래스의 equal()메소드를 구현하여 두 객체가 동등한지 판별할 수 있다.

# 가변 객체 vs. 불변 객체

  • 가변 객체는 객체를 생성한 후 상태를 바꿀 수 있는 객체를 의미한다.
  • 불변 객체는 객체를 생성한 후 상태를 바꿀 수 없는 객체를 의미한다.
  • 불변 객체를 사용하면 다음과 같은 장점이 있다.
    • 상태를 변경할 수 없기 때문에 멀티 스레드 환경에서 안전한다.
    • 사이드 이펙트를 방지할 수 있다.

# 접근 제한자

  • private: 멤버변수와 메소드에만 붙일 수 있으며, 해당 클래스 내부에서만 접근할 수 있다.
  • protected: 멤버변수와 메소드에만 붙일 수 있으며, 해당 클래스와 이를 상속하는 클래스 내부에서만 접근할 수 있다.
  • default: 클래스, 멤버변수, 메소드에 붙일 수 있으며, 해당 클래스가 포함된 패키지의 다른 클래스에서 자유롭게 접근할 수 있다.
  • public: 클래스, 멤버변수, 메소드에 붙일 수 있으며, 다른 패키지에서도 자유롭게 접근할 수 있다.

# static

  • 멤버변수와 메소드에 붙일 수 있으며 이를 정적 멤버변수정적 메소드라고 한다.
  • 정적 멤버변수와 정적 메소드는 인스턴스를 생성하지 않고도 접근할 수 있다.
  • 정적 멤버변수는 모든 인스턴스가 값을 공유한다.

# 함수형 인터페이스

  • 객체지향 프로그래밍인 Java에 함수형 프로그래밍 패러다임을 적용하기 위해 Java 8에서 도입된 기능
  • Kotlin은 함수 타입이 존재하기 때문에 람다식을 변수에 할당하거나, 파라미터로 전달하거나 반환값으로 반환할 수 있다.
  • Java는 함수 타입이 없기 때문에 함수형 인터페이스로 람다식을 변수에 할당하거나, 파라미터로 전달하거나, 반환값으로 반환할 수 있다.
  • 함수형 인터페이스는 단 하나의 추상 메소드를 가지고 있는 인터페이스에 @FunctionalInterace 어노테이션을 붙여주면 된다.

# 리플렉션

런타임에 클래스의 구체적인 타입을 몰라도 그 클래스의 멤버변수, 메소드 등을 분석하고 접근할 수 있도록 하는 자바 API와 기법이다

# 클래스 참조

Java의 모든 클래스와 인터페이스는 컴파일 후 .class파일로 변환된다. 이 파일에는 생성자, 멤버변수, 메서드 등 객체의 정보가 포함되어있는데 Class클래스를 사용하면 이 파일에서 가져온 객체의 정보를 담을 수 있다. 이를 클래스 참조(Class Reference)라고 한다.

String str = new String();
Class clazz = str.getClass()

# 메소드 참조

Java 8부터 추가된 기능으로 람다식이 오직 하나의 메소드만을 호출하는 경우 메소드 참조를 사용하여 단축할 수 있다. 메소드 참조는 콜론 두 개(::)를 사용한다.

Lambda lambda = (something) -> {
    System.out.println(something);
}
Lambda lambda = System.out::println;

# 생성자 참조

Java 8부터 추가된 기능으로 생성자 호출을 생성자 참조를 사용하여 단축할 수 있다.

Lambda lambda = (str) -> {
    return new Person(str);
}
Lambda lambda = (str) -> new Person(str);
Lambda lambda = Person::new;

# Optional API

Optional은 Java 8에 추가된 기능으로, NullPointException을 쉽게 핸들링할 수 있다. null이 발생할 수 있는 객체를 Optional로 래핑한 후 isPresent(), ifPresent() 메소드로 null 체크를 할 수 있다.

# Stream API

Java 8에서 추가된 스트림(Stream)을 사용하면 Collection을 더욱 쉽게 순회, 필터링, 변환할 수 있다. 특히 람다식과 함께 사용하면 코드가 더욱 간결해진다.

# default 메소드

default 메소드를 사용하면 인터페이스에도 메소드를 구현할 수 있다. 이 기능을 사용하여 하위 호환성을 확보할 수 있다.

# JDBC

  • Java에서 데이터베이스 연결 및 조작을 위한 자바 API
  • SQL Mapper, JPA는 내부적으로 모두 JDBC를 사용한다.

# SOLID 원칙

객체지향 프로그래밍에서 유지보수가 쉽고 변경에 유연하게 대응할 수 있도록 적용하는 원칙

# SRP

  • 단일 책임 원칙(Single Responsibility Priciple)
  • 한 클래스는 하나의 책임만 가져야한다.
  • 즉 하나의 클래스는 해당 클래스와 연관된 기능들만 가져야하며, 모듈 하나의 응집도를 높이고 모듈 사이의 결합도를 낮추는 것과 관련된다.
  • 예를 들어 계산기 클래스는 덧셈, 뺄셈, 곱셈, 나눗셈 기능만을 포함해야한다.

# OCP

  • 개방 폐쇠 원칙(Open Closed Priciple)
  • 변경에는 닫혀있으나 확장에는 열려있다.
  • 요구사항이 변경되었을 때 기존 코드를 수정하지 않고도 확장의 형태로 재사용할 수 있어야한다.
  • 요구사항이 변경될 수도 있는 부분을 인터페이스로 정의하고, 변경된 요구사항은 새로운 구현체로 구현한다.
  • Spring 에서는 JDBC나 JPA를 사용할 때 인터페이스 형태로 Driver에 접근하고, 이 인터페이스를 따르는 MySQL, Oracle, H2 등의 Driver를 사용하여 쉽게 변경에 대응할 수 있다.
  • 변경될 부분과 절대 변경되지 않을 부분을 구분하는게 핵심이다.

# LSP

  • 리스코프 치환 원칙(Liskov Substitution Principle)
  • 잘못된 상속을 피하기 위한 원칙으로 부모 클래스 타입의 변수에 자식 클래스의 인스턴스를 넣어도 잘 작동해야한다.
  • 자식 클래스는 부모 클래스의 기능을 오버라이딩하기보단 새롭게 메소드를 정의하는 형태로 구현해야한다.

# ISP

  • 인터페이스 분리 원칙(Interface Segregation Principle)
  • 클래스는 자신이 사용하지 않는 인터페이스를 implements 하지 말아야한다.
  • 또한 하나의 인터페이스에 선언을 몰아넣는 것보다 연관된 작은 단위로 인터페이스에 분리하여 필요한 인터페이스만 구현하는 것이 낫다.

# DIP

  • 의존성 역전 원칙(Dependency Inversion Principle)
  • 의존하는 객체를 직접 만들고 관리하는 것이 아니라 외부에서 주입받는 원칙으로 의존성 주입의 기반이 된다.