[자바 라이브 스터디] 4주차 - 제어문
•
1. 선택문
(1) if문
•
정의)
if(조건식) {
...
} else if (조건식) {
...
} else {
...
}
Java
복사
•
특징)
◦
중첩 가능
(2) switch문
•
정의)
switch (조건식) {
case 값1: case 값2:
...
break;
case 값3:
...
break;
default:
...
}
Java
복사
•
특징)
◦
중첩 가능
◦
제약조건
▪
조건식 결과는 정수 또는 문자열이어야 함
▪
case문의 값은 정수 상수만 가능하며, 중복되지 않아야 함
◦
새로운 switch문(Java SE 12 switch expressions)
▪
yield : case의 결과로 반환하고 싶은 값이 있을 경우, yield를 사용하면 됨
▪
-> : :대신 사용할 수 있음
▪
예시)
•
기존
public enum Day { SUNDAY, MONDAY, TUESDAY,
WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; }
// ...
int numLetters = 0;
Day day = Day.WEDNESDAY;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
default:
throw new IllegalStateException("Invalid day: " + day);
}
System.out.println(numLetters);
Java
복사
•
arrow 사용
Day day = Day.WEDNESDAY;
System.out.println(
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException("Invalid day: " + day);
}
);
Java
복사
•
yield문을 사용하여 정수값을 반환
Day day = Day.WEDNESDAY;
int numLetters = switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
yield 6;
case TUESDAY:
System.out.println(7);
yield 7;
case THURSDAY:
case SATURDAY:
System.out.println(8);
yield 8;
case WEDNESDAY:
System.out.println(9);
yield 9;
default:
throw new IllegalStateException("Invalid day: " + day);
};
System.out.println(numLetters);
Java
복사
2. 반복문
(1) for문
•
정의)
for(초기화; 조건식; 증감식){
}
Java
복사
•
특징)
◦
중첩 가능
◦
향상된 for문
for(타입 변수명 : 배열 또는 컬렉션) {
}
Java
복사
◦
forEach 메서드
배열.forEach( 타입 변수명 -> { // 배열을 순회하며 꺼내온 값을 변수에 삽입하고, 매 시행마다 블럭을 실행
});
Java
복사
(2) while문
•
정의)
while(조건식) {
...
}
Java
복사
(3) do-while문
•
정의)
do {
...
} while(조건식)
Java
복사
3. XUnit 테스팅 Framework 만들기
•
내 전체 코드
•
정의) 자바만 단위 테스팅 프레임 워크인 JUnit만 있는게 아니다. 다른 언어도 단위 테스트를 위한 프레임워크가 존재하며 보통 이름을 xUnit이라 칭한다.
xUnit이름 | 해당 언어 | 관련 사이트 |
JUnit | Java | |
CUnit | C | |
CppUnit | C++ | |
PHPUnit | PHp | |
PyUnit | Python |
•
결과물 예시)
Exception in thread "main" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at xunit.TestCase.run(TestCase.java:30)
at xunit.XUnitTest.main(XUnitTest.java:31)
Caused by: java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at xunit.TestCase.run(TestCase.java:24)
... 1 more
Caused by: java.lang.AssertionError: expected <true> but was <false>
at xunit.Assert.assertEquals(Assert.java:7)
at xunit.TestCaseTest.testRunning(TestCaseTest.java:15)
... 6 more
Java
복사
4. JUnit 5 학습하세요.
•
구성)
◦
JUnit 플랫폼 : 테스팅 프레임워크를 구동하기 위한 런처와 테스트 엔진을 위한 API 제공
◦
JUnit 주피터 : Junit5를 위한 테스트 API와 실행 엔진 제공
◦
JUnit qlsxlwl : JUnit 3과 4로 작성된 테스트를 JUnit5 플랫폼에서 실행하기 위한 모듈 제공
•
기본 구조)
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*; // 값 검증 위한 다양한 정적 메서드들 제공하는 클래스
public class sumTest {
@Test // test 실행할 메서드에 붙임. 해당 메서드는 private이면 안됨
void sum() {
int result = 1+2;
assertEquals(5, result);
}
Java
복사
•
주요 단언 메서드)
메서드 | 설명 |
assertEquals(expected, actual) | actual과 expected가 같은지 확인 |
assertNotEquals(unexpected, actual) | actual과 unexpected가 같은지 확인 |
assertSame(Object expected, Object actual) | 두 객체가 동일한 객체인지 검사 |
assertNotSame(Object unexpected, Object actual) | 두 객체가 동일하지 않은 객체인지 검사 |
assertTrue(boolean condition) | 값이 true인지 검사 |
assertFalse(boolean condition) | 값이 false인지 검사 |
assertNull(Object actual) | 값이 null인지 검사 |
assertNotNull(Object actual) | 값이 null이 아닌지 검사 |
fail() | 테스트를 실패 처리 |
참고
5. live-study 대시 보드를 만들기
live-study 대시 보드 Requirements https://github.com/whiteship/live-study/issues
•
깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크 할 것.
•
참여율을 계산. 총 18회에 중에 몇 %를 참여했는지 소숫점 두자리까지 보여줄 것.
•
Github 자바 라이브러리를 사용하면 좋음.
•
내 전체 코드
•
구현 과정
1.
build.gradle에 아래 코드 추가
implementation 'org.kohsuke:github-api:1.124'
Java
복사
3.
메인 코드
import org.kohsuke.github.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GithubClient {
private static final String token = System.getenv("token");
private static final Logger logger = (Logger) LogManager.getLogger(GithubClient.class);
private GitHub github;
private GHRepository repository;
private List<GHIssue> issues;
private List<GHIssueComment> comments;
private Map<String, Double> userMap = new HashMap<>();
public GithubClient() {
try {
github = new GitHubBuilder().withOAuthToken(token).build();
repository = github.getRepository("whiteship/live-study");
issues = repository.getIssues(GHIssueState.ALL);
for(GHIssue issue: issues) {
System.out.println(issue.getNumber());
comments = issue.getComments();
for(GHIssueComment comment: comments) {
final String login = comment.getUser().getLogin();
System.out.println(login);
userMap.put(login, userMap.getOrDefault(login, 0.0)+1);
}
}
logger.info("Connection Success");
} catch (Exception e) {
logger.info("Connection Failed");
}
}
public void run() {
for(String user : userMap.keySet()) {
final double percent = userMap.get(user) / 18*100;
System.out.printf("%s : %.2f\n", user, percent);
}
}
}
Java
복사
4.
결과
리팩토링 1) 병렬 처리, 증분 연산 통해 실행 시간 2m44s에서 24s로 감소
•
userMap을 ConcurrentHashMap으로 변경하여 동시성 문제를 방지
•
issues 리스트를 병렬 스트림으로 변환하여 병렬 처리하도록 수정
•
issue.getComments()를 issue.listComments().toList()로 변경하여 한 번에 여러 코멘트를 가져오게 수정
•
userMap.put() 대신 userMap.compute()를 사용하여 증분 연산을 수행
import org.kohsuke.github.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class GithubClient {
private static final String token = System.getenv("token"); // GitHub 인증 토큰
private static final Logger logger = LogManager.getLogger(GithubClient.class); // 로그 기록 위한 Logger 객체
private Map<String, Double> userMap = new ConcurrentHashMap<>(); // 사용자별로 댓글 개수를 저장하는 Map 객체
public GithubClient() {
try {
GitHub github = new GitHubBuilder().withOAuthToken(token).build(); // GitHub 인증 토큰으로 GitHub 객체
GHRepository repository = github.getRepository("whiteship/live-study"); // "whiteship/live-study" 리포지토리의 정보
List<GHIssue> issues = repository.getIssues(GHIssueState.ALL); // 모든 이슈 정보
issues.parallelStream().forEach(issue -> { // issues를 병렬 스트림으로 변환해 병렬 처리
try {
List<GHIssueComment> comments = issue.listComments().toList(); // 한 이슈에 달린 모든 댓글 (한 번에 가져옴)
for (GHIssueComment comment : comments) {
String login = comment.getUser().getLogin(); // 댓글 작성자의 로그인 ID를 가져옴
userMap.compute(login, (key, value) -> value == null ? 1.0 : value + 1.0); // 사용자별로 댓글 개수를 증가시킴(증분 연산)
}
} catch (IOException e) {
logger.debug("Failed to fetch comments for issue", e); // 이슈의 댓글 가져오기 실패
}
});
logger.debug("Connection Success"); // 깃허브 연결, 리포지토리 가져오기, 이슈 가져오기 성공
} catch (IOException e) {
logger.debug("Connection Failed", e); // 깃허브 연결, 리포지토리 가져오기, 이슈 가져오기 실패
}
}
public void run() {
for (Map.Entry<String, Double> entry : userMap.entrySet()) {
String user = entry.getKey(); // 사용자 ID
double count = entry.getValue(); // 댓글 개수
double percent = (count / 18) * 100; // 댓글 비율 계산
System.out.printf("%s : %.2f\n", user, percent); // 사용자별 댓글 비율을 출력
}
}
}
Java
복사
참고
6. Stack을 구현하세요.
•
int 배열을 사용해서 정수를 저장하는 Stack을 구현하세요.
•
void push(int data)를 구현하세요.
•
int pop()을 구현하세요.
•
스택
◦
정의) 후입선출
▪
top : 삽입과 삭제가 일어나는 위치
▪
push : top 위치에 새로운 데이터를 삽입하는 연산
▪
pop : top 위치에 현재 있는 데이터를 삭제하고 확인하는 연산
▪
peek : top 위치에 현재 있는 데이터를 단순 확인하는 연산
◦
특징)
▪
깊이 우선 탐색, 백트래킹 종류에 효과적
▪
재귀 함수 알고리즘 원리와 일맥상통
◦
코드)
public class Stack {
int[] stack; // int 배열을 사용해서 정수를 저장하는 stack
int top; // 삽입과 삭제가 일어나는 위치
static final int DEFAULT_CAPACITY = 5; // 최소 용량 크기
// 생성자
public Stack() {
this.stack = new int[DEFAULT_CAPACITY]; // null로 초기화
this.top = -1;
}
// top 위치에 새로운 데이터를 삽입하는 연산
public void push(int data) {
if(isFull()) {
resize();
}
else stack[++top] = data;
}
// top 위치에 현재 있는 데이터를 삭제하고 확인하는 연산
public int pop() {
if(isEmpty()) {
throw new EmptyStackException();
}
int value = stack[top];
stack[top] = 0; // 굳이 필요한가?
top--;
resize();
return value;
}
// top 위치에 현재 있는 데이터를 단순 확인하는 연산
public int peek() {
if(isEmpty()) {
throw new EmptyStackException();
}
return stack[top];
}
// 스택이 비어있는지 확인하는 연산
public boolean isEmpty() {
return (top == -1);
}
// 스택이 꽉 찼는지 확인하는 연산
public boolean isFull() {
return (top == stack.length - 1); // top이 마지막 인덱스면 true 반환
}
// 스택 꽉 찼을 때 배열 크기 늘리는 내부 연산(가변 배열 처리)
private void resize() {
int capacity = stack.length - 1; // 용량 (top과 비교할 거라서 0부터 시작하는 인덱스 기준으로 용량 카운트)
// 용량이 꽉 찬 경우, 2배로 늘림
if(top == capacity) {
stack = Arrays.copyOf(stack, stack.length * 2); // 새 용량 만큼 설정하고 기존 원소들을 복사해서 넣고 반환 (빈공간은 null)
}
// 용량에 비해 데이터 양이 적은 경우, 1/2배로 줄임
if(top < (capacity/2)) {
stack = Arrays.copyOf(stack, Math.max(stack.length / 2, DEFAULT_CAPACITY)); // 새 용량과 디폴트 용량 중 큰 거만큼 설정
}
}
public String toString() {
String result = "";
if (isEmpty()) return result;
for (int x : stack) {
result += String.valueOf(x + ',');
}
return result; //.substring(0, result.length()-1);
}
}
Java
복사
참고