Java - day16 - Stream、异常体系、日志框架


Java - day16 - Stream、异常体系、日志框架

创建不可变集合

什么是不可变集合?

  • 不可变集合,就是不可被修改的集合。
  • 集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。

为什么要创建不可变集合?

  • 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
  • 或者当集合对象被不可信的库调用时,不可变形式是安全的。

如何创建不可变集合?

  • 在列表、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。

实例

import java.util.HashMap;  
import java.util.List;  
import java.util.Map;  
import java.util.Set;  
  
/**  
  目标:不可变集合。  
  */  
public class CollectionDemo {  
    public static void main(String[] args) {  
        // 1、不可变的List集合  
  List<Double> lists = List.of(569.5, 700.5, 523.0,  570.5);  
        // lists.add(689.0);  
 // lists.set(2, 698.5); // System.out.println(lists);  double score = lists.get(1);  
        System.out.println(score);    //700.5  
  
 // 2、不可变的Set集合  
  Set<String> names = Set.of("迪丽热巴", "迪丽热九", "马尔扎哈", "卡尔眨巴" );  
        // names.add("三少爷");  
  System.out.println(names);       //[迪丽热巴, 迪丽热九, 马尔扎哈, 卡尔眨巴]  
  
 // 3、不可变的Map集合  
  Map<String, Integer> maps = Map.of("huawei",2, "Java开发", 1 , "手表", 1);  
        // maps.put("衣服", 3);  
  System.out.println(maps);    //{Java开发=1, huawei=2, 手表=1}  
  }  
}

Stream流

Stream流的概述

什么是Stream流?

  • 在Java8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念。
  • 目的(作用):用于简化集合和数组操作的API。

Stream流式思想的核心:

  • 先得到集合或者数组的Stream流(就是一根传送带);
  • 把元素放上去;
  • 然后就用这个Stream流简化的API来方便的操作元素。

案例:体验Stream流的作用

实例

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  
  
/**  
  目标:初步体验Stream流的方便与快捷  
  */  
public class StreamTest {  
    public static void main(String[] args) {  
        List<String> names = new ArrayList<>();  
        Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");  
        System.out.println(names);   //[张三丰, 张无忌, 周芷若, 赵敏, 张强]  
  
 // 1、从集合中找出姓张的放到新集合  
  List<String> zhangList = new ArrayList<>();  
        for (String name : names) {  
            if(name.startsWith("张")){  
                zhangList.add(name);  
            }  
        }  
        System.out.println(zhangList);          //[张三丰, 张无忌, 张强]  
  
 // 2、找名称长度是3的姓名  
  List<String> zhangThreeList = new ArrayList<>();  
        for (String name : zhangList) {  
            if(name.length() == 3){  
                zhangThreeList.add(name);  
            }  
        }  
        System.out.println(zhangThreeList);      //[张三丰, 张无忌]  
  
 //3、使用Stream实现的  
  /*张三丰  
 张无忌 */  names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));  
    }  
}

Stream流的获取

Stream流的三类方法

获取Stream流

  • 创建一条流水线,并把数据放到流水线上准备进行操作

中间方法

  • 流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。

终结方法

  • 一个Stream流只能有一个终结方法,是流水线上的最后一个操作

Stream操作集合或者数组的第一步是先得到Stream流,然后才能使用流的功能。

集合获取Stream流的方式

  • 可以使用Collection接口中的默认方法stream( )生成流。

数组获取Stream流的方式

实例

import java.util.*;  
import java.util.stream.Stream;  
  
