1

1218097468

V1

2023/04/03阅读:30主题:默认主题

Java高效编程

Java高效编程技巧实践

第一章 引言

第二章 函数编程

实战案例:函数编程演变史

  • 实战:根据实际场景演示,为了应对不断变化的业务需求,代码的多个改进版本

购物车案例

同事在前段时间的618往购物车里加了一堆的东西,准备疯狂的消费一把。结果呢,资金有问题,所以他需要向女朋友申请费用,然后他的女朋友就要他对加入购物车里的商品进行不同维度的查找。比如:按价钱分,超过多少钱的不给买;按种类分,什么类型的不给买。

代码准备

  • 商品类
/**
 * 下单商品信息对象
 * @author ShenTao
 * @date 2022/9/25 21:40
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Sku {
    //编号
    private Integer skuId;
    //商品名称
    private String skuName;
    //单价
    private Double skuPrice;
    //购买个数
    private Integer totalNum;
    //总价
    private Double totalPrice;
    //商品类型
    private Enum skuCategory;
}
  • 商品类别枚举
/**
 * 商品类型枚举
 */

public enum SkuCategoryEnum {
    CLOTHING(10"服装类"),
    ELECTRONICS(20"数码类"),
    SPORTS(30"运动类"),
    BOOKS(40"图书类");

    // 商品类型的编号
    private Integer code;
    // 商品类型的名称
    private String name;
  
    SkuCategoryEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
}
  • 购物车类
/**
 * 购物车服务类
 * @author ShenTao
 * @date 2022/9/25 21:45
 */

public class CartService {
    private static List<Sku> cartSkuList = new ArrayList<Sku>(){
        {
            add(new Sku(654032"无人机"4999.0014999.00, SkuCategoryEnum.ELECTRONICS));

            add(new Sku(642934"VR一体机"2299.0012299.00, SkuCategoryEnum.ELECTRONICS));

            add(new Sku(645321"纯色衬衫"409.0031227.00, SkuCategoryEnum.CLOTHING));

            add(new Sku(654327"牛仔裤"528.001528.00, SkuCategoryEnum.CLOTHING));

            add(new Sku(675489"跑步机"2699.0012699.00, SkuCategoryEnum.SPORTS));

            add(new Sku(644564"Java编程思想"79.80179.80, SkuCategoryEnum.BOOKS));

            add(new Sku(678678"Java核心技术"149.001149.00, SkuCategoryEnum.BOOKS));

            add(new Sku(697894"算法"78.20178.20, SkuCategoryEnum.BOOKS));

            add(new Sku(696968"TensorFlow进阶指南"85.10185.10, SkuCategoryEnum.BOOKS));
        }
    };
}

代码1.0 硬编码业务逻辑 - 找到电子类产品

/**
 * 购物车服务类
 *
 * @author ShenTao
 * @date 2022/9/25 21:45
 */

public class CartService {
    private static List<Sku> cartSkuList = new ArrayList<Sku>() {
        {
            add(new Sku(654032"无人机"4999.0014999.00, SkuCategoryEnum.ELECTRONICS));
            add(new Sku(642934"VR一体机"2299.0012299.00, SkuCategoryEnum.ELECTRONICS));
            add(new Sku(645321"纯色衬衫"409.0031227.00, SkuCategoryEnum.CLOTHING));
            add(new Sku(654327"牛仔裤"528.001528.00, SkuCategoryEnum.CLOTHING));
            add(new Sku(675489"跑步机"2699.0012699.00, SkuCategoryEnum.SPORTS));
            add(new Sku(644564"Java编程思想"79.80179.80, SkuCategoryEnum.BOOKS));
            add(new Sku(678678"Java核心技术"149.001149.00, SkuCategoryEnum.BOOKS));
            add(new Sku(697894"算法"78.20178.20, SkuCategoryEnum.BOOKS));
            add(new Sku(696968"TensorFlow进阶指南"85.10185.10, SkuCategoryEnum.BOOKS));
        }
    };

    /**
     * 获取购物车列表
     * @return 购物车列表信息
     */

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

    /**
     * 找出购物车中所有电子产品
     *
     * @param cartSkuList 购物车列表
     * @return 所有电子产品
     */

    public static List<Sku> filterElectronicsSkus(List<Sku> cartSkuList) {
        List<Sku> result = new ArrayList<>();
        for (Sku sku : cartSkuList) {
            if (SkuCategoryEnum.ELECTRONICS.equals(sku.getSkuCategory())) {
                result.add(sku);
            }
        }
        return result;
    }
}

代码2.0 单一维度条件参数化

​ 第一个需求就已经完成了,业务方在第一个需求的基础上,又提出了第二个需求:想看一下每类的商品都有哪些?

