Java高效编程

881 阅读7分钟

函数式编程以及Lambda表达式

函数式编程

需求

有一些商品信息,现在需要将这些商品添加到购物车中获取购物车中的商品信息,然后完成以下需求.

  1. 找出购物车中所有属于电子数码 的商品
  2. 根据商品类型查找商品
  3. 通过商品类型或总价过滤商品
  4. 根据不同的商品判断标准查找商品

商品实体类

package lambda.cart;

/**
 * 下单商品信息对象
 */
public class Sku {
    // 商品编号
    private Integer skuId;
    // 商品名称
    private String skuName;
    // 商品单价
    private Double skuPrice;
    // 商品个数
    private Integer totalNum;
    // 商品总价
    private Double totalPrice;
    // 商品分类
    private Enum skuCategory;


    /**
     * 有参构造
     * @param skuId
     * @param skuName
     * @param skuPrice
     * @param totalNum
     * @param totalPrice
     * @param skuCategory
     */
    public Sku(Integer skuId, String skuName, Double skuPrice, Integer totalNum, Double totalPrice, Enum skuCategory) {
        this.skuId = skuId;
        this.skuName = skuName;
        this.skuPrice = skuPrice;
        this.totalNum = totalNum;
        this.totalPrice = totalPrice;
        this.skuCategory = skuCategory;
    }

    public Integer getSkuId() {
        return skuId;
    }

    public String getSkuName() {
        return skuName;
    }

    public Double getSkuPrice() {
        return skuPrice;
    }

    public Integer getTotalNum() {
        return totalNum;
    }

    public Double getTotalPrice() {
        return totalPrice;
    }

    public Enum getSkuCategory() {
        return skuCategory;
    }
}

商品分类枚举

package lambda.cart;

/**
 * 商品分类枚举
 */
public enum SkuCategoryEnum {
    CLOTHING(10, "服装"),
    DIGITAL(20, "数码产品"),
    SPORTS(30, "运动类"),
    BOOKS(40, "书籍类");

    // 商品类型的编号
    private Integer code;
    // 商品类型的名称
    private String name;

    /**
     * 构造方法
     *
     * @param code
     * @param name
     */
    SkuCategoryEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }


}

添加所有商品信息到购物车中

private static List<Sku> cartSkuList = new ArrayList<Sku>() {
        {
            add(new Sku(654032, "无人机", 22.2, 2, 44.4, SkuCategoryEnum.DIGITAL));
            add(new Sku(654033, "VR眼镜", 11.1, 2, 22.2, SkuCategoryEnum.DIGITAL));
            add(new Sku(654034, "充气娃娃", 1000.0, 2, 2000.0, SkuCategoryEnum.SPORTS));
            add(new Sku(654035, "Java核心技术", 50.0, 2, 100.0, SkuCategoryEnum.BOOKS));
            add(new Sku(654036, "Python编程", 49.0, 2, 96.0, SkuCategoryEnum.BOOKS));
            add(new Sku(654037, "女装", 66.0, 1, 66.0, SkuCategoryEnum.CLOTHING));
        }
    };

获取购物车中的所有商品

public static List<Sku> getCartSkuList() {
        return cartSkuList;
    }

需求分析和完成

找出购物车中所有属于电子数码的商品

  • 实现方式:使用循环遍历购物车中的所有商品信息,然后使用枚举进行比对
public static List<Sku> filterElectronicsSku(List<Sku> cartSkuList) {
        List<Sku> result = new ArrayList<>();
        for (Sku sku : cartSkuList
        ) {
            if (SkuCategoryEnum.DIGITAL.equals(sku.getSkuCategory())) {
                result.add(sku);
            }
        }
        return result;
    }

根据商品类型查找商品

  • 实现方式:将商品类别枚举作为参数传递给方法,使用商品分类枚举进行比对
/**
     * 根据商品类型查找商品
     * Version:2.0(单一维度条件参数化)
     *
     * @param cartSkuList
     * @param skuCategoryEnum
     * @return
     */
    public static List<Sku> filterSkuByCategory(List<Sku> cartSkuList, SkuCategoryEnum skuCategoryEnum) {
        List<Sku> result = new ArrayList<>();
        for (Sku sku : cartSkuList
        ) {
            if (skuCategoryEnum.equals(sku.getSkuCategory())) {
                result.add(sku);
            }
        }
        return result;
    }

通过商品类型或总价过滤商品

/**
     * 通过商品类型或总价过滤商品
     * Version: 3.0
     *
     * @param cartSkuList
     * @param categoryEnum
     * @param totalPrice
     * @param categoryOrPrice false:使用价格过滤,true使用分类
     * @return
     */
    public static List<Sku> filterSku(
            List<Sku> cartSkuList,
            SkuCategoryEnum categoryEnum,
            Double totalPrice,
            Boolean categoryOrPrice
    ) {
        List<Sku> result = new ArrayList<Sku>();
        for (Sku s : cartSkuList
        ) {
            if (
                    (categoryOrPrice && categoryEnum.equals(s.getSkuCategory()))
                            || (!categoryOrPrice && s.getTotalPrice() > totalPrice)
            ) {
                result.add(s);
            }
        }
        return result;
    }

