[자바 스터디 4주차] 제어문

github.com/whiteship/live-study/issues/4

 

4주차 과제: 제어문 · Issue #4 · whiteship/live-study

목표 자바가 제공하는 제어문을 학습하세요. 학습할 것 선택문 반복문 과제 과제 0. JUnit 5 학습하세요. 인텔리J, 이클립스, VS Code에서 JUnit 5로 테스트 코드 작성하는 방법에 익숙해 질 것. 이미 JUn

github.com

목표

  • 자바가 제공하는 다양한 연산자를 학습하세요.

학습할 것

  • 선택문

  • 반복문

  • ※ ♥스터디원 참고 및 리뷰

과제

  • JUnit 5 학습하세요.

  • live-study 대시 보드를 만드는 코드를 작성하세요.

  • LinkedList를 구현하세요.

  • Stack을 구현하세요.

  • 앞서 만든 ListNode를 사용해서 Stack을 구현하세요.

  • Queue를 구현하세요.

 

제어문이란 코드의 실행 흐름(순서)을 개발자가 원하는 방향으로 제어하는 구문이다.

 

선택문

1.if문

if(condition1){
	System.out.println("condition1 true 입니다.");
}

if문에 주어진 조건이 참이면 해당 블럭안의 내용을 실행한다.

※ 괄호안의 문장이 한줄 일 경우 괄호를 생략할 수 있다.

 

 

2.if - else 문

if(condition1){
	System.out.println("condition1 true 입니다.");
}else{
	System.out.println("condition1 false 입니다.");
}

- if문에 주어진 조건이 참이면 해당 블럭안의 내용을 실행하고, 거짓일시 건너뛰고 else문을 실행한다.

 

 

3.if - else if - else 문

if(condition1){
	System.out.println("condition1 true 입니다.");
}else if(condition2){
	System.out.println("condition1 false, condition2 true 입니다.");
}else{
	System.out.println("condition1, condition2 false 입니다.");
}

else if문은 if와 else사이에 무한히 존재할 수 있다.

위에서 아래방향으로 순차적으로 조건을 확인하여 true인 블럭을 만나면 해당 조건의 블럭만 실행하고 if - else문이 종료된다.

 

 

4.switch - case문

 

if문은 위에서부터 순차적으로 내려와 조건을 확인하지만

switch문은 들어오는 조건을 확인하여 즉각 조건에 해당하는 문장으로 진입한다.

 

int score = 90;

switch(score) {
	case 100 :
    	System.out.println("100점입니다.");
        break;
    case 90 :
    	System.out.println("90점입니다.");
        break;
    default : 
    	System.out.println("0점입니다.");
}

switch문에 주어진 변수인 score의 값이 100점이면 "100점입니다."를 출력하고 break문에 의해 swtich문 블럭을 종료,

90점이면 "90점입니다."를 출력하고 break문에 의해 swtich문 블럭을 종료,

100점, 90점도 해당하지 않으면 "0점입니다."를 출력하고 switch문 블럭을 종료합니다.

 

 

String str = "A";

switch(str){
	case "A": 
		System.out.println("1");
	case "B":
		System.out.println("2");
	case "C" :
		System.out.println("3");
	default :
		System.out.println("그 외의 숫자");
}

JDK1.7부터 switch의 조건문에 문자열 타입의 변수도 올 수 있다.

 

※ switch문의 조건식에 들어갈 값은 정수 또는 문자(열)이여야 한다. 변수나 실수는 안된다.

int test, result;
final int THREE = 3;
 
switch(result) {
	case '1' :    // 문자 상수 O
	case THREE :    // 정수 상수 O
	case "JAVA" :  // 문자열 상수 O
	case num :    // 에러발생. 변수 X
	case 0.1 :    // 에러발생. 실수 X
}

 

switch문에서 break문은 각 case문의 영역을 구분하는 역할을 하는데, 만일 break문을 생략하면 case문 사이의 구분이 없어지므로 다른 break문을 만나거나 switch문 블럭{}의 끝을 만날 때까지 나오는 모든 문장들을 수행한다. 이점에 유의해야하지만 고의로 break문을 생략하여 작성하는 경우도 있다.

switch(level) {
	case 3 : 
		grantDelete(); //삭제권한을 준다.
	case 2 : 
		grantWrite(); //쓰기권한을 준다.
	case 1 : 
		grantRead(); //읽기권한을 준다.
}

