수집, 컬렉션, 수집, 수집, 수집가
컬렉션은 Java 컬렉션의 조상 인터페이스입니다.
컬렉션은 java.util 패키지의 도구 클래스로, 컬렉션을 처리하기위한 다양한 정적 방법을 의미합니다.
java.util.stream.stream#collect (java.util.stream.collector <? super t, a, r>)는 스트림 수집을 담당하는 스트림의 함수입니다.
java.util.stream.collector는 수집가의 기능을 선언하는 함수를 수집하기위한 인터페이스입니다.
java.util.comparators는 일련의 수집가 구현이 내장 된 수집기 도구 클래스입니다.
수집가의 기능
Java8 스트림을 화려하고 게으른 데이터 세트 반복자로 생각할 수 있습니다. 중간 작업 (예 : 필터, 맵) 및 터미널 작업 (예 : Count, FindFirst, Foreach, Reduce)의 두 가지 유형의 작업을 지원합니다. 중간 작업을 연결하여 한 스트림을 다른 스트림으로 변환 할 수 있습니다. 이러한 작업은 스트림을 소비하지 않으며 목적은 파이프 라인을 만드는 것입니다. 대조적으로, 터미널 작업은 클래스를 소비하여 최종 결과를 초래합니다. 수집은 축소와 마찬가지로 감소 작업입니다. 다양한 방법을 매개 변수로 받아들이고 스트림의 요소를 요약 결과로 축적 할 수 있습니다. 특정 접근법은 새로운 수집기 인터페이스를 정의하여 정의됩니다.
사전 정의 된 수집가
다음은 기본 내장 수집기의 간단한 데모입니다. 시뮬레이션 된 데이터 소스는 다음과 같습니다.
최종 ArrayList <shy> dishes = lists.newarraylist (새로운 요리 ( "돼지 고기", 거짓, 800, 유형. 과일 ", true, 120, type.other), 새로운 요리 ("피자 ", True, 550, Type.other), 새로운 요리 ("새우 ", 거짓, 300, Type.fish), 새 접시 ("Salmon ", False, 450, Type.fish));최대 값, 최소값, 평균값
// 왜 선택 사항을 반환합니까? 스트림이 널이면 어떻게해야합니까? 광원은이 시점에서 옵션의 <shy> mostcaloriedish = dishes.stream (). max (comparator.comparingint (hish :: getcalories)); 선택 사항 <shy> mincaloriedish = dishes.stream (). min (comparator.comparingint (dish :: getcalories)); double avgcalories = double avgcalories = min (double avgcalories = 옵션 dishes.stream (). Collect (Collectors.avagingInt (Dish :: GetCalories)); intsummaryStatistics SummaryStatistics = Dishes.stream (). Collect (Collectors.summarizingInt (Dish :: GetCalories)); Double Average = SummaryStatistics.getAverage (); long count = summarystatistics.getCount (); int max = summarystatistics.getMax (); int min = summarystatistics.getmin (); long sum = summarystatistics.getSum ();
이 간단한 통계 지표에는 수집기 내장 수집기 기능, 특히 숫자 유형의 Unboxing 기능에 대한 수집가 기능이 있으며 포장 유형을 직접 작동하는 것보다 훨씬 저렴합니다.
수집기를 연결하십시오
스트림의 요소를 구성하고 싶습니까?
// String join1 = dishes.stream (). 맵 (dish :: getName) .collect (collectors.joining ()); // comma string join2 = dishes.stream (). map (dish :: getName) .collect (collectors.joining ( ","));
톨리스트
list <string> names = dishes.stream (). map (dish :: getName) .collect (tolist ());
원래 스트림을 단일 요소 스트림에 매핑하고 목록으로 수집하십시오.
토셋
set <type> type = dishes.stream (). 맵 (dish :: gettype) .collect (collectors.toset ());
유형을 세트로 수집하면 반복 할 수 있습니다.
TOMAP
Map <type, dish> bytype = dishes.stream (). collect (tomap (dish :: gettype, d-> d));
때로는 배열을 캐시의 맵으로 변환해야 할 수도 있으며, 이는 여러 계산 및 획득을 용이하게합니다. Tomap은 메소드 k와 v의 생성 기능을 제공합니다 (위의 데모는 구덩이이므로 이와 같이 사용할 수 없습니다 !!! Tomap (기능, 기능, 바이러스 퍼퍼)을 사용하십시오).
위는 거의 가장 일반적으로 사용되는 수집가이며 기본적으로 충분합니다. 그러나 초보자로서 이해는 시간이 걸립니다. 이것이 왜 수집하는 데 사용될 수 있는지 진정으로 이해하려면 내부 구현을 확인해야합니다. 이 수집기는 처음에 언급 된 수집가의 구현 클래스 인 java.util.stream.collectors.collectorimpl을 기반으로한다는 것을 알 수 있습니다. 커스텀 컬렉터는 나중에 특정 사용법을 배우게됩니다.
맞춤형 감소
이전 소수는 공장 감소 방법에 의해 정의 된 감소 과정의 특수한 경우입니다. 실제로, 수집가. 감소는 수집기를 만드는 데 사용될 수 있습니다. 예를 들어, 합계를 찾으십시오
정수 TotalCalories = dishes.stream (). Collect (삭제 (0, dish :: getCalories, (i, j) -> i + j)); // 화살표 기능 대신 내장 기능을 사용합니다. 정수 TotalCalories2 = dishes.stream (). Collect (0, dish :: getCalories, Integer :: Sum);
물론, 당신은 직접 감소를 사용할 수도 있습니다
옵션 <integer> totalcalories3 = dishes.stream ().지도 (dish :: getCalories) .reduce (Integer :: Sum);
괜찮지 만 효율성을 고려하면 여전히 다음을 선택해야합니다.
int sum = dishes.stream (). maptoint (dish :: getCalories) .sum ();
상황에 따라 최상의 솔루션을 선택하십시오
위에서 언급했듯이 기능 프로그래밍은 일반적으로 동일한 작업을 수행하는 여러 가지 방법을 제공합니다. 수집기 수집을 사용하는 것이 스트림 API를 사용하는 것보다 더 복잡합니다. 장점은 수집이 더 높은 수준의 추상화와 일반화를 제공 할 수 있으며 재사용 및 사용자 정의가 더 쉽다는 것입니다.
우리의 조언은 가능한 한 많은 문제에 대한 다른 솔루션을 탐색하는 것입니다. 항상 가장 전문적인 것을 선택하는 것입니다. 이는 일반적으로 가독성과 성능 측면에서 가장 좋은 결정입니다.
초기 값을 수신하는 것 외에도 최초 값을 초기 값으로 사용할 수 있습니다.
옵션 <shy> mostcaloriedish = dishes.stream () .collect (reting ((d1, d2) -> d1.getCalories ()> d2.getCalories ()? d1 : d2));
감소
감소의 사용은 상당히 복잡하며 목표는 두 값을 하나의 값으로 통합하는 것입니다.
public static <t, u> collector <t,?, u> 감소 (u Identity, function <? super t,? extends u> mapper, binaryoperator <u> op)
먼저, 나는 3 개의 제네릭을 보았다.
u는 반환 값의 유형입니다. 예를 들어, 위의 데모에서 계산 된 열은 정수입니다.
T는 스트림의 요소 유형입니다. 함수 함수에서 Mapper의 기능은 매개 변수 T를 수신 한 다음 DEC의 접시에 해당하는 결과 U.를 반환하는 것임을 알 수 있습니다.
return 값 수집기가있는 일반 목록의 중간에서 컨테이너 유형을 나타냅니다. 물론 수집가는 데이터를 저장하기 위해 컨테이너가 필요합니다. 여기? 이것은 컨테이너 유형이 불확실하다는 것을 의미합니다. 실제로, 여기의 컨테이너는 u []입니다.
매개 변수에 대해 :
ID는 반환 값 유형의 초기 값이며, 이는 축적기의 출발점으로 이해 될 수 있습니다.
Mapper는 MAP의 기능이며 그 중요성은 스트림 스트림을 원하는 유형 스트림으로 변환하는 데 있습니다.
OP는 핵심 기능이며 그 기능은 두 가지 변수를 다루는 방법입니다. 그중 첫 번째 변수는 누적 값이며,이 값은 합으로 이해 될 수 있으며 두 번째 변수는 계산할 다음 요소입니다. 따라서, 축적이 달성된다.
첫 번째 매개 변수를 생략하기위한 과부하 메소드도 있습니다. 즉, 스트림의 첫 번째 매개 변수가 초기 값으로 사용됨을 의미합니다.
Public STATIC <T> Collector <T,?, 옵션 <T >> 감소 (BinaryOperator <T> op)
반환 값의 차이점을 살펴 보겠습니다. t는 입력 값과 리턴 값 유형, 즉 입력 값 유형 및 출력 값 유형을 나타냅니다. 또 다른 차이점은 선택 사항입니다. 초기 값이없고 첫 번째 매개 변수가 무일하게 될 수 있기 때문입니다. 스트림 요소가 null이면 선택 사항을 반환하는 것이 매우 의미가 있습니다.
매개 변수 목록을 살펴보면 바이러스 수퍼가 남아 있습니다. BinaryOperator는 트리플 함수 인터페이스이며 목표는 동일한 유형의 두 매개 변수와 동일한 유형의 리턴 값을 계산하는 것입니다. 1> 2로 이해할 수 있습니까? 1 : 2, 즉 두 숫자의 최대 값을 찾으십시오. 최대 값을 찾는 것은 비교적 이해하기 쉬운 진술입니다. Lambda 표현식을 사용자 정의하여 리턴 값을 선택할 수 있습니다. 그런 다음 여기서는 두 개의 스트림의 요소 T를 수신하고 T 형의 리턴 값을 반환하는 것이 좋습니다. 또한 합을 사용하여 이해해도 괜찮습니다.
위의 데모에서 감소 및 수집 기능은 거의 동일하며 둘 다 최종 결과를 반환합니다. 예를 들어, 우리는 탁월한 효과를 줄일 수 있습니다.
// TolistCollector --- 감소의 남용, 불변 규정 남용 --- 평행 목록 <integer> calories = dishes.stream (). map (dish :: getcalories) .reduce (new arraylist <integer> (), (list <integer> l, integer e)-> {l.Adger (e); (list <intger <) l2) -> {l1.addall (l2);위의 관행을 설명하겠습니다.
<u> u 감소 (u 정체성, 이중 기능 <u,? super t, u> accumulator, binaryoperator <u> combiner);
U는 리턴 값 유형이며 여기에 목록이 있습니다
이중 공학 <u,? Super T, U> Accumulator는 축합기이며 목표는 개별 요소에 대한 값 및 계산 규칙을 축적하는 것입니다. 다음은 목록 및 요소의 작동이며 마지막으로 리턴 목록입니다. 즉, 목록에 요소를 추가하십시오.
BinaryOperator <u> combiner는 콤비너이며 목표는 리턴 값 유형의 두 변수를 하나로 병합하는 것입니다. 다음은 두 목록의 합병입니다.
이 솔루션에는 두 가지 문제가 있습니다. 하나는 의미 론적 문제이고 다른 하나는 실질적인 문제입니다. 의미 론적 문제는 감소 방법이 두 값을 결합하여 새로운 값을 생성하는 것을 목표로한다는 것입니다. 이는 불변의 감소입니다. 대신, 수집 방법의 설계는 컨테이너를 변경하고 결과를 출력하는 결과를 축적하는 것입니다. 이것은 위의 코드 스 니펫이 목록을 어큐뮬레이터로 변경하기 때문에 감소 방법을 남용하고 있음을 의미합니다. 감소 방법을 사용하는 잘못된 의미론도 실용적인 문제를 만듭니다. 여러 스레드에 의해 동일한 데이터 구조의 동시 수정이 목록 자체를 파괴 할 수 있기 때문에이 감소는 병렬로 작동 할 수 없습니다. 이 경우 스레드 안전을 원하는 경우 한 번에 새 목록을 할당해야하며 객체 할당은 성능에 영향을 미칩니다. 이것이 바로 수집이 돌연변이 가능한 컨테이너의 감소를 표현하는 데 적합한 이유이며, 더 중요한 것은 병렬 작업에 적합합니다.
요약 : 감소는 불변의 컨테이너 감소에 적합하며, 수집은 돌연변이 가능한 컨테이너 감소에 적합합니다. 수집은 병렬 처리에 적합합니다.
그룹화
데이터베이스는 종종 그룹 합산의 필요성을 만나고 프리미티브로 그룹을 제공합니다. Java에서는 교육 스타일 (수동으로 루프)을 따르는 경우 매우 번거롭고 오류가 발생하기 쉽습니다. Java 8은 기능적 솔루션을 제공합니다.
예를 들어, 그룹 접시 별 유형. 이전 Tomap과 유사하지만 그룹화 값은 접시가 아니라 목록입니다.
Map <type, list <dish>> dishesbytype = dishes.stream (). collect (Groupingby (dish :: gettype));
여기
public static <t, k> collector <t,?, map <k, list <t >>> groupingby (function <? super t,? extends k> classifier)
매개 변수 분류기는 하나의 매개 변수를 수신하고 다른 유형으로 변환하도록 설계된 함수입니다. 위의 데모는 스트림 요소 접시를 유형 유형으로 변환 한 다음 유형에 따라 스트림을 그룹화하는 것입니다. 내부 그룹화는 Hashmap을 통해 구현됩니다. GroupingBy (분류기, Hashmap :: new, 다운 스트림);
스트림 요소 자체의 속성 함수에 따라 그룹화하는 것 외에도 열 범위에 따라 그룹화와 같은 그룹화 기준을 사용자 정의 할 수도 있습니다.
GroupingBy의 매개 변수가 함수이고 함수의 매개 변수 유형이 접시라는 것을 이미 알고 있으므로 분류기를 다음과 같이 사용자 정의 할 수 있습니다.
개인 Caloriclevel getCaloriclevel (접시 d) {if (d.getCalories () <= 400) {return caloriclevel.diet; } else if (d.getCalories () <= 700) {return caloriclevel.normal; } else {return caloriclevel.fat; }}매개 변수를 전달하십시오
Map <Caloriclevel, List <Dish>> DishesByLevel = dishes.stream () .Collect (GroupingBy (this :: getCaloriclevel));
다단계 그룹화
GroupingBy는 다음과 같은 다른 여러 방법을 과부하시킵니다
public static <t, k, a, d> collector <t,?, map <k, d >> groupingby (function <? super t,? extends k> 분류기, collector <? super t, a, d> 다운 스트림)
많은 제네릭과 공포가 있습니다. 간단히 이해해 봅시다. 분류기는 또한 분류기이며 스트림의 요소 유형을 수신하고 그룹화하려는 기본, 즉 그룹화 기준의 기본을 반환합니다. 따라서 t는 스트림의 현재 요소 유형을 나타내고 k는 그룹화의 요소 유형을 나타냅니다. 두 번째 매개 변수는 다운 스트림이고 다운 스트림은 수집기 수집기입니다. 이 수집기 요소 유형은 t의 서브 클래스이고, 컨테이너 유형 컨테이너는 A이고, 감소 반환 값 유형은 D입니다. 즉, 그룹의 k는 분류기를 통해 제공되며 그룹의 값은 두 번째 매개 변수의 수집기를 통해 감소됩니다. 이전 데모의 소스 코드는 다음과 같습니다.
public static <t, k> collector <t,?, map <k, list <t >>> groupingby (function <? super t,? extends k> classifier) {return groupingby (classifier, tolist ()); }Tolist를 감소 수집기로 사용하면 최종 결과는 목록 <shy>이므로 그룹 종료의 값 유형은 List <shy>입니다. 그런 다음 값 유형은 감소 수집기에 의해 유사하게 결정될 수 있으며 수백만 개의 감소 수집기가 있습니다. 예를 들어, 값을 다시 그룹화하고 싶습니다. 그리고 그룹화도 일종의 감소입니다.
// 다중 레벨 그룹화 맵 <type, map <caloriclevel, list <dish>> bytypeandcalory = dishes.stream (). Collect (Groupingby (dish :: gettype, groupingby (this :: getCaloriclevel)); bytypeandcalory.foreach ((type, bycalory) -> {v. System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- System.out.println ( "/t" + level);검증 결과는 다음과 같습니다.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [접시 (이름 = 쇠고기, 채식 = 거짓, 칼로리 = 700, type=MEAT)]------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 타입 = 기타)]
요약 : GroupingBy의 핵심 매개 변수는 K 생성기 및 V 생성기입니다. V 생성기는 모든 유형의 수집기 수집기가 될 수 있습니다.
예를 들어, V 생성기는 숫자를 계산할 수 있으므로 표 A 그룹에서 SQL 문에서 SELECT COUNT (*)를 구현할 수 있습니다.
Map <type, long> typescount = dishes.stream (). Collect (Groupingby (dish :: gettype, counting ()); system.out.println (typescount); ---------- {fish = 2, meat = 3, 기타 = 4} SQL 검색 그룹 최고 점수 select MAX(id) from table A group by Type
Map <type, 선택적 <dish>> mostcaloricbytepe = dishes.stream () .collect (Groupingby (dish :: gettype, maxby (comparator.comparingint (dish :: getcalories)));
여기서 선택 사항은 확실히 무의미하지 않기 때문에 의미가 없습니다. 그런 다음 꺼내야했습니다. 수집 및 사용
Map <type, dish> mostCaloricByType = dishes.stream () .Collect (GroupingBy (dish :: getType, collectingandthen (maxby (maxby (compartor.comparingInt (dish :: getCalories))), 선택 사항 :: get));
결과가 여기에 나오는 것 같지만 아이디어는 동의하지 않습니다. 노란색 알람을 컴파일하고 다음으로 변경합니다.
Map <type, dish> mostCaloricByType = dishes.stream () .collect (tomap (dish :: gettype, function.identity (), binaryoperator.maxby (비교 (dish :: getcalories)));
예, GroupingBy는 Tomap이되고 키는 여전히 유형이며 값은 여전히 접시이지만 매개 변수가 하나 더 있습니다! ! 여기서 우리는 처음에 구덩이에 반응합니다. 처음의 TOMAP 데모는 쉽게 이해하기위한 것입니다. 실제로 사용되면 죽을 것입니다. 우리는 목록을지도로 재구성하는 것이 필연적으로 같은 문제에 직면한다는 것을 알고 있습니다. k가 동일 할 때 V는 그것을 무시하거나 무시합니까? 이전 데모 방법은 K를 다시 삽입하고 K가있을 때 직접 예외를 던지는 것입니다.
java.lang.ilegalstateexception : java.util.stream.collectors.lambda $ trashingmerger $ 0 (collectors.java:133)의 중복 키 접시 (이름 = 돼지 고기, 야채 = 거짓, 칼로리 = 800, 유형 = 고기)
올바른 방법은 충돌을 처리하기위한 기능을 제공하는 것입니다. 이 데모에서 충돌을 처리하는 원리는 가장 큰 것들을 찾는 것입니다. 가장 큰 것들이 그룹화 및 가장 큰 것을 찾는 데 필요한 요구 사항을 충족시키는 것입니다. (저는 더 이상 Java 8 기능 학습을하고 싶지 않습니다. 모든 곳에서 성능 문제가있는 것 같습니다)
데이터베이스 SQL 매핑 계속, select sum(score) from table a group by Type
Map <type, integer> totalcaloriesbytype = dishes.stream () .collect (Groupingby (dish :: gettype, summingint (dish :: getCalories)));
그러나 GroupingBy와 함께 종종 사용되는 다른 수집기는 매핑 방법에 의해 생성됩니다. 이 메소드는 두 가지 매개 변수를 수신합니다. 한 기능은 스트림에서 요소를 변환하고 다른 기능은 변환 된 결과 객체를 수집합니다. 목적은 축적 전에 각 입력 요소에 매핑 함수를 적용하여 특정 유형의 요소를 수신하는 수집기가 다른 유형의 객체에 적응할 수 있도록하는 것입니다. 이 수집가를 사용하는 실질적인 예를 살펴 보겠습니다. 예를 들어, 각 유형의 접시에 대한 메뉴에 열량 벨이있는 것을 얻으려고합니다. Groupingby 및 매핑 수집기를 다음과 같이 결합 할 수 있습니다.
map <type, set <caloriclevel >> caloriclevelsbytype = dishes.stream () .collect (Groupingby (dish :: gettype, 매핑 (this :: getCaloriclevel, toset ()));
여기의 TOSET은 기본적으로 해시 세트를 사용하며 특정 구현 토 수집 (Hashset :: New)을 수동으로 지정할 수도 있습니다.
분할
분할은 특별한 그룹화의 경우입니다. 술어 (부울 값을 반환하는 함수)는 분류 함수로 사용되며, 이는 파티션 함수라고합니다. 파티션 함수는 부울 값을 반환합니다. 즉, 그룹화 된 맵의 주요 유형이 부울이므로 True 또는 False의 최대 두 그룹으로 나눌 수 있습니다. 예를 들어, 채식인이라면 채식주의 자 및 비 채식주의 자로 메뉴를 분리 할 수 있습니다.
Map <boolean, list <shy>> partitionedMenu = dishes.stream (). collect (partitioningby (dish :: isvegetarian));
물론 필터를 사용하면 동일한 효과를 얻을 수 있습니다.
목록 <shy> echetariandishes = dishes.stream (). 필터 (Dish :: isvegetarian) .collect (collectors.tolist ());
파티셔닝의 장점은 두 개의 사본을 저장하는 것입니다. 목록을 분류하려는 경우 유용합니다. 동시에 GroupingBy와 같이 PartitioningBy는 그룹화 값의 유형을 지정할 수있는 방법을 과부하 시켰습니다.
Map <boolean, map <type, list <dish <shy >>> 채식주의 자식은 dishes.stream () .collect (partitioningby (dish :: isvegetarian, groupingby (dish :: gettype)); Map <boolean, integer> 채식주의 자 디스 토탈 칼로리 = 채식주의 자 (dishes.stream). summingint (dish :: getCalories)));지도 <부울, dish> mostCaloricPartitionedByVegetarian = dishes.stream () .Collect (partitioningby (dish :: isvegetarian, collectingandthen (maxby (dish :: getcalories)), 옵션 :: get));
Partitioningby Collector를 사용하는 마지막 예로, 메뉴 데이터 모델을 제쳐두고보다 복잡하고 흥미로운 예를보십시오. 배열을 프라임 및 비 프라임 번호로 나누십시오.
먼저 주요 파티션 함수를 정의하십시오.
개인 부울 ISPRIME (int 후보) {int ictubtateroot = (int) Math.sqrt ((이중) 후보); intstream.rangeclosed (2, candidateroot) .nonematch (i-> 후보 % i == 0);}그런 다음 1 ~ 100의 프라임 및 비 프라임 번호를 찾으십시오.
Map <boolean, list <integer >> partitionPrimes = intstream.rangeclosed (2, 100) .boxed () .collect (partitioningby (this :: isprime));