[면접예상질문] Java

ChangSol·2024년 7월 4일

Java 특징

  • 객체 지향 언어
  • 기본 자료형을 제외한 모든 요소들이 객체로 표현된다.
  • 객체 지향 특징인 캡슐화, 상속, 추상화, 다형성을 갖는다.
  • 장점
    • JVM에서 동작하기 때문에 운영체제에 독립적이다.
    • GabageCollector를 통한 자동적인 메모리 관리가 가능하다.
  • 단점
    • JVM에서 동작하기 때문에 실행 속도가 상대적으로 느리다.
    • 다중 상속이나 타입에 엄격하며, 제약이 많다.

OOP

  • 객체 지향 프로그래밍
  • 특징 : 캡슐화, 상속, 추상화, 다형성
    • 캡슐화 : 데이터와 코드의 형태를 외부로부터 알 수 없게 하고, 데이터의 구조와 역할, 기능을 하나의 캡슐 형태로 만드는 방법
    • 상속 : 부모 클래스에 정의된 변수 및 메서드를 자식 클래스에서 상속받아 사용하는 것
    • 추상화 : 클래스들의 공통적인 특성(변수, 메소드)들을 묶어 표현하는 것. (추상클래스, 인터페이스)
    • 다형성 : 다양한 형태로 표현이 가능한 구조를 말한다.

JVM

  • JVM은 스택 기반으로 동작하며, Java Byte Code를 OS에 맞게 해석 해주는 역할을 하고 가비지컬렉션을 통해 자동적인 메모리 관리를 해줍니다.

Compile process

  1. .java 파일로 개발
  2. build 시 java compiler가 javac 명령어를 통해 .class(바이트코드)를 생성
  3. Class Loader를 통해 JVM 메모리 내 로드
  4. 실행엔진이 바이트코드들을 인터프리터 방식 혹은 jit컴파일러 방식으로 실행

primitive(원시타입), wrapper class(래퍼클래스)

Primitive TypeWrapper Class
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter
  • Wrapper Class는 Primitive Type을 객체화한 것
int primitive = 10;
Integer wrapper = 10;
wrapper = null;  // null 값이 들어갈 수 있습니다.
String str = wrapper.toString();  // 메서드를 사용할 수 있습니다.
ArrayList<Integer> integerList = new ArrayList();  // 제네릭에 사용할 수 있습니다.

int와 Integer를 예로 든다면 int의 경우 단순하게 숫자로서만 사용할 수 있지만 Integer의 경우 객체 형태로 생성되어 null 값이 들어갈 수 있고,. toString과 같은 메서드를 사용할 수 있으며, 제네릭 Integer의 형태로도 사용할 수 있게 됩니다.

  • primitive type 은 stack에 적재되며, 비교 시 ==, null 값을 가질 수 없음.
  • wrapper class type 은 heap에 적재되며, 비교 시 equals, null 값을 가질 수 있음
  • Boxing : primitive -> wrapper class
  • UnBoxing : wrapper class -> primitive

Overloading, Overriding

  • Overloading : 매개변수의 개수나 타입을 다르게 하여 같은 이름의 메소드를 여러 개 정의
  • Overriding : 상위 클래스에 있는 메소드를 하위 클래스에서 재정의 하는 것

try-with-resources

  • try-with-resources는 try-catch-finally의 문제점을 보완하기 위해 나온 개념입니다.
  • try( ... ) 안에 자원 객체를 전달하면, try블록이 끝나고 자동으로 자원 해제 해주는 기능을 말합니다.
  • 따로 finally 구문이나 모든 catch 구문에 종료 처리를 하지 않아도 되는 장점이 있습니다.

불변 객체

  • 불변 객체는 객체 생성 이후 내부의 상태가 변하지 않는 객체를 말합니다.
  • Java에서는 필드가 원시 타입인 경우 final 키워드를 사용해 불변 객체를 만들 수 있고,
  • 참조 타입일 경우엔 추가적인 작업이 필요합니다.
