1. SOLID
  2. 동일성(identity)와 동등성(equality)
  3. 원시타입과 참조타입
  4. String, StringBuilder, StringBuffer
  5. Java8에서 추가된 기능
  6. Checked Exception과 Unchecked Exception
  7. 스프링 트랜잭션 추상화에서 rollback 대상

1. SOLID

객체 지향 설계의 5가지 원칙

좋은 설계 시스템에 새로운 요구사항이나 변경 사항이 있을 때 영향을 받는 범위가 적은 구조 시스템에 예상하지 못한 변경사항이 발생하더라도, 유연하게 대처하고 확장성 있는 시스템

SRP : 단일 책임 원칙

  • 한 클래스는 하나의 책임만 가져야 한다
  • ‘변경’이 있을 때 파급효과가 적으면 단일 책임 원칙을 잘 따른 것
  • 다른 클래스들이 서로 영향을 미치는 연쇄작용을 줄일 수 있음
  • 응집도는 높이고 결합도는 낮춤
  • 책임을 적절하게 분배하여 코드의 가독성을 향상시키고 유지보수를 용이하게 함

OCP : 개방 폐쇄 원칙

  • 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야 한다
  • 요구사항의 변경이나 추가사항이 발생하더라도 기존 구성 요소는 수정이 이루어지지 않아야 하며, 쉽게 확장이 가능하여 재사용할 수 있어야 함
  • 관리가 용이하고 재사용 가능한 코드를 만드는 기반
  • 추상화와 다형성
  • 변할 부분과 변하지 않을 부분을 명확히 구분해 변할 수 있는 부분은 추상화하여 상속하는 클래스가 의존할 수 있게 코드를 작성해야 함

LSP : 리스코프 치환 원칙

  • 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다
  • 부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 한다
  • 프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위타입의 인스턴스로 바꿀 수 있어야 함
  • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것을 의미 (다형성을 위한 원칙)

Screenshot

Screenshot

ISP : 인터페이스 분리 원칙

  • 클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안된다
  • 자신이 사용하지 않는 인터페이스는 구현하지 않아야 함
  • 특정 클라이언트를 위한 인터페이스가 여러개가 범용 인터페이스 하나보다 낫다
  • 하나의 큰 인터페이스를 상속받기 보다 인터페이스를 구체적이고 작은 단위들로 분리시켜 꼭 필요한 인터페이스만 상속하자는 의미
  • 인터페이스의 단일 책임 강조

DIP : 의존 관계 역전 원칙

  • 추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다
  • DIP를 구현하는 방법 = DI
  • 구현 클래스에 의존하지 말고 인터페이스에 의존 = 인터페이스를 적극 활용
  • 상위클래스일수록, 인터페이스일수록, 추상클래스일수록 변하지 않을 가능성이 크므로 더 추상적인것에 의존

Question

SOLID 원칙을 지키는 코드가 항상 더 나은 코드인가요? 이유와 함께 설명해주세요

SOLID 원칙을 따른 코드가 클래스가 지나치게 세분화되어 복잡해 보일 수는 있지만, 장기적으로 봤을때 유지보수성이 향상될 수 있습니다.

2. 동일성과 동등성

동일성(Identity)

  • 두 객체가 메모리 상에서 같은 위치를 참조하는지 확인하는 개념
  • 두 객체가 완전히 동일한 객체일 때만 참
  • == 연산자
Object obj1 = new Object();
Object obj2 = obj1; // obj1과 obj2는 동일성 참조
System.out.println(obj1 == obj2); // true

동등성(Equality)

  • 두 객체가 의미적으로 동일한 값이나 상태를 가지는지 확인하는 개념
  • 객체의 값 또는 속성 비교
  • equals()
  • null 객체 비교 시 equals() 호출은 NullPointerException을 유발할 수 있음
String str1 = new String("hello");
String str2 = new String("hello"); 
System.out.println(str1.equals(str2)); // true (값 비교)
System.out.println(str1 == str2);     // false (다른 참조)
 

Question

equals()와 hashCode()를 올바르게 구현하지 않을 경우 발생할 수 있는 문제를 설명하세요.

equals()와 hashCode()를 잘못 구현하면, HashMap 또는 HashSet 같은 컬렉션에서 동일한 값을 가진 객체를 중복 삽입하거나 삭제하는 과정에서 예상치 못한 결과가 발생할 수 있습니다.

3. 원시타입과 참조타입

원시타입

  • 값 자체를 저장하는 데이터 유형
  • 메모리상 스택에 저장
  • 고정된 크기와 단순한 구조
  • byte, short, int, long, float, double, char, boolean

참조타입

  • 값이 저장된 메모리의 주소를 저장
  • 스택에 주소 저장, 실제 데이터는 힙 메모리에 저장
  • 동적으로 크기가 변하거나 복잡한 구조
  • class, interface, array, enum, String