根据不同的商品判断标准去过滤商品

实现方式:定义一个函数式接口,然后定义多个实现类,分别对应多个筛选条件.

函数式接口

有且只有一个抽象方法的接口被称为函数式接口,函数式接口适用于函数式编程的场景,Lambda就是Java中函数式编程的体现,可以使用Lambda表达式创建一个函数式接口的对象,一定要确保接口中有且只有一个抽象方法,这样Lambda才能顺利的进行推导。

package lambda.cart;

/**
 * Sku选择谓词接口
 */
public interface SkuPredicate {
    /**
     * 选择判断标准
     * @param sku
     * @return
     */
    boolean test(Sku sku);
}

根据商品是否为图书分类进行筛选

package lambda.cart;

/**
 * 该商品是否为图书分类
 */
public class SkuBooksCategoryPredicate implements SkuPredicate {
    @Override
    public boolean test(Sku sku) {
        return SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory());
    }
}

根据商品的总价过滤商品


package lambda.cart;

/**
 * 根据商品的总价过滤商品
 */
public class SkuTotalPricePredicate implements SkuPredicate {
    @Override
    public boolean test(Sku sku) {
        return sku.getTotalPrice() > 2000;
    }
}

需求实现

将函数式接口作为参数传给方法,然后调用函数式接口的test方法.接口有不同的实现类根据传递不同的实现类进行调用.

方法定义

/**
     * Version: 4.0
     * 根据不同的商品判断标准
     *
     * @param cartSkuList  商品列表
     * @param skuPredicate 不同的商品属性判断标准
     * @return
     */
    public static List<Sku> filterSku(List<Sku> cartSkuList, SkuPredicate skuPredicate) {
        List<Sku> result = new ArrayList<Sku>();
        for (Sku s : cartSkuList
        ) {
            if (skuPredicate.test(s)) {
                result.add(s);
            }
        }
        return result;
    }

实体类的调用方法

/**
     * 将判断逻辑参数化之实体类
     */
    @Test
    public void filterSku2() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        List<Sku> skus = CartService.filterSku(cartSkuList, new SkuBooksCategoryPredicate());
        System.out.println(JSON.toJSONString(skus, true));

    }

匿名类的调用方法

/**
     * 将判断逻辑参数化之 匿名类
     */
    @Test
    public void filterSku3() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        List<Sku> skus = CartService.filterSku(cartSkuList, new SkuPredicate() {
            @Override
            public boolean test(Sku sku) {
                return sku.getSkuPrice() >= 1000;
            }
        });
        System.out.println(JSON.toJSONString(skus, true));

    }

Lambda方法进行调用

/**
     * 将判断逻辑参数化之 Lambda
     */
    @Test
    public void filterSku4() {
        List<Sku> cartSkuList = CartService.getCartSkuList();
        List<Sku> skus = CartService.filterSku(cartSkuList, (Sku sku) -> sku.getSkuPrice() >= 1000);
        System.out.println(JSON.toJSONString(skus, true));

    }

使用函数式接口处理文件内容|文件控制台打印

第一:自定义一个函数式接口

FileConsumer

/**
 * Created by  肖橙橙
 * CreateTime 2020-07-27  14:09
 * 文件处理函数式接口
 */
@FunctionalInterface
public interface FileConsumer {
    /**
     * 抽象方法
     * 接收的是一个文件对象
     * @param fileContent 文件内容的字符串
     */
    void FileHandler(String fileContent);
}

第二:创建一个文件服务类

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
 
/**
 * Created by  肖橙橙
 * CreateTime 2020-07-27  14:08
 * 文件服务类
 */
public class FileService {
 
    /**
     * 通过URL获取本地内容,调用函数式接口处理
     * @param url
     * @param fileConsumer
     */
    public void fileHandler(String url, FileConsumer fileConsumer) throws Exception {
 
        //创建文件读取流
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(url)));
        //定义一个行变量和内容sb
        String line;
        StringBuilder sb = new StringBuilder();
 
        //循环读取文件内容
        while ((line = bufferedReader.readLine())!=null){
            //如果不为null,则将该行添加
            sb.append(line+"\n");
        }
        //调用函数式接口方法,将文件内容传递给lambda表达式
        fileConsumer.FileHandler(sb.toString());
    }
 
}

第三:编写一个测试类

public class FileServiceTest {
 
