Notice
Recent Posts
Recent Comments
Link
«   2025/12   »
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
Tags
more
Archives
Today
Total
관리 메뉴

기록하자..

제어문 | 백기선님 LIVE-STUDY 본문

자바

제어문 | 백기선님 LIVE-STUDY

P23Yong 2022. 5. 2. 09:43

제어문

학습할 것

  • 선택문
  • 반복문

제어문

자바 프로그램을 시작하고 프로그램의 실행 흐름이 존재할 때, 실행 흐름을 원하는 방향으로 바꿀 수 있도록 해주는 것이 제어문이다.

선택문

if 문

if문은 의사결정문이라고 할 수 있다. 다음은 if문의 형식과 실행 흐름이다.

if (조건식) {   // 조건식이 false일 때는 이 블록을 실행하지 않는다.

    // 조건식이 true
} 

if문은 else문과 함께 사용할 수 있다. 조건식의 결과에 따라 실행 블록을 결정하는데, 조건식이 true이면 if문의 블록을 실행하고, false라면 else문의 블록을 실행한다.

if (조건식) {
    // 조건식이 true
} else {
    // 조건식이 false
}

조건식을 여러 개 넣고 싶다면 else if문을 사용할 수 있다. 처음 if문의 결과가 flase일 경우 다른 조건식의 결과에 따라 실행 블록을 선택할 수 있게 해준다.

if (조건식1) {
    // 조건식1이 true
} else if (조건식2) {
    // 조건식1이 false
    // 조건식2가 true
} else {
    // 조건식1, 2가 모두 false
}

switch문

switch문도 if문과 마찬가지로 조건 제어문이다. 하지만 switch문은 if문처럼 조건식이 true일 경우가 아니라 변수가 어떤 값을 갖느냐에 따라 실행문이 선택된다.

switch문은 괄호 안의 값과 동일한 값을 가지는 case로 가 실행문을 실행시키게 된다. 동일한 값을 갖는 case가 없다면 default로 가 실행하게 된다.

int num = (int) Math.random() * 6 - 1;
    switch (num) {
        case 1:
            System.out.println("1번이 나왔습니다.");
            break;
        case 2:
            System.out.println("2번이 나왔습니다.");
            break;
        case 3:
            System.out.println("3번이 나왔습니다.");
            break;
        case 4:
            System.out.println("4번이 나왔습니다.");
            break;
        case 5:
            System.out.println("5번이 나왔습니다.");
            break;
        default:
            System.out.println("6번이 나왔습니다.");
            break;
    }
  • 자바 6까지는 switch의 괄호에는 정수타입(byte, char, short, int, long)변수나 정수값을 산출하는 연산식만 올 수 있었지만, 자바7부터는 String 타입의 변수도 가능하게 되었다.
  • case 끝에는 모두 break가 들어있는 것을 확인할 수 있다. 이는 break가 없다면 연달아 다음 case문을 실행하게 되기 때문이다.

반복문

for문

for문은 주어진 횟수만큼 실행문을 반복할 때 유용하다. 다음은 for문의 형식이다.

for (1. 초기화식; 2. 조건식; 3. 증감식) {
    // 조건식이 true일 경우 실행
    // 실행문
}
// 조건식이 false일경우 for문 종료
int sum = 0;

for (int i = 1; i <= 100; i++) {
    sum += i;
}

System.out.println("1~100까지의 합 = " + sum);
1~100까지의 합 = 5050

향상된 for문

index의 값이 아닌 요소를 통한 값의 순회를 진행하는 for문이다. 컬렉션의 값을 처리하기 좋다.

int arr[] = {1, 2, 3, 4, 5, 7, 6, 9, 8, 10};

for (int num : arr) {
    System.out.println("num = " + num);
}
num = 1
num = 2
num = 3
num = 4
num = 5
num = 7
num = 6
num = 9
num = 8
num = 10
String[] days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
List<String> dayList = new ArrayList<>();
dayList.addAll(Arrays.stream(days).collect(Collectors.toList()));

