식(expression)
으로 표현한것이다. 실제구현에서는 익명 함수
형태로 사용된다. int method(){
return (int) (Math.random()*5)+1;
}
//위 메서드를 람다식으로 표현하면
(i) -> (Math.random()*5)+1;
자바에서는 ->
와 같은 화살표의 형태를 기호로 사용하여 매개변수를 함수 바디로 전달한다.
람다식의 형태는 () -> { ... };
과 같이 이루어져있고 ()
에는 파라미터가 {...}
에는 실행문이 들어간다.
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.stream().forEach((Integer i) -> {System.out.println("list = " + i);});
/*결과
list = 1
list = 2
list = 3
*/
(Integer i) -> {System.out.println("list = " + i);}
이 코드가 위에서 말한 람다식의 형태이다.(Integer i) -> System.out.println("list = " + i)
실행문이 한줄로 이루어져 있을때 중괄호를 뺄수있다. 이때 ;
세미콜론도 빼줘야한다.i -> System.out.println("list = " + i)
와 같이 타입을 생략해줘도 된다.System.out.println("list = " + i)
(System.out::println)
와 같이 표현해줄수도있다.람다표현식을 담을수 있는타입
@FunctionalInterface
를 인터페이스 위에 적어줌으로서 컴파일 검사를 진행할수있다.@FunctionalInterface // 이런식으로
public interface InterfaceSample{
void testMethod()
}
testMethod()
메서드가 하나라 오류가 안나지만 추가적인 메서드가 있다면 오류가 발생하게 된다.여기서 T는 Type을 R은 Return Type을 나타낸다
java.lang.Runnable
-> 매개변수도 없고, 반환값도 없다Supplier<T>
-> 매개변수는 없고, 반환값만 있다. (랜덤숫자 출력해줄때 등..)Consumer<T>
-> Supplier와 반대로 매개변수만 있고, 반환값이 없다.(매개변수를 받아 출력할때 등..)Function<T,R>
-> 일반적인 함수, 하나의 매개변수를 받아서 결과를 반환한다.(사용할때 apply 이용)public interface Function<T, R> {
R apply(T t);
}
Predicate<T>
-> 조건식을 표현하는데 사용된다. 매개변수는 하나, 반환타입은 boolean
//Predicate<T>의 사용예시, String의 length가 0이면 true를 반환
Predicate<String> isEmptyStr = s -> s.length() ==0;
String s = "";
if(isEmptyStr(s)){
System.out.println("This is an empty String.");
}
매개변수가 두개인 함수형 인터페이스
T,U,V,W.. 모두 매개변수를 의미한다.
Biconsumer<T,U>
-> 두개의 매개변수만 있고, 반환값이 없다.
BiPredicate<T,U>
-> 조건식을 표현하는데 사용, 매개변수는2개, 반환값은 boolean.
BiFunction<T,U,R>
-> 두개의 매개변수를 받아 하나의 결과를 반환
매개변수가 3개 이상일때
Functional<String,Integer> f = (String s) -> Integer.parseInt(s);
Functional<String,Integer> f = Integer::parseInt;
//생성자의 메소드 참조
Supplier<MyClass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new;
//매개변수가 있는 생성자
Function<Integer,MyClass> f = (i) -> new MyClass(i); //람다식
Function<Integer,MyClass> f2 = new MyClass:: new; // 메서드 참조
Collection F/W를 통해 관리하는 데이터를 처리하기 위해 주로 사용한다.
기존에 우리는 List 에서는 정렬을위해 Collection.sort()를 사용하고, 배열을 정렬할때는 Arrays.sort()를 사용했는데 스트림을 사용하면 데이터 소스를 추상화시켜 같은 기능의 메서드들의
중복을 해결할수 있다.
Stream<String> strStream1 = strList.stream();// strList는 정렬되지 않은 list라 할때
List<String>sortedStrList = strStream1.sorted().collect(Collectors.toList()); //이런식으로 정렬한 Stream을 저장후 사용가능하다
Stream은 일회용이다.
스트림은 한번 사용하면 닫혀서 다시사용할수없다. 이미 사용한 스트림을 재사용하고싶다면 스트림을 다시 생성해야한다.
스트림은 작업을 내부 반복으로 처리한다.
스트림은 반복문을 내부에 숨길수있다. forEach()라는 스트림에 정의된 메서드를 통해 반복된 행위를 할수있다.
for(String str:strList)
System.out.println(str);
stream.forEach(System.out::println) // (str) -> System.out.println(str)을 메서드 참조를 통해 표현;
오타(forEact -> forEach)
Collection
List<String> list = Arrays.asList("A","B","C");
Stream<String> stream = list.stream();
IntStream
과 LongStream
은 지정된 범위의 연속된 정수를 스트림으로 생성하는 range()
와 rangeClose()
를 가지고있다.range()
-> 경계의 끝이 포함되지 않는다.ranged()
-> 경계의 끝이 포함된다.Stream은 연산의 연결을 통해 파이프라인을 구성할수있다. 즉 다양한 연산을 조합할수 있다.
이때 각 연산에 필요한 람다표현식을 적용하는것이 중요하다
스트림은 중간연산과 최종 연산이 있다.
중간 연산
최종 연산이 실행되어야 중간연산이 처리된다.즉 중간연산들로만 구성된 메소드 체인은 실행되지 않는다.
skip(), limit()
skip(3)
-> 처음 3개의 요소를 건너뛴다.
limit(5)
-> 스트림의 요소를 5개로 제한한다.
IntStream intStream=IntStream.rangedClosed(1,10);
intStream.skip(3).limit(5).forEach(System.out::print); //4,5,6,7,8
filter(),distinct()
distinct()
-> 중복된 요소들을 제거한다.
filter()
-> 주어진 조건에 맞지 않는 요소를 걸러낸다. 이때 filter()는 매개변수로 Predicate를 필요로한다
Predicate대신 i -> i%2 ==0 와 같은 람다식을 사용해도 된다.
intStream.filter(i->i%2==0).forEach(System.out::print);
필요에따라 .filter().filter() 처럼 여러개의 filter를 연결해도 된다.
sorted()
sorted()
는 Stream을 정렬할때 사용한다.
map()
map()
은 Stream의 요소에서 저장된 값들중 원하는 필드만 뽑아내거나 특정 형태로 변환할때 사용하며, 연산결과는 Stream<T>
이다.
List<String> names=Arrays.asList("John","Jane","Jim");
// Use the map method to convert each name to uppercase
List<String> upperCaseNames=names.stream()
.map(name->name.toUpperCase())
.collect(Collectors.toList());
// Print the result
upperCaseNames.forEach(System.out::println);
//result
JOHN
JANE
JIM
mapToInt(),mapToLong(),mapToDouble()
map()의 연산결과는 Stream<T>
이다. 이때 스트림의 요소를 숫자로 변환하는 경우 IntStream
과 같은 기본형 스트림으로 반환하는게 더 유리할때가 있다. 이때 사용하는것이 위 3개의 메서드이다.
Stream<T>
는 count()만 지원하지만 IntStream과 같은 기본형 스트림은 sum(), average(), max(), min()
숫자를 다루기 편한 메서드를 제공한다.
sum(), average(), max(), min()
이 4개의 메서드들은 최종연산이라 sum()
과 average()
를 출력한다던가 두개를 연속으로 사용할수 없다.
반대로 IntStream
을 Stream로 변환할때는 mapToObj()
를 Stream로 반환할때는 boxed()
를 사용한다.
스트림의 요소가 배열이거나 map()의 연산결과가 배열일때, 즉 스트림의 타입이 Stream<T[]>
인 경우, Stream로 다루는게 편할때가 있는데 이때 map대신 flatMap()
을 사용하면 된다.
즉, 메서드에 의해 생성된 모든 개별 스트림이 하나의 단일 스트림으로 병합된다고 생각하면된다.
peek()
은 연산과 연산 사이에 올바르게 처리가 되고있는지 확인하고 싶을때 사용한다.filter()
또는 map()
의 결과를 확인할때 유용하다.최종연산
List<String> list = Arrays.asList("APPLEMANGO","BANANA","CHOCOLATE");
List<String> isContainApple = list.stream()
.filter(str -> str.contains("APPLE"))
.collect(Collectors.toList());
System.out.println(isContainApple);
//출력
// [APPLEMANGO]