首页 > 技术文章 > Java8 Streams流

dongguangming 2020-11-28 15:42 原文

自从java8 引入了streams的方略,我就爱不释手,总结下以前开发中用到的情况。

前提把备用java类准备好:

性别用枚举表示

public enum Gender {

	MALE("男"), FEMALE("女"), UNKNOWN("未知");
	 
	private final String gender;

	private Gender(String gender) {
		this.gender = gender;
	}

	public String getGender() {
		return gender;
	}
}

用户类:

/**
 * @author dgm
 * @describe "用户类"
 * @date 2020年11月28日
 */
public class User implements java.io.Serializable {

	 private static final long serialVersionUID = 1L;

	    /**
	     * 编号(唯一标识)
	     */
	    protected Long id;
	    
	    /**
	     * 登录名
	     */
		private String username;
	    /**
	     * 昵称
	     */
		private String nickname;
	    /**
	     * 密码
	     */
		private String password;
	   
	    /**
	     * 手机号码
	     */
		private String phone;
	    /**
	     * 邮箱地址
	     */
		private String email;
		
		 /**
	     * 所属身份
	     */
		private String province;
		
		/**
		 * 年龄
		 */
		private int age;
		
		/**
		 * 性别
		 */
		private Gender gender;
		
        /**
         * 头像
         */
		private String icon;

		/**
		 * 账户是否锁定
		 */
		private Boolean locked;

		public User(Long id, String username, String nickname, String password,
				String phone, String email, String province, int age, Gender gender/*, String icon,
				Boolean locked*/) {
			super();
			this.id = id;
			this.username = username;
			this.nickname = nickname;
			this.password = password;
			this.phone = phone;
			this.email = email;
			this.province = province;
			this.age = age;
		    this.gender = gender;
			//this.icon = icon;
			//this.locked = locked;
		}

		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		public String getUsername() {
			return username;
		}

		public void setUsername(String username) {
			this.username = username;
		}

		public String getNickname() {
			return nickname;
		}

		public void setNickname(String nickname) {
			this.nickname = nickname;
		}

		public String getPassword() {
			return password;
		}

		public void setPassword(String password) {
			this.password = password;
		}

		public String getPhone() {
			return phone;
		}

		public void setPhone(String phone) {
			this.phone = phone;
		}

		public String getEmail() {
			return email;
		}

		public void setEmail(String email) {
			this.email = email;
		}

		public int getAge() {
			return age;
		}

		public void setAge(int age) {
			this.age = age;
		}

		public String getProvince() {
			return province;
		}

		public void setProvince(String province) {
			this.province = province;
		}

		public Gender getGender() {
			return gender;
		}

		public void setGender(Gender gender) {
			this.gender = gender;
		}

		public String getIcon() {
			return icon;
		}

		public void setIcon(String icon) {
			this.icon = icon;
		}

		public Boolean getLocked() {
			return locked;
		}

		public void setLocked(Boolean locked) {
			this.locked = locked;
		}

		@Override
		public String toString() {
			return "User [id=" + id + ", username=" + username + ", nickname="
					+ nickname + ", password=" + password + ", phone=" + phone
					+ ", email=" + email + ", province=" + province
					+ ", gender=" + gender.getGender() + ", age=" + age+ "]";
		}
}

//用户订单
public class Order implements  java.io.Serializable, Comparable<Order> {
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private Long orderId;//订单id号
    private String username;//顾客名(方便演示,对应user里username)
    private int amount;//此订单消费总额

    public Order(Long orderId, String username, int amount) {
        this.orderId = orderId;
        this.username = username;
        this.amount = amount;
    }
    。。。。。。略
 }

数据构造,组装成数据集合(特别注意实际上是从数据库检索出来的,如下:select * from user,映射成User数据列表),出于演示,就人为构造数据

public class StreamTest {

