[신입 개발자 면접 준비] 면접 스터디 1차 (JAVA)
2024.11.28
스터디원들과 면접 스터디를 진행하였다. 이 날은 눈이 정말 많이 왔다... 진짜 너무 많이 쌓여서 걷는게 불편하고 버스에서는 도로 표시선도 흐릿하고 눈보라 때문에 앞이 하얗게 보일 정도였다...
그럼에도 스터디는 진행되었다!!!!! 모두들 수고했다~ 먹물 최고!
한 3주 정도 Java에 관해서 각자 공부를 진행하고, 공부한 내용을 발표하면서 많은 지식을 쌓았다.
각자 서로 공부한 부분을 공유하고 궁금한 부분에 대해서 질문도 하고 잘 정리되지 않는 부분에 대해서 토의하면서 생각과 지식을 정리하니 혼자 공부할 때보다 훨씬 잘 기억에 남고 이해가 되었다.
1차 면접 스터디는 6명이 각자 돌아가면서 면접 질문을 하고, 각자 정답을 알거나 아는 부분이 있으면 먼저 이야기하고, 다른 사람들은 답변이 부족하거나 틀리다면 다시 답변을 보충할 수 있도록 하였다!
이런 식으로 진행하니까 답변하는게 걱정되지도 않고 재미있었다!
이제는 스터디 때 나왔던 질문들에 대해서 정리해보겠다!
가비지 컬렉션에 대해서 아는만큼 설명하세요.
가비지 컬렉션은 더이상 참조되지 않는 객체를 비워주는 작업을 합니다.
알고리즘으로는 Mark & Sweep 또는 Mark & Compact 가 있습니다. 힙에서 더이상 참조되지 않는 객체인 unreachable 객체와 reachable 객체를 구분하여 reachable 객체를 Mark를 합니다. Reachable 객체를 탐색하기 위해 GC Roots(스택 변수, 정적 필드, JNI 참조 등)에서 시작해 그래프 탐색을 수행합니다. 이후 해당 unreachable 객체들을 Sweep 작업을 통해 제거합니다. 이후 제거하고 생긴 메모리 파편들을 다시 모아서 정리해주는 Compactation 작업을 진행하여 효율적으로 메모리를 사용하도록 합니다.
또한 GC는 Major GC와 Minor GC로 나누어집니다. 먼저 Heap에서 메모리들이 어떻게 저장되는지 말씀드리겠습니다. Heap은 Old Generation과 Young Generation으로 나뉘는데, Young Generation은 새로운 객체가 생성되면 Eden에 할당됩니다. Eden에서는 또 참조되지 않는 객체들을 제거하는데 이때 살아남은 객체는 Survivor1에 저장됩니다. 그리고 Survivor1에서 다시 참조되지 않는 객체들을 다 비우는데 그 가운데서 살아남은 객체는 다시 Survivor2에 저장됩니다. 이때 Survivor1과 Survivor2에는 단 한 공간에만 객체가 존재할 수 있습니다. 이 과정을 Minor GC라고 하고, 계속 빠르고 빈번하게 이루어집니다. 이후에 살아남은 객체가 일정 횟수 이상 GC를 통과하면 Old Generation으로 이동합니다. Old Generation은 자주 일어나지 않지만 많은 객체들을 정리하고, 모든 thread 작업이 멈추는 Stop-the-world 현상이 발생합니다. 해당 과정은 Major GC 라고 하고, 이 과정에서 성능이 많은 영향을 받습니다.
현대 JVM에서는 G1 GC, CMS GC 같은 고급 GC 알고리즘을 통해 Stop-the-World 시간을 줄이고 메모리 관리를 최적화합니다. GC는 -XX 옵션 등을 통해 튜닝할 수 있으며, 메타데이터와 JIT 코드가 저장되는 Metaspace와 Code Cache 같은 힙 외 메모리도 관리 대상에 포함됩니다.
자바는 call by value, call by reference 중에 무엇을 지원하는가에 대해서 말하고, 어떻게 동작하는지 설명하세요.
자바는 call-by-value 방식을 사용합니다.
자바는 메서드에 인자를 전달할 때 해당 객체의 참조값을 바로 전달하지 않고, 객체 참조값의 복사본을 전달합니다. 객체의 참조값 복사본을 통해서 객체에 접근해 객체의 내용을 수정할 수 있지만, 해당 객체의 참조값을 개발자가 절대 변경할 수 없습니다. 그렇기 때문에 java는 call-by-reference가 아닌 call-by-value입니다.
클래스, 객체, 인스턴스에 대해 설명하세요
자바에서 클래스는 객체를 설계하는 설계도 또는 청사진을 말합니다.
객체는 세상에 실재하는 것을 말합니다. 인스턴스는 어떤 클래스에서 new 키워드를 통해서 생성되어 메모리에 할당된 객체를 말합니다.
예를 들어, 현실의 차라는 것을 소프트웨어로 구현하기 위해, Car 클래스를 만들어서 엔진과 앞으로 직진하는 동작을 기술했다고 가정하겠습니다. 이때 메인 메서드에서 car1객체를 new 키워드를 통해서 생성된 객체가 바로 Car 클래스의 인스턴스이고, Car는 현실에 자동차를 설계한 설계도 입니다. 또한 현실의 자동차와 메인 메서드를 통해서 생긴 car1도 객체라고 볼 수 있습니다.
캡슐화와 은닉화의 차이에 대해 설명하세요
캡술화는 데이터와 메서드를 묶어서 외부로부터 보호하는 것을 말합니다. 이때 은닉화는 캡슐화에 속한 개념으로 정보를 은닉하여 외부로부터 보호하는 것을 말합니다. 예를 들어, private 접근 지정자를 활용하여 변수를 숨기고 public으로 지정한 getter, setter를 통해서만 수정하거나 조회할 수 있도록 하는 것이 있습니다.
문자열을 다루는 클래스 세 개 중에 불변성과 가변성을 관련지어 설명하세요.
문자열을 다루는 클래스에는 String, StringBuilder, StringBuffer가 있습니다. String은 String 객체를 생성할 경우 수정이 불가능하여 해당 객체에 변화를 주면 새로운 객체가 생성되기 때문에 불변성을 나타냅니다. 그에 반해, StringBuilder와 StringBuffer는 한 객체에 대해서 수정이 가능하기 때문에 가변성을 특징으로 합니다.
String 클래스는 왜 불변(Immutable)으로 설계되었을까요?
String 객체는 불변객체로 보안, 캐싱, 재사용성, 동기화를 지원합니다.
보안적으로는 자기 자신은 생성된 이후 수정이 불가능하기 때문에 외부에서 절대 수정할 수 없습니다. 두번째로, String은 HashMap, HashTable, HashSet과 같은 해시 구현에서도 사용합니다. 이때 hashCode()로 정수 값을 받아서 키 값으로 이용하도록 컬렉션들이 설계되어있습니다. String의 hashCode() 메서드를 보면, 최초 1번만 실계 계산을 수행하고, 이후에는 계산해서 나온 hash code를 재사용하도록 오버라이드 되어있습니다. String이 불변객체라 변경되지 않는 문자열을 보장하기 때문에 Caching이 가능합니다. 그리고 재사용성을 보면, String은 생성되면 String Constant Pool에 저장되어 같은 내용의 String 객체를 생성하면 String Constant Pool에 있기 때문에 다시 생성하지 않거나 복사하지 않고 이미 있는 String 객체를 사용할 수 있습니다. 다음으로 어디서도 수정이 불가능하기 때문에 멀티 스레드에서도 Thread-safe하여 동기화를 지원합니다.
객체지향 프로그래밍의 4대 원칙에 대해 설명해보세요.
객체지향 프로그래밍의 4대원칙에는 상속, 추상화, 다형성, 캡슐화가 있습니다. 상속을 통해서는 클래스 간 계층 구조를 가질 수 있어 확장에도 용이하고, 코드 재사용성을 높일 수 있습니다. 추상화를 통해서는 중요한 속성과 동작만 정의하여 복잡성을 줄입니다. 예를 들어, 인터페이스에 주요 기능만을 정의하고 해당 인터페이스를 구현하는 클래스에서 구현하도록 하여 복잡성을 줄일 수 있습니다.
동일성과 동등성의 차이는 무엇인가요?
동일성은 identity 라고하며 두 객체가 같은 메모리 주소를 참조하는지 확인하고 == 연산자로 비교합니다.
동등성은 equality 라고 하며 두 객체의 내용이 같은지를 equals 연산자로 확인합니다.
하지만 equals 메서드는 Object 클래스에서 보면 == 연산자를 통해서 비교하여 값을 반환합니다. 하지만 String 클래스에서는 equals 메서드를 오버라이딩하여 내용이 같은지 비교하여 반환합니다. 어떤 클래스를 만들어서 객체를 비교하려면 equals를 오버라이딩하여 동등성을 비교해야합니다. 추가적으로 equals 메서드만 오버라이딩해서는 안되고 객체의 주소 값을 이용해서 해싱(hashing) 기법을 통해 만드는 hashcode 메서드 또한 오버라이딩 해야합니다. 그 이유는 equals()의 결과가 true인 두 객체의 해시코드는 반드시 같아야 한다는 자바의 규칙 때문입니다. 그래서 객체의 주소가 아닌 객체의 필드의 값을 비교하기 위해서 equals 메서드를 오버라이딩하면 hashCode도 같이 객체의 필드를 다루도록 오버라이딩해야합니다.
name과 age 필드를 가진 Person 객체가 있습니다. Set과 Map에 name과 age가 모두 같은 Person 1 객체와 Person 2 객체를 넣는 것이 가능할까요?
equals(), hashcode()를 오버라이딩하지 않은 경우 필드 값이 모두 같은 객체를 넣는 것은 가능합니다.
- 꼬리 질문. 그러면 두 객체가 같게끔 판단하려면 어떻게 해야 하나요?
-
더보기equals()로 name과 age가 같을 경우 동등하다고 판단하게 오버라이딩하고 hashcode() 또한 name과 age가 같을 경우 같은 hashcode를 반환하도록 오버라이딩해야 두 객체가 같다고 판단할 수 있습니다.
-
JDK와 JRE의 차이를 설명해 보세요.
JDK(Java Development Kit) 안에 JRE(Java Runtime Environment)가 존재합니다. JRE는 자바를 실행시키기 위한 환경을 말합니다. 그 안에는 JVM도 포함됩니다. 그렇기 때문에 자바를 실행만하고 싶은 경우 JRE만 사용하면 되고, 자바 언어로 개발을 진행하는 경우에는 JDK를 다운로드하여 개발 도구를 통해서 자바를 개발하고 실행하면 됩니다.
제네릭에 대해 설명해주시고, 왜 사용하는지 알려주세요.
제네릭은 데이터 타입을 일반화한다는 것을 의미합니다. 제네릭은 클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법입니다. 컴파일 시에 미리 타입 검사를 수행하면 클래스나 메서드 내부에서 사용되는 객체의 타입 안정성을 높일 수 있고, 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있습니다.
오버라이드 와 오버로딩에 대해서 설명해주세요.
오버로딩은 자바에 한 클래스 내에 같은 이름을 가진 메서드가 있더라도 다른 타입의 매개변수 또는 다른 매개 변수 개수를 가진 메서드를 만들 수 있는 것을 말합니다. 오버라이드는 부모 클래스에 있는 메서드를 자식이 상속받아 재정의하여 사용하는 것을 말합니다. 오버로딩은 컴파일 시에 적절한 메서드를 찾아 실행합니다. 그에 반해 오버라이드는 런타임 시점에 자식 클래스부터 메서드를 찾아서 실행합니다. 런타임 시점에 발생한 오류는 치명적이기 때문에 오버라이드 애너테이션을 사용하면 컴파일 시에 에러를 파악할 수 있어 런타임 에러를 방지할 수 있습니다.
Java가 컴파일 되는 과정에 대해서 설명하세요.
Java가 컴파일 되는 과정은 일단 개발자가 .java 파일을 작성합니다. 그 이후에 javac 를 통하여 .class 확장자의 바이트 코드로 변환합니다. 그 이후에 클래스 로더가 .class 파일을 로드하고 Runtime Data Area에 메타데이터를 올립니다. Method Area에 바이트코드, 상수 풀, 메서드 참조 등을 저장하고, Heap 영역에는 객체와 static 변수를 저장합니다. Execution Engine은 Runtime Data Area에서 데이터를 참조하여 명령어를 실행합니다. Method 영역에서는 명령어를 가져오고 Heap에서는 객체 데이터, Stack에서는 로컬 변수를 참조하며 명령을 처리합니다. 바이트코드는 인터프리터나 JIT 컴파일러를 통해 실행됩니다. 바이트코드가 메모리에 로드되어 있으므로, 실행 엔진이 빠르게 참조할 수 있습니다.
primitive 타입과 reference 타입에 대해서 말씀해주세요.
primitive 타입은 integer, boolean 등 메모리에 실제 '값'을 을 저장하고, 참조타입은 '주소'를 저장한다.
기본 데이터 타입은 스택 영역에 생성이 되고 참조 데이터 타입은 힙 영역에 생성이 된다. 또한, reference 타입은 null의 사용이 가능하며, 제네릭 타입에서 사용 가능합니다.
Error와 Exception의 차이에 대해서 설명해주세요.
Error는 주로 JVM에서 발생하며 복구가 불가능한 심각한 문제를 말합니다. 예로는 OutOfMemoryError 가 있습니다. Exception은 프로그램에서 발생할 수 있는 예외 상황을 의미하며, CheckedException과 UncheckedException으로 나뉩니다. CheckedException은 컴파일러가 예외 처리를 강제하며 IOException가 있습니다. 또한, UncheckedException은 런타임에 발생하고 NullPointerException 이 있습니다.
람다와 스트림에 대해서 설명하세요.
람다식은 익명 함수를 간단히 표현하는 방법으로, 불필요한 코드 작성을 줄입니다. 스트림은 데이터 처리를 함수형 스타일로 수행할 수 있는 API로, 필터링, 매핑, 병렬 처리를 쉽게 구현할 수 있습니다.
컬렉션 프레임워크에 대해서 설명해주세요.
컬렉션 프레임워크는 데이터를 효율적으로 관리하기 위한 클래스와 인터페이스의 집합입니다. 주요 인터페이스는 List, Set, Queue, 그리고 데이터를 키-값 쌍으로 저장하는 Map이 있습니다. List는 순서가 있는 데이터 관리, Set은 중복을 허용하지 않는 데이터 관리, Queue는 FIFO 구조, Map은 키를 통해 값을 매핑하는 구조를 제공합니다. 각각의 인터페이스를 구현한 클래스에는 ArrayList, HashSet, PriorityQueue, HashMap 등이 있으며, 상황에 따라 다양한 구현체를 선택해 사용할 수 있습니다. 이를 통해, 데이터 구조를 쉽게 사용할 수 있고 코드 재사용성과 유지보수성을 높여줍니다.