告别996笔记

告别996笔记

文章目录

函数编程演化历程

  1. 将业务逻辑直接写死在代码里。

  2. 将单一维度的条件做为参数传入方法中。方法内根据参数进行业务逻辑实现。

  3. 将多个维度的条件做为参数传入方法中。业务实现需要根据不同的参数处理不同

    逻辑。

  4. 将业务逻辑封装为一个实体类,方法接受实体类为参数,方法内部调用实体类的

    处理逻辑。

  5. 调用方法时不在创建实体类,而是使用匿名函数的形式替代。

  6. 使用Lambda表达式替代匿名函数的形式,做为方法的参数。真正实现判断逻辑

    参数化传递。

Lambda表达式

Lambda表达式简介

  • Java8引入函数式编程
  • 可以理解为一种匿名函数的代替
  • 通过行为参数化传递代码

Lambda表达式的结构

  1. (paramters) -> expression

    (参数) -> 表达式

  2. (paramters) -> { statements; }

    (参数) -> { 语句; }

Lambda表达式的形式(5种)

  1. 没有参数
() -> System.out.println("Hello World");
  1. 只有一个参数
 name -> System.out.println("My name is " + name);
  1. 没有参数,逻辑复杂
 () -> {
            System.out.println("Hello");
            System.out.println("World");
        }
  1. 包含两个参数的方法
BinaryOperator<Long> functionAdd = (x, y) -> x + y;
Long result = functionAdd.apply(1L, 2L);
  1. 对参数显示声明
BinaryOperator<Long> functionAdd = (Long x, Long y) -> x + y;
functionAdd.apply(1L, 2L);

说明:

  1. 可选的参数类型 不声明参数类型 编译器可自动识别参数类型
  2. 可选的参数圆括号 一个参数时不需要定义圆括号
  3. 可选的大括号 如果主体只包含了一个表达式 就不需要使用大括号
  4. 可选的返回关键字 如果主体只有一个表达式返回值 则编译器会自动返回值

函数式接口定义

  • 接口中只有一个抽象方法
  • Java8的函数式接口注解: @FunctionInterface (只是为了帮助编译器校验函数式接口是否符合定义也就是是否只有一个抽象方法)
  • 函数式接口的抽象方法签名: 函数描述符

常用的函数式接口及使用

接口参数返回类型描述
PredicateTboolean用于判别一个对象,比如求一个人是否为男性
ConsumerTvoid用于接收一个对象进行处理但没有返回,比如 接收一个人的并且打印他的名字
Functiion<T, R>TR转换一个对象为不同类型的对象
SupperNoneT提供一个对象
UnaryOperatorTT接收对象并返回同类型的对象
BinaryOperator(T, T)T接收两个同类型的对象,并返回一个原类型对象

方法引用定义

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

如: User::getUserName

解释为: User为目标引用 双冒号分隔符 getUserName为方法名

方法引用用法(3种)

  1. 指向静态方法的方法引用:
	/**
     * (args) -> ClassName.staticMethod(args)
     * ClassName::staticMethod;
     */
    public void test1() {
        Consumer<String> consumer1
                = (String number) -> Integer.parseInt(number);
        Consumer<String> consumer2
                = Integer::parseInt;
    }
  1. 指向任意类型实例方法的方法引用:
	/**
     * (args) -> args.instanceMethod();
     * ClassName::instanceMethod;
     */
    public void test2() {
        Consumer<String> consumer1
                = (String str) -> str.length();
        Consumer<String> consumer2
                = String::length;
    }
  1. 指向现有对象的实例方法的方法引用
	/**
     * (args) -> object.instanceMethod(args);
     * object::instanceMethod;
     */
    public void test3() {
        StringBuilder stringBuilder = new StringBuilder();
        Consumer<String> consumer1
                = (String str) -> stringBuilder.append(str);
        Consumer<String> consumer2
                = stringBuilder::append;
    }

说明:

问? 如何判断属于哪类方法引用?如何区分Integer::parseInt是静态方法引用还是任意类型实例方法引用?

答: 因为静态方法引用和任意类型实例方法引用都是使用类名::方法名的形式来表示,所以分辨的方法就是看方法是否为static修饰的就可以了
现有对象的实例方法引用都是使用对象::方法名的形式来表示

流定义

  • JDK1.8引入的新成员,以声明式方式处理集合数据
  • 将基础操作链接起来,完成复杂的数据处理流水线
  • 提供透明的并行处理

Java8实战中的定义
从支持数据处理操作生成的元素序列

流与集合的区别

  1. 时间与空间(可以理解为集合面向于存储;而流面向于计算)
  2. 遍历次数(集合可以遍历多次; 流只能遍历一次)
  3. 外部迭代与内部迭代(集合是外部迭代的;流是内部迭代的)

流的组成

  1. 数据源
  2. 中间操作
  3. 终端操作

流程解释如下:
Cart(流的数据源) —> filter(中间操作) —> sorted (中间操作)—> map(中间操作) —> collect(终端操作)

流操作分类

  • 中间操作(Intermediate)
    • 无状态操作 ------- filter(过滤)/map(映射)/peek(遍历)/flatMap(扁平化)
    • 有状态操作 ------- dictinct(去重)/sorted(排序)/limit(截取)/skip(跳过)
  • 终端操作(Terminal)
    • 非短路操作 ------- forEach(遍历)/reduce(规约)/max(最大值)/collect(聚合)/min(最小值)/count(计数)
    • 短路操作 ------- allMatch(所有匹配)/anyMatch(任意匹配)/noneMatch(不匹配)/findFirst(查找首个)/findAny(查找任意)
