💻/스터디

기술 면접 스터디 #1. JAVA

김씨리 2020. 8. 8. 06:00

 

 

기술 면접이 날로 어려워지고 있다... 알고 있던 내용도 긴장하면 까먹기가 다반사고 어렴풋하게 알던 내용들을 말로 정리해서 말하는 것은 더 어렵다. 겸사겸사 개념들 다시 짚어보기도 하고 나중에 한번 쭉 읽어보면 좋을 것 같아서 친구들과 화상 모의 면접으로 준비한 내용을 정리해보겠다❗️

 

 

추후 CS 공부를 하면서 더 추가할 예정〰️

 

 

 

 


 

 

📌 1주차 주제 : JAVA

 

Q1. 오버로딩 vs. 오버라이딩

 

오버로딩(Overloading) : 같은 이름의 메소드를 여러 개 정의하는 것.

- 파라미터 타입, 개수, 순서가 달라야 함.

- 리턴 타입이나 접근 제어자(public, private 등...)는 영향❌

- 정적 바인딩(실행할 메소드를 컴파일 하는 시간에 결정)

- 생성자 오버로딩 & 메소드 오버로딩

 

💡생성자 오버로딩 & 메소드 오버로딩 ?

  • 생성자 오버로딩 : 매개변수가 다른 생성자를 여러개 만들어내는 것.

    같은 클래스 내에서 이루어져야만 하고 선언된 파라미터 타입순서개수가 달라야 함. 파라미터의 이름은 상관이 없고 타입만 중요.

    아래 3개는 같은 이름이지만 파라미터 개수가 다르기 때문에 생성자 오버로딩이 된 상태. 
Music(String title)
Music(String title, String artist){}
Music(String title, String artist, int trackNum){}

 

  • 메소드 오버로딩 : 대부분 오버로딩이라 하면 메소드 오버로딩을 지칭.

    리턴타입 메소드이름(파라미터){} 의 형식.

    같은 클래스 내에서 이루어져야만 하고 선언된 파라미터 타입의 순서와 개수 중 하나만 달라도 메소드 오버로딩에 해당됨.

    맨 앞의 리턴 타입은 무관하고, 파라미터 타입의 개수가 다르므로 아래 2개는 메소드 오버로딩이 된 상태.
int music(String title, int trackNum){}
double music(String title, int trackNum, String producer){}

 

 

 

오버라이딩(Overriding) : 상속에서 나온 개념

- 부모 클래스의 메소드를 자식 클래스에서 재정의(상속 필수)

- 동적 바인딩(실행할 메소드를 컴파일이 아닌 실행시간에 결정)

- 리턴 타입도 상관⭕️

- 자식 클래스는 접근 지정자를 부모 클래스보다 좁은 범위로 변경할 수 없음

 

💡접근 지정자 (아래로 갈수록 좁은 범위)

  접근 범위 같은 클래스 같은 패키지 다른 패키지의 자식 클래스 다른 패키지
public 제한 없음 O O O O
protected 같은 패키지 내, 다른 패키지라도 상속받은 패키지 내 O O O  
default 같은 패키지 내 O O    
private 같은 클래스 내 O      

 

 

오버라이딩 사용 예시

class Music{

    public String title;
    public int trackNum;
    
    public void info(){
        System.out.println("노래 제목은 "+title+", 트랙 번호는 "+trackNum+"입니다.");
    
    }
}
 
class Song extends Music{

    String artist;
    
    public void info() {
        super.info();
        System.out.println("아티스트는 "+artist+"입니다.");
    }
    
}

public class Test {
    public static void main(String[] args) {

    	Song song = new Song();
        
        song.title = "여름 안에서";
        song.trackNum = 2;
        song.artist = "싹쓰리";
       
        job.info();
        
    }
    
}

위와 같이 실행 시켰을 때의 결과는 다음과 같다.

노래 제목은 여름 안에서, 트랙 번호는 2입니다.
아티스트는 싹쓰리입니다.

 

정리!

=> 오버로딩: 같은 클래스내 동일한 메소드명, 파라미터 타입/개수/순서 다르게 구현.

=> 오버라이딩: 상위 클래스의 메소드 재정의.

 

 

 

 

 