public class StreamDemo02 {  
    public static void main(String[] args) {  
        /** --------------------Collection集合获取流-------------------------------   */  
  Collection<String> list = new ArrayList<>();  
        Stream<String> s =  list.stream();  
  
        /** --------------------Map集合获取流-------------------------------   */  
  Map<String, Integer> maps = new HashMap<>();  
        // 键流  
  Stream<String> keyStream = maps.keySet().stream();  
        // 值流  
  Stream<Integer> valueStream = maps.values().stream();  
        // 键值对流(拿整体)  
  Stream<Map.Entry<String,Integer>> keyAndValueStream =  maps.entrySet().stream();  
  
        /** ---------------------数组获取流------------------------------   */  
  String[] names = {"赵敏","小昭","灭绝","周芷若"};  
        Stream<String> nameStream = Arrays.stream(names);  
        Stream<String> nameStream2 = Stream.of(names);  
    }  
}

Stream流的常用API

Stream流的常用API(中间操作方法)

  • 注意:
  • 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
  • 在Stream流中无法直接修改集合、数组中的数据。

实例

import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.List;  
import java.util.function.Consumer;  
import java.util.function.Function;  
import java.util.function.Predicate;  
import java.util.stream.Stream;  
  
public class StreamDemo03 {  
    public static void main(String[] args) {  
        List<String> list = new ArrayList<>();  
        list.add("张无忌");  
        list.add("周芷若");  
        list.add("赵敏");  
        list.add("张强");  
        list.add("张三丰");  
        list.add("张三丰");  
  
        // Stream<T> filter(Predicate<? super T> predicate)  
  list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));  
  
        long size = list.stream().filter(s -> s.length() == 3).count();  
        System.out.println(size);  
  
        // list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s -> System.out.println(s));  
  list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(System.out::println);  
  
        list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(System.out::println);  
  
        // map加工方法: 第一个参数原材料 -> 第二个参数是加工后的结果。  
  // 给集合元素的前面都加上一个:黑马的:  
  list.stream().map(s -> "黑马的:" + s).forEach(a -> System.out.println(a));  
  
        // 需求:把所有的名称 都加工成一个学生对象。  
  list.stream().map(s -> new Student(s)).forEach(s -> System.out.println(s));  
//        list.stream().map(Student::new).forEach(System.out::println); // 构造器引用  方法引用  
  
  // 合并流。  
  Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));  
        Stream<String> s2 = Stream.of("java1", "java2");  
        // public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)  
  Stream<String> s3 = Stream.concat(s1 , s2);  
        s3.distinct().forEach(s -> System.out.println(s));  
    }  
}

Stream流的常见终结操作方法

  • 注意:终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了。

终结和非终结方法的含义是什么?

  • 终结方法后流不可以继续使用,非终结方法会返回新的流,支持链式编程。

Stream流的综合应用

实例

import java.math.BigDecimal;  
import java.math.RoundingMode;  
import java.util.ArrayList;  
import java.util.List;  
import java.util.stream.Stream;  
  
