람다식 표현 정리
•
방법 1
public static Comparator<Book> byAuthor() {
return Comparator.comparing(Book::getAuthor);
}
Java
복사
•
방법 2
public static Comparator<Book> byTitle() {
return Comparator.comparing(new Function<Book, String>() {
@Override
public String apply(Book book) {
return book.getTitle();
}
});
}
Java
복사
•
방법 3
// 둘 다 같은 로직
public static Comparator<Book> byYear() {
return Comparator.comparingInt(book -> book.getYear());
}
public static Comparator<Book> byYear() {
return Comparator.comparingInt(new ToIntFunction<Book>() {
@Override
public int applyAsInt(Book book) {
return book.getYear();
}
});
}
Java
복사
람다식 - 생성자 참조
•
생성자를 참조한다는 것은 객체를 생성한다는 것을 의미한다.
•
람다식이 단순히 객체를 생성하고 반환하도록 구성된다면, 람다식을 생성자 참조로 대체할 수 있게 된다.
•
생성자가 오버로딩되어 여러 개가 있을 경우, 컴파일러는 함수형 인터페이스의 추상 메소드와 동일한 매개변수 타입과 개수를 가지고 있는 생성자를 찾아 실행한다.
// Person.java
package com.lambda2;
public class Person {
public Member getMember1(Creatable1 c) {
// 아이디를 가지고 멤버를 생성
String id = "summer";
Member m = c.create(id);
return m;
}
public Member getMember2(Creatable2 c) {
// 아이디와 이름을 가지고 멤버를 생성 및 반환
String id = "summer";
String name = "한여름";
Member m = c.create(id, name);
return m;
}
}
Java
복사
// Member.java
package com.lambda2;
public class Member {
private String id, name;
public Member(String id) {
this.id = id;
System.out.println("멤버(id만으로 생성)");
}
public Member(String id, String name) {
this.id = id;
this.name = name;
System.out.println("멤버(id, name으로 생성)");
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
Java
복사
package com.lambda2;
public class ConstructorExample {
public static void main(String[] args) {
Person p = new Person();
p.getMember1(new Creatable1() {
@Override
public Member create(String id) {
return new Member(id);
}
});
// p.getMember1(id -> new Member(id));
// p.getMember1(Member::new);
//
// p.getMember2((id, name) -> new Member(id, name));
p.getMember2(Member::new);
}
}
Java
복사
package com.lambda2;
@FunctionalInterface
public interface Creatable1 {
public Member create(String id);
}
// ---------------------------------------------------------------------------
package com.lambda2;
@FunctionalInterface
public interface Creatable2 {
public Member create(String id, String name);
}
Java
복사
스트림
•
stream() 메소드로 Stream 객체를 얻고, forEach() 메소드로 요소를 어떻게 처리할 지 람다식으로 제공해준다.
•
스트림의 장점
◦
내부 반복자이므로 보다 빠른 처리 속도를 가지고 있으며 병렬 처리에 효과적이다.
◦
람다식으로 다양한 요소 처리를 정의할 수 있다.
◦
중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.
•
for문, iterator는 컬렉션 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리하지만 스트림은 요소 처리 방법을 컬렉션 내부로 주입시켜서 요소를 반복 처리하는 내부 반복자이다.
•
for문과의 차이
package com.stream1;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamExample {
public static void main(String[] args) {
List<String> languageList = new ArrayList<>();
languageList.add("Java");
languageList.add("JavaScript");
languageList.add("Python");
languageList.add("C");
// 병렬 스트림 얻기
Stream<String> parallelStream = languageList.parallelStream();
parallelStream.forEach(name -> {
System.out.println(name + " : " + Thread.currentThread().getName());
});
System.out.println("===============================================");
// for 문으로 동작시키기
for (String name : languageList) {
System.out.println(name + " : " + Thread.currentThread().getName());
}
}
}
== 결과 ==
-- 순서는 바뀔 수 있음 --
JavaScript : ForkJoinPool.commonPool-worker-1
Java : ForkJoinPool.commonPool-worker-3
C : ForkJoinPool.commonPool-worker-2
Python : main
===============================================
Java : main
JavaScript : main
Python : main
C : main
Java
복사
스트림 - 중간 처리와 최종 처리
•
스트림은 하나 이상 연결될 수 있다.
•
아래 그림을 보면 컬렉션의 오리지널 스트림 뒤에 필터링 중간 스트림이 연결될 수 있고, 그 뒤에 매핑 중간 스트림이 연결될 수도 있다.
•
이와 같이 스트림이 연결되어 있는 것을 스트림 파이프라인(Stream Pipeline)이라고 한다.
•
중간 스트림은 최종 처리를 위해 요소를 걸러내거나(필터링), 요소를 변환시키거나(매핑), 정렬하는 작업을 수행한다.
•
최종 처리는 중간 처리에서 정제된 요소들을 반복하거나, 집계 처리(카운팅, 총합, 평균) 작업을 수행한다.
•
컬렉션에서 Student 스트림을 얻고, 중간 처리를 통해 score 스트림으로 변환한 후 최종 집계 처리로 score 평균을 구하는 과정이다.
package com.stream2;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalDouble;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class StreamExample {
public static void main(String[] args) {
List<Student> sList = Arrays.asList(
new Student("Alice", 90),
new Student("Bob", 80),
new Student("Jonathan", 85),
new Student("David", 95)
);
// Stream<Student> originalStream = sList.stream();
// IntStream intStream = originalStream.mapToInt(Student::getScore);
// OptionalDouble optAverage = intStream.average();
// double avg = optAverage.getAsDouble();
// 위의 주석처리된 코드를 다음과 같이 표현할 수 있다.
double avg = sList.stream()
.mapToInt(Student::getScore)
.average()
.getAsDouble();
System.out.println(avg);
}
}
Java
복사
스트림 - 리소스로부터 스트림 얻기
•
java.util.stream 패키지에는 스트림과 관련한 인터페이스들이 있다.
•
BaseStream 인터페이스를 부모로 한 자식 인터페이스는 상속 관계를 이룬다.
•
BaseStream에는 모든 스트림에서 사용할 수 있는 공통 메소드들이 정의되어 있으며,
◦
Stream은 객체 요소를 처리하는 스트림이다.
◦
IntStream, LongStream, DoubleStream은 각각 기본 타입인 int, long, double 요소를 처리하는 스트림이다.
•
스트림 인터페이스들은 주로 컬렉션과 배열에서 얻는다.
•
java.util.Collection 인터페이스는 stream() 메소드와 parallelStream() 메소드를 가지고 있기 때문에 자식 인터페이스인 List와 Set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.
package com.stream2;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ProductExample {
public static void main(String[] args) {
List<Product> pList = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
Product p = new Product(i, (int) (10000 * Math.random()), "상품" + i, "회사명");
pList.add(p);
}
Stream<Product> stream = pList.stream();
stream.forEach(System.out::println);
}
}
Java
복사
스트림 - 리소스로부터 스트림 얻기 (배열)
•
java.util.Arrays 클래스를 이용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있다.
package com.stream4;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class StreamExample {
public static void main(String[] args) {
String[] strArr = {"맥북", "이어폰", "에어팟"};
// 배열로부터 스트림 얻는 두 가지 방법
Stream<String> strStream1 = Stream.of(strArr);
Stream<String> strStream2 = Arrays.stream(strArr);
strStream1.forEach(i -> System.out.print(i + ", "));
System.out.println();
strStream2.forEach(i -> System.out.print(i + ", "));
System.out.println("\n");
int[] intArr = { 3, 1, 4, 1, 5, 9, 2 };
// 배열로부터 스트림 얻는 두 가지 방법
IntStream intStream1 = IntStream.of(intArr);
IntStream intStream2 = Arrays.stream(intArr);
intStream1.forEach(i -> System.out.print(i + ", "));
System.out.println();
intStream2.forEach(i -> System.out.print(i + ", "));
}
}
Java
복사
스트림 - 리소스로부터 스트림 얻기 (숫자 범위)
•
IntStream 또는 LongStream의 정적 메소드인 range( ) 와 rangeClosed( ) 메소드를 이용하면, 특정 범위의 정수 스트림을 얻을 수 있다.
•
range( ) 와 rangeClosed( ) 메소드의 첫 번째 매개값은 시작 수이고, 두 번째 매개값은 끝 수이다.
•
range( ) 메소드는 끝 수를 포함하지 않고, rangeClosed( ) 메소드는 끝 수를 포함한다.
package com.stream5;
import java.util.stream.IntStream;
public class StreamExample {
public static void main(String[] args) {
// 특정 범위의 정수 스트림을 만들기
int sum1 = IntStream.range(1, 10).sum();
System.out.println(sum1 + "\n");
int sum2 = IntStream.rangeClosed(1, 10).sum();
System.out.println(sum2);
}
}
Java
복사
스트림 - 리소스로부터 스트림 얻기 (파일)
•
java.nio.file.Files의 lines( ) 메소드를 이용하면 텍스트 파일의 행 단위 스트림을 얻을 수 있어서,
텍스트 파일에서 한 행씩 읽고 처리할 때 유용하게 사용할 수 있다.
(복사경로: “/Users/haminsung/Desktop/test/java”)
// data.txt - raw data
{"pno": 1, "name": "아이폰", "company": "apple", "price": 2000000}
{"pno": 2, "name": "맥북 프로", "company": "apple", "price": 3000000}
{"pno": 3, "name": "갤럭시 워치", "company": "samsung", "price": 400000}
{"pno": 4, "name": "PS5", "company": "sony", "price": 500000}
{"pno": 5, "name": "아이패드", "company": "apple", "price": 800000}
Java
복사
// data.txt - 가공
1 아이폰 apple 2000000
2 맥북프로 apple 3000000
3 갤럭시워치 samsung: 400000
4 PS5 sony 500000
5 아이패드 apple 800000
Java
복사
package com.stream6;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class StreamExample {
public static void main(String[] args) throws IOException {
String absolutePath = "/Users/haminsung/Desktop/test/java/data.txt";
Path path = Paths.get(absolutePath);
Stream<String> stream = Files.lines(path, Charset.defaultCharset());
// stream.forEach(line -> System.out.println(line));
stream.forEach(l -> {
String[] productInfo = l.split(" ");
int pno = Integer.parseInt(productInfo[0]);
String pName = productInfo[1];
String pCompany = productInfo[2];
int price = Integer.parseInt(productInfo[3]);
Product p = new Product(pno, pName, pCompany, price);
System.out.println(p);
});
stream.close();
}
}
/// Product.java ///
package com.stream6;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class Product {
private int pno;
private String pname;
private String pCompany;
private int price;
}
Java
복사
스트림 - 요소 걸러내기 (필터링)
•
필터링은 요소를 걸러내는 중간 처리 기능으로, distinct()와 filter() 메소드가 필터링에 쓰인다.
•
Predicate 인터페이스는 함수형 인터페이스로 객체를 조사하는 인터페이스다.
◦
Predicate: 객체 요소 조사 인터페이스
◦
IntPredicate, LongPredicate, DoublePredicate는 각각 기본 타입인 int, long, double 요소를 처리하는 스트림이다.
•
Predicate 인터페이스에는 매개값을 조사한 후, boolean을 반환하는 test() 메소드가 있다.
package com.stream7;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamFilterExample {
public static void main(String[] args) {
List<String> nameList = Arrays.asList("우상혁", "황선우", "김우민", "서채현", "신유빈", "우상혁");
// distinct로 중복이 제거되었다!
nameList.stream()
.distinct()
.forEach(n -> System.out.print(n + ", "));
System.out.println();
// filter로 "우"가 들어간 이름만 나온다.
nameList.stream()
.filter(n -> n.contains("우"))
.forEach(n -> System.out.print(n + ", "));
System.out.println();
System.out.println("=======================================");
// distinct로 중복된 요소가 제거하고, filter로 "우"가 들어간 이름만 나오게 하기
// nameList.stream()
// .distinct()
// .filter(n -> n.contains("우"))
// .forEach(n -> System.out.print(n + ", "));
// 리스트 형태로 출력됨
List<String> filteredList = nameList
.stream()
.distinct()
.filter(n -> n.contains("우"))
.collect(Collectors.toList()); // 리스트 형태로 변환
System.out.println(filteredList);
}
}
Java
복사
스트림 - 요소 변환 (매핑)
•
스트림의 요소를 다른 요소로 변환하는 중간 처리 기능
•
mapXxx( ) 메소드의 종류는 상당히 다양하며, 해당 요소를 변환할 때 사용된다.
◦
map( ), mapToInt( ), mapToLong( ), mapToDouble( ), mapToObj( )…
•
mapXxx() 메소드의 매개변수에는 Function 인터페이스 타입이 들어오는데, Function은 함수형 인터페이스이다.
•
모든 Function 인터페이스에는 매개값을 반환값으로 매핑(변환) 하는 applyXxx() 메소드가 있다.
•
asDoubleStream( ), asLongStream( ) 메소드는 기본 타입 간의 변환에 사용되며,
•
boxed( ) 메소드는 기본 타입 요소를 래퍼 객체 요소로 변환할 때 사용된다.
스트림 - 요소 변환 (매핑)
•
flatMapXxx() 메소드는 하나의 요소를 복수 개의 요소들로 변환해 새로운 스트림을 반환한다.
•
flatMapXxx() 메소드의 종류는 상당히 다양하며, 해당 요소를 변환할 때 사용된다.
◦
flatMap(), flatMapToInt(), flatMapToLong(), flatMapToDouble() …
•
flatMapXxx() 메소드의 매개변수에도 MapXxx()와 동일하게 Function 인터페이스 타입이 들어온다.
스트림 - 요소 정렬
•
스트림의 중간 처리 기능으로 정렬이 있고, 이는 sorted() 메소드로 사용 가능하다.
•
sorted() 메소드는 요소가 Comparable을 구현하고 있어야만 사용 가능하다.
•
만약 Comparable을 구현하지 않은 객체로 구성된 스트림인 경우에는 ClassCastException이 발생한다.
package com.stream12;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class SortExample {
public static void main(String[] args) {
List<Student> sList = new ArrayList<>();
sList.add(new Student("박명수", 60));
sList.add(new Student("유재석", 100));
sList.add(new Student("정준하", 40));
sList.stream()
.sorted((s1, s2) -> s1.getScore() - s2.getScore())
.forEach(s -> System.out.println(s));
System.out.println();
sList.stream()
.sorted((s1, s2) -> s2.getScore() - s1.getScore())
.forEach(s -> System.out.println(s));
}
}
Java
복사