​ 那么很明显,咱们第一个找出购物车中所有的电子产品是不满足要求的。那咱们在业务方提出的需求上进行迭代。再实现一个根据商品类型进行查找商品的方法。

/**
     * 根据传入商品类型参数,找出购物车中同种商品类型的商品列表
     *
     * @param cartSkuList 购物车列表
     * @param categoryEnum 商品类型
     * @return 传入类型的商品列表
     */

public static List<Sku> filterSkusByCategory(List<Sku> cartSkuList, SkuCategoryEnum category) {
  List<Sku> result = new ArrayList<>();
  for (Sku sku : cartSkuList) {
    if (category.equals(sku.getSkuCategory())) {
      result.add(sku);
    }
  }
  return result;
}

代码3.0 多维度条件参数化

​ 第二个需求也完成了,但是业务方提出了另一个新需求:在兼容商品类别的基础上,想要看一下商品超过xxx多少钱的有什么?即支持商品类型或总价来过滤商品

/**
     * 支持通过产品类型或总价过滤商品
     *
     * @param cartSkuList 购物车列表
     * @param category 商品类型
     * @param totalPrice 价格总价
     * @param categoryOrPrice 商品类型或是总价  true:产品类型 false:产品总价
     * @return 符合条件的产品列表
     */

public static List<Sku> filterSkus(List<Sku> cartSkuList, SkuCategoryEnum category, Double totalPrice, Boolean categoryOrPrice) {
    List<Sku> result = new ArrayList<>();
    for (Sku sku : cartSkuList) {
      if ((categoryOrPrice && category.equals(sku.getSkuCategory())
           || (!categoryOrPrice && sku.getTotalPrice() > totalPrice))) {
        result.add(sku);
      }
    }
    return result;
}

代码4.0 判断逻辑参数化-实体类

  • Service
/**
     * 根据不同的Sku判断标准,对Sku列表进行过滤
     *
     * @param cartSkuList 购物车信息列表
     * @param predicate
     * @return
     */

public static List<Sku> filterSkus(List<Sku> cartSkuList, SkuPredicate predicate) {
    List<Sku> result = new ArrayList<>();
    for (Sku sku : cartSkuList) {
      //根据不同的Sku判断策略,对Sku进行判断
      if (predicate.test(sku)) {
        result.add(sku);
      }
    }
    return result;
}
  • SkuPredicate接口: 选择判断
/**
 * Sku选择谓词接口
 */

public interface SkuPredicate {
    /**
     * 选择标准判断
     */

    boolean test(Sku sku);
}
  • 接口实现类:具体的判断标准

    • 图书类判断标准
    /**
     * 对Sku的商品类型为图书类的判断标准
     */

    public class SkuBooksCategoryPredicate implements SkuPredicate {
        @Override
        public boolean test(Sku sku) {
            return SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory());
        }
    }
    • 总价是否超出2000作为判断标准
    /**
     * 对Sku的总价是否超出2000作为判断标准
     */

    public class SkuTotalPricePredicate implements SkuPredicate {
        @Override
        public boolean test(Sku sku) {
            return sku.getTotalPrice() > 2000;
        }
    }

  • 使用test

@Test
public void testFilterSkus() {
    List<Sku> cartSkuList = CartService.getCartSkuList();
    //根据商品类别
    List<Sku> result = CartService.filterSkus(cartSkuList, new SkuBooksCategoryPredicate());
    System.out.println(JSON.toJSONString(result, true));

    //根据商品总价
    result = CartService.filterSkus(cartSkuList, new SkuTotalPricePredicate());
    System.out.println(JSON.toJSONString(result, true));
}

代码5.0 判断逻辑参数化-匿名类

​ 从代码4.0中,我们想增加一个新的条件需要新建一个类,类中只实现了一个test方法,怎么优化呢? -- 使用匿名类呀!

@Test
public void testFilterSkus5() {
    List<Sku> cartSkuList = CartService.getCartSkuList();
    //单价大于1000的商品
    List<Sku> result = CartService.filterSkus(cartSkuList, new SkuPredicate() {
      @Override
      public boolean test(Sku sku) {
        return sku.getSkuPrice() > 1000;
      }
    });
    System.out.println(JSON.toJSONString(result, true));
}

代码6.0 Lambda表达式

//使用Lambda表达式
@Test
public void testFilterSkus6() {
    List<Sku> cartSkuList = CartService.getCartSkuList();
    //单价大于1000的商品
    List<Sku> result = CartService.filterSkus(cartSkuList, (Sku sku) -> sku.getSkuPrice() > 1000);
    System.out.println(JSON.toJSONString(result, true));
}

