전체 보기

[백기선 자바] 제어문

작성일자
2023/06/19
태그
SUB PAGE
프로젝트
백기선 자바
책 종류
1 more property

[자바 라이브 스터디] 4주차 - 제어문

목표) 자바가 제공하는 제어문을 학습합니다.
4
issues
선택문, 제어문은 공식 문서 보고 키워드 사용법 제대로 이해하고 새롭게 업데이트 된 부분 공부하는 데에 초점 맞추기

1. 선택문

(1) if문

정의)
if(조건식) { ... } else if (조건식) { ... } else { ... }
Java
복사
특징)
중첩 가능

(2) switch문

정의)
switch (조건식) { case1: case2: ... break; case3: ... 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 자바 라이브러리를 사용하면 좋음.
GitHub API란
정의) GitHub가 제공하는 다양한 정보와 기능을 개발자의 응용 프로그램에서 사용할 수 있도록 제공하는 GitHub의 인터페이스
특징) 해당 라이브러리에는 GHUser(깃허브유저)GHRepository(깃허브리포지토리),GHOrganization(깃허브그룹) 등 깃허브에서 사용되는 각각의 도메인 모델들을 제어하기 위한 클래스가 존재하며, 각 클래스의 메서드를 통해서 깃허브 사이트에서 사용할 수 있는 다양한 기능을 프로그램 내에서 사용할 수 있다.
구현 과정
1.
build.gradle에 아래 코드 추가
implementation 'org.kohsuke:github-api:1.124'
Java
복사
2.
토큰 발급 & 저장
깃허브 접속을 위한 아래와 같은 방법들이 있는데 나는 두 번째 방법 사용함
환경변수로 토큰 저장
IntelliJ에서 환경 변수 설정
화면 오른쪽 상단에 뜨는 현재 실행할 클래스 이름을 오른쪽 클릭해서 구성 편집 선택
환경변수 오른쪽 끝에 네모 박스 클릭
사용자 환경변수에 + 아이콘 클릭해서 토큰값 저장
String token = System.getenv("token"); 처럼 사용할 수 있음
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
복사
참고