Q2. 가비지 컬렉터(Garbage Collector)란?

 

  • 가비지 : 정리되지 않은 메모리, 유효하지 않은 메모리 주소.
  • 가비지 컬렉션(GC) : 동적으로 할당된 메모리 영역사용하지 않는 영역을 방지하여 해제하는 기능. 이를 수행하는 게 컬렉터.

 

💡자바에서 "동적으로 할당된 메모리 영역" 이란? -> Heap 영역

 

자바 메모리에서의 Heap 영역, Stack 영역은 자료 구조에서의 heap, stack과 같은 의미가 아님❗️

 

Stack 영역

  - 정적으로 할당한 메모리 영역.

  - 원시 타입(byte, int, char, double ... )의 데이터가 값과 함께 할당된다. (참조값 저장이 아니라 실제 값을 stack에 직접 저장)

  - Heap 영역에 생성된 Object 타입의 데이터의 참조값 할당.

  - 지역 변수들은 scope에 따른 visibility를 가짐.

  - 각 Thread는 자신만의 stack을 가짐.

 

Heap 영역

  - 동적으로 할당한 메모리 영역.

  - 모든 Object 타입(Integer, String, ArrayList ... )의 데이터가 할당. (이 데이터의 참조값이 stack 영역에 할당됨)

  - 주로 긴 생명주기를 가지는 데이터들이 저장됨.

  - stack에 있는 데이터 제외하고 나머지 전부!

  - 몇 개의 Thread가 존재하든 Heap 영역은 단 하나만 존재.

  - Young Generation & Old Generation 이 있음.

https://www.waitingforcode.com/off-heap/on-heap-off-heap-storage/read

가비지 컬렉션 프로세스 :

  1. 새로운 객체는 Eden 에 할당된다. 두 개의 Survival 영역은 비워진 채로 시작.
  2. Eden 영역이 가득 차면 Minor GC가 일어난다.
  3. Minor GC가 발생하면, Reachable 객체(Heap 영역의 객체 중 Stack에서 도달 가능)는 S0으로 옮겨진다. Unreachable 객체들은 Eden 영역이 비워질 때 같이 메모리에서 사라진다.
  4. 다음 Minor GC가 발생할 때, Eden 영역은 3번과 똑같은 과정이 일어난다. Unreachable 객체들은 지워지고 Reachable 객체들은 S 영역으로 옮겨진다. 기존에 S0에 있었던 Reachable 객체들은 S1로 옮겨지는데, 이때 Age가 증가한다. 살아남은 모든 객체들이 S1으로 전부 옮겨지면, S0과 Eden은 클리어 된다. (S 영역간 이동 시에는 age가 항상 증가한다)
  5. 다음 Minor GC가 발생할 때, 4번 과정이 일어나는데 S1이 꽉 차 있으므로 S1에서 살아남은 객체들은 S0으로 옮겨진다. 그리고 Eden과 S1은 클리어 된다. (S 영역간 이동 시에는 age가 항상 증가한다)
  6. 이렇게 Young Generation에서 계속 살아남으면서 age 값이 증가한 객체들은 age 값이 특정값 이상이 되면 Old Generation으로 넘어가게 된다. 이를 Promotion 이라고 한다.
  7. 결국 Minor GC가 계속 일어나게 되면 Promotion 작업도 계속 발생한다.
  8. Promotion 작업이 반복돼서 Old Generation이 꽉 차게 되면 Major GC가 발생한다.

 

위 과정의 반복이 바로 가비지 컬렉션이다.

 

이를 Mark & Sweep 이라고도 부른다.

GC가 스택의 모든 변수 또는 Reachable 객체를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾는 과정을 Mark,

Mark 되어있지 않은 객체들을 힙에서 제거하는 과정이 Sweep.

 

 

 

 

 

 

 

Q3. 자바에서 print문이 실행되는 자세한 과정?

System.out.println("Hello");

위의 출력문을 수행하는 자바 코드가 있다고 할 때, 이 코드가 실행되어 우리에게 Hello 라는 문장을 보여줄 때까지의 과정을 작성해보겠다.

 

 

https://d2.naver.com/helloworld/1230