常见的中间操作实战
filter使用: 过滤掉不符合断言判断的数据
/**
     * filter使用: 过滤掉不符合断言判断的数据
     */
    @Test
    public void filterTest() {
        list.stream()
                .filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
                .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
map使用: 将一个元素转换成另一个元素
	/**
     * map使用: 将一个元素转换成另一个元素
     */
    @Test
    public void mapTest (){
        list.stream()
                .map(sku -> sku.getSkuName())
                .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
flatMap使用: 将一个对象转换成流 (将sku流[Stream]转换成由商品名称字符数组组成的流[Stream])
	/**
     * flatMap使用: 将一个对象转换成流 (将sku流[Stream<Sku>]转换成由商品名称字符数组组成的流[Stream<String>])
     */
    @Test
    public void flatMapTest() {
        list.stream()
                .flatMap(sku -> Arrays.stream(sku.getSkuName().split("")))
                .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
peek使用: 对流中元素进行遍历操作,与foreach类似 但不会销毁流元素
	/**
     * peek使用: 对流中元素进行遍历操作,与foreach类似 但不会销毁流元素
     */	
	@Test
    public void peekTest() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
sorted使用: 对流中元素进行排序 可选择自然排序或指定排序规则 有状态操作
 /**
     * sorted使用: 对流中元素进行排序 可选择自然排序或指定排序规则 有状态操作
     */
    @Test
    public void sortedTest() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                // sorted为有状态操作 会在sorted中做一个汇总 进而再执行下一个操作
                .sorted(Comparator.comparing(Sku::getTotalPrice))
                .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
distinct使用: 对流元素进行去重 有状态操作
/**
     * distinct使用: 对流元素进行去重 有状态操作
     */
    @Test
    public void distinctTest() {
        list.stream()
                .map(sku -> sku.getSkuCategory())
                .distinct()
                .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
skip使用: 跳过前N条记录 有状态操作
 /**
     * skip使用: 跳过前N条记录 有状态操作
     */
    @Test
    public void skipTest() {
        list.stream()
                .sorted(Comparator.comparing(Sku::getTotalPrice))
                .skip(3)
                .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
limit使用: 截断前N条记录 有状态操作
/**
     * limit使用: 截断前N条记录 有状态操作
     *
     * 可以与skip配合使用 形成一个假的分页 如:
     *  xxList.stream
     *   .skip((pageNumber-1) * pageSize)
     *   .limit(pageSize)
     *   .forEach(System.out::println)
     */
    @Test
    public void limitTest (){
        list.stream()
                .sorted(Comparator.comparing(Sku::getTotalPrice))
                .limit(3)
                .forEach(item -> System.out.println(JSON.toJSONString(item, true)));
    }
常见终端操作的实战 1----- 短路的终端操作 ----- 查找
allMatch使用: 含义为所有元素匹配返回true 是终端操作, 是短路操作
/**
     * allMatch使用: 含义为所有元素匹配返回true 是终端操作, 是短路操作
     */
    @Test
    public void allMatchTest() {
        boolean b = list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                // 匹配总价是否都大于100 如果有不满足的就停止(因为是短路操作)
                .allMatch(sku -> sku.getTotalPrice() > 100);
        System.out.println(b);
    }
anyMatch使用: 含义为任何元素匹配返回true 是终端操作, 是短路操作
/**
     * anyMatch使用: 含义为任何元素匹配返回true 是终端操作, 是短路操作
     */
    @Test
    public void anyMatchTest() {
        boolean b = list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                // 匹配总价是否有大于100的  有就停止(因为是短路操作)
                .anyMatch(sku -> sku.getTotalPrice() > 100);
        System.out.println(b);
    }
noneMatch使用: 含义为任何元素都不匹配返回true 是终端操作, 是短路操作
/**
     * noneMatch使用: 含义为任何元素都不匹配返回true 是终端操作, 是短路操作
     */
    @Test
    public void noneMatchTest() {
        boolean b = list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                // 匹配总价是否所有的都小于等于10000  有小于等于10000的就停止(因为是短路操作)
                .noneMatch(sku -> sku.getTotalPrice() > 10000);
        System.out.println(b);
    }
findFirst使用: 找到第一个元素
/**
     * findFirst使用: 找到第一个元素
     */
    @Test
    public void findFirstTest() {
        Optional<Sku> optional = list.stream()
                .findFirst();
        System.out.println(JSON.toJSONString(optional.get()));

    }
findAny使用: 找到任何一个元素
/**
     * findAny使用: 找到任何一个元素
     */
    @Test
    public void findAnyTest() {
        Optional<Sku> optional = list.stream()
                .findAny();
        System.out.println(JSON.toJSONString(optional.get()));
    }
常见终端操作的实战 2----- 非短路的终端操作 ----- 最大、最小、计数
max使用: 获取总价最大值
/**
     * max使用: 获取总价最大值
     */
    @Test
    public void maxTest() {
        OptionalDouble optionalDouble = list.stream()
                .mapToDouble(Sku::getTotalPrice)
                .max();
        System.out.println(optionalDouble.getAsDouble());

    }
min使用: 获取总价最小值
 /**
     * min使用: 获取总价最小值
     */
    @Test
    public void minTest() {
        OptionalDouble optionalDouble = list.stream()
                .mapToDouble(Sku::getTotalPrice)
                .min();
        System.out.println(optionalDouble.getAsDouble());

    }
count使用: 获取list总大小
/**
     * count使用: 获取list总大小
     */
    @Test
    public void countTest() {
        long count = list.stream()
                .count();
        System.out.println(count);
    }

流的构建

流的构建形式有4种:

  1. 由值创建流
  2. 由数组创建流
  3. 由文件生成流
  4. 由函数生成流(无限流)
1. 由值创建流
/**
     * 1. 通过数值创建流
     */
    @Test
    public void streamFromValue() {
        Stream stream = Stream.of(1, 2, 3, 4, 5);
        stream.forEach(System.out::println);
    }
2. 由数组创建流
/**
     * 2. 通过数组构建流
     */
    @Test
    public void streamFromArray() {
        int[] numbers = {1, 2, 3, 4, 5};
        IntStream stream = Arrays.stream(numbers);
        stream.forEach(System.out::println);
    }
3. 由文件生成流
  /**
     * 3. 通过文件构建流
     */
    @Test
    public void streamFromFile() throws IOException {
        Stream<String> stream = Files.lines(Paths.get("D:\\git_project\\996\\src\\test\\java\\lambda\\stream\\StreamConstructor.java"));
        stream.forEach(System.out::println);
    }
4. 由函数生成流(无限流)
 /**
     * 4. 通过函数构建流
     */
    @Test
    public void streamFromFunction() {
        // 第一种 迭代的方式  后一个基于上次的值
//        Stream<Integer> stream = Stream.iterate(0, n -> n + 2);
        // 第二种 生成器的方式 随机生成
        Stream<Double> stream = Stream.generate(Math::random);
        stream.limit(100)
                .forEach(System.out::println);
    }
流的收集
流的收集定义
  • 将流中的元素积累成一个结果
  • 作用于终端操作collect()上
  • collect(终端操作)/Collector(接口)/Collections(工具类)
预定义收集器功能
  • 将流元素规约和汇总为一个值
  • 将流元素分组
  • 将流元素分区
常见的预定义收集齐的使用
/**
     * 集合收集器 toList使用
     */
    @Test
    public void toListTest() {
        List<Sku> list = CartService.getCartSkuList();
        List<Sku> result = list.stream()
                .filter(sku -> sku.getTotalPrice() > 100)
                .collect(Collectors.toList());
        System.out.println(JSON.toJSONString(result, true));
    }

    /**
     * 集合分组
     */
    @Test
    public void groupTest() {
        List<Sku> list = CartService.getCartSkuList();
        // Map<分组条件, 结果集合>
        Map<Enum, List<Sku>> group = list.stream()
                .collect(Collectors.groupingBy(Sku::getSkuCategory));
        System.out.println(JSON.toJSONString(group, true));
    }

    /**
     * 集合分区  分区是分组的特殊情况  返回boolean类型的分组  false为一组不满足条件的 true为一组满足条件的
     */
    @Test
    public void partitionTest() {
        List<Sku> list = CartService.getCartSkuList();
        Map<Boolean, List<Sku>> partition = list.stream()
                .collect(Collectors.partitioningBy(sku -> sku.getTotalPrice() > 100));
        System.out.println(JSON.toJSONString(partition, true));
    }

优雅的关闭资源

垃圾回收(GC)的特点

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

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

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

物理资源不手动释放会产生的问题

  • 资源被长时间无效占用
  • 超过最大限制后,将无资源可用
  • 导致系统无法正常运行

try-with-resource简介

  • Java7引入新特性
  • 优雅关闭资源
  • 一种Java语法糖
实战案例

原始写法demo

package Resource;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * JDK7之前的文件拷贝功能
 */
public class FileCopyTest {

    @Test
    public void copyFile() {
        /**
         * 1. 创建输入/输出流
         * 2. 执行文件拷贝, 读取文件内容, 写入到另一个文件中
         * 3. **关闭文件流资源**
         */
        // 定义输入路径和输出路径
        String originalUrl = "D:\\git_project\\996\\lib\\FileCopyTest.java";
        String targetUrl = "D:\\git_project\\996\\targetTest\\target.txt";

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

        try {
            // 实例化文件流对象
            originalFileInputStream = new FileInputStream(originalUrl);
            targetFileOutputStream = new FileOutputStream(targetUrl);

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

            // 迭代 读取并写入字节
            while ((content = originalFileInputStream.read()) != -1) {
                targetFileOutputStream.write(content);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (targetFileOutputStream != null) {
                try {
                    targetFileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (originalFileInputStream != null) {
                try {
                    originalFileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}

try-with-resource写法demo

package Resource;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 基于JDK7之后,实现正确关闭流资源的方式
 * try - with - resource
 */
public class NewFileCopyTest {
    @Test
    public void copyFile() {
        // 先定义输入/输出路径
        String originalUrl = "D:\\git_project\\996\\lib\\NewFileCopyTest.java";
        String targetUrl = "D:\\git_project\\996\\targetTest\\newTargert.txt";

        // 初始化输入/输出流对象
        try (
                FileInputStream originFileInputStream = new FileInputStream(originalUrl);
                FileOutputStream targetFileOutputStream = new FileOutputStream(targetUrl);
        ) {
            int content;
            // 迭代拷贝数据
            while ((content = originFileInputStream.read()) != -1){
                targetFileOutputStream.write(content);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

实战案例源码解析

可查看编译后的class文件 编译后还是会有输入流输出流的close操作

try-with-resource使用

  • 多资源自动关闭
  • 实现AutoCloseable接口
  • 避免异常屏蔽

资源关闭顺序问题

  • 先开后关原则
  • 从外到内原则
  • 底层资源单独声明原则

资源关闭特殊情况

  • 资源对象被return的情况下,由调用方关闭
  • ByteArrayInputStream等不需要检查关闭的资源对象
  • 使用Socket获取的InputSream和OutputStream对象不需要关闭

Google Guava工具集

使用和避免null

大多数情况下,使用null表明的是某种缺失情况

Guava引入Optional表明可能为null的T类型引用,Optional实例可能包含非null的引用(引用存在),也可能什么也不包括(引用缺失)

受到Guava的启发,Java8将optional类作为一个新特性引入进Java8的类库

Java8中Optional学习

三种创建Optional对象方式
// 创建空的Optional对象
Optional.empty();

// 使用非null值创建Optional对象
Optional.of("bd2star");

// 使用任务值创建Optional对象
Optional.ofNullable(null);
optional常见用法
 		/**
         * 判断是否引用缺失的方法(建议不使用)
         */
        optional.isPresent();

        /**
         * 当optional引用存在时 执行括号中的方法
         * 类似的方法有: map filter flatMap
         */
        optional.ifPresent(System.out::println);

        /**
         * 当optional引用缺失时执行的方法
         */
        optional.orElse("引用缺失");
        /**
         * 自定义引用缺失时的返回值
         */
        optional.orElseGet(()->{
                return "自定义引用缺失返回值";
        });
        /**
         * 自定义引用缺失时抛出异常
         */
        optional.orElseThrow(() -> {
            throw new RuntimeException("自定义引用缺失时抛出异常");
        });
如何使用optinal防止空值
list.stream().forEach(System.out::println);

如果list为null则会立即抛出空指针异常

 Optional.ofNullable(list)
                .map(List::stream)
                .orElseGet(Stream::empty)
                .forEach(System.out::println);

改进后如果list传入是null值 则会呗orElseGet方法赋值为引用缺失返回值不会抛出异常

不可变集合

创建对象的不可变拷贝是一项很好的防御性编程技巧

Guava为所有的JDK标准集合类型和Guava新集合类型都提供了简单易用的不可变版本

不可变对象的优点
  • 当对象被不可信的库调用时,不可变形式是安全的
  • 不可变对象被多个线程调用时,不存在竞态条件问题
  • 不可变集合不需要考虑变化,因此可以节省时间和空间
  • 不可变对象因为有固定不变,可以作为常量来安全使用
JDK提供的unmodifiableXXX方法缺点
  • 笨重而且累赘
  • 不安全
  • 低效
public static void test(List<Integer> list) {
        list.remove(0);
    }

    public static void main(String[] args) {
        List<Integer> list = Lists.newArrayList(1, 2, 3);

        // 经过包装将可变的集合设置成不可变的集合 方法list为恶意修改
        List<Integer> newList = Collections.unmodifiableList(list);

        test(newList);
        System.out.println(newList);
    }
Guava提供的三种创建不可变集合的方式
  • copyOf方法: ImmutableSte.copyOf(set)
  • of方法: ImmutableSet.of(“a”, “b”, “c”)
  • Builder工具: ImmutableSet.builder().build()
ublic void immutable() {
        List<Integer> list = Lists.newArrayList(1, 2, 3);
        /**
         * 构建不可变集合对象三种方式
         */
        // 通过已经存在的集合创建
        ImmutableSet immutableSet1 = ImmutableSet.copyOf(list);

        // 通过初始值 直接创建不可变集合
        ImmutableSet immutableSet2 = ImmutableSet.of(1, 2, 3);

        // 以builder方式创建
        ImmutableSet immutableSet3 = ImmutableSet.builder()
                .add(1)
                .addAll(Sets.newHashSet(2, 3))
                .add(4)
                .build();
    }

新集合类型

Guava引入了很多JDK没有的,但明显有用新集合类型,这些新类型是为了和JDK集合框架共存,而没有往JDK集合抽象中硬塞其他概念

Set 无序不可重复的集合类型
List 有序 但是可重复的集合类型
Multiset 集合两者特点 无序 可重复  由Guava提供的
Multiset理解(以ArrayList和Map的视角)
  • 没有元素顺序显示的ArrayList
    • add(E): 添加单个给定元素
    • iterator(): 返回一个迭代器,包含Multiset所有元素(包括重复元素)
    • size(): 返回所有元素的总个数(包括重复元素)
  • Map<E, Integer>, 键为元素,值为计数
    • count(Object): 返回给定元素的计数
    • entrySet(): 返回set<Multiset.Entry>,和Map的entrySet类似
    • elementSet(): 返回所有不重复元素的Set,和Map的keySet类似
Multiset与Map的区别
  • 元素计数只能是正数
  • multiset.size()返回集合大小
  • multiset.iterator()会迭代重复元素
  • multiset支持直接设置元素的计数
  • 没有元素,multiset.count(E)为0
Multiset的实现(5种)
  • HashMultiset
  • TreeMultiset
  • LinkedHashMultiset
  • ConcurrentHashMultiset
  • ImmutableMultiset
Multiset的使用
/**
 * 实现: 使用Multiset统计一首古诗的文字出现频率
 */
public class MultisetTest {

    private static final String text = "楼角初消一缕霞,淡黄杨柳暗栖鸦,玉人和月摘梅花。\n" +
            "笑捻粉香归洞户,更垂帘幕护窗纱,东风寒似夜来些。";


    @Test
    public void handle() {
        // multiset 创建
        Multiset<Character> multiset = HashMultiset.create();
        // String 转换成 char 数组
        char[] chars = text.toCharArray();
        // 遍历数组 添加到multiset中
        Chars.asList(chars)
                .stream()
                .forEach(charItem -> {
                    multiset.add(charItem);
                });
        System.out.println("size: "+ multiset.size());
        System.out.println("count:" + multiset.count('人'));
    }
}

集合工具类

  • 使用Sets工具类操作Set集合
  • 使用Lists工具类操作List集合

demo演示

package guava;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.junit.Test;

import java.util.List;
import java.util.Set;

/**
 * 测试集合工具类
 */
public class SetsTest {
    /**
     * Sets工具类的常用方法
     * 并集/交集/差集/分解集合中的所有子集/求两个集合的笛卡尔积
     * <p>
     * Lists工具类的常用方式
     * 反转/拆分
     */

    private static final Set set1 = Sets.newHashSet(1, 2);
    private static final Set set2 = Sets.newHashSet(4);


    /**
     * 并集
     */
    @Test
    public void union() {
        Set<Integer> set = Sets.union(set1, set2);
        System.out.println(set);
    }

    /**
     * 交集
     */
    @Test
    public void intersection() {
        Set<Integer> set = Sets.intersection(set1, set2);
        System.out.println(set);
    }

    /**
     * 差集
     */
    @Test
    public void difference() {
        // 标准差集 如果元素属于a 而且不属于b 此时会存放到set里面 顺序重要
        Set<Integer> set = Sets.difference(set1, set2);
        System.out.println(set);

        // 相对差集方法 属于a而且不属于b 或者属于b而且不属于a  顺序不重要
        Set<Integer> set0 = Sets.symmetricDifference(set1, set2);
        System.out.println(set0);
    }

    /**
     * 拆解所有可能元素的子集合
     */
    @Test
    public void powerSet() {
        Set<Set<Integer>> powerSet = Sets.powerSet(set1);
        System.out.println(JSON.toJSONString(powerSet, true));
    }

    /**
     * 计算两个集合笛卡尔积
     */
    @Test
    public void cartesianProduct() {
        Set<List<Integer>> product = Sets.cartesianProduct(set1, set2);
        System.out.println(JSON.toJSONString(product));
    }

    /**
     * 将list拆分为3个一组的
     */
    @Test
    public void partition() {
        List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7);
        List<List<Integer>> partition = Lists.partition(list, 3);
        System.out.println(partition);
    }


    /**
     * list反转
     */
    @Test
    public void reverser() {
        List<Integer> list = Lists.newLinkedList();
        list.add(1);
        list.add(2);
        list.add(3);
        List<Integer> reverse = Lists.reverse(list);
        System.out.println(reverse);
    }


}

对字节流/字符流提供的工具方法

  • ByteStreams: 提供对InputStream/OutputStream的操作
  • CharStreams: 提供对Reader/Writer的操作

对源(Source)与汇(Sink)的抽象

  • 源是可读的: ByteSource/CharSource
  • 汇是可写的: ByteSink/CharSink

IO实战

package guava;

import com.google.common.base.Charsets;
import com.google.common.io.CharSink;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import org.junit.Test;

import java.io.File;
import java.io.IOException;

/**
 * 演示如何使用流(Source)与汇(Sink)来对文件进行常用操作
 */
public class IOTest {

    @Test
    public void copyFile() throws IOException {
        /**
         * 创建对应的Source和Sink
         */
        CharSource charSource = Files.asCharSource(new File("SourceTest.txt"), Charsets.UTF_8);
        CharSink charSink = Files.asCharSink(new File("TargetTest.txt"), Charsets.UTF_8);

        /**
         * 拷贝
         */
        charSource.copyTo(charSink);
    }
}

线程相关

线程与加入线程池的对比

package com.mooc.bd2star.threadpool;

import org.junit.Test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadVs {

    /**
     * 老的处理方式
     */
    @Test
    public void oldHandle() throws InterruptedException {

        /**
         * 使用循环来模拟许多用户请求的场景
         */
        for (int request = 0; request <= 100; request++) {
            new Thread(() -> {
                System.out.println("文档处理开始!");

                try {
                    // 将word转换为PDF格式: 处理时长很长的耗时过程
                    Thread.sleep(1000L * 30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("文档处理结束!");
            }).start();
        }
        Thread.sleep(1000L * 1000);
    }

    /**
     * 新的处理方式  使用线程池 每个线程池中线程的个数是10
     */
    @Test
    public void newHandle() throws InterruptedException {
        /**
         * 开启了一个线程池: 线程个数是10个
         */
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        /**
         * 使用循环来模拟许多用户请求的场景
         */
        for (int request = 0; request <= 100; request++) {
            threadPool.execute(() -> {
                System.out.println("文档处理开始!");

                try {
                    // 将word转换为PDF格式: 处理时长很长的耗时过程
                    Thread.sleep(1000L * 30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("文档处理结束!");
            });
        }
        Thread.sleep(1000L * 1000);
    }
}

线程池简介

什么是线程池?

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

线程池带来的好处

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

简单的线程池设计

V1.0版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pFGawiot-1575535927702)(images/image-20191126102711035.png)]
在这里插入图片描述
V2.0版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3aNKJzLt-1575535927703)(images/image-20191126102936239.png)]
在这里插入图片描述

线程池的核心参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Aiuxay6-1575535927703)(images/image-20191126103408672.png)]
在这里插入图片描述

线程池处理流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEIPfF0e-1575535927704)(images/image-20191126104020307.png)]
在这里插入图片描述

线程池可选择的阻塞队列

  • 无界队列
  • 有界队列
  • 同步移交队列

demo案例

package com.mooc.bd2star.threadpool;

import org.junit.Test;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;

public class QueueTest {

    /**
     * 基于数组有界队列 队列容量为10 (添加到了10个时候程序会阻塞)
     *
     * @throws InterruptedException
     */
    @Test
    public void arrayBlockingQueueTest() throws InterruptedException {
        /**
         * 基于数组的有界阻塞队列 队列容量为10
         */
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
        // 循环向队列添加元素
        for (int i = 0; i < 20; i++) {
            queue.put(i);
            System.out.println("向队列添加值: " + i);
        }
    }

    /**
     * 基于链表有界/无界队列
     *
     * @throws InterruptedException
     */
    @Test
    public void linkedBlockingQueueTest() throws InterruptedException {
        /**
         * 基于链表的有界阻塞队列 队列容量为10
         */
        // 有界的阻塞队列  10个
//        LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
        // 无界的阻塞队列
        LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        // 循环向队列添加元素
        for (int i = 0; i < 20; i++) {
            queue.put(i);
            System.out.println("向队列添加值: " + i);
        }
    }

    /**
     * 同步移交阻塞队列  没有储存能力的 类似于生产者 接收一个消费一个
     * @throws InterruptedException
     */
    @Test
    public void synchronousQueueTest() throws InterruptedException {
        SynchronousQueue<Integer> queue = new SynchronousQueue<>();
        // 插入值
        new Thread(() -> {
            try {
                queue.put(1);
                System.out.println("插入成功");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 删除值
//        new Thread(() -> {
//            try {
//                queue.take();
//                System.out.println("删除成功");
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }).start();

        Thread.sleep(1000L * 30);
    }

}

线程池可选择的饱和策略

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

线程池的执行示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifVDC0xu-1575535927704)(images/image-20191126152027204.png)]
在这里插入图片描述

常用线程池之newCachedThreadPool

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WmWdSO4y-1575535927704)(images/image-20191126152236646.png)]
在这里插入图片描述

常用线程池之newFixedThreadPool

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NDVAFlY8-1575535927705)(images/image-20191126152337946.png)]
在这里插入图片描述

常用线程池之newSingleThreadExecutor

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fknbo5GF-1575535927705)(images/image-20191126152423267.png)]
在这里插入图片描述

案例: 向线程池提交任务

两种方式:

  • submit方法(有返回值给主线程)
  • execute方法(无返回值给主线程)
package com.mooc.bd2star.threadpool;

import org.junit.Test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class RunTest {

    /**
     * 方式一  利用submit方法提交任务 接受任务的返回结果
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void submitTest() throws ExecutionException, InterruptedException {
        // 创建线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();

        // 利用submit方法提交任务 接受任务的返回结果
        Future<Integer> future = threadPool.submit(() -> {
            Thread.sleep(1000L * 10);
            return 2 * 5;
        });

        /**
         * 阻塞方法,直到任务有返回值, 才向下执行
         */
        Integer num = future.get();
        System.out.println("执行结果: " + num);
    }


    /**
     * 方式二 利用execute方法 该方法没有返回值
     * @throws InterruptedException
     */
    @Test
    public void executeTest() {
        // 创建线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();

        // 利用execute方法提交任务 接受任务的返回结果
        threadPool.execute(() -> {
            try {
                Thread.sleep(1000L * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Integer num = 2 * 5;
            System.out.println("执行结果: " + num);
        });
    }
}

线程池的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxMLOOuF-1575535927705)(images/image-20191126153538174.png)]
在这里插入图片描述

Lombok学习与使用

Lombok简介

Project Lombok 是一个Java库,可以自动插入编辑器并构建工具,无需在写getter或equals方法,使用一个注释,您的类会具有一个功能齐全的构建器

Lombok实现原理

注解的两种解析方式

  • 运行时解析
  • 编译时解析

编译时解析的两种机制

  • Annotation Processing Tool(注解处理器)
  • Pluggable Annotation Processing API(JSR269插入式注解处理器)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iip6xVZG-1575535927705)(images/image-20191126154519079.png)]
在这里插入图片描述

常用注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CqowrCVO-1575535927706)(images/image-20191126154652072.png)]
在这里插入图片描述

