기술 면접 준비

[CS 모의면접 스터디] 자바 편

Lea Hwang 2023. 11. 16. 17:18

23.11.16 CS 모의면접 스터디 <자바 편>에서 사용할 질문&예상답변을 정리하였습니다. 

 

 

Q. 자바에서 String을 초기화하는 방법에는 두 가지가 있습니다. 두 가지 방법에 대해 설명부탁드립니다. 

리터럴 방식과 new키워드를 사용하는 방식이 있습니다. 

 

[꼬리질문] 그럼 두 방식의 차이점은 무엇일까요?

둘 다 문자열을 생성하지만, 방식에 따라 저장되는 위치가 다릅니다. 

리터럴을 사용해 선언 및 생성을 하면 해당 문자열은 StringConstantPool에 저장되고 추후 

같은 문자열을 생성하려고하면 먼저 StringConstantPool 안을 찾아본 후 같은 문자열이 있다면 같은 주소값을 반환합니다. 

 

반면 new 키워드를 사용하면 해당 문자열은 Heap영역에 저장되며, 객체를 생성할 때마다

Heap영역 안에 새롭게 객체를 생성하므로 메모리 낭비가 발생할 수 있습니다. 

 

[꼬리질문] 그럼 무조건 리터럴이 좋겠네요?

아닙니다. 성능이나 메모리 관점에서는 리터럴 방식이 우수하나, 만약 해당 객체가 재활용이 안될 

예정이거나 반대로 재활용이 되면 안 되는 상황이라면 new연산자를 사용해야 합니다. 

 

 

Q.  문자열 연산을 할 때, 예를들어 문자열 연결을 할 때, String와 + 연산을 쓸 수 있습니다. 

그렇다면 여기서 String방식의 문제점과 다른 대안은 있는지 말씀부탁드립니다. 

String은 + 연산을 이용해서 문자열을 연결할 수 있습니다. 하지만 String은 불변이기에 기존

문자열에서 수정을 하는 것이 아닌 새로운 객체를 매번 생성하므로 메모리 관점과 성능 관점에서 

낭비가 발생합니다. 

 

이를 보안하기 위해 등장한 것이 StringBuffer와 String Builder입니다. 

두 클래스는 동작방식이 동일하고 가변적이라는 공통점이 있습니다. 

차이점은 멀티스레드에서 Thread safe 한지 안 한지의 차이점이 존재합니다. 

StringBuffer 클래스는 Thread safe 합니다. 동기화를 지원하기에 멀티 스레드 환경에서도

안전하게 동작합니다. 

반면, StringBuilder 클래스는 Tread safe하지 않고 동기화를 지원하지 않기에 싱글 스레드나 비동기를

사용하지 않을 것이라면 StringBuffer가 아닌 StringBuilder를 사용하는 것이 좋습니다. 

 

 

Q.  자료형(데이터 타입)에대해 말씀해 주세요. 

자료형이란 변수에 적재할 데이터가 메모리에 어떻게 저장되고 처리되어야 할지 명시적으로 알려주는 키워드입니다.

primitive type(기본형)과 reference type(참조형)이 있습니다. 기본형은 값을 저장하고 참조형은 객체의 주소를 저장합니다.

 

기본형 8개입니다.

논리형boolean, 문자형 char, 정수형 byte, short, int,  long, 실수형float, double입니다. null값을 가질 수 없습니다.

기본형을 제외한 나머지는 참조형 타입입니다. 배열 타입, 열거 타입, 클래스, 인터페이스가 있고 null로 초기화시킬 수 있습니다. (기본값이 null)

기본형 할당되는 메모리 크기 기본값
boolean 1 byte false
char 2 byte '\u0000' 
byte 1 byte 0
short 2 byte 0
int (기본) 4 byte 0
long 8 byte 0L
float 4 byte 0.0F
double (기본) 8 byte 0.0

 

[꼬리질문] 기본형과 참조형은 메모리 어느 영역에 저장이 될까요?

primitive type 변수는 stack영역, reference type 변수는 heap영역에 저장됩니다. 

 