//Topperformer{name='猪八戒', money=55000.0}  
//开发一部的平均工资是:42500.0  
//开发部的平均工资是:34285.71  
public class StreamDemo04 {  
    public static double allMoney ;  
    public static double allMoney2 ; // 2个部门去掉最高工资,最低工资的总和  
  public static void main(String[] args) {  
        List<Employee> one = new ArrayList<>();  
        one.add(new Employee("猪八戒",'男',30000 , 25000, null));  
        one.add(new Employee("孙悟空",'男',25000 , 1000, "顶撞上司"));  
        one.add(new Employee("沙僧",'男',20000 , 20000, null));  
        one.add(new Employee("小白龙",'男',20000 , 25000, null));  
  
        List<Employee> two = new ArrayList<>();  
        two.add(new Employee("武松",'男',15000 , 9000, null));  
        two.add(new Employee("李逵",'男',20000 , 10000, null));  
        two.add(new Employee("西门庆",'男',50000 , 100000, "被打"));  
        two.add(new Employee("潘金莲",'女',3500 , 1000, "被打"));  
        two.add(new Employee("武大郎",'女',20000 , 0, "下毒"));  
  
        // 1、开发一部的最高工资的员工。(API)  
  // 指定大小规则了  
//        Employee e = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(),  e2.getSalary() + e2.getBonus()))  
//                .get();  
//       System.out.println(e);  
  Topperformer t = 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(t);  
  
        // 2、统计平均工资,去掉最高工资和最低工资  
  one.stream().sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(),  e2.getSalary() + e2.getBonus()))  
                .skip(1).limit(one.size() - 2).forEach(e -> {  
                    // 求出总和:剩余员工的工资总和  
  allMoney += (e.getSalary() + e.getBonus());  
                });  
        System.out.println("开发一部的平均工资是:" + allMoney / (one.size() - 2));  
  
        // 3、合并2个集合流,再统计  
  Stream<Employee> s1 = one.stream();  
        Stream<Employee> s2 = two.stream();  
        Stream<Employee> s3 = Stream.concat(s1 , s2);  
        s3.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(),  e2.getSalary() + e2.getBonus()))  
                .skip(1).limit(one.size() + two.size() - 2).forEach(e -> {  
                    // 求出总和:剩余员工的工资总和  
  allMoney2 += (e.getSalary() + e.getBonus());  
                });  
  
        // BigDecimal  
  BigDecimal a = BigDecimal.valueOf(allMoney2);  
        BigDecimal b = BigDecimal.valueOf(one.size()  + two.size() - 2);  
        System.out.println("开发部的平均工资是:" + a.divide(b,2, RoundingMode.HALF_UP));  
    }  
}

收集Stream流

Stream流的收集操作

  • 收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。

收集Stream流的作用?

  • Stream流是操作集合/数组的手段
  • 操作的结果数据最终要恢复到集合或者数组中去。

Stream流的收集方法

Collectors工具类提供了具体的收集方式

实例

import java.util.*;  
import java.util.function.IntFunction;  
import java.util.stream.Collectors;  
import java.util.stream.Stream;  
  
/**  
  目标:收集Stream流的数据到 集合或者数组中去。  
  */  
public class StreamDemo05 {  
    public static void main(String[] args) {  
        List<String> list = new ArrayList<>();  
        list.add("张无忌");  
        list.add("周芷若");  
        list.add("赵敏");  
        list.add("张强");  
        list.add("张三丰");  
        list.add("张三丰");  
  
        Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));  
        List<String> zhangList = s1.collect(Collectors.toList()); // 可变集合  
  zhangList.add("java1");  
        System.out.println(zhangList);  
  
//       List<String> list1 = s1.toList(); // 得到不可变集合  
//       list1.add("java");  
//       System.out.println(list1);  
  
 // 注意注意注意:“流只能使用一次”  
  Stream<String> s2 = list.stream().filter(s -> s.startsWith("张"));  
        Set<String> zhangSet = s2.collect(Collectors.toSet());  
        System.out.println(zhangSet);  
  
        Stream<String> s3 = list.stream().filter(s -> s.startsWith("张"));  
//         Object[] arrs = s3.toArray();  
  String[] arrs = s3.toArray(String[]::new); // 可以不管,拓展一下思维!!  
  System.out.println("Arrays数组内容:" + Arrays.toString(arrs));  
  
    }  
}

异常处理

异常概述、体系

什么是异常?

  • 异常是程序在“编译”或者“执行”的过程中可能出现的问题。
  • 注意:语法错误不算在异常体系中。
  • 比如:数组索引越界、空指针异常、日期格式化异常,等…

为什么要学习异常?

  • 异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止。
  • 研究异常并且避免异常,然后提前处理异常,体现的是程序的安全,健壮性。

异常分为几类?

  • 编译时异常、运行时异常。
  • 编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错。
  • 运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。

异常体系

编译时异常和运行时异常

常见运行时异常

运行时异常

实例