로그인한 사용자의 등급을 체크하여, 등급에 맞는 권한을 부여하는 방식이다. 제일 높은 등급인 3을 가진 사용자는 grantDelete(), grantWrite(), grandRead()가 모두 수행되어 읽기, 쓰기, 삭제권한까지 모두 갖게 되고, 제일 낮은 등급인 1을 가진 사용자는 읽기 권한만을 가지게 된다.

 

char a = 'b';

switch(a){
	case 'a':
		System.out.println("a 입니다.");
	break;
	case 'b':
		System.out.println("b 입니다.");
	break;
	default:
		System.out.println("아무것도 아닙니다.");
}

char타입은 하나의 문자를 다루기 위한 것이지만, char타입의 값은 사실 문자가 아닌 정수(유니코드)로 저장되기 때문에 char타입의 값도 switch문의 조건식과 case문에 사용할 수 있다.

 

 

반복문

반복문이란 코드 내에서 똑같은 명령을 일정 횟수만큼 반복하여 수행하도록 제어하는 명령문이다.

 

1. for문

for문의 기본구조는 다음과 같다

for(초기식; 조건식; 증감식) {
  조건식의 결과가 참인 동안 반복적으로 실행하고자 하는 명령문;
}

순서는 다음과 같다

1.초기식 

2.조건식

3.for문 내부 블럭 실행

4.증감식

... 2 - 3 - 4 반복

 

1.초기식 : 초기식은 한번만 발생한다. 변수를 선언 및 초기화하거나 이미 초기화된 변수를 사용할 수 있다.

2.조건식 : true or false를 반환하는 조건을 작성한다. 조건이 거짓이 될때까지 계속 실행한다.

3.for문 내부 블럭 실행 : 조건이 참이면 블럭안의 내용을 실행한다.

4.증감식 : 변수의 값을 증가/감소 시킨다.

 

※ for( ;; )의 경우는 무한루프이다. 

 

블럭 내의 문장이 하나뿐일 때 괄호를 생략할 수 있다.

for (int i=0; i<210; ++i) System.out.println("괄호생략");

 

2. while문

while문은 특정 조건을 만족할 때까지 계속해서 주어진 명령문을 반복 실행한다.

 

int i = 0;

while (i < 5) {
    System.out.println("while 문이 " + (i + 1) + "번째 반복 실행중입니다.");
    i++; // 이 부분을 삭제하면 무한 루프에 빠지게 됨.
}

조건식이 참인지 판단 후, 참이면 내부의 명령을 실행한다.

실행 후 다시 조건식으로 돌아와 참인지를 판단하여 반복을 할지말지 결정한다.

while문의 조건이 거짓이 나오게끔 코딩을 하지 않으면 무한루프(infinite loop)에 빠지므로 프로그램은 영원히 종료되지 않게 되므로 유의하자.

 

int a = 0;

while(true){
	if(a > 3){
    	    break; //while문 탈출
    	}
    a++;
}

while문은 돌면서 a를 1씩 증가시킨다. 만약 a가 4가 되면 if문의 조건에 해당하여 break문으로 가장 가까운 반복문을 탈출한다.

 

 

3. do-while문

while문의 변형으로 기본적인 구조는 while문과 같으나 조건식과 블럭{}의 순서를 바꿔놓은 것이다. 그래서 while문과 반대로 블럭{}을 먼저 수행한 후에 조건식을 평가한다. while문은 조건식의 결과에 따라 블럭{}이 한번도 수행되지 않을 수 있지만, do-while문은 최소한 한번은 수행될 것을 보장한다.

 

do {
  //조건식의 연산결과가 참일 때 수행될 문장들을 적는다.
} while(조건식); //끝에 ';'을 잊지 않도록 주의

 

4. 향상된 for문(forEach문)

JDK1.5부터 추가된 문법으로 컬렉션 프레임워크와 배열에서 유용하게 자주 사용된다.

int array[] = {10, 20, 30, 40, 50};
for(int number : array) {
	System.out.println(number);
}

 

배열의 수만큼 실행부분을 반복하여 반복이 이루어질 때마다 배열의 항목을 순서대로 꺼내서 변수 number에 대입하여준다.

배열의 값을 순서대로 가져와야할때 유용한 문법이다.

주의해야할 것은 값을 읽기만 할 수 있고 수정할 수 없고 인덱스를 사용할 수 없다.

 

 

 

5.이름붙은 반복문

break문은 근접한 단 하나의 반복문만 벗어날 수 있기 때문에, 여러 개의 반복문이 중첩된 경우에는 break문으로 중첩 반복문을 완전히 벗어날 수 없다. 이때는 중첩 반복문 앞에 이름을 붙이고 break문과 continue문에 이름을 지정해 줌으로써 하나 이상의 반복문을 벗어나거나 반복을 건너뛸 수 있다.

 

        Loop1 : for(int i=2; i<=9; i++){
            for(int j=1; j<=9; j++){
                if(j==5)
                    break Loop1;
                System.out.println(i+"*"+ j + "=" + i*j);
            } //end of for i
            System.out.println();
        } //end of Loop1

