[JAVA]제어문

선택문(Decision-making Statement)

if-then

 가장 기본적인 제우문 중 하나로 지정한 조건이 만족할 시에 지정한 블록({}) 안에 있는 코드가 실행

if(statement) {		//한 줄일 경우 scope 생략 가능, statement : 조건식 -> true or false
	code();		// statement가 true일 때 실행될 코드
}

if-then-else

 if-then에서 false일 때, 실행될 코드를 추가한 형태이다. 즉, 조건식이 참일 경우와 거짓일 경우를 나누어 실행될 코드를 위치할 수 있다.

if(statement){
	// 조건식이 참일 경우
} else if(statement2){
	// statement가 거짓이고, statement2가 참일 때 실행된다.
} else {
	// 조건식이 거짓일 경우
}

switch

 변수에 대해 평가를 하고, 이에 대하여 분기를 할 수 있다. 평가 당하는 변수는 Primitive Type 또는 Enum 그리고 Stirng, Wrapper 등의 클래스도 가능하다.

switch(var) {
	case a:
    	// var이 a에 해당하는 경우
        break;
    case b:
    	// var이 b에 해당하는 경우
    case c:
    	// var이 c에 해당하는 경우
        break;
    default:
    	// var이 조건 분기 중 어떠한 값에도 해당하지 않는 경우
        break;

 위 코드에서 var의 값이 a와 일치할 경우 case a에 해당하는 코드를 실행하고 break 키워드에 걸려 제어문을 탈출한다.

 하지만 var의 값이 b와 일치할 경우, case b에 해당하는 코드를 실행하고 break문이 존재하지 않아 break문을 만나거나, switch 구문 끝날 때까지 존재하는 코드를 모두 실행한다.

 즉, case b의 코드와 case c의 코드를 실행하고 break문을 만나 탈출한다.

반복문(Looping Statement)

 어떠한 코드를 반복시키기 위해 사용하는 구문이다.

for

for(초기화; 조건식; 증감식){
	// 반복될 코드
}

// 초기화 : 반복문 안에서 사용할 값을 초기화한다.
// 조건식 : 반복문의 동작 조건을 나타낸다. 해당 조건식의 반환값이 false가 될 경우 반복문을 실행하지 않는다.
// 증감식 : 반복문 안에서 사용하는 변수의 값을 증가 또는 감소시킨다.

JDK 5.0 이상부터 배열 혹은 컬렉션의 순회 시에 다음과 같은 for문을 사용할 수 있다.

for(type var : Array/Collection){
	// 반복될 코드
}

// Array/Collection에 속한 type을 순회하며, var의 이름으로 순회하는 값을 하나씩 사용할 수 있다.

for문 vs for-each

  - LinkedList : LinkedList에 내부적으로 iterator가 순서대로 접근하기 때문에 get()의 접근 방식이 head에서 순회하여 O(n)의 시간 복잡도로 해당 인덱스를 찾는 LinkedList에서는 for-each가 더 짧은 수행 시간을 보여준다.

  - ArrayList : ArrayList의 get()은 연결된 메모리 주소를 통해 O(1)로 해당 인덱스를 찾기 때문에 ArrayList에서는 기본적인 for문이 더 짧은 수행시간을 보여준다.

  - Array : ArrayList와 유사하게 for문에서의 속도가 더 빠르다.

while

 특정 조건이 참일 경우에 scope 안의 코드를 반복한다. 조건식이 항상 참이 될 경우 무한 루프에 빠질 수 있으므로 주의한다

while(statement){
	// statement가 true일 경우 반복될 코드
}

do-while

do {
	// statement가 true일 때 반복될 코드
}while(statement);

 while문과 다르게 반복될 코드를 최초 1회 수행하고, 이후 statement의 조건에 맞춰 scope 안의 코드가 반복 수행된다.

 

(참고) 분기문(Branching Statement)

 조건문에서 개발자의 의도에 따라 중간 흐름을 바꿀 수 있는 구문

break

 라벨링이 되지 않은 break문은 가장 가까운 조건문을 종료한다.

for(int i = 0; i <= 10; i++){
	if(i == 5) break;
    System.out.println(i);
}

/**********
출력 결과
1
2
3
4
***********/

라벨링이 된 break문의 경우 해당 라벨링이 된 제어문 자체를 종료한다. 즉, 가장 가까운 제어문뿐 아니라 라벨링이 된 제어문을 종료한다.

int findIt = 10;
search:
for(int i =0; i < arr.length; i++){
	for(int j = 0; j < arr[i].length; j++){
    	if(arr[i][j] == findIt){
        	break search;
        }
    }
}

// arr[i][j] == findIt 일 때, search로 라벨링된 for(i)가 종료된다.


Continue

 반복문 안에서 사용되며, 조건에 해당할 경우에만 반복문을 건너뛴다.

 break문과 동일하게 라벨링이 된 경우 라벨링이 된 반복문의 끝으로 건너뛴다.

for(int i = 0; i <= 10; i++){
	if(i == 5) continue;
    System.out.println(i);
}

/**********
출력 결과
1
2
3
4
6
7
8
9
10
***********/
int findIt = 10;
search:
for(int i =0; i < arr.length; i++){
	for(int j = 0; j < arr[i].length; j++){
    	if(arr[i][j] == findIt){
        	break search;
        }
    }
}

// arr[i][j] == findIt 일 때, search로 라벨링된 for(i)의 끝으로 이동한다.

Enumeration

 - 초기 컬렉션에만 지원

 - Snap-shot : 원본과 달리 사본을 남겨 처리, 다른 처리가 발생하면 오류 가능성 생김

 - hasMoreElements(), nextEelement()

Vector<Integer> vector = new Vector<>(Arrays.asList(1,2,3));
Enumeration elements = vector.elements();
while (elements.hasMoreElements()) {
	int e = (int)elements.nextElement();
    System.out.println(e);
}

Iterator

 - 모든 Collection 지원

 - enumeration에 비해 빠르다

 - hasNext(), next(), remove()

Iterator<Integer> iterator = Arrays.asList(1, 2, 3).iterator();
while (iterator.hasNext()) {
	nteger number = iterator.next();
}

JUnit 5

  JUnit Platform : 테스트를 실행해주는 런처와 TestEngine API를 제공함

  Jupiter : TestEngine API 구현체로 JUnit5에서 제공함

  Vintage : TEstEngine API 구현체로 JUnit 3, 4에서 제공함

JUnit5 어노테이션 내용 JUnit4 어노테이션
@Test 테스트 Method임을 선언함 @Test
@ParameterizedTest 매개변수를 받는 테스트를 작성할 수 있음  
@RepeatedTest 반복되는 테스트를 작성할 수 있음  
@TestFactory @Test로 선언된 정적테스트가 아닌 동적으로테스트를 사용함  
@TestInstance 테스트 클래스의 생명주기를 설정함  
@TestTEmplate 공급자에 의해 여러 번 호출될 수 있또록 설계된 테스트 케이스 템플릿임을 나타냄  
@TestMethodOrder 테스트 메소드 실행 순서를 구성하는데 사용함  
@DisplayName 테스트 클래스 또는 메소드의 사용자 정의 이름을 선언할 때 사용함  
@DisplayNameGeneration 이름 생성기를 선엄함. 예를 들어 '_'를 공백 문자로 치환해주는 생성기가 있음
Ex) new_test -> new test
 