IDEA Lombok插件的安装

案例: Lombok常用注解使用

@Getter使用
@Setter使用
@ToString使用
@EqualsAndHashCode使用
@Data使用(包含了 @Getter @Setter @ToString @EqualsAndHashCode)
@Val使用 弱语言变量 作用在方法中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L2UZLqbo-1575535927706)(images/image-20191126171713640.png)]
在这里插入图片描述

@NotNull使用 生成非空的检查 一般用在方法的入参或者属性上
@AllArgsConstructor、@NoArgsConstructor、@RequiredArgsConstructor使用
@Cleanup使用 资源关闭

Lombok优点

  • 通过注解自动生成样板代码,提高开发效率
  • 代码简洁,只关注相关属性
  • 新增属性后,无需刻意修改相关方法

Lombok确定

  • 降低了源代码的可读性和完整性
  • 加大对问题排查的难度
  • 需要IDE的相关插件的支持

验证框架

分层验证与JavaBean验证

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NvmYiHyT-1575535927706)(images/image-20191127164216481.png)]
在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WBZWJPzL-1575535927706)(images/image-20191127164232349.png)]
在这里插入图片描述

Bean Validation简介

Bean Validation为 JavaBean 验证 定义了相应的 元数据模型和API

JCP JSR简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OunNqINU-1575535927707)(images/image-20191127164509849.png)]
在这里插入图片描述

  • JSR303 对应的是 BeanValidation1.0
  • JSR349 对应的是 BeanValidation1.1
  • JSR380 对应的是 BeanValidation2.0

