기술 면접 준비

[기술면접] JAVA - 3/4 (주로 컬렉션 프레임워크)

Lea Hwang 2023. 3. 3. 14:28

[기술면접] JAVA - 3/4 (주로 컬렉션 프레임워크)의 목차

  • 컬렉션 프레임워크에 대해 설명해주세요. 
  • ArrayList란?
  • LinkedList란?
  • Vector란?
  • HashSet이란?
  • TreeSet이란?
  • HashMap 이란?
  • TreeMap이란?
  • 프로세스와 쓰레드의 차이에 대해 설명해주세요.
  • 멀티 프로세스와 멀티 쓰레드의 특징에 대해 설명해주세요.
  • 스프링에서 멀티스레드를 어떻게 구현하는지 말씀해주세요.

 

 


 

 

 

😎 컬렉션 프레임워크에 대해 설명해주세요.

  • 다수의 데이터를 쉽고 효과적으로 관리할 수 있는 표준화된 방법을 제공하는 클래스의 집합을 의미합니다.
  • 자바 컬렉션에는 List, Set, Map 인터페이스를 기준으로 여러 구현체가 존재하고, 이에 더해 Stack, Queue 인터페이스도 존재합니다.

▶ 꼬리질문1 - List, Set, Map의 특징에 대해 설명해주세요.

▶ 꼬리질문2 - Set과 Map의 타입이 Wrapper Class가 아닌 Object를 받을 때 중복 검사는 어떻게 할건지 설명해주세요.

▶ 꼬리질문3 - Vector와 List의 차이를 설명해주세요.

▶ 꼬리질문4 - 자바 컬렉션 프레임워크(List, Set, Map) 자세히 설명해주세요.

 

 

 

꼬리질문1- List, Set, Map의 특징에 대해 설명해주세요.

  • 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

 

 

 

꼬리질문2 - Set과 Map의 타입이 Wrapper Class가 아닌 Object를 받을 때 중복 검사는 어떻게 할건지 설명해주세요.

hashCode() 메소드를 오버라이딩하여 리턴된 해시코드 값이 같은지를 보고 해시코드 값이 다르다면 다른 객체로 판단하고, 해시코드 값이 같으면 equals() 메소드를 오버라이딩하여 다시 비교합니다. 이 두 개가 모두 맞으면 중복 객체입니다.

 

 

꼬리질문3 - Vector와 List의 차이를 설명해주세요.

  • 벡터는 데이터 삽입시 원소를 밀어내지만 리스트는 노드를 연결만 하기 때문에, 삽입 삭제 부분에서 리스트가 시간복잡도의 우위를 가집니다.
  • 벡터는 랜덤부분접근이 가능하지만 리스트는 더블링크드리스트(노드가 양쪽으로 연결)로 되어있기 때문에 랜덤 접근이 되지 않습니다. 검색적인 측면에서는 벡터가 우위에 있습니다.
  • 벡터는 리스트와 달리 항상 동기화되는 장점이자 단점을 가지고 있습니다. 멀티 쓰레드 환경에서 안전하게 객체를 추가하고 삭제할 수 있지만, 단일쓰레드 환경 일때도 동기화를 하기 때문에 List보다 성능이 떨어집니다.

 

 

 

 

꼬리질문4 - 자바 컬렉션 프레임워크(List, Set, Map) 자세히 알아보기

  • 컬렉션 프레임워크란?
  • 컬렉션 프레임워크 주요 인터페이스
  • 주요 인터페이스의 간략한 특징
  • List 컬렉션
  • List 클래스 주요 메서드
  • List 클래스의 자료구조들
  • Set 컬렉션
  • Set 클래스의 자료구조들
  • Map 컬렉션
  • Map 클래스의 자료구조들

 

컬렉션 프레임워크란?

배열을 사용하다 보면 여러가지 비효율적인 문제가 생긴다. 가장 큰 문제점은 크기가 고정적이라는 것이다.

배열의 크기는 생성할 때 결정되며 그 크기를 넘어가게 되면 더이상 데이터를 저장할 수 없다. 

