자바 문법 구조의 이해
자바는 강력한 객체지향 프로그래밍 언어로, 그 문법 구조를 깊이 이해하는 것은 효율적인 코드 작성의 기본입니다.
패키지 구조의 중요성
1
2
3
4
5
6
|
package com.example.application.service;
import java.util.List;
import java.util.ArrayList;
import static java.util.stream.Collectors.toList;
|
cs |
패키지 구조는 단순한 코드 정리 이상의 의미를 갖습니다. 잘 설계된 패키지 구조는
-
가독성 향상: 관련 클래스들을 논리적으로 그룹화
-
접근 제어 강화: 패키지 접근 제한자를 통한 캡슐화
-
이름 충돌 방지: 동일한 클래스 이름의 충돌 방지
-
배포 용이성: 모듈화된 배포 가능
권장 패키지 구조
-
com.company.project.domain
: 비즈니스 도메인 객체 -
com.company.project.service
: 비즈니스 로직 -
com.company.project.repository
: 데이터 액세스 -
com.company.project.controller
: 사용자 인터페이스 로직 -
com.company.project.util
: 유틸리티 클래스
클래스 구조 최적화
클래스 구조를 최적화하기 위한 팁
-
일관된 순서 유지: 상수, 정적 변수, 인스턴스 변수, 생성자, 메서드, 내부 클래스 순으로 배치
-
단일 책임 원칙: 클래스는 하나의 책임만 가져야 함
-
불변성 활용: 가능한
final
키워드 사용 -
접근 제한자 최소화: 필요한 최소한의 접근 권한만 부여
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public class UserService {
// 상수 정의
private static final int MAX_LOGIN_ATTEMPTS = 5;
// 정적 변수
private static int userCount = 0;
// 인스턴스 변수
private final UserRepository repository;
private String serviceId;
// 생성자
public UserService(UserRepository repository) {
this.repository = repository;
this.serviceId = generateServiceId();
userCount++;
}
// 메서드
public User findById(Long id) {
return repository.findById(id)
.orElseThrow(() –> new UserNotFoundException(id));
}
// 정적 메서드
public static int getUserCount() {
return userCount;
}
// 내부 클래스
private class ServiceMetrics {
// 내부 구현
}
}
|
cs |
자바 코드 최적화 기법
1. 문자열 처리 최적화
문자열은 자바에서 가장 많이 사용되는 데이터 타입 중 하나이지만, 비효율적으로 사용하면 성능 저하의 원인이 됩니다.
성능 비교: 위 두 코드의 실행 시간은 수백 배 차이가 날 수 있습니다. 첫 번째 방법은 매 반복마다 새로운 String 객체를 생성하지만, 두 번째 방법은 단일 StringBuilder 객체를 재사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 비효율적인 방법
String result = “”;
for (int i = 0; i < 10000; i++) {
result += i; // 매 반복마다 새 String 객체 생성
}
// 최적화된 방법
StringBuilder result = new StringBuilder();
for (int i = 0; i < 10000; i++) {
result.append(i); // 기존 객체 재사용
}
String finalResult = result.toString();
|
cs |
2. 루프 최적화
루프 최적화 팁
-
루프 불변 코드 외부로 이동
-
향상된 for 루프 사용 (가독성)
-
적절한 경우 스트림 API 활용 (병렬 처리 가능)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// 기본 for 루프
for (int i = 0; i < list.size(); i++) { // 매 반복마다 size() 호출
process(list.get(i));
}
// 최적화된 for 루프
int size = list.size(); // size() 한 번만 호출
for (int i = 0; i < size; i++) {
process(list.get(i));
}
// 향상된 for 루프 (가장 가독성 좋음)
for (Element e : list) {
process(e);
}
// 스트림 API (함수형 접근)
list.stream()
.filter(e –> e.isValid())
.forEach(e –> process(e));
|
cs |
3. 조건문 최적화
조건문 최적화 전략
-
빈도 기반 정렬: 가장 자주 발생하는 조건을 먼저 검사
-
조기 반환: 조건 충족 시 즉시 반환하여 불필요한 검사 방지
-
복잡한 조건은 분리: 단순한 조건부터 평가하여 단락 평가 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 비효율적인 조건문
if (condition1) {
// 자주 실행되는 코드
} else if (condition2) {
// 가끔 실행되는 코드
} else if (condition3) {
// 드물게 실행되는 코드
}
// 최적화된 조건문
if (condition1) {
// 자주 실행되는 코드
return;
}
if (condition2) {
// 가끔 실행되는 코드
return;
}
// 드물게 실행되는 코드
|
cs |
4. 객체 생성 최적화
객체 생성 최적화 방법
-
객체 풀링: 자주 사용되는 객체는 풀에서 재사용
-
지연 초기화: 필요할 때만 객체 생성
-
불변 객체 활용: 스레드 안전성 확보 및 재사용 가능
1
2
3
4
5
6
7
8
9
10
11
12
|
// 비효율적인 방법
for (int i = 0; i < 1000; i++) {
Date now = new Date(); // 매 반복마다 객체 생성
process(now);
}
// 최적화된 방법
Date now = new Date();
for (int i = 0; i < 1000; i++) {
process(now); // 객체 재사용
}
|
cs |
실전 예제: 성능 최적화된 데이터 처리
다음은 대용량 사용자 데이터를 처리하는 최적화된 코드 예제입니다.
이 코드는 다음과 같은 최적화 기법을 적용했습니다.
-
스트림 API를 사용한 선언적 프로그래밍
-
병렬 처리로 멀티코어 활용
-
필터링을 통한 불필요한 처리 방지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public List<UserDTO> processUsers(List<User> users) {
return users.stream()
.filter(user –> user.isActive()) // 활성 사용자만 필터링
.parallel() // 병렬 처리로 성능 향상
.map(user –> {
// 객체 변환 로직
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setName(user.getName());
dto.setEmail(user.getEmail());
return dto;
})
.collect(Collectors.toList());
}
|
cs |