기록하자..
제어문 | 백기선님 LIVE-STUDY 본문
제어문
학습할 것
- 선택문
- 반복문
제어문
자바 프로그램을 시작하고 프로그램의 실행 흐름이 존재할 때, 실행 흐름을 원하는 방향으로 바꿀 수 있도록 해주는 것이 제어문이다.
선택문
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 = 10String[] 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 = Fridayfor-each문
Java8이후로 stream을 사용해 Collection이 구현하고 있는 for-each를 사용할 수 있다.
dayList.forEach(System.out::println);
Monday
Tuesday
Wednesday
Thursday
Fridaywhile문
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 = 9do-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());
}
}
참고자료
'자바' 카테고리의 다른 글
| 상속 | 백기선님 LIVE-STUDY (0) | 2022.05.02 |
|---|---|
| 클래스 | 백기선님 LIVE-STUDY (0) | 2022.05.02 |
| 자바가 제공하는 다양한 연산자 | 백기선님 LIVE-STUDY (0) | 2022.03.24 |
| 자바 데이터 타입, 변수 그리고 배열 | 백기선님 LIVE-STUDY (0) | 2022.03.24 |
| JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가 | 백기선님 LIVE-STUDY (0) | 2022.03.09 |