JavaSE进阶笔记:07(stream流、异常体系、日志框架)

128 阅读11分钟

一、Stream流

1.概述

  • JDK8中,得益于Lambda的函数式编程,引入Stream;
  • 目的:用于简化集合和数组操作的API;

2.使用步骤

  • 先得到集合或者数组的Stream流
  • 元素放上去,进过一次次过筛,留下想要的元素;
  • Stream流简化的API来操作元素
/**
 * 使用Stream流:
 * 寻找集合中“张”开头且长度为3的名字
 */
public class StreamDemo01 {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        Collections.addAll(names,"张36","张三","王海峰","张23","张风风风","张九六");
        //使用Stream流
        names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
        /**
         * 张36
         * 张23
         * 张九六
         */
    }
}

3.Stream流的三种方法

  • 获取Stream流
    • 创建流水线,数据放上,准备操作;
    • 集合:
      • 直接使用stream()方法
    • 数组:
      • 使用静态的方法Arrays.stream()
      • Stream.of()
  • 中间方法
    • 流水线上操作,一次操作后,还可以其他操作
  • 终结方法
    • 一个Stream流只能有一个终结方法,是流水线最后一个操作;
/**
 * 集合和数组
 *      创建Stream流的方式
 */
public class StreamDemo02 {
    public static void main(String[] args) {
        /**
         * 集合
         */
        Collection<String> lists = new ArrayList<>();
        Stream<String> ls = lists.stream();

        HashMap<String, Integer> maps = new HashMap<>();
        //键流
        Stream<String> keySets = maps.keySet().stream();
        //值流
        Stream<Integer> vs = maps.values().stream();
        //键值对流
        Stream<Map.Entry<String, Integer>> entryStream = maps.entrySet().stream();

        /**
         * 数组
         */
        String[] names = {"完美","结论","五项","全能"};
        Stream<String> namesStream = Arrays.stream(names);
        Stream<String> namesStream2 = Stream.of(names);
    }
}

4. Stream流的常用API

  • 注意:
  • 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
  • Stream流中无法直接修改集合和数组
/**
 * Stream流的API
 * 过滤;计数;取前面几个值;取之(n)后的值
 * 去重;Map加工方法(修改值,变换类型,封入对象)
 * 合并流;
 */
public class StreamDemo03 {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        Collections.addAll(names,"张36","张三","王海峰","张23","张风风风","张九六");

        //Stream<T> filter(Predicate<? super T> predicate);
        names.stream().filter(s -> s.startsWith("张"));
        //过滤条件,遍历集合中的每一个值,把符合条件的值返回
        names.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                //return false;
                return s.startsWith("张");
            }
        });
        
        //终结:返回符合条件的值个数
        long count = names.stream().filter(s -> s.length() == 3).count();
        System.out.println(count);
        //取前两个值输出
        names.stream().limit(2).forEach(s -> System.out.println(s));
        System.out.println("前=============后面的:");
        //跳过前两个
        names.stream().skip(2).forEach(s -> System.out.println(s));
        //去重
        names.stream().distinct().forEach(System.out::println);

        
        //Map加工方法
        //new Function<String, Strin> 前一个是集合内值得类型,后一个是加工后值得类型
        //集合所有元素前+妈的
        names.stream().map(new Function<String, String>() {
            @Override
            public String apply(String s) {
                return "妈的"+ s;
            }
        });
        names.stream().map(s -> "妈的2"+ s);

        //需求:把上述名字加工成Teacher对象
        //循环输出,对象都是s,可以直接System.out::println
        names.stream().map(s -> new Teacher(s)).forEach(System.out::println);
        //简化:构造器引用,方法引用
        names.stream().map(Teacher::new).forEach(System.out::println);

        //合并流:合并后的类型必须是两个的父类,或者相同类
        Stream<String> ss1 = names.stream().filter(s -> s.startsWith("张"));
        Stream<Integer> ss2 = Stream.of(55,66);
        Stream<Object> ss3 = Stream.concat(ss1,ss2);
        ss3.forEach(System.out::println);
    }
}