Bean Validation 与 Hibernate Validation

  • Bean Validation1.0 参考实现: Hibernate Validation4.3.1.Final
  • Bean Validation1.1 参考实现: Hibernate Validation5.1.1.Final
  • Bean Validation2.0 参考实现: Hibernate Validation6.0.1.Final

Hibernate Validation 与 Spring Validation

Spring Validation在Hibernate Validation的基础上对其进行了二次封装,以满足在Spring环境中更简单、高效的对数据进行验证

常用的约束注解

  • 空值校验类: @Null, @NotNull, @NotEmpty, @NotBlank等
  • 范围校验类: @Min, @Size, @Digits, @Future, @Negative等
  • 其他校验类: @Email, @URL, @AssertTrue, @Pattern等

实战案例: 初级约束注解使用

pom文件引入相关jar

  <!-- Validation 相关依赖 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.16.Final</version>
        </dependency>
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.6</version>
        </dependency>
package com.mooc.bd2star.validation;

import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.*;
import java.util.Date;
import java.util.List;

/**
 * 待验证对象实体类
 * 用户信息类
 */
public class UserInfo {

    // 用户ID
    @NotNull(message = "用户ID不能为空")
    private String userId;

    /**
     * 用户名
     * NotEmpty 不会自动去掉前后空格
     */
    @NotEmpty(message = "用户名不能为空")
    private String userName;