[꼬리질문] 자바의 특징 중에 GC가 있습니다. 알아서 더이상 사용하지 않는 객체를 청소해 주는 역할을 하는데 그럼 primitive type 변수와 reference type 변수 모두 GC가 지워주는 것일까요?

GC는 Heap영역만 관장하기에 reference type 변수만 해당됩니다. 

 

 

Q.  자바에서는 GC가 알아서 더이상 참조하지 않을 객체를 지워주는 데, 우리는 왜 GC를 공부해야 할까요?

JVM은 메모리를 자동으로 관리해 줍니다. 이러한 이유로 GC는 성능입장에서 매우 중요합니다. 

그리고 GC를 수행할 때 다른 Thread의 작업이 모두 멈추는 Stop the World이벤트가 발생하는데 

이는 곧 성능 문제 및 여러 장애로 번질 가능성이 있고 잦은 STW는 사용자 경험도 좋지 않게 합니다. 따라서 프로그램 설계단계 때부터 GC도 함께 고려해야 하므로 개발자는 GC에 대해서 잘 알고 있어야 합니다. 

 

[꼬리질문] GC에 대한 정의를 말씀해 주세요.

가비지 컬렉터는 자바 메모리 관리 기법 중 하나로 JVM의 Heap영역에서 동적으로 할당했던 메모리 중

더 이상 사용하지 않는 객체를 주기적으로 제거하는 과정입니다. 

간단히 말씀드리면, Heap내의 객체 중 Garbage 대상을 식별해서 처리하고 할당된 Heap메로리를 회수합니다. 

 

[꼬리질문] 가비지 객체를 판별하는 방법에 대해 말씀해 주세요.

GC는 객체가 가비지인지 판별하기 위해 root로부터 참조가 되어있는지 확인합니다. 만약 참조와 무관하다면

unreachable객체로 GC 대상입니다. 

 

[꼬리질문] 가비지 객체를 처리할 때 사용하는 알고리즘에 대해 설명해 주세요. 

Mark and Sweep 동작 알고리즘을 사용합니다. 

root로부터 그래프 순회를 돌면서 어떤 객체를 참조하고 있는지 mark 합니다. 그리고 

GC대상이 되는 가비지 객체는 Heap에서 제거합니다. 그 후 선택적으로 분산된 객체들을

Heap의 시작 주소로 모아서 메모리가 할당된 부분과 아닌 부분으로 압축합니다. 

 

[꼬리질문] GC의 대상이 되는 공간은 Heap입니다. 이를 크게 두 영역으로 나누면 Young영역과 Old영역입니다. Young영역과 Old영역에서 어떤 일이 발생할까요? 

Young영역은 다시 3개의 영역으로 나뉩니다. Eden영역과 두 개의 Survivor 0,1영역입니다.

새롭게 생성한 객체의 대부분이 Eden영역에 위치합니다. 객체가 계속 생성되어 영역이 꽉 차게 되면  MInor GC가 실행됩니다. 그 후 Make and Sweep동작 알고리즘을 통해 살아남은 객체는 Survivor영역으로 이동합니다. 이 과정을 계속 반복 후 살아남은 객체는 Old영역으로 이동하게 됩니다. 

 

Old영역도 기본적으로 데이터가 가득 차면 GC를 실행하는데 이때는 MajorGC 또는 FullGC가 발생합니다. 

이 과정에서 Stop the world 이벤트가 발생하게 됩니다. 

 

[꼬리질문] GC알고리즘은 stw 문제를 해결하고자 계속해서 발전되어 왔습니다. 알고 있는 GC알고리즘이 있다면 말씀해 주세요. 

GC알고리즘 종류는 여러 가지가 있지만 대표적으로는 5개가 있습니다. 

1. Serial GC

2. Parallel GC

3. Parallel Old GC

4. Concurrent Mark&Sweep GC

5. G1(garbage first) GC

 

Serial GC는 싱글스레드, CPU코어가 1개일 때 사용하기 위해 개발된 것으로 운영 서버에서는 사용하면 안 되고 가장 stop-the-world시간이 깁니다. 

 

