최근에 Java를 사용하면서 헷갈렸던 것들을 가져와봤습니다.
1. @AllArgsConstructor 주의점
@AllArgsConstructor를 자주 사옹하지는 않지만 처음에 서버에서 오류가 나길래 한참 해맸더니 파라미터 순서를 지키지 않아서 발생한 오류였습니다.
2. final, static 개념
final은 고정하는 것, static은 어디서든 접근할 수 있음
fianl이 선언되면 해당 변수를 수정할 수 없고 클래스는 상속할 수 없습니다.
static이 선언되면 해당 변수, 메서드를 다른 클래스에 공유해서 생성자를 만들필요 없이 바로 사용할 수 있습니다.
3. 정적 팩터리 메서드 개념
객체를 생성할 때 생성자 방식보다 정적 팩터리 메서드를 주로 사용한다. 생성자 방식은 잘 사용하지 않는 이유는 여러 군데서 생성자를 생성해서 사용하면 나중에 컬럼이 추가된다고 가정하면 모든 생성자 생성하는 곳을 찾아서 수정해야하는데 관리가 힘들어서 정적 팩터리 메서드 방식을 사용한다.
비교 요약
특징 생성자 정적 팩터리 메서드
이름 | 클래스 이름과 동일 | 자유롭게 지정 가능 (create, getInstance 등) |
반환 타입 | 없음 | 지정 가능 |
객체 생성 방식 | 항상 새로운 객체 생성 | 재사용 가능, 조건에 따라 다른 객체 반환 가능 |
서브클래스 반환 | 불가능 | 가능 |
가독성 | 낮음 (클래스 이름과 동일) | 높음 (메서드 이름을 통해 목적 설명 가능) |
추가 로직 | 제한적 | 유연하게 추가 가능 (캐싱, 로깅 등) |
예제 비교
생성자 사용
Person person1 = new Person("Alice");
정적 팩터리 메서드 사용
Person person2 = Person.create("Alice");
쿠팝비즈 ex)
public class OrderDetailSearchDto {
@Getter
public static class Response {
OrderMainDto orderMain;
List<OrderResultCampaignDto> orderCampaignList;
@Builder
private Response(OrderMainDto orderMain, List<OrderResultCampaignDto> orderCampaignList) {
this.orderMain = orderMain;
this.orderCampaignList = orderCampaignList;
}
public static Response of(OrderMainDto orderMainDto, List<OrderResultCampaignDto> orderCampaignList) {
return Response.builder()
.orderMain(orderMainDto)
.orderCampaignList(orderCampaignList)
.build();
}
}
}
위 코드에서 of 가 정적 팩토리 메서드이다. 팩토리 메서드란 객체를 생성하는 메서드를 의미한다.
Builder 패턴
@Getter
@Builder(access = AccessLevel.PRIVATE)
public static class UseLimitSummary {
private final String userName;
private final String position;
private final String department;
private final Long useLimit;
public static UserUseLimitDto.UseLimitSummary of(String userName, String position, String department, Long useLimit, Long balance) {
return UseLimitSummary.builder()
.userName(userName)
.position(position)
.department(department)
.useLimit(useLimit)
.build();
}
}
Dto 객체를 제공할 때는 위 코드처럼 빌드를 해서 제공해주는게 좋습니다. 만약 서비스에서 빌드를 한다면 관리가 쉽지 않기 떄문에 객체를 한 곳에서 관리해주는게 좋습니다.
그리고 빌더를 private로 설정해 다른 곳에서 빌드할 수 없도록 해야합니다.
@Getter
@NoArgsConstructor
public class Master {
private Long masterUserNo;
private Long companyNo;
@Builder
public Master(Long masterUserNo, Long companyNo) {
this.masterUserNo = masterUserNo;
this.companyNo = companyNo;
}
}
반면 엔티티 객체를 제공할 때는 빌드를 public으로 하는게 좋습니다. 보통 엔티티 객체에는 필드가 많아서 파라미터를 잘못 입력할 가능성이 높기 때문입니다.
그리고 엔티티 객체 같은 경우에는 builder를 사용할 일이 많지 않기 떄문에 public으로 제공해도 관리 측면에서 크게 문제는 없습니다.
Null 처리 방법
Enum 값 비교
userRepository.getUserType(userNo).equals(UserType.MASTER)
UserType.MASTER.equals(userRepository.getUserType(userNo))
위 두 코드를 중에 아래 코드가 더 안전하다고 합니다. 왜냐하면 UserType.MASTER는 null이 될 수 없기 때문에 (Null Pointer Exception)가 발생하지 않습니다.
String 비교
String 비교라면 equals보다는 StringUtils.equals를 사용하는게 더 안전합니다.
그 이유는 StringUtils.equals는 비교 대상 중 하나가 null이어도 NullPointerException을 발생시키지 않고 안전하게 처리합니다.
Stream 장단점
Stream 동작 방식 -> [원소]를 하나하나씩 본다. 그 원소 중 만약 [조건]이면 [연산]을 한다.
1. 가독성 향상
<Collection 방식>
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = new ArrayList<>();
for(Integer number : numbers) {
if(number % 2 == 0) {
evenNumbers.add(number);
}
}
<Stream 방식>
- 체이닝 : 필터링, 매핑, 정렬을 연속적으로 표현
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.toList();
장점
- 유지 보수성 향상 -> 하지만 지나친 stream 사용은 가독성이 떨어집니다
Stream의 구조
1. 생성
- 최초 1번 수행됨
- 필요한 데이터만 메모리에 로드
// 리스트로부터 스트림 생성
Stream<String> stream = list.stream();
// 배열로부터 스트림 생성
Stream<String> stream = Arrays.stream(array);
2. 가공(중간 연산)
- 일련의 데이터를 원하는 형태로 가공하는 처리
- filter, map, sort 등의 가공을 의미
- 각 가공 처리의 입력값과 결과물 모두 Stream
- 연산을 계속 연결해서 여러 번 수행할 수 있음
// 필터링
Stream<String> filteredStream = list.stream()
.filter(s -> s.startsWith("a"));
// 매핑
Stream<String> mappedStream = list.stream()
.map(String::toUpperCase);
// 정렬
Stream<String> sortedStream = list.stream()
.sorted();
// 중복 제거
Stream<String> distinctStream = list.stream()
.distinct();
// 개수 제한
Stream<String> limitedStream = list.stream()
.limit(3);
3. 최종 연산
- 최종 목적물을 얻는 과정
- list, sum 등의 연산을 의미
// 리스트로 변환
List<String> resultList = list.stream()
.filter(s -> s.startsWith("a"))
.toList();
// 합계 계산
int sum = IntStream.range(1, 100)
.sum();
// 요소 개수 세기
long count = list.stream()
.filter(s -> s.startsWith("a"))
.count();
// reduce를 사용한 합계 계산(하나의 데이터로 만드는 작업)
int reducedSum = IntStream.range(1, 10)
.reduce(100, Integer::sum);
// collect를 이용한 그룹화
Map<Integer, List<String>> groupedByLength = list.stream()
.collect(Collectors.groupingBy(String::length));
'웹 개발 > Back End' 카테고리의 다른 글
스프링 배치(Spring Batch)란? (0) | 2025.04.12 |
---|---|
이벤트 리스너 사용법 (0) | 2024.09.17 |
마이바티스 사용법 (0) | 2024.09.15 |
@Mapper 사용법(Dto -> 엔티티 매핑) (0) | 2024.09.09 |
스프링 시큐리티 개념 (0) | 2024.05.19 |
댓글