函数编程演化历程

函数编程演化历程

  1. 将业务逻辑直接写死在代码里。
  2. 将单一维度的条件做为参数传入方法中。方法内根据参数进行业务逻辑实现。
  3. 将多个维度的条件做为参数传入方法中。业务实现需要根据不同的参数处理不同逻辑。
  4. 将业务逻辑封装为一个实体类,方法接受实体类为参数,方法内部调用实体类的处理逻辑。
  5. 调用方法时不在创建实体类,而是使用匿名函数的形式替代。
  6. 使用Lambda表达式替代匿名函数的形式,做为方法的参数。真正实现判断逻辑参数化传递。

Lambda表达式

  • Java8引入函数式编程风格

  • 可以理解为一种匿名函数的替代,能够完全替代匿名内部类

  • 通过行为参数化传递代码

表达式形式

  • 形式

    • (parameters) -> expression

    • (parameters) -> { statement; }

  • 特点

    • 参数可选,可以为空; 一个参数可以不加括号;多个参数必须加括号。
    • 大括号可选,主体一个表达式,可以不加大括号。
    • 返回关键字可选,主体只有一个表达式,会自动返回;有多个语句,在大括号中指明返回值。
  • 例子

    • 形式一:没有参数
    () -> System.out.println("hello world")
    • 形式二:只有一个参数
    name -> System.out.println("hello world" + name + "!");
    • 形式三:没有参数,多个逻辑
    () -> {
      System.out.println("hello");
      System.out.println("world");
    }
    • 形式四:包含两个参数的方法
    BinaryOperator<Long> functionAdd = (x, y) -> x + y;
    Long result = functionAdd.apply(1L2L);
    • 形式五:对参数显示声明
    BinaryOperator<Long> functionAdd = (Long x, Long y) -> x + y;
    Long result = functionAdd.apply(1L2L);

函数式接口

  • 接口中只有一个抽象方法

  • Java8函数式接口注解:@FucntionInterface

    ​ 用于校验方法是否符合函数式接口的定义

  • 函数式接口的抽象方法签名:函数描述符,作为函数式接口的校验标准

自定义函数接口示例

常用函数接口

​ 上述例子中,函数式接口还是比较具象的(需要新建一个接口),都到这一步了,还有没有更泛化的方法呢?-- 当然有啦,那就是Java提供给我们一些常见的函数接口:

image-20220926233336888

方法引用

​ 在代码中,可能会看到如下写法

Optional.ofNullable(cartSkuList)
    .map(List::stream)
    .orElseGet(Stream::empty)
    .sorted(Comparator.comparing(Sku::getTotalPrice))
    .forEach(result::add);

​ 是不是很晕?这里的::是什么意思呀? 这一串一串的是什么东西啊?

方法引用定义

​ 调用特定方法的Lambda表达式的一种快捷写法,可以让你重复使用现有的方法定义,并像Lambda表达式一样传递他们。

image-20220926234120808

方法引用方式及举例

​ 方法引用主要分为三种方式,通过例子来体会方法引用。

  1. 指向静态方法的方法引用

(args) -> ClassName.staticMethod(args); ==> ClassName::staticMethod;

举例:

@Test
public void test1() {
    Consumer<String> consumer1 = (String number) -> Integer.parseInt(number);
  
    Consumer<String> consumer2 = Integer::parseInt;
}
  1. 指向任意类型实例方法的方法引用

(args) -> args.instanceMethod(); ==> ClassName::instanceMethod()

举例:

@Test
public void test02() {
    Consumer<String> consumer1 =(String str) -> str.length();

    Consumer<String> consumer2 = String::length;
}
  1. 指向现有对象的实例方法的方法引用

(args) -> Object.instanceMethod(); ==> object::instanceMethod;

@Test
public void test03() {
    StringBuilder sb = new StringBuilder();
  
    Consumer<String> consumer1 = (String str) -> sb.append(str);
  
    Consumer<String> consumer2 = sb::append;
}
  1. 构造方法的方法引用
Optional.ofNullable(skuList).orElseGet(ArrayList::new);

方法引用具体示例

public class MethodReferenceTest {
  static class Sku {
        private String skuName;
        private Integer skuPrice;
        
       public Integer getSkuPrice() {
            return this.skuPrice;
        }
     
    //静态方法
        public static int staticComparePrice(Sku sku1, Sku sku2) {
            return sku1.getSkuPrice() - sku2.getSkuPrice();
        }
     
    //非静态方法
        public int instanceComparePrice(Sku sku) {
            return this.getSkuPrice() - sku.getSkuPrice();
        }

    }
  