	public static void main(String args[]) {
		//手工构造数据
       User dgm = new User(1L,"dgm","董广明","123456","15850669069","1056764180@qq.com","河南", 32,Gender.MALE);
	   //System.out.println(dgm);
       User wangwu = new User(2L,"wangwu","王五","12345678","13850669069","21056764180@qq.com","河南",22,Gender.MALE);
       User maliu = new User(3L,"maliu","马六","123456","14850669069","3056764180@qq.com","河南", 18,Gender.UNKNOWN);
       User zhangsan = new User(4L,"zhangsan","张三","123456000","16850669069","4056764180@qq.com","江苏",24,Gender.MALE);
       User masi = new User(5L,"nasi","马四","123456789","17850669069","5056764180@qq.com","江苏",31,Gender.FEMALE);
       User gaoba = new User(6L,"gaoba","高八","654321","18850669069","6056764180@qq.com","安徽",40,Gender.MALE);
       User dongguangming = new User(7L,"dongguangming","董","65432111","19850669069","9056764180@qq.com","河南",31,Gender.MALE);

       //组装成数据集合(特别注意实际上是从数据库检索出来的,如下:select * from user,映射成User数据列表)
       List<User> userList = Arrays.asList(dgm,wangwu,zhangsan,maliu,masi,gaoba,dongguangming);
       for(User user: userList) {
    	   System.out.println(user);
       }
    }
}

打印输出:

User [id=1, username=dgm, nickname=董广明, password=123456, phone=15850669069, email=1056764180@qq.com, province=河南, gender=男, age=32]
User [id=2, username=wangwu, nickname=王五, password=12345678, phone=13850669069, email=21056764180@qq.com, province=河南, gender=男, age=22]
User [id=4, username=zhangsan, nickname=张三, password=123456000, phone=16850669069, email=4056764180@qq.com, province=江苏, gender=男, age=24]
User [id=3, username=maliu, nickname=马六, password=123456, phone=14850669069, email=3056764180@qq.com, province=河南, gender=未知, age=18]
User [id=5, username=nasi, nickname=马四, password=123456789, phone=17850669069, email=5056764180@qq.com, province=江苏, gender=女, age=31]
User [id=6, username=gaoba, nickname=高八, password=654321, phone=18850669069, email=6056764180@qq.com, province=安徽, gender=男, age=40]
User [id=7, username=dongguangming, nickname=董, password=65432111, phone=19850669069, email=9056764180@qq.com, province=河南, gender=男, age=31]

 

下面就列举几种情况:

1.  按条件返回数据给前端,转list

1.1  早期实现,基于for循环

在没有java8之前,我们是循环列表加判断条件,然后组装数据,比如

  //过滤出id大于5的数据集合
       //(注意也可以通过定制sql:select * from user where id>5)
       //实际根据情况在sql还是通过java处理组装
       List<User> idUserList = new ArrayList<>();
       for(User user: userList) {
    	   if(user.getId()>5) {
    		   idUserList.add(user);
    	   }
       }
       for(User user: idUserList) {
    	   System.out.println("只要用户id大于5的用户:" +user);
       }

注意也可以通过定制sql:select * from user where id>5,实际根据情况在sql中还是通过java处理组装数据集合,回到了现状(数据处理计划在数据库端还是java端还是前端处理的问题)

此时打印输出

​​

1.2 java8实现,基于streams

 //转换流成list
       List<User> filterList = userList.stream()
               .filter((user) -> user.getId()>5)
               .collect(Collectors.toList());
       
       System.out.println("java8 之后");
       filterList.forEach((user) -> 
       {
    	   System.out.println("只要用户id大于5的用户:" +user);
       });

打印输出

​​

 

2.  Stream转Map

比如只要是河南的用户(注意filter过滤条件依实际情况而写)

//stream流转map 
Map<Long,User>  idFilteredMap  = userList
    	        .stream()
    	        .filter((user) -> user.getProvince().equalsIgnoreCase("河南"))
    	        .collect(Collectors.toMap(User::getId,  user->user));
       System.out.println("用户id和用户: "+idFilteredMap);
       
       idFilteredMap.values().forEach(user -> 
       {
    	   System.out.println("只要省份是河南的用户:" +user);
       });

打印输出

​​

有时候我们只需要用户名列表

  //只获得用户名
       List<String> userNamesByLambda =  userList
    		   .stream()
    		   .map(user -> user.getUsername())
    		   .collect(Collectors.toList());	
       System.out.println("只要用户名(map通过Lambda expression): "+userNamesByLambda);

       List<String> userNamesByMethod =  userList
    		   .stream()
    		   .map(User::getUsername)
    		   .collect(Collectors.toList());	
       System.out.println("只要用户名(map通过method refrence): "+userNamesByMethod);