public class ExceptionDemo {  
    public static void main(String[] args) {  
        System.out.println("程序开始。。。。。。");  
        /** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/  
  int[] arr = {1, 2, 3};  
        System.out.println(arr[2]);  
        // System.out.println(arr[3]); // 运行出错,程序终止  
  
  /** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */  
  String name = null;  
        System.out.println(name); // null  
 // System.out.println(name.length()); // 运行出错,程序终止  
  
  /** 3.类型转换异常:ClassCastException。 */  
  Object o = 23;  
        // String s = (String) o;  // 运行出错,程序终止  
  
  /** 5.数学操作异常:ArithmeticException。 */  
 //int c = 10 / 0;  
 /** 6.数字转换异常: NumberFormatException。 */  
 //String number = "23";  String number = "23aabbc";  
        Integer it = Integer.valueOf(number); // 运行出错,程序终止  
  System.out.println(it + 1);  
  
        System.out.println("程序结束。。。。。");  
    }  
}

常见编译时异常

编译时异常

  • 不是RuntimeException或者其子类的异常,编译阶就报错,必须处理,否则代码不通过。
  • 编译时异常:继承自Exception的异常或者其子类

编译时异常示例

实例:

import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
  
public class ExceptionDemo9 {  
    public static void main(String[] args) throws ParseException {  
        String date = "2015-01-12 10:23:21";  
        // 创建一个简单日期格式化类:  
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");  
        // 解析字符串时间成为日期对象  
  Date d = sdf.parse(date);  
        //  
  System.out.println(d);  
    }  
}

异常的默认处理流程

默认异常处理机制

  • 默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡!

实例

public class ExceptionDemo165 {  
    public static void main(String[] args) {  
        System.out.println("程序开始。。。。。。。。。。");  
        chu(10, 0);  
        System.out.println("程序结束。。。。。。。。。。");  
    }  
  
    public static void chu(int a , int b){  
        System.out.println(a);  
        System.out.println(b);  
        int c = a / b;  
        System.out.println(c);  
    }  
}

编译时异常的处理机制

  • 编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过。

编译时异常的处理形式有三种:

  • 出现异常直接抛出去给调用者,调用者也继续抛出去。
  • 出现异常自己捕获处理,不麻烦别人。
  • 前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。

异常处理方式1 —— throws

  • throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
  • 这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。

实例

import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.io.InputStream;  
import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
  
  
public class ExceptionDemo01 {  
  
    public static void main(String[] args) throws Exception {  
        System.out.println("程序开始。。。。。");  
        parseTime("2011-11-11 11:11:11");  
        System.out.println("程序结束。。。。。");  
    }  
  
    public static void parseTime(String date) throws Exception {  
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
        Date d = sdf.parse(date);  
        System.out.println(d);  
  
        InputStream is = new FileInputStream("E:/meinv.jpg");  
    }  

}

异常处理方式2 —— try...catch...

  • 监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
  • 这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。

实例

import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.io.InputStream;  
import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
  
public class ExceptionDemo02 {  
    public static void main(String[] args) {  
        System.out.println("程序开始。。。。");  
        parseTime("2011-11-11 11:11:11");  
        System.out.println("程序结束。。。。");  
    }  
  
    public static void parseTime(String date) {  
        try {  
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");  
            Date d = sdf.parse(date);  
            System.out.println(d);  
  
            InputStream is = new FileInputStream("E:/meinv.jpg");  
        } catch (Exception e) {  
            e.printStackTrace(); // 打印异常栈信息  
  }  
    }   
}

异常处理方式3 —— 前两者结合

  • 方法直接将异通过throws抛出去给调用者
  • 调用者收到异常后直接捕获处理。

实例

import java.io.FileInputStream;  
import java.io.InputStream;  
import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
  
  
public class ExceptionDemo03 {  
    public static void main(String[] args) {  
        System.out.println("程序开始。。。。");  
        try {  
            parseTime("2011-11-11 11:11:11");  
            System.out.println("功能操作成功");  
        } catch (Exception e) {  
            e.printStackTrace();  
            System.out.println("功能操作失败");  
        }  
        System.out.println("程序结束。。。。");  
    }  
  