또 데이터를 삭제하면 해당 인덱스의 데이터는 비어있어 메모리 낭비되는 등 여러 문제점들이 발생한다.

 

그렇기에 자바는 배열의 이러한 문제점을 해결하기 위해, 널리 알려져 있는 자료구조를 바탕으로 객체나 데이터들을

효율적으로 관리(추가, 삭제, 검색, 저장)할 수 있는 자료구조들을 만들어 놓았다.

 

이러한 자료구조들이 있는 라이브러리 컬렉션 프레임워크라고 한다.

컬렉션 프레임워크는 자바의 인터페이스(interface)를 사용하여 구현된다.

대표적으로 List, Set, Map, Stack, Queue 등이 있다.

 

 

 

 

컬렉션 프레임워크 주요 인터페이스

컬렉션 프레임워크에서는 데이터를 저장하는 자료 구조에 따라 다음과 같은 핵심이 되는 주요 인터페이스를 정의한다.

  • List 인터페이스
  • Set 인터페이스
  • Map 인터페이스

이 중에서 List와 Set은 모두 Collection 인터페이스를 상속받지만, 구조상의 차이로 인해 Map 인터페이스는 별도로 정의된다.

 

따라서 List 인터페이스와 Set 인터페이스의 공통된 부분을 Collection 인터페이스에서 정의하고 있다.

 

 

 

 

주요 인터페이스의 간략한 특징

컬렉션 프레임워크를 구성하고 있는 주요 인터페이스의 간략한 특징은 다음과 같다.

 

 

 

List 컬렉션

List 컬렉션은 객체를 일렬로 늘어놓은 구조를 가지고 있다.

 

객체를 인덱스로 관리하기 때문에 객체를 저장하면 자동 인덱스가 부여되고 인덱스로 객체를 검색, 삭제할 수 있는 기능을 제공한다.

List 컬렉션은 객체 자체를 저장하는 것이 아니라 위와 같이 객체의 번지를 참조한다. 

 

동일한 객체를 중복 저장할 수 있는데 이 경우 동일한 번지가 참조된다. null도 저장이 가능한데, 이 경우 해당 인덱스는 객체를 참조하지 않는다.

 

List 컬렉션을 구현하는 대표적인 클래스들은 ArrayList, LinkedList, Vector가 있다.

이 세 가지 클래스는 List 인터페이스를 같이 상속하므로 공통적으로 사용할 수 있는 메소드들이 많다.

 

 

 

List 클래스 주요 메소드

List<String> list = ...;
list.add("홍길동");    //맨끝에 객체 추가
list.add(1, "홍길동"); //지정된 인덱스에 객체 삽입
String str = list.get(1);   //인덱스로 객체 찾기
list.remove(0); //인덱스로 객체 삭제
list.remove("홍길동"); //객체 삭제

 

 

List 클래스의 자료구조들

-  ArrayList

- LinkedList

- Vector

 

 

 

 

Set 컬렉션

List 컬렉션은 저장 순서를 유지하지만, Set 컬렉션은 저장 순서가 유지되지 않는다.

또한 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있다.

그렇기에 Set컬렉션은 순서 자체가 없으므로 인덱스로 객체를 검색해서 가져오는 get(index) 메소드도 없다.

 

대신 전체 객체를 대상으로 한 번씩 반복해서 가져오는 반복자(Iterator)를 제공한다.

iterator() 메소드를 호출하면 얻을 수 있다.

 

 

Set 컬렉션을 구현하는 대표적인 클래스들은 HashSet과 TreeSet이 있다.

이 두 가지 클래스는 Set 인터페이스를 같이 상속하므로 공통적으로 사용할 수 있는 메소드들이 존재한다.

 

 

Set 클래스의 자료구조들

- HashSet

- TreeSet

 

 

 

Map 컬렉션

Map 컬렉션은 키(key)와 값(value)으로 구성된 객체를 저장하는 구조를 가지고 있다.

