Backend
home

2024. 7. 24

람다식 표현 정리

방법 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
복사