들어가면서

여러 회사의 인터뷰를 보면서 자주 나오는 질문들을 정리해, 개념을 다시 상기시키고자 이 글을 작성했다. 면접을 대비하는 목적과 함께, 공부한 내용을 복기하는 목적도 함께 담았다.

OOP

객체지향프로그래밍(OOP)은 인간 중심적인 프로그래밍 패러다임으로, 현실 세계를 프로그래밍으로 옮겨오는 방식이다. 현실 세계의 사물을 객체로 표현하고, 그 객체로부터 애플리케이션에 필요한 특징을 추출해 프로그래밍한다. 대표적인 특징으로 추상화, 상속, 캡슐화, 다형성이 있다. 예를 들어 자동차를 만든다면, 자동차는 하나의 객체이며 배기량, 차종, 색상 같은 특징과 전진, 후진, 경적 같은 기능을 함께 가진다.

객체 지향적 설계 원칙의 종류

객체지향 설계에는 SOLID라 불리는 다섯 가지 원칙이 있다. 단일 책임 원칙은 클래스가 단 하나의 책임만 가져야 하며, 클래스를 변경하는 이유도 단 하나여야 한다는 원칙으로, 객체 간의 관계를 명확히 해 확장성과 재사용성을 높인다. 개방-폐쇄 원칙은 확장에는 열려 있고 변경에는 닫혀 있어야 한다는 원칙으로, 새로운 기능을 추가할 때 기존 코드는 건드리지 않고 다형성으로 확장할 수 있도록 한다. 리스코프 치환 원칙은 상위 타입의 객체를 하위 타입의 객체로 바꿔 넣어도, 상위 타입을 사용하는 프로그램이 정상적으로 동작해야 한다는 원칙이다. 인터페이스 분리 원칙은 인터페이스를 사용하는 클라이언트를 기준으로 분리해, 꼭 필요한 메서드로만 구성하고 불필요한 의존성을 없애야 한다는 원칙이다. 의존 역전 원칙은 고수준 모듈이 저수준 모듈에 의존해서는 안 된다는 원칙이다.

JVM(Java Virtual Machine)

JVM의 메모리는 몇 개의 영역으로 나뉜다. **Method Area(메소드 영역)**는 클래스 로딩 시 클래스 바이트코드 정보, static 변수, final 상수, 메서드 코드, 클래스 메타데이터를 저장한다. **Heap(힙 영역)**은 new 연산자로 생성한 객체 인스턴스와 배열 등 동적으로 생성된 데이터를 저장하며, GC의 대상이 되는 영역이다. **Stack(스택 영역)**은 스레드마다 별도로 존재하며, 메서드를 호출할 때 생성되는 프레임과 지역 변수, 매개변수, 리턴 값, 연산 결과를 담는다.

직렬화(Serialization)

자바에서는 입출력할 때 스트림(stream)이라는 통로로 데이터가 이동한다. 그런데 객체는 그 자체로는 스트림을 통해 파일에 저장되거나 네트워크로 전송될 수 없다. 객체를 스트림으로 입출력하려면 별도의 치환 작업이 필요한데, 이를 직렬화라 하고, 반대로 직렬화된 객체를 다시 되돌리는 작업을 역직렬화라 한다.

접근제어자

접근제어자는 접근할 수 있는 범위가 넓은 순서대로 네 가지다. public은 언제 어디서나 접근할 수 있고, protected는 같은 패키지 안이나 상속받은 자식 클래스에서 접근할 수 있으며, default(생략 시)는 같은 패키지 안에서만 접근할 수 있고, private는 해당 클래스 안에서만 접근할 수 있다.

인터페이스와 추상 클래스

인터페이스는 다중 상속이 가능하며 생성자와 일반 변수를 가질 수 없다. 구현 객체의 동일성을 보장하기 때문에, 팀원 간 협업 시 결과를 예측하며 작업할 수 있게 해준다. 추상 클래스는 다중 상속이 불가능하고 추상 메서드가 하나 이상 반드시 있어야 하지만, 일반 변수와 일반 메서드도 함께 쓸 수 있어 메서드를 부분적으로 구현할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
abstract class Animal {

abstract void cry();

void sleep() {
System.out.println("잔다.");
}
}

class Cat extends Animal {
private final String name = "치즈냥";
private final int age = 14;

@Override
void cry() {
StringBuilder sb = new StringBuilder();
sb.append(age).append("살").append("\t").append(name).append("야옹");
System.out.print(sb.toString());
}
}

class Dog extends Animal {
private final String name = "댕댕이";
private final int age = 10;

@Override
void cry() {
StringBuilder sb = new StringBuilder();
sb.append(age).append("살").append("\t").append(name).append("멍멍");
System.out.print(sb.toString());
}
}


public class Main {

public static void main(String[] args) {
Animal[] animals = { new Cat(), new Dog() };

for (Animal animal : animals) {
animal.cry(); //XXX 다형성에 따라 각자의 cry()가 호출됨
animal.sleep(); //XXX 공통 메서드
}
}

}

try-with-resources

try-with-resources는 JDK 7부터 도입된, 자원을 자동으로 해제해주는 기능이다. AutoCloseable을 구현한 객체라면, try 구문이 끝날 때 close() 메서드가 자동으로 호출된다.

Synchronized

synchronized는 함수나 블록 단위로 만들어 동기화 범위를 지정하는 데 쓰인다.

Collection Framework

