一、Lambda表达式&方法引用
方法引用:是结合Lambda表达式的一种语法特性,有如下三种形式。
- 静态方法引用
类型名称.方法名称() ----> 类型名称 :: 静态方法名称
- 实例方法引用
创建类型对象的一个引用---> 对象引用 :: 实例方法名称
- 构造方法应用
构造方法引用必须要绑定函数式接口---->类型名称 :: new
代码示例:
public class App
{
public static void main( String[] args )
{
List<User> userList = new ArrayList<>();
userList.add(new User("tom","男",12));
userList.add(new User("sunny","女",23));
userList.add(new User("tony","男",19));
userList.add(new User("jimmy","男",15));
userList.add(new User("kin","女",23));
//1.匿名内部类实现排序
Collections.sort(userList, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.getAge()-o2.getAge();
}
});
System.out.println(userList);
//2.Lambda表达式实现排序
Collections.sort(userList,(u1,u2)-> u1.getAge()-u2.getAge());
System.out.println(userList);
//3.静态方法引用
Collections.sort(userList,User::compareByAge);
System.out.println(userList);
//4.实例方法的引用
UserUtil userUtil = new UserUtil();
Collections.sort(userList,userUtil::compareByName);
System.out.println(userList);
//5.构造方法引用--------构造方法引用需要绑定函数式接口
IUser iUser = User::new;
User user = iUser.initUser("Juby","女",34);
System.out.println(user);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class User{
private String name;
private String gender;
private Integer age;
public static int compareByAge(User user1,User user2){
return user1.getAge()-user2.getAge();
}
}
//User的工具类
class UserUtil{
public int compareByName(User user1,User user2){
return user1.getName().hashCode()-user2.getName().hashCode();
}
}
@FunctionalInterface
interface IUser{
User initUser(String name,String gender,Integer age);
}
二、Stream
(1)什么是流?
Stream(流)是一个来自数据源的元素队列并支持聚合操作的抽象接口。
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 针对批量数据,类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
总的来说,Stream 就是使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
示例:对List过滤的传统方式 和 Stream中使用filter的对比
public class App2 {
public static void main( String[] args )
{
List<User> userList = new ArrayList<>();
userList.add(new User("tom","男",12));
userList.add(new User("sunny","女",23));
userList.add(new User("tony","男",19));
userList.add(new User("jimmy","男",15));
userList.add(new User("kin","女",23));
List<User> adultUserList = new ArrayList<>(); //已成年用户的List
//问题:找出已成年的用户
//方法1:迭代器遍历
Iterator<User> userIterator = userList.iterator();
while (userIterator.hasNext()){
User user = userIterator.next();
if (user.getAge() >= 18){
adultUserList.add(user);
}
}
System.out.println(adultUserList);
adultUserList.clear(); //清空List
//方法2:for循环遍历-------for循环遍历的本质上还是迭代器遍历
for (User user : userList) {
if (user.getAge() >= 18){
adultUserList.add(user);
}
}
System.out.println(adultUserList);
adultUserList.clear();
//方法3:Stream结合Lambda表达式过滤
adultUserList = userList.stream().filter(user -> user.getAge() >= 18).collect(Collectors.toList());
System.out.println(adultUserList);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class User{
private String name;
private String gender;
private Integer age;
}
(2)Stream的处理流程: 数据源->数据转换->获取结果
- 数据源-------获取Stream对象
① 从集合或者数组中获取
Collection.stream()、Collection.parallelStream()、Arrays.stream(T t) 等
② BufferReader
BufferReader.lines()->stream()
-
数据转换-------中间操作API
中间操作API,操作结果是一个Stream,中间操作可以有一个或者多个连续的中间操作。
需要注意的是,中间操作只记录操作方式,不做具体执行,直到结束操作发生时,才做数据的最终执行。
中间操作:就是业务逻辑处理。
中间操作过程区分:
无状态:数据处理时,不受前置中间操作的影响。 map/filter/peek/parallel/sequential/unordered
有状态:数据处理时,受到前置中间操作的影响。 distinct/sorted/limit/skip
-
获取结果-------终结操作 | 结束操作
需要注意:一个Stream对象,只能有一个Terminal操作,这个操作一旦发生,就会真实处理数据,生成对应的处理结果。
终结操作区分:
非短路操作:当前的Stream对象必须处理完集合中所有数据,才能得到处理结果。forEach/forEachOrdered/toArray/
reduce/collect/min/max/count/iterator
短路操作:当前的Stream对象在处理过程中,一旦满足某个条件,就可以得到结果。 anyMatch/allMatch/noneMatch/
findFirst/findAny等 Short-circuiting,无限大的Stream-> 有限大的Stream。
三、Stream操作示例
public class App3 {
public static void main(String[] args) {
//1.批量数据 -> Stream对象
//多个数据
Stream stream1 = Stream.of("tom","sunny","tony");
//数组
String[] stringArray = new java.lang.String[]{"tom","sunny","tony"};
Stream stream2 = Arrays.stream(stringArray);
//列表
List<String> list = new ArrayList<>();
list.add("tom");
list.add("sunny");
list.add("tony");
Stream stream3 = list.stream();
//集合
Set<String> set = new HashSet<>();
set.add("tom");
set.add("sunny");
set.add("tony");
Stream stream4 = set.stream();
//map
Map<String,Integer> map = new HashMap<>();
map.put("tom",14);
map.put("sunny",17);
map.put("tony",32);
Stream stream5 = map.entrySet().stream(); //map中的数据在进行流处理的时候,是以entrySet的方式进行的。
//2.Stream对象对于基本数据类型的功能封装(int、long、double)
IntStream stream6 = IntStream.of(new int[]{1,2,3,4});
IntStream stream7 = IntStream.range(1,4); //生成1,2,3(不包括4)
IntStream stream8 = IntStream.rangeClosed(6,10); //生成6,7,8,9,10
LongStream stream9 = LongStream.of(new long[]{1,2,3,4});
DoubleStream stream10 = DoubleStream.of(new double[]{1,2,3,4});
//3.Stream对象 --> 转换得到指定的数据类型
//注意:在一个stream中,如果终结操作(类似于toArray()、collect()等)一旦发生,stream中的数据就会被清空,再去操作这个stream中的数据就会报错。
//数组
Object[] strArray = stream1.toArray(String[]::new);
//字符串
String str = stream2.collect(Collectors.joining()).toString(); //joining()是将所有的字符串拼接起来
System.out.println(str);
//列表
List<String> listx = (List<String>) stream3.collect(Collectors.toList()); //这里的数据入口stream1没有指定数据类型,所以在转换完成后需要进行一次强制类型转换
List<String> listy = list.stream().collect(Collectors.toList()); //这种情况下就不需要进行强制类型转换
//集合
Set<String> setx = (Set<String>) stream4.collect(Collectors.toSet());
//map
/**
* <p>
* Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
* Function<? super T, ? extends U> valueMapper)
*
* 其中,toMap方法中的参数是两个Function函数式接口,因此我们可以直接用Lambda表达式的方式进行实现
* toMap(x->x,y->y)------------这是使用了Lambda表达式的精简语法。
* </p>
*/
Map<String,Integer> mapx = (Map<String, Integer>) stream3.collect(Collectors.toMap(x->"key:"+x, y->"value:"+y));
System.out.println(mapx);
}
}
四、Stream-Collection的相关操作---------知识点写在注释上
public class App4 {
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User("tom","男",12));
userList.add(new User("sunny","女",23));
userList.add(new User("tony","男",19));
userList.add(new User("jimmy","男",15));
userList.add(new User("kin","女",23));
userList.add(new User("kin","女",23));
//map()中间操作,map()方法接收一个Functional<T,R>接口
List<User> descUserList = userList.stream().map(x->{
x.setName("我叫"+x.getName());
x.setGender("性别:"+x.getGender());
x.setAge(x.getAge()+1);
return x;
}).collect(Collectors.toList());
System.out.println(descUserList);
//filter()添加过滤条件,过滤符合条件的用户
List<User> adultMaleUserList = userList.stream().filter(x->"男".equals(x.getGender())&&x.getAge()>=18).collect(Collectors.toList()); //成年男性用户list
System.out.println(adultMaleUserList);
//forEach增强型循环
userList.forEach(x-> System.out.println("forEach-->>" + x));
//peek()----可以简化对整个集合进行多次迭代的操作,属于中间操作,迭代数据完成对数据的依次处理过程
userList.stream().peek(x->{
if ("男".equals(x.getGender())){
System.out.println("男用户:"+x);
}
}).peek(x->{
if (x.getAge()>=18){
System.out.println("成年用户:"+x);
}
}).forEach(System.out::println);
//skip(n)----跳过前面n个元素,属于中间操作,有状态
userList.stream().skip(2).forEach(System.out::println);
//limit(n)----输出当前位置后的n个数据,属于中间操作,有状态
userList.stream().skip(2).limit(2).forEach(System.out::println); //跳过前两个数据后再输出两个数据
//distinct()----剔除重复的数据,属于中间操作,有状态
userList.stream().distinct().forEach(System.out::println);
//sort()----排序,属于中间操作,有状态
userList.stream().sorted((x,y)->x.getAge()-y.getAge()).forEach(System.out::println);
userList.stream().sorted(User::compareByAge).forEach(System.out::println);
//max()----获取最大值
Optional max = userList.stream().max(User::compareByAge);
System.out.println(max.get());
//min()----获取最小值
Optional min = userList.stream().min(User::compareByAge);
System.out.println(min.get());
//reduce()----合并处理数据
List<Integer> ageList = userList.stream().map(x->x.getAge()).collect(Collectors.toList());
Optional ageSum = ageList.stream().reduce((sum,x)-> sum + x); //等同于sum+=x <==> sum=sum+1
System.out.println("所有用户的年龄总和:"+ageSum);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class User{
private String name;
private String gender;
private Integer age;
//通过age比较
public static int compareByAge(User user1,User user2){
return user1.getAge()-user2.getAge();
}
}
五、parallelStream的线程安全问题
-
解决方法:
(1)自定义编码添加线程锁的方式
(2)Stream的API中提供的线程安全的终端操作如:collect()、reduce()等。
注:forEach()是非线程安全的。
(3)在一般的场景中,都是采用线程安全的集合去规范数据源。