4. String, StringBuilder, StringBuffer

String

  • 불변 객체 (객체의 값 변경 불가능) 보안, 캐싱, 쓰레드 안전성이 우수
  • 값을 변경할 때마다 새로운 객체 생성 성능 저하와 메모리 낭비 발생
  • 문자열이 자주 변경되지 않고 주로 읽기 작업만 수행되는 경우 사용

StringBuilder

  • 가변 객체 , 객체를 새로 생성하지 않고 동일 객체 내부 값 변경
  • 비동기 환경에서 안전하지 않음, 단일 쓰레드 환경에서만 안전
  • 성능이 중요하고 동기화가 필요 없는 환경에서 사용
  • 문자열 변경 작업이 많고, 단일 쓰레드 환경

StringBuffer

  • 가변 객체, String Builder와 동일
  • 동기화 처리가 되어있어 멀티쓰레드 환경에서도 안전
  • 성능이 중요하고 동기화가 필요한 환경에서 사용
  • 동기화로 인해 성능이 StringBuilder보다 낮음
  • 문자열 변경이 많고, 멀티 쓰레드 환경에서 안전성이 중요한 경우

Question

StringBuilder가 String보다 성능이 좋은 이유는 무엇인가요?

String은 불변 객체이기 때문에 문자열을 변경할 때마다 새로운 객체를 생성합니다. StringBuilder는 가변 객체로 동일한 객체에서 문자열을 변경하므로 객체 생성 비용과 메모리 사용량이 줄어듭니다.

Question

멀티쓰레드 환경에서 StringBuilder를 사용하면 발생하는 문제?

StringBuilder는 동기화를 지원하지 않기 때문에 여러 쓰레드가 동시에 같은 StringBuilder 객체를 수정하려고 할 때 데이터의 일관성을 잃거나 데이터에 손상이 일어날 수 있습니다. 이런 상황에서는 동기화를 지원하는 StringBuffer를 사용해야 합니다.

5. Java8에서 추가된 기능

람다 표현식(Lambda Expressions)

  • 함수형 프로그래밍을 지원하며 익명 함수처럼 동작
  • 코드 간결성을 높이고 컬렉션 처리 및 이벤트 핸들링을 쉽게 만들어 줌

Optional

  • NullPointerException의 부담을 줄이기 위해 등장한 Wrapper 클래스
  • 명시적으로 반환값이 Null일 수 있음을 알려줌
  • 시스템 오버헤드가 증가해 성능이 저하됨
  • NullPointerException 방지, 불필요한 null 체크 감소

Stream

  • 데이터의 흐름으로 람다를 사용할 수 있도록 제공
  • 컬렉션, 배열 등의 데이터 소스를 처리하는 데 사용
  • 병렬처리 가능
  • 코드의 양을 줄이고 간결하게 표현 가능

Question

Stream API와 기존 for 반복문의 차이

Stream API는 데이터를 병렬로 처리할 수 있지만 for 반복문에서는 병렬 처리 구현을 별도로 해야 합니다. Stream API는 for 반복문에 비해 간결하고 복잡한 필터링, 매핑, 정렬등의 복잡한 작업을 쉽게 처리할 수 있습니다.

함수형 인터페이스

  • 일반적으로 구현해야 할 추상메소드가 하나만 정의된 인터페이스
  • 람다식의 타입으로 사용
  • @FunctionalInterface 를 사용하면컴파일 타임에 에러를 잡아줌

java.time 패키지

  • 불변 객체로 설계되어 객체를 수정할 수 없고 안전하게 사용 가능
  • 다양한 시간 연산 지원

6. Checked Exception과 Unchecked Exception

Checked Exception

  • 개발자가 명시적으로 예외처리 해야하는 예외
  • 컴파일 오류 발생
  • IOException, SQLException, FileNotFoundException 등

Unchecked Exception

  • 런타임시 발생하는 예외
  • 강제적으로 처리를 요구하지 않음
  • 발생 시점에 프로그램이 예외를 처리하거나 종료
  • NullPointException, ArrayIndexOutOfBoundsExcetpion 등

Question

Checked Exception과 Unchecked Exception의 차이

오류 발생 시점에 차이가 있습니다. Checked Exception은 컴파일시에, UncheckedException은 런타임시에 오류가 발생합니다.

Question

try-catch와 throws의 차이점

try-catch는 예외를 처리하기위해 사용되며, throws는 예외를 전파하기 위해 사용됩니다. try-catch는 예외가 발생할 가능성이 있는 코드에 직접 적용하여 예외를 즉시 처리하고, thwos는 메서드 선언에 사용하여 예외를 호출한 쪽에서 처리하도록 지정합니다.

7. 스프링 트랜잭션 추상화에서 rollback 대상

  • 기본적인 롤백 대상 : Unchecked Exception
  • @Transactional : 스프링에서 사용하는 롤백 제어 어노테이션