5. Stream流的常见终结方法

  • 终结操作方法,调用完成后,流无法继续使用,不会再返回Stream了
    • forEach()
    • count()

6. Stream流综合应用

/**
 * Stream流的案例
 * 1.求每个部门工资最高的员工,封入优秀员工表
 * 2.取平均工资,去掉最高最低
 * 3.统计两个部门的平均工资,去掉最高最低   两个流,合并
 */
public class StreamDemo04 {
    public static double allMoney = 0;
    public static double allTwoMoney = 0;
    public static void main(String[] args) {
        ArrayList<Employee> one = new ArrayList<>();
        one.add(new Employee("A",'男',3000,2000,null));
        one.add(new Employee("B",'男',3600,2000,null));
        one.add(new Employee("C",'男',9000,1000,null));
        one.add(new Employee("D",'男',8800,2000,null));
        ArrayList<Employee> two = new ArrayList<>();
        two.add(new Employee("AA",'男',6600,2000,null));
        two.add(new Employee("BB",'男',7800,2500,"键"));
        two.add(new Employee("CC",'男',6900,2000,null));
        two.add(new Employee("DD",'男',3600,4000,"欠"));
        two.add(new Employee("EE",'男',3000,6000,null));

        //1.每个部门工资最高的员工
        //选出一个值,直接get取到
       // Employee eMax1 = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())).get();
        TopperFormer topperFormerOne = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
                .map(e -> new TopperFormer(e.getName(), e.getSalary() + e.getBonus())).get();
        System.out.println(topperFormerOne);
        

        //2.取平均工资,去掉最高最低
        one.stream().sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
                .skip(1).limit(one.size()-2)
                .forEach(e ->{
                    //累计工资和;独立方法栈,变量必须取在main外的静态变量
                    allMoney+=(e.getSalary()+e.getBonus());
                });
        //一部门平均工资:7800.0
        System.out.println("一部门平均工资:"+allMoney/(one.size()-2));
        

        //3.统计两个部门的平均工资,去掉最高最低
        //两个流,合并
        Stream<Employee> oneStream = one.stream();
        Stream<Employee> twoStream = two.stream();
        Stream<Employee> concatStream = Stream.concat(oneStream, twoStream);
        concatStream.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
                .skip(1).limit(one.size()+two.size()-2)
                .forEach(e ->{
                    //累计工资和;独立方法栈,变量必须取在main外的静态变量
                    allTwoMoney+=(e.getSalary()+e.getBonus());
                });
        //处理精度问题
        BigDecimal a = BigDecimal.valueOf(allTwoMoney);
        BigDecimal b = BigDecimal.valueOf(one.size()+two.size()-2);
        //保留两位小数,四舍五入
        //两部门平均工资:8571.43  8571.42857142857
        System.out.println("两部门平均工资:"+a.divide(b,2, RoundingMode.HALF_UP));
    }
}

7.收集Stream流

  • 收集含义:流操作后的结果传回集合或数组中。
  • Stream流:方便操作集合、数组手段;
  • 集合、数组:才是开发目的;
/**
 * 收集Stream流
 */
public class StreamDemo05 {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        Collections.addAll(names,"张36","张三","王海峰","张23","张风风风","张九六");

        Stream<String> stream = names.stream().filter(s -> s.startsWith("张"));
        //收集流打成list
        //Collectors.toList() new ArrayList 循环,将流内的数据添加
        List<String> lists = stream.collect(Collectors.toList());

        //下方的最新API的集合还是不可变集合,不可修改添加数据
        //List<String> lists = stream.toList();
        
        /**
         * 注意:
         *  流只能使用一次,上面用过就关闭了
         */
        //HashSet::new
        Set<String> sets = stream.collect(Collectors.toSet());

        //直接收集为数组;默认为Object类型
        Object[] objects = stream.toArray();
        //自定义收集数组类型;根据流的长度创建数组
        String[] obj = stream.toArray(value -> new String[value]);
        String[] obj2 = stream.toArray(String[]::new);
    }
}