输出结果:

只要用户名(map通过Lambda expression): [dgm, wangwu, zhangsan, maliu, nasi, gaoba, dongguangming]
只要用户名(map通过method refrence): [dgm, wangwu, zhangsan, maliu, nasi, gaoba, dongguangming]

 

3. Stream转set

比如只要是31岁的用户(注意filter过滤条件依实际情况而写)

//Converting Stream to Set
       Set<User>  filterSet =  userList
   	        .stream()
   	        .filter((user) -> user.getAge()==31)
   	        .collect(Collectors.toSet());
      
       filterSet.forEach(user -> 
      {
   	   System.out.println("只要年龄是31的用户:" +user);
      });

打印输出

只要年龄是31的用户:User [id=5, username=nasi, nickname=马四, password=123456789, phone=17850669069, email=5056764180@qq.com, province=江苏, gender=女, age=31]
只要年龄是31的用户:User [id=7, username=dongguangming, nickname=董, password=65432111, phone=19850669069, email=9056764180@qq.com, province=河南, gender=男, age=31]

4.  Stream转数组

有的时候,我们需要把流转换成数组

 //Converting Stream to Array
       User[]  filteredArray  =  userList
   	        .stream()
   	        .filter((user) -> user.getGender()==Gender.FEMALE)
   	        .toArray(User[] :: new);
   	        //.collect(Collectors.toSet());
       for(User user: filteredArray) {
    	   System.out.println("只要性别是女的用户:" +user);
       }
 

执行结果

​​

5. 统计函数

根据filter做统计计数

5.1  计算总数量

 //统计
       long count = userList
    		   .stream() 
    		   .filter(user -> user.getUsername().startsWith("d")) 
    		   .count();
       System.out.println("用户名以d开头的用户数量是: "+count);
打印输出

​​

 

5.2  计算最大最小值

  //统计id最大最小
       Optional<User> maxIdUser = userList
    		   .stream()
    		   .max(Comparator.comparing(User::getId));
       System.out.println("ID最大的用户是: "+maxIdUser);
       Optional<User> minIdUser = userList
    		   .stream()
    		   .min(Comparator.comparing(User::getId));
       System.out.println("ID最小的用户是: "+minIdUser);
输出结果:

​​

 

5.3  分组

按条件分组,类似于sql里group by

 

// Grouping people by 省份
       Map<String, List<User>> userByProvince = userList.stream()
           .collect(Collectors.groupingBy(
               User::getProvince,
               Collectors.toList()));
       System.out.println("按省份分组用户: "+userByProvince);
输出结果
省份和用户: {河南=[User [id=1, username=dgm, nickname=董广明, password=123456, phone=15850669069, email=1056764180@qq.com, province=河南, gender=男, age=32], User [id=2, username=wangwu, nickname=王五, password=12345678, phone=13850669069, email=21056764180@qq.com, province=河南, gender=男, age=22], User [id=3, username=maliu, nickname=马六, password=123456, phone=14850669069, email=3056764180@qq.com, province=河南, gender=未知, age=18], User [id=7, username=dongguangming, nickname=董, password=65432111, phone=19850669069, email=9056764180@qq.com, province=河南, gender=男, age=31]], 
江苏=[User [id=4, username=zhangsan, nickname=张三, password=123456000, phone=16850669069, email=4056764180@qq.com, province=江苏, gender=男, age=24], User [id=5, username=nasi, nickname=马四, password=123456789, phone=17850669069, email=5056764180@qq.com, province=江苏, gender=女, age=31]], 
安徽=[User [id=6, username=gaoba, nickname=高八, password=654321, phone=18850669069, email=6056764180@qq.com, province=安徽, gender=男, age=40]]}
 

更多详情(虽然不一定都用到)参看oracle java官方文档 https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

 

附图:

​​

 

还记得数据库sql的语法吗

 [ [SELECT] WITH vname AS (select_statement) [,vname AS...] ]
SELECT [TOP row_count] [ALL | DISTINCT] {* | select_list}
FROM { { {table | view [{TABLESAMPLE [method] (percentage) [REPEATABLE (arg)]}] }
       |  joined_table
       | derived_table
       | literal }
      [correlation] }