for (String day : dayList) {
    System.out.println("day = " + day);
}
day = Monday
day = Tuesday
day = Wednesday
day = Thursday
day = Friday

for-each문

Java8이후로 stream을 사용해 Collection이 구현하고 있는 for-each를 사용할 수 있다.

dayList.forEach(System.out::println);
Monday
Tuesday
Wednesday
Thursday
Friday

while문

for문이 정해진 수만큼 반복하는 것이라면 while문은 조건식이 true일 동아능ㄴ 계속해서 반복한다.
다음은 while문의 형식이다.

while (조건식) {
    // 조건식이 true일 경우 실행
    // 실행문
}

안의 실행문이 모두 실행되면 다시 조건식으로 돌아가 조건식을 다시 평가하게 된다. 만약 조건식이 true일 경우 실행문을 다시 수행하게 되고, false라면 while문을 종료한다.

int num = 0;
while (num < 10) {
    System.out.println("num = " + num++);
}
num = 0
num = 1
num = 2
num = 3
num = 4
num = 5
num = 6
num = 7
num = 8
num = 9

do-while문

do-while문은 조건식에 의해 반복 실행한다는 것에서 while문과 동일하다. while문은 처음부터 조건식을 검사해 블록내부를 실행할지 결정하지만, do-while문은 일단 블록내부의 실행문을 실행하고 실행 결과에 따라 반복 실행을 계속할지 결정한다. 다음은 do-while문의 형식이다.

do {
    // 실행문
} while (조건식);
int num = 0;
do {
    System.out.println("num = " + num++);
} while (num > 10);
num = 0

과제

과제 0. JUnit 5 학습하세요.

  • 인텔리J, 이클립스, VS Code에서 JUnit 5로 테스트 코드 작성하는 방법에 익숙해 질 것.
  • 이미 JUnit 알고 계신분들은 다른 것 아무거나!

JUnit5란 무엇인가?

JUnit5는 무엇일까? JUnit5는 여러 서브 프로젝트의 모듈들로 구성되어 있다.

JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

각 서브 프로젝트에 대해 알아보자.

  • Platform
    • 테스트를 실행해주는 런쳐를 제공한다.
    • TestEngine API를 제공한다.
  • Jupiter
    • TestEngine API구현체로 JUnit5를 제공한다.
    • Platform에서 사용하는 TestEngine은 Jupiter를 통해 제공하는 것이다.
  • Vintage
    • JUnit4와 3를 지원하는 TestEngine구현체이다.

Platform이 JVM에서 테스트 프레임워크를 실행시키는 근간이 되며, 이를 실제적으로 구현한 것이 Jupiter와 Vintage이다.

한 가지 알아야 할 것은 런타임시 자바8이상을 필요로 한다는 것이다. 하지만 이전 버전의 JDK로 컴파일된 테스트 코드는 실행가능하다.

스프링에서 JUnit5 사용하기

스프링에서 JUnit5을 사용하기 위해서는 기본으로 JUnit5 의존성이 추가되어 있는 2.2+버전의 스프링 부트 프로젝트를 만들면 된다.

스프링 프로젝트를 만들지 않는다면 gradle환경에서는 다음과 같이 작성하면 된다.

tasks.named('test') {
    useJUnitPlatform()
}
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}

기본 애노테이션

Annotation 설명
@Test 해당 메서드가 테스트 메서드임을 알린다.
@BeforeAll 모든 테스트가 실행되기 전에 한 번 실행된다.
static 키워드를 붙여 메서드를 정의하면 좋다.
@AfterALl 모든 테스트가 종료된 후 한 번 실행된다.
static 키워드를 붙여 메서드를 정의하면 좋다.
@BeforeEach 각 테스트가 실행되기 전 실행된다.
@AfterEach 각 테스트가 실행된 후 실행된다.
@Disabled 테스트 클래스 혹은 메서드를 비활성화하는데 사용한다.
@DisplayNameGeneration Method와 Class레퍼런스를 이용해 테스트 이름을 표기한다.
기본 구현체로 ReplaceUnderscores를 제공한다.
@DisplayName 어떤 테스트인지 테스트 이름을 보다 쉽게 표현할 수 있게 해주는 애노테이션이다.
@Timeout 주어진 시간을 초과할 경우, 테스트 실패를 나타내기 위해 사용한다.