    class PriceComparator {
        public int instanceComparePrice(Sku sku1, Sku sku2) {
            return sku1.getSkuPrice() - sku2.getSkuPrice();
        }
    }
  
  
   @Test
    public void test() {
      
      List<Sku> skuList = new ArrayList<>();
       //lambda表达式
       skuList.sort((sku1, sku2) -> sku1.getSkuPrice() - sku2.getSkuPrice());

      
        //使用方法引用替换
        //类名::静态方法名
        skuList.sort(Sku::staticComparePrice);
        //展开写法
        skuList.sort((Sku sku1, Sku sku2) -> {
            return Sku.staticComparePrice(sku1, sku2);
        });
      
      
        //类名::实例方法名
        skuList.sort(Sku::instanceComparePrice);
        //展开写法
        skuList.sort((Sku object, Sku sku) -> {
            return object.instanceComparePrice(sku);
        });
      
      
        //对象::实例方法名
        PriceComparator priceComparator = new PriceComparator();
        skuList.sort(priceComparator::instanceComparePrice);
        //展开写法
        skuList.sort((Sku sku1, Sku sku2) -> {
            return priceComparator.instanceComparePrice(sku1, sku2);
        });

        //构造方法引用
        Optional.ofNullable(skuList)
            .orElseGet(ArrayList::new);
    }
}

Tips:常用List集合初始化方式:

1. 先创建List再赋值

标准方式,先创建集合对象,然后逐个调用add方法初始化。用起来比较繁琐,不太方便!

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

2. 使用{{}}双大括号初始化

使用匿名内部类完成初始化。外层的{}定义了一个ArrayList的匿名内部类,内层的{}定义了一个实例初始化代码块。有内存泄漏的风险!不建议在生产项目中使用!

List<Integer> list3 = new ArrayList<Integer>(){
    {
        add(1);
        add(2);
        add(3);
    }
};

3. 使用Arrays.asList

使用Arrays的静态方法asList初始化。返回的list集合是不可变的!

import java.util.Arrays;
List<Integer> list = Arrays.asList(123);

4. 使用Stream(JDK8以上)

使用JDK8引入的Stream的of方法生成一个stream对象,调用collect方法进行收集,形成一个List集合。

import java.util.stream.Stream;
List<Integer> list = Stream.of(123).collect(Collectors.toList());

5. 使用Google Guava工具集Lists

借助Google Guava工具集中的Lists工具类初始化。需要引入Guava包才能用!

import com.google.common.collect.Lists;
List<Integer> list = Lists.newArrayList(123);

6. 使用List(JDK9以上)

使用JDK9引入的List.of方法完成初始化。

import com.sun.tools.javac.util.List;
List<Integer> list = List.of(123);

第三章 流式编程

先感受一下流式编程的魅力吧

​ 接着上述的例子,一个新的需求如下,我们来对比一下

  1. 想要看看购物车有什么商品
  2. 图书类商品都给买
  3. 其余的商品中买两件最贵的
  4. 只需要两件商品的名称和总价
  • 原始集合操作
public void oldCartHandle() {
    //1. 打印所有商品
    List<Sku> cartSkuList = CartService.getCartSkuList();
    for (Sku sku : cartSkuList) {
        System.out.println(JSON.toJSONString(sku, true));
    }

    //2. 图书类过滤掉
    List<Sku> noteBookSkuList = new ArrayList<>();
    for (Sku sku : cartSkuList) {
        if (!sku.getSkuCategory().equals(SkuCategoryEnum.BOOKS)) {
            noteBookSkuList.add(sku);
        }
    }

    //3. 排序
    noteBookSkuList.sort(new Comparator<Sku>() {
        @Override
        public int compare(Sku o1, Sku o2) {
            return o2.getSkuPrice().compareTo(o1.getSkuPrice());
        }
    });

    //4.TOP2
    List<Sku> top2SkuList = new ArrayList<>();
    for (int i = 0; i < 2; i++) {
        top2SkuList.add(noteBookSkuList.get(i));
    }

    //5. 求两件商品的总价
    Double money = 0.0;
    for (Sku sku : top2SkuList) {
        money += sku.getTotalPrice();
    }

    //6. 获取两件商品的名称
    List<String> resultSkuNameList = new ArrayList<>();
    for (Sku sku : top2SkuList) {
        resultSkuNameList.add(sku.getSkuName());
    }

    System.out.println(JSON.toJSONString(resultSkuNameList, true));
    System.out.println("商品总价:" + money);
}
  • 流式编程
/**
 * 以stream流方式实现需求
 */