@BeforeEach 모든 테스트 실행 전에 실행할 테스트에 사용함 @Before
@AfterEach 모든 테스트 실행 후에 실행한 테스트에 사용함 @After
@BeforeAll 현재 클래스를 실행하기 전 제일 먼저 실행할 테스트 작성 static으로 선언 @BeforeClass
@AfterAll 현재 클래스 종료 후 해당 테스트를 실행
static으로 선언
@AfterClass
@Nested 클래스가 정적이 아닌 중첩 테스트 클래스임을나타냄  
@Tag 클래스 또는 메소드 레벨에서 태그를 선언할 때 사용
Maven을 사용할 경우 설정에서 테스트 태그를인식해 포함하거나 제외 가능
 
@Disabled 해당 클래스나 테스트를 사용하지 않음을 표시 @Ignore
@Timeout 테스트 실행 시간을 선언 후 초과되면 실패하도록 설정  
@ExtendWidth 확장을 선언적으로 등록할 때 사용  
@RegisterExtension 필드를 통해 프로그래밍 방식으로 확장을 등록할 때 사용  
@TempDir 필드 주입 또는 매개변수 주입을 통해 임시 디렉토리를 제공하는데 사용  

JUnit5의 사용자 정의 어노테이션(Annotation)

 어노테이션을 메타 어노테이션으로 사용 가능

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Tag;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {	// 메타 어노테이션 선언
}

