# 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 APIJDK
: 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
영역
- 메소드, 클래스, 인터페이스, Static 변수, 상수가 배치되는
# 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
클래스와 비교했을 때 자동 형변환을 제공한다.
- List, Set, Map 같은
# 어노테이션
- 컴파일러에게 컴파일 타임이나 런타임에 특정 처리를 하도록 정보를 제공하는 것
- 컴파일 과정에서 경고나 에러를 감지하도록 컴파일러에게 정보를 제공
- 컴파일 과정에서 특정 코드를 생성하도록 컴파일러에게 정보를 제공
- 런타임에서 특정 기능을 실행하도록 정보를 제공
# 직렬화
- 객체를 스트림으로 입출력하기 위해서 바이트 배열로 변환하는 것
- 바이트 배열을 객체로 변환하는 것을
역직렬화
라고 한다. - 직렬화할 클래스는
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)
- 의존하는 객체를 직접 만들고 관리하는 것이 아니라 외부에서 주입받는 원칙으로 의존성 주입의 기반이 된다.