제일 바깥의 for문에 Loop1라는 이름을 붙였다.

그리고 2차 for문에서 j가 5일 시 break문을 수행하는데 Loop1라는 for문을 벗어나게끔 코드를 작성했다.

반복문의 이름이 지정되지 않은 break문은 자신이 속한 하나의 반복문만 벗어날 수 있지만, 지금처럼 반복문에 이름을 붙여주고 break문에 반복문 이름을 지정해주면 하나 이상의 반복문도 벗어날 수 있다.

 

 

과제 0. JUnit5 학습하세요.

1.Junit이란?

- Java의 단위 테스팅(Unit Testingf) 도구

- public static void main(String[] args) 메소드에서 System.out으로 번거로운 디버깅 X

 

※ 단위 테스트란?

- 소스코드의 특정 프로덕션 코드가 의도한 대로 정확히 작동하는지 검증하는 절차

- 모든 메소드에 대해 테스트 케이스(Test Case)를 작성하는 절차

 

2.Junit 특징

- 각 메소드별로 @Test 어노테이션을 선언하여 독립적인 테스트 가능

- 단정문(Assertions)으로 테스트 케이스의 수행결과 판별 가능

- 결과는 성공(녹색), 실패(붉은색)중 하나로 표시

- 클래스 단위로 테스트 수행 시 테스트 메서드들의 실행순서는 무작위로 실행된다.

 

3.Junit 어노테이션

 

@BeforeAll : 현재 클래스의 모든 테스트 메서드보다 먼저 실행, static 메서드이어야 함

@BeforeAll
static void setup() {
    log.info("@BeforeAll");
}

 

@BeforeEach : 각각의 테스트 메소드 수행 전에 실행되는 메서드

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

- 인스턴스 변수의 초기화에 사용되기도 한다.

 

@AfterAll : 메서드보다 이후에 실행, static 메서드이어야 함

@AfterEach
void tearDown() {
    log.info("@AfterEach");
}

 

@AfterEach : 각각의 테스트 메소드 수행 후에 실행되는 메서드

    @AfterEach
    private void 초기화() {
        memberRepository.clearStore();
    }

- 배열이나 해쉬맵의 데이터 등을 초기화 할때 사용되기도 한다.

※ 테스트 코드에서는 메서드 이름을 한글로 해도 된다.(한국 한정)

 

@DisplayName : 테스트 메소드의 이름을 정의

 

@Disable : 테스트 메서드를 비활성화 할 수 있다.

 

4.단정문

junit에서 제공하는 단정문보다 가독성이 좋은 AssertJ 자바 라이브러리를 많이 사용한다.

assertEquals(expected, actual);         // Junit의 단정문
assertThat(actual).isEqualTo(expected); // AssertJ의 단정문

Junit은 왼쪽과 오른쪽의 매개변수가 헷갈릴 가능성이 있다.

하지만 AssertJ에서 제공하는 단정문이 직독직해 방식으로 가독성이 훨씬 좋다.

 

 

4-1 Junit 메서드

 

assertThrows(Class<> classType, Executable executable)

- 첫번째 인자로 발생할 예외 클래스의 class타입을 받은 뒤 executable을 실행하여 예외가 발생할 경우 classType과 발생된 Exception이 같은 타입인지 확인.

    IllegalStateException e = assertThrows(IllegalStateException.class,
        () -> memberService.join(member2));
    assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

 

4-2.AssertJ 메서드

 import static org.assertj.core.api.Assertions.*;
 ...
 assertThat(size()).isEqualTo(5);

아래 다양한 메서드를 확인하여 isEqualTo() 부분만 바꿔 사용하면 된다.

 

4-2-1. 기본 검증 메서드

  • isEqualTo(값) : 값과 같은지 검증한다.

  • isNotEqualTo(값) : 값과 같지 않은지 검증한다.

  • isNull() : null인지 검증한다

  • isNotNull() : null이 아닌지 검증한다.

  • isIn(값 목록) : 값 목록에 포함되어 있는지 검증한다.

  • isNotIn(값 목록) : 값 목록에 포함되어 있지 않은지 검증한다.