Parallel GC는 java8의 디폴트 GC이 Young영역의 MinorGC는 멀티 스레드로 수행되고 Old영역은 여전히 싱글 스레드 사용합니다. Serial GC보다 빠르게 객체를 처리할 수 있습니다. 메모리가 충분하고 코어의 개수가 많을 때 유리합니다. 

 

Parallel Old GC는 Old영역에서도 멀티스레드로 GC를 수행합니다. 

 

Concurrent Mark&Sweep GC의 경우 stw시간이 매우 짧다는 장점이 있지만 GC과정이 매우 복잡하고 다른 방식보다 메모리와 CPU를 더 많이 사용합니다. Java9부터 더 이상 사용이 권장되지 않고, 14부터는 중단되었습니다. 

 

G1(garbage first) GC

java9 이상 버전의 디폴트 GC입니다. garbage만 있는 영역을 먼저 회수하는 방식입니다. 

대용량의 메모리가 있는 멀티 프로세서 시스템을 위해 제작되었습니다. 

다른 알고리즘과 달리 Heap 메모리 전체 탐색이 아닌 영역을 나눠서 탐색하고 메모리가 많이 차 있는 영역을 우선적으로 GC 합니다. 가비지로 가득 찬 영역을 빠르게 회수하여 빈 공간을 확보하므로 GC빈도가 줄어드는 효과가 있지만 만약 공간이 부족한 상황이 오면 Full GC가 발생하는데 이때는 싱글스레드로 동작하므로 작은 Heap공간을 가지는 애플리케이션에서는 추천하지 않습니다. 

 

 

Q.  객체지향설계 5가지 원칙, SOLID에 대해 설명해 주세요.

SOLID 원칙이란 객체지향 설계에서 지켜줘야 할 5개의 소프트웨어 개발 원칙( SRP, OCP, LSP, ISP, DIP )을 말합니다. 시스템에 예상하지 못한 변경사항이 발생하더라도, 유연하게 대처하고 이후에 확장성이 있는 시스템 구조를 만들 수 있어야 하는데 이때 사용되는 원칙이 SOLID입니다. 

해당 원칙을 따라 프로그래밍을 하면 시간이 지나도 변경이 용이하며, 유지보수와 확장에 도움이 되는 원칙입니다. 

S: 단일책임원칙

O: 개방-폐쇄원칙

L: 리스코프 치환 원칙

I: 인터페이스 분리 원칙

D: 의존 역전 원칙

 

단일책임원칙은, 하나의 클래스는 하나의 기능을 담당해 하나의 책임을 가져야 한다는 원칙입니다. 

만약 하나의 클래스에 여러 기능이 있다면 유지보수할 때 수정해야 할 코드가 많아집니다. 

 

개방-폐쇄원칙은, 확장에는 열려있어야 하며, 수정에는 닫혀있어야 합니다. 

OOP특징 중 추상화를 꼽을 수 있고 추상클래스와 상속을 통한 클래스 관계를 나타냅니다. 

확장에는 열려있다는 의미는, 새로운 변경 사항이 발생했을 때 유연하게 코드를 추가하면서 기능을 확장할 수 있음을 의미합니다. 그리고 변경에는 닫혀있다는 의미는 새로운 변경 사항이 발생했을 때 객체를 직접적으로 수정을 제한합니다. 

리스코프 치환 원칙은, 하위 타입은 언제나 상위(부모) 타입으로 교체할 수 있어야 한다는 원칙으로 다형성이 적용되며 예로는 메서드 오버라이딩이 있습니다.
상위 클래스 형식으로 선언한 객체는 하위 클래스의 인스턴스를 받아 사용할 때도 정상적으로 작동해야 합니다.

 

인터페이스 분리 원칙은, 각각의 클라이언트에 맞게 인테페이스를 분리하여 설계하는 원칙입니다. 

클라이언트의 목적과 용도에 적합한 인터페이스를 제공하는 것이 목표입니다. 

 

