ストリームの概要
Java 8 API には、宣言的な方法でデータを操作できる Stream と呼ばれる新しい抽象化が追加されています。Stream は、SQL ステートメントを使用してデータベースからデータをクエリするのと同様の直感的な方法で、Java のコレクション操作と表現の高レベルの抽象化を提供します。
Stream API を使用すると、Java プログラマーの生産性が大幅に向上し、効率的でクリーンで簡潔なコードを作成できるようになります。
このスタイルは、要素のコレクションをストリームとして処理するものとみなし、パイプラインで送信され、フィルタリング、並べ替え、集計などのパイプラインのノードで処理できます。
要素フローはパイプラインの中間操作で処理され、最後に最終操作で前の処理の結果が得られます。
ストリームとは何ですか?
ストリーム (ストリーム) はデータ ソースからの要素のキューであり、集計操作をサポートします。
- 要素は、キューを形成する特定のタイプのオブジェクトです。Java のストリームは要素を保存しませんが、オンデマンドで要素を計算します。
- データ ソース ストリームのソース。コレクション、配列、I/O チャネル、ジェネレーターなどが考えられます。
- 集計操作は 、フィルター、マップ、リデュース、検索、一致、並べ替えなどの SQL ステートメントに似ています。
以前のコレクション操作とは異なり、ストリーム操作には次の 2 つの基本機能もあります。
- パイプライン処理: 中間操作はストリーム オブジェクト自体を返します。この方法では、Fluent スタイルと同様に、複数の操作をパイプラインに連結できます。そうすることで、遅延やショートサーキットなどの最適化が可能になります。
- 内部反復: 以前は、コレクションの走査は Iterator または For-Each を通じて実行され、コレクションの外側で明示的に反復されていました。これは外部反復と呼ばれます。ストリームは、訪問者パターン (Visitor) を通じて実装される内部反復メソッドを提供します。
なぜストリームが必要なのか
Java 8 のハイライトとして、Stream は java.io パッケージの InputStream および OutputStream とはまったく異なる概念です。これは、StAX の XML 解析用の Stream とも異なりますし、Amazon Kinesis のビッグデータのリアルタイム処理用の Stream とも異なります。Java 8 のストリームは、コレクション (コレクション) オブジェクト関数の拡張機能であり、コレクション オブジェクトに対するさまざまな非常に便利で効率的な集約操作 (集約操作) またはバルク データ操作 (バルク データ操作) に焦点を当てています。Stream API は、同じ新しい Lambda 式によってプログラミングの効率とプログラムの読みやすさを大幅に向上させます。同時に、集約操作にシリアル モードとパラレル モードを提供し、マルチコア プロセッサの利点を最大限に活用できるコンカレント モードでは、フォーク/ジョイン パラレル モードを使用してタスクを分割し、処理プロセスを高速化します。通常、並列コードの作成は難しく、エラーが発生しやすくなりますが、Stream API を使用すると、マルチスレッド コードを 1 行も記述することなく、高性能の並列プログラムを簡単に作成できます。したがって、Java 8 で初めて登場した java.util.stream は、関数型言語 + マルチコア時代の総合的な影響の産物です。
集計操作とは何ですか
従来の J2EE アプリケーションでは、Java コードは多くの場合、次のようなことを実現するためにリレーショナル データベースの集計操作に依存する必要があります。
- 月あたりの平均顧客支出額
- 最も高価な販売品
- 今週完了した有効な注文 (無効な注文を除く)
- ホームページの推奨事項として 10 個のデータ サンプルを取得する
そのような操作。
しかし、今日のデータ爆発の時代では、データソースが多様化し、データが膨大になると、多くの場合、RDBMS を離れるか、最下層から返されたデータに基づいてより高いレベルのデータ統計を実行する必要があります。Java のコレクション API には、少数の補助メソッドしかなく、多くの場合、プログラマは Iterator を使用してコレクションを走査し、関連する集計アプリケーション ロジックを完了する必要があります。これは効率的とは程遠く、不器用なアプローチです。Java 7 では、タイプが食料品であるすべてのトランザクションを検索する場合は、トランザクション値の降順でソートされた一連のトランザクション ID を返します。
流れを生成する
Java 8 では、コレクション インターフェイスにストリームを生成する 2 つのメソッドがあります。
-
stream() -コレクションのシリアルストリームを作成します。
-
ParallelStream() -コレクションの並列ストリームを作成します。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
Collector インターフェースのメソッド:
1 List toList() ストリーム内の要素をリストに収集します。
例: List<Employee> emps= list.stream().collect(Collectors.toList());
2 Set toSet() Set するストリーム内の要素を収集します
。 例: Set<Employee> emps= list.stream().collect(Collectors.toSet());
3 Collection toCollection() ストリーム内の要素を作成したコレクションに収集します。
例: Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
4 Long counting() ストリーム内の要素の数をカウントします。
例: long count = list.stream().collect(Collectors.counting());
5 Integer summingInt() ストリーム内の要素の整数属性を合計します。
例: int total=list.stream().collect(Collectors.summingInt(Employee::getAge));
6 Double averagingInt() ストリーム内の要素の Integer 属性の平均値を計算します。
例: double avg = list.stream().collect(Collectors.averagingInt(Employee::getAge));
7 IntsummaryStatistics summarizingInt() ストリーム内の Integer 属性の統計値を収集します。例: 平均値
例: int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getAge));
String join() ストリーム内の各文字列を文字列で結合します
例: String str= list.stream().map(Employee::getName).collect(Collectors.joining());
例: String str= list.stream() 。 map(Employee::getName).collect(Collectors.joining(",", "{", "}")));
例: String str= list.stream().map(Employee::getName).collect ( Collectors.joining(","));
9: オプション maxBy() コンパレータに応じて最大値を選択します
例: Optional<Emp>max= list.stream().collect(Collectors.maxBy(Comparator.comparingDouble(Employee: : getSalary)));
10:Optional minBy() コンパレータに従って最小値を選択します。
例: Optional<Emp> min = list.stream().collect(Collectors.minBy(Comparator.comparingDouble(Employee::getSalary)));
11:reducing() はアキュムレータとして初期値から開始し、BinaryOperator を使用してストリーム内の要素を 1 つずつ結合して単一の値に削減します。 例: int total=list.stream().
collect(Collectors.reducing(0) 、従業員::getAge、整数::sum));
12:collectionAndThen() 変換関数によって返される型は別のコレクターをラップします
結果変換関数の例: int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)) ;
13:Map<K, List> groupingBy() 特定の属性値に従ってストリームをグループ化します。属性は K、結果は V
例: Map<String, List<Employee>> map= list.stream().collect (Collectors.groupingBy( Employee::getName));
14:Map<Boolean, List> PartitioningBy() 収集後の true または false に応じたパーティション化
例:employees.stream().collect(Collectors.partitioningBy(employee ->employee.getSalary()>8000,Collectors.mapping(Employee: :getName,Collectors.counting()))).forEach((Key, value)-> System.out.println(Key+"----"+value));
15:mapping() メソッドは結果を別のコレクターに適用します。
例:employees.stream().collect(Collectors.partitioningBy(employee ->employee.getSalary()>8000,Collectors.mapping(Employee::getName,Collectors.toList()))).forEach((Key, value) -> System.out.println(キー+"----"+値));
16: flatMapping () – Collectors.mapping () メソッドに似ていますが、より細かい粒度です。どちらも要素を収集するために関数とコレクター パラメーターを受け取りますが、 flatMapping 関数は要素ストリームを受け取り、コレクターを通じて蓄積操作を実行します。コレクターは通常、collect() でネストされたストリームを処理するために使用されます。
17:filtering() – Stream filter() メソッドと同様で、入力要素をフィルタリングするために使用され、groupingBy/partitioningBy と組み合わせてよく使用されます。
18:toUnmodifiableMap() – 要素を変更不可能なマップに収集します。マップ内のオブジェクト アドレスは変更できません。マップ内のオブジェクトが変更をサポートしている場合、実際には変更できます。
19:toUnmodifiableSet() - 要素を変更不可能な HashSet に収集します
20:toMap() - 要素をマップに収集します
21:toConcurrentMap() - 同時実行をサポートする concurrentHashMap に要素を収集します。
22:toUnmodifiableList() - 要素を変更不可能な ArrayList に収集します
23: groupingByConcurrent() - groupingBy() に似ていますが、集約されたコレクションは同時実行されます。HashMap は同時実行をサポートしており、並列ストリームのグループ化の効率を向上させることができます。
24:teeing() – 2 つのダウンストリーム コレクターから構成されるコレクターを返します。結果のコレクターに渡された各要素は下流のコレクターによって処理され、その結果は指定されたマージ関数を使用して最終結果にマージされます。
2 つの個別のコレクターを使用してストリームを収集し、提供された bifunction を使用して結果をマージすることをサポートします。
包括的な例
1. エンティティクラスとデータを作成する
ユーザー.java
import java.math.BigDecimal;
/**
* 用户信息实体类 by 青冘
**/
public class User
{
private int id; //用户ID
private String name; //用户名称
private String sex; //性别
private int age; //年龄
private String department; //部门
private BigDecimal salary; //薪资
//构造方法
public User(int id,String name,String sex,int age,String department,BigDecimal salary)
{
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
this.department = department;
this.salary = salary;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex = sex;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public String getDepartment()
{
return department;
}
public void setDepartment(String department)
{
this.department = department;
}
public BigDecimal getSalary()
{
return salary;
}
public void setSalary(BigDecimal salary)
{
this.salary = salary;
}
@Override
public String toString()
{
return "ID:" + this.id + " 名称:" + this.name + " 性别:" + this.sex
+ " 年龄:" + this.age + " 部门:" + this.department + " 薪资:" + this.salary + "元";
}
}
UserService.class
import com.pjb.streamdemo.entity.User;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 用户信息业务逻辑类 by 青冘
**/
public class UserService
{
/**
* 获取用户列表
*/
public static List<User> getUserList()
{
List<User> userList = new ArrayList<User>();
userList.add(new User(1, "青冘的博客_01", "男", 32, "研发部", BigDecimal.valueOf(1600)));
userList.add(new User(2, "青冘的博客_02", "男", 30, "财务部", BigDecimal.valueOf(1800)));
userList.add(new User(3, "青冘的博客_03", "女", 20, "人事部", BigDecimal.valueOf(1700)));
userList.add(new User(4, "青冘的博客_04", "男", 38, "研发部", BigDecimal.valueOf(1500)));
userList.add(new User(5, "青冘的博客_05", "女", 25, "财务部", BigDecimal.valueOf(1200)));
return userList;
}
}
2. 問い合わせ方法
2.1 forEach()
forEach() を使用してリスト データを反復処理します。
/**
* 使用forEach()遍历列表信息 by 青冘
*/
@Test
public void forEachTest()
{
//获取用户列表
List<User> userList = UserService.getUserList();
//遍历用户列表
userList.forEach(System.out::println);
}
上記の走査ステートメントは、次のステートメントと同等です。
userList.forEach(user -> {System.out.println(user);});
結果:
2.2 フィルタ(T -> ブール値)
filter() を使用してリストデータをフィルタリングします
所属部署が「研究開発部」のユーザーの一覧を取得します。
/**
* 使用filter()过滤列表信息 by 青冘
*/
@Test
public void filterTest()
{
//获取用户列表
List<User> userList = UserService.getUserList();
//获取部门为“研发部”的用户列表
userList = userList.stream().filter(user -> user.getDepartment() == "研发部").collect(Collectors.toList());
//遍历用户列表
userList.forEach(System.out::println);
}
結果:
2.3 findAny() と findFirst()
findAny() と findFirst() を使用して、最初のデータを取得します。
ユーザー名が「Qingli's Blog_02」のユーザー情報を取得し、見つからない場合はnullを返します。
/**
* 使用findAny()获取第一条数据 by 青冘
*/
@Test
public void findAnytTest()
{
//获取用户列表
List<User> userList = UserService.getUserList();
//获取用户名称为“青冘的博客_02”的用户信息,如果没有找到则返回null
User user = userList.stream().filter(u -> u.getName().equals("青冘的博客_02")).findAny().orElse(null);
//打印用户信息
System.out.println(user);
}
結果:
注: findFirst() と findAny() はどちらもリスト内の最初のデータ項目を取得しますが、findAny() は動作し、返される要素は不確実です。同じリストに対して findAny() を複数回呼び出すと、異なる値が返される可能性があります。パフォーマンスをより効率的にするには、findAny() を使用してください。データが少ない場合、シリアルの場合は通常最初の結果が返されますが、パラレル(ParallelStream)の場合は最初の結果であるとは限りません。
2.4map(T -> R) と flatMap(T -> Stream)
map() を使用して、ストリーム内の各要素 T を R にマップします (型変換と同様)。
flatMap() を使用して、ストリーム内の各要素 T をストリームにマップし、各ストリームをストリームに接続します。
map() メソッドを使用して、ユーザー リストの名前列を取得します。
/**
* 使用map()获取列元素 by 青冘
*/
@Test
public void mapTest()
{
//获取用户列表
List<User> userList = UserService.getUserList();
//获取用户名称列表
List<String> nameList = userList.stream().map(User::getName).collect(Collectors.toList());
//或者:List<String> nameList = userList.stream().map(user -> user.getName()).collect(Collectors.toList());
//遍历名称列表
nameList.forEach(System.out::println);
}
返される結果は配列型であり、次のように記述されます。
//数组类型
String[] nameArray = userList.stream().map(User::getName).collect(Collectors.toList()).toArray(new String[userList.size()]);
結果:
flatMap() を使用して、ストリーム内の各要素をストリームに連結します。
/**
* 使用flatMap()将流中的每一个元素连接成为一个流 by 青冘
*/
@Test
public void flatMapTest()
{
//创建城市
List<String> cityList = new ArrayList<String>();
cityList.add("北京;上海;深圳;");
cityList.add("广州;武汉;杭州;");
//分隔城市列表,使用 flatMap() 将流中的每一个元素连接成为一个流。
cityList = cityList.stream()
.map(city -> city.split(";"))
.flatMap(Arrays::stream)
.collect(Collectors.toList());
//遍历城市列表
cityList.forEach(System.out::println);
}
結果:
2.5 個別()
重複データを削除するには、distinct() メソッドを使用します。
部門のリストを取得し、重複したデータを削除します。
/**
* 使用distinct()去除重复数据 by 青冘
*/
@Test
public void distinctTest()
{
//获取用户列表
List<User> userList = UserService.getUserList();
//获取部门列表,并去除重复数据
List<String> departmentList = userList.stream().map(User::getDepartment).distinct().collect(Collectors.toList());
//遍历部门列表
departmentList.forEach(System.out::println);
}
結果:
2.6 リミット(long n) と スキップ(long n)
limit(long n) メソッドは最初の n データを返すために使用され、skip(long n) メソッドは最初の n データをスキップするために使用されます。
/**
* limit(long n)方法用于返回前n条数据
* skip(long n)方法用于跳过前n条数据 by 青冘
*/
@Test
public void limitAndSkipTest()
{
//获取用户列表
List<User> userList = UserService.getUserList();
//获取用户列表,要求跳过第1条数据后的前3条数据
userList = userList.stream()
.skip(1)
.limit(3)
.collect(Collectors.toList());
//遍历用户列表
userList.forEach(System.out::println);
}
結果:
3. 統計的手法
3.1 Reduce((T, T) -> T) と Reduce(T, (T, T) -> T)
合計、積、最大化など、ストリーム内の要素を結合するには、reduce((T, T) -> T) およびreduce(T, (T, T) -> T) を使用します。
@Test
public void testReduce() {
Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8});
//求集合元素之和
System.out.println("求集合元素之和");
Integer result = stream.reduce(0, Integer::sum);
System.out.println(result);
stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
//求和
System.out.println("求和");
stream.reduce((i, j) -> i + j).ifPresent(System.out::println);
stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
//求最大值
System.out.println("求最大值");
stream.reduce(Integer::max).ifPresent(System.out::println);
stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
//求最小值
System.out.println("求最小值");
stream.reduce(Integer::min).ifPresent(System.out::println);
stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
//做逻辑
System.out.println("做逻辑");
stream.reduce((i, j) -> i > j ? j : i).ifPresent(System.out::println);
stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
//求逻辑求乘机
System.out.println("求逻辑求乘机");
int result2 = stream.filter(i -> i % 2 == 0).reduce(1, (i, j) -> i * j);
Optional.of(result2).ifPresent(System.out::println);
}
結果:
3.2 mapToInt(T -> int) 、mapToDouble(T -> double) 、mapToLong(T -> long)
int sumVal = userList.stream().map(User::getAge).reduce(0,Integer::sum); 要素の合計を計算するメソッドはボックス化コストを意味し、map(User::getAge) メソッドはStream 型になると、各 Integer はボックス化されてプリミティブ型になり、sum メソッドで合計する必要があるため、効率に大きく影響します。この問題を解決するために、Java 8 では数値ストリーム IntStream、DoubleStream、LongStream が良心的に導入されており、このストリームの要素はすべてプリミティブ データ型、つまり int、double、long です。
ストリームは数値ストリームに変換されます。
- mapToInt(T -> int) : IntStream を返します
- mapToDouble(T -> double) : DoubleStream を返します
- mapToLong(T -> long) : LongStream を返します
//用户列表中年龄的最大值、最小值、总和、平均值
int maxVal = userList.stream().mapToInt(User::getAge).max().getAsInt();
int minVal = userList.stream().mapToInt(User::getAge).min().getAsInt();
int sumVal = userList.stream().mapToInt(User::getAge).sum();
double aveVal = userList.stream().mapToInt(User::getAge).average().getAsDouble();
3.3 counting() と count()
リストデータは counting() と count() を使用してカウントできます。
//统计研发部的人数,使用 counting()方法进行统计
Long departCount = userList.stream().filter(user -> user.getDepartment() == "研发部").collect(Collectors.counting());
//统计30岁以上的人数,使用 count()方法进行统计(推荐)
Long ageCount = userList.stream().filter(user -> user.getAge() >= 30).count();
3.4 summingInt()、summingLong()、summingDouble()
合計を計算するには、関数の引数を受け取ります。
//计算年龄总和
int sumAge = userList.stream().collect(Collectors.summingInt(User::getAge));
3.5 averagingInt()、averagingLong()、averagingDouble()
平均を計算するために使用されます
//计算平均年龄
double aveAge = userList.stream().collect(Collectors.averagingDouble(User::getAge));