
Optional 객체
값이 있을 수도 있고 없을 수도 있는 컨테이너
메서드 반환 자료형에 선언해서 해당 메서드가 null을 반환될 가능성을 전달
ofNullable()을 사용하여 null을 반환될 수 있는 객체를 감싸서 사용
isPresent() 같은 API를 통해 확인하여 처리
public class Camp {
private Student student;
// null을 반환할 수 있는 메서드
public Student getStudent() {
return student;
}
// nullable check 가능하게 만드는 Optional
public Optional<Student> getStudentOptional() {
return Optional.ofNullable(student);
}
public void setStudent(Student student) {
this.student = student;
}
}
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
// null check 방법 1
Optional<Student> student = camp.getStudentOptional();
// isPresent() -> 값이 존재할 경우 true, 없으면 false
if(student.isPresent()) {
String studentName = student.get().getName();
System.out.println("studentName = " + studentName);
} else {
System.out.println("student is null");
}
// null check 방법 2 -> 생성자를 이용해서 이름에 배정해버리는 방식
// orElseGet()은 값이 없을 때만 기본값을 제공하는 로직을 실행하는 메서드
Student student2 = camp.getStudentOptional().orElseGet(() -> new Student("미등록 학생"));
System.out.println("studentName = " + student2.getName());
}
}
람다
익명 클래스를 더 간결하게 표현하는 문법
함수형 인터페이스를 통해 구현하는 것을 권장
1개의 추상 메서드만 가져야하기 때문
허나, 하나의 추상 메서드를 가진 일반 일반 인터페이스를 통해서도 가능함
public class Main { public static void main(String[] args) { // 익명 클래스 Calculator calculator = new Calculator() { @Override public int sum(int a, int b) { return a + b; } }; int result = calculator.sum(1, 2); System.out.println("result = " + result); // 람다식 Calculator calculator2 = (int a, int b) -> a + b; int result2 = calculator2.sum(1, 2); System.out.println("result2 = " + result2); // 람다식을 매개변수로 전달 -> 여하튼 Calculator로 컴파일러가 추론하기 때문에 가능 int result3 = calculate(1, 2, calculator2); System.out.println("result3 = " + result3); // 람다식을 직접 전달 int result4 = calculate(1, 2, (int a, int b) -> a + b); System.out.println("result4 = " + result4); } public static int calculate(int a, int b, Calculator calculator) { return calculator.sum(a, b); } }@FunctionalInterface public interface Calculator { int sum(int a, int b); // 하나의 추상 메서드만 선언해야함 int sum(int a, int b, int c); // Error 한개만 가져야함 }
스트림
데이터를 효율적으로 처리할 수 있는 흐름
선언형 스타일 → 가독성 좋음
스트림은 컬렉션과 자주 활용
데이터 준비 → 중간연산 → 최종연산
Stream 은 불변 변환용도
내부 요소를 변환해서 다시 적용해야지 같은 건 Stream 의 목적이 아님을 유의
단계 | 설명 | 주요 API |
1. 데이터 준비 | 컬렉션을 스트림으로 변환 |
|
2. 중간 연산 등록 | 데이터 변환 및 필터링 |
|
3. 최종 연산 | 최종 처리 및 데이터 변환 |
|
List<Integer> result6 = arrList.stream() // 1. 데이터 준비: 스트림 생성
.filter(num -> num % 2 == 0) // 2. 중간 연산: 짝수만 필터링
.map(num -> num * 10) // 3. 중간 연산: 10배로 변환
.collect(Collectors.toList()); // 4. 최종 연산: 리스트로 변환
System.out.println(result6);중간연산 정리
연산자
역할
간단한 예시
filter()조건에 맞는 요소만 남김
.filter(x -> x > 10)map()요소를 변환
.map(x -> x * 2)flatMap()중첩된 Stream 평탄화
.flatMap(List::stream)distinct()중복 제거
.distinct()sorted()정렬
.sorted()또는.sorted(Comparator)limit()앞에서 n개만 남김
.limit(5)skip()앞에서 n개 건너뜀
.skip(2)peek()중간 디버깅용
.peek(System.out::println)카테고리
주요 연산자
기능 요약
필터링
filter(),distinct()조건 맞는 요소만 남기거나 중복 제거
변환/매핑
map(),flatMap()형태를 바꾸거나 내부 컬렉션을 펼침
정렬/슬라이싱
sorted(),limit(),skip()순서 조정, 일부만 추출
디버깅용
peek()중간 과정 출력
filter() → 조건
List<Integer> result = Stream.of(5, 10, 15, 20) .filter(x -> x >= 10) .collect(Collectors.toList()); System.out.println(result); // [10, 15, 20]map() → 각 요소 변환
List<String> result = Stream.of("apple", "banana") .map(s -> s.toUpperCase()) .collect(Collectors.toList()); System.out.println(result); // [APPLE, BANANA]flatMap() → 중첩 Stream 평탄화
List<List<String>> data = List.of( List.of("A", "B"), List.of("C", "D") ); List<String> result = data.stream() .flatMap(List::stream) .collect(Collectors.toList()); System.out.println(result); // [A, B, C, D]distinct() → 중복 제거
List<Integer> result = Stream.of(1, 2, 2, 3, 3) .distinct() .collect(Collectors.toList()); System.out.println(result); // [1, 2, 3]sorted() → 정렬
List<Integer> result = Stream.of(3, 1, 2) .sorted() .collect(Collectors.toList()); System.out.println(result); // [1, 2, 3] // 또는 Comparator .sorted((a, b) -> b - a) // 내림차순limit(), skip() → 일부만 잘라내기
List<Integer> result = Stream.of(1, 2, 3, 4, 5) .skip(2) // 앞의 2개 건너뛰기 .limit(2) // 그 다음 2개만 .collect(Collectors.toList()); System.out.println(result); // [3, 4]peek() → 중간 확인용
List<Integer> result = Stream.of(1, 2, 3) .peek(x -> System.out.println("before map: " + x)) .map(x -> x * 10) .peek(x -> System.out.println("after map: " + x)) .collect(Collectors.toList());
최종연산 정리
collect() → 스트림의 요소들을 모아서하나의 결과(자료구조)로 만드는 역할
toList()
List<String> names = Stream.of("홍길동", "김철수", "이영희") .filter(n -> n.length() == 3) .collect(Collectors.toList());결과:
["홍길동", "김철수", "이영희"]toSet()
Set<Integer> nums = Stream.of(1, 2, 2, 3) .collect(Collectors.toSet());결과:
[1, 2, 3](중복 제거됨)joining(”str”);
String result = Stream.of("a", "b", "c") .collect(Collectors.joining(", "));결과:
"a, b, c"toMap(key → key, value → value)
Map<String, Integer> map = Stream.of("apple", "banana", "cherry") .collect(Collectors.toMap( s -> s, // key s -> s.length() // value ));결과:
{apple=5, banana=6, cherry=6}summingInt(number → number), averagingInt… 등등
int sum = Stream.of(1, 2, 3, 4, 5) .collect(Collectors.summingInt(i -> i));결과:
15정리
구분
설명
예시
toList()
List로 수집
.collect(Collectors.toList())toSet()
Set으로 수집
.collect(Collectors.toSet())toMap()
Map으로 수집
.collect(Collectors.toMap(...))joining()
문자열로 합치기
.collect(Collectors.joining(", "))summingInt()
합계 계산
.collect(Collectors.summingInt(...))groupingBy()
그룹화 (SQL의 GROUP BY처럼)
.collect(Collectors.groupingBy(...))forEach() → 스트림의 요소를 하나씩 실행하거나 출력할 때
Stream.of("A", "B", "C") .forEach(item -> System.out.println(item)); //---------- A B C //---------- // 자주 쓰는 패턴 list.stream() .filter(x -> x.startsWith("A")) .forEach(System.out::println);count() → 스트림의 요소 개수를 셀 때
long cnt = Stream.of("A", "BB", "CCC") .filter(s -> s.length() >= 2) .count(); System.out.println(cnt); // 2
Thread
프로그램 내 독립적으로 실행되는 하나의 작업 단위
싱글/멀티로 구현 가능
Runnable
Thread의 구현을 담당하는 인터페이스
왜 사용?
Thread는 쓰레드를 제어하기 위해 존재함
내부 실행 로직도 구현 가능하나 내부 실행 로직마저도 확장받아 구현할 경우 재사용성과 타 클래스의 메서드를 사용할 수 있는 확장의 가능성이 없어짐
public class MyRunnable implements Runnable extends anotherClass { // 확장 가능
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "- started");
for (int i = 0; i < 10; i++) {
System.out.println(threadName + "-" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(threadName + "- finished");
}
}
public class MyThread extends Thread { // -> Thread를 상속받아야 하므로 확장 가능성 없어짐
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "Thread Start");
for (int i = 0; i < 10; i++) {
System.out.println(threadName + "Thread Started" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(threadName + "Thread End");
}
}
public class Main {
public static void main(String[] args) {
System.out.println("main Thread Start");
MyThread myThread = new MyThread();
MyThread myThread2 = new MyThread();
long startTime = System.currentTimeMillis();
System.out.println("main Thread - myThread Start");
myThread.start();
System.out.println("main Thread - myThread2 Start");
myThread2.start();
// join을 통해 두 쓰레드가 끝나기 전까지 타 쓰레드를 기다리게 만듬
try {
myThread.join();
myThread2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
System.out.println("process time: " + (endTime - startTime)); // join이 없으면 바로 종료될 수 있음
System.out.println("main Thread End");
//---------------------------------------------------
// Runnable 사용
System.out.println("main Thread Start");
MyRunnable myRunnable = new MyRunnable();
myRunnable.executedAnotherMethod(); // Runnable 클래스에서 확장받아온 메서드
Thread myThread3 = new Thread(myRunnable);
Thread myThread4 = new Thread(myRunnable);
long startTime2 = System.currentTimeMillis();
System.out.println("main Thread - myThread3 Start");
myThread3.start();
System.out.println("main Thread - myThread4 Start");
myThread4.start();
// join을 통해 두 쓰레드가 끝나기 전까지 타 쓰레드를 기다리게 만듬
try {
myThread3.join();
myThread4.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long endTime2 = System.currentTimeMillis();
System.out.println("process time: " + (endTime2 - startTime2)); // join이 없으면 바로 종료될 수 있음
System.out.println("main Thread End");
}
}