키는 중복 저장될 수 없지만 값은 중복 저장될 수 있으며 중복된 key값이 들어온다면 기존의 값은 없어지고 새로운 값으로 대치된다.

Map은 Key와 Value라는 것을 한 쌍으로 갖는 자료형이다.

 

Map은 리스트나 배열처럼 순차적으로 해당 요소 값을 구하지 않고 key를 통해 value를 얻는게 큰 특징이다.

따라서 Map 컬렉션은 키(key)로 데이터를 관리한다.

 

대표적인 클래스들은 HashMap, Hashtable, LinkedHashMap, TreeMap 이 있다.

이 클래스들은 Map 인터페이스를 같이 상속하고 있으므로 공통적으로 사용할 수 있는 메소드들이 존재한다.

 

 

Map 클래스의 자료구조들

- HashMap

- TreeMap

 

 


 

 

 

😎 ArrayList란?

ArrayList는 List 인터페이스를 상속받은 클래스로 크기가 가변적으로 변하는 선형리스트이다.

일반적인 배열과 같은 순차리스트이며 인덱스로 내부의 객체를 관리한다는점 등이 유사하지만 한번 생성되면 크기가 변하지 않는 배열과는 달리 ArrayList는 객체들이 추가되어 저장 용량을 초과한다면 자동으로 부족한 크기만큼 저장 용량이 늘어난다는 특징을 가지고 있다.

 

 

😎 LinkedList란?

연결 리스트(LinkedList)는 각 노드가 데이터와 포인터를 가지고 한 줄로 연결되어 있는 방식의 자료구조이다.

 

데이터를 담고 있는 노드들이 연결되어 있고, 노드의 포인터가 이전 노드와 다음 노드와의 연결을 담당한다.
Node는 LinkedList에 객체를 추가하거나 삭제하면 앞뒤 링크만 변경되고 나머지 링크는 변경되지 않는다.
중간에 데이터를 추가나 삭제하더라도 전체의 인덱스가 한 칸씩 뒤로 밀리거나 당겨지는 일이 없기에
ArrayList에 비해서 데이터의 추가나 삭제가 용이하나 인덱스가 없기에 특정 요소에 접근하기 위해서는 순차 탐색이 필요로 하여 탐색 속도가 떨어진다는 단점이 있다.

결론
검색 또는 정렬을 자주 하는 경우엔 배열을 사용하고
데이터의 추가/삭제가 많은 경우 연결 리스트를 사용하는 것이 좋다.

 

 

😎 Vector란?

Vector는 ArrayList와 동일한 내부구조를 가지고 있다. ArrayList와 마찬가지로 Vector내부에 값이 추가되면 자동으로 크기가 조절되며 그 다음 객체들은 한 자리씩 뒤로 이동된다.
하지만 Vector와 ArrayList의 한 가지 다른 점이 있는데 Vector는 동기화된 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 이 메소드들을 실행할 수 없고, 하나의 스레드가 실행을 완료해야만 다른 스레드들이 실행할 수 있다.
그래서 멀티 스레드 환경에서 안전하게 객체를 추가하고 삭제할 수 있다.

 

▶ 꼬리질문 - Vector의 단점 (ArrayList와의 비교)

벡터는 항상 동기화되는 장점이자 단점을 가지고 있다. 스레드가 1개일때도 동기화를 하기 때문에 ArrayList보다 성능이 떨어진다.

ArrayList는 기본적인 기능은 벡터와 동일하나 자동 동기화 기능이 빠져있고, 동기화 옵션이 존재한다.
그리고 벡터에 비해 속도가 더 빠르기 때문에 벡터에 비해 많이 쓰이고 있다.

 

 

 

😎 HashSet이란?

HashSet은 Set 인터페이스의 구현 클래스이다. 그렇기에 Set의 성질을 그대로 상속 받는다.
Set은 객체를 중복해서 저장할 수 없고 하나의 null 값만 저장할 수 있다. 또한 저장 순서가 유지되지 않는다.

 

▶꼬리질문 - 만약 요소의 저장 순서를 유지해야한다면?