참조 타입일 경우 추가적인 작업
  1. 참조 변수가 일반 객체인 경우 객체를 사용하는 필드의 참조 변수도 불변 객체로 변경해야 합니다. 
  2. 배열일 경우 배열을 받아 copy해서 저장하고, getter를 clone으로 반환하도록 하면 됩니다.
    (배열을 그대로 참조하거나, 반환할 경우 외부에서 내부 값을 변경할 수 있음. 때문에 clone을 반환해 외부에서 값 변경하지 못하게 함) 
  3. 리스트인 경우에도 배열과 마찬가지로 생성시 새로운 List를 만들어 값을 복사하도록 해야 합니다.
    배열과 리스트는 내부를 복사하여 전달하는데, 이를 방어적 복사(defensive-copy)라고 합니다.
불변 객체나 final을 굳이 사용해야 하는 이유
  1. Thread-Safe하여 병렬 프로그래밍에 유용하며, 동기화를 고려하지 않아도 된다.
    (공유 자원이 불변이기 때문에 항상 동일한 값을 반환하기 때문) 
  2. 실패 원자적인 메소드를 만들 수 있다.
    (어떠한 예외가 발생되더라도 메소드 호출 전의 상태를 유지할 수 있어 예외 발생 전과 똑같은 상태로 다음 로직 처리 가능) 
  3. 부수효과를 피해 오류를 최소화 할 수 있다.
    ※ 부수효과 : 변수의 값이 바뀌거나 객체의 필드 값을 설정하거나 예외나 오류가 발생하여 실행이 중단되는 현상 
  4. 메소드 호출 시 파라미터 값이 변하지 않는다는 것을 보장할 수 있다. 
  5. 가비지 컬렉션 성능을 높일 수 있다.
    (가비지 컬렉터가 스캔하는 객체의 수가 줄기 때문에 Gc 수행 시 지연시간도 줄어든다.)

싱글톤패턴

  • 싱글톤 패턴은 단 하나의 인스턴스를 생성해 사용하는 디자인 패턴입니다.
  • 인스턴스가 1개만 존재해야 한다는 것을 보장하고 싶은 경우와
  • 동일한 인스턴스를 자주 생성해야 하는 경우에 주로 사용합니다. (메모리 낭비 방지)
  • 싱글톤 패턴의 대표적인 예시는 Spring Bean
  • 스프링은 요청할 때마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.
    (프로토타입 빈, @Scope("prototype"))

가비지 컬렉션(Garbage Collection)

  • 가비지 컬렉션은 JVM의 메모리 관리 기법 중 하나로 시스템에서 동적으로 할당됐던 메모리 영역 중에서 필요없어진 메모리 영역을 회수하여 메모리를 관리해주는 기법입니다.
  • GC의 작업을 수행하기 위해 JVM이 어플리케이션의 실행을 잠시 멈추고, GC를 실행하는 쓰레드를 제외한 모든 쓰레드들의 작업을 중단 후 (Stop The World 과정) 사용하지 않는 메모리를 제거(Mark and Sweep 과정)하고 작업이 재개됩니다.
  • GC의 작업은 Young 영역에 대한 Minor GC와 Old 영역에 대한 Major GC로 구분됩니다.

객체지향의 설계원칙(SOLID)

  • SRP - 단일 책임 원칙 : 한 클래스는 하나의 책임만 가져야 한다.
  • OCP - 개방-폐쇄 원칙 : 확장에는 열려있고, 수정에는 닫혀있어야 한다.
  • LSP - 리스코프 치환 원칙 : 하위 타입은 항상 상위 타입을 대체 할 수 있어야 한다.
  • ISP - 인터페이스 분리 원칙 : 인터페이스 내에 메소드는 최소한 일수록 좋다. (하나의 일반적인 인터페이스보다 여러 개의 구체적인 인터페이스가 낫다.) SRP와 같은 문제에 대한 두 가지 다른 해결책이다.
  • DIP - 의존관계 역전 원칙 : 구체적인 클래스보다 상위 클래스, 인터페이스, 추상클래스와 같이 변하지 않을 가능성이 높은 클래스와 관계를 맺어라. DIP 원칙을 따르는 가장 인기 있는 방법은 의존성 주입(DI)이다.