이것말고도 다양한 애노테이션이 존재한다. ->
JUnit5 Annotations

Assertion

Jupiter는 JUnit4에 있는 많은 Assertion 메서드와 함께 제공되고 Java8의 람다식과 함께 사용하기에 적합한 몇 가지를 추가한다. 모든 JUnit Jupiter의 Assertion은 정적 메서드로 정의되어 있다.

@Test
void assertion() {
    Study startedStudy = new Study();
    Study endedStudy = new Study();
    Study exceptionStudy = new Study(-1);

    startedStudy.setStatus(StudyStatus.STARTED);
    endedStudy.setStatus(StudyStatus.ENDED);

    // 기본 assertion
    assertEquals(StudyStatus.STARTED, startedStudy.getStatus());

    // 그룹 assertion -> 여러 테스트 실패를 한 번에 확인할 수 있다.
    assertAll(
            () -> assertNotNull(startedStudy),
            () -> assertEquals(StudyStatus.ENDED, endedStudy.getStatus())
    );

    // 예외
    assertThrows(IllegalArgumentException.class, () -> exceptionStudy.getLimit());
}

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

  • 깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크 할 것.
  • 참여율을 계산하세요. 총 18회에 중에 몇 %를 참여했는지 소숫점 두자리가지 보여줄 것.
  • Github 자바 라이브러리를 사용하면 편리합니다.
  • 깃헙 API를 익명으로 호출하는데 제한이 있기 때문에 본인의 깃헙 프로젝트에 이슈를 만들고 테스트를 하시면 더 자주 테스트할 수 있습니다.
public class GithubConnection {

    private GitHub gitHub;
    private GHRepository repo;

    private final String REPO_NAME = "whiteship/live-study";

    private Map<String, Set<Integer>> userDashboard;
    private Map<String, Double> userAttendanceRate;

    public GithubConnection(String token) throws IOException {
        GitHubBuilder ghBuilder = new GitHubBuilder();

        gitHub = ghBuilder.withOAuthToken(token).build();
        repo = gitHub.getRepository(REPO_NAME);
        userDashboard = new HashMap<>();
        userAttendanceRate = new HashMap<>();
    }

    private List<GHIssue> getGhIssues() throws IOException {
        return repo.getIssues(GHIssueState.ALL);
    }

    private List<GHIssueComment> getGhIssueComments(GHIssue issue) throws IOException {
        return issue.getComments();
    }

    private GHUser getAuthor(GHIssueComment comment) throws IOException {
        return comment.getUser();
    }

    private void setUserDashBoard(List<GHIssue> issues) throws IOException {
        for (GHIssue issue : issues) {
            for (GHIssueComment comment : getGhIssueComments(issue)) {
                String loginId = getAuthor(comment).getLogin();
                int issueNumber = issue.getNumber();

                if (userDashboard.containsKey(loginId)) {
                    userDashboard.get(loginId).add(issueNumber);
                } else {
                    userDashboard.put(loginId, new HashSet<>(Arrays.asList(issueNumber)));
                }
            }
        }
    }

    private void setAttendanceRate() throws IOException {
        List<GHIssue> issues = getGhIssues();
        setUserDashBoard(issues);

        double issueCount = issues.size();

        for (String author : userDashboard.keySet()) {
            double attendanceRate = userDashboard.get(author).size() / issueCount;
            userAttendanceRate.put(author, attendanceRate);
        }
    }