4-2-2. Exception 관련 검증 메서드

  • assertThatThrownBy() : 인자로 받은 람다에서 익셉션이 발생하는지 검증한다.

  • isInstanceOf() : 익셉션의 타입을 추가로 검증할 수 있다.

  • assertThatExceptionOfType() : 발생할 익셉션의 타입을 지정하고 isThrownBy() 메서드를 이용해서 익셉션이 발생할 코드 블록을 지정할 수 있다.

4-2-3. String에 대한 추가 검증 메서드

  • contains(CharSequance... values) : 인자로 지정한 무자열들을 모두 포함하고 있는지 검증한다.

  • containsOnlyOnce(CharSequance sequance) : 해당 문자열을 딱 한 번만 포함하는지 검증한다.

  • containsOnlyDigits() : 숫자만 포함하는지 검증한다.

  • containsWhitespaces() : 공백 문자를 포함하고 있는지 검증한다.

  • containsOnlyWhitespaces() : 공백 문자만 포함하는지 검증한다. 공백 문자 여부를 Chracter#isWhitespace() 메서드를 따른다.

4-2-4. String에 대한 추가 검증 메서드

  • isZero() / isNotZero() : 0인지 또는 0이 아닌지를 검증한다.

  • isOne() : 1 인지를 검증한다.

  • isPositive() / isNotPositive() : 양수인지 또는 양수가 아닌지를 검증한다.

  • isNegative() / isNotNegative() : 음수인지 또는 음수가 아닌지를 검증한다.

5.Gradle에서 Junit / AssertJ추가

dependencies {
    testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.6.2'
    testCompile group: 'org.assertj', name: 'assertj-core', version: '3.16.1'
    testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.6.2'
}

test {
    useJUnitPlatform()
}

 

과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.

...

과제 2. LinkedList를 구현하세요.

배열은 간단하고 인덱스를 이용하여 데이터를 접근하는 시간이 빠르다는 장점이 있지만 다음과 같은 단점도 존재한다.

1.크기를 변경할 수 없다.

  - 사용중 크기 제한시 새로운 배열을 생성해서 데이터를 복사해야한다.

  - 혹은 충분히 큰 크기의 배열을 생성해야 하므로 메모리 낭비가 초래된다.

 

2.비연속적인 데이터의 추가 또는 삭제에 오랜시간이 걸린다.

  - 중간 데이터의 추가 및 삭제시 빈자리를 채우기 위한 데이터 이동(복사)이 발생한다.

이러한 단점을 보완하기 위해 링크드 리스트(연결 리스트)라는 자료구조가 나왔다.

 

링크드 리스트란 아래의 그림과 같이 각각의 노드가 데이터와 다음 노드를 가리키는 주소값을 가지고 한 줄로 연결되어 있는 방식으로 데이터를 저장한다.

 

 

2-1. 링크드 리스트 데이터 삭제

삭제는 간단하다. 삭제하고자 하는 노드의 이전노드가 삭제하고자 하는 노드가 가리키고 있는 주소값을 가지도록 변경하기만 하면 된다. 배열처럼 복사하는 과정이 없기 때문에 처리속도가 매우 빠르다.

 

 

2-2. 링크드 리스트 데이터 추가

새로운 데이터를 추가할 때는 새로운 노드를 생성한 다음 추가하고자 하는 위치의 이전 노드의 주소값을 새로운 노드의 주소값으로 할당해주고, 새로운 노드가 그 다음 노드의 주소값을 가리키도록 변경하기면 하면 되므로 이것 역시 속도가 매우 빠르다.

 

전체 과제 코딩

github.com/jikimee64/live-study-assignment

 

jikimee64/live-study-assignment

백기선님 온라인 자바 스터디 과제 소스 코드. Contribute to jikimee64/live-study-assignment development by creating an account on GitHub.

github.com

 

※ ♥스터디원 참고 및 리뷰

 

1. JUnit

JUnit 목성

  - @TestFactory - 동적 테스트를 위한 테스트 팩토리 인 메소드

  - @DisplayName - 테스트 클래스 또는 테스트 메서드에 대한 사용자 지정 표시 이름을 정의

  - @Nested - 주석이 달린 클래스가 중첩 된 비 정적 테스트 클래스임을 나타냄

  - @Tag - 테스트 필터링을위한 태그 선언

  - @ExtendWith - 사용자 지정 확장을 등록하는 데 사용

  - @BeforeEach - 주석이 달린 메소드가 각 테스트 메소드 이전에 실행됨을 나타냄(이전 @Before) (여러번 Before)

  - @AfterEach - 각 테스트 메서드(이전에는 @After) 후에 주석이 추가 된 메소드가 실행됨을 나타냄

  - @BeforeAll - 주석이 추가 된 메서드가 현재 클래스의 현재 클래스의 모든 테스트 메서드보다 먼저 실행됨을 나타냄

  - @AfterAll - 현재 클래스의 모든 테스트 메서드 후에 주석이 추가 된 메서드가 실행됨을 나타냄

  - @Disable - 테스트 클래스 또는 메서드를 비활성화하는 데 사용(이전에는 @Ignore)

 

 