컬렉션 프레임워크는 다수의 데이터를 쉽고 효과적으로 처리할 수 있도록 표준화된 방법을 제공하는 클래스의 집합이다. 데이터를 저장하는 자료구조에 따라 주요 인터페이스가 정의되어 있다.

인터페이스 설명 구현 클래스
List 순서가 있는 데이터의 집합, 데이터의 중복을 허용 Vector, ArrayList, LinkedList, Stack, Queue
Set 순서가 없는 데이터의 집합, 데이터의 중복을 허용하지 않음 HashSet, TreeSet
Map<K,V> 키와 값의 한 쌍의 데이터의 집합, 키는 중복을 허용하지 않음, 값은 중복을 허용 HashMap, TreeMap, Hashtable, Properties

Vector vs ArrayList

Vector는 동기화가 되어 있어(Thread safe) 상대적으로 속도가 느리고, ArrayList는 동기화가 되어 있지 않아 상대적으로 속도가 빠르다. 멀티스레드 환경이 아니라면 일반적으로 ArrayList 사용이 권장된다.

ArrayList vs LinkedList

ArrayList는 내부적으로 데이터를 배열로 관리하며, 데이터를 추가하거나 삭제할 때 임시 배열을 만들어 데이터를 복사한다. 데이터마다 인덱스가 있어 검색에는 유리하지만, 임시 배열을 쓰는 만큼 추가·삭제 속도는 상당히 느리다. 반면 LinkedList는 노드 단위로 데이터를 관리한다. 별도의 인덱스가 없어 검색할 때는 노드를 처음부터 순회해야 해 속도가 느리지만, 불필요한 데이터 복사가 없어 추가·삭제 속도는 빠르다.

Generic

제네릭은 자바의 타입 안전성을 책임지는 기능으로, 객체의 타입을 컴파일 시점에 검사한다. 덕분에 타입 안전성이 높아지고 형변환의 번거로움이 줄어들며, 코드도 자연스럽게 간결해진다.

Overloading와 Overriding

오버로딩은 이름과 리턴 타입은 같지만 매개변수만 다른 메서드를 여러 개 만드는 것으로, 다양한 상황에서 상황에 맞는 메서드가 호출되도록 해준다. 오버라이딩은 상위 클래스에 있는 메서드를 하위 클래스에서 필요에 맞게 재정의하는 것이다.

Checked Exception과 Unchecked Exception

Checked Exception은 반드시 처리해야 하는 예외로, 컴파일 단계에서 검사되며 트랜잭션을 롤백하지 않는다. 런타임 예외를 제외한 나머지 예외들이 여기 속한다. Unchecked Exception은 예외 처리가 강제되지 않고, 런타임 단계에서 발생하며 트랜잭션을 롤백한다. RuntimeException의 하위 예외들이 여기 속한다.

final class, final method, final variable

final은 위치에 따라 각기 다른 제약을 건다. 클래스에 붙이면 다른 클래스가 상속할 수 없게 하고, 메서드에 붙이면 다른 클래스에서 오버라이딩할 수 없게 하며, 변수에 붙이면 값을 한 번만 할당할 수 있는 상수로 만든다.

finalize

finalize는 가비지 컬렉터(GC)에 의해 호출되는 함수다.

POJO

POJO(Plain Old Java Object)는 평범한 자바 객체를 뜻한다. 특정 클래스를 상속하거나, 인터페이스를 구현하거나, 특정 어노테이션을 붙여야 하는 제약이 없는 객체를 말한다.

JPA

JPA는 ORM(객체-관계 매핑) 기술에 관한 API로, 관계형 데이터베이스를 자동으로 연결해준다.

JPA 영속성 컨텍스트

영속성 컨텍스트는 엔티티를 영구 저장하는 환경이다. 엔티티 매니저를 생성하면 영속성 컨텍스트도 함께 자동으로 만들어지며, 이 컨텍스트를 통해 엔티티를 관리하거나 보관할 수 있다.

영속성 컨텍스트의 네 가지 상태

엔티티는 영속성 컨텍스트와의 관계에 따라 네 가지 상태를 가진다. 비영속은 영속성 컨텍스트와 전혀 무관한, 순수한 객체 상태다. 영속은 영속성 컨텍스트에 저장되어 관리받는 상태다. 준영속은 영속성 컨텍스트가 관리하다가 분리된 상태로, @Id 값은 그대로 가지고 있다. 삭제는 엔티티가 삭제된 상태다.

영속성 컨텍스트의 특징

영속성 컨텍스트는 몇 가지 중요한 특징을 갖는다. 1차 캐시@Id를 키로 갖는 Map으로, 조회할 때 DB에 바로 접근하지 않고 이 1차 캐시를 먼저 확인한다. 동일성 보장은 식별자가 같은 엔티티라면 동일한 객체로 취급된다는 뜻이다. 쓰기 지연은 트랜잭션이 커밋되는 시점에, 쓰기 지연 SQL 저장소에 모아둔 쿼리들을 한 번에 실행하는 것을 말한다. 변경 감지는 트랜잭션이 커밋되면 flush가 호출되고, 엔티티와 최초 스냅샷을 비교해 변경된 엔티티를 찾은 뒤 쿼리를 생성해 쓰기 지연 SQL 저장소에 담고, 다시 flush와 commit이 이어지는 흐름으로 이루어진다.

즉시 로딩, 지연 로딩

즉시 로딩은 엔티티를 조회할 때 연관된 엔티티까지 함께 조회하는 방식이고, 지연 로딩은 연관된 엔티티를 실제로 사용하는 시점에 조회하는 방식이다.