의존 역전 원칙은, 어떤 class를 참조해야 한다면, 구현 클래스에 의존하지 말고 추상 클래스 또는 인터페이스에 의존하라는 원칙입니다. 클래스 간의 결합도를 낮추는 것이 목표입니다. 

 

 

Q.  객체지향 프로그래밍 OOP의 4가지 특징에 대해 말씀해 주세요.

캡슐화, 상속, 추상화, 다형성이 있습니다.

캡슐화란 

클래스 안에 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것을 말합니다. 

구현하는 방법은 접근제어자를 사용하는 방법이 있습니다. public, protected, default, private

대표적인 예로는 String클래스가 있습니다. 내부 코드를 보면 필드는 전부 private제어자로 선언되어 있어 외부에서 접근해서 변경할 수 없습니다. 

 

상속이란

상위 클래스의 속성(변수)과 기능(메서드)을 상속한 하위클래스가 전부 물려받는 것을 말합니다. 클래스 간 공유될 수 있는 속성과 기능은 상위클래스로 추상화시켜 해당 상위 클래스로부터 확장된 여러 개의 하위 클래스들이 모두 상위 클래스의 속성과 기능을 간편하게 사용 가능합니다. 또한 공통적인 부분에 코드 변경이 필요한 경우 상위 클래스에서 한 번의 수정으로 모든 클래스에 반영될 수 있어 코드의 중복제거, 코드 재사용성이 증대됩니다. 

 

[꼬리질문] 인터페이스와 상속의 차이점을 알고 계시나요?

공통점으로는 양자 모두 상위클래스-하위클래스 관계에서 공통적인 속성과 기능을 공유할 수 있습니다.

인터페이스의 경우, 인터페이스에 정의된 추상메서드의 내용이 하위 클래스에서 반드시 구현되어야 하지만

상속의 경우, 상위 클래스의 속성과 기능등은 하위 클래스에서 그대로 받아 사용하거나 오버라이딩을 통해서 재정의를 하여 사용할 수 이습니다. 

 

추상화

객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 말합니다. 공통 기능을 정의해 두면 기능 상속을 통해 빠르게 구조를 확장할 수 있습니다. 대표적인 예로는 추상 클래스와 인터페이스가 있습니다. 

 

[꼬리질문] 인터페이스와 추상 클래스의 차이점은 무엇일까요?

인터페이스는 확장, 관련이 적은 클래스끼리 묶어주고

추상클래스는 공통부분 관리에 초점을 맞추고,  주로 부모자식 관계를 묶어줍니다.

  추상 클래스 인터페이스
키워드 abstract interface
사용 가능 변수 제한 없음 상수 (static final)
사용 가능 접근 제어자  제한 없음 public
사용 가능 메서드  제한 없음 abstract method, (default method, static method, private method)
상속 키워드 extends implements
다중 상속 가능 여부 불가능 가능
사용처 중복 멤버 통합 1. 관련이 적은 클래스들(예) 형제관계)을 묶은 후 구현 객체가 같은 동작을 한다는 것을 보장하기 위해

2. 다중 상속을 해야할 때
공통점 1. 추상 메서드를 가지고 있다.
2. (추상 메서드 구현이 안 되었으므로) 인스턴스화 할 수 없다.
3. 추상 클래스 또는 인터페이스를 상속받아 구현한 구현체의 인스턴스를 사용한다.

 

다형성

특정 객체의 속성이나 기능이 상황 따라 다양한 역할을 수행할 수 있는 특징을 말합니다. 조상클래스의 참조 변수로 자손 클래스 객체를 다루거나, 동일한 이름을 가진 여러 형태의 메서드를 생성할 수 있는 능력을 의미합니다.

대표적인 예로는, 오버로딩, 오버라이딩, 업캐스팅, 다운 캐스팅, 추상클래스, 인터페이스 등이 있습니다. 

 

일반적으로 동일한 클래스 타입으로 참조 변수를 생성하지만, 부모자식 관계에서는 부모 타입의 참조 변수를 사용하여 자식 클래스의 객체를 다룰 수 있습니다. 한 부모클래스를 상속하는 자식클래스가 많다면 공통적인 타입으로 묶음으로써 코드 가독성이 좋아지고 코드 중복 제거 그리고 코드 재사용성을 증가시키는 특성을 가집니다. 

 

 