위 그림 중에서, 먼저 JVM(Java Virtual Machine)에 도달하기 전까지의 과정!

 

  1. 코드를 작성한 .java 파일을 컴파일러(javaC)를 통해 자바 바이트 코드(.class)로 컴파일한다.
  2. 바이트 코드를 클래스 로더에게 전달한다.
  3. 클래스 로더는 동적 로딩(Dynamic Loading)을 통해 필요한 클래스들을 로드 및 링크 해서 런타임 데이터 영역, 즉 JVM의 메모리에 올린다.
  4. 실행 엔진(Execution Engine)은 JVM 메모리인 런타임 데이터 영역에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행한다.

 

이제 JVM 의 구조와 그 안에서의 과정을 보자.

JVM은 위 그림에서 볼 수 있듯이 Class Loader, Runtime Data Areas, Execution Engine 으로 이루어져 있다. 하나씩 뜯어보자❗️

 

 

1. Class Loader

 

https://www.javatpoint.com/classloader-in-java

  • 계층 구조를 이루고 있다.
    • Bootstrap Class Loader

      : 최상위 클래스 로더.
      : 네이티브 코드(CPU와 OS가 직접 실행할 수 있는 코드)로 구현되어 있다.
      : JVM이 실행될 때 같이 메모리에 올라간다.
      : Object 클래스를 비롯하여 JAVA API들을 로드한다.

    • Extensions Class Loader

      : 기본 JAVA API를 제외한 확장 클래스들을 로드한다.(다양한 보안 확장기능 로드)

    • System Class Loader

      : 위 두 개가 JVM 자체의 구성 요소를 로드한다면, 여기서는 어플리케이션의 클래스를 로드한다.
      : 사용자가 지정한 $CLASSPATH 내의 클래스들을 로드한다.

    • Custom Class Loader

      : 어플리케이션 사용자가 직접 코드상에서 생성하여 사용하는 클래스 로더. 

  • 위임 모델

    처음 바이트 코드를 넘겨받은 클래스 로더가 필요한 클래스를 로드할 때 / 실행 엔진에서 명령어 단위로 바이트 코드를 실행하다가 처음으로 참조하는 클래스에 대해 클래스 로더에게 로드를 요청할 때

     --> 로드를 요청받은 클래스 로더는 다음 순서로 요청받은 클래스가 있는지 확인

    • 클래스 로더 캐시. 이전에 로드된 클래스인지 캐시를 확인함.
    • 상위 클래스 로더. 캐시 확인해서 없으면 상위 클래스 로더를 하나씩 거슬러 올라가면서 확인. 올라가는 중에 클래스를 발견하더라도 최상위의 부트스트랩 클래스 로더까지 확인하고, 부트스트랩 클래스 로더에도 클래스가 존재하면 거기에 있는 클래스를 로드.
    • 자기 자신. 위까지 다 확인했는데도 없으면 로드를 요청받은 자기 자신 클래스 로더가 파일 시스템에서 해당 클래스를 찾음.

  • 가시성 제한

    위처럼 클래스 로드를 요청받은 클래스 로더가 위임 모델을 타고 올라가면서 클래스 로더를 확인할 때, 자기 자신보다 하위 클래스 로더에 있는 클래스는 확인할 수 없는 특징.


  • 언로드(Unload) 불가

    로드는 가능, 언로드는 불가.

  • Name Space

    로드된 클래스를 보관하는 공간으로, 각 클래스 로더들이 가지고 있음.

    위임 모델에서 클래스 로더를 확인할 때, 실제로 확인하는게 바로 이 네임 스페이스. 네임 스페이스에 보관되는 기준은 FQCN(Fully Qualified Class Name)으로 패키지명까지 포함되어 있는 식별자.

    각각의 클래스 로더가 각자의 네임 스페이스를 가지고 있기 때문에 FQCN이 같은 클래스라도 네임 스페이스가 다르면(다른 클래스 로더가 로드한 클래스라면) 다른 클래스로 간주.

 

