https://github.com/whiteship/live-study
백기선님 자바 기초 스터디 4주차
목표
자바가 제공하는 제어문을 학습하세요.
학습할 것
선택문
반복문
과제 0 ~ 5
선택문
선택문, 일명 조건문은 조건식과 문장을 포함하는 블럭{ }으로 구성되어 있으며, 조건식의 연산결과에 따라 실행할 문장이 달라져서 프로그램의 실행흐름을 변경할 수 있다. 조건문은 if문과 switch문, 두 가지가 있으며 if문이 많이 사용된다.
if문
if (조건식) {
//조건식이 참(true)일 때, 수행될 문장들을 적는다.
}
조건식은 일반적으로 비교연산자와 논리연산자로 구성된다.
ex)
if (score > 60) {
System.out.println("합격입니다.");
}
이처럼 블럭 내의 문장이 하나뿐인 경우 괄호{ }를 생략할 수 있지만 가능하면 구분을 위해 생략하지 않고 사용하는 것이 바람직하다.
if (score > 60)
System.out.println("합격입니다.");
if-else문
if (조건식) {
//조건식이 참(true)일 때, 수행될 문장들을 적는다.
} else {
//조건식이 거짓(false)일 때, 수행될 문장들을 적는다.
}
ex)
if (score > 60) {
System.out.println("합격입니다.");
} else {
System.out.println("불합격입니다.");
}
if-else if문
ex)
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else if (score >= 70) {
System.out.println("C");
} else {
System.out.println("F");
}
중첩 if문
ex)
if (score >= 60) {
if (score != 60) {
System.out.println("합격입니다.");
}
} else {
System.out.println("불합격입니다.");
}
중첩 if문에서는 괄호{ } 생략에 더욱 조심해야 한다. 바깥쪽 if문과 안쪽의 if문이 서로 엉켜서 if문과 else블럭의 관계가 이도한 바와 다르게 형성될 수도 있기 때문이다.
if (score >= 60)
if (score != 60)
System.out.println("합격입니다.");
else
System.out.println("불합격입니다.");
위 코드는 언뜻 보기에 else블럭이 바깥쪽의 if문에 속한 것처럼 보이지만, 괄호가 생략되었을 때 else블럭은 가까운 if문에 속한 것으로 간주되기 때문에 실제로는 아래와 같이 안쪽 if문의 else블럭이 되버린다.
if (score >= 60) {
if (score != 60) {
System.out.println("합격입니다.");
} else {
System.out.println("불합격입니다.");
}
}
이는 맨 처음 코드에서 의도한 바와 완전히 다른 코드가 되기 때문에 중첩 if문에서는 괄호{ }를 확실히 해주는 것이 중요하다.
switch문
조건식의 결과가 참, 거짓 두 가지 밖에 없는 if문과 달리 switch문은 단 하나의 조건식으로 많은 경우의 수를 처리할 수 있다.
switch문 처리 과정
- 조건식을 계산한다.
- 조건식의 결과와 일치하는 case문으로 이동한다.
- 이후의 문장들을 수행한다.
- break문이나 switch문의 끝을 만나면 switch문 전체를 빠져나간다.
ex)
switch (month) {
case 3:
case 4:
case 5:
System.out.println("봄");
break;
case 6:
case 7:
case 8:
System.out.println("여름");
break;
case 9:
case 10:
case 11:
System.out.println("가을");
default: //나머지
System.out.println("겨울");
}
month의 값과 일치하는 case문으로 이동해 문장을 수행하고 break문을 만나면 switch문을 빠져나간다.
만약 case 5에서 break가 없다면 "봄"과 "여름" 모두 출력하게 되니 break를 적절한 위치에 꼭 사용하도록 하자.
switch문은 편리하고 직관적으로 보이지만 다음 제약조건이 따른다.
- switch문의 조건식 결과는 정수 또는 문자열이어야 한다.
- case문의 값은 정수, 상수만 가능하며, 중복되지 않아야 한다.
따라서 조건을 정수 혹은 문자열로 표현할 수 있는 경우 switch를 사용해도 좋지만 일반적으로 유연하게 조건을 설정할 수 있는 if문을 사용한다.
게다가 switch-case문은 매번 break, case를 장황하게 적어줘야 하는 것도 번거롭다.
그래서 자바12 이후에 switch 연산자를 제공하여 보다 편리하게 switch를 사용할 수 있게 되었다.
switch 연산자는 다음 글을 참고
https://gksdudrb922.tistory.com/135
반복문
반복문은 어떤 작업이 반복적으로 수행되도록 할 때 사용되며, 반복문의 종류로는 for문과 while문, 그리고 while문의 변형인 do-while문이 있다.
for문
for문은 아래와 같이 '초기화', '조건식', '증감식', '블럭{ }', 모두 4부분으로 이루어져 있으며, 조건식이 참인 동안 블럭 { } 내의 문장들을 반복하다 거짓이 되면 반복문을 벗어난다.
for (초기화;조건식;증감식) {
//조건식이 참일 때 수행될 문장들을 적는다.
}
제일 먼저 초기화(1)가 수행되고, 그 이후버트는 조건식이 참인 동안 조건식(2) -> 수행될 문장(3) -> 증감식(4) 순서로 계속 반복된다. 그러다가 조건식이 거짓이 되면, for문 전체를 빠져나가게 된다.
ex)
for (int i = 1; i <= 5; i++) {
System.out.println(i);
}
결과
1
2
3
4
5
향상된 for문
자바5부터 배열과 컬렉션에 저장된 요소에 접근할 때 기존보다 편리한 방법으로 처리할 수 있도록 for문의 새로운 문법이 추가되었다.
for ( 타입 변수명 : 배열 또는 컬렉션) {
//반복할 문장
}
ex)
int[] arr = { 10, 20, 30, 40, 50 }
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
-> 향상된 for문
for (int tmp : arr) {
System.out.println(tmp);
}
두 for문은 동등하며, 향상된 for문이 더 간결하다는 것을 알 수 있다. 그러나 향상된 for문은 일반적인 for문과 달리 배열이나 컬렉션에 저장된 요소를 읽어오는 용도로만 사용할 수 있다는 제약이 있다.
while문
while (조건식) {
//조건식의 연산결과가 참(true)인 동안, 반복될 문장들을 적는다.
}
<for문과의 비교>
for (int i = 1; i <= 5; i++) {
System.out.println(i);
}
int i = 1;
while (i <= 5) {
System.out.println(i);
i++;
}
while문의 경우 이처럼 초기화(1)와 증감식(4)을 따로 설정해줘야 한다.
do-while문
do-while문은 while문의 변형으로 기본적인 구조는 while문과 같으나 조건식과 블럭{ }의 순서를 바꿔놓은 것이다. 그래서 while문과 반대로 블럭{ }을 먼저 수행한 후에 조건식을 평가한다.
while문은 조건식의 결과에따라 블럭{ }이 한 번도 수행되지 않을 수 있지만, do-while문은 최소한 한번은 수행될 것을 보장하낟.
do {
//조건식의 연산결과가 참일 때 수행될 문장들을 적는다.
} while (조건식); <- 끝에 ';'을 잊지 않도록 주의
ex)
do {
System.out.println(i);
i++;
} while (i <= 5);
break문
앞서 switch문에서 break문에 대해 배웠던 것을 기억할 것이다. 반복문에서도 break문을 사용할 수 있는데, switch문에서 그랬던 것처럼, break문은 자신이 가장 가까운 반복문을 벗어난다. 주로 if문과 함께 사용되어 특정 조건을 만족하면 반복문을 벗어 나도록 한다.
int i = 1;
while (true) {
if (i >= 5) {
break;
}
System.out.println(i);
i++;
}
결과
1
2
3
4
무한 loop, while(true)를 통해 i를 1부터 출력하면서 i가 5 이상이 되면 반복을 그만한다.
continue문
continue문은 반복문 내에서만 사용될 수 있으며, 반복이 진행되는 도중에 continue문을 만나면 다음 반복으로 넘어간다.
for문의 경우 증감식으로 이동하며, while문의 경우 조건식으로 이동한다.
전체 반복 중에 특정 조건을 만족하는 경우를 제외하고자 할 때 유용하다.
for (int i = 1; i <= 10; i++) {
if (i % 3 == 0) {
continue;
}
System.out.println(i);
}
결과
1
2
4
5
7
8
10
1과 10사이의 숫자를 출력하되 그 중에서 3의 배수인 것은 제외하도록 하였다.
이름 붙은 반복문
break문은 근접한 단 하나의 반복문만 벗어날 수 있기 때문에, 여러 개의 반복문이 중첩된 경우에는 break문으로 중첩 반복문을 완전히 벗어날 수 없다. 이 때는 중첩 반복문 앞에 이름을 붙이고 break문에 이름을 지정해 줌으로써 하나 이상의 반복문을 벗어날 수 있다.
다음 구구단 예시를 보자.
for (int i = 2; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
System.out.println(i + "*" + j + "=" + i * j);
}
System.out.println();
}
결과
2*1=2
2*2=4
2*3=6
...
2*9=18
3*1=3
3*2=6
3*3=9
...
3*9=27
...
9*1=9
9*2=18
9*3=27
...
9*9=81
일반적인 구구단 코드이다.
여기에 특정 break 조건을 더해보겠다.
for (int i = 2; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
if (j == 5) {
break;
}
System.out.println(i + "*" + j + "=" + i * j);
}
System.out.println();
}
결과
2*1=2
2*2=4
2*3=6
2*4=8
3*1=3
3*2=6
3*3=9
3*4=12
...
9*1=9
9*2=18
9*3=27
9*4=36
j가 5일 때 가장 가까운 반복문 j loop를 탈출하기 때문에 위와 같은 결과가 나온다.
그렇다면 j가 5일 때 상위 반복문인 i loop를 탈출하고 싶다면 어떻게 해야할까? 이 때, 반복문에 이름을 붙이면 된다.
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);
}
System.out.println();
}
결과
2*1=2
2*2=4
2*3=6
2*4=8
이처럼 i loop에 Loop1이라는 이름을 붙이고 break문에서 이름을 지정해주면 해당 이름의 반복문을 탈출할 수 있게 된다.
+) continue문에도 마찬가지로 이름 붙인 반복문을 사용할 수 있다.
과제 0. JUnit5를 학습하세요.
JUnit은 자바 프로그래밍 언어용 유닛 테스트 프레임워크이다.
JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: JVM에서 테스트 프레임워크를 실행할 수 있도록 한다.
- JUnit Jupiter: 테스트를 하기 위한 여러 도구들을 갖고 있다.
- JUnit Vintage: JUnit3 또는 JUnit4 기반 테스트를 실행할 수 있도록 도와준다.
여기서는 IntelliJ Gradle 프로젝트의 JUnit5를 학습할 것이다.
JUnit5 시작
Intellij에서 Gradle 프로젝트를 생성한다면, 자동으로 JUnit5가 dependencies에 등록된다.
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}
test {
useJUnitPlatform()
}
만약 위 코드가 없다면 직접 의존성을 추가해주면 된다.
아래 useJUnitPlatform()은 말 그대로 JUnit Platform을 사용하여 테스트를 실행하도록 한다.
만약 JUnit5 이전 버전을 사용하고자 한다면 아래 의존성을 추가하면 된다.
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
testCompileOnly 'junit:junit:4.13'
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'
}
Intellij는 src/test/java 아래에서 JUnit5 테스트를 진행할 수 있다.
혹은 특정 클래스에 command + shift + t를 입력하면 테스트를 만들 수 있다. (윈도우 ctrl + shift + t)
테스트는 기본적으로 단위 테스트 별로 @Test 애노테이션을 지정하면 된다.
@Test
public void test1() {
}
@Test
public void test2() {
}
+) 참고로 라이브 템플릿을 통해 단위 테스트 템플릿을 만들면 편리하다. 다음 글을 참고하자.
https://gksdudrb922.tistory.com/28?category=959621
JUnit5에서 자주 사용하는 API
JUnit5는 여러 상황들에 대해 테스트할 수 있는 도구인 Assertions 클래스를 제공한다.
본 포스팅에서는 Assertions가 제공하고 자주 사용하는 API들을 소개한다.
나머지 API들에 대해서는 상황, 필요에 따라 직접 찾아가며 테스트를 진행하는 것이 훨씬 효율적이다.
assertEquals
@Test
public void test1() {
int num = 1;
Assertions.assertEquals(1, num);
}
두 인자 간에 동일성 검사('==')를 한다.
assertAll
@Test
public void test1() {
List<Integer> numbers = List.of(1, 2, 3);
Assertions.assertAll(
() -> Assertions.assertEquals(1, numbers.get(0)),
() -> Assertions.assertEquals(2, numbers.get(1)),
() -> Assertions.assertEquals(3, numbers.get(2))
);
}
여러 Assertions들을 한 데로 묶을 수 있다.
assertThrows
@Test
public void test1() {
List<Integer> numbers = List.of(1, 2, 3);
Assertions.assertThrows(ArrayIndexOutOfBoundsException.class,
() -> numbers.get(3));
}
Exception이 나올만한 상황에 대해 해당 Exception을 검사할 수 있다.
과제 1. live-study 대시 보드를 만드는 코드를 작성하세요
과제 2. LinkedList를 구현하세요.
LinkedList란?
각 노드가 데이터와 포인터를 가지고 한 줄로 연결되어 있는 방식의 자료구조이다.
데이터를 담고 있는 노드들이 연결되어 있고, 노드의 포인터가 이전 노드와의 연결을 담당한다.
<ListNode>
public class ListNode {
private int data;
private ListNode next;
public ListNode() {
}
public ListNode(int data) {
this.data = data;
}
public int getData() {
return data;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}
<LinkedList>
public class LinkedList {
public static ListNode add(ListNode head, ListNode nodeToAdd, int position) {
if (position < 0) {
exceptByPosition();
}
ListNode pre = updatePreNode(head, position);
nodeToAdd.setNext(pre.getNext());
pre.setNext(nodeToAdd);
return nodeToAdd;
}
public static ListNode remove(ListNode head, int positionToRemove) {
if (positionToRemove < 0) {
exceptByPosition();
}
ListNode pre = updatePreNode(head, positionToRemove);
if (pre.getNext() == null) {
exceptByPosition();
}
ListNode node = pre.getNext();
pre.setNext(pre.getNext().getNext());
return node;
}
public static boolean contains(ListNode head, ListNode nodeToCheck) {
for (ListNode node = head; node != null; node = node.getNext()) {
if (node == nodeToCheck) {
return true;
}
}
return false;
}
private static ListNode exceptByPosition() {
throw new IllegalStateException("Invalid Position.");
}
private static ListNode updatePreNode(ListNode head, int position) {
ListNode pre = head;
for (int i = 0; i < position; i++) {
pre = pre.getNext();
if (pre == null) {
exceptByPosition();
}
}
return pre;
}
}
<테스트>
class LinkedListTest {
@Test
public void addTest() {
//given
ListNode head = new ListNode();
ListNode listNode1 = new ListNode(1);
ListNode listNode2 = new ListNode(2);
ListNode listNode3 = new ListNode(3);
//when
LinkedList.add(head, listNode1, 0);
LinkedList.add(head, listNode2, 0);
LinkedList.add(head, listNode3, 1);
//then
assertAll(
() -> assertEquals(listNode2, head.getNext()),
() -> assertEquals(listNode3, head.getNext().getNext()),
() -> assertEquals(listNode1, head.getNext().getNext().getNext()),
() -> assertThrows(IllegalStateException.class,
() -> LinkedList.add(head, new ListNode(4), 4))
);
}
@Test
public void removeTest() {
//given
ListNode head = new ListNode();
ListNode listNode1 = new ListNode(1);
ListNode listNode2 = new ListNode(2);
ListNode listNode3 = new ListNode(3);
LinkedList.add(head, listNode1, 0);
LinkedList.add(head, listNode2, 1);
LinkedList.add(head, listNode3, 2);
//when
ListNode removedNode = LinkedList.remove(head, 1);
//then
assertAll(
() -> assertEquals(listNode1, head.getNext()),
() -> assertEquals(listNode3, head.getNext().getNext()),
() -> assertEquals(listNode2, removedNode),
() -> assertThrows(IllegalStateException.class,
() -> LinkedList.remove(head, 2))
);
}
@Test
public void containsTest() {
//given
ListNode head = new ListNode();
ListNode listNode1 = new ListNode(1);
ListNode listNode2 = new ListNode(2);
ListNode listNode3 = new ListNode(3);
LinkedList.add(head, listNode1, 0);
LinkedList.add(head, listNode2, 1);
LinkedList.add(head, listNode3, 2);
//when
boolean result1 = LinkedList.contains(head, listNode2);
boolean result2 = LinkedList.contains(head, new ListNode(4));
//then
assertTrue(result1);
assertFalse(result2);
}
}
과제 3. Stack을 구현하세요.
<Stack>
public class Stack {
private static final int STACK_SIZE = 100;
private int top = -1;
private int[] stack = new int[STACK_SIZE];
public void push(int data) {
if (++top == STACK_SIZE) {
throw new IllegalStateException("stack is full");
}
stack[top] = data;
}
public int pop() {
if (top == -1) {
throw new IllegalStateException("stack is empty.");
}
return stack[top--];
}
public int getTop() {
return top;
}
public int[] getStack() {
return stack;
}
}
<테스트>
class StackTest {
@Test
public void stackTest() {
//given
Stack stack = new Stack();
//when
stack.push(1);
stack.push(2);
stack.push(3);
int popData = stack.pop();
//then
int top = stack.getTop();
assertAll(
() -> assertEquals(2, stack.getStack()[top]),
() -> assertEquals(3, popData)
);
}
@Test
public void stackFull() {
//given
Stack stack = new Stack();
//when
for (int i = 0; i < 100; i++) {
stack.push(i);
}
//then
assertThrows(IllegalStateException.class,
() -> stack.push(100));
}
@Test
public void stackEmpty() {
//given
Stack stack = new Stack();
//when
stack.push(1);
stack.pop();
//then
assertThrows(IllegalStateException.class,
() -> stack.pop());
}
}
과제 4. 앞서 만든 ListNode를 사용해서 Stack을 구현하세요.
<ListNodeStack>
public class ListNodeStack {
private ListNode head;
public ListNodeStack() {
head = new ListNode();
}
public void push(int data) {
LinkedList.add(head, new ListNode(data), 0);
}
public int pop() {
return LinkedList.remove(head, 0).getData();
}
public ListNode getHead() {
return head;
}
}
<테스트>
class ListNodeStackTest {
@Test
public void listNodeStackTest() {
//given
ListNodeStack listNodeStack = new ListNodeStack();
//when
listNodeStack.push(1);
listNodeStack.push(2);
listNodeStack.push(3);
int data = listNodeStack.pop();
//then
assertAll(
() -> assertEquals(2, listNodeStack.getHead().getNext().getData()),
() -> assertEquals(1, listNodeStack.getHead().getNext().getNext().getData()),
() -> assertEquals(3, data)
);
}
@Test
public void listNodeStackEmpty() {
//given
ListNodeStack listNodeStack = new ListNodeStack();
//when
listNodeStack.push(1);
listNodeStack.pop();
//then
assertThrows(IllegalStateException.class,
() -> listNodeStack.pop());
}
}
과제 5. Queue를 구현하세요.
배열 사용
<Queue>
public class Queue {
private static final int QUEUE_SIZE = 100;
private int front =-1, rear = -1;
private int[] queue = new int[QUEUE_SIZE];
public void offer(int data) {
if (++rear == QUEUE_SIZE) {
throw new IllegalStateException("queue is full");
}
queue[rear] = data;
}
public int poll() {
if (front == rear) {
throw new IllegalStateException("queue is empty.");
}
return queue[++front];
}
public int getFront() {
return front;
}
public int getRear() {
return rear;
}
public int[] getQueue() {
return queue;
}
}
<테스트>
class QueueTest {
@Test
public void queueTest() {
//given
Queue queue = new Queue();
//when
queue.offer(1);
queue.offer(2);
queue.offer(3);
int popData = queue.poll();
//then
int front = queue.getFront();
int rear = queue.getRear();
assertAll(
() -> assertEquals(2, queue.getQueue()[front + 1]),
() -> assertEquals(3, queue.getQueue()[rear]),
() -> assertEquals(1, popData)
);
}
@Test
public void queueFull() {
//given
Queue queue = new Queue();
//when
for (int i = 0; i < 100; i++) {
queue.offer(i);
}
//then
assertThrows(IllegalStateException.class,
() -> queue.offer(100));
}
@Test
public void queueEmpty() {
//given
Queue queue = new Queue();
//when
queue.offer(1);
queue.poll();
//then
assertThrows(IllegalStateException.class,
() -> queue.poll());
}
}
ListNode 사용
<ListNodeQueue>
public class ListNodeQueue {
private ListNode head;
private int size = 0;
public ListNodeQueue() {
head = new ListNode();
}
public void offer(int data) {
LinkedList.add(head, new ListNode(data), size++);
}
public int poll() {
return LinkedList.remove(head, 0).getData();
}
public ListNode getHead() {
return head;
}
}
<테스트>
class ListNodeQueueTest {
@Test
public void listNodeQueueTest() {
//given
ListNodeQueue listNodeQueue = new ListNodeQueue();
//when
listNodeQueue.offer(1);
listNodeQueue.offer(2);
listNodeQueue.offer(3);
int data = listNodeQueue.poll();
//then
assertAll(
() -> assertEquals(2, listNodeQueue.getHead().getNext().getData()),
() -> assertEquals(3, listNodeQueue.getHead().getNext().getNext().getData()),
() -> assertEquals(1, data)
);
}
@Test
public void listNodeQueueEmpty() {
//given
ListNodeQueue listNodeQueue = new ListNodeQueue();
//when
listNodeQueue.offer(1);
listNodeQueue.poll();
//then
assertThrows(IllegalStateException.class,
() -> listNodeQueue.poll());
}
}
'java > java' 카테고리의 다른 글
[Java] 상속 (3) | 2021.07.12 |
---|---|
[Java] 클래스 (1) | 2021.07.07 |
[Java] Java 환경 변수 설정 - MacOS (0) | 2021.06.29 |
[Java] 연산자 (2) | 2021.06.23 |
[Java] 배열의 복사 (0) | 2021.06.23 |