-
[Study] 자바 4주차 과제JAVA 2020. 12. 8. 20:22반응형
4주차 과제입니다.
프로그램의 흐름(flow)를 바꾸는 역할을 하는 문장들을 제어문이라고 하고 제어문에는 '선택문(조건문)과 반복문'이 있습니다.
조건문은 조건에 따라 다른 문장이 수행되도록 하고, 반복문은 특정 문장들을 반복해서 수행합니다.
선택문
if문
if문은 가장 기본적인 선택문이며, 다음과 같이 '조건식'과 '괄호{}'로 이루어져 있습니다.
'if'의 뜻이 '만일 ~이라면...' 이므로 '만일(if) 조건식이 참(true)이면 괄호{} 안의 문장들을 수행하라' 라는 의미로 이해하면 됩니다.
/* if (조건식) { // 조건식이 참(true)일 때 수행될 문장들을 적는다. } */ int score = 80; if (score > 60) { System.out.println("합격입니다."); } //괄호 생략 가능 if (score > 60) System.out.println("합격입니다."); //출력 결과 //합격입니다.
if-else문
if문에 'else 블럭'이 더 추가된 것이 if-else문입니다.
'else'의 뜻이 '그 밖의 다른'이므로 조건식의 결과가 참이 아닐 때, 즉 거짓일 때 else 블럭의 문장을 수행하라는 뜻입니다.
/* if (조건식) { // 조건식이 참(true)일 때 수행될 문장들을 적는다. } else { // 조건식이 거짓(false)일 때 수행될 문장들을 적는다. } */ Scanner scanner = new Scanner(System.in); int input = scanner.nextInt(); if (input > 0) { System.out.println("입력하신 수는 자연수입니다."); } else { System.out.println("입력하신 수는 자연수가 아닙니다."); }
처리해야 할 경우의 수가 셋 이상인 경우에는 여러 개의 조건식을 쓸 수 있는 'if-else if'문을 사용하면 됩니다.
/* if (조건식1) { // 조건식1의 연산결과가 참(true)일 때 수행될 문장들을 적는다. } else if (조건식2) { // 조건식2의 연산결과가 참(true)일 때 수행될 문장들을 적는다. } else if (조건식3) { // 여러 개의 else if를 사용할 수 있다. // 조건식3의 연산결과가 참(true)일 때 수행될 문장들을 적는다. } else { // 마지막은 보통 else 블럭으로 끝나며, else 블럭은 생략 가능하다. // 위의 어느 조건식도 만족하지 않을 때 수행될 문장들을 적는다. } */ Scanner scanner = new Scanner(System.in); int score = scanner.nextInt(); char grade = ' '; if (score >= 90) { grade = 'A'; } else if (score >= 80) { grade = 'B'; } else if (score >= 70) { grade = 'C'; } else { grade = 'D'; }
if문의 블럭 내에 또 다른 if문을 포함시키는 것도 가능한데 이것을 중첩 if문이라고 부르며 중첩의 횟수에는 거의 제한이 없습니다.
/* if (조건식1) { // 조건식1의 연산결과가 참(true)일 때 수행될 문장들을 적는다. if (조건식2) { // 조건식1과 조건식2가 모두 true일 때 수행될 문장들 } else { // 조건식1이 true이고, 조건식2가 false일 때 수행되는 문장들 } } else { // 조건식1이 false일 때 수행되는 문장들 } */ Scanner scanner = new Scanner(System.in); int score = scanner.nextInt(); String grade = ""; if (score >= 90) { grade = "A"; if (score >= 98) { grade += "+"; } else if (score < 94) { grade += "-"; } } else if (score >= 80) { grade = "B"; } ...
switch문
switch문은 단 하나 조건식으로 많은 경우의 수를 처리할 수 있고, 표현도 간결하기에 처리할 경우의 수가 많은 경우에는 if문 보다 switch문으로 작성하는 것이 좋습니다.
다만 switch문은 제약 조건이 있기 때문에, 경우의 수가 많아도 어쩔 수 없이 if문으로 작성해야 하는 경우가 있습니다.
그리고 3주차 과제에서 공부했던 것처럼 Java 13부터 기존의 switch문의 break 키워드가 yield 키워드로 변경되면서 둘 다 사용이 가능합니다.
또한 단일 표현식을 나타낼 때는 yield도 생략해서 화살표 방식으로 사용 가능합니다.
/* 1. 조건식을 계산한다. 2. 조건식의 결과와 일치하는 case문으로 이동한다. 3. 이후의 문장들을 수행한다. 4. break문이나 switch문의 끝을 만나면 switch문 전체를 빠져나간다. switch (조건식) { case 값1 : // 조건식의 결과가 값1과 같을 경우 수행될 문장들 // ... break; case 값2 : // 조건식의 결과가 값2와 같을 경우 수행될 문장들 // ... break; //switch문을 벗어난다. // ... default : // 조건식의 결과와 일치하는 case문이 없을 때 수행될 문장들 // ... } switch문의 제약 조건 1. switch문의 조건식 결과는 정수 또는 문자열이어야 한다. 2. case문의 값은 정수 상수 (문자 포함), 문자열만 가능하며, 중복되지 않아야 한다. */ //기존의 switch문 int result1; switch (s) { case "a" : result1 = 1; break; case "b" : result1 = 2; break; default: result1 = 3; break; } //yield로 변경된 switch문 int result2 = switch (s) { case "a" : yield 1; case "b" : yield 2; default: yield 3; }; //화살표 방식 switch문 int result3 = switch (s) { case "a" -> 1; case "b" -> 2; default -> 3; };
반복문
반복문은 어떤 작업이 반복적으로 수행되도록 할 때 사용되며, 반복문의 종류로는 for문과 while문, 그리고 while문의 변형인 do-while문이 있습니다.
for문과 while문은 구조와 기능이 유사하여 어느 경우에나 서로 변환이 가능하며, 반복 횟수를 알고 있을 때는 for문을, 그렇지 않을 때는 while문을 사용합니다.
for문
/* for (초기화; 조건식; 증감식) { // 조건식이 참(true)인 동안 수행될 문장들을 적는다. } 1. 초기화 2. 조건식 3. 수행될 문장 4. 증감식 순서로 진행 */ for (int i = 1; i <= 5; i++) { System.out.println(i); } //출력 결과 //1 //2 //3 //4 //5
for문 안에 또 다른 for문을 포함시키는 것이 가능하며, 중첩 for문이라고 합니다. 중첩 횟수는 거의 제한이 없습니다.
for (int i = 1; i <= 5; i++) { for (int j = 1; j <= i; j++) { System.out.println("*"); } System.out.println(); } //출력 결과 //* //** //*** //**** //*****
개선된 for문 (향상된 for문)
JDK 5에서부터 지원하였으며 Array, Collection 등의 모든 방(처음부터 끝까지)의 값을 출력할 수 있습니다.
인덱스를 사용할 수 없으며 기존의 for문보다 느리지만 간편하게 사용할 수 있습니다.
/* for (데이터형 변수명 : 배열명) { //배열방의 데이터형과 일치하는 데이터형 사용 } */ String[] arr = {"a", "b", "c"}; for (String str : arr) { System.out.println(str); } //출력 결과 //a //b //c
while문
/* while (조건식) { // 조건식의 연산결과가 참(true)인 동안, 반복될 문장들을 적는다. } 1. 조건식이 참(true)이면 블럭 {} 안으로 들어가고, 거짓(false)이면 while문을 벗어난다. 2. 블럭 {}의 문장을 수행하고 다시 조건식으로 돌아간다. */ int sum = 0; int i = 0; while (sum <= 100) { System.out.printf("%d - %d%n", i, sum); sum += ++i; } //출력 결과 //0 - 0 //1 - 1 //2 - 3 //3 - 6 //4 - 10 //5 - 15 //6 - 21 //7 - 28 //8 - 36 //9 - 45 //10 - 55 //11 - 66 //12 - 78 //13 - 91
do-while문
/* do { // 조건식의 연산결과가 참일 때 수행될 문장들을 적는다. (처음 한 번은 무조건 실행) } while (조건식); */ int input = 0; int answer = (int)(Math.random()*100) + 1; do { input = scanner.nextInt(); if (input > answer) { System.out.println("더 작은 수로 다시 시도해보세요."); } else if (input < answer) { System.out.println("더 큰 수로 다시 시도해보세요."); } } while (input != answer); System.out.println("정답입니다.");
break문, contiune문
break문은 자신이 포함된 가장 가까운 반복문을 벗어나고, continue문은 반복문의 끝으로 이동하여 다음 반복으로 넘기는 역할을 합니다.
while (true) { if (조건식) { //... break; } //... } for (int i = 0; i <= 10; i++) { if (조건식) { //... continue; } //... }
JUnit 5
JUnit5는 차세대 Java Test 프레임워크로 3가지의 서브 패키지로 구성되어 있습니다.
JUnit5를 사용하려면 최소한 JDK8 버전 이상을 사용하고 있어야합니다.
JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform : JUnit Platform은 JVM에서 테스트 프레임워크를 시작하기 위한 기초적인 역할을 수행합니다. 또한 테스트 개발을 위한 API를 제공합니다.
- JUnit Jupiter : JUnit5에서 테스트 및 Extension을 작성하기 위한 새로운 프로그래밍 모델과 확장 모델의 조합, TestEngine을 제공합니다.
- JUnit Vintage : 하위 호환성을 위해서 JUnit4, JUnit3를 실행할 수 있는 TestEngine입니다.
Maven의 경우 pom.xml에 아래와 같은 의존성을 추가하여야 합니다.
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.1.0</version> <scope>test</scope> </dependency>
1. 기본 Annotation
@BeforeAll , @BeforeEach
- @BeforeAll
해당 annotation이 달린 메소드가 현재 클래스의 모든 테스터 메소드보다 먼저 실행됩니다.
static 이어야 하고 이전 버전의 @BeforeClass와 동일합니다. - @BeforeEach
해당 annotation이 달린 메소드가 각 테스트 메소드 전에 실행됩니다.
이전 버전의 @Before과 동일합니다.
@BeforeAll static void beforeAll() { System.out.println("BeforeAll"); } @BeforeEach void beforeEach() { System.out.println("BeforeEach"); }
@DisplayName , @Disable
- @DisplayName
테스트 클래스 또는 테스트 메소드의 이름을 정의할 수 있습니다. - @Disable
테스트 클래스 또는 테스트 메소드를 비활성화할 수 있으며 이전 버전의 @Ignore와 동일합니다.
@DisplayName("DisplayName Test") @Test void testDisplayName() { System.out.println("Display"); } @Test @Disabled("Disabled Test") void testDisabled() { System.out.println("Disabled"); }
@AfterAll , @AfterEach
- @AfterAll
해당 annotation이 달린 메소드가 현재 클래스의 모든 테스터 메소드보다 이후에 실행됩니다.
static 이어야 하고 이전 버전의 @AfterClass와 동일합니다. - @AfterEach
해당 annotation이 달린 메소드가 각 테스트 메소드 이후에 실행됩니다.
이전 버전의 @After와 동일합니다.
@AfterAll static void afterAll() { System.out.println("AfterAll"); } @AfterEach void afterEach() { System.out.println("AfterEach"); }
2. Assertions and Assumptions
Assertions
Assertions는 번역하면 단정문이라는 뜻으로 만약 성공하지 않으면 테스트를 실패처리 하기 위해서 사용합니다.
JUnit4와 가장 큰 차이점은 Java8의 람다를 사용할 수 있다는 것으로 테스트 코드를 Functional하게 작성할 수 있습니다.
@Test void assertTrueTest() { assertTrue(Stream.of(1,2,3) .mapToInt(i -> i) .sum() > 5, () -> "Sum should be greater than 5"); } @Test void assertAllTest() { int[] numbers = {0, 1, 2, 3, 4}; assertAll("numbers", () -> assertEquals(numbers[0], 0), () -> assertEquals(numbers[2], 2) ); }
assertAll()을 사용하면 assertions를 그룹화하여 그룹 내에서 실패한 assertions를 MultipleFailuresError와 함께 기록할 수 있습니다.
즉, 실패의 정확한 위치를 파악할 수 있기 때문에 보다 복잡한 assertions를 만들어도 안전하게 사용할 수 있습니다.
Assumptions
Assumptions는 번역하면 가정문으로 특정 상황에서만 test문을 실행하고자 할 때, 반대로 특정 상황에서만 실행하지 않고자 할 때 사용하는 키워드입니다. 여기서 말하는 특정 상황이라는 것은 local 환경 등을 들 수 있습니다.
@Test void trueAssumption() { assumeTrue(5 > 1); assertEquals(5 + 2, 7); } @Test void falseAssumption() { assumeFalse(5 < 1); assertEquals(5 + 2, 7); } @Test void assumingThatTest() { String s = "String"; assumingThat( s.equals("String"), () -> assertEquals(2+2, 4) ); }
3. Exception Testing
assertThrows()를 이용하여 두 가지의 예외 테스트 방법을 구현할 수 있습니다.
1) 발생한 예외의 세부 사항을 확인
@Test void shouldThrowException() { Throwable exception = assertThrows(UnsupportedOperationException.class, () -> { throw new UnsupportedOperationException("Not supported"); }); assertEquals(exception.getMessage(), "Not supported"); }
2) 예외 유형의 유효성을 검사
@Test void assertThrowsException() { String str = null; assertThrows(IllegalArgumentException.class, () -> { Integer.valueOf(str); }); }
4. Dynamic Tests
- @TestFactory
해당 annotation이 달린 메소드는 동적 테스트를 위한 test factory 메소드입니다.
런타임에 생성된 테스트 케이스를 선언하고 실행할 수 있습니다.
@TestFactory Collection<DynamicTest> dynamicTestCollection() { return Arrays.asList( DynamicTest.dynamicTest("Add test", () -> assertEquals(2, Math.addExact(1, 1))), DynamicTest.dynamicTest("Multiply Test", () -> assertEquals(4, Math.multiplyExact(2, 2)))); }
Live-study 대시 보드 만드는 코드
- 깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크 할 것.
- 참여율을 계산하세요. 총 18회에 중에 몇 %를 참여했는지 소숫점 두자리가지 보여줄 것.
- Github 자바 라이브러리를 사용하면 편리합니다.
- 깃헙 API를 익명으로 호출하는데 제한이 있기 때문에 본인의 깃헙 프로젝트에 이슈를 만들고 테스트를 하시면 더 자주 테스트할 수 있습니다
import org.kohsuke.github.*; import java.io.IOException; import java.util.*; public class GithubHomework { public static void main(String[] args) throws IOException { GithubHomework githubHomework = new GithubHomework(); githubHomework.createGithub("본인 Personal access tokens"); List<GHIssue> issues = githubHomework.getRepositoryIssues("whiteship/live-study"); Map<String, Integer> participants = githubHomework.getParticipants(issues); for(String userLogin : participants.keySet()) { System.out.printf("%-20s: %.2f%%\n", userLogin, (participants.get(userLogin) / (float)(issues.size()))*100); } } public GitHub gitHub; public void createGithub(String personalToken) throws IOException { GitHubBuilder gitHubBuilder = new GitHubBuilder(); gitHub = gitHubBuilder.withOAuthToken(personalToken).build(); } public List<GHIssue> getRepositoryIssues(String repoName) throws IOException { GHRepository gitRepo = gitHub.getRepository(repoName); return gitRepo.getIssues(GHIssueState.ALL); } public Map<String, Integer> getParticipants(List<GHIssue> issues) throws IOException { Map<String, Integer> participants = new HashMap<String, Integer>(); for(GHIssue issue : issues) { List<GHIssueComment> issueComments = issue.getComments(); Set<String> userLoginSet = duplicateCheckComment(issueComments); for(String userLogin : userLoginSet) { participants.put(userLogin, participants.getOrDefault(userLogin, 0)+1); } } return participants; } public Set<String> duplicateCheckComment(List<GHIssueComment> issueComments) throws IOException { Set<String> duplicateCheckSet = new HashSet<String>(); for(GHIssueComment issueComment : issueComments) { duplicateCheckSet.add(issueComment.getUser().getLogin()); } return duplicateCheckSet; } }
LinkedList 구현
LinkedList는 자료들을 반드시 연속적으로 배열시키지는 않고 임의의 기억공간에 기억시키되, 자료 항목의 순서에 따라 노드의 포인터 부분을 이용하여 서로 연결시킨 자료 구조입니다.
LinkedList의 특징
- 노드의 삽입, 삭제 작업이 용이합니다.
- 기억공간이 연속적으로 놓여 있지 않아도 저장이 가능합니다.
- 연결을 위한 링크(포인터) 부분이 필요하기 때문에 ArrayList에 비해 기억공간 이용 효율이 좋지 않습니다.
- 연결을 위한 포인터를 찾는 시간이 필요하기 때문에 접근 속도가 느립니다.
- 중간 노드 연결이 끊어지면 그 다음 노드를 찾기 힘듭니다.
- 희소 행렬을 LinkedList로 표현하면 기억장소가 절약됩니다.
- 트리를 표현할 때 가장 적합한 자료구조 입니다.
아래는 LinkedList의 시간복잡도 입니다.
작업 LinkedList 탐색 O(n) 삽입 O(1): 맨 앞, 맨 뒤
O(n): 탐색을 통한 중간 삽입삭제 O(1): 맨 앞, 맨 뒤
O(n): 탐색을 통한 중간 삭제public class MyLinkedList { public ListNode add(ListNode head, ListNode nodeToAdd, int position) { ListNode node = head; if (position == 0) { if (head == null) { return nodeToAdd; } ListNode newNode = nodeToAdd; newNode.next = head; head = newNode; return head; } for (int i = 0; i < position - 1; i++) { node = node.next; } nodeToAdd.next = node.next; node.next = nodeToAdd; return head; } public ListNode remove(ListNode head, int positionToRemove) { ListNode node = head; if (positionToRemove == 0) { head = head.next; return head; } for (int i = 0; i < positionToRemove - 1; i++) { node = node.next; } ListNode tmpNode = node.next; node.next = tmpNode.next; return head; } public boolean contains(ListNode head, ListNode nodeToCheck) { while (head != null) { if (head.data == nodeToCheck.data) { return true; } head = head.next; } return false; } public void printNodeList(ListNode head) { ListNode node = head; while (node != null) { System.out.print(node.data + " "); node = node.next; } System.out.println(); } } class ListNode { int data; ListNode next; public ListNode(int data) { this.data = data; this.next = null; } }
배열로 Stack 구현
public class ArrayStack { private int[] arr; private int top; private int size; public ArrayStack(int size) { arr = new int[size]; top = -1; this.size = size; } public void push(int data) { if (top + 1 >= size) { throw new ArrayIndexOutOfBoundsException("stack is full"); } arr[++top] = data; } public int pop() { if (isEmpty()) { throw new ArrayIndexOutOfBoundsException("stack is empty"); } return arr[top--]; } public int peek() { if (isEmpty()) { throw new ArrayIndexOutOfBoundsException("stack is empty"); } return arr[top]; } public boolean isEmpty() { return top == -1; } public int size() { return size; } public void printData() { for (int i = 0; i <= top; i++) { System.out.print(arr[i] + " "); } System.out.println(); } }
ListNode로 Stack 구현
public class ListNodeStack { private ListNode head; public ListNodeStack() { head = null; } public void push(int data) { ListNode node = new ListNode(data); node.next = head; head = node; } public int pop() { if (isEmpty()) { throw new ArrayIndexOutOfBoundsException("stack is empty"); } int data = head.data; head = head.next; return data; } public int peek() { if (isEmpty()) { throw new ArrayIndexOutOfBoundsException("stack is empty"); } return head.data; } public boolean isEmpty() { return head == null; } public void printData() { String tmp = ""; ListNode tmpNode = head; while (tmpNode != null) { tmp = tmpNode.data+" "+tmp; tmpNode = tmpNode.next; } System.out.println(tmp); } }
배열로 Queue 구현
public class ArrayQueue { private int[] arr; private int front; private int rear; private int size; public ArrayQueue(int size) { arr = new int[size]; front = 0; rear = 0; this.size = size; } public void add(int data) { if (isFull()) { throw new ArrayIndexOutOfBoundsException("queue is full"); } arr[rear++] = data; } public boolean isFull() { return rear >= size; } public int poll() { if (isEmpty()) { throw new ArrayIndexOutOfBoundsException("queue is empty"); } int data = arr[front++]; if (isEmpty()) { front = 0; rear = 0; } return data; } public int peek() { if (isEmpty()) { throw new ArrayIndexOutOfBoundsException("queue is empty"); } return arr[front]; } public boolean isEmpty() { return front == rear; } public int size() { return size; } public void printData() { for (int i = front; i < rear; i++) { System.out.print(arr[i] + " "); } System.out.println(); } }
ListNode로 Queue 구현
public class ListNodeQueue { private ListNode front; private ListNode rear; public ListNodeQueue() { front = null; rear = null; } public boolean isEmpty() { return front == null; } public void add(int data) { ListNode node = new ListNode(data); if (isEmpty()) { front = node; rear = node; }else { rear.next = node; rear = node; } } public int poll() { if (isEmpty()) { throw new ArrayIndexOutOfBoundsException("queue is empty"); } int data = front.data; front = front.next; if (front == null) { rear = null; } return data; } public int peek() { if (isEmpty()) { throw new ArrayIndexOutOfBoundsException("queue is empty"); } return front.data; } public void printData() { ListNode tmpNode = front; while (tmpNode != null) { System.out.print(tmpNode.data+" "); tmpNode = tmpNode.next; } System.out.println(); } }
References
- 자바의 정석 (남궁성 지음)
- lob-dev.tistory.com/entry/Live-StudyWeek-04-%EC%A0%9C%EC%96%B4%EB%AC%B8-%EA%B3%BC%EC%A0%9C
- gmlwjd9405.github.io/2019/11/26/junit5-guide-basic.html
- sabarada.tistory.com/80?category=85581
- hee96-story.tistory.com/46?category=818557
728x90'JAVA' 카테고리의 다른 글
[Study] 자바 6주차 과제 (0) 2021.01.15 [Study] 자바 5주차 과제 (0) 2021.01.10 [Study] 자바 3주차 과제 (0) 2020.11.28 [Study] 자바 2주차 과제 (0) 2020.11.21 [Study] 자바 1주차 과제 (0) 2020.11.17