    public static void parseTime(String date) throws Exception {  
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy、MM-dd HH:mm:ss");  
        Date d = sdf.parse(date);  
        System.out.println(d);  
  
        InputStream is = new FileInputStream("D:/meinv.jpg");  
    }  
}

异常处理的总结

  • 在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。
  • 实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式似乎也都是可以的。

运行时异常的处理机制

运行时异常的处理形式

  • 运行时异常编译阶段不会出错,是运行时才可能出错的,所以编译阶段不处理也可以。
  • 按照规范建议还是处理:建议在最外层调用处集中捕获处理即可。

实例

public class Test {  
    public static void main(String[] args) {  
        System.out.println("程序开始。。。。。。。。。。");  
        try {  
            chu(10, 0);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        System.out.println("程序结束。。。。。。。。。。");  
    }  
  
    public static void chu(int a , int b) { // throws RuntimeException{  
  System.out.println(a);  
        System.out.println(b);  
        int c = a / b;  
        System.out.println(c);  
    }  
}

异常处理使代码更稳健的案例

实例

import java.util.Scanner;  
  
/**  
  需求:需要输入一个合法的价格为止 要求价格大于 0  
 */public class Test2 {  
    public static void main(String[] args) {  
        Scanner sc = new Scanner(System.in);  
        while (true) {  
            try {  
                System.out.println("请您输入合法的价格:");  
                String priceStr = sc.nextLine();  
                // 转换成double类型的价格  
  double price = Double.valueOf(priceStr);  
  
                // 判断价格是否大于 0  if(price > 0) {  
                    System.out.println("定价:" + price);  
                    break;  
                }else {  
                    System.out.println("价格必须是正数~~~");  
                }  
            } catch (Exception e) {  
                System.out.println("用户输入的数据有毛病,请您输入合法的数值,建议为正数~~");  
            }  
        }  
    }  
}

自定义异常

自定义异常的必要?

  • Java无法为这个世界上全部的问题提供异常类。
  • 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。

自定义异常的好处:

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

自定义异常的分类

自定义编译时异常

自定义运行时异常

日志框架

日志技术的概述

想清楚的知道一个系统运行的过程和详情怎么办?

  • 程序中的日志:程序中的日志可以用来记录程序运行过程中的信息,并可以进行永久存储。

日志技术具备的优势

  • 可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)。
  • 可以随时以开关的形式控制是否记录日志,无需修改源代码。

日志技术的具体优势

日志技术体系结构

体系结构

日志的规范是什么,常见的几种形式

  • 日志规范大多是一些接口,提供给实现框架去设计的。
  • 常见的规范是:
  • commons Logging
  • simple Logging Facade for Java

日志的实现框架有哪些常见的?

  • Log4J
  • Logback(重点学习的,其他的都大同小异)

Logback概述

Logback日志框架

Logback主要分为三个技术模块

Logback快速入门

Logback配置详解 - 输出位置、格式设置

  • Logback日志系统的特性都是通过核心配置文件logback.xml控制的。

Logback日志输出位置、格式设置

  • 通过logback.xml 中的<append>标签可以设置输出位置和日志信息的详细格式。
  • 通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中。

Logback配置详解 - 日志级别设置

如果系统上线后只想记录一些错误的日志信息或者不想记录日志了,怎么办?

  • 可以通过设置日志的输出级别来控制哪些日志信息输出或者不输出。

日志级别

  • 级别程度依次是:TRACE < DEBUG < INFO < WARN < ERROR;
  • 默认级别是debug(忽略大小写),对应其方法。
  • 作用:用于控制系统中哪些日志级别是可以输出的,只输出级别不低于设定级别的日志信息。
  • ALL和OFF分别是打开全部日志信息,及关闭全部日志信息。

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Java - day16 - Stream、异常体系、日志框架


三二一的一的二