본문 바로가기

멘토링

Java loop 사용 시 주의할 것들

루프 선언할 때 주의 할 것

- for(int i =0, a=2; i<20; i++, j++) {} 

-> 초기화와 값 변화에는 여러개의 변수를 쓸 수 있지만, 조건은 단 하나만 작성해야 한다.

-> 만약 조건에서 i<20, j<20이라고 적혀있으면 컴파일 할 때부터 오류발생

- 어떤 루프를 사용할지 고민해봐야한다.

-> for loop 는 루프의 회수가 미리 정해져있을 때 사용하면 좋음

 

for문 사용할 때 자주하는 안좋은 습관

public void example(List<Integer> numbers) {

 // 아래처럼 할 경우 size() 메소드를 조건을 확인할 때마다 호출한다.
 // 이용량이 많은 서비스일 경우 성능에 영향을 줄 수 있다. 
 // 따라서 int numbersSize = numbers.size()로 작성하는게 성능에 더 좋다.;
 // 가독성과 불필요한 size 호출을 잡으려면 아래 처럼 작성해도 좋음
 // for(int i=0, numbersSize=numbers.size(); i<numbersSize;i++)
  for (int i = 0; i < numbers.size(); i++) {
  }
}

for-each 문 사용 시 주의사항

 

for문에 비해 가독성도 좋지만, 데이터의 처음부터 끝까지 순서대로 처리해야할 경우에만 유용하다. 거꾸로 불러오고 싶거나 특정 index부터 데이터를 탐색할 때는 좋지 않다.

또한 내부적으로 메서드를 호출해야 해서 for문보다 느리다.

 

while문 사용 시 주의사항

 

잘못하면 무한 루프에 빠질 수 있어서 되도록이면 for문을 사용하자.

왜냐하면 무한 루프에 빠질 경우 손해가 매우 크기 때문이다. (서버 재시작, 쓰레드 강제 종료, 서버 부하)

while문을 사용할 때 유리한 경우도 있으니 잘 고민해 보고 사용해야 한다.

 

무한 루프는 언제 발생하는가?

 

조건이 항상 참일 경우 무한 루프가 발생한다. 아래의 for 루프와 while 루프는 무한루프다. 실제로는 overflow가 발생하여 무한히 진행되진 않는다.

즉 조건에 입력된 변수가 잘못 적혔을 경우나 조건 자체가 잘못되었을 때 발생한다.

for(int i=0; i>=0; i++) {
  System.out.println(3);
}
while(true) {
  System.out.println("hi");

}

 

객체 생성을 LOOP 내에서 할까, 밖에서 할까?

 

Stackoverflow에서 찾아보니 제한된 Scope를 쓰는게 좋다고 한다. 

그 이유는 Scope는 성능에 영향을 주지 않기 때문이다. 그리고 불필요한 초기화는 안하는게 좋기 때문이다.

JIT compiler가 코드를 잘 최적화해주기 때문에 우리가 신경 써야하는 것은 더 읽기 좋고 유지 보수하기 좋은 코드를 작성하는 것이다. 따라서 Scope를 줄이는 게 중요하다.

 

다양한 사이트를 돌아다녀봤는데 결국 유의미한 차이는 없으니 읽기 좋게 코드를 쓰라고 했다. 

 

객체를  루프 안에서 생성하니 참조값이 계속해서 변했다. 반면에 밖에서 생성을 한 경우엔 참조값에 변경이 없다.

안에서 생성할 경우 다른 객체가 생성되서 성능에 악영향을 미칠 것 같지만, 큰 영향이 없다는 글도 봤다. 

(논쟁도 많고 그래서 내가 정리한게 틀렸을 수도 있다.)

 

--> 우리가 20년 전 컴퓨터를 쓴다고 생각해보자. 그런 컴퓨터는 하드웨어의 성능도 매우 부족하기 때문에 loop 내에서 객체를 생성한다면, 절대적인 용량이 적기 때문에 Major GC가 자주 발생할 수 있다. 실제 서비스는 단순히 for문이나 while문에서 객체를 생성하는 것보다 훨씬 복잡하게 작동하기 때문에, 이런 사소한 실수가 GC 발생 확률을 높일 수 있으니 주의 해야 한다.

 

package basic;



public class Package{

  public static void main(String[] args) {
    Package p;
    for(int i=0; i<5; i++){
      p = new Package();
      System.out.println(p);
    }
    System.out.println();

    Package p1 = new Package();
    for(int i=0;i<5;i++) {
      System.out.println(p1);
    }
    System.out.println();

    for(int i=0;i<5;i++) {
      Package p3 = new Package();
      System.out.println(p3);
    }

  }
}

출력값

int a;
for(int i=0; i<1000;i++) {
  a = i;
  System.out.println(a);
}

for(int i=0;i<1000;i++) {
  int a = i;
  System.out.println(a);
}

Object가 아닌 primitive type variable에 대해서도 찾아봤다.

성능상에도 차이가 없고(만약 차이가 있다면 컴파일러가 잘못되었을 수 있다고함),  내부에 선언할 때 더 좋은 코드라고 했다. 

직접해보니 내부에서 변수를 초기화하고 값을 할당 했을 때, 시간이 좀 더 걸렸다. 심지어 천만번을 진행했을 때 그 차이가 나왔다.

    long beforeTime = System.currentTimeMillis(); //코드 실행 전에 시간 받아오기

//실험할 코드 추가

    for(int i=0; i<10000000;i++) {
     int a = i;
      System.out.println(a);
    }
    long afterTime = System.currentTimeMillis(); // 코드 실행 후에 시간 받아오기
    long secDiffTime = (afterTime - beforeTime)/1000; //두 시간에 차 계산
    System.out.println("시간차이(m) : "+secDiffTime); 
    // 시간차이: 38ms


   long beforeTime = System.currentTimeMillis(); //코드 실행 전에 시간 받아오기

//실험할 코드 추가
    int a;
    for(int i=0; i<10000000;i++) {
    a = i;
      System.out.println(a);
    }
    long afterTime = System.currentTimeMillis(); // 코드 실행 후에 시간 받아오기
    long secDiffTime = (afterTime - beforeTime)/1000; //두 시간에 차 계산
    System.out.println("시간차이(m) : "+secDiffTime);
    // 시간차이: 32ms
    }
    
    }

 

참고 자료

https://stackoverflow.com/questions/8803674/declaring-variables-inside-or-outside-of-a-loop

https://stackoverflow.com/questions/4501482/java-declaring-variables-in-for-loops

https://stackoverflow.com/questions/110083/which-loop-has-better-performance-why#110389

https://stackoverflow.com/questions/377763/declare-an-object-inside-or-outside-a-loop

https://hyeon9mak.github.io/Effective-Java-item6/

https://tecoble.techcourse.co.kr/post/2020-08-31-java-loop/

https://stackoverflow.com/questions/48397928/error-in-for-loop-java

https://www.cs.umd.edu/~clin/MoreJava/ControlFlow/infinite.html