// 함수 1
int max(int a, int b) {
return a > b ? a : b;
}
// 람다식 1
(int a, int b) -> return a > b ? a : b
// 함수 2
int sumArr(int[] arr) {
int sum = 0;
for (int i : arr) {
sum += i ;
}
reutrn sum ;
}
// 람다식 2
(int[] arr) -> {
int sum = 0;
for (int i : arr) {
sum += i;
}
reutrn sum;
}
// 함수 3
int square(int x) {
return x * x;
}
// 람다식 3
x -> x * x
void printProductName(String product, int i) {
System.out.println(prouct + " " + i);
}
(product, i) -> System.out.println(prouct + " " + i)
(a, b) -> a > b ? a : b
/**
* 익명 객체 구현
*/
// Object로 만들고 참조 변수 타입도 Object로 하면 에러가 발생함
new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
}
// 참조 변수 대입 => 실패
Object obj = new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
}
// 예시 1
@FunctionalInterface
interface MyFunction {
// public abstract는 생략 가능
public abstract int max(int a, int b);
}
// 인터페이스를 구현한 함수 1
My function f = new MyFunction() {
public int max(int a, int b) {
return a > b ? a : b;
}
};
int mx = f.max(10, 15);
// 인터페이스를 구현한 람다식 1
// 아래의 세미콜론은 참조변수 대입을 하기 때문에 적어야 함
My function f = (int a, int b) -> a > b ? a : b;
int mx = f.max(10, 15) ;
// 예시 2
@FunctionalInterface
interface Comparator<T> {
int compare(T o1, T o2);
}
List<String> list = Arrays.asList("abc", "aaa", "bbb", "ddd", "aaa");
// 익명 객체 생성
Collections.sort(list, new Comparator<String>() {
public int compare(String s1, String s2) {
return s2.compareTo(s1);
}
});
// 람다식으로 변환
Collections.sort(list, (s1, s2) -> s2.compareTo(s1));
@FunctionalInterface
interface MyFunction {
void myMethod();
}
...
void aMethod(MyFunction f) {
f.myMethod();
}
...
MyFunction f = () -> System.out.println("람다식을 이용한 myMethod");
aMethod(f);
aMethod(() -> System.out.println("람다식을 이용한 myMethod"));
MyFunction myMethod() {
MyFunction f = () -> {};
return f;
}
...
MyFunction myMethod() {
return () -> {};
}
@FunctionalInterface
public interface UnaryOperator<T> extend Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
static에 제네릭이 붙어 있는 메소드를 제너릭 메소드라 함
public class Member<T> {
static T getName(T name) {
return name;
}
}
위의 코드와 같이 해당 클래스 타입 객체를 매개 변수로 받거나, 반환 타입이 해당 클래스 타입이라면, 인스턴스가 생성되기 전까지 T의 타입이 무엇인지 정해지지 않기 때문에 오류가 발생함
public class Member<T> {
static <T> getName(T name) {
return name;
}
}
따라서 위와 같이 작성해야하며, 코드 상으로는 class의 제네릭 와 static 메소드의 가 같아 보일 수 있지만 완전히 다른 별개의 제네릭 타입으로, 해당 클래스가 인스턴스화가 되지 않더라도 먼저 메모리에 올라갈 수 있도록 분리시켜 지정하는 방법임
Predicate<Integer> p = i -> i < 100 ;
Predicate<Integer> q = i -> i < 200 ;
Predicate<Integer> r = i -> i % 2 == 0 ;
Predicate<Integer> notP = i -> p.negate() ; // i >= 100
Predicate<Integer> all = notP.and(q.or(r)) ; // 100 <= i && (i < 200 || i%2 == 0 )
Predicate<Integer> all2 = notP.and(q).or(r); // 100 <= i && i < 200 || i%2 == 0
System.out.println(all.test(150)) ; // true
System.out.println(all.test(2)) ; // false
System.out.println(all2.test(2)) ; // true
String str1 = "abc";
Predicate<String> p = Predicate.isEqual(str1);
System.out.println(p.test("def")); // false
// 위의 코드를 한 줄로 줄인 것, str1.equals("def")와 같음
boolean result = Predicate.isEqual(str1).test("def");
System.out.println(result);
// 입력값 : String / 반환값 : Integer
Function<String, Integer> f = (s) -> Integer.parseInt(s, 16)
// 입력값 : Integer / 반환값 : String
Function<Integer, String> g = (i) -> Integer.toBinaryString(i)
// 두 함수를 하나의 함수로 결합
Function<String, String> h = f.andThen(g) ;
// andThen 역순 == g.andThen(f)
Function<Integer, Integer> rh = f.compose(g) ;
Interface | Method | 설명 |
---|---|---|
Collection | boolean removeIf() | 조건에 맞는 요소 삭제 |
List | void replaceAll(UnaryOperator operator) | List 내 모든 요소 변환 & 대체 |
Iterable | void forEach(Consumer action) | 모든 요소에 작업(action) 수행 |
Map | V compute(K Key, BiFunction<K, V, V> f) _ 람다식 | 지정된 키의 값에 작업(f) 수행 |
V computeIfAbsent(K key, Function<K, V> f) _ 람다식 | 키가 없으면, 값에 작업(f) 수행 & 추가 | |
V computeIfPresent(K key, BiFunction<K, V, V> f) _ 람다식 | 지정 키가 있으면, 값에 작업(f) 수행 | |
V merge(K key, V value, BiFunction<V, V, V> f) | 모든 요소에 병합 작업(f) 수행 | |
void forEach(BiConsumer<K, V> action) | 모든 요소에 작업(action) 수행 | |
void replaceAll(BiFunction<K, V, V> f) | 모든 요소에 치환작업(f) 수행 |
// 기존 메소드
Integer chgInt(String s) {
return Integer.parseInt(s);
}
// 람다식
Function<String, Integer> f = (String s) -> Intger.parseInt(s);
// 메소드 참조
Function<String, Integer> f = Integer::parseInt;
// 람다식 생성자
Supplier<MyClass> s = () -> new MyClass();
// 생성자 메소드 참조
Supplier<MyClass> s = MyClass::new;
// 매개 변수가 1개인 경우
Function<Integer, MyClass> f = (i) -> new MyClass(i); // 람다식
Function<Integer, MyClass> f = MyClass::new; // 생성자 메소드
// 매개 변수가 2개인 경우
BiFunction<Integer, String, MyClass> f2 = (i, s) -> new MyClass(i, s); // 람다식
BiFunction<Integer, String, MyClass> f2 = MyClass::new; // 생성자 메소드
Function<Integer, int[]> arrF = x -> new int[x]; // 람다식
Function<Integer, int[]> arrF = int[]::new; // 생성자 메소드
// String 배열
Function<Integer, String[]> arrF2 = String[]::new;
// 컬렉션
List<Integer> list = Arrays.asList(1,2,3,4,5);
// 컬렉션 -> Stream
Stream<Integer> intStream = list.stream();
// 배열 -> Stream
Stream<String> strStream = Stream.of(new String[]{"a", "b", "c"});
// 람다식 -> Stream
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2) ;
// 메서드 참조 : 람다식 -> Stream
Stream<Double> randomStream = Stream.generate(Math::random) ;
// Int형 최적화 Stream : IntStream
IntStream intStream = new Random().ints(5) ; // 난수 스트림
/**
* distinct => 중복 제거
* limit => ~개 까지만
* sorted => 정렬
*
* forEach => 각 요소마다
*/
stream.distinct().limit(5).sorted().forEach(System.out::println);
// |----------중간연산----------||----------최종연산----------|
List<Integer> list = Arrays.asList(3, 1, 5, 4, 2);
// 정렬해서 새로운 List에 저장
List<Integer> sortedList = list.stream().sorted().collect(Collections.toList());
System.out.println(list); // [3,1,5,4,2] => 변경되지 않음
System.out.println(sortedList); // [1,2,3,4,5]
int[] intArr = {1, 2, 3};
Stream<Integer> intStream = Arrays.stream(intArr);
// 가변인자(배열의 길이를 정하지 않고)로 List 만들기 위한 .asList
List<Integer> list = Arrays.asList(1,2,3,4,5) ;
// List -> Stream
Stream<Integer> intStream = list.stream() ;
// Stream
Stream<T> Stream.of(T...values) // 가변
Stream<T> Stream.of(T[])
// Arrays
Stream<T> Arrays.stream(T[])
// index로 원하는 구간의 값들만으로 stream 생성 가능
Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive)
// int[] 는 불가능 -> 쓰고싶으면 IntStream 사용
Integer[] intArr = {1, 2, 3, 4, 5};
// intArr가 참조형(Integer)이여야 가능
Stream<Integer> intStream = Arrays.stream(intArr) ;
// String(문자열) Stream 만들기
Stream<String> strStream = Stream.of("a", "b", "c")
Stream<String> strStream = Stream.of(new String[]{"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[]{"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[]{"a", "b", "c"}, 0, 3);
// 기본형 IntStream
IntStream IntStream.of(int...values)
IntStream IntStream.of(int[])
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array, int startInclusive, int endExclusive)
// IntStream 뿐만 아니라 LongStream과 DoubleStream도 위 방법과 동일
IntStream intStream = new Random().ints(); // 무한 스트림
intStream.limit(5).forEach(System.out::println); // 임의 난수 5개 출력(유한 스트림)
IntStream ints(long streamSize)
LongStream longs(long streamSize)
DoubleStream doubles(long streamSize)
// 매개 변수를 활용한 유한 스트림
IntStream intStream = new Random().ints(5); // 매개 변수 5를 통해 유한 스트림으로 만듬
intStream.forEach(System.out::println);
// 무한 스트림
IntStream ints(int begin, int end) // begin <= ints() < end
LongStream longs(long begin, long end) // begin <= longs() < end
DoubleStream doubles(double begin, double end) // begin <= doubles() < end
// 유한 스트림
IntStream ints(long streamSize, int begin, int end) // begin <= ints() < end
LongStream longs(long streamSize, long begin, long end) // begin <= longs() < end
DoubleStream doubles(long streamSize, double begin, double end) // begin <= doubles() < end
IntStream IntStream.range(int begin, int end) // end 값 포함 X
IntStream IntStream.rangeClosed(int begin, int end) // end 값 포함 O
IntStream int1 = IntStream.range(1, 5); // 1, 2, 3, 4
IntStream int2 = IntStream.rangeClosed(1, 5); // 1, 2, 3, 4, 5
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static <T> Stream<T> generate(Supplier<T> s)
// 0 -> 2, 2 -> 4, 4 -> 6 ... (무한 스트림)
Stream<Integer> evenStream = Stream.iterate(0, n -> 2);
int[] num = {3, 4, 5};
// int[] => IntStream
int[] num = Arrays.stream(num);
// IntStream => Stream<Integer>
Stream<Intger> boxed = stream.boxed();
// Stream<Intger> => Integer[] 1
Integer[] result = boxed.toArray(Integer[]::new);
// int[] => IntStream => Stream<Integer> => Integer[] oneLine
Integer[] result = Arrays.stream(num)
.boxed()
.toArray(Integer[]::new);
// Arrays.stream 사용
List<Integer> list1 = Arrays.stream(arr) // int[] => IntStream
.boxed() // IntStream => Stream<Integer>
.collect(Collectors.toList()); // Stream<Integer> => List<Integer>
// IntStream.of 사용
List<Integer> list2 = IntStream.of(arr) // int[] => IntStream
.boxed() // IntStream => Stream<Integer>
.collect(Collectors).toList()); // Stream<Integer> => List<Integer>
// 방법 1 : 직접 int[] 생성해서 넣기
int[] arr1 = new int[list.size()]
for (int i=0; i<list.size(); i+=)
arr1[i] = list.get(i).intValue();
// 방법 2 : 람다식
int[] arr2 = list.stream()
.mapToInt(i -> i)
.toArray();
// 방법 3 : 메소드 참조
int[] arr3 = list.stream()
.mapToInt(Integer::intValue)
.toArray();
int[] ints = {1, 2, 3, 4};
// 방법 1 : HashSet 인자에 List<Integer> 넣기
Set<Integer> set = new HashSet<>(Arrays.stream(ints) // int[] => IntStream
.boxed() // IntStream => Stream<Integer>
.collect(Collectors.toList())); // Stream<Integer> => Set<Integer>
// 방법 2 : stream으로 한 줄에 작성
Set<Integer> set = Arrays.stream(ints) // int[] => IntStream
.boxed() // IntStream => Stream<Integer>
.collect(Collectors.toList()); // Stream<Integer> => Set<Integer>
// 방법 3 : int[] => Integer[]
HashSet<Integer> hashSet = IntStream.of(ints) // int[] => IntStream
.boxed() // IntStream => Stream<Integer>
.collect(Collectors.toCollection(HashSet::new)); // Stream<Integer> => HashSet<Integer>
Stream<Path> Files.list(Path dir)
Stream<String> Files.lines(Path path)
Stream<String> Files.lines(Path path, Charset cs)
Stream<String> lines() // BufferedReader 클래스의 메소드
Stream emptyStream = Stream.empty(); // 비어있는 스트림 생성 및 반환
long cnt = emptyStream.count(); // 0
Stream<T> skip(long n)
// 기본형 스트림에서의 skip
IntStream skip(long n)
Stream<T> limit(long maxSize)
// 기본형 스트림에서의 limit
IntStream limit(long maxSize)
Stream<T> filter(Predicate<? super T> predicate)
IntStream intStream = IntStream.rangeClosed(1, 10); // 1 ~ 10
intStream.filter(i -> i % 2 == 0).forEach(System.out::println); // 246810
intStream.filter(i -> i % 2 != 0 && i % 3 != 0).forEach(System.out::println); // 157
intStream.filter(i -> i % 2 != 0).filter(i -> i % 3 != 0).forEach(System.out::println); // 157
Stream<T> distinct()
IntStream intStream = IntStream.of(1,2,2,3,3,3,4,5,5,6);
intStream.distinct().forEach(System.out::print); // 123456
만일, Comparator를 지정하지 않으면 스트림의 요소 클래스에서 구현해놓은 Comparable 기준으로 정렬됨 (Comparable을 구현하지 않은 클래스 요소라면 comparator를 지정하지 않을 경우 예외 발생)
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
// CASE_INSENSITIVE_ORDER 같은 경우는 미리 static 으로 구현되어 있음
static Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
/*
최종 연산 forEach(System.out::print) 를 수행시켰다고 가정
*/
// 기본 정렬 -> CCaaabccdd
strStream.sorted() ; // String 클래스의 Comparable
strStream.sorted(Comparator.naturalOrder()) ; // Comparator 클래스에 구현된 기본 정렬 기준
strStream.sorted((s1, s2) -> s1.compareTo(s2)) ; // 람다식
strStream.sorted(String::compareTo) ;
// 역순 정렬 -> ddccbaaaCC
strStream.sorted(Comparator.reverseOrder())
// 기본 정렬 (대소문자 구분 X) -> aaabCCccdd
strStream.sorted(String.CASE_INSENSITIVE_ORDER)
// 역순 정렬 (대소문자 구분 X) -> ddCCccbaa (주의, 대문자가 앞에 위치함)
strStream.sorted(String.CASE_INSENSITIVE_ORDER.reversed())
// 별도 기준 정렬 : comparing
strStream.sorted(Comparator.comparing(String::length))
strStream.sorted(Comparator.comparingInt(String::length))
// 별도 기준 역순 정렬 : comparing().reversed()
strStream.sorted(Comparator.comparing(String::length).reversed())
comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)
comparingInt(ToIntFunction<T> keyExtractor)
comparingLong(ToLongFunction<T> keyExtractor)
comparingDouble(ToDoubleFunction<T> keyExtractor)
thenComparing(Comparator<T> other)
thenComparing(Function<T, U> keyExtractor)
thenComparing(Fucntion<T, U> keyExtractor, Comparator<U> keyComparator)
// Stream<T> -> Stream<R>
Stream<R> map(Function<? super T, ? extends R> mapper)
Stream<File> fileStream = Stream.of(new File("F1.java"),
new File("F2.java"),
new File("F3.java"));
// fileStream.map(File::getName).forEach(System.out::println) 로도 가능
Stream<String> fileNameStream = fileStream.map(File::getName); // F1.javaF2.javaF3.java
fileNameStream..forEach(System.out::println)
Stream<File> fileStream = Stream.of( new File("F1.java"), new File("F1.bak"),
new File("F2.java"), new File("F3.txt"),
new File("F3"), new File("F4.java"));
// 요소 하나씩 하나씩 처리
fileStream.map(File::getName)
.filter(s -> s.indexOf('.') != -1) // 확장자가 없는 경우, 출력하지 못함
.peek(s -> System.out.printf("filename = %s%n", s))
.map(s -> s.substring(s.indexOf('.') + 1))
.peek(s -> System.out.printf("extension = %s%n", s))
.map(String::toUpperCase)
.distinct() // 확장자가 중복되는 경우, 출력하지 못함
.forEach(System.out::println);
/*
filename = F1.java
extension = java
JAVA
filename = F1.bak
extension = bak
BAK
filename = F2.java
extension = java
filename = F3.txt
extension = txt
TXT
filename = F4.java
extension = java
*/
Stream<Stream<String>> strStrStream = 배열스트림.map(Arrays::stream)
// Stream<String>의 스트림 반환
Stream<String[]> 스트림.flatMap(Arrays::stream)
중간 연산 | 설명 |
---|---|
Stream distinct() | 중복 제거 |
Stream filter( Predicate predicate ) | 조건에 맞는 요소만 필터링 |
Stream limit( long maxSize ) | 스트림의 요소 제한 (잘라내기) |
Stream skip( long n ) | 스트림의 요소 일부 Skip |
Stream peek( Consumer action ) | 스트림의 요소 작업 수행, 보통 " 중간 결과를 볼 때 " 사용 |
Stream sorted()Stream sorted( Comparator comparator ) | 스트림의 요소 정렬, Comparator로 정렬 기준 |
중간 연산 | 설명 | |
---|---|---|
Stream | map( Function<T, R> mapper ) | |
DoubleStream | mapToDouble( ToDoubleFunction mapper ) | |
IntStream | mapToInt( ToIntFunction mapper ) | |
LongStream | mapToLong( ToLongFunction mapper ) | |
스트림 요소 반환 | ||
Stream | map( Function<T, R> mapper ) | |
DoubleStream | map( Function<T, R> mapper ) | |
IntStream | map( Function<T, R> mapper ) | |
LongStream | map( Function<T, R> mapper ) |
void forEach(Consumer<? super T> action)
void forEachOrdered(Consumer<? super T> action)
boolean allMatch(Predicate<? super T> Predicate) // 모든 요소 만족 => true
boolean anyMatch(Predicate<? super T> Predicate) // 하나라도 요소 만족 => true
boolean noneMatch(Predicate<? super T> Predicate) // 모든 요소 불만족 => true (<-> allMatch)
Optional<Student> result = stuStream.filter(s -> s.getTotalScore <= 100).findFirst();
Optional<Student> result = stuStream.parallel()
.filter(s -> s.getTotalScore <= 100).findAny();
// 기본
Optional<T> reduce(BinaryOperator<T> accumulator)
/*
* identity => 초기값
* accumulator => 이전 연산 결과 & 스트림 요소에 수행할 연산
* combiner => (병렬 스트림) 결과를 합치는데 사용할 연산
*/
T reduce(T identity, BinaryOperator<T> accumulator)
// 입력 값을 2개 받는 BinaryFunction F.I의 연산
// combiner => 합치기 연산
U reduce (U identity, BiFunction<U, T, U> accumulator, BinaryOperator<U> combiner)
// T(요소) --> A에 누적(reducing) --> 결과 R로 변환&반환
public interface Collector<T, A, R> {
// 누적할 곳 (ex. StringBuilder::new)
Supplier<A> supplier();
// 누적 방법 (ex. (sb, s) -> sb.append(s))
BiConsumer<A, T> accumulator();
// 결합 방법(병렬) (ex. (sb1, sb2) -> sb1.append(sb2))
BinaryOperator<A> combiner();
// 최종 변환 (R타입으로) (ex. sb -> sb.toString())
Function<A, R> finisher();
// Collector 특성 Set 반환
Set<Characteristics> characteristics() ;
...
}
toList(), toSet(), toMap(), toCollection()
List나 Set과 같은 컬렉션은 위와 같이 사용하면 되지만, ArrayList나 LinkedList 등과 같이 특정 컬렉션을 지정할 때에는 toCollection() 메소드에 원하는 컬렉션의 생성자 참조를 매개 변수로 넣어주면 됨
List<String> names = childrenStream.map(Child::getName)
.collect(Collectors.toList());
// String 요소 List 객체 스트림 => ArrayList 컬렉션에 저장
ArrayList<String> list = names.stream()
.collect(Collectors.toCollection(ArrayList::new));
toMap()은 key-value 쌍으로 저장해야 하므로, 연산 대상인 객체의 어떤 필드들을 키와 값으로 사용할지를 정해줘야 함
// 주민번호 -> 키 & 객체 -> 값
Map<String, Person> map = personStream
.collect(Collectors.toMap(p -> p.getRegId(), p -> p));
toArray()
스트림의 저장 요소들을 배열로 변환
주의할 점은 매개 변수로 해당 타입의 생성자 참조를 넣어줘야하며, 만약 넣지 않는다면 Object[] 타입이 반환되므로 배열을 담을 참조 변수 타입도 Object[]로 일치시켜줘야 함
Child[] childrenNames = childrenStream.toArray(Child[]::new) ;
// 에러 : Object[] 배열 반환 -> 불일치
Child[] childrenNames = childrenStream.toArray() ;
// 매개 변수 타입과 반환 타입 일치
Child[] childrenNames = (Child[])childrenStream.toArray() ;
Object[] childreanNames = childrenStream.toArray() ;
counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarizingInt() 등
그룹화 메소드인 groupingBy() 메소드와 주로 함께 사용
import static java.util.Stream.Collectors.* ;
long count = stuStream.count();
long count = stuStream.collect(counting()); // Collectors
long totalScore = stuStream.mapToInt(Student::getTotalScore).sum();
long totalScore = stuStream.collect(summingInt(Student::getTotalScore)); // Collectors
Optional<Student> topStudent = stuStream.max(Comparator.compareInt(Student::getTotalScore));
Optional<Student> topStudent = stuStream.collect(maxBy(Comparator.compareInt(Student::getTotalScore)));
IntSummaryStatistics stat = stuStream.mapToInt(Student::getTotalScore).summaryStatistics();
IntSummaryStatistics stat = stuStream.collect(summarizingInt(Student::getTotalScore));
joining()
구분자 & 접두사 & 접미사 모두 지정 가능함
단, 스트림의 요소가 String이나 StringBuffer처럼 CharSequence의 자손인 경우에만 결합이 가능하기 때문에, 요소가 문자열이 아닌 경우 map()을 통해 먼저 문자열로 변환해야함
import static java.util.Stream.Collectors.* ;
// 이름 : Kim, Jung, Choi, Park이 있다고 가정
// 출력 >> KimJungChoiPark
String stuNames = stuStream.map(Student::getName).collect(joining());
// 출력 >> Kim,Jung,Choi,Park
String stuNames = stuStream.map(Student::getName).collect(joining(","));
// 출력 >> [Kim, Jung, Choi, Park]
String stuNames = stuStream.map(Student::getName).collect(joining(",", "[", "]"));
/* collect() */
Collector reducing(BinaryOperator<T> op)
Collector reducing(T identity, BinaryOperator<T> op)
// 변환 작업까지 필요한 경우
Collector reducing(U identity, Function<T, U> mapper, BinaryOperator<U> op)
/* reduce() */
import static java.util.Stream.Collectors.* ;
IntStream intStream = new Random().ints(1, 46).distinct().limit(6) ;
// collect() 활용
Optinal<Integer> max = intStream.boxed().collect(reducing(Integer::max));
// reduce() 활용
OptionalInt max = intStream.reduce(Integer::max) ; // 전체 대상 reducing 에 유리
long sum = intStream.reduce(0, (a,b) -> a + b);
long sum = intStream.boxed().collect(reducing(0, (a,b) -> a + b));
int grandTotal = stuStream.map(Student::getTotalScore)
.reduce(0, Integer::sum);
// 초기값 설정, 변환 작업, 수행 작업 reducing
int grandTotal = stuStream
.collect(reducing(0, Student::getTotalScore, Integer::sum));
Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)
// 기본 분할 -> 기준 적용 결과가 Key값에 해당하는 값들의 List
Map<Boolean, List<Student>> stuBySex = stuStream.collect(partitioningBy(Student::isMale));
List<Student> maleStudent = stuBySex.get(true) ; // 남학생 리스트
List<Student> femaleStudent = stuBySex.get(false) ; // 여학생 리스트
/* 각 그룹의 학생 수 */
Map<Boolean, Long> stuNumBySex = stuStream.collect(partitioningBy(Student::isMale, counting()));
System.out.println("남학생 인원 : " + stuNumBySex.get(true)); // 남학생 인원
System.out.println("여학생 인원 : " + stuNumBySex.get(false)); // 여학생 인원
/* 각 성별 그룹에서의 성적 1등 */
Map<Boolean, Optional<Student>> topScoreBySex = stuStream.collect(
partitioningBy(Student::isMale, maxBy(comparingInt(Student::getScore))));
System.out.println("남학생 1등 : " + topScoreBySex.get(true)); // 추출된 "Optional"객체의 toString() 결과
System.out.println("여학생 1등 : " + topScoreBySex.get(false)); // 추출된 "Optional"객체의 toString() 결과
/* 강 성별 그룹에서의 성적 1등 결과 값을 객체의 타입으로 반환 */
Map<Boolean, Optional<Student>> topScoreBySex = stuStream.collect(
partitioningBy(Student::isMale,
collectingAndThen(maxBy(comparingInt(Student::getScore)),
Optional::get)));
System.out.println("남학생 1등 : " + topScoreBySex.get(true)); // 추출된 객체의 toString() 결과
System.out.println("여학생 1등 : " + topScoreBySex.get(false)); // 추출된 객체의 toString() 결과
Map<Boolean, Map<Boolean, List<Student>>> failedStuBySex = stuStream.collect(
partitioningBy(Student::isMale,
partitioningBy(s -> s.getScore() < 100)));
List<Student> failedMaleStudent = failedStuBySex.get(true).get(true); // "불합격" 남학생 리스트
List<Student> failedFemaleStudent = failedStuBySex.get(false).get(true); //"불합격" 여학생 리스트
Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
Map<Integer, List<Student>> stuByBan = stuStream
.collect(groupingBy(Student::getBan));
// HashSet 컬렉션으로 반환
Map<Integer, HashSet<Student>> stuByBan = stuStream
.collect(groupingBy(Student::getBan, toCollection(HashSet::new));
Map<Student.Level, Long> stuByLevel = stuStream
.collect(groupingBy( s -> {
if(s.getScore() >= 200)
return Student.Level.HIGH;
else if(s.getScore() >= 100)
return Student.Level.MID;
else
return Student.Level.LOW;
}, counting()));
List<Student> failedMaleStudent = failedStuBySex.get(true).get(true) ; // "불합격" 남학생 리스트
List<Student> failedFemaleStudent = failedStuBySex.get(false).get(true) ; // "불합격" 여학생 리스트
// 학년별 그룹화 -> 반별 그룹화
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream
.collect(groupingBy(Student::getHak,
groupingBy(Student::getBan)));
// 각 학년의 각 반별 학생들 + 성적 등급별 그룹화 & 각 등급에 해당하는 학생 수
Map<Integer, Map<Integer, Set<Student.Level>>> stuByHakAndBan =
stuStream.collect(
groupingBy( Student::getHak ,
groupingBy(Student::getBan,
mapping(s -> {
if(s.getScore() >= 200)
return Student.Level.HIGH;
else if(s.getScore() >= 100)
return Student.Level.MID;
else
return Student.Level.LOW;
}, toSet()))));
// 각 반의 1등을 해당 값 객체 타입으로 받기
Map<Integer, Map<Integer, Student>> topStuByHakAndBan =
stuStream.collect(
groupingBy(Student::getHak ,
groupingBy(Student::getBan,
collectingAndThen(
maxBy(comparingInt(Student::getScore)),
Optional::get))));
중간 연산 | 설명 |
---|
| void forEach( Consumer<? super T> action )
void forEachOrdered( Consumer<? super T> action ) : 순서 유지 (병렬 스트림 처리 시에 주로 이용) | 각 요소에 지정된 작업 수행 |
| long count() | 스트림 요소 개수 |
| Optional max( Comparator comparator )
Optional min( Comparator comparator ) | 최대 / 최솟값 |
| Optional findFirst() : 첫번째 요소 (직렬)
Optional findAny() : 아무거나 하나 (병렬, filter()와 자주 사용) | 스트림의 요소 하나 반환 |
| boolean allMatch( Predicate p ) : 모두 만족
boolean anyMatch( Predicate p ) : 하나라도 만족
boolean noneMatch( Predicate p ) : 모두 만족 X | 모든 요소가 주어진 조건 만족 여부 확인 |
| Object[] toArray()A[] toArray( IntFinction<A[]> generator ) | 스트림 요소 → 배열 반환 |
| 《 핵 심 》 | |
| Optional reduce( BinaryOperator accumulator )
T reduce( T identity, BinaryOperator accumulator )
U reduce( U identity, BiFunction<U, T, U> accumulator, BinaryOperator combiner ) | 요소를 하나씩 줄여가면서(Reducing) 계산 |
| R collect( Collector<T, A, R> collector )
R collect( Supplier supplier, BiConsumer<R, T> accumulator, BiConsumer<R, R> combiner ) | 스트림 요소 수집
주로 그룹화 / 분할 결과 컬렉션에 담아 반환할 때 사용 |
// "abc" 값을 가진 String 객체 1
String str = "abc";
// "abc" 값의 주소를 참조하는 String 객체를 가진 Optional 객체 1
Optional<String> optVal = Optional.of(str);
// "abc" 값의 String 객체를 가진 Optional 객체 2
Optional<String> optVal = Optional.of("abc");
// "abc" 값을 가진 새로운 String 객체를 가진 Optional 객체 3
Optional<String> optVal = Optional.of(new String("abc"));
참조 변수의 값이 null일 가능성이 있을 경우, of() 대신 ofNullable()을 사용해야 함
// "abc" 값을 가진 String 객체 1
String str = "abc";
Optional<String> optVal = Optional.ofNullable(str); // Optional객체로 Wrapping
Optional<String> optVal = Optional.ofNullable(null);
Optional<String> optVal = Optional.of(null); // NullPointerException 발생
Optional 타입의 참조 변수를 초기화할 때 null이 가능하긴 하나 바람직한 방법이 아니끼 때문에, Optional 타입 참조 변수를 초기화하려면 empty() 메소드를 사용해야 함
empty() 메소드는 제네릭 메소드로, 추정 가능하기 때문에 보통 생략할 수 있음
Optional<String> optVal = null; // 가능하지만 권장하지 않음
Optional<String> optStrVal = Optional.empty();
Optional<Integer> optIntVal = Optional.empty();
값이 null인 경우, 매개 변수로 입력된 대체 값을 반환
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get(); // str1 = "abc"
String str2 = optVal.orElse("EMPTY"); // str2 = "abc"
Optional<String> optVal = Optional.ofNullable(null);
String str1 = optVal.get(); // NoSuchElementException 예외
String str2 = optVal.orElse("EMPTY"); // str2 = "EMPTY"
T orElseGet(Supplier<? extends T> other)
T orElseGet(Supplier<? extends T> exceptionSupplier)
Optional<String> optVal = Optional.ofNullable(null);
// null 이면 () -> new String()과 동일하게 객체 생성
String str3 = optVal.orElseGet(String::new);
// NoSuchElementException가 아닌 NullPointerException 예외를 생성하여 throw
String str4 = optVal.orElseThrow(NullPointerException::new);
Optional 객체의 값이 null이면 false, null이 아니면 true 반환
if (Optional.ofNullable(str).isPresent()) {
system.out.println(str); // null이면 실행 X
}
매개 변수로 Consumer 타입(반환 값이 없는 람다식)을 받을 수 있는데, 값이 있으면 람다식을 실행하고, 값이 없으면 아무 작업도 하지 않음
// null이면 해당 코드 수행 X (반환 없음)
Optional.ofNullabe(str).ifPresent(System.out::println);
public final class OptionalInt {
private final int value; // int 타입 기본형 참조 변수
// 기본 값이 비어있는지(기본 값 0), 아니면 정수 값 0인지 비교하기 위해 필요
private final boolean isPresent; //값이 저장되어 있으면 true
...
}
참고 : https://velog.io/@yummygyudon/JAVA-람다와-스트림-Lambda-Stream
// 메서드
int max(int a, int b) {
return a > b ? a : b;
}
// 람다식
// (int a, int b) -> return a > b ? a : b
(int a, int b) -> a > b ? a : b
// 메서드
int printVar(String name, int i) {
System.out.println(name + "=" + i);
}
// 람다식
(String name, int i) -> System.out.println(name + "=" + i)
// 메서드
int square(int x) {
return x * x;
}
// 람다식
// (int x) -> return x * x
(int x) -> x * x
// 메서드
int roll() {
return (int)(Math.random() * 6);
}
// 람다식
// () -> return (int)(Math.random() * 6);
() -> (int)(Math.random() * 6)
// 메서드
int sumArr(int[] arr) {
int sum = 0;
for(int i : arr)
sum += i;
return sum;
}
// 람다식
(int[] arr) -> {
int sum = 0;
for(int i : arr)
sum += i;
return sum;
}
// 메서드
int[] emptyArr() {
return new int[]{};
}
// 람다식
() -> new int[]{}
// 람다식
(String s) -> s.length()
// 메소드 참조
String::length
// 람다식
() -> new int[]{}
// 메소드 참조
int[]::new
// 람다식
arr -> Arrays.stream(arr)
// 메소드 참조
Arrays::stream
// 람다식
(String str1, String str2) -> strl.equals(str2)
// 메소드 참조
String::equals
// 람다식
(a, b) -> Integer.compare(a, b)
// 메소드 참조
Integer::compare
// 람다식
(String kind, int num) -> new Card(kind, num)
// 메소드 참조
Card::new
// 람다식
(x) -> System.out.println(x)
// 메소드 참조
System.out::println
// 람다식
() -> Math.random()
// 메소드 참조
Math::random
// 람다식
(str) -> str.toUpperCase()
// 메소드 참조
String::toUpperCase
// 람다식
() -> new NullPointerException()
// 메소드 참조
NullPointerException::new
// 람다식
(Optional opt) -> opt.get()
// 메소드 참조
Optional::get
// 람다식
(StringBuffer sb, String s) -> sb.append(s)
// 메소드 참조
StringBuffer::append
// 람다식
(String s) -> System.out.println(s)
// 메소드 참조
System.out::println
( ) f; // 함수형 인터페이스 타입의 참조변수 f를 선언
f = (int a, int b) -> a > b ? a : b;
// 정답 => BinaryOperator (매개 변수가 int 2개, 반환 값 int 1개)
/* 기본 함수형 인터페이스 */
// Supplier => 매개 변수 X, 반환 값 O
// Consumer => 매개 변수 O, 반환 값 X
// Function => 매개 변수 O, 반환 값 O (일반적)
// Predicate => 매개 변수 1개, boolean 타입 반환 값 1개
/* 매개 변수가 2개인 함수형 인터페이스 */
// BiConsumer => 매개 변수 2개, 반환 값 X
// BiFunction => 매개 변수 2개, 반환 값 1개
// BiPredicate => 매개 변수 2개, boolean 타입 반환 값 1개
/* 매개 변수 타입과 반환 값 타입이 같은 함수형 인터페이스 */
// UnaryOperator => 매개 변수 2개, 반환 값 X
// BinaryOperator => 매개 변수 2개, 반환 값 1개
String[] strArr = { "aaa", "bb", "c", "dddd" };
// 실행결과 => sum = 10
/* 정답 */
// 스트림 생성
Stream<String> strStream = Stream.of(strArr);
// mapToInt를 통해 Stream<String>을 IntStream으로 변환하여 sum() 메소드 사용
// 람다식
int sum = strStream.mapToInt(s -> s.length()).sum();
// 메소드 참조
int sum = strStream.mapToInt(String::length).sum();
System.out.println(sum);
String[] strArr = { "aaa", "bb", "c", "dddd" };
// 실행결과 => 4
/* 정답 */
Stream<String> strStream = Stream.of(strArr);
/* 가장 긴 문자열의 길이 출력
* strStream.map(String::length) => 각 요소들의 길이로 형태 변환
* .sorted(Comparator.reverseOrder()) => 각 요소들의 길이를 기준으로 내림차순 정렬
* .limit(1).forEach(System.out::println) => 정렬된 요소들 중 맨 앞의 1개만 출력
*/
strStream.map(String::length).sorted(Comparator.reverseOrder()).limit(1)
.forEach(System.out::println);
/* 가장 긴 문자열 출력
* strStream.sorted(Comparator.comparingInt(String::length).reversed())
* => 각 요소들의 길이를 기준으로 내림차순 정렬
* .limit(1).forEach(System.out::println) => 정렬된 요소들 중 맨 앞의 1개만 출력
*/
strStream.sorted(Comparator.comparingInt(String::length).reversed()).limit(1)
.forEach(System.out::println);
/* Comparator 기본 */
// comparing(Function<T, U> keyExtractor)
// comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)
/* 스트림과의 비교 대상이 기본형인 경우 */
// comparingInt(ToIntFunction<T> keyExtractor) => int 타입 비교
// comparingLong(ToLongFunction<T> keyExtractor) => Long 타입 비교
// comparingDouble(ToDoubleFunction<T> keyExtractor) => Double 타입 비교
/* 정렬 조건이 두 개 이상인 경우 => thenComparing()으로 체이닝 */
// sorted(Comparator.comparing().thenComparing().thenComparing()…thenComparing())
/* 실행결과
* 1
* 20
* 25
* 33
* 35
* 42
*/
/*
* new Random().ints(1, 46).distinct() => 1~46사이(46은 포함 X) 랜덤한 정수 중 중복 제거
* .limit(6) => 무한 스트림이기 때문에 6개로 제한
* .sorted().forEach(System.out::println) => 정렬 후 출력
*/
IntStream intStream = new Random().ints(1, 46).distinct();
intStream.limit(6).sorted().forEach(System.out::println);
/* 무한 스트림 */
// IntStream ints(int begin, int end) // begin <= ints() < end
// LongStream longs(long begin, long end) // begin <= longs() < end
// DoubleStream doubles(double begin, double end) // begin <= doubles() < end
/* 유한 스트림 */
// IntStream ints(long streamSize, int begin, int end) // begin <= ints() < end
// LongStream longs(long streamSize, long begin, long end) // begin <= longs() < end
// DoubleStream doubles(long streamSize, double begin, double end) // begin <= doubles() < end
/* 실행결과
* [1,5]
* [2,4]
* [3,3]
* [4,2]
* [5,1]
*/
/* 정답 */
IntStream.rangeClosed(1, 6) // 1~6의 범위를 가진 IntStream 생성
.boxed() // IntStream => Stream<Integer>로 오토박싱
// 아래의 flatMap은 dice1과 dice2의 조합으로 나올 수 있는 모든 조합을 하나의 스트림으로 평탄화한 것
// IntStream이 아닌 Stream<Integer>를 사용한 이유는 int[]에 있는 각 int 타입 값을 활용하기 위함
.flatMap(dice1 -> IntStream.rangeClosed(1, 6) // 1~6의 값을 가지는 IntStream
.mapToObj(dice2 -> new int[]{dice1, dice2})) // 1~6의 값을 가지는 IntStream을 모든 주사위의 경우의 수가 들어간 Stream<Integer[]>로 오토박싱
.filter(dice -> dice[0] + dice[1] == 6) // 두 주사위의 합이 6인 것만 필터링
.forEach(dice -> System.out.println("[" + dice[0] + ", " + dice[1] + "]"));
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
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;}
public String toString() {
return String.format("[%s, %s, %d학년 %d반, %3d점 ]",
name, isMale ? "남" : "여", hak, ban, score);
}
// groupingBy()에서 사용 성적을 상,중,하 세 단계로 분류
enum Level {
HIGH, MID, LOW
}
}
class Exercise {
public static void main(String[] args) {
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)
};
/* 답변 작성 START */
Map<Boolean, Map<Boolean, Long>> failedStuBySex
= Stream.of(stuArr).collect(partitioningBy(Student::isMale, // (2) 남학생 여학생 2분할
partitioningBy(s -> s.getScore() < 150, // (1) 150점 미만(불합격) 2분할
counting())));
/* 답변 작성 END */
// partitioningBy => true or false 2분할
// groupingBy => 조건에 따른 N분할
long failedMaleStuNum = failedStuBySex.get(true).get(true);
long failedFemaleStuNum = failedStuBySex.get(false).get(true);
System.out.println("불합격[남자]:"+ failedMaleStuNum +"명");
System.out.println("불합격[여자]:"+ failedFemaleStuNum +"명");
}
}
/* 실행결과
* 불합격[남자]:2명
* 불합격[여자]:4명
*/
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
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; }
public String toString() {
return String.format("[%s, %s, %d학년 %d반 , %3d점 ]", name, isMale ? "남" : "여", hak, ban, score);
}
enum Level {
HIGH, MID, LOW
}
}
class Exercise {
public static void main(String[] args) {
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)
};
/* 답변 작성 START */
Map<Integer, Map<Integer, Long>> totalScoreByHakAndBan
= Stream.of(stuArr).collect(groupingBy(Student::getHak, // (2) 각 학년별 그룹핑
groupingBy(Student::getBan, // (1) 각 반별 그룹핑
summingLong(Student::getScore))));
/* 답변 작성 END */
for(Object e : totalScoreByHakAndBan.entrySet()) {
System.out.println(e);
}
}
}