@Fast				// 메타 어노테이션
@Test
void myFastTest() {
    // ...
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {		// 복수의 어노테이션을 포함하는 메타 어노테이션
}

@FastTest				// 메타 어노테이션 적용
void myFastTest{

}

테스트 클래스와 메소드

  테스트 클래스는 하나 이상의 테스트 메소드를 포함하는 최상위 클래스, static 멤버 클래스, 또는 중첩 클래스이어야 한다. 또한, 테스트 클래스는 추상 클래스로 정의되면 안 되며, 단일 생성자만 가져야 한다.

  - 테스트 메소드 : @Test, @RepeatedTest, @ParameterizedTest, @TestFActory, @TestTemplate 중 하나의 어노테이션으로 선언된 메소드

  - 생명 주기 메소드 : @BeforeAll, @AfterAll, @BeforeEach, @AfterEach 중 하나의 어노테이션으로 선언된 메소드

  - 테스트 메소드와 생명 주기 메소드는 모두 추상 메소드로 정의되면 안 되며, 값을 반환해서도 안 되며 non-static인 인스턴스 메소드여야 한다.

import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @Test
    void succeedingTest() {
    }

    @Test
    void failingTest() {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
        // not executed
    }

    @Test
    void abortedTest() {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

  위 코드에서의 실행 순서 : initAll() -> init() -> succedingTest() -> tearDown() -> init() -> failingTest() -> tearDown() -> init() -> abortedTest() -> tearDown() -> tearDownAll()

JUnit의 Assertions

 JUnit5 Jupiter Assertins 메소드는 모두 static 메소드라는 특징이 있으며 JUnit5 Assertions 를 누르면 확인할 수 있다.

 - assertEquals() : 인자를 2개 사용했을 경우, 인자가 서로 같은지 확인. 인자를 3개 사용했을 경우, 1, 2번째 인자를 비교하고 테스트가 실패하였을 경우 3번째 인자를 메시지로 출력.

@Test
void standardAssertions() {
    assertEquals(2, calculator.add(1, 1));
    assertEquals(4, calculator.multiply(2, 2),
            "The optional failure message is now the last parameter");
    assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- "
            + "to avoid constructing complex messages unnecessarily.");
}

 - assertAll() : 여러개의 assertions가 만족할 경우에만 테스트를 통과했다고 판단하고 싶을 때 사용. 인자로 람다식을 사용하여 여러개의 람다식이 동시에 실행됨

@Test
void groupedAssertions() {
    // In a grouped assertion all assertions are executed, and all
    // failures will be reported together.
    assertAll("person",
        () -> assertEquals("Jane", person.getFirstName()),
        () -> assertEquals("Doe", person.getLastName())
    );
}

@Test
void dependentAssertions() {
    // Within a code block, if an assertion fails the
    // subsequent code in the same block will be skipped.
    assertAll("properties",
        () -> {
            String firstName = person.getFirstName();
            assertNotNull(firstName);

            // Executed only if the previous assertion is valid.
            assertAll("first name",
                () -> assertTrue(firstName.startsWith("J")),
                () -> assertTrue(firstName.endsWith("e"))
            );
        },
        () -> {
            // Grouped assertion, so processed independently
            // of results of first name assertions.
            String lastName = person.getLastName();
            assertNotNull(lastName);

            // Executed only if the previous assertion is valid.
            assertAll("last name",
                () -> assertTrue(lastName.startsWith("D")),
                () -> assertTrue(lastName.endsWith("e"))
            );
        }
    );
}

 - assertThrows() : 특정 예외가 발생하였는지 확인. 첫번째 인자는 확인할 예외 클래스, 두번째 인자는 테스트하고자 하는 코드를 작성

@Test
void exceptionTesting() {
    Exception exception = assertThrows(ArithmeticException.class, () ->
        calculator.divide(1, 0));
    assertEquals("/ by zero", exception.getMessage());
}

 

 - assertTimeout(), assertTimeoutPreemptively() : assertions가 특정 시간 안에 실행이 완료되는지 확인하고 싶을 때 사용

   assertTimeout()은 특정 시간안에 완료되는지만 확인, assertTimeoutPreemptively()는 특정 시간안에 완료되지 않으면 종료

JUnit5의 Assumptions

 Assumptions는 특정 환경에 있을 때만 테스트를 진행

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class AssumptionsDemo {

    private final Calculator calculator = new Calculator();

    @Test
    void testOnlyOnCiServer() {
        assumeTrue("CI".equals(System.getenv("ENV")));
        // remainder of test
    }

    @Test
    void testOnlyOnDeveloperWorkstation() {
        assumeTrue("DEV".equals(System.getenv("ENV")),
            () -> "Aborting test: not on developer workstation");
        // remainder of test
    }

    @Test
    void testInAllEnvironments() {
        assumingThat("CI".equals(System.getenv("ENV")),
            () -> {
                // perform these assertions only on the CI server
                assertEquals(2, calculator.divide(4, 2));
            });

        // perform these assertions in all environments
        assertEquals(42, calculator.multiply(6, 7));
    }

}

 testOnlyOnCiServer()에서 assumeTure()를 통해 인자 비교. 이때, assumeTrue()의 두번째 인자로 System.getenv("ENV")를 사용하여 환경 변수를 가져옴. 해당 환경 변수가 CI와 같다면 테스트를 실행. 유의할 점으로는 현재 환경이 CI가 아니라면 테스트를 실패하는 것이 아니라 해당 테스트를 건너 뛴다.

 

ref {

  https://juntcom.tistory.com/118

  https://siyoon210.tistory.com/99

  https://steady-coding.tistory.com/349

 

For-each문은 For문 보다 얼마나 빠를까?

For-each vs For 자바에서 For-each 문이 전통적인 For문보다 더 빠르다는 정도만 알고 있엇는데, 특별하게 ArrayList에 경우는 오히려 For문이 더 빠르다는 얘기를 듣게 되었습니다. 실제로 얼마나 차이가

siyoon210.tistory.com

 

[Java] 자바 기본 제어문 - 선택문, 반복문

제어문 Java에서 코드는 위에서 아래 순으로 읽고 실행된다. 모든 일을 순차적으로 실행할 수 있다면 아무런 상관이 없겠지만, 어떤 코드를 반복해야 될 수도 있고 어떤 코드는 건너뛰는 등의 순

juntcom.tistory.com

}

댓글