자바의 메모리 영역

  • 자바의 메모리 공간은 크게 Method 영역, Stack 영역, Heap 영역으로 구분되고, 데이터 타입에 따라 할당됩니다.
  • 메소드(Method) 영역 : 전역변수와 static변수를 저장하며, Method영역은 프로그램의 시작부터 종료까지 메모리에 남아있다. JVM이 동작해서 클래스가 로딩될 때 생성
  • 스택(Stack) 영역 : 지역변수와 매개변수 데이터 값이 저장되는 공간이며, 메소드가 호출될 때 메모리에 할당되고 종료되면 메모리가 해제된다. LIFO(Last In First Out) 구조를 갖고 변수에 새로운 데이터가 할당되면 이전 데이터는 지워진다. 메소드가 호출될 때 할당
  • 힙(Heap) 영역 : new 키워드로 생성되는 객체(인스턴스), 배열 등이 Heap 영역에 저장되며, 가비지 컬렉션에 의해 메모리가 관리되어 진다. Runtime에서 할당

Synchronized

  • 여러 개의 쓰레드가 한 개의 자원을 사용하고자 할 때, 현재 데이터를 사용하고 있는 쓰레드를 제외하고 나머지 쓰레드들은 데이터에 접근할 수 없게 막는 개념입니다.
  • 데이터의 thread-safe를 하기 위해 자바에서 Synchronized 키워드를 제공해 멀티 쓰레드 환경에서 쓰레드간 동기화를 시켜 데이터의 thread-safe를 보장합니다.
  • Synchronized는 변수와 메소드에 사용해서 동기화 할 수 있으며, Synchronized 키워드를 남발하게 되면 오히려 프로그램의 성능저하를 일으킬 수 있습니다.

String, StringBuffer, StringBuilder

  • String은 불변의 속성을 가지며, StringBuffer와 StringBuilder는 가변의 속성을 가집니다.
  • StringBuffer는 동기화를 지원하여 멀티 쓰레드 환경에서 주로 사용하며,
  • StringBuilder는 동기화를 지원하지 않아 싱글 쓰레드 환경에서 주로 사용합니다.

접근 제한자

  • 변수 또는 메소드의 접근 범위를 설정해주기 위해서 사용하는 Java의 예약어를 의미하며, 총 4 가지 종류가 있습니다.
  • public - 접근 제한이 없다. (같은 프로젝트 내 어디서든 사용 가능)
  • protected - 해당 패키지 내, 다른 패키지에서 상속받아 자손 클래스에서 접근 가능하다.
  • (default) - 해당 패키지 내에서만 접근 가능
  • private - 해당 클래스에서만 접근 가능

static

  • static 키워드를 사용한 변수나 메소드는 클래스가 메모리에 올라갈 때 자동으로 생성되며 클래스 로딩이 끝나면 바로 사용할 수 있습니다. 즉, 인스턴스(객체) 생성 없이 바로 사용 가능합니다.
  • 모든 객체가 메모리를 공유한다는 특징이 있고, GC 관리 영역 밖에 있기 때문에 프로그램이 종료될 때까지 메모리에 값이 유지된 채로 존재하게 됩니다.

reflection

  • 리플렉션이란 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API 입니다.

collection

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

Set과 Map의 타입이 Wrapper Class가 아닌 Object를 받을 때 중복 검사

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

generic

  • 제네릭은 데이터의 타입을 하나로 지정하지 않고 사용할 때마다 범용적이고 포괄적으로 지정한다는 의미입니다.
  • 제네릭 타입을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있어 에러를 사전에 방지할 수 있습니다.

직렬화

  • 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술이며, 반대로 직렬화된 바이트 형태의 데이터를 다시 객체로 변환하는 과정을 '역직렬화'라고 합니다.
  • (간단히) JVM의 메모리에 상주(힙 or 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술
SerialVersionUID를 선언해야 하는 이유
  • JVM은 직렬화와 역직렬화를 하는 시점의 클래스에 대한 버전 번호를 부여하는데, 만약 그 시점에 클래스의 정의가 바뀌어 있다면 새로운 버전 번호를 할당하게 됩니다. 그래서 직렬화할 때의 버전 번호와 역직렬화를 할 때의 버전 번호가 다르면 역직렬화가 불가능하게 될 수 있기 때문에 이런 문제를 해결하기 위해 SerialVersionUID를 사용합니다.
  • 만약 직렬화할 때 사용한 SerialVersionUID의 값과 역직렬화 하기 위해 사용했던 SVUID가 다르다면 InvalidClassException이 발생할 수 있다.

[reference]
https://dev-coco.tistory.com/153
https://wildeveloperetrain.tistory.com/12
https://dev-coco.tistory.com/32

profile
Back-End Developer

0개의 댓글