java函数式编程基础

Stream流

  • 可以被用来对集合或数组进行链状流式的操作
  • 如果没有终结操作,则中间操作不会被执行
  • 流是一次性的,终结操作后不能再对该流进行操作
  • 对流的各种操作不会影响到原始数据

快速入门

@Data
@NoArgsConstructor
@AllArgsConstructor
//用于后续的元素之间的比较,重写了equals方法,使对象之间比较由比较地址改为比较属性值
@EqualsAndHashCode
public class User(){
    private String name;
    private int age;
    private List<String> interest;
}

public List<User> getUser(){
    List<User> users = new ArrayList<>();
    String interest1 = "唱歌,跳舞";
    String interest2 = "跳舞,打篮球";

    users.add(new User("li",28,interest1));
    users.add(new User("zhang",16,interest1));
    users.add(new User("wang",34,interest2));
    users.add(new User("wang",34,interest2));
}

List<User> users = getUser();
users.stream()//将users集合转换为流
    .distinct()//对该流元素进行进行去重
    .filter(user->user.getAge()<18)//去除流中年龄<18的人
    .forEach(user->System.out.println(user.getName()))//遍历流打印名称

创建流

单列集合:

List<User> users = getUsers();
Stream<User> userStream = users.stream();

数组:

Integer[] arr = {1,2,3,4,5};
Stream<Integer> IntegerStream = Arrays.stream(arr);
//Stream<Integer> IntegerStream = Stream.of(arr);

双列集合:

Map<String,Integer> map = new HashMap<>();
map.put("苹果",5);
map.put("香蕉",4);
map.put("梨",3);
// 先转换成单列的set然后再转换成stream
Stream<Map.Entry<String,Integer>> mapStream = map.entrySet().stream();

中间操作

下面的例子使用快速入门中的user对象

filter

对流中元素进行条件过滤

List<User> users = getUsers();
users.stream()
     .filter(user->user.getAge<18)//使用filter将流中年龄小于18的获取出来
     .forEach(user->System.out.println(user.getName()));//输出年龄小于18的名字
map

对流中元素进行计算或转换

List<User> users = getUsers();
users.stream()
     .map(user->user.getAge())//将该流中user转换为对应的age属性
     .map(age->age+1)//将该流中每个age元素加1
     .forEach(age->System.out.println(age));//打印age
distinct

去除重复元素
底层调用了之前重写的equals方法来进行比较

List<User> users = getUsers();
users.stream()
     .distinct();
     .forEach(user->System.out.println(user.getName()));
sorted

对流中元素按照给定规则排序
这里是使用了Comparator接口,在java中能进行比较的对象都实现了该接口

List<User> users = getUsers();
users.stream()
     .distinct();
     .sorted((o1,o2)->o1.getAge()-02.getAge())//按照年龄升序排列
     .forEach(user->System.out.println(user.getName()));
limit

截取指定长度的流

List<User> users = getUsers();
users.stream()
     .distinct();
     .sorted((o1,o2)->o2.getAge()-o1.getAge())//按照年龄降序排列
     .limit(1)//截取年龄最大的元素
     .forEach(user->System.out.println(user.getName()));
skip

跳过流中前n个元素,返回后续元素

List<User> users = getUsers();
users.stream()
     .distinct();
     .sorted((o1,o2)->o2.getAge()-o1.getAge())//按照年龄降序排列
     .skip(1)//去除年龄最大的元素
     .forEach(user->System.out.println(user.getName()));
flatMap

将一个对象转换成多个对象作为流中的元素

List<User> users = getUsers();
users.stream()
     .distinct()
     .flatMap(user->user.getInterest().stream())//将所有user的interest转换为stream并替换当前流中元素
     .distinct()//去除重复的元素(interest字符串)
     .flatMap(interest->Arrays.stream(interest.split(",")))//将interest字符串按照逗号进行分隔,再用其替换掉当前流中元素
     .distinct()//去除重复的兴趣
     .forEach(interest->System.out.println(interest));//输出所有不重复的interest

终结操作

对于流而言必须要执行终结操作,否则中间操作不会被执行

forEach

遍历所有元素,传入要对每个元素做的操作

count

获取当前流中所有元素个数

max&min

获取当前流中的最大值或最小值

List<User> users = getUsers();
int max = users.stream()
               .distinct();
               .map(user->user.getAge())
               .max((score1,score2)->score1-score2);//同sorted要指定排序方法
collect

将当前流转换为一个集合
需要注意的是,在指定生成map时需要指定map的key和value(还需要注意key的唯一性)
例:users.stream().flatMap(user->user.getName,user.getInterest)

