일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 자바
- 이스티오
- IntellJ
- 자바 ORM 표준 JPA 프로그래밍
- Stream
- 백준
- OS
- 토비의 스프링
- 토비의 스프링 정리
- 보조스트림
- list
- 스트림
- thread
- spring
- 쿠버네티스
- K8s
- jvm
- Stack
- redis
- Kotlin
- mysql
- JPA
- SpringBoot
- MSA
- gradle
- GC
- Collection
- Real MySQL
- Java
- 스프링
- Today
- Total
인생을 코딩하다.
[Java] 자바 성능 튜닝 - 반복문? 알고 쓰자! 본문
자바 초심자 입장에서 은근히 간과할 수 있는 내용일수도 있다고 생각하여 글을 작성해보았습니다.
저 또한 자바 성능 튜닝 이야기를 처음 읽었을때 "앗 차!" 했던 내용이였습니다. 혹시나 자바를 처음 시작하신 분들에게 도움이 되었으면 하는 마음에 이 글을 바칩니다.
보통 가장 많이 쓰는 for문
public static void example(List<Integer> numbers) {
for (int i = 0; i < numbers.size(); i++) {
....
}
}
만약 numbers의 size가 10만 이라고 가정하면, 위의 코드는 불필요한 size()메서드를 10만번 반복 하는 것입니다.
public static void example(List<Integer> numbers) {
int numbersSize = numbers.size();
for (int i = 0; i < numbersSize; i++) {
....
}
}
코드를 이렇게 바꾼다면, 불필요한 size() 메서드의 10만번 반복을 줄일 수 있습니다. 또 가독성을 위해 불필요한 size()호출을 없애려면 밑의 코드처럼 하는 것도 고려해볼만 합니다.
public static void example(List<Integer> numbers) {
for (int i = 0, numberSize = numbers.size(); i < numberSize; i++) {
....
}
}
for - each문
public void example(List<Integer> numbers) {
for (int number : numbers) {
...
}
}
for-each문을 사용하면 형변환이나 get()메서드를 호출할 필요가 없습니다. for문에 비하면 가독성도 좋고 위에서 본 불필요한 반복과 ArrayIndexOutOfBoundsException를 신경 쓸 필요가 없습니다.
하지만, 이 반복문은 순서대로 처리해야 할 경우에만 유용하고, 반복의 순서를 거꾸로 돌리거나 특정 위치부터 반복하는 등의 반복문에는 유용하지 않습니다.
for-each는 내부적으로 아래와 같이 작동합니다.
public static void example(List<String> words) {
Iterator value = words.iterator();
while(value.hasNext()) {
String word = (String)value.next();
...
}
}
첫 번째 라인을 보면 Iterable 인터페이스를 구현한 객체가 iterable() 메서드로 Iterable 인터페이스를 구현한 객체를 반환합니다.
그 후 Iterable 인터페이스의 hasNext() 메서드와 next() 메서드로 반복을 실행하는 구조입니다.
for-each문은 Iterable 인터페이스의 iterable() 메서드가 필요하므로 Iterable 인터페이스를 구현한 객체만이 for-each문을 실행 할 수 있습니다. Collection 인터페이스도 Iterable 인터페이스를 상속하고 있기 때문에 위의 예제에서 List 인터페이스로 for-Each문을 실행할 수 있는 것입니다.
for-each문은 for문 보다 조금 느립니다. (차이가 크지는 않지만) -> for-each문은 내부적으로 메서드를 호출하는 비용이 있기 때문입니다.
for의 속도를 택할지 for-each문의 가독성, 편리함, 안전함을 택할지는 반복문 내부 로직과 예상 반복 횟수 및 상황에 따라 고민해보는게 좋을 것 같다고 생각합니다.
for-each문을 실행하려면 Iterable 인터페이스를 구현한 객체여야 한다는 것은 알겠습니다. 그럼 List가 아닌 문자열 배열 이라면 어떻게 될까요? String 객체는 Iterable 인터페이스를 구현하고 있지 않습니다. 이론대로라면 아래 코드는 실행 될 수 없을 것입니다.
public void example(String[] words) {
for (String word : words) {
...
}
}
예상과 다르게 위의 코드는 잘 실행됩니다. for-each문이 Array Type으로 실행된 경우에는 일반 for문으로 적절하게 번역해 주는 것을 확인 할 수 있습니다. 컴파일한 클래스 파일을 java 파일로 디컴파일한 내부 코드는 다음과 같습니다.
public void example(String[] words) {
String[] value1 = words;
int value2 = words.length;
for(int value3 = 0; value3 < value2; ++value3) {
String word = value1[value3];
System.out.println(word);
}
}
stream의 forEach 메서드
stream의 forEach 메서드는 아래와 같이 사용합니다.
public void example(List<String> words) {
words.stream()
.forEach(word -> System.out.println(word));
}
일반적인 for문에 비해 람다식으로 훨씬 간결하게 표현할 수 있습니다. 하지만 단순히 반복문을 위해 Stream forEach문을 쓰면 Stream 생성비용이 낭비됩니다.
Stream 생성 비용을 낭비하지 않으면서 반복문의 간결함을 유지하고 싶다면 iterable 인터페이스의 default 메서드인 forEach 메서드를 사용하면 됩니다. iterable 인터페이스는 Collection 인터페이스가 상속하고 있으므로 아래와 같이 Collection 객체들은 전부 사용할 수 있습니다.
public void example(List<String> words) {
words.forEach(word -> System.out.println(word));
}
다만 이 forEach 메서드를 사용할 떈 주의해야 하는 점이 있습니다. 그 내용은 Stream의 foreach와 for - loop는 다르다는 것입니다.
while문
while문은 잘못하면 무한 루프에 빠질 수 있습니다. 되도록이면 for문을 사용하는게 좋습니다.
아래 코드를 살펴볼까요?
public void example(List<String> words) {
boolean flag = true;
int index = 0;
while (flag) {
if(words.get(index++).equals("book")) {
flag = false;
}
}
}
List에서 "book"라는 문자열을 발견하면 while문을 빠져나오는 코드입니다. 만약 List에 book라는 단어가 없다면 어떻게 될까요? 코드를 작성한 사람 입장에서는 book이라는 단어가 없을 수가 없는 상황이라고 말할 수 있습니다. 하지만 협업하는 환경에서 누군가 book이 없는 List를 인자로 해당 메서드를 호출할지는 아무도 모르는 일입니다.
만에 하나 반복문이 계속해서 돌아가는 사태가 발생한다면 해당 애플리케이션은 서버를 재시작하거나 스레드를 강제 정료시킬 때까지 계속 반복문을 수행할 것입니다. 그렇게 되면 당연히 서버에 부하도 많이 발생하게 됩니다.
무조건 while문을 사용하는 것을 비추하는 것은 아닙니다. 상황에 따라 while문이 더 좋을 수도 있습니다. 다만 문제를 유발할 가능성이 있는 while문인지 아닌지는 항상 점검해 볼 필요가 있다고 생각합니다.
자바에서 반복문들을 쓸때 성능의 차이가 미미할지라도 어떤 상황에서는 큰 영향을 미칠지 모릅니다. 상황에 따라 어떤 반복문을 사용하면 좋을지 항상고민해보는 습관을 지니면 좋을 것같습니다. 감사합니다.
참고자료
'Java' 카테고리의 다른 글
[Java] 디자인패턴(DesignPattern) - 데코레이터 패턴(Decorator Pattern) (0) | 2020.11.12 |
---|---|
[Java] 디자인패턴(DesignPattern) -> 싱글톤 패턴(singleton Pattern)(1) (0) | 2020.11.10 |
[Java] 디자인패턴(DesignPattern) -> 템플릿 메서드 패턴(template method pattern) (0) | 2020.11.10 |
[Java] stream method 정리 (0) | 2020.10.18 |
[Java] stream API matching method (0) | 2020.10.18 |