Q.  데이터 타입 간의 변환인 캐스팅 또는 형변환에 대해 말씀해 주세요. 

형변환을 하는 이유는 프리미티브 타입 간의 형변환의 경우 연산을 수행할 때 같은 타입끼리 가능하기 때문입니다.  형변환 유형에는 두 가지가 있습니다. 

프리미티브 타입 간의 형변환 (Primitive Type Casting) 그리고 클래스 타입 간의 형변환 (Object Casting)이 있습니다. 두 유형 다 형변환 연산자를 사용해서 캐스팅합니다. 

클래스 타입 간의 형변환의 예로는 조상 클래스와 자손 클래스 간의 형변환이나 인터페이스와 구현 클래스 간의 형변환 이 있습니다. 여기서 조상에서 자손으로 가는 건 다운캐스팅, 반대의 경우는 업캐스팅이라 합니다. 

 

그리고 형변환 방법에는 두 가지가 있습니다. 자동 형변환과 강제 형변환입니다.  

메모리에 할당받은 바이트의 크기가 작은 타입에서 큰 타입으로 형변환 시 : 자동 형변환

반대로 큰 타입에서 작은 타입으로 갈 때는 강제로 형변환 해주어야 합니다. 이때는 데이터 손실이 발생합니다.

이를 방지하기 위해서 변환될 타입의 최솟값과 최댓값을 벗어나는지 검사를 하고 만약 벗어난다면 형변환을 하지 않는 식의 방법을 사용할 수 있습니다. 

 

[꼬리질문] 클래스 타입 간의 형변환에 대한 질문을 이어서 하겠습니다. 여기서 참조변수의 형변환이란 어떤 의미일까요?

참조변수의 형변환은 사용할 수 있는 멤버의 개수를 조절하는 것입니다. 

 

[꼬리질문]  업캐스팅에 대해 말씀해주세요.

자식 클래스가 부모 클래스 타입으로 캐스팅되는 것으로 업캐스팅을 하면 멤버의 개수가 감소되어서 자식 클래스에만 있는 멤버는 사용할 수 없게 됩니다. 그리고 오버라이딩 된 메서드가 있다면 부모 클래스의 메서드가 아닌 자식 클래스의 메서드로 실행이 됩니다. 

 

업캐스팅을 하는 이유는 공통적으로 할 수 있는 부분을 만들어 간단하게 다루기 위해서입니다. 상속관계에서 상속받은 하위 클래스가 몇 개이든 하나의 인스턴스로 관리할 수 있습니다. 

 

업캐스팅의 문제점은 부모 클래스 타입에서 자식 클래스 고유의 메서드를 사용할 수 없는 것인데, 이때 다운 캐스팅이 필요합니다. 

 

[꼬리질문] 다운캐스팅에 대해 말씀해주세요.

부모 클래스로 업 캐스팅된 자식 클래스를 복구하여 본인의 필드와 기능을 회복합니다. 

 

[꼬리질문] 참조 캐스팅을 잘못했다간 런타임 환경에서 에러가 나서 프로그램이 종료가 될 수 있습니다. 이때 어떤 걸 이용하면 예방할 수 있을까요?

형변환 전에 형변환이 가능한지 알아보는 instanceof연산자를 사용합니다. 참조형 타입 캐스팅에서만 사용할 수 있고 기본형 타입에서는 사용이 불가합니다. 

 

 

Q. Stream API와 람다식, 함수형 인터페이스

JDK8부터 Stream API와 람다식, 함수형 인터페이스 등을 지원하면서 Java를 이용해 함수형으로 프로그래밍할 수 있는 API 들을 제공해주고 있습니다. Stream 연산들은 매개변수로 함수형 인터페이스(Functional Interface)를 받도록 되어있습니다. 그리고 람다식은 반환값으로 함수형 인터페이스를 반환하고 있어 세 개념 모두 알고 있어야 합니다. 

 