    /**
     * 密码
     * NotBlank 自动去掉字符串前后空格后验证是否为空
      */
    @NotBlank(message = "用户密码不能为空")
    @Length(min = 6, max = 20, message = "密码长度不能少于6位多于12位")
    private String passWord;
    // 邮箱

    @Email(message = "邮箱必须为有效邮箱")
    private String email;
    // 手机号
    private String phone;

    @Min(value = 18, message = "年龄不能小于18岁")
    @Max(value = 60, message = "年龄不能大于60岁")
    private Integer age;

    // 生日
    @Past(message = "生日不能为未来或当前时间点")
    private Date birthday;
    
    // 好友列表
    @Size(min = 1, message = "不能少于1个好友")
    private List<UserInfo> friends;

  	// 省略 getter setter方法
}

package com.mooc.bd2star.validation;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Set;

/**
 * 验证测试类
 */
public class ValidationTest {

    // 验证器对象
    private Validator validator;

    // 待验证对象
    private UserInfo userInfo;

    // 验证结果集合
    private Set<ConstraintViolation<UserInfo>> set;


    /**
     * 初始化操作
     */
    @Before
    public void init() {
        // 初始化验证器
        validator = Validation.buildDefaultValidatorFactory()
                .getValidator();
        // 初始化待验证对象 - 用户信息
        userInfo = new UserInfo();
        userInfo.setUserId("110110");
        userInfo.setUserName("bd2star");
        userInfo.setPassWord("123456");
        userInfo.setEmail("bd2star@aliyun.com");
        userInfo.setAge(30);
        Calendar calendar = Calendar.getInstance();
        calendar.set(2012, 1, 1);
        userInfo.setBirthday(calendar.getTime());

        UserInfo friend = new UserInfo();
        friend.setUserId("110111");
        friend.setUserName("bd3star");
        friend.setPassWord("123456");
        friend.setEmail("bd3star@aliyun.com");
        userInfo.setFriends(new ArrayList(){{add(friend);}});
    }