    public void showDashboard() throws IOException {
        setAttendanceRate();

        for (String author : userAttendanceRate.keySet()) {
            System.out.print("참여자 : " + author);
            System.out.printf(", 참여율 : %.2f", userAttendanceRate.get(author) * 100);
            System.out.println(" %");
        }
    }
}

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

  • LinkedList에 대해 공부하세요.
  • 정수를 저장하는 ListNode 클래스를 구현하세요.
  • ListNode add(ListNode head, ListNode nodeToAdd, int position)를 구현하세요.
  • ListNode remove(ListNode head, int positionToRemove)를 구현하세요.
  • boolean contains(ListNode head, ListNode nodeTocheck)를 구현하세요.

ListNode 클래스

public class ListNode {

    private Integer item;
    private ListNode next;

    public ListNode() {
        item = null;
        next = null;
    }

    public ListNode(int item) {
        this.item = item;
        this.next = null;
    }

    public ListNode add(ListNode head, ListNode nodeToAdd, int position) {
        if (position < 0) {
            throw new IndexOutOfBoundsException("유효한 위치가 아닙니다.");
        }

        ListNode before = head;
        ListNode cur = head.getNext();

        while (position-- > 0) {
            if (cur == null) {
                throw new IndexOutOfBoundsException("유효한 위치가 아닙니다.");
            }

            before = cur;
            cur = cur.getNext();
        }

        before.setNext(nodeToAdd);
        nodeToAdd.setNext(cur);
        return nodeToAdd;
    }

    public ListNode remove(ListNode head, int positionToRemove) {
        if (positionToRemove < 0) {
            throw new IndexOutOfBoundsException("유효한 범위가 아닙니다.");
        }

        ListNode before = head;
        ListNode cur = head.getNext();
        while (positionToRemove-- > 0) {
            before = cur;
            cur = cur.getNext();

            if (cur == null) {
                throw new IndexOutOfBoundsException("유효한 범위가 아닙니다.");
            }
        }

        before.setNext(cur.getNext());
        return cur;
    }

    public boolean contains(ListNode head, ListNode nodeToCheck) {
        while (head != null) {
            if (head == nodeToCheck) {
                return true;
            }
            head = head.getNext();
        }

        return false;
    }

    public ListNode getNode(ListNode head, int position) {
        if (position < 0) {
            throw new IndexOutOfBoundsException("유효한 위치가 아닙니다.");
        }

        ListNode cur = head.getNext();
        while (position-- > 0) {
            if (cur == null) {
                throw new IndexOutOfBoundsException("유효한 위치가 아닙니다.");
            }
            cur = cur.getNext();
        }

        return cur;
    }

    public Integer getItem() {
        return item;
    }

    public ListNode getNext() {
        return next;
    }

    public void setNext(ListNode next) {
        this.next = next;
    }
}

ListNode 테스트 코드

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ListNodeTest {

    private ListNode head;
    private ListNode first;

    @BeforeAll
    void init() {
        head = new ListNode();
        first = new ListNode(1);
        head.setNext(first);
        head.add(head, new ListNode(2), 1);
        head.add(head, new ListNode(3), 2);
        head.add(head, new ListNode(4), 3);
        head.add(head, new ListNode(5), 4);
    }

    @DisplayName("리스트에 있는 모든 노드를 출력합니다.")
    @Test
    void print_list() {
        while (head != null) {
            head = head.getNext();
            System.out.println(head);
        }
    }

    @DisplayName("연결리스트에 노드를 더합니다.")
    @Test
    void add_node() {
        ListNode firstNode = new ListNode(0);
        head.add(head, firstNode, 0);

        assertThat(head.getNode(head, 0)).isEqualTo(firstNode);
        assertThat(head.getNode(head, 1)).isEqualTo(first);
    }

    @DisplayName("연결리스트에 노드를 더하는 것이 실패합니다.")
    @Test
    void fail_add_node() {
        ListNode newNode = new ListNode(-1);

        assertThrows(IndexOutOfBoundsException.class, () -> head.add(head, newNode, -1));
        assertThrows(IndexOutOfBoundsException.class, () -> head.add(head, newNode, 100));
    }

    @DisplayName("연결리스트에 지정한 위치의 노드를 삭제합니다.")
    @Test
    void remove_node() {
        assertThat(head.remove(head, 2).getItem()).isEqualTo(3);
        assertThat(head.remove(head, 0)).isEqualTo(first);
    }

    @DisplayName("연결리스트에 지정한 위치의 노드를 삭제하는 것이 실패합니다.")
    @Test
    void fail_remove_node() {
        assertThrows(IndexOutOfBoundsException.class, () -> head.remove(head, -1));
        assertThrows(IndexOutOfBoundsException.class, () -> head.remove(head, 5));
    }

    @DisplayName("연결리스트에 특정한 노드가 있는지 확인합니다.")
    @Test
    void contains_node() {
        ListNode newNode = new ListNode(100);
        ListNode newNode2 = new ListNode(1000);

        head.add(head, newNode, 4);

        assertTrue(head.contains(head, newNode));
        assertFalse(head.contains(head, newNode2));
    }
}