二、异常

1.什么是异常

  • 程序在编译或者执行中出现的问题;
  • 如:数组索引越界、空指针异常、日期格式化异常;
  • 注意:
    • 异常没有提前处理,程序会退出JVM虚拟机而终止;
    • 研究异常,避免异常,提前处理异常、体现的是程序的安全健壮性

2.异常体系

  • Throwable:(抛出异常)
    • Error 错误
      • 系统级别问题,JVM退出等,代码无法干预
    • Exception 异常
      • RuntimeException及其子类:
        • 运行时异常,编译时不会报错
        • 编译成class文件不需要处理,运行字节码文件可能出现异常
        • 数组索引越界、空指针异常
      • 除了RuntimeException之外所有异常
        • 编译时异常(又称 受检异常),编译期必须处理,否则程序不能通过编译
        • 日期格式化异常

3.运行时异常

一般是编程逻辑不严谨导致的程序错误;

数组索引越界异常:ArrayIndexOutOfBoundsException
空指针异常:Null Pointer Exception
数字操作异常:Arithmetic Exception
类型转换异常:ClassCastException
数字转换异常:NumberFormatException
public class ExceptionDemo01 {
    public static void main(String[] args) {
        String a = "123456";
        Integer aa = Integer.valueOf(a);

        Integer b = 123456;
        String bb = b.toString();
        String bbb = String.valueOf(b);

        String c = "123456abc";
        //运行时异常,非法参数
        Integer cc = Integer.valueOf(c);
    }
}

4.编译时异常

  • 写代码时就报错,不处理,跑不起来。
  • 在容易出错的地方,提醒程序员
public static void main(String[] args) throws ParseException {
        String data = "2022-08-03 23:30:30";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = sdf.parse(data);
        System.out.println(data);
    }

5.异常默认处理流程

  1. 默认在异常代码处自动创建异常对象:Arithmetic Exception
  2. 异常会从方法中出现位置抛给调用者,调用者抛给虚拟机
  3. JVM接收到异常对象后,控制台输出异常栈信息数据
  4. 直接从当前执行的异常点干掉当前程序;
  5. 程序死亡,后续代码不执行

6.编译时异常处理形式

  • 三种处理形式:
    • 异常直接抛给调用者,调用者继续抛出
      • throws(异常1,异常2,...):用在方法上,异常抛给调用者处理
      • throws Exception 抛出一切异常,不用单独列出
      • 不好:不处理异常,如果异常最终传递到虚拟机,程序死亡
    • 自己捕获异常处理
      • try catch
        • 可以有多个异常放在一个try 放多个catch分别处理各个异常
        • 建议用一个catch ;Exception打印出一切异常
      • 监视捕获异常,方法内部,直接处理
      • 好处:独立完成异常处理,程序可以继续往下执行。
    • 二者结合,异常抛给调用者,调用者捕获处理
      • 好处:上层调用者可以知道底层失败原因,决策下一步怎么走
      • 该种处理最符合开发规范

7.运行时异常处理

建议在最外层调用处同一集中处理;

8.异常处理案例

public class RuntimeDemo01 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (true){
            try {
                System.out.println("输入合法价格:");
                String s = sc.nextLine();
                Double price = Double.valueOf(s);
                if (price > 0){
                    System.out.println("价格是:"+price);
                    break;
                }else {
                    System.out.println("请输入正数");
                }
            } catch (Exception e) {
                System.out.println("请检查输入值");
            }
        }
    }
}

9.自定义异常

9.1 自定义异常必要性

  • java无法为所有的问题提供异常;
  • 可以通过异常来管理某个业务问题;

9.2好处

  • 可以使用异常的机制管理业务问题,提醒程序员注意
  • 出现bug,异常形式,清晰指出出错地方;

9.3 自定义异常分类