만약 요소의 저장 순서를 유지해야만 한다면 LinkedHashSet 클래스를 사용하면 된다.

 

 

▶꼬리질문 - HashSet과 TreeSet의 차이점은?

Set 인터페이스를 구현한 클래스로는 HashSet과 TreeSet이 있는데 

HashSet의 경우 정렬을 해주지 않고
TreeSet의 경우 자동정렬을 해준다는 차이점이 있으며,  이진 탐색 트리(BinarySearchTree)구조로 이루어져있다.

 

 

▶꼬리질문 - Set의 장단점은?
Set의 가장 큰 장점은 중복을 자동으로 제거해준다는 점이다.

비선형 구조이기에 순서가 없고 그렇기에 인덱스도 존재하지 않는다.

그래서 값을 추가하거나 삭제할 때는 추가 혹은 삭제하고자 하는 값이 Set 내부에 있는지 검색한 뒤 추가나 삭제를 해야 하므로 속도가 List 구조에 비해 느리다.

 

 

▶꼬리질문 - HashSet에서 중복을 걸러내는 과정을 설명해주세요.

HashSet은 객체를 저장하기 전에 먼저 객체의 hashCode()메소드를 호출해서
해시 코드를 얻어낸 다음 저장되어 있는 객체들의 해시 코드와 비교한 뒤 같은 해시 코드가 있다면 다시 equals() 메소드로
두 객체를 비교해서 true가 나오면 동일한 객체로 판단하고 중복 저장을 하지 않는다.

 

 

 

 

😎 TreeSet이란?

TreeSet은 HashSet과 마찬가지로 Set 인터페이스를 구현한 클래스로써 객체를 중복해서 저장할 수 없고 저장 순서가 유지되지 않는다는 Set의 성질을 그대로 가지고 있다.

하지만 HashSet과는 달리 TreeSet은 이진 탐색 트리(BinarySearchTree)구조로 이루어져 있다.
이진 탐색 트리는 추가와 삭제에는 시간이 조금 더 걸리지만 정렬, 검색에 높은 성능을 보이는 자료구조이다.

그렇기에 HashSet보다 데이터의 추가/삭제는 시간이 더 걸리지만 검색과 정렬에는 유리하다.

 

 

▶꼬리질문 - TreeSet은 어떤 구조로 되어있는지 설명해주세요.

TreeSet은 이진탐색트리 중에서도 성능을 향상시킨 레드-블랙 트리(Red-Black Tree)로 구현되어 있다.


일반적인 이진 탐색 트리는 트리의 높이만큼 시간이 걸린다.
데이터의 값이 트리에 잘 분산되어 있다면 효율성에 큰 문제가 없으나 데이터가 들어올 때 값이 편향되게 들어올 경우
한 쪽으로 크게 치우쳐진 트리가 되어 굉장히 비효율적인 퍼포먼스를 낸다.

이 문제를 보완하기 위해 레드 블랙 트리가 등장했다.
레드 블랙 트리는 부모노드보다 작은 값을 가지는 노드는 왼쪽 자식으로, 큰 값을 가지는 노드는 오른쪽 자식으로 배치하여
데이터의 추가나 삭제 시 트리가 한 쪽으로 치우쳐지지 않도록 균형을 맞추어준다.

 

출처 :&nbsp;https://coding-factory.tistory.com/555

 

 

 

 

 

😎 HashMap 이란?

HashMap은 Map 인터페이스를 구현한 대표적인 Map 컬렉션이다.
Map은 키와 값으로 구성된 Entry 객체를 저장하는 구조를 가지고 있는 자료구조이고 여기서 키와 값은 모두 객체이다.

값은 중복 저장될 수 있지만 키는 중복 저장될 수 없다.
만약 기존에 저장된 키와 동일한 키로 값을 저장하면 기존의 값은 없어지고 새로운 값으로 대치된다.

HashMap은 이름 그대로 해싱(Hashing)을 사용하기 때문에 많은 양의 데이터를 검색하는 데 있어서 뛰어난 성능을 보인다.