List<User> users = getUser();
List<String> names = users.stream()
                          .distinct()
                          .map(user->user.getName())
                          .collect(Collectors.toList());//Collectors是一个工具类,用于指定转换的集合类型
查找与匹配

anyMatch
判断是否由任意符合匹配条件的元素,返回boolean

List<User> users = getUser();
boolean have = users.stream()
                    .anyMatch(user->user.getAge<18);判断是否有年龄小于18的用户

allMatch
判断是否都符合条件,返回boolean

noneMatch
判断是否都不符合,返回boolean

findAny
获取任意一个元素

List<User> users = getUser();
Optional<User> optionalUser->user.stream()
                                 .filter(user->user.getAge<18)
                                 .findAny();
// 使用ifPresent方法判断该Optional是否为空
optionalUser.ifPresent(user->System.out.println(user.getName));

findFirst
获取流中第一个元素

reduce归并

对流中数据按指定方法计算出一个结果(例如对某个数字进行求和)

两个参数形式

reduce(T identity,BinaryOperator<T> accumulator)

两个参数下重载内部原理

// result为给定的初始值,要与流中数据类型相同
// apply指定计算方法
T result = identity;
for(T element : this stream)
    result = accumulator.apply(result,element)
    return result;

求和例子

List<User> users = getUser();
Integer sum = users.stream()
                   .map(user->user.getAge())//将流转变为数字元素
                   .reduce(0,(result,element)->result+element);//对流中元素求和

求最值例子
(在max,min等方法中,其底层就是用的reduce)

List<User> users = getUser();
Integer max = users.stream()
                   .map(user->user.getAge())
                   .reduce(Integer.MIN_VALUE,(result,element)->result<element?element:result);
一个参数形式

reduce(BinaryOperator<T> accumulator)

一个参数形式下重载内部原理

boolean foundAny = false;
T result = null;
for (T element : this stream){
    // 第一次进入时,将第一个元素赋值给result
    if(!foundAny){
        foundAny = true;
        result = element;
    }else{
        // 后续进入同两个参数形式下,对当前元素和result进行计算
        result = accumulator.apply(result,element);
    }
}
// 将结果封装成Optional进行返回
return foundAny?Optional.of(result):Optional.empty();

Optional

Optional就类似一个包装类,将我们具体的数据封装到Optional对象的内部,之后再调用该类的一些方法,可以使得对数据的调用非常优雅。

创建对象

User user = getUser();
// 调用Optional的静态方法ofNullable来创建Optional对象
// 该方法用于不确定待封装对象是否为空的情况,若确定不为空则可以使用Optional.of(),若确定为空则使用Optional.empty()。通常情况下建议使用ofNullable方法
// mybatis同时也支持将数据封装成一个Optional来返回,我们只需要将返回值类型定义为Optional即可
Optional<User> userOptional = Optional.ofNullable(user);

安全的使用值

对于Optional对象,我们可以使用ifPresent方法来使用其中的值,该方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码。可以优雅的避免空指针异常

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.ifPresent(user->System.out.println(user.getName()));

安全的获取值

  • orElseGet
    • 获取数据并且设置数据为空时的默认值。
    • 如果数据不为空则可以获取到该数据,如果数据为空则返回默认值
Optional<User> userOptional = Optional.ofNullable(user);
// 这里通过lambda表达式设置了默认返回一个User对象
User user = userOptional.orElseGET(()->new User())
  • orElseThrow
    • 获取数据,如果数据不为空就能获取到该数据。如果为空则根据传入参数来创建异常抛出
Optional<User> userOptional = Optional.ofNullable(user);
try{
    User user = userOptional.orElseThrow((Supplier<Throwable>)()->new RuntimeException("user为空"));
    System.out.println(user.getName());
}catch(Throwable throwable){
    throwable.printStackTrace();
}

过滤

使用同在stream中的filter
(同样的还有map,作用和用法也与Stream流中相同)

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.filter(user->user.getAge()<18)
            .ifPresent(user->System.out.println(user.getName()));

函数式接口

只有一个抽象方法的接口称为函数接口

JDK的函数式接口都加上了@FunctionalInterface注解进行标识

常见的函数式接口

  • Consumer 消费性接口
    • 该接口中抽象方法接受一个参数,并且没有返回值,故只能对传入数据进行消费
@FunctionalInterface
public interface Consumer<T>{
    void accept(T t);
}
  • Function 计算转换接口
    • 该接口中抽象方法接受一个T的泛型,并且返回一个R的泛型,故该接口可以对数据进行转换,并将结果返回