    /**
     * 结果打印
     */
    @After
    public void print() {
        set.forEach(item -> System.out.println(item.getMessage()));

    }

    /**
     * 初级验证
     */
    @Test
    public void Validation() {
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo);
    }

    /**
     * 中级验证 验证泛型内的对象
     */
    @Test
    public void Validation1() {
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo);
    }

}


@NotEmpty, @NotBlank 区别:
1. 都会检验空的字符串
2. NotBlank会自动去掉字符串前后空格  再验证是否为空的字符串  NotEmpty不会
	比如 " " notBlank会认为为空的字符串  NotEmpty会认为不是用的字符串 因为有空白字符

实战案例: 中级约束注解使用

  • 级联验证
  • 分组验证
  • 组序列
package com.mooc.bd2star.validation;

import org.hibernate.validator.constraints.Length;

import javax.validation.GroupSequence;
import javax.validation.Valid;
import javax.validation.constraints.*;
import javax.validation.groups.Default;
import java.util.Date;
import java.util.List;

/**
 * 待验证对象实体类
 * 用户信息类
 */
public class UserInfo {

    // 登录场景
    public interface LoginGroup {
    }

    // 注册场景
    public interface RegisterGroup {
    }

    // 组排序场景
    @GroupSequence({
            LoginGroup.class,
            RegisterGroup.class,
            Default.class
    })
    public interface Group {

    }

    // 用户ID
    @NotNull(message = "用户ID不能为空", groups = LoginGroup.class)
    private String userId;

    /**
     * 用户名
     * NotEmpty 不会自动去掉前后空格
     */
    @NotEmpty(message = "用户名不能为空")
    private String userName;

    /**
     * 密码
     * NotBlank 自动去掉字符串前后空格后验证是否为空
     */
    @NotBlank(message = "用户密码不能为空")
    @Length(min = 6, max = 20, message = "密码长度不能少于6位多于12位")
    private String passWord;
    // 邮箱

    @NotNull(message = "邮箱不能为空", groups = RegisterGroup.class)
    @Email(message = "邮箱必须为有效邮箱")
    private String email;
    // 手机号
    private String phone;

    @Min(value = 18, message = "年龄不能小于18岁")
    @Max(value = 60, message = "年龄不能大于60岁")
    private Integer age;

    // 生日
    @Past(message = "生日不能为未来或当前时间点")
    private Date birthday;

    // 好友列表
    @Size(min = 1, message = "不能少于1个好友")
    private List<@Valid UserInfo> friends;

    // 省略getter setter方法
}

package com.mooc.bd2star.validation;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Set;

/**
 * 验证测试类
 */
public class ValidationTest {

    // 验证器对象
    private Validator validator;

    // 待验证对象
    private UserInfo userInfo;

    // 验证结果集合
    private Set<ConstraintViolation<UserInfo>> set;


    /**
     * 初始化操作
     */
    @Before
    public void init() {
        // 初始化验证器
        validator = Validation.buildDefaultValidatorFactory()
                .getValidator();
        // 初始化待验证对象 - 用户信息
        userInfo = new UserInfo();
        userInfo.setUserId("110110");
        userInfo.setUserName("bd2star");
        userInfo.setPassWord("123456");
        userInfo.setEmail("bd2star@aliyun.com");
        userInfo.setAge(30);
        Calendar calendar = Calendar.getInstance();
        calendar.set(2012, 1, 1);
        userInfo.setBirthday(calendar.getTime());

        UserInfo friend = new UserInfo();
        friend.setUserId("110111");
        friend.setUserName("bd3star");
        friend.setPassWord("123456");
        friend.setEmail("bd3star@aliyun.com");
        userInfo.setFriends(new ArrayList(){{add(friend);}});
    }