출처 :&nbsp; https://coding-factory.tistory.com/556

 

 

HashMap은 해시 함수를 통해 '키'와 '값'이 저장되는 위치를 결정하므로, 사용자는 그 위치를 알 수 없고, 삽입되는 순서와 들어 있는 위치 또한 관계가 없다.

 

 

▶ 꼬리질문 - Java의 HashTable과 HashMap의 차이점에 대해 설명해주세요.

  • HashTable은 JDK 1.0부터 있던 Java의 API이고, HashMap은 Java 2에서 처음 선보인 JCF(Java Collections Framework)에 속한 API이다.
  • HashTable 또한 Map 인터페이스를 구현하고 있기때문에 HashMap과 HashTable이 제공하는 기능은 같다.

차이점

  • 보조 해시 함수
    • HashMap은 보조 해시 함수를 사용하기 때문에 보조 해시 함수를 사용하지 않는 HashTable에 비하여 해시 충돌이 덜 발생할 수 있어 상대적으로 성능상 이점이 있다.
  • 동기화
    • HashTable은 `synchronized` 메소드를 사용해 동기화를 지원하는 반면에, HashMap은 동기화를 지원하지 않는다.

 

 

 

😎 TreeMap이란?

TreeMap은 이진트리를 기반으로 한 Map 컬렉션이다.

TreeMap에 객체를 저장하면 자동 정렬되는데, 키는 저장과 동시에 자동 오름차순으로 정렬되고 타입이 숫자일 경우 값으로, 문자열일 경우 유니코드로 정렬한다.

정렬 순서는 기본적으로 부모 키값과 비교해서 키값이 낮은 것은 왼쪽 자식 노드에 키 값이 높은 것은 오른쪽 자식 노드에 Map.Entry 객체를 저장한다.

 


TreeMap은 일반적으로 Map으로써 성능이 HashMap보다 떨어진다.
TreeMap은 데이터를 저장할 때 즉시 정렬하기에 추가나 삭제가 HashMap보다 오래 걸린다.
하지만 정렬된 상태로 Map을 유지해야 하거나 정렬된 데이터를 조회해야 하는 범위 검색이 필요한 경우 TreeMap을 사용하는 것이 효율성면에서 좋다.

 

 

▶꼬리질문 - 같은 Tree구조로 이루어진 TreeMap과 TreeSet의 차이점은?

TreeSet은 그냥 값만 저장한다면, TreeMap은 키와 값으로 구성된 Entry를 저장한다는 점이다.

 

 

▶꼬리질문 - TreeMap과 HashMap을 비교해주세요.

TreeMap은 데이터를 저장할 때 즉시 정렬하기에 추가나 삭제가 HashMap보다 오래 걸린다.

하지만 만약 정렬된 상태로 작업을 하는 경우 그리고 데이터 조회시 TreeMap을 사용하는 것이 효율성이 좋다.

 

 

 

 


 

 

 

 

  1. 프로세스랑 스레드 차이→ 그럼 스프링에서 멀티스레드는 어떻게 구현할 수 있는가?
    → 왜 스프링은 멀티스레드여야 하는가?
    → 자바는 왜 멀티스레드 환경?

 

 

위의 꼬리질문들에 대한 답변

😎 프로세스와 쓰레드의 차이에 대해 설명해주세요.

프로세스는 실행 중인 프로그램을 말하며

프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)를 할당받아 프로세스가 되고

프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어있다.

 

완벽히 독립적이기 때문에 메모리 영역(Code, Data, Heap, Stack)을 다른 프로세스와 공유하지 않습니다. 프로세스는 최소 1개의 쓰레드(메인 쓰레드)를 가지고 있습니다.



쓰레드는 프로세스 내에서 Stack만 따로 할당 받고, 그 이외의 메모리 영역(Code, Data, Heap)영역을 공유하기 때문에 다른 쓰레드의 실행 결과를 즉시 확인할 수 있습니다.

쓰레드는 프로세스 내에 존재하며 프로세스가 할당받은 자원을 이용하여 실행됩니다.

 

 