과제 3. Stack을 구현하세요.

  • int 배열을 사용해서 정수를 저장하는 Stack을 구현하세요.
  • void push(int data)를 구현하세요.
  • int pop()을 구현하세요.

Stack 클래스

public class Stack {

    private int[] stack;
    private int top;

    public Stack(int size) {
        stack = new int[size];
        top = -1;
    }

    public void push(int data) {
        if (top+1 >= stack.length) {
            int[] tmp = new int[stack.length+1];

            IntStream.range(0, stack.length).forEach(i -> tmp[i] = stack[i]);
            stack = tmp;
        }
        stack[++top] = data;
    }

    public int pop() {
        if (top < 0) {
            throw new IndexOutOfBoundsException("제거할 항목이 없습니다.");
        }
        return stack[top--];
    }

    public int peek() {
        if (top < 0) {
            throw new IndexOutOfBoundsException("스택이 비어있습니다.");
        }

        return stack[top];
    }
    @Override
    public String toString() {
        return Arrays.toString(stack);
    }
}

테스트 코드

class StackTest {

    @Test
    void push() {
        Stack stack = new Stack(3);

        stack.push(1);
        stack.push(2);

        assertEquals(stack.peek(), 2);

        stack.push(3);
        stack.push(4);
        stack.push(5);

        assertEquals(stack.peek(), 5);
    }

    @Test
    void pop() {
        Stack stack = new Stack(3);

        stack.push(1);
        stack.push(2);
        stack.push(3);

        int data = stack.pop();

        assertAll(
                () -> assertThat(data).isEqualTo(3),
                () -> assertThat(stack.peek()).isEqualTo(2)
        );

        stack.pop();
        stack.pop();

        assertThrows(IndexOutOfBoundsException.class, () -> stack.pop());
    }
}

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

  • ListNode head를 가지고 있는 ListNodeStack 클래스를 구현하세요.
  • void push(int data)를 구현하세요.
  • int pop()을 구현하세요.
public class ListNodeStack {

    private ListNode head;

    public ListNodeStack(ListNode head) {
        this.head = head;
    }

    public void push(int data) {
        ListNode newNode = new ListNode(data);

        newNode.setNext(head.getNext());
        head.setNext(newNode);
    }

    public int pop() {
        if (head.getNext() == null) {
            throw new IllegalStateException("스택이 비어있습니다.");
        }

        int data = head.getNext().getItem();
        head.setNext(head.getNext().getNext());

        return data;
    }