public void newCartHandle() {
    AtomicReference<Double> money = new AtomicReference<>(0.0);

    //以流的方式
    //1. 打印商品信息   -> 过滤图书商品  -> 排序 -> 总价top的商品  ->  计算商品的总价值 -> 获取商品名称  —> 输出结果
    List<String> resultSkuNameList = CartService.getCartSkuList()
        .stream()
        .peek(sku -> {
            System.out.println(JSON.toJSONString(sku, true));
        })
        .filter(sku -> !SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
        .sorted(Comparator.comparing(Sku::getTotalPrice).reversed())
        .limit(2)
        .peek(sku -> money.set(money.get() + sku.getTotalPrice()))
        .map(Sku::getSkuName)
        .collect(Collectors.toList());

    System.out.println(JSON.toJSONString(resultSkuNameList, true));
    System.out.println("商品总价:" + money);
}

​ 怎么样?流式编程方式是不是很简洁 很优雅? 但是小伙伴可能不熟悉相关函数,是不是很抓狂? 没关系,接下来我们来介绍其详细使用!

流是什么

​ 从支持数据处理操作的源生成的元素序列 -- Java8实战

流和集合的区别

  • 空间和时间:集合需要获取所有数据,流只需要当前数据
  • 遍历次数:集合能够遍历多次,流只能遍历一次
  • 外部迭代和内部迭代:集合需要外部迭代,流式内部迭代

流的组成

​ 流由三部分组成:数据源 + 中间操作 + 终端操作

image-20221002231931108

流操作分类

  • 有状态/无状态:需要所有数据的操作是有状态操作, 无需所有数据的操作是无状态的
  • 短路/无短路:找到对应的数据即停的操作是短路操作,所有数据都需要执行一遍的操作是非短路操作
image-20221002231549396
image-20221002231549396

流操作分类,具体版

image-20221002232010454

流的使用

中间操作

  • 无状态操作

    • fliter(过滤)的使用,参数为Predicate函数式接口。 过滤掉不符合断言判断的数据
    //过滤只剩下BOOKS类别的商品
    @Test
    public void filterTest() {
        SkuList.stream()
          .filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
          .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
    • map(映射)的使用,参数为Function函数式接口。 使用给定函数转换流的元素,返回一个流。
    //将Sku对象转换成skuName,返回商品名
    @Test
    public void mapTest() {
        SkuList.stream()
          .map(Sku::getSkuName)
          .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
    • flatMap(扁平化)的使用:参数为Function接口。接收一个元素返回新的流,可为多个流。
    //本例将商品名称切分并返回一个字符流
    @Test
    public void flatMapTest() {
        SkuList.stream()
          .flatMap(sku -> Arrays.stream(sku.getSkuName().split("")))
          .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
    • peek(遍历)的使用:参数为Consumer接口,功能等同于forEach
    //本例打印sku名称(注意:peek和forEach是交替执行的,这也是无状态操作下流的特点)
    @Test
    public void peekTest() {
        SkuList.stream()
          .peek(sku -> System.out.printf(sku.getSkuName()))
          .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }


    //加上有状态的操作 结果对比,发现先执行完中间操作后才执行终端操作
    @Test
    public void peekTest2() {
        SkuList.stream()
          .peek(sku -> System.out.printf(sku.getSkuName()))
          .sorted(Comparator.comparing(Sku::getTotalPrice))
          .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }

  • 有状态操作

    • distinct(去重)的使用:获取去重后的流
    //获得去重后的商品类别
    @Test
    public void distinctTest() {
        SkuList.stream()
          .map(Sku::getSkuCategory)
          .distinct()
          .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
    • skip(跳过)的使用:跳过前几条数据
    //过滤掉总价最低的3件商品
    @Test
    public void skipTest() {
        SkuList.stream()
          .sorted(Comparator.comparing(Sku::getTotalPrice))
          .skip(3)
          .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
    • limit(截断)的使用:取前几条数据
    //取总价最低的三件商品
    @Test
    public void skipTest() {
        SkuList.stream()
            .sorted(Comparator.comparing(Sku::getTotalPrice))
            .skip(3)
            .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
    • sorted(排序)的使用:参数为Comparator接口。 将流中数据排序。
    //按照总价将商品排序
    @Test
    public void sortTest() {
        SkuList.stream()
            .sorted(Comparator.comparing(Sku::getTotalPrice))
            .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }

终端操作

  • 短路操作

    • allMatch(所有匹配):所有流数据满足条件,返回true
    @Test
    public void allMatchTest() {
        boolean match = SkuList.stream()
            .peek(sku -> System.out.println(sku.getSkuName()))
            .allMatch(sku -> sku.getTotalPrice() > 100);
        System.out.println(match);
    }
    • anyMatch(任意匹配):任意流数据满足条件,返回true
    @Test
    public void anyMatchTest() {
        boolean match = SkuList.stream()
            .peek(sku -> System.out.println(sku.getSkuName()))
            .anyMatch(sku -> sku.getTotalPrice() < 100);
        System.out.println(match);
    }
    • noneMatch(不匹配):所有流数据都不满足条件,返回true
    @Test
    public void noneMatchTest() {
        boolean match = SkuList.stream()
            .peek(sku -> System.out.println(sku.getSkuName()))
            .noneMatch(sku -> sku.getTotalPrice() > 100);
        System.out.println(match);
    }
    • findFirst(查找首个):返回满足条件的第一个数据
    @Test
    public void findFirstTest() {
        Optional<Sku> opt = SkuList.stream().findFirst();
        System.out.println(JSON.toJSONString(opt, true));
    }
    • finAny(查找任意一个):返回满足条件的数据,不一定是第一个(多线程下)
    @Test
    public void findAnyTest() {
        Optional<Sku> opt = SkuList.stream().findAny();
        System.out.println(JSON.toJSONString(opt, true));
    }
  • 非短路操作

    • forEach(遍历):遍历流中的数据
    @Test
    public void filterTest() {
        SkuList.stream()
            .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
    • max(最大值):获取流中的最大值元素
    public void maxTest() {
        OptionalDouble max = SkuList.stream()
            .mapToDouble(Sku::getTotalPrice)
            .max();
        System.out.println(max.getAsDouble());
    }
    • min(最小值):获取流中的最小值元素
    @Test
    public void minTest() {
        OptionalDouble min = SkuList.stream()
            .mapToDouble(Sku::getTotalPrice)
            .min();
        System.out.println(min.getAsDouble());
    }
    • count(总数):获取流的总数
    @Test
    public void countTest() {
        long count = SkuList.stream()
            .count();
        System.out.println(count);
    }
    • collect(汇总):将流中的元素累积成一个结果

      • collect :终端操作

        Collector : 接口

        Collectors:工具类

      • 将流中的数据聚合为list返回

      //将流中总价大于100的流作为list返回
      @Test
      public void toList() {
          List<Sku> list = CartService.getCartSkuList();
          List<Sku> collect = list.stream()
              .filter(sku -> sku.getTotalPrice() > 100)
              .collect(Collectors.toList());
          System.out.println(JSON.toJSONString(collect, true));
      }
      • 分组,返回 Map<Object, List >形式
      //将流中的数据以类别分组
      @Test
      public void group() {
          List<Sku> list = CartService.getCartSkuList();
          Map<Object, List<Sku>> map = list.stream()
              .collect(Collectors.groupingBy(Sku::getSkuCategory));
          System.out.println(JSON.toJSONString(map, true));
      }
      • 分区,返回两种值一种为true, 一种为false
      //将流中的数据以总价是否大于100分区
      @Test
      public void partition() {
          List<Sku> list = CartService.getCartSkuList();
          Map<Boolean, List<Sku>> map = list.stream()
              .collect(Collectors.partitioningBy(sku -> sku.getTotalPrice() > 100));
          System.out.println(JSON.toJSONString(map, true));
      }
    • reduce(归约) : 将Stream流中元素转换成一个值

    归约操作接口定义
     identity -- 初始值
      accumulator -- 计算逻辑
      combiner -- 并行执行时多个结果的合并方式
      
    <U> reduce(U identity,
          BiFunction<U, ? super T, U> accumulator,
          BinaryOperator<U> combiner)
    ;
    //计算订单信息的平均价格
    @Test
    public void reduceTest() {

        @Data
        @AllArgsConstructor
        class Order {
            private Integer id;
            private Integer productCount;
            private Double totalAmount;
        }

        //初始化数据
        List<Order> list = Lists.newArrayList();
        list.add(new Order(1225.12));
        list.add(new Order(25527.23));
        list.add(new Order(3323332.12));

        //使用reduce 计算平均价格
        Order order = list.stream()
            .parallel() //参数3的执行前提是执行该条语句,即并行操作
            .reduce(
                //初始值
                new Order(000.0),
                //第二个参数:Stream中两个元素的计算逻辑
                (Order o1, Order o2) -> {
                    System.out.println("执行 计算逻辑 部分");
                    int productCount = o1.getProductCount() + o2.getProductCount();
                    double totalAmount = o1.getTotalAmount() + o2.getTotalAmount();
                    return new Order(0, productCount, totalAmount);
                },
                //并行情况下,多个并行结果如何合并
                //如果是并行计算,那多个并行计算的结果是如何汇总的呢?
                //或者说,第三个参数是:汇总并行计算的逻辑
                (Order o1, Order o2) -> {
                    System.out.println("执行 合并逻辑");
                    int productCount = o1.getProductCount() + o2.getProductCount();
                    double totalAmount = o1.getTotalAmount() + o2.getTotalAmount();
                    return new Order(0, productCount, totalAmount);
                });

        System.out.println(JSON.toJSONString(order, true));
    }
    • collect(汇总)接口:将集合元素汇总成一个值
    //根据account字段将count和amount相加,返回Map<String, Order>
    @Test
    public void collectTest() {

        @Data
        @AllArgsConstructor
        class Order {
            private Integer id;
            private String account;
            private Integer productCount;
            private Double totalAmount;
        }

        //初始化数据
        List<Order> list = Lists.newArrayList();
        list.add(new Order(1"zhangSan"225.12));
        list.add(new Order(2"zhangSan"5527.23));
        list.add(new Order(3"liSi"323332.12));

        HashMap<String, Order> collect = list.stream()
            .collect(
                () -> new HashMap<String, Order>(),
                (HashMap<String, Order> map, Order newOrder) -> {
                    String account = newOrder.getAccount();
                    if (map.containsKey(account)) {
                        Order order = map.get(account);
                        order.setProductCount(
                            newOrder.getProductCount() + order.getProductCount());
                        order.setTotalAmount(
                            newOrder.getTotalAmount() + order.getTotalAmount());
                    } else {
                        map.put(account, newOrder);
                    }
                },
                (HashMap<String, Order> map1, HashMap<String, Order> map2) -> {
                    map2.forEach((key, value) -> {
                        map1.merge(key, value, (order1, order2) -> {
                            return new Order(0,
                                             key,
                                             order1.getProductCount() + order2.getProductCount(),
                                             order1.getTotalAmount() + order2.getTotalAmount()
                            );
                        });
                    });
                }
            );
        System.out.println(JSON.toJSONString(collect, true));
    }

流的创建

  • 由值创建流
@Test
public void streamFromValue() {
    Stream<Integer> stream = Stream.of(12345);
    stream.forEach(System.out::println);
}
  • 由数组创建流
@Test
public void streamFromArray() {
    int[] numbers = {12345};
    IntStream stream = Arrays.stream(numbers);
    stream.forEach(System.out::println);
}
  • 由文件生成流
@Test
public void streamFromFile() throws IOException {
    Stream<String> stream = Files.lines(Paths.get("src/test/java/com/st/stream/StreamConstructor.java"));
    stream.forEach(System.out::println);
}
  • 由函数生成流(无限流)
//生成偶数流
@Test
public void streamFromFunction() {
    Stream<Integer> stream = Stream.iterate(0, n -> n + 2);
    stream.forEach(System.out::println);
}

//生成随机流
@Test
public void streamFromFunction2() {
    Stream<Double> stream = Stream.generate(Math::random);
    stream.forEach(System.out::println);
}

第四章 资源关闭

垃圾回收(GC)的特点

  • 垃圾回收机制只负责回收堆内存资源,不会回收任何物理资源
  • 程序无法精准的控制垃圾回收动作发生的具体发生时间
  • 在垃圾回收之前,最会先调用它的finalize方法

常见需要手动释放的物理资源

  • 文件、流资源
  • 套接字资源
  • 数据库连接资源

传统方式关闭资源

/**
 * 1.创建输入 输出流
 * 2.执行文件拷贝
 * 3.关闭文件流资源
 */

@Test
public void copyFile() {
    String originalUrl = "src/test/java/com/st/resource/FileCopyTest.java";
    String targetUrl = "src/test/java/com/st/resource/target.java";

    //声明文件输入流  文件输出流
    FileInputStream originalFileInputStream = null;
    FileOutputStream targetFileOutputStream = null;

    //读取的字节信息
    int content;

    try {
        FileInputStream fileInputStream = new FileInputStream(originalUrl);
        FileOutputStream fileOutputStream = new FileOutputStream(targetUrl);
        //迭代,读取、写入字节
        while ((content = originalFileInputStream.read()) != -1) {
            targetFileOutputStream.write(content);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        if (targetFileOutputStream != null) {
            try {
                targetFileOutputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        if (originalFileInputStream != null) {
            try {
                originalFileInputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

TWR方式关闭流资源

@Test
public void copyFile() {
    String originalUrl = "src/test/java/com/st/resource/FileCopyTest.java";
    String targetUrl = "src/test/java/com/st/resource/target.java";

    try (
        FileInputStream inputStream = new FileInputStream(originalUrl);
        FileOutputStream outputStream = new FileOutputStream(targetUrl);
    ) {
        int content = 0;
        //迭代,读取、写入字节
        while ((content = inputStream.read()) != -1) {
            outputStream.write(content);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

}

第五章 Guava

Optional的使用

Optional三种创建方式

  • Optional.empty(): 创建空的Optional对象
  • Optional.of("zhangSan"): 使用非null值创建Optional对象
  • Optional.ofNullable("abc"): 使用任意值创建Optional对象

Optional 使用

  • optional.isPresent():判断引用缺失的方法(不建议使用)

  • optional.ifPresent():如果引用非空,则执行 类似方法还有:map filter flatMap

  • optional.orElse("引用缺失"):当optional引用缺失时执行

  • optional.orElseThrow(() -> new RuntimeException("返回一个异常!")) : 如果引用缺失,则抛出一个异常

不可变集合

创建对象的不可变拷贝是一项很好到防御性编程技巧,Guava为所有的JDK标准集合类型和Guava新集合类型都提供了简单易用的不可变版本。

不可变对象的优点

  • 当对象被不可信的库调用时,不可变形式是安全的
  • 不可变对象被多个线程调用时,不存在竞态条件问题
  • 不可变集合不需要考虑变化,因此可以省略时间和空间
  • 不可变对象因为是固定不变,可以作为常量来安全使用

不可变集合创建方式

  • copyOf方法:ImmutableSet.copyOf(set)
  • of方法:ImmutableSet.of("a", "b", "c")
  • Builder工具:ImmutableSet.builder().build()

第六章 线程池

什么是线程池

​ 线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程,不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

线程池好处

  • 降低资源消耗
  • 响应速度
  • 提高线程的可管理性

简单线程池设计 - 你能独立设计一个简单的线程池吗?

image-20221005105145356

线程池组成部分:队列 + 线程池子 + 执行器

线程池执行流程:提交任务放入队列中,执行器从队列取任务并从池子中取线程执行该任务,并将执行结果异步返回。

线程池的使用

线程池的核心参数

/**
corePoolSize:池中的线程数,即使其处于IDLE状态
maximumPoolSize:池中允许的最大线程数
keepAliveTime:当线程数大于核心时,空闲线程在终止之前等待新任务的最长时间
unit:keepAliveTime的时间单位
workQueue:队列,用于在执行task之前保存task
handler:当达到了线程边界和队列容量,无法及时处理时,reject task使用的处理程序
*/

public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    RejectedExecutionHandler handler)
 
{}

线程池处理流程

image-20221005111635159
image-20221005111635159

线程池可选择的阻塞队列

  • 无界队列:队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。 当然这种队列,maximumPoolSize 的值也就无效了。
  • 有界队列:当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
  • 同步移交队列,如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列,该队列不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。当然只有在使用无界线程池或者有饱和策略时才建议使用该队列。

线程池可供选择的饱和策略

  • AbortPolicy : 终止策略(默认)
  • DiscardPolicy : 抛弃策略(默认)
  • DiscardOldestPolicy : 抛弃旧任务策略(默认)
  • CallerRunsPolicy : 调用者运行策略(默认)

线程池的执行示意图

image-20221005114923541

线程池的提交任务

  • 返回Future

  • 返回Runnable

线程池的状态

线程池的状态转移有两条路径:

  • 当调用 shutdown() 方法时,线程池的状态会从 RUNNING 到 SHUTDOWN,再到 TIDYING,最后到 TERMENATED 销毁状态。
  • 当调用 shutdownNow() 方法时,线程池的状态会从 RUNNING 到 STOP,再到 TIDYING,最后到 TERMENATED 销毁状态。
image-20221005125021269

第七章 Lombok

Lombok介绍

Lombok实现原理

  • 注解处理器
  • JSR269插入式注解处理器
image-20221005130539310

常用注解

image-20221005130654907

第八章 验证框架

分层验证与JavaBean验证

  • 分层验证模型
image-20221005173643133
image-20221005173643133
  • JavaBean验证模型
image-20221005173707113
image-20221005173707113

什么是JCP JSR

image-20221005173907323
image-20221005173907323
image-20221005174005614

BeanValidation与HibernateValidation

image-20221005174055060
image-20221005174055060

​ SpringValidation对HibernateValidation进行了二次封装,提供了更高效的验证方式。

常用约束注解

空值校验类

  • @Null

  • @NotNull

  • @NotEmpty

  • @NotBlank

范围校验类

  • @Min

  • @Size

  • @Digits

  • @Future

  • @Negative

其他校验类

  • @Email

  • @URL

  • @AssertTrue

  • @Pattern

分类:

后端

标签:

后端

作者介绍

1
1218097468
V1