[Stream API]

Stream API는 원본의 데이터를 변경하지 않고, 일회용이며 내부 반복으로 작업을 처리한다는 특징이 있습니다.  원본의 데이터를 조회 후 별도의 요소로 Stream을 생성합니다. 일회용이므로 재사용은 불가합니다. Stream을 이용하면 코드가 간결해진다는 이점이 있는데 이 이유는 내부 반복 때문입니다. 반복문 문법이 stream.forEach() 내부에 숨기고 있기 때문입니다. 

생성, 가공, 결과 3단계로 이루어지는데 가공단계에서는 연산 결과를 다시 Stream으로 반환하기에 .을 이용해서 연산을 이어나갈 수 있습니다. 

Stream 연산들은 매개변수로 함수형 인터페이스(Functional Interface)를 받도록 되어있다. 그리고 람다식은 반환값으로 함수형 인터페이스를 반환하고 있다. 

[람다식]
함수를 하나의 식으로 표현한 것입니다. 메서드의 이름이 필요 없기에 익명함수의 한 종류입니다. 
(매개변수,...) -> {실행문....}으로 표현합니다. 
특징으로는 람다식 내에서 사용되는 지역변수는 final을 붙이지 않아도 상수로 간주되고 람다식으로 선언된 변수명은 다른 변수명과 중복될 수 없습니다. 
불필요한 코드를 줄이고 가독성을 높이기 위한 방법이지만 디버깅이 어렵고 람다를 남발하면 비슷한 함수가 중복 생성되므로 코드가 지저분해질 수 있습니다. 

[함수형 인터페이스]
함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션으로 인터페이스에 선언하여 단 하나의 추상 메서드만
갖도록 제한합니다. 이를 통해 함수를 변수처럼 선언할 수 있다. 
함수형 인터페이스를 사용하는 이유는 자바의 람다식이 함수형 인터페이스를 반환하기 때문입니다. 

 

Q. 에러와 예외, 그리고 Checked Exception와 UnChecked Exception 차이점

에러는 개발자가 대응할 수 없는 것, 예외는 개발자가 대응 할 수 있다는 차이점이 있습니다. 

다시 예외는 체크예외와 언체크예외로 나뉘는 데 핵심적인 차이는 반드시 예외 처리를 해야 하는가?입니다. 

 

Checked Exception은 체크하는 시점이 컴파일 단계이기 때문에, 별도의 예외 처리를 하지 않는다면 컴파일 자체가 되지 않습니다. 따라서 Checked Exception이 발생할 가능성이 있는 메서드라면 반드시 로직을 try - catch로 감싸거나 throws로 던져서 처리해야 합니다. 

 

반면에 Unchecked Exception의 경우는  RuntimeException 클래스를 상속받는 예외 클래스  명시적인 예외 처리를 하지 않아도 됩니다. 따라서  try - catch 처리하지 않더라도 컴파일도 되고 실행까지 가능합니다.

 

 

Q. 자료형 중 실수형 float, double이 있습니다. 이런 자료형은 특히 돈계산할 때 쓰지 말라고 하는 데 왜일까요?

float와 double은  정확한 값(precise value)이 아닌 근삿값(approximate value)을 담고 있기에 매번 오차가 존재하기 때문입니다. 이 문제를 부동소수점 문제라고 합니다. 

[꼬리질문] 그럼 무엇을 써야 할까?

BigDecimal 클래스를 사용하면 됩니다. 

 

 

Q. ==과 equals()의 차이점에 대해 말씀해 주세요.

두 연산은 동일성과 동등성의 차이입니다. 

==은 두 객체가 동일한 객체인지 즉, 같은 메모리 주소를 가졌는지 확인하는 동일성을 확인할 때 사용하고

. equals()는 두 객체의 값이 같은지 확인하는 동등성을 확인할 때 사용합니다. 

 

equals()의 경우, 따로 오버라이드 하지 않으면 두 객체의 hashCode() 값을 비교하게 되므로,
객체의 동등성을 비교해야 하는 상황이 온다면 두 객체의 equals(), hashCode()를 모두 오버라이드 해야 합니다.

 