💡클래스 로더가 아직 로드되지 않은 클래스를 로드하는 과정?

  1. 로드(Loading) : 클래스 파일을 가져와서 JVM 메모리에 로드한다.
  2. 검증(Verifying) : 읽어들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 구성되어 있는지 검사한다.
  3. 준비(Preparing) : 클래스가 필요로 하는 메모리(클래스에서 정의된 필드, 메소드, 인터페이스 등)를 할당한다. 
  4. 분석(Resolving) : 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다. 심볼릭 레퍼런스는 실제 메모리가 아니라 참고하는 대상의 이름만을 지칭한 것이다. 따라서 다이렉트 레퍼런스로 변경한다는 것은 실제 메모리 주소를 찾아서 연결해준다는 것!
  5. 초기화(Initializing) : 클래스 변수들을 적절한 값으로 초기화한다.

 

 

2. Runtime Data Area

 

https://d2.naver.com/helloworld/1230

그림에 나와있는대로 Thread마다 PC Register, JVM Stack, Native Method Stack이 하나씩 생성되고, Heap, Method Area, Runtime Constant Pool은 모든 Thread가 공유한다.

 

  • PC Register

    현재 수행중인 명령의 주소를 가지며, 스레드가 시작될 때 생성된다.

  • JVM Stack

    스택 프레임이라는 구조체를 저장하는 스택이다. 예외 발생 시, printStackTrace() 를 통해 보여지는 Stack Trace의 각 라인이 스택 프레임을 표현한다.
    이것도 스레드가 시작될 때 생성된다.


  • Native Method Stack

    자바 외의 네이티브 코드를 위한 스택이다.
    JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택. C면 C스택이, C++이면 C++스택이 생성된다.


  • Heap

    인스턴스 또는 객체를 저장하는 공간. 가비지 컬렉션 대상이다.(Q2 참고!!)


  • Method Area

    모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성된다.
    JVM이 읽어들인 각각의 클래스, 인터페이스에 대한 런타임 상수 풀, 필드와 메소드에 관한 정보, static 변수, 메소드의 바이트 코드 등을 보관한다.


  • Runtime Constant Pool

    JVM 동작에서 가장 핵심적인 역할을 수행하므로 JVM 명세에서도 따로 중요하게 기술한다.
    각 클래스와 인터페이스의 상수, 메소드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블. 어떤 메소드나 필드를 참조할 때, JVM은 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리상 주소를 찾아 참조한다.

 

 

3. Execution Engine

 

실행 엔진은 클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행한다.

바이트 코드의 각 명령어는 1바이트 크기의 OpCode (=Operation Code)와 추가 피연산자로 이루어져 있다. 실행 엔진은 하나의 OpCode를 가져와 피연산자와 작업을 수행한 후, 그 다음번 OpCode를 수행하는 식으로 동작한다.

 

이 때, 실행 엔진이 바이트 코드를 기계가 실행할 수 있는 방법으로 바꾸는 두 가지 방법이 있다.

 

  1. 인터프리터

    바이트 코드 명령어를 하나씩 읽어서 실행한다. 하나하나의 해석은 빠르지만 전체적인 실행 속도는 느리다. JVM 안에서 바이트 코드는 기본적으로 인터프리터 방식으로 동작한다.

  2. JIT 컴파일러(Just-In-Time Compiler)

    인터프리터 방식을 보완하기 위해 도입되었다.

    바이트 코드 전체를 컴파일하여 네이티브 코드로 바꾸고, 이후에는 더이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행한다. 하나씩 실행이 아니라 전체가 컴파일된 네이티브 코드로 실행하기 때문에 속도가 빠르다. 또한 네이티브 코드는 캐시에 보관하기 때문에 더더욱 속도가 빠르다.

    하지만 JIT 컴파일러가 컴파일하는 과정은 하나씩 인터프리팅 하는 것보다 오래 걸리기 때문에, JVM은 내부적으로 해당 메소드가 얼마나 자주 호출되고 실행되는지 체크해서 일정 기준을 넘을 때만 JIT 컴파일러를 사용해야 한다.

https://d2.naver.com/helloworld/1230

 

 

 

 

 

Q4. 제네릭(Generic)?

