Java 개념 정리V
Java 개념 정리
람다와 스트림 (Lambda & Stream)
1. 람다(Lambda)
1-1. 람다식(Lambda Expression)이란;
- 함수(메서드)를 간단한 ‘식(Expression)’ 으로 표현한는 방법
int max(int a, int b){ return a > b a : b; } ↓ (a,b) -> a > b ? a : b
- 익명 함수(이름이 없는 함수, annonymous function)
int max(int a, int b){ return a > b ? a : b; } ↓ (int a, int b) -> { return a > b ? a : b; }
- 함수와 메서드의 차이
- 근본적으로 동일. 함수는 일반적 용어, 메서드는 객체지향개념 용어
- 함수는 클래스에 독립적 ,메서드는 클래스에 종속적
1-2. 람다식 작성하기
- 메서드의 이름과 반환타입을 제거하고 ‘->’를 볼록{} 앞에 추가한다.
int max(int a, int b){ return a > b ? a : b; } ↓ (int a, int b) -> { return a > b ? a : b; }
- 반환 값이 있는 경우, 식이나 값만 적고 return문 생략 가능 ( 끝에 ‘ ;’ 안 붙암)
(int a, int b)->{ return a > b ? a : b; } ↓ (int a, int b) -> a > b ? a : b
- 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략 가능)
(int a, int b) -> a > b ? a : b ↓ (a,b) -> a > b ? a : b
- 주의 사항
- 매개변수가 하나인 경우, 괄호() 생략 가능 (타입이 없을 때만)
a -> a * a //OK int a -> a * a //에러 (int a)->a*a
- 블록 안의 문장이 하나뿐 일 때, 괄호{} 생략 가능(끝에 ‘;’ 안 붙임)
(int i) -> System.out.println(i)
- 단, 하나뿐인 문장이 return문이면 괄호{} 생략 불가
(int a, int b) -> {return a > b ? a : b ; }
1-3. 함수형 인터페이스
- 람다식은 익명 함수? 사실은 익명 객체
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; } }; int value = obj.max(3,5); //에러. Object클래스에 max()가 없음
- 함수형 인터페이스 - 단 하나의 추상 메서드만 선언된 인터페이스
interface MyFunction{ public abstract int max(int a , int b); } MyFunction f = new MyFunction(){ public int max(int a, int b){ return a > b ? a : b; } }; int value = f.max(3,5); //OK. MyFunction에 max()가 있음
- 함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있음 (단, 함수형 인터페이스의 메서드와 람다식의 매개변수 개수와 반환타입이 일치해야 함.)
MyFunction f = (a,b) -> a > b ? a : b; int value = f.max(3,5); //실제로는 람다식(익명 함수)이 호출됨
- 익명 객체를 람다식으로 대체
List<String> list = Arrays.asList("abc","aaa","bbb","ddd","aaa"); Collections.sort(list,(s1,s2)-> s2.compareTo(s1));
1-4. java.util.function패키지
- 자주 사용되는 다양한 함수형 인터페이스를 제공
Predicate<String> isEmptyStr = s-> s.length()==0; String s = ""; if(isEmptyStr.test(s)) //if(s.length()==0) System.out.println("This is an empty String.");
-
매개변수가 2개인 함수형 인터페이스
@FunctionalInterface interface TriFunction<T,U,V,R>{ R apply(T t, U u, V v); }
- 매개변수의 타입과 반환타입이 일치하는 함수형 인터페이스
@FunctinalInterface public interface UnaryOperator<T> extends Function<T,T>{ static <T> UnaryOperator<T> identity(){ return t -> t; } }
- 함수형 인터페이스를 사용하는 컬렉션 프레임웍의 메서드
list.forEach(i->System.out.print(i+",")); //list의 모든 요소를 출력 list.removeIf(x->x%2==0||x%3==0); //2또는 3의 배수를 제거 list.remplace(i->i&10); //모든 요소에 10을 곱한다. //map의 모든 요소를 {k,v}의 형식으로 출력 amp.forEach((k,v)->System.out.println("{"+k+","+v+"},"));
- 기본형을 사용하는 함수형 인터페이스
IntSupplier s = () -> (int)(Math.random()*100)+1; static void makeRandomList(IntSupplier s, int[] arr){ for(int i=0;i<arr.length;i++) arr[i]= s.getAsInt(); }
1-5. Function의 합성
- Function 타입의 두 람다식을 하나로 합성 - addThen()
Function<String, Integer> f = (s) -> Integer.parseInt(s,16); //s를 16진 정수로 변환 Function<Integer,String> g = (i) -> Integer.toBinaryString(i); //2진 문자열로 변환 Function<String, String> h = f.addThen(g); //f+g -> h
- Function 타입의 두 람다식을 하나로 합성 - compose()
Function<Integer, String> g = (i) -> Integer.toBinaryString(i); //2진 문자열로 변환 Function<String, Integer> f = (s) -> Integer.parseInt(s,16); // s를 16진 정수로 변환 Function<Integer, Integer> h = f.compose(g); //g+f -> h
1-6. Predicate의 결합
- and(), or(), negate()로 두 Predicate를 하나로 결합(default 메서드)
Predicate<Integer> p = i -> i < 100; Predicate<Integer> q = i -> i < 200; Predicate<Integer> r = i -> i % 2 == 0; Predicate<Integer> notP = 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)
- 등가비교를 위한 Predicate의 작성에는 isEqual()를 사용 (static메서드)
Predicate<String> p = Predicate.isEqual(str1); //isEqual()은 static메서드
1-7. 메서드 참조(method reference)
- 하나의 메서드만 호출하는 람다식은 ‘메서드 참조’로 간단히 할 수 있다.
- static메서드 참조 - 람다식: (x)->className.method(x) / 메서드 참조: ClassName::method
- 인스턴스메서드 참조 - 람다식: (obj.x)->obj.method(x) / 메서드 참죄: ClassName::method
- 특정 객체 인스턴스메서드 참조 - 람다식: (x) -> obj.method(x) / 메서드 참조: obj::method
- static 메서드 참조
Integer method(String s){ return Integer.parseInt(s); } ↓ Function<String,Integer> f = (String s) -> Integer.parseInt(s); ↓ Function<String,Integer> f = Integer::parseInt; // 메서드 참조
- 인스턴스 메서드 참조
BiFunction<String,String,Boolean> f = (s1,s2) -> s1.equals(s2); ↓ BiFunction<String,String,Boolean> f = String::equals;
- 특정 객체의 인스턴스 메서드 참조
MyClass obj = new MyClass(); Function<String, Boolean> f = (x) -> obj.equals(x); //람다식 Function<String BOolean> f2 = obj::equals; //메서드 참조
- new 연산자(생성자,배열)와 메서드 참조
Supplier<MyClass> s = MyClass::new; //() -> new MyClass() Function<Integer, MyClass> f2 = MyClass::new; // (i) -> new MyClass(i) Function<Integer int[]> f2 = int[]::new; //x -> new int[x];
2. 스트림(stream)
2-1. 스트림(Stream) 이란?
- 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
List<Integer> list = Arrays.asList(1,2,3,4,5); Stream<Integer> intStream = list.stream(); //컬렉션 Stream<String> strStream = Stream.of(new String[] {"a","b","c"}); //배열 Stream<Integer> evenStream = Stream.iterate(0, n->n+2); 0,2,4,6,... Stream<Dobule> randomStream = Stream.generate(Math::random); //람다식 IntStream intStream = new Random().int(5); //난수 스트림(크기가 5)
- 스트림이 제공하는 기능 - 중간 연산과 최종 연산
- 중간 연산: 연산 결과가 스트림인 연산. 반복적으로 적용가능
- 최종 연산: 연산결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 한번만 적용가능
stream.distinct().limit(5).sorted().forEach(System.out::println) 중간연산 중간연산 중간연산 최종연산 String[] strArr = {"dd","aaa","CC","cc","b"}; Stream<String> stream = Stream.of(strArr); //문자열 배열이 소스인 스트림 Stream<String> filteredStream = stream.filter(); //걸러내기(중간연산) Stream<String> distinctedSTream = stream.distinct(); //중복제거(중간연산) Stream<String> sortedStream = stream.sort(); //정렬(중간연산) Stream<String> limitedStream = stream.limit(5); //스트림 자르기(중간연산) int total = stream.count(); //요소 개수 세기(최종연산)
2-2. 스트림(Stream)의 특징
- 스트림은 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않는다.
List<Integer> list = Arrays.asList(3,1,5,4,2); List<Integer> sortedList = list.stream().sorted().collect(Collectors.toList()); //list를 정렬해서 새로운 List에 저장 System.out.println(list); //[3,1,5,4,2] System.out.println(sortedList); //[1,2,3,4,5]
- 스트림은 Iterator처럼 일회용이다.(필요하면 다시 스트림을 생성해야 함)
strStream.forEach(System.out::println); //모든 요소를 화면에 출력(최종연산) int numOfStr = strStream.count(); //에러. 스트림이 이미 닫혔음
- 최종 연산 전까지 중간연산이 수행되지 않는다 -지연된 연산
IntStream intStream = new Random().ints(1,46) //1~45범위의 무한 스트림 IntStream.distinct().limit(6).sorted().forEach(i-> System.out.print(i+","));
- 스트림은 작업을 내부 반복으로 처리한다.
void forEach(Consumer<? super T> action){ Objects.requireNonNull(action); //매개변수의 널 체크 for(T t : src) //내부 반복(for문을 메서드 안으로 넣음) action.accept(T); }
- 스트림의 작업을 병렬로 처리 - 병렬스트림
Strema<String> strStream = Stream.of("dd","aaa","CC","cc","b"); int sum = strStream.parallel() //병렬 스틞으로 전환(속성만 변경) .mapToInt(s -> s.length()).sum(); //모든 문자열의 길이의 합
- 기본형 스트림 - IntStream, LongStream, DoubleStream
- 오토박싱&언박싱의 비효율이 제거됨(Stream
대신 IntStream사용) - 숫자와 관련된 유용한 메서드를 Stream
보다 더 많이 제공
- 오토박싱&언박싱의 비효율이 제거됨(Stream
2-3. 스트림의 생성
- 컬렉션으로부터 스트림 생성하기
List<Integer> list = Arrays.asList(1,2,3,4,5); Stream<Integer> intStream = list.stream(); //Stream<T> Collection.stream()
- 배열로부터 스트림 생성하기
Stream<String> strStream = Stream.of("a","b","c"); //가변 인자 Strema<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.range(1,5); //1,2,3,4 IntStream intStream = IntStream.rangeClosed(1,5); //1,2,3,4,5
- 난수를 요소로 갖는 스트림 생성하기
IntStream intStream = new Random().ints(); //무한 스트림 intStream.limit(5).forEach(System.out::println); //5개의 요소만 출력한다 IntStream intStrema = new Random().ints(5); //크기가 5인 난수 스트림을 반환
- 람다식을 소스로 하는 스트림 생성하기
static<T> Stream<T> iterate(T seed, UnaryOperator<T> f) //이전 요소에 종속적 static<T> Stream<T> generate(Supplier<T> s) //이전 요소에 독립적
- 파일을 소스로 하는 스트림 생성하기
Stream<Path> Files.list(Path dir) //Path는 파일 또는 디렉토리
2-4. 스트림의 중간연산
- 스트림 자르기: skip(), limit()
Stream<T> skip(long n) //앞에서부터 n개 건너뛰기 Strema<T> limit(long maxSize) //maxSize 이후의 요소는 잘라냄
- 스트림의 요소 걸러내기: filter(), distinct()
Stream<T> filter(Predicate<? super T> predicate) //조건에 맞지 않는 요소 제거 Stream<T> distinct() //중복제거
- 스트림 정렬하기: sorted()
Stream<T> sorted(); //스트림 요소의 기본 정렬(Comparable)로 정렬 Strema<T> sorted(Comparator<? super T> comparator) //지정된 Comparator로 정렬
- 스트림의 요소 반환하기: map()
Stream<R> map(Function<? super T,? extends R> mapper) //Stream<T> -< Stream<R>
- 스트림을 기본 스트림으로 변환: mapToInt(), mapToLong(), mapToDouble()
IntStream mapToInt(ToIntFunction<? super T> mapper) //Stream<T> -> IntStream LongStream mapToLong(ToLongFunction<? super T> mapper) //stream<T> -> LongStream DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper) //Stream<T> -> DoubleStream
- 기본 스트림을 스트림으로 변환: mapToObj(), boxed()
Stream<T> mapToObj(IntFunction<? extends T> mapper) //IntStream -> Stream<T> Stream<Integer> boxed() //IntStream -> Stream<Integer>
- 스트림의 스트림을 스트림으로 변환: flatMap()
Stream<String[]> strArrStrm = Stream.of(new String[]{"abc","def","ghi"}, new String[]{"ABC","GHI","JKLMN"}); Stream<Stream<string>> strStrStrm = strArrStrm.map(Arrays::stream); Strea<String> strStrStrm= strArrStrm.flatMap(Arrays::stream); //Arrays.stream(T[])
- 스트림의 요소를 소비하지 않고 엿보기: peek()
Stream<T> peek(Consumer<? super T> actoin) //중간연산(스트림을 소비x) void forEach(Consumer<? super T> action) //최종연산(스트림을 소비O)
2-5. Optional과 OptionalInt
- ‘T’타입 객체의 래퍼클래스 - Optional
String str = "abc"; Optional<String> optVal = Optional.of(str); Optional<String> optVal = Optinal.of("abc"); Optional<String> optVal = Optinal.of(null); //NullPointerException발생 Optional<String> optval = Optinal.ofNullable(null); //OK
- Optional객체의 값 가져오기 - get(), orElse(), orElseGet(), orElseThrow()
Optional<String> optVal = Optional.of("abc"); String str1 = optVal.get(); //optVal에 저장된 값을 반환. null이면 예외발생 String str2 = optVal.orElse(""); //optVal에 저장된 값이 null일 때는, ""를 반환 String str3 = optVal.orElseGet(String::new); //람다식 사용가능() -> new String() String str4 = optVal.orElseThrow(NullPointerException::new); //널이면 예외발생
- isPresent() - Optional객체의 값이 null이면 false, 아니면 true를 반환
if(Optional.ofNullable(str).isPresent()){ //if(str!=null){ System.out.println(str); }
- 기본형 값을 감싸는 래퍼클래스 - OptionalInt, OptionalLong, OptionalDouble
public final class OptionalInt{ private final boolean isPresent; //값이 저장되어 있으면 true private final int value; //int타입의 변수 }
- OptionalInt의 값 가져오기 - int getAsInt()
2-6. 스트림의 최종연산
- 스트림의 모든 요소에 지정된 작업을 수행 - forEach(), forEachOrdered()
void forEach(Consumer<? super T> action) //병렬스트림인 경우 순서가 보장되지 않음 void forEachOrdered(Consumer<? super T> action) //병렬스트림인 경우에도 순서가 보장됨 IntStream.range(1,10).sequential().forEach(System.out::print); //123456789 IntStream.range(1,10).sequential().forEachOrdered(System.out::print); //123456789 IntStream.range(1,10).parallel().forEach(System.out::print); //683295714 IntStream.range(1,10).parallel().forEachOrdered(System.out::print); //123456789
- 스트림을 배열로 변환 - toArray()
Object[] toArray() //스트림의 모든 요소를 Object배열에 담아 반환 A[] toArray(IntFunction<A[]> generator) //스트림의 모든 요소를 A타입의 배열에 담아 반환
- 조건 검사 - allMatch(), anyMatch(), noneMatch()
boolean allMatch(Predicate<? super T> predicate) //모든 요소가 조건을 만족시키면 true boolean anyMatch(Predicate<? super T> predicate) //한 요소라도 조건을 만족시키면 true boolean noneMatch(Predicate<? super T> predicate) //모든 요소가 조건을 만족시키지 않으면 true
- 조건에 일치하는 요소 찾기 - findFirst(), findAny()
Optional<T> findFirst() //첫 번째 요소를 반환. 순차 스트림에 사용 Optional<T> findAny() //아무거나 하나를 반환. 병렬 스트림에 사용
- 스트림에 대한 통계정보 제공 - count(), sum(), average(), max(), min()
long count() Optional<T> max(Comparator<? super T> comparator) Optional<T> min(Comparator<? super T> comparator) long count() Int sum() OptionalDouble average() OptionalInt max() OptionalInt min() IntSummaryStatistice summaryStatistics()
- 스트림의 요소를 하나씩 줄여가며 누적연산 수행 - reduce()
Optional<T> reduce(BinaryOperator<T> accumulator) T reduce(T identity, BinaryOperator<T> accumulator) U reduce(U identity, Bifunction<U,T,U> accumulator, BinaryOperator<U> combiner) identity -초기값 accumulator - 이전 연산결과와 스트림의 요소에 수행할 연산 combiner - 병렬처리된 결과를 합치는데 사용할 연산 (병렬 스트림)
2-7. collect(), Collector, Collectors
- collect()는 Collector를 매개변수로 하는 스트림의 최종연산
Object collect(Collect collect) //Collector를 구현한 클래스의 객체를 매개변수로 Object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)
- Collector는 수집(collect)에 필요한 메서드를 정의해 놓은 인터페이스
- Collects클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공
변환 - mapping(), toList(), toSet(), toMap(), toCollection() 통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarizingInt() 문자열 결합 - joining() 리듀싱 - reducing() 그룹화와 분활 - groupingBy(), partitioningBy(), collectingAndThen()
2-8. Collectors의 메서드
- 스트림을 컬렉션으로 변환 - toList(), toSet(), toMap(), toCollection()
List<String> names = stuStream.map(Student::getName) // Stream<Student> -> Stream<String> .collect(Collectors.toList()); //Stream<String> -> List<String>
- 스트림의 통계정보 제공 - counting(), summingInt(), maxBy(), minBy()
long count = stuStream.count(); long count = stuStream.collect(counting()); //Collectors.counting()
- 스트림을 리듀싱 - reducing()
Collector reducing(BinaryOperator<T> op) Collector reducing(T identity, BinaryOperator<T> op) Collector reducing(U identity, Function<T,U> mapper, BinaryOperator<U> op) //map+reduce
- 문자열 스트림의 요소를 모두 연결 - joining()
String studentNames = stuStream.map(student::getName).collect(joining());
- 스트림의 요소를 2분할 - partitioningBy()
Collector partitioningBy(Predicate predicate)
- 스트림의 요소를 그룹화 - groupingBy()
Collector groupingBy<Function classifier> Collector groupingBy<Function classifier, Collector downstream> Collector groupingBy<Function classifier, Supplier mapFactory, Collector downstream>