2. 애플리케이션에서 중요한 핵심 성능은 DB쿼리

 

 

3. Queue

Queue<Integer> queue = new LinkedList<>();
//하는일은 똑같음
boolean queue.offer(1); //사이즈에따라 true,false반환
boolean queue.add(1); //add()는 큐에 못들어갈경우 예외를 던짐

queue.poll(); //꺼낼것이 없으면 null반환
queue.remove(); //꺼낼것이 없으면 예외던짐

queue.peek(); //실제로 꺼내진 않고 값만 확인, null리턴
queue.element(); //실제로 꺼내진 않고 값만 확인, 예외던짐

//구현시 일관성있게 코드를 작성하는 것이 중요
Ex) 모든것을 null반환 메소드 사용 vs 모든 것을 예외 메소드 사용 

 

4. if문 / switch 지양할 것

상속과 다형성을 이용하여 if문효과를 내는 방법

https://refactoring.guru/replace-conditional-with-polymorphism

※ 다만, 3~4개정도는 괜찮다(늘어나지 않는다는 가정하에)

 

5. Test Double이란?

테스트를 진행하기 어려운 경우(테스트 환경을 구현하는 코드가 복잡할 경우 등) 이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체이다. 자바 진영에서는 대표적으로 Mockito가 있다.

 

6. Junit

  - 테스트 코드 작성시 각가의 메소드마다 인스턴스를 새로 만듬

  - 하지만 사진과 같이 @TestInstance(TestInstance.Lifecycle.PER_CLASS)를 주면 인스턴스를 클래스 당 한개를 만들겠다는 의미이다.(PER_METHOD는 기본값이라 생략이 가능)

  - 테스트 순서는 무작위지만 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)를 클래스에 선언하고

@Order(순서)를 메소드에 지정해주면 메서드 실행 순서를 정할 수 있다.

 

※ 백기선님은 항상 TDD를 하지 않는다. 가끔 TDD 방식으로 작성한다.

=> 장점은 구현할 코드가 명확해진다.

=> TDD가 아니더라도 테스트코드는 반드시 작성해야한다.

 

7. LinkedList vs ArrayList

  - ArrayList:

    º 내부적으로 데이터를 배열에서 관리

      => 추가, 삭제 시 임시 배열 생성해서 복사. 이부분이 상당하 성능 저하의 요인 O(N)

    º but, 배열로 관리 하므로, Index를 통해 검색하는 장점이 있다. O(1)

 

  - LinkedList:

    º Node간의 연결을 통해 관리

      => 데이터 검색 시 첫 노드(head)부터 순회. 탐색이 성능 저하의 요인 O(N)

    º 추가, 삭제 시 복사가 없고 노드만으로 가능 O(1)

 

 

8.재귀

public class QueueSample{
	public static void main(String[] args){
		QUeueSample queueSample = new QueueSample();
		queueSample.factorial(0); //1
		queueSample.factorial(1); //1
		queueSample.factorial(2); //2
		queueSample.factorial(3); //6
		queueSample.factorial(4); //24
	}
	
	private int factorial(int number){
		if(number < 2) {
			return 1;
		}
		return number * factorial(number - 1);
	}
}

재귀는 끝나는 조건에 대해서 명확히 해줘야한다.

재귀의 복잡도

시간 복잡도 : O(N)

공간 복잡도 : O(N)

 

※ 다른 언어(스칼라)를 쓰면 꼬리 재귀를 써서 O(N)을 O(1)로 최적화 가능

 

재귀는 지양하는 정도까지는 아니다. 공간복잡도가 O(N) 혹은 O(Log(N)까지는 괜찮다.

하지만, O(N^2)가 넘어가면 고민을 해야한다.

 

 

9. 과제

참고 : https://www.notion.so/4-c45c85fea0254d5d8fbc911d7f66c046

 

 

참조

www.yes24.com/Product/Goods/24259565

aahc.tistory.com/6

namocom.tistory.com/653

gmlwjd9405.github.io/2019/11/26/junit5-guide-basic.html

incheol-jung.gitbook.io/docs/study/undefined-3/d.-assertj

www.yes24.com/Product/Goods/24259565