    @Test
    public void fileHandler() throws Exception {
 
        //通过lambda表达式打印文件内容
        FileService fileService = new FileService();
        fileService.fileHandler("填写文件的绝对路径地址",fileContent-> System.out.println(fileContent));
    }

常用JDK函数式接口

image.png

方法引用

方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。

双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

参考文档:blog.csdn.net/m0_46502538…

流编程

image.png

短路操作:找到符合条件的元素后,不再执行。
非短路操作:每个元素执行一次

无状态操作: 每个元素可以单独执行
有状态操作: 需要建立在所有元素基础上执行

image.png

image.png

filter

list.stream().filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory())).forEach(
                item ->{
                    System.out.println(JSON.toJSONString(item,true));
                }
);

map

list.stream().map(sku -> sku.getSkuName()).forEach(
                item -> System.out.println(JSON.toJSONString(item,true))

flatMap

list.stream()
.flatMap(sku-> Arrays.stream(sku.getSkuName().split("")))
.forEach( item -> System.out.println(JSON.toJSONString(item,true)) );

peek

list
.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.forEach( item -> System.out.println(JSON.toJSONString(item,true)) );

sort

list
.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice))
.forEach( item -> System.out.println(JSON.toJSONString(item,true)) );

distinct

对流中元素进行去重

list
.stream()
.map(Sku::getSkuCategory)
.distinct()
.forEach( item -> System.out.println(JSON.toJSONString(item,true)) );

skip

# 总价格按照从低到高排序,并跳过前3个     

list
.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice)) 
.skip(3) 
.forEach( item -> System.out.println(JSON.toJSONString(item,true)) );

limit

截断前n条记录

list
.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice)) 
.limit(5) 
.forEach( item -> System.out.println(JSON.toJSONString(item,true)) );

AllMatch/anyMatch/noneMatch

  • AllMatch:所有元素匹配,返回true
  • anyMatch:任何元素匹配,返回true
  • noneMatch:任何元素都不匹配,返回true
/**
  * allMatch使用: 操作,短路操作。所有元素匹配,返回true
  */
  @Test
  public void allMatchTest(){
    boolean match = list.stream()
      .allMatch(sku -> sku.getTotalPrice()>100);
    System.out.println(match);
  }
  
  /**
       * anyMatch使用:任何元素匹配,返回true
       */
  @Test
  public void anyMatchTest(){
    boolean match = list.stream()
      .anyMatch(sku -> sku.getTotalPrice()>100);
    System.out.println(match);
  }
  
  /**
       * noneMatch使用:任何元素都不匹配,返回true
       */
  @Test
  public void testNoneMatch(){
    boolean match = list.stream()
      .noneMatch(sku -> sku.getTotalPrice()<0);
    System.out.println(match);
  }

findFirst/findAny

  • findFirst:找到第一个
  • findAny:找到任意一个
 /**
   * 找到第一个
   */
   @Test
   public void findFirstTest(){
     Optional<Sku> optional = list.stream()
       .peek(sku -> System.out.println(sku.getSkuName()))
       .findFirst();
     System.out.println(JSON.toJSONString(optional.get(),true));
   }
   
   /**
     * 找到任意一个
     */
   @Test
   public void findAnyTest(){
     Optional<Sku> optional = list.stream()
       .peek(sku -> System.out.println(sku.getSkuName()))
       .findAny();
     System.out.println(JSON.toJSONString(optional.get(),true));
   }

min/max/count

  • min: 找出最小的项
  • max:找出最大的项
  • count:统计总数
@Test
public void maxTest(){
  OptionalDouble optionalDouble = list.stream()
    // 获取总价
    .mapToDouble(Sku::getTotalPrice)

    .max();

  System.out.println(optionalDouble.getAsDouble());
}

@Test
public void minTest(){
  OptionalDouble optionalDouble = list.stream()
    // 获取总价
    .mapToDouble(Sku::getTotalPrice)

    .min();

  System.out.println(optionalDouble.getAsDouble());
}

/**
 * count
 */
@Test
public void countTest(){
  long count = list.stream().count();
  System.out.println(count);
}

toList

将处理后的结果收集为list

// 收集总价格超过100的Sku
List<Sku> cartSkuList = CartService.getCartSkuList();
List<Sku> result = cartSkuList
  .stream()
  .filter(sku -> sku.getTotalPrice() > 100)
  .collect(Collectors.toList());
System.out.println(JSON.toJSONString(result,true));

toMap

@Test
public void testMapTest(){
  List<Sku> cartSkuList = CartService.getCartSkuList();
  //如果两个id重复,两个value可以映射到同一个id,如 678678重复
  Map<Integer, String> map = cartSkuList.stream().collect(Collectors.toMap(Sku::getSkuId, Sku::getSkuName,
                                                                           (e1, e2) -> e1 + "," + e2));
  System.out.println(map.get(678678));
  System.out.println(JSON.toJSONString(map,true));
}

groupBy


@Test
public void groupTest(){
  List<Sku> cartSkuList = CartService.getCartSkuList();
  Map<Enum, List<Sku>> group = cartSkuList
    .stream()
    .collect(Collectors.groupingBy(sku -> sku.getSkuCategory()));
  System.out.println(JSON.toJSONString(group,true));
}