다양한 타입의 객체들을 다루는 메소드나 컬렉션 클래스에서 컴파일 시 타입체크를 해주는 기능.

 

  • 잘못된 타입을 컴파일 단계에서 방지할 수 있음.
  • 외부에서 타입 지정 -> 따로 타입을 체크, 변환 ❌ (제네릭 말고 Object으로 받으면 타입 체크, 변환 ⭕️)
  • 코드 중복 없어짐.(다양한 타입 객체들에게 비슷한 기능 제공을 위한 코드)

 

제네릭 타입 = 타입을 파라미터로 가지는 클래스와 인터페이스.

public class 클래스명<T>{...}
public interface 인터페이스명<T>{...}

 

 

 

 

 

Q5. 컬렉션 프레임워크?

다수의 데이터를 쉽고 효과적으로 처리할 수 있도록 자료구조를 제공하는 인터페이스.

즉, 데이터를 저장하는 자료 구조와 데이터를 처리하는 알고리즘구조화하여 클래스로 표현한 것. by Interface.

 

List, Set, Map 등이 있고, 주요 인터페이스간 상속 관계는 아래 그림과 같다.

http://tcpschool.com/java/java_collectionFramework_concept

 

 

 

 

 

Q6. 어노테이션(Annotation)?

= 메타데이터

= App.이 처리할 데이터가 아니라, 컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지 알려주는 정보.

 

  • 형태
@Annotation
  • 용도
    • 컴파일러가 코드 문법 에러 체크하도록 정보 제공
    • SW 개발 툴이 빌드 또는 배치 시 코드 자동 생성하도록 정보 제공
    • 실행 시(런타임 시) 특정 기능 실행하도록 정보 제공 (클래스 역할 정의 등)

ex) @Override

- 컴파일러가 코드 문법 에러 체크하도록 컴파일러에게 정보 제공.

- 메소드 선언 시 사용, 메소드가 재정의되었음을 컴파일러에게 알려줌. 컴파일러가 체크.

- 오버라이드(재정의)되지 않았다면, 컴파일러가 에러 발생시킴.

 

 

 

 

 

Q7. 직렬화 vs. 역직렬화

직렬화(Serialization) = 객체들의 데이터를 연속적인 데이터로 변형하여 Stream을 통해 데이터를 읽도록 함.

 

- 전송 가능한 형태로 만든다.

- 객체들을 통째로 파일로 저장 or 전송하고 싶을 때 사용.

 

 

역직렬화(Deserialization) = 직렬화된 파일 등을 역으로 직렬화해서 다시 객체 형태로 만드는 것.

 

- 저장된 파일을 읽거나 전송된 Stream 데이터를 읽어, 원래 객체로 복원.

 

 

public class A implements Serializable{

    ...
    
}

    직렬화를 하려면 위 코드처럼 클래스 A에 Serializable 인터페이스를 implements 하여 사용한다.

 

 

 

 

 

Q8. ArrayList vs. LinkedList

둘다 List 인터페이스를 구현한 Collection 객체이지만 내부적으로 동작하는 방식이 다르다.

 

  • ArrayList
    • 메모리의 위치가 쭉 이어짐(메모리 밀도가 1).
    • 대량의 자료를 추가/삭제하는 경우에 그만큼 데이터의 복사가 많이 일어나서 성능 저하 일어날 수 있다(추가/삭제 성능 고려).
    • 대신 각 데이터가 인덱스를 가지고 있으므로 한번에 참조 가능해서 검색에 유리하다(검색 속도 빠름).
    • 초기 용량을 설정해주면 더 효율적.
  • LinkedList
    • 메모리의 위치가 뒤죽박죽(메모리 밀도가 분포된 상태).
    • 데이터를 저장하는 각 노드가 이전과 다음 노드 상태만 알고 있기 때문에 데이터 추가/삭제 시 불필요한 데이터 복사가 없다(추가/삭제 속도 빠름).
    • 검색 시에는 처음부터 노드를 순회해서 찾아야 하기 때문에 불리하다(검색 성능 고려)

 

=> 순차적으로 추가/삭제 시 : ArrayList(빠름) > LinkedList(느림)

=> 중간 데이터를 추가/삭제 시 : ArrayList(느림) < LinkedList(빠름)

 

 

 

 

 