[꼬리질문] equals()로 등등성을 확인할 수 있는 데 오버라이딩을 해서 사용하는 경우는 왜일까요?

똑같은 클래스 타입과 똑같은 값으로 객체 생성을 했다고 가정하면 프로그램은 주소 값으로 비교하므로 다른 객체로 인식합니다. 하지만 주소 값이 아닌 객체의 필드값을 기준으로 동등 비교 기준을 변경하고 싶다면, equals 메서드를 오버라이딩해서 주소가 아닌 필드값을 비교하도록 재정의 해주면 된다.

[꼬리질문] hashcode()란 무엇일까요?
hashCode 메서드는 객체의 주소 값을 이용해서 해싱(hashing) 기법을 통해 해시 코드를 만든 후 반환합니다. 그렇기 때문에 서로 다른 두 객체는 같은 해시 코드를 가질 수 없는 고유한 숫자값을 가지게 됩니다. 

[꼬리질문] 객체의 동등성을 비교할 때 두 객체의 equals(), hashCode()를 모두 오버라이드(재정의) 해야 합니다. 이유는 뭘까요?
두 객체를 비교하는 순서가 hashCode()의 리턴값이 먼저이기 때문입니다. 이때 해시코드가 같다면 그 후 equals() 메서드의 리턴 값을 비교하게 되고, true이면 논리적으로 같은 객체라고 판단합니다. 

 

일반적인 String 타입이나 자바에서 미리 만들어놓은 객체 데이터의 경우에는 문제가 되지 않습니다. 문제는 사용자 정의 클래스 자료형을 만들어 사용할 때 equals와 hashCode를 재정의 하지 않으면 오작동이 일어날 수 있다는 점이다. 


Q. Java에서 어느 부분이 call by value이고 어느 부분이 call by reference에 해당하나요?
Java는 기본적으로 모든 전달 방식이 call by value 입니다.

call by value는 메서드 호출 시에 인자로 값을 복사하여 전달해 주는 것이고, call by reference는 인자로 값의 주소를 전달하는 방식입니다.

 

참조형의 경우 객체의 주소값을 매개변수로 전달하니 call by reference가 아니냐는 의문을 가질 수 있지만,
정확하게 말하면 주소값이 아니라, '주소를 가리키는 참조값'입니다.
또한, 주소값 자체를 복사 없이 인자로 전달하는 게 아니라 자기 자신이 갖고 있는 값을 복사해서 전달합니다.
결국 기본형 변수나 참조형 변수 모두 자기 자신이 갖고 있는 값을 복사해서 전달하기 때문에 call by value입니다.

 

Q. 불변 (Immutable) 객체에 대해 설명해 주세요.

객체 생성 이후에는 객체의 상태가 바뀌지 않는 객체를 의미합니다. 

불변 객체의 장점
1. 안정적인 서비스 개발에 도움이 된다.
메서드의 파라미터로 해당 객체를 넘길 때, 만약에 가변 객체라면 그 메서드 내부 코드에서 해당 객체의 상태 정보를 바꿀 수 있는 위험이 존재해 나중에 다시 사용할 때는 객체를 DB에서 가져와야 합니다. 하지만 불변객체라면 이와 같은 걱정을 하지 않아도 됩니다. 


2. 일반적으로 thread-safe 하다.
불변객체는 상태 정보가 항상 같기 때문에, 여러 스레드가 하나의 객체를 공유할 때 경쟁 상황 없이 사용할 수 있다. 그런데, 외부에 노출되는 상태 정보는 immutable 하지만 내부에서만 관리되는 상태는 mutable 한 경우에도 immutable 객체라고 부르기도 한다. 이때는 thread-safe 하지 않을 수 있습니다.

3. 불변객체를 필드로 사용하면 방어적 복사를 할 필요가 없습니다.

이러한 장점으로 모든 것을 불변 객체로 표현할 수는 없겠지만, 가능하다면 불변 객체를 적극적으로 쓰는 것을 추천합니다. 

 