😎 멀티 프로세스와 멀티 쓰레드의 특징에 대해 설명해주세요.

멀티 프로세스는 하나의 프로세스가 죽어도 다른 프로세스에 영향을 끼치지 않고 계속 실행된다는 장점이 있지만
멀티 쓰레드보다 많은 메모리 공간과 CPU 시간을 차지한다는 단점이 있습니다.

멀티 쓰레드는 멀티 프로세스보다 적은 메모리 공간을 차지하고 문맥 전환이 빠르다는 장점이 있지만
하나의 쓰레드에 문제가 생기면 전체 쓰레드가 영향을 받으며 동기화 문제도 있다는 단점이 있습니다.

 

 

 

😎 스프링에서 멀티스레드를 어떻게 구현하는지 말씀해주세요.

프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있으며

프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드이다.

모든 프로세스에는 최소 하나 이상의 쓰레드가 존재하며,

 

둘 이상의 쓰레드를 가진 프로세스를 멀티쓰레드 프로세스라고 한다.

 

멀티쓰레딩은 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이 가능하다.

실제로 한 개의 CPU가 한 번에 단 한가지 작업만 수행할 수 있기 때문에 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 동시에 여러 작업이 수행되는 것처럼 보이게 하는 것이다.

 

그래서 프로세스의 성능이 쓰레드의 개수에 비례하지 않으며, 하나의 쓰레드를 가진 프로세스보다 두 개의 쓰레드를 가진 프로세스가 오히려 더 낮은 성능을 보일 수도 있다.

 

▶ 꼬리질문 - 멀티쓰레딩의 장점

  • CPU의 사용률 향상
  • 자원을 보다 효율적으로 사용
  • 사용자에 대한 응답성 향상
  • 작업이 분리되어 코드가 간결해짐

 

 

▶ 꼬리질문 - 쓰레드를 구현하는 방법

쓰레드를 구현하는 방법Thread 클래스를 상속받는 방법Runnable 인터페이스를 구현하는 방법,  2가지가 있다.

둘 중 어느 쪽을 사용해도 별 차이는 없지만 Java에서는 다중 상속을 허용하지 않기 때문에,

Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없어서 Runnable 인터페이스를 구현하는 방법이 일반적이다.

 

 

 

▶ 꼬리질문 - Runnable  인터페이스를  구현하는 방법과 장점은?

Runnable  인터페이스를  구현하는 방법은 클래스 뒤에 implements Runnable을 기재한다.

장점으로는 재사용성이 높고  코드의  일관성을  유지할  수  있다는  장점이  있기  때문에 보다  객체지향적인 방법이라 할 수 있다.

Runnable인터페이스는 run()메소드만 정의되어 있는 간단한 인터페이스이다.

1. Thread클래스를 상속
public class MyThread extends Thread {
           public void run() { /* 작업내용 */}          // Thread 클래스의 run()을 오버라이딩
}

2. Runnable인터페이스를 구현
public class MyThread implements Runnable {
@Override
public void run() { /* 작업내용 */}          //Runnable인터페이스의 추상메소드 run()을 구현
}

 

 

 

 

 

 

 

 

 

관련 포스팅

[기술면접] JAVA - 1/4
[기술면접] JAVA - 2/4 (주로 예외처리Exception)
[기술면접] JAVA - 3/4 (주로 컬렉션 프레임워크)
[기술면접] JAVA - 4/4

 

 

 

 

 


출처:

https://mangkyu.tistory.com/94
https://dev-coco.tistory.com/153
https://gyoogle.dev/blog/

 

'기술 면접 준비' 카테고리의 다른 글

[기술면접] 데이터베이스  (0) 2023.03.23
[기술면접] JAVA - 4/4  (0) 2023.03.03
[기술면접] JAVA - 2/4 (주로 예외처리Exception)  (0) 2023.03.03
[기술면접] JAVA - 1/4  (0) 2023.03.02
[기술면접] Spring - 3/3  (0) 2023.03.02