이 글은 자바의 정석 3판을 정리한 내용입니다.
자바의 정석 3판
✔ 스레드(Thread)
1. 프로세스와 스레드(Process & Thread)
- 프로세스 : 실행 중인 프로그램, 자원(resources)[메모리, cpu, ...]과 스레드로 구성
- 스레드 : 프로세스 내에서 실제 작업을 수행. 모든 프로세스는 최소한 하나의 스레드를 가지고 있다
2. 멀티 스레드 장단점
장점
- 시스템 자원을 보다 효율적으로 사용할 수 있다
- 사용자에 대한 응답성이 향상
- 작업이 분리되어 코드가 간결
단점
- 동기화(synchronization)에 주의
- 교착상태(dead-lock)가 발생하지 않도록 주의
- 각 스레드가 효율적으로 고르게 실행될 수 있게 해야 한다
- 프로그래밍할 때 고려해야 할 사항들이 많다
3. 스레드의 구현과 실행
Thread 클래스를 상속
class MyThread extends Thread {
public void run() {}
}
MyThread t = new MyThread();
t.start();
Runnable 인터페이스를 구현
class MyThread implements Runnable {
public void run() {}
}
Runnable r = new MyThread();
Thread t = new Thread(r);
// Thread t = new Thread(new MyThread()); 위의 2줄 간략히
t.start();
4. 스레드의 I/O 블로킹(blocking)
입출력 작업, 네트워크로 파일을 주고받는 작업 등으로 인한 프로그램이 잠시 멈춘 상황
교착상태(dead-lock) : 두 스레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰 있는 상태
5. 스레드 그룹
- 서로 관련된 스레드를 그룹으로 묶어서 다루기 위한 것
- 모든 스레드는 반드시 하나의 스레드 그룹에 포함되어 있어야 한다
- 스레드 그룹을 지정하지 않고 생성한 스레드는 main 스레드 그룹에 속한다
- 자신을 생성한 스레드(부모 스레드)의 그룹과 우선순위를 상속받는다
6. 데몬 스레드(Daemon Thread)
- 일반 스레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행
- 일반 스레드가 모두 종료되면 자동적으로 종료된다
- 가비지 컬렉터(GC), 워드프로세서의 자동 저장, 화면 자동 갱신 등에 사용된다
- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성
// 스레드를 데몬 스레드로 또는 사용자 스레드로 변경한다
// 매개변수 on의 값을 true로 지정하면 데몬 스레드가 된다
void setDaemon(boolean on)
Thread t = new Thread();
t.setDaemon(true);
t.start();
7. 스레드의 실행제어
메서드 | 설 명 |
static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간동안 스레드를 일시정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다. |
void join() void join(long millis) void join(long millis, int nanos) |
지정된 시간동안 스레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 스레드로 다시 돌아와 실행을 계속한다. |
void interrupt() | sleep(), join()에 의해 일시정지 상태인 스레드를 깨워서 실행대기 상태로 만든다. 해당 스레드에서는 'InterruptedException'이 발생함으로써 일시정지 상태를 벗어나게 된다. |
static void yield() | 실행 중에 자신에게 주어진 실행시간을 다른 스레드에게 양보(yield)하고 자신은 실행대기 상태가 된다. |
stop(), suspend(), resume() 메서드는 스레드를 교착상태(dead-lock)로 만들기 쉽기 때문에 deprecated 되었다.
8. 스레드의 상태
상 태 | 설 명 |
NEW | 스레드가 생성되고 아직 start()가 호출되지 않은 상태 |
RUNNABLE | 실행 중 또는 실행 가능한 상태 |
BLOCKED | 동기화블록에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) |
WAITING, TIMED_WAITING |
스레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태. TIMED_WAITING은 일시정지 시간이 지정된 경우를 의미한다. |
TERMINATED | 스레드의 작업이 종료된 상태 |
9. 스레드의 동기화(Synchronization)
공유 데이터를 사용하는 코드 영역을 임계 영역(critical section)으로 지정해 놓고, 공유 데이터(객체)가 가지고 있는 잠금(lock)을 획득한 단 하나의 스레드만 이 영역 내의 코드를 수행할 수 있게 한다.
그리고 해당 스레드가 임계 영역 내의 모든 코드를 수행하고 lock을 반납해야만 다른 스레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있다.
- 멀티 스레드 프로세스에서는 다른 스레드의 작업에 영향을 미칠 수 있다
- 진행 중인 작업이 다른 스레드에게 간섭받지 않게 하려면 '동기화'가 필요하다
- 스레드의 동기화 : 한 스레드가 진행 중인 작업을 다른 스레드가 간섭하지 못하게 막는 것
- 동기화하려면 간섭받지 않아야 하는 문장들을 '임계 영역'으로 설정
- 임계 영역은 락(lock)을 얻은 단 하나의 스레드만 출입 가능(객체 1개에 락 1개)
10. synchronized를 이용한 동기화
synchronized로 임계 영역(lock이 걸리는 영역)을 설정하는 방법 2가지(임계 영역은 최소화하는 게 좋다)
// 1. 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum() { // 임계 영역(critical section) 시작
...
} // 임계 영역(critical section) 끝
// 2. 특정한 영역을 임계 영역으로 지정
synchronized(객체의 참조변수) { // 임계 영역(critical section) 시작
...
} // 임계 영역(critical section) 끝
첫 번째 방법은 스레드는 synchronized 메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 락(lock)을 얻어 작업을 수행하다가 메서드가 종료되면 락(lock)을 반환한다.
두 번째 방법은 메서드 내의 코드 일부를 블록{}으로 감싼다. 객체의 참조변수는 락(lock)을 걸고자 하는 객체를 참조하는 것이어야 한다. 블록의 영역 안으로 들어가면서부터 스레드는 지정된 객체의 락(lock)을 얻게 되고, 블록을 벗어나면 락(lock)을 반납한다.
두 방법 모두 락(lock)의 획득, 반납이 자동적으로 이루어지므로 임계 영역만 잘 설정해 주면 된다.
임계 영역은 멀티스레드 프로그램의 성능을 좌우하기 때문에 가능하면 메서드 전체에 락(lock)을 거는 것보다 특정한 영역을 임계 영역으로 최소화해서 보다 효율적인 프로그램이 되도록 해야 한다.
// 1. 메서드 전체를 임계 영역으로 지정
public synchronized void withdraw(int money) {
if (balance >= money) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
balance -= money;
}
}
// 2. 특정한 영역을 임계 영역으로 지정
public void withdraw(int money) {
synchronized(this) {
if (balance >= money) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
balance -= money;
}
}
}
11. wait()과 notify()
- 동기화의 효율을 높이기 위해 wait(), notify()를 사용
- Object클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할 수 있다
- wait() : 객체의 락(lock)을 풀고, 스레드를 해당 객체의 waiting pool에 넣는다
- notify() : waiting pool에서 대기 중인 스레드 중의 하나를 깨운다
- notifyAll() : waiting pool에서 대기 중인 모든 스레드를 깨운다
12. Lock과 Condition을 이용한 동기화
- ReentrantLock 클래스 : 재진입이 가능한 락(lock), 가장 일반적인 배타 락(lock)
- ReentrantReadWriteLock 클래스 : 읽기에는 공유적이고, 쓰기에는 배타적인 락(lock)
- StampedLock 클래스 : ReentrantReadWriteLock에 낙관적인 락(lock)의 기능을 추가
- Condition 클래스 : 공유 객체의 waiting pool이 아닌 각각의 waiting pool을 사용하도록 만든다
✔ 람다(Lambda)
1. 람다식(Lambda Expression)
함수(메서드)를 간단히 '식(expression)'으로 표현하는 방법
람다식 작성하기 (규칙)
1. 메서드의 이름과 반환타입은 제거하고 '→'를 블록{} 앞에 추가한다
int max(int a, int b) {
return a > b ? a : b;
}
// 위의 코드를 람다식으로 표현하면 아래와 같다
(int a, int b) -> {
return a > b ? a : b;
}
2. 반환값이 있는 경우, 식이나 값만 적고 return문 생략 가능(끝에 ';' 안 붙임)
int max(int a, int b) {
return a > b ? a : b;
}
// 위의 코드를 람다식으로 표현하면 아래와 같다
(int a, int b) -> a > b ? a : b
3. 매개변수의 타입이 추론 가능하면 생략 가능(대부분의 경우 생략 가능)
int max(int a, int b) {
return a > b ? a : b;
}
// 위의 코드를 람다식으로 표현하면 아래와 같다
(a, b) -> a > b ? a : b
람다식 작성하기 (주의 사항)
1. 매개변수가 하나인 경우, 괄호() 생략 가능(타입이 없을 때만)
// 1.
(a) -> a * a // OK
// 2.
(int a) -> a * a // OK
// 1번을 괄호 생략
a -> a * a // OK 괄호() 생략 가능
// 2번을 괄호 생략
int a -> a * a // Error 괄호() 생략 불가
2. 블록 안의 문장이 하나뿐일 때, 괄호{} 생략 가능
(int i) -> {
System.out.println(i);
}
// 위의 괄호{} 생략 가능
(int i) -> System.out.println(i)
2. 함수형 인터페이스
단 하나의 추상 메서드만 선언된 인터페이스
@FunctionalInterface
interface MyFunction {
int max(int a, int b); // public abstract 생략
}
MyFunction f = new MyFunction() {
public int max(int a, int b) {
return a > b ? a : b;
}
};
함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있다.(단, 함수형 인터페이스의 메서드와 람다식의 매개변수 개수와 반환타입이 일치해야 한다)
MyFunction f = (a, b) -> a > b ? a : b;
int value = f.max(3, 5); // 실제로는 람다식(익명 함수)이 호출됨
3. java.util.function 패키지
가장 기본적인 함수형 인터페이스
매개변수가 두 개인 함수형 인터페이스
매개변수의 타입과 반환타입의 타입이 모두 일치하는 함수형 인터페이스
4. 메서드 참조(Method Reference)
하나의 메서드만 호출하는 람다식은 '메서드 참조'로 간단히 할 수 있다.
"클래스이름::메서드이름"
종 류 | 람다식 | 메서드 참조 |
static 메서드 참조 | (x) -> ClassName.method(x) | ClassName::method |
인스턴스 메서드 참조 | (obj, x) -> obj.method(x) | ClassName::method |
특정 객체 인스턴스 메서드 참조 | (x) -> obj.method(x) | obj::method |
// <String, Integer> -> String : 입력, Integer : 출력
Function<String, Integer> f = (String s) -> Integer.parseInt(s);
// 위의 람다식을 메서드 참조로 변경
// 입력 값이 어떤 건지 알기 때문에 매개변수가 불필요
Function<String, Integer> f = Integer::parseInt; // 메서드 참조
생성자의 메서드 참조
Supplier<MyClass> s = () -> new MyClass();
// 위의 람다식을 메서드 참조로 변경
Supplier<MyClass> s = MyClass::new;
Function<Integer, MyClass> s = i -> new MyClass(i);
// 위의 람다식을 메서드 참조로 변경
Function<Integer, MyClass> s = MyClass::new;
배열과 메서드 참조
Function<Integer, int[]> f = x -> new int[x];
// 위의 람다식을 메서드 참조로 변경
Function<Integer, int[]> f = int[]::new;
✔ 스트림(Stream)
다양한 데이터 소스(컬렉션, 배열)를 표준화된 방법으로 다루기 위한 것이다.
스트림 만들기 → 중간 연산(0 ~ n번) → 최종 연산(1번)
- 중간 연산 : 연산 결과가 스트림인 연산. 반복적으로 적용 가능
- 최종 연산 : 연산 결과가 스트림이 아닌 연산. 단 한 번만 적용 가능(스트림의 요소를 소모)
1. 스트림의 특징
1. 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않는다.
List<Integer> list = Arrays.asList(3, 1, 5, 4, 2);
List<Integer> sortedList = list.stream().sorted().collect(Collectors.toList());
System.out.println(list); // [3, 1, 5, 4, 2]
System.out.println(sortedList); // [1, 2, 3, 4, 5]
2. 스트림은 Iterator처럼 일회용이다.(필요하면 다시 스트림을 생성해야 한다)
strStream.forEach(System.out::println); // forEach 최종 연산
int numOfStr = strStream.count(); // Error. 스트림이 이미 닫힌 상태
3. 최종 연산 전까지 중간 연산이 수행되지 않는다.(지연된 연산)
IntStream intStream = new Random().ints(1, 46); // 1~45 범위의 무한스트림
intStream
.distinct() // 중간 연산
.limit(6) // 중간 연산
.sorted() // 중간 연산
.forEach(i -> System.out.println(i + ",")); // 최종 연산
기본형 스트림
IntStream, LongStream, DoubleStream 등이 있다.
스트림의 작업을 병렬로 처리
병렬스트림을 사용. parallel()로 병렬 처리할 수 있다.
2. Optional<T>
지네릭 클래스로 'T타입의 객체'를 감싸는 래퍼 클래스이다. 그래서 Optional타입의 객체에는 모든 타입의 참조변수를 담을 수 있다.(JDK1.8부터 추가)
public final class Optional<T> {
private final T value; // T타입의 참조변수
}
T타입의 객체의 래퍼 클래스(간접적으로 null 다루기)
- null을 직접 다루는 것은 위험(NullPointerException)
- null 체크. if문 필수(코드가 지저분 해짐)
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get().orElse(""); // 값 반환. null일 때는 ""를 반환
String str2 = optVal.orElseGet(String::new); // () -> new String()와 동일
String str3 = optVal.orElseThrow(NullPointerException::new); // null이면 예외발생
3. 스트림의 최종 연산
스트림의 최종 연산 중 하나인 collect()
- collect() : 스트림의 최종 연산, 매개변수로 컬렉터를 필요로 한다
- Collector : 인터페이스. 컬렉터는 이 인터페이스를 구현해야 한다
- Collectors : 클래스. static 메서드로 미리 작성된 컬렉터를 제공한다
counting(), summingInt(), averagingInt(), maxBy(), minBy()
long count = stuStream.count();
long count = stuStream.collect(Collectors.counting());
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum();
long totalScore = stuStream.collect(Collectors.summingInt(Student::getTotalScore));
OptionalInt topScore = stuStream.mapToInt(Student::getTotalScore).max();
Optional<Student> topScore = stuStream.max(
Comparator.comparingInt(Student::getTotalScore));
Optional<Student> topScore = stuStream.collect(
Collectors.maxBy(Comparator.comparingInt(Student::getTotalScore)));
// 전체 통계 정보 getCount(), getSum(), getMax(), getMin(), getAverage()
IntSummaryStatistics stat = stuStream.mapToInt(Student::getTotalScore).summaryStatistics();
IntSummaryStatistics stat = stuStream.collect(
Collectors.summarizingInt(Student::getTotalScore));
reducing()
IntStream에는 매개변수 3개짜리 collect()만 정의되어 있으므로 boxed()를 통해 IntStream을 Stream<Integer>로 변환해야 매개변수 1개짜리 collect()를 쓸 수 있다.
IntStream intStream = new Random().ints(1, 46).distinct().limit(6);
OptionalInt max = intStream.reduce(Integer::max);
Optional<Integer> max = intStream.boxed()
.collect(Collectors.reducing(Integer::max));
long sum = intStram.reduce(0, (a, b) -> a + b);
long sum = intStram.boxed()
.collect(Collectors.reducing(0, (a, b) -> a + b));
int grandTotal = stuStream.map(Student::getTotalScore).reduce(0, Integer::sum);
int grandTotal = stuStream.collect(
Collectors.reducing(0, Student::getTotalScore, Integer::sum));
그룹화와 분할 - groupingBy(), partitioningBy()
그룹화는 스트림의 요소를 특정 기준으로 그룹화하는 것을 의미, 분할은 스트림의 요소를 두 가지, 지정된 조건에 일치하는 그룹과 일치하지 않는 그룹으로의 분할을 의미한다. groupingBy()는 스트림의 요소를 Function으로, partitioningBy()는 Predicate로 분류한다.
Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)
ex) 예시에 사용될 Student클래스 & 데이터
class Student {
String name;
boolean isMale;
int hak;
int ban;
int score;
Student(String name, boolean isMale, int hak, int ban, int score) {
this.name = name;
this.isMale = isMale;
this.hak = hak;
this.ban = ban;
this.score = score;
}
String getName() { return name; }
boolean isMale() { return isMale; }
int getHak() { return hak; }
int getBan() { return ban; }
int getScore() { return score; }
@Override
public String toString() {
return String.format("[%s, %s, %d학년 %d반, %3d점]", name, isMale ? "남" : "여", hak, ban, score);
}
enum Level { HIGH, MID, LOW }
}
Student[] stuArr = {
new Student("나자바", true, 1, 1, 300),
new Student("김지미", false, 1, 1, 250),
new Student("김자바", true, 1, 1, 200),
new Student("이지미", false, 1, 2, 150),
new Student("남자바", true, 1, 2, 100),
new Student("안지미", false, 1, 2, 50),
new Student("황지미", false, 1, 3, 100),
new Student("강지미", false, 1, 3, 150),
new Student("이자바", true, 1, 3, 200),
new Student("나자바", true, 2, 1, 300),
new Student("김지미", false, 2, 1, 250),
new Student("김자바", true, 2, 1, 200),
new Student("이지미", false, 2, 2, 150),
new Student("남자바", true, 2, 2, 100),
new Student("안지미", false, 2, 2, 50),
new Student("황지미", false, 2, 3, 100),
new Student("강지미", false, 2, 3, 150),
new Student("이자바", true, 2, 3, 200)
};
partitioningBy()
- 기본 분할
Map<Boolean, List<Student>> stuBySex = Stream.of(stuArr)
.collect(Collectors.partitioningBy(Student::isMale)); // 학생들을 성별로 분할
List<Student> maleStudent = stuBySex.get(true); // Map에서 남학생 목록을 얻는다
List<Student> femaleStudent = stuBySex.get(false); // Map에서 여학생 목록을 얻는다
- 기본 분할 + 통계 정보
Map<Boolean, Long> stuNumBySex = Stream.of(stuArr)
.collect(Collectors.partitioningBy(Student::isMale, Collectors.counting()));
System.out.println("남학생 수:" + stuNumBySex.get(true)); // 남학생 수:8
System.out.println("여학생 수:" + stuNumBySex.get(false)); // 여학생 수:10
Map<Boolean, Student> topScoreBySex = Stream.of(stuArr)
.collect(Collectors.partitioningBy(Student::isMale,
Collectors.collectAndThen(
Collectors.maxBy(Comparator.comparingInt(Student::getScore)),
Optional::get
)
));
System.out.println("남학생 1등:" + topScoreBySex.get(true)); // 남학생 1등:[나자바, 남, 1, 1, 300]
System.out.println("여학생 1등:" + topScoreBySex.get(false)); // 여학생 1등:[김지미, 여, 1, 1, 250]
- 이중 분할
// 성적이 150점 아래인 학생들을 성별로 분류(불합격 목록)
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = Stream.of(stuArr)
.collect(Collectors.partitioningBy(Student::isMale,
Collectors.partitioningBy(s -> s.getScore < 150)));
List<Student> failedMaleStu = failedStuBySex.get(true).get(true); // 남학생 중 불합격
List<Student> failedFemaleStu = failedStuBySex.get(false).get(true); // 여학생 중 불합격
groupingBy()
- 기본 그룹화
Map<Integer, List<Student>> stuByBan = Stream.of(stuArr)
.collect(Collectors.groupingBy(Student::getBan)); // toList() 생략됨
// 위와 동일
Map<Integer, List<Student>> stuByBan = Stream.of(stuArr)
.collect(Collectors.groupingBy(Student::getBan, Collectors.toList()));
Map<Integer, Set<Student>> stuByBan = Stream.of(stuArr)
.collect(Collectors.groupingBy(Student::getBan,
Collectors.toCollection(HashSet::new)));
// 성적별 그룹화
Map<Student.Level, List<Student>> stuByLevel = Stream.of(stuArr)
.collect(Collectors.groupingBy(s -> {
if (s.getScore() >= 200) return Student.Level.HIGH;
else if (s.getScore() >= 100) return Student.Level.MID;
else return Student.Level.LOW;
}));
- 기본 그룹화 + 통계 정보
Map<Student.Level, Long> stuCntByLevel = Stream.of(stuArr)
.collect(Collectors.groupingBy(s -> {
if (s.getScore() >= 200) return Student.Level.HIGH;
else if (s.getScore() >= 100) return Student.Level.MID;
else return Student.Level.LOW;
}, Collectors.counting())); // HIGH:8, MID:8, LOW:2
- 다중 그룹화
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = Stream.of(stuArr)
.collect(Collectors.groupingBy(Student::getHak,
Collectors.groupingBy(Student::getBan)));
- 다중 그룹화 + 통계 정보
Map<Integer, Map<Integer, Student>> topStuByHakAndBan = Stream.of(stuArr)
.collect(Collectors.groupingBy(Student::getHak,
Collectors.groupingBy(Student::getBan,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingInt(Student::getScore)),
Optional::get
)
));
4. 스트림의 변환
from | to | 변환 메서드 |
1. 스트림 → 기본형 스트림 | ||
Stream<T> | IntStream LongStream DoubleStream |
mapToInt(ToIntFunction<T> mapper) mapToLong(ToLongFunction<T> mapper) mapToDouble(ToDoubleFunction<T> mapper) |
2. 기본형 스트림 → 스트림 | ||
IntStream LongStream DoubleStream |
Stream<Integer> Stream<Long> Stream<Double> |
boxed() |
Stream<U> | mapToObj(DoubleFunction<U> mapper) | |
3. 기본형 스트림 → 기본형 스트림 | ||
IntStream LongStream DoubleStream |
LongStream DoubleStream |
asLongStream() asDoubleStream() |
4. 스트림 → 부분 스트림 | ||
Stream<T> IntStream |
Stream<T> IntStream |
skip(long n) limit(long maxSize) |
5. 두 개의 스트림 → 스트림 | ||
Stream<T>, Stream<T> | Stream<T> | concat(Stream<T> a, Stream<T> b) |
IntStream, IntStream | IntStream | concat(IntStream a, IntStream b) |
LongStream, LongStream | LongStream | concat(LongStream a, LongStream b) |
DoubleStream, DoubleStream | DoubleStream | concat(DoubleStream a, DoubleStream b) |
6. 스트림의 스트림 → 스트림 | ||
Stream<Stream<T>> | Stream<T> | flatMap(Function mapper) |
Stream<IntStream> | IntStream | flatMapToInt(Function mapper) |
Stream<LongStream> | LongStream | flatMapToLong(Function mapper) |
Stream<DoubleStream> | DoubleStream | flatMapToDouble(Function mapper) |
7. 스트림 ←→ 병렬 스트림 | ||
Stream<T> IntStream LongStream DoubleStream |
Stream<T> IntStream LongStream DoubleStream |
parallel() // 스트림 → 병렬 스트림 sequential() // 병렬 스트림 → 스트림 |
8. 스트림 → 컬렉션 | ||
Stream<T> IntStream LongStream DoubleStream |
Collection<T> | collect(Collectors.toCollection(Supplier factory)) |
List<T> | collect(Collectors.toList()) | |
Set<T> | collect(Collectors.toSet()) | |
9. 컬렉션 → 스트림 | ||
Collection<T> List<T> Set<T> |
Stream<T> | stream() |
10. 스트림 → Map | ||
Stream<T> IntStream LongStream DoubleStream |
Map<K, V> | collect(Collectors.toMap(Function key, Function value)) collect(Collectors.toMap(Function k, Function v, BinaryOperator merge)) collect(Collectors.toMap(Function k, Function v, BinaryOperator merge, Supplier mapSupplier)) |
11. 스트림 → 배열 | ||
Stream<T> | Object[] | toArray() |
T[] | toArray(IntFunction<A[]> generator) | |
IntStream LongStream DoubleStream |
int[] long[] double[] |
toArray() |
🔗 Reference
'java' 카테고리의 다른 글
버전 매니저 asdf 설치 & jdk 설치 (0) | 2025.01.02 |
---|---|
자바 정리 (3) (0) | 2024.02.19 |
자바 정리 (2) (1) | 2024.02.06 |
자바 정리 (1) (0) | 2024.01.22 |
Java 버전 관리 도구 (0) | 2023.11.18 |