[꼬리질문] 자바에서 대표적인 불변객체는 무엇입니까?

String

[꼬리질문] 그럼 자바에서 클래스를 Immutable(불변)하게 만드는 방법에 대해 말씀해 주세요.

1. 상태 변경 가능한 set메서드를 삭제합니다. 

2. 모든 필드를 private final로 지정합니다. 

3. 클래스가 오버라이딩이 가능하면 상태가 변경 가능해지므로 클래스 앞에 final 키워드를 붙입니다. (클래스 상속 금지)

4. mutable 객체의 레퍼런스 공유를 금지합니다.

이 경우는 mutable객체를 멤버변수로 가지게 될 때 객체 자체를 새롭게 만들어서 복사하는 방어적 복사를 사용하면 됩니다. 이렇게 하면 mutable객체를 멤버변수로 가지게 될 때에도 imutable객체의 하게 만들 수 있습니다. 다른 말로 하면 불변 객체를 필드로 사용하면 방어적 복사를 할 필요가 없다는 것입니다.

 

 

Q. 객체를 복사할 때 얕은 복사와 깊은 복사의 차이점에 대해 말씀해 주세요.

얕은 복사의 경우 객체의 주소 값을 복사하기에 원본을 변경하면 복사본도 영향을 받습니다.

반면에 깊은 복사는 실제값을 복사해서 이 값을 새로운 메모리 공간에 복사하므로 원본과 복사본이 서로 다른 객체를 참조하기에 원본의 변경이 복사본에 영향을 미치지 않습니다. 

 

Q. Wrapper클래스를 사용하는 이유가 무엇일까요?

기본 타입(primitive type)을 객체로 다루기 위해서 사용하는 클래스들을 래퍼 클래스(wrapper class)라고 합니다.  자바는 모든 기본타입(primitive type) 값을 갖는 객체를 생성할 수 있습니다.  int는 Integer, char는 Character이고 나머지는 대문자로 시작합니다. 

 

프로그래밍을 하다 보면 기본 타입의 데이터를 객체로 표현해야 하는 경우가 종종 생기게 됩니다. 

예를 들어 메서드의 인수로 객체 타입만이 요구될 경우, 멀티스레드 환경에서 동기화 데이터를 사용해야 할 경우 이를 객체화해야 할 필요성이 생기는데요. 이렇게 객체를 포장하는 기능 외에도, 래퍼 클래스는 자체 지원하는 parse타입 메서드를 이용하여 데이터 타입을 형변환할 때도 유용히 쓰입니다.

 

래퍼 클래스로 감싸고 있는 기본 타입 값은 외부에서 변경할 수 없다.

 

[꼬리질문] 박싱과 언박싱은 무엇일까요? 왜 필요할까요?

값을 포장하여 객체로 만들면 이 형태로는 값을 연산할 수 없습니다. 이때 필요한 행위가 박싱(Boxing)과 언박싱(UnBoxing)입니다. 박싱은 기본 타입 데러를 래퍼 클래스의 인스턴스로 변환하는 것을 말하며, 언박싱은 래퍼클래스의 인스턴스에 저장된 을 기본 타입 데이터로 변환하는 것을 말합니다. 

래퍼클래스를 언박싱 한 뒤에 값을 변경한 뒤 박싱해야 합니다. 

 

 

Q. 반복문 루프 안에서 객체를 생성하는 것은 좋지 않다고 하는데 왜 그럴까요?

반복문안에서 기본형 int 그리고 참조형 Integer로 객체를 생성할 수 있습니다. 만약 Integer를 이용하면 힙 메모리에 계속 객체는 쌓일 것입니다.  두 방식의 결과값은 같겠지만, 앞서 만든 Integer객체는 영원히 안 쓰이므로 힙 메모리가 다 차게 되면 GC가 발생하게 되고 그때마다 stw이벤트가 발생해 성능에까지 영향을 미칩니다. 따라서 GC까지 고려해서 프리미티브 타입을 사용할 건지 레버런스 타입을 사용할 건지 결정해야 합니다.