, ...
[WHERE search_condition]
[GROUP BY { column_name | column_number | GROUPING SETS (column_list), ... } ]
[HAVING search_condition]
[ORDER BY { {column_name | column_number}
            [ASC | DESC]
            [NULLS {FIRST | LAST | MAX | MIN}], ... }]
[{AT {NOW | FULL_HISTORY | at_mode }]
[FETCH FIRST row_count ROWS ONLY | LIMIT row_count ]
 

​​

 
相当于把数据库sql里的where、distinct、order by 、 group by、max()、avg()等放到java层处理,数据库只负责捞数据。

 

 

总结:数据组装过程,看实际情况需要,酌情考虑(实在不行把数据库人员、java码农、前端开发者主要是JavaScript,聚到议事堂公开讨论下)在哪层(数据库层、java中间层还是前端js)处理

 

参考文献:

  1. Java 8 Central  https://www.oracle.com/java/technologies/java8.html 

  2. Java 8 Stream https://www.runoob.com/java/java8-streams.html

  3. Java 8 特性 – 终极手册 http://ifeve.com/java-8-features-tutorial/

  4. Java 8 中的 Streams API 详解

    https://developer.ibm.com/zh/articles/j-lo-java8streamapi/

  5. Java 8 数据流教程  http://blog.didispace.com/books/java8-tutorial/ch2.html

  6. java-8-streamcollect-example  https://www.java67.com/2018/06/java-8-streamcollect-example.html?m=1#.X2gbYwe9xIs.twitter

  7. JAVA8十大新特性详解(精编)https://www.jianshu.com/p/0bf8fe0f153b

     8.  Java 8 正式发布,新特性全搜罗  https://www.iteye.com/news/28870-java-8-release

     9.  专题:Java8 新特性探究_51CTO.COM https://developer.51cto.com/art/201404/435591.htm

     10.  Java™ Platform, Standard Edition 8
API Specification  https://docs.oracle.com/javase/8/docs/api/

     11.  Java Stream API  https://soshace.com/java-stream-api/

     12.  

Converting stream to collections and Arrays https://javagoal.com/java-stream-collect/

     13  

 

Collect Data to Map http://www.java2s.com/Tutorials/Java_Streams/Tutorial/Streams/Collect_Data_to_Map.htm

14  

10 Examples of Stream API in Java 8 - count + filter + map + distinct + collect

 https://www.java67.com/2014/04/java-8-stream-examples-and-tutorial.html#ixzz6f1PBYO48

 

15  

Stream in Java 8  https://javagoal.com/java-8-stream/

16 . 

Overview of Java 8 Stream API 

https://stacktraceguru.com/overview-java-8-stream-api

 

17.  

Practical Guide to Java Stream API https://praveergupta.in/practical-guide-to-java-stream-api-7aadc02908f7

 

18    

Java 8 中的 Streams API 详解  

https://developer.ibm.com/zh/languages/java/articles/j-lo-java8streamapi/

 

19     

Functional Programming in Java: Stream API   

https://www.cognizantsoftvision.com/blog/functional-programming-in-java-stream-api/

20  

A Complete Tutorial on Java Streams  https://pdf.co/blog/java-streams

 

21. 

Java 8 Stream API https://howtodoinjava.com/java8/java-streams-by-examples/

22  

A In-Depth guide to Java 8 Stream API https://java2blog.com/java-8-stream/

 

23. 

Functional Programming With Java: Streams https://belief-driven-design.com/functional-programming-with-java-streams-190eda591a5/

24.  

A Guide to Java Streams in Java 8: In-Depth Tutorial With Examples

 

https://stackify.com/streams-guide-java-8/

25 

4 Examples of Stream.collect() method in Java 8

https://www.java67.com/2018/06/java-8-streamcollect-example.html#ixzz6f1lmX200

26  A Beginner’s Guide to Complete Analysis of Apache Spark RDD and Java 8 Streams  https://www.msystechnologies.com/blog/a-beginners-guide-to-complete-analysis-of-apache-spark-rdds-and-java-8-streams/

 

27 

6 Most Useful Java 8 Stream Functions with Real-time Examples https://www.javachinna.com/2020/08/21/java-8-stream-functions-with-examples/?utm_source=rss&utm_medium=rss&utm_campaign=java-8-stream-functions-with-examples

 

28

​​

​​
​​
​​

推荐阅读