@FunctionalInterface
public interface Function<T,R>{
    R accept(T t);
}
  • Supplier 生产型接口
    • 该接口中抽象方法不接收参数,但是会返回一个T的泛型,故该接口可以生产数据
@FunctionalInterface
public interface Supplier<T>{
    T get();
}

常用的默认方法

  • and
    • 用于使用Predicate接口时的判断条件拼接
List<User> users = getUser();
users.stream()
     .filter(new Predicate<User>(){
        @Override
        public boolean test(User user){
            return user.getAge()>16;
        }.and(new Predicate<user>(){
            @Override
            public boolean test(User user){
                return user.getName.length()>2;
            }
        })
     })
     .forEach(user->System.out.println(user));
  • or
    • 用于使用Predicate接口时的判断条件并列
List<User> users = getUser();
users.stream()
     .filter(new Predicate<User>(){
        @Override
        public boolean test(User user){
            return user.getAge()>16;
        }.or(new Predicate<user>(){
            @Override
            public boolean test(User user){
                return user.getAge()<9;
            }
        })
     })
     .forEach(user->System.out.println(user));
  • negate
    • 用于取反

方法引用

  • 在使用lambda时,若方法体中只有一个方法的调用(包括构造方法)时可以使用
  • 类名或对象名::方法名
  • 对于方法引用而言,其本身不能携带参数。方法引用是一种简化的Lambda表达式的语法糖,作用是引用一个已经存在的方法。
  • 方法中所需要的参数是通过上下文推到而来的,而不是在方法引用中显式传递

引用类的静态方法

  • 重写方法的方法体中只有一行代码
  • 调用的是某个类的静态方法
  • 要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中
List<User> users = getUser();
users.stream()
     .map(user->user.getAge())
     .map(String::valueOf)
// .map(new Function<Integer,String>(){
//     @Override
//     public String apply(Integer age){
//         return String.valueOf(age);
//     }
// })

引用对象的实例方法

  • 重写方法的方法体中只有一行代码
  • 调用了某个对象的成员方法
  • 要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中
List<User> users = getUser();
List<User> userList = new ArrayList<>();
users.stream()
     .distinct()
     .forEach(userList::add);
// .forEach(new Consumer<User>(){
//     @Override
//     public void accept(User user){
//         userList.add(user);
//     }
// })

引用类的实例方法

  • 重写方法的方法体只有一行代码
  • 调用的是第一个参数的成员方法
  • 要重写的抽象方法中剩余的所有参数都按照顺序传入了该成员方法中
interface UseString{
    String use(String str,int start,int length);
}
public static String subUserName(String str,UseString useString){
    return useString.use(str,0,1);
}

@Test
public void test(){
    subUserName("张三",String::substring);
    // subUserName("张三",new UseString(){
    //     @Override
    //     public String use(String str,int start,int length){
    //         return str.substring(start,length);
    //     }
    // });
}

引用构造器

  • 重写方法的方法体只有一行代码
  • 调用的是某个类的构造器
  • 要重写的抽象方法中的所有参数都按顺序传入到该构造方法中
List<String> strList = new ArrayList<>();
strList.add("abcd");
strList.add("efg");
strList.stream()
   .map(StringBuilder::new)
   .map(sb->sb.reverse().toString())
   .forEach(System.out::println)

// .map(new Function<String, StringBuilder>() {
//     @Override
//     public StringBuilder apply(String s) {
//         return new StringBuilder(s);
//     }
// })
// .map(new Function<StringBuilder, String>() {
//     @Override
//     public String apply(StringBuilder sb) {
//         return sb.reverse().toString();
//     }
// })

数据类型优化与并行流

基本数据类型优化

由于jdk5引入的自动装箱和自动拆箱机制,使得在使用流时可能会不停的重复装箱拆箱操作,从而浪费时间。

  • Stream提供了多种专门针对基本数据类型的map方法
  • mapToInt;mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等
  • 使用方法同map

并行流

使用stream提供的.parallel()方法可以快速的实现并发编程。

List<Integer> arr = new ArrayList<>();
int i=0;
while(i<9999){
    arr.add(++i);
}
// 这里使用的.peek()是流用用以调试的方法,是中间操作
Integer sum = arr.stream().parallel()
   .peek(num->System.out.println(num+Thread.currentThread().getName()))
   .reduce((result,ele)->result+ele)
   .orElse(0);//如果值不存在则返回0
System.out.println(sum);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值