1.自定义编译时异常

  • 定义异常类继承Exception;
  • 重写构造器;
  • 出现异常的地方用throw new 自定义对象抛出;
  • 作用:提醒强烈!
/**
 * 自定义年龄非法异常
 *    1.继承Exception
 *    2.重写构造器
 */
public class AgeIlleagaException extends Exception{
    public AgeIlleagaException() {
    }
    //返回异常信息
    public AgeIlleagaException(String message) {
        super(message);
    }
}

public class ExceptionDemo01 {
    public static void main(String[] args) {
        try {
            checkAge(-99);
        } catch (AgeIlleagaException e) {
            e.printStackTrace();
        }
    }

    public static void checkAge(int age) throws AgeIlleagaException {
        if (age<0 || age>200){
            //throws 用在方法上,抛出异常
            //throw 方法内部直接创建一个异常对象,并从此点抛出
            throw new AgeIlleagaException(age+"年龄非法");
        }else {
            System.out.println("合法");
        }
    }
}

2.自定义运行时异常

  • 定义异常类继承RuntimeException;
  • 重写构造器;
  • 出现异常的地方用throw new 自定义对象抛出;
  • 提醒不强烈;

三、日志

1.日志概述

用来记录程序运行过程中的信息,并可以进行永久存储;

1.1日志技术具备的优势

  • 将系统执行的信息选择性记录到指定位置(控制台、文件、数据库);
  • 随时以开关形式控制是否记录日志,无需修改源代码;
  • 性能较好;多线程,同步执行,避开业务记录日志

2.日志技术体系

  • 体系结构:(日志规范接口)
    • Commons Logging 简称JCL
    • Simple Logging Facade for Java 简称 slf4j
      • Log4j
      • JUL
      • Logback
  • 日志规范:
    • 一些接口,提供给日志实现框架设计的标准
  • 日志框架
    • 第三方做好的日志记录实现代码,直接拿去用

3. Logback日志框架

3.1框架介绍

模块
logback-core为其他两个模块奠定了基础。
logback-classiclog4j 的一个改良的版本,实现了slf4j APIlogback-classic原生实现了SLF4J API
可以在logback和其他日志框架(如log4j 或java.util.logging (JUL ))之间来回切换。
logback-access集成了Servlet容器,如Tomcat和Jetty,以提供HTTP访问日志功能。注意,可以在logback-core的基础上轻松构建自己的模块。

3.2 Logback使用哪几个模块,各自作用

模块作用
slf4j-api日志规范
logback-core基础模块
logback-classiclog4j的改良版本,完整实现了slf4j-api

4.使用Logback框架开发步骤

  1. 项目下新建lib文件,相关jar包导入,并添加到项目库中
  2. 核心配置文件logback.xml放到src目录下
  3. 代码中获取日志对象;
  4. 使用日志对象输出日志信息;

5.日志级别

  • 级别程度依次:TRACE(路径跟踪)<DEBUG(调试)<INFO(关键信息)<WARN(警告)<ERROR(错误)
  • 默认级别DEBUG
  • 作用:用于控制日志输出级别,只输出不低于设定级别的日志
  • ALL和OFF分别是打印全部日志,关闭全部日志

6.logback.xml配置详解

<?xml version="1.0" encoding="UTF-8"?>

<!-- 配置文件修改时重新加载,默认true -->
<configuration scan="true">

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--System.err打印字体红色-->
        <target>System.out</target>
        <encoder charset="UTF-8">
            <!-- 输出日志记录格式 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- FILE输出方向通向文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder charset="UTF-8">
            <!-- 输出日志记录格式 -->
            <!-- %thread线程名, %-5level级别从左显示五个字符宽度
               %msg日志消息 %n换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>F:/work/data/data.log</file>
       <!-- 指定日志文件拆分和压缩规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>F:/work/data/data-%d{yyyyMMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>


    <!-- 设置日志输出级别 -->
    <root level="ALL">
        <!--控制台和文件的控制开关,注释后不再打印日志-->
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

RecordDate:2021/08/14