Q9. 추상 클래스 vs. 인터페이스

  • 추상 클래스
    • "미완성 설계도"
    • abstract 로 추상 메소드를 선언
    • 상속을 통해 자식 클래스에서 완성하도록 유도
    • 상속 위한 것이므로 따로 객체 생성 ❌
    • abstract class 클래스명{
          ...
          public abstract void 메소드명();
      }
  • 인터페이스
    • "기본 설계도"
    • 추상 클래스처럼 다른 클래스 작성하는 데 도움을 줌
    • 다중 상속 가능
    • interface 인터페이스명{
          public static final 상수1 = 'aaa';
          public abstract void 메소드명();
      }

 

 

둘다 추상 메소드를 사용할 수 있다! BUT 사용 용도가 다름...

추상 클래스 --- "이런 게 있다"
인터페이스 ---- "이런 걸 할 수 있다"

https://myjamong.tistory.com/150

 

 

 

 

 

Q10. Mutable, Immutable 변수

변할 수 있는, 변할 수 없는.

눈으로 보는 String 값이 변하는 여부가 아니라, 새로운 객체를 재할당 받는지 아닌지!

 

대표적인 Immutable 클래스 : String, Boolean, Integer, Float, Long 등

 

  • Immutable한 String
    • String result = "Hello";
      result = result.concat("World");
    • Hello의 주소값 : 1000, HelloWorld의 주소값 : 2000
    • Hello가 그대로 있고 HelloWorld가 새로운 주소로 생성됨.
    • concat하면 new String()으로 새로운 객체 만들어 할당받기 때문.
    • concat이 아니라 값을 바꾸는 경우에도, 새로 생성하고 재할당하는 것. 이전 값과 바뀐 값 모두 존재.
  • mutable한 StringBuilder
    • StringBuilder result = new StringBuilder();
      result.append("Hello");
      result.append("World");
    • Hello의 주소값 : 1000, HelloWorld가 되면 그대로 주소값 1000
    • 같은 주소에서 변경됨.
    • 이전 값은 GC가 일어나서 사라짐.

 

 

 

 

 

Q11. StringBuilder vs. StringBuffer

Q10 에서 본 것처럼 String은 immutable하여 기존 값이 변경되지 않는다. 반면, StringBuilder와 StringBuffer는 mutable하다.

따라서 문자열의 추가, 수정, 삭제가 빈번하게 일어날 경우 StringBuilder/StringBuffer 사용하는 것이 좋음!

 

 

차이점 : 동기화 유무

  • StringBuilder
    • 동기화 지원 ❌
    • 멀티 스레드에서의 사용은 부적합
    • 단일 스레드에서의 성능은 뛰어남
  • StringBuffer
    • 동기화 지원 ⭕️
    • 멀티 스레드 환경에서 안전(thread-safe)

 

 

 

 


참고 : https://sas-study.tistory.com/53

참고(오버로딩) : https://hunit.tistory.com/156

참고(접근지정자) : https://studymake.tistory.com/424

참고(오버로딩, 오버라이딩) : https://private.tistory.com/25

참고(가비지 컬렉터) : https://velog.io/@litien/%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%ED%84%B0GC

참고(자바 메모리) : https://yaboong.github.io/java/2018/05/26/java-memory-management/

참고(자바 코드 실행 과정) : steady-snail.tistory.com/67#recentEntries

참고(자바 코드 실행 과정) : d2.naver.com/helloworld/1230

참고(제네릭) : https://coding-factory.tistory.com/573

참고(컬렉션 프레임워크) : http://tcpschool.com/java/java_collectionFramework_concept

참고(어노테이션) : https://kephilab.tistory.com/55

참고(직렬화, 역직렬화) : https://flowarc.tistory.com/entry/Java-%EA%B0%9D%EC%B2%B4-%EC%A7%81%EB%A0%AC%ED%99%94Serialization-%EC%99%80-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94Deserialization

참고(ArrayList, LinkedList) : https://devlog-wjdrbs96.tistory.com/64

참고(추상클래스, 인터페이스) : https://myjamong.tistory.com/150

참고(mutable, immutable) : https://cdy0510.github.io/2018/05/10/mutable-immutable/

참고(StringBuilder, StringBuffer) : https://ifuwanna.tistory.com/221