    /**
     * 结果打印
     */
    @After
    public void print() {
        set.forEach(item -> System.out.println(item.getMessage()));

    }

    /**
     * 初级验证
     */
    @Test
    public void Validation() {
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo);
    }

    /**
     * 中级验证 验证泛型内的对象
     */
    @Test
    public void Validation1() {
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo);
    }
    /**
     * 中级验证 分组验证测试方法
     */
    @Test
    public void Validation2() {
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo, UserInfo.LoginGroup.class);
//        set = validator.validate(userInfo, UserInfo.RegisterGroup.class);
//        set = validator.validate(userInfo, UserInfo.LoginGroup.class, UserInfo.RegisterGroup.class);
    }

    /**
     * 组排序  会验证出不通过后就跳出 避免后面做无用的验证
     */
    @Test
    public void groupSequenceValidation() {
        set = validator.validate(userInfo, UserInfo.Group.class);
    }

}

实战案例: 高级约束注解使用

  • 校验参数
  • 校验返回值
  • 校验构造方法
package com.mooc.bd2star.validation;

import org.hibernate.validator.constraints.Length;

import javax.validation.GroupSequence;
import javax.validation.Valid;
import javax.validation.constraints.*;
import javax.validation.groups.Default;
import java.util.Date;
import java.util.List;

/**
 * 待验证对象实体类
 * 用户信息类
 */
public class UserInfo {

    // 登录场景
    public interface LoginGroup {
    }

    // 注册场景
    public interface RegisterGroup {
    }

    // 组排序场景
    @GroupSequence({
            LoginGroup.class,
            RegisterGroup.class,
            Default.class
    })
    public interface Group {

    }

    // 用户ID
    @NotNull(message = "用户ID不能为空", groups = LoginGroup.class)
    private String userId;

    /**
     * 用户名
     * NotEmpty 不会自动去掉前后空格
     */
    @NotEmpty(message = "用户名不能为空")
    private String userName;

    /**
     * 密码
     * NotBlank 自动去掉字符串前后空格后验证是否为空
     */
    @NotBlank(message = "用户密码不能为空")
    @Length(min = 6, max = 20, message = "密码长度不能少于6位多于12位")
    private String passWord;
    // 邮箱

    @NotNull(message = "邮箱不能为空", groups = RegisterGroup.class)
    @Email(message = "邮箱必须为有效邮箱")
    private String email;
    // 手机号
    private String phone;

    @Min(value = 18, message = "年龄不能小于18岁")
    @Max(value = 60, message = "年龄不能大于60岁")
    private Integer age;

    // 生日
    @Past(message = "生日不能为未来或当前时间点")
    private Date birthday;

    // 好友列表
    @Size(min = 1, message = "不能少于1个好友")
    private List<@Valid UserInfo> friends;
    
 	// 省略getter setter方法
}

package com.mooc.bd2star.validation;

import javax.validation.Valid;

/**
 * 用户信息服务类
 */
public class UserInfoService {

    /**
     * userInfo 作为输入参数进行校验
     * @param userInfo
     */
    public void setUserInfo (@Valid UserInfo userInfo) {

    }

    /**
     * userInfo 作为输出参数
     * @return
     */
    public @Valid UserInfo getUserInfo() {
        return new UserInfo();
    }

    /**
     * 默认构造函数
     */
    public UserInfoService () {

    }

    /**
     * 接受userInfo作为参数的构造函数
     * @param userInfo
     */
    public UserInfoService(@Valid UserInfo userInfo) {

    }
}

package com.mooc.bd2star.validation;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Set;

/**
 * 验证测试类
 */
public class ValidationTest {

    // 验证器对象
    private Validator validator;

    // 待验证对象
    private UserInfo userInfo;

    // 验证结果集合
    private Set<ConstraintViolation<UserInfo>> set;

    // 验证结果集合
    private Set<ConstraintViolation<UserInfoService>> otherSet;

    /**
     * 初始化操作
     */
    @Before
    public void init() {
        // 初始化验证器
        validator = Validation.buildDefaultValidatorFactory()
                .getValidator();
        // 初始化待验证对象 - 用户信息
        userInfo = new UserInfo();
        userInfo.setUserId("110110");
        userInfo.setUserName("bd2star");
        userInfo.setPassWord("123456");
        userInfo.setEmail("bd2star@aliyun.com");
        userInfo.setAge(30);
        Calendar calendar = Calendar.getInstance();
        calendar.set(2012, 1, 1);
        userInfo.setBirthday(calendar.getTime());

        UserInfo friend = new UserInfo();
        friend.setUserId("110111");
        friend.setUserName("bd3star");
        friend.setPassWord("123456");
        friend.setEmail("bd3star@aliyun.com");
        userInfo.setFriends(new ArrayList() {{
            add(friend);
        }});
    }

    /**
     * 结果打印
     */
    @After
    public void print() {
//        set.forEach(item -> System.out.println(item.getMessage()));
        otherSet.forEach(item -> System.out.println(item.getMessage()));

    }

 

    /**
     * 对方法输入参数进行约束注解校验
     */
    @Test
    public void paramValidation() throws NoSuchMethodException {
        // 获取校验执行器
        ExecutableValidator executableValidator = validator.forExecutables();

        // 待验证对象
        UserInfoService service = new UserInfoService();
        // 待验证方法
        Method method = service.getClass().getMethod("setUserInfo", UserInfo.class);
        // 方法的输入参数
        Object[] paramObjects = new Object[]{new UserInfo()};


        // 对方法的输入参数进行校验
        otherSet = executableValidator.validateParameters(service, method, paramObjects);
    }

    /**
     * 对方法返回值进行约束校验
     */
    @Test
    public void returnValueValidation() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 获取校验执行器
        ExecutableValidator executableValidator = validator.forExecutables();
        // 构造要验证的方法对象
        UserInfoService service = new UserInfoService();
        Method method = service.getClass().getMethod("getUserInfo");

        // 调用方法得到返回值
        Object returnValue = method.invoke(service);
        // 校验方法返回值是否符合约束
        otherSet = executableValidator.validateReturnValue(service, method, returnValue);

    }

    /**
     * 对构造函数输入参数进行校验
     */
    @Test
    public void constructorValidation() throws NoSuchMethodException {

        // 获取校验执行器
        ExecutableValidator executableValidator = validator.forExecutables();

        // 获取构造函数
        Constructor<UserInfoService> constructor = UserInfoService.class.getConstructor(UserInfo.class);

        Object[] paramObjects = new Object[]{new UserInfo()};
        // 校验构造函数
        otherSet = executableValidator.validateConstructorParameters(constructor, paramObjects);
    }

}

