Today's 코드카타
class Solution {
public int solution(int num1, int num2) {
if (num1 < 0 || num1 > 10000 || num2 < 0 || num2 > 10000) {
throw new IllegalArgumentException("입력값은 0 이상 10,000 이하여야 합니다.");
}
if (num1 == num2) {
return 1;
} else {
return -1;
}
}
}
같으면을 먼저 비교하면 간단했는데, 같지 않으면을 조건으로 먼저 설정하려니 살짝 헤맸다.
int answer = (num1 == num2) ? 1 : -1;
return answer;
다른 사람들 풀이를 통해 삼항 연산자 라는 걸 알게 되었다!
class Solution {
public int solution(int num1, int num2) {
boolean isInRange = (-50000<=num1 && num1<=50000 && -50000<=num2 && num2 <=50000);
int answer = 0;
if (isInRange) {
answer = num1 + num2;
}
return answer;
}
}
boolean 으로 변수를 만들고 true일 때 if문을 실행하는 방식으로 짜봤다.
변수명을 누가 봐도 이해하기 쉽게 잘 만들고 싶은데, 영어 공부를 해야하나...
class Solution {
public int solution(int num1, int num2) {
int answer = 0;
if (0 < num1 && num1 <= 100 && 0 < num2 && num2 <= 100) {
answer = (int)(((double) num1 / num2) * 1000);
}
return answer;
}
}
형변환(Type Casting)을 이용하는 방법을 배웠다.
처음에 위의 num1,2 변수를 double로도 바꿔보고 해봤는데, 형변환을 이용하니 간단하다.
return num1 * 1000 / num2; 다른 사람들의 풀이를 보며 배운다. 이건 생각도 못 했다...
Java 문법 3주차 마무리!
람다(Lambda)
- 익명클래스: 별도의 클래스 파일을 만들지 않고 코드 내에서 일회성으로 정의해 사용하기에 익명 클래스라 부른다!
- 람다: 익명 클래스를 더 간결하게 표현하는 문법 / 함수형 인터페이스를 통해 구현하는 것을 권장
※주의사항: 람다식에선 함수형 인터페이스를 활용하는데, 두 개 이상의 추상 메서드가 존재하면 컴파일러가 어떤 메서드를
구현하는지 모호해짐으로 오직 하나의 추상 메서드만 선언해야한다.
- 오버로딩: 같은 클래스나 인터페이스 내에서 동일한 메서드 이름을 사용해서 선언하는 기능이다.
→ 매개변수의 개수나 타입, 순서는 다르게 선언해야 한다.
- 오버라이딩: 부모 클래스에 정의된 메서드를 자식 클래스에서 재정의하는 것을 의미한다.
-- Main --
// 람다식을 매개변수로 전달하는 방법
// 1. 익명클래스를 변수에 담아 매개변수로 전달
Calculator cal1 = new Calculator() {
@Override
public int sum(int a, int b) {
return a + b;
}
};
int re3 = calculate(3, 3, cal1);
System.out.println("re3 = " + re3);
// 2. 람다식을 변수에 담아 매개변수로 전달
Calculator cal2 = (a, b) -> a + b;
int ret4 = calculate(4, 4, cal2);
System.out.println("ret4 = " + ret4);
// 3. 람다식을 직접 매개변수로 전달
int ret5 = calculate(5, 5, (a, b) -> a + b);
System.out.println("ret5 = " + ret5);
// 함수형 인터페이스를 매개변수로 받아서 호출
public static int calculate(int a, int b, Calculator calculator) {
return calculator.sum(a, b);
}
-- Calculator --
@FunctionalInterface // 함수형 인터페이스
public interface Calculator {
int sum(int a, int b); // 오직 하나의 추상 메서드만 가져야한다
}
스트림(Stream)
- 스트림: 데이터를 효율적으로 처리할 수 있는 흐름 / 데이터 준비 → 중간 연산 → 최종 연산
- map: 각 요소를 변환하는 중간 연산 중 하나
- filter: 조건에 맞는 요소만 걸러내는 중간 연산 중 하나
ArrayList<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
// for 명령형 스타일: 각 요소 * 처리
ArrayList<Integer> ret1 = new ArrayList<>();
for (Integer num : arrayList) {
Integer multipliedNum = num * 10;
ret1.add(multipliedNum);
}
System.out.println("ret1 = " + ret1);
// stream 선언형 스타일: 각 요소 * 10 처리
List<Integer> ret2 = arrayList.stream() // 데이터 준비 : stream(흐름)을 생성
.map(num -> num * 10) // 중간 연산 등록 : 각 요소를 10로 변환하는 로직 등록
.collect(Collectors.toList()); // 최종 연산 : 최종 결과 리스트로 변환
System.out.println("ret2 = " + ret2);
// 1. 익명클래스를 직접 만들어서 변수에 담아 매개변수로 전달
Function<Integer, Integer> function = new Function<>() {
@Override
public Integer apply(Integer integer) {
return integer * 10;
}
};
List<Integer> ret3 = arrayList.stream()
.map(function)
.collect(Collectors.toList());
System.out.println("ret3 = " + ret3);
// 2. 람다식을 만들어서 변수에 담아 매개변수로 전달
Function<Integer, Integer> functionLambda = (integer -> integer * 10);
List<Integer> ret4 = arrayList.stream()
.map(functionLambda)
.collect(Collectors.toList());
System.out.println("ret4 = " + ret4);
// 3. 람다식을 직접 매개변수로 전달
List<Integer> ret5 = arrayList.stream()
.map(num -> num * 10)
.collect(Collectors.toList());
System.out.println("ret5 = " + ret5);
// 중간 연산을 함께 사용하는 방법 (filter() + map())
// 요구사항: 리스트에서 짝수만 찾아서 * 10
List<Integer> ret6 = arrayList.stream() // 1. 데이터 흐름 준비 (1, 2, 3, 4, 5)
.filter(num -> num % 2 == 0) // 2. 중간 연산: 짝수 필터링
.map(num -> num * 10) // 3. 중간 연산: 10배
.collect(Collectors.toList());
System.out.println("ret6 = " + ret6);
쓰레드(Thread)
- 쓰레드: 프로그램 내에서 독립적으로 실행되는 하나의 작업 단위
- 싱글 쓰레드: 한 명의 일꾼이 작업 → 순차적 처리
- 멀티 쓰레드: 여러 명의 일꾼이 작업 → 동시 처리
- join(): 쓰레드가 다른 쓰레드가 종료될 때까지 기다리게 하는 메서드
-- Main --
System.out.println("::: main 쓰레드 시작");
MyThread thread0 = new MyThread();
MyThread thread1 = new MyThread();
long startTime = System.currentTimeMillis(); // 시작시간
// 1. thread0 시작
System.out.println("thread0 시작");
thread0.start(); // run()을 직접 호출하면 현재 쓰레드에서 실행됨
// 2. thread1 시작
System.out.println("thread1 시작");
thread1.start();
// main 쓰레드를 대기 시키기
try {
thread0.join(); // thread0이 끝날 때까지 기다려라
thread1.join();
} catch (InterruptedException ee){
ee.printStackTrace();
}
// join을 활용해서 소요 시간도 확인해볼 수 있다
long endTime = System.currentTimeMillis(); // 종료시간
long totalTime = endTime = startTime; // 시작 - 종료
System.out.println("작업 소요 시간: " + totalTime + "ms");
System.out.println("::: main 쓰레드 종료");
-- console --
::: main 쓰레드 시작 // 관리자처럼 새로운 쓰레드 생성 업무를 하고 업무 지시
thread0 시작
thread1 시작
::: main 쓰레드 종료
현재 시작된 쓰레드: Thread-0
현재 시작된 쓰레드: Thread-1
...
종료된 쓰레드: Thread-0
종료된 쓰레드: Thread-1
::: main 쓰레드 종료 // join을 활용하면 마지막에 종료되게 할 수 있다!
- Runnable 인터페이스: 유지 보수성과 재사용성 향상 (실행 로직을 별도의 구현체로 분리할 수 있다)
확장 가능성 (클래스는 다중 상속이 불가능하지만 인터페이스이므로 가능)
extends: 상속하다 / implements: 구현하다
-- Runnable --
public class MyRunnable extends MyNewClass implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("현재 시작된 쓰레드: " + threadName);
for (int i = 0; i < 10; i++) {
System.out.println("현재 쓰레드: " + threadName + " - " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("종료된 쓰레드: " + threadName);
}
}
-- Main --
MyRunnable myTask = new MyRunnable();
myTask.printMessage(); // 기능을 확장해서 사용!
Thread thread0 = new Thread(myTask); // 하나의 작업을 여러 쓰레드에서 공유
Thread thread1 = new Thread(myTask);
thread0.start();
thread1.start();
Calculator 만들기 2단계
// 첫 번째 숫자
int num1 = 0;
while (true) {
System.out.print("첫 번째 숫자를 입력하세요: ");
try {
num1 = scanner.nextInt();
if (num1 < 0) { // 음수인 경우
System.out.println("0를 포함한 양의 정수만 입력이 가능합니다.");
continue; // 다시 입력 받기
}
break; // 음수도 아니고 예외도 없다면 루프 탈출!
} catch (InputMismatchException e) { // 입력값이 숫자가 아닌 경우
System.out.println("숫자가 아닌 값을 입력하였습니다. 다시 입력해주세요!");
scanner.next(); // 남아 있는 잘못된 입력값을 버퍼에서 제거
}
}
이 파트를 꽤 붙잡고 있었는데, 두번 반복되니까 따로 메서드로 만들 수 있지않을까 싶다. 내일 도전!
scanner.next(); 는 없으니까 예외 발생 시 스캐너가 끝없이 출력돼서 ai통해서 물어보고 넣었는데,
잘못 입력한 값이 남아있어서 그 값을 제거해주기 위해 넣는다는 것까진 이해했으나 저 코드를 넣어서
제거가 되는 이유? 순서?는 이해가 아직 안 된다. 공부가 필요하다.
2단계 가이드 첫 번째 조건에 '사칙연산을 수행한 후, 결과값을 반환하는 메서드 구현' 부분은 1단계에서 main 메서드 안에 이미 가이드를 따라 만들어놓기도 했고, 강의를 들으며 어느 정도 main 메서드와 class와의 구조에 대한 감을 잡았다고 생각했는데, 막상 class에 작성 후 호출해서 사용하려면 어떻게 해야하지? 라고 생각해봤을 때 한참동안 머리가 좀 고장난 기분이었다. 또 컬렉션을 활용하는 것도 일단 순서대로 저장해야할테고 각각의 계산에 대한 결과값이니 중복도 문제없겠다! 생각해서 ArrayList를 써야겠구나하는 생각은 바로 들었는데, List<Integer> results = new ArrayList<>(); 이렇게 변수만 선언해놓고 뭘 어디다 적어야하는지 연결이 어려웠다. 그래도 내가 정리해놨던 내용들을 다시 읽어보고 강의자료도 한번 더 살펴보며 이런저런 시도들을 하다보니 조금조금씩 가닥이 잡혔다. 그리고 1단계에서 많은 코드를 작성했던 것도 아닌데, 썼던 이유가 떠오르지 않아서 다시 코드 하나하나를 보면서 왜 이 코드를 작성했는지 제대로 이해하고 넘어가려고 했던 것 같다. 나 자신한테 '이 코드 왜 썼니?' 이렇게 묻는 듯한 느낌이었다. 2단계 중후반부 쯤에 '간접 접근을 통해 필드에 접근하여 수정할 수 있도록 구현합니다.' 이 부분은 그래도 스스로 게터, 세터 메서드에 대한 부분은 어느정도 이해했다고 생각해서 어떤 식으로 쓰면 되겠다는 생각은 있었는데, '내가 입력한 값에 따라 결과가 나왔는데, 굳이 수정을 다시 할 게 뭐가 있지?' 이런 생각이 들면서 사고가 굳어지는 느낌을 받았다. 그런 나를 보면서 개발자는 사고의 유연성도 중요한 부분이라는 생각을 했다. 어렵기도 했지만 더 나은 구조를 위해 끊임없이 고민하게 되는 시간이었다.
오늘의 회고
오늘도 느낀거지만 듣고 대략 이해한 것과 직접 활용해보는 것은 완전히 다른 차원이다. 프로그래밍 언어라는 것은 사람이 입을 통해 소통하는 것과는 달라보이지만 자꾸 말해보고 반복해서 그 언어를 사용할수록 그 언어와 가까워진다는 점에서는 공통점이 있는 듯하다. 계속 가까워져서 얼른 둘도 없는 친구 같이 되고 싶은 마음이다. 아직... 너무 어색하다. 그렇지만 막막함을 느끼며 받는 스트레스보다 이것저것 구현해보고 싶다는 마음이 커서 이 시간이 기꺼웠다. 물론 이 다음 도전해볼 3단계는 괜시리 두렵기도 하다. 한 단계 한 단계 난이도가 상당히 올라가는 느낌이다. 그래도 자꾸 나를 어려운 환경으로 밀어넣는게 나의 성장을 위하는 일이라는 걸 알고있기에 좀 오글거리지만 오늘의 나를 칭찬하며 내일의 나를 응원해본다. 내일은 java랑 더 친해지기를.