    public int peek() {
        return head.getNext().getItem();
    }
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ListNodeStackTest {

    ListNode head;
    ListNodeStack stack;

    @BeforeAll
    void init() {
        head = new ListNode();
        stack = new ListNodeStack(head);
    }

    @Test
    void push() {
        stack.push(1);
        stack.push(2);

        assertThat(stack.peek()).isEqualTo(2);

        stack.push(3);

        assertThat(stack.peek()).isEqualTo(3);
    }

    @Test
    void pop() {
        stack.push(1);
        stack.push(2);
        stack.push(3);

        assertAll(
                () -> assertThat(stack.pop()).isEqualTo(3),
                () -> assertThat(stack.pop()).isEqualTo(2),
                () -> assertThat(stack.pop()).isEqualTo(1)
        );
    }

    @Test
    void fail_pop() {
        head = new ListNode();
        stack = new ListNodeStack(head);

        assertThrows(IllegalStateException.class, () -> stack.pop());
    }
}

과제 5. Queue를 구현하세요.

  • 배열을 사용해서 한번
  • ListNode를 사용해서 한번.

배열을 사용한 queue

public class Queue {

    private int[] queue;
    private int tailIdx;

    public Queue(int size) {
        queue = new int[size];
        tailIdx = -1;
    }

    public void add(int data) {
        if (tailIdx >= queue.length-1) {
            int[] tmp = new int[queue.length+1];
            for (int i = 0; i < queue.length; i++) {
                tmp[i] = queue[i];
            }

            queue = tmp;
        }

        queue[++tailIdx] = data;
    }

    public int poll() {
        if (tailIdx < 0) {
            throw new IllegalStateException("꺼낼 항목이 없습니다.");
        }

        int data = queue[0];
        for (int i = 0; i < tailIdx; i++) {
            queue[i] = queue[i+1];
        }
        tailIdx--;
        return data;
    }

    public int peek() {
        if (tailIdx < 0) {
            throw new IllegalStateException("꺼낼 항목이 없습니다.");
        }
        return queue[0];
    }

    @Override
    public String toString() {
        return Arrays.toString(queue);
    }
}

테스트 코드

class QueueTest {

    @Test
    void add() {
        Queue queue = new Queue(5);
        queue.add(1);
        queue.add(2);
        queue.add(3);
        queue.add(4);
        queue.add(5);
        queue.add(6);

        assertEquals(1, queue.peek());

        System.out.println(queue);
    }

    @Test
    void poll() {
        Queue queue = new Queue(3);

        queue.add(1);
        queue.add(2);
        queue.add(3);

        assertAll(
                () -> assertEquals(1, queue.poll()),
                () -> assertEquals(2, queue.poll()),
                () -> assertEquals(3, queue.poll())
        );
    }

    @Test
    void fail_poll() {
        Queue queue = new Queue(1);

        assertThrows(IllegalStateException.class, () -> queue.poll());
    }
}

ListNode를 이용한 Queue

public class ListNodeQueue {

    private ListNode head;
    private int tailIdx;

    public ListNodeQueue(ListNode head) {
        this.head = head;
        tailIdx = -1;
    }

    public void add(int data) {
        head.add(head, new ListNode(data), ++tailIdx);
    }

    public int poll() {
        if (tailIdx < 0) {
            throw new IllegalStateException("꺼낼 항목이 없습니다.");
        }
        return head.remove(head, 0).getItem();
    }

    public int peek() {
        return head.getNext().getItem();
    }
}

테스트 코드

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ListNodeQueueTest {

    private ListNode head;
    private ListNodeQueue queue;

    @BeforeEach
    void init() {
        head = new ListNode();
        queue = new ListNodeQueue(head);
    }

    @Test
    void add() {
        queue.add(1);
        queue.add(2);

        assertEquals(1, queue.peek());
    }

    @Test
    void poll() {
        queue.add(1);
        queue.add(2);
        queue.add(3);

        assertAll(
                () -> assertEquals(1, queue.poll()),
                () -> assertEquals(2, queue.poll()),
                () -> assertEquals(3, queue.poll())
        );
    }

    @Test
    void fail_poll() {
        assertThrows(IllegalStateException.class, () -> queue.poll());
    }
}

참고자료

이것이 자바다

JUnit5 공식문서

더 자바, 애플리케이션을 테스트하는 다양한 방법