完成验证的步骤

  1. 约束注解的定义
  2. 约束验证规则(约束验证器)
  3. 约束注解的声明
  4. 约束验证流程

自定义手机号约束注解

  • 定义 @interface Phone注解
  • 实现约束验证器PhoneValidator.java
  • 声明@Phone约束验证
  • 执行手机号约束验证流程
package com.mooc.bd2star.validation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * 自定义手机号约束注解
 */
@Documented
// 注解的作用
@Target({ElementType.FIELD})
// 注解的保留策略
@Retention(RetentionPolicy.RUNTIME)
// 与非约束注解的不同之处
// 约束注解关联验证器类
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {

    // 约束注解验证时输出的信息
    String message() default "手机号校验错误";

    // 约束注解在验证时所属的组别
    Class<?>[] groups() default {};

    // 约束注解的有效负载
    Class<? extends Payload>[] payload() default {};
}

package com.mooc.bd2star.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 自定义手机号约束注解关联验证器
 */
public class PhoneValidator implements ConstraintValidator<Phone, String> {

    /**
     * 自定义校验逻辑方法
     * @param s
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        // 演示demo
        // 手机号验证规则 158...
        String check = "158\\d{8}";
        Pattern regex = Pattern.compile(check);

        String phone = Optional.ofNullable(s).orElse("");

        Matcher matcher = regex.matcher(phone);
        return matcher.matches();
    }
}

package com.mooc.bd2star.validation;

import org.hibernate.validator.constraints.Length;

import javax.validation.GroupSequence;
import javax.validation.Valid;
import javax.validation.constraints.*;
import javax.validation.groups.Default;
import java.util.Date;
import java.util.List;

/**
 * 待验证对象实体类
 * 用户信息类
 */
public class UserInfo {

    // 登录场景
    public interface LoginGroup {
    }

    // 注册场景
    public interface RegisterGroup {
    }

    // 组排序场景
    @GroupSequence({
            LoginGroup.class,
            RegisterGroup.class,
            Default.class
    })
    public interface Group {

    }

    // 用户ID
    @NotNull(message = "用户ID不能为空", groups = LoginGroup.class)
    private String userId;

    /**
     * 用户名
     * NotEmpty 不会自动去掉前后空格
     */
    @NotEmpty(message = "用户名不能为空")
    private String userName;

    /**
     * 密码
     * NotBlank 自动去掉字符串前后空格后验证是否为空
     */
    @NotBlank(message = "用户密码不能为空")
    @Length(min = 6, max = 20, message = "密码长度不能少于6位多于12位")
    private String passWord;

    // 邮箱
    @NotNull(message = "邮箱不能为空", groups = RegisterGroup.class)
    @Email(message = "邮箱必须为有效邮箱")
    private String email;

    // 手机号
    @Phone(message = "手机号不是158开头")
    private String phone;

    @Min(value = 18, message = "年龄不能小于18岁")
    @Max(value = 60, message = "年龄不能大于60岁")
    private Integer age;

    // 生日
    @Past(message = "生日不能为未来或当前时间点")
    private Date birthday;

    // 好友列表
    @Size(min = 1, message = "不能少于1个好友")
    private List<@Valid UserInfo> friends;

    // 省略 getter setter方法
}

package com.mooc.bd2star.validation;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Set;

/**
 * 验证测试类
 */
public class ValidationTest {

    // 验证器对象
    private Validator validator;

    // 待验证对象
    private UserInfo userInfo;

    // 验证结果集合
    private Set<ConstraintViolation<UserInfo>> set;



    /**
     * 初始化操作
     */
    @Before
    public void init() {
        // 初始化验证器
        validator = Validation.buildDefaultValidatorFactory()
                .getValidator();
        // 初始化待验证对象 - 用户信息
        userInfo = new UserInfo();
        userInfo.setUserId("110110");
        userInfo.setUserName("bd2star");
        userInfo.setPassWord("123456");
        userInfo.setEmail("bd2star@aliyun.com");
        userInfo.setAge(30);
        Calendar calendar = Calendar.getInstance();
        calendar.set(2012, 1, 1);
        userInfo.setBirthday(calendar.getTime());
        userInfo.setPhone("15808080808");

        UserInfo friend = new UserInfo();
        friend.setUserId("110111");
        friend.setUserName("bd3star");
        friend.setPassWord("123456");
        friend.setEmail("bd3star@aliyun.com");
        friend.setPhone("15808080808");
        userInfo.setFriends(new ArrayList() {{
            add(friend);
        }});
    }

    /**
     * 结果打印
     */
    @After
    public void print() {
        set.forEach(item -> System.out.println(item.getMessage()));


    }

    /**
     * 初级验证
     */
    @Test
    public void Validation() {
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo);
    }


}

ng phone;

@Min(value = 18, message = "年龄不能小于18岁")
@Max(value = 60, message = "年龄不能大于60岁")
private Integer age;

// 生日
@Past(message = "生日不能为未来或当前时间点")
private Date birthday;

// 好友列表
@Size(min = 1, message = "不能少于1个好友")
private List<@Valid UserInfo> friends;

// 省略 getter setter方法

}


```java
package com.mooc.bd2star.validation;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Set;

/**
 * 验证测试类
 */
public class ValidationTest {

    // 验证器对象
    private Validator validator;

    // 待验证对象
    private UserInfo userInfo;

    // 验证结果集合
    private Set<ConstraintViolation<UserInfo>> set;



    /**
     * 初始化操作
     */
    @Before
    public void init() {
        // 初始化验证器
        validator = Validation.buildDefaultValidatorFactory()
                .getValidator();
        // 初始化待验证对象 - 用户信息
        userInfo = new UserInfo();
        userInfo.setUserId("110110");
        userInfo.setUserName("bd2star");
        userInfo.setPassWord("123456");
        userInfo.setEmail("bd2star@aliyun.com");
        userInfo.setAge(30);
        Calendar calendar = Calendar.getInstance();
        calendar.set(2012, 1, 1);
        userInfo.setBirthday(calendar.getTime());
        userInfo.setPhone("15808080808");

        UserInfo friend = new UserInfo();
        friend.setUserId("110111");
        friend.setUserName("bd3star");
        friend.setPassWord("123456");
        friend.setEmail("bd3star@aliyun.com");
        friend.setPhone("15808080808");
        userInfo.setFriends(new ArrayList() {{
            add(friend);
        }});
    }

    /**
     * 结果打印
     */
    @After
    public void print() {
        set.forEach(item -> System.out.println(item.getMessage()));


    }

    /**
     * 初级验证
     */
    @Test
    public void Validation() {
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo);
    }


}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iBaoxing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值