目录

创建不可变集合

Stream流

Stream流的概述

Strea流的获取

Stream流的常用API

Stream流的综合应用

收集Stream流

异常处理

异常概述、体系

常见运行时异常

常见编译时异常

异常的默认处理流程

编译时异常的处理机制

运行时异常的处理机制

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

自定义异常

日志框架

日志技术的概述

日志技术体系结构

Logback概述

Logback快速入门

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

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


创建不可变集合

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

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

如何创建不可变集合?
    在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。

方法名称

说明

static <E> List<E> of(E...elements)

创建一个具有指定元素的List集合对象

static <E> Set<E> of(E...elements)

创建一个具有指定元素的Set集合对象

static <K, V> Map<K, V> of(E...elements)

创建一个具有指定元素的Map集合对象

import java.util.*;

public class Test {
    public static void main(String[] args) {
        //1、不可变List集合
        List<Double> lists = List.of(569.5, 700.5, 523.0, 570.5);
//        lists.add(686.5);
//        lists.set(2,550.5);
        System.out.println(lists);

        double score = lists.get(1);
        System.out.println(score);

        //2、不可变Set集合
        Set<String> names = Set.of("lisa", "rose", "jennie", "jisoo");
        // names.add("BLACKPINK");
        System.out.println(names);

        //3、不可变集合Map
        Map<String, Integer> maps = Map.of("肯德基", 19, "麦当劳", 29);
        //maps.put("华莱士",9);
        System.out.println(maps);
    }
}

Stream流

Stream流的概述

在Java 8中,得益于Lambda所带来的函数式编程,引入一个全新的Stream流概念
目的:用于简化集合和数组操作的API
案例:体验Stream流的作用
需求:按照下面的要求完成集合的创建和遍历
    创建一个集合,存储多个字符串元素
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
    把集合中所有以“张”开头的元素存储到一个新的集合
    把“张”开头的集合中的长度为3的元素存储到一个新的集合
    遍历上一步得到的集合中的元素输出。
Stream流式思想的核心:
1.先得到集合或者数组的Stream流(就是一根传送带)
2.把元素放上去
3.然后就用这个Stream流简化的API来方便的操作元素。
package com.deng.hello;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

//使用stream流
public class Test {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        Collections.addAll(names, "张三丰", "张无忌", "周芷若", "赵敏", "张三");
        System.out.println(names);

        //1、从集合中找出姓张的放到集合中去
        List<String> zhangNames = new ArrayList<>();
        for (String name : names) {
            if (name.startsWith("张")) {
                zhangNames.add(name);//[张三丰, 张无忌, 周芷若, 赵敏, 张三]
            }
        }
        System.out.println(zhangNames);//[张三丰, 张无忌, 张三]

        //2、从集合中找出名字长度为3的姓名
        List<String> threeName = new ArrayList<>();
        for (String name : zhangNames) {
            if (name.length() == 3) {
                threeName.add(name);
            }
        }
        System.out.println(threeName);//[张三丰, 张无忌]

        //stream流实现
        names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));//张三丰 张无忌

    }
}

Strea流的获取

Stream流的三类方法
● 获取Stream流
    创建一条流水线,并把数据放到流水线上准备进行操作
● 中间方法
    流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。
● 终结方法
    一个Stream流只能有一个终结方法,是流水线上的最后一个操作

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

集合获取Stream流的方式
● 可以使用Collection接口中的默认方法stream()生成流

名称

说明

default Stream<E> stream()

获取当前集合对象的Stream流

数组获取Stream流的方式

名称

说明

public static <T> Stream<T> stream(T[] array)

获取当前数组的Stream流

public static<T> Stream<T> of(T... values) 

获取当前数组/可变数据的Stream流

package com.deng.hello;

import java.util.*;
import java.util.stream.Stream;

public class Test {
    public static void main(String[] args) {
        //Collection集合获取Stream流
        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<T> filter(Predicate<? super T> predicate)

用于对流中的数据进行过滤。

Stream<T> limit(long maxSize)

获取前几个元素

Stream<T> skip(long n)

跳过前几个元素

Stream<T> distinct()

去除流中重复的元素。依赖(hashCode和equals方法)

static <T> Stream<T> concat(Stream a, Stream b)

合并a和b两个流为一个流

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

 Stream流的常见终结操作方法

名称

说明

void for Each (Consumer action)

对此流的每个元素执行遍历操作

long count()

返回此流中的元素数

注意:终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了。 
终结和非终结方法的含义是什么?
    终结方法后流不可以继续使用,非终结方法会返回新的流,支持链式编程
package com.deng.hello;

import java.util.Objects;

public class Student {
    private String name;


    public Student() {
    }

    public Student(String name) {
        this.name = name;

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}
package com.deng.hello;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

public class Test {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        Collections.addAll(names, "张三丰", "张无忌", "周芷若", "赵敏", "张三", "张强");

        names.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));

        long size = names.stream().filter(s -> s.length() == 3).count();
        System.out.println(size);//3

        //names.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s ->System.out.println(s));
        //获取前几个元素
        names.stream().filter(s -> s.startsWith("张")).limit(2).forEach(System.out::println);

        //跳过前几个元素
        names.stream().filter(s -> s.startsWith("张")).skip(2).forEach(System.out::println);

        //map加工方法:第一个参数是原材料  第二个参数是加工后的结果
        //给集合加上一个“学习的”
        names.stream().map(s -> "学习的" + s).forEach(System.out::println);

        //需求:把所有名字都加工成一个学生对象
        names.stream().map(s -> new Student(s)).forEach(s -> System.out.println(s));
        //names.stream().map(Student::new).forEach(System.out::println);//构造器引用 方法引用

        //合并流
        Stream<String> s1 = names.stream().filter(s -> s.startsWith("张"));
        Stream<String> s2 = Stream.of("java1", "java2");
        Stream<String> s3 = Stream.concat(s1, s2);
        // s3.forEach(s -> System.out.println(s));
        s3.forEach(System.out::println);

        s3.distinct().forEach(System.out::println);//去除流中的重复数据
    }
}

Stream流的综合应用

需求:某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算。
分析:
①:员工信息至少包含了(名称、性别、工资、奖金、处罚记录)
②:开发一部有4个员工、开发二部有5名员工
③分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer
④:分别统计出2个部门的平均月收入,要求去掉最高和最低工资。
⑤:统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。
public class Employee {
    private String name;
    private char sex;
    private double salary;
    private double bonus;
    private String punish; // 处罚信息

    public Employee(){
    }

    public Employee(String name, char sex, double salary, double bonus, String punish) {
        this.name = name;
        this.sex = sex;
        this.salary = salary;
        this.bonus = bonus;
        this.punish = punish;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public String getPunish() {
        return punish;
    }

    public void setPunish(String punish) {
        this.punish = punish;
    }

    public double getTotalSalay(){
        return salary * 12 + bonus;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", salary=" + salary +
                ", bonus=" + bonus +
                ", punish='" + punish + '\'' +
                '}'+"\n";
    }
}
public class Topperformer {
    private String name;
    private double money; // 月薪

    public Topperformer() {
    }

    public Topperformer(String name, double money) {
        this.name = name;
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Topperformer{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
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流的收集方法

名称

说明

R collect(Collector collector)

开始收集Stream流,指定收集器

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

名称

说明

public static <T> Collector toList()

把元素收集到List集合中

public static <T> Collector toSet()

把元素收集到Set集合中

public static  Collector toMap(Function keyMapper , Function valueMapper)

把元素收集到Map集合中

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));
    }
}
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虚拟机而终止.
研究异常并且避免异常,然后提前处理异常,体现的是程序的安全, 健壮性。

异常体系

java基础类型哪些不可变的 java不可变集合_开发语言

Error:

系统级别问题、JVM退出等,代码无法控制。

Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题

RuntimeException及其子类:运行时异常,编译阶段不会报错。 (空指针异常,数组索引越界异常)
除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译。 (日期格式化异常)。

编译时异常和运行时异常

java基础类型哪些不可变的 java不可变集合_开发语言_02

异常是什么?
    异常是代码在编译或者执行的过程中可能出现的错误。
异常分为几类?
    编译时异常、运行时异常。
    编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错。
    运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
学习异常的目的?
    避免异常的出现,同时处理可能出现的异常,让代码更稳健。

常见运行时异常

运行时异常
    直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误。
运行时异常示例
数组索引越界异常: ArrayIndexOutOfBoundsException
    空指针异常 : NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
    数学操作异常:ArithmeticException
    类型转换异常:ClassCastException
    数字转换异常: NumberFormatException
运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误,
自己的水平有问题!
/**
    拓展: 常见的运行时异常。(面试题)
         运行时异常的概念:
             继承自RuntimeException的异常或者其子类,
             编译阶段是不会出错的,它是在运行时阶段可能出现的错误,
             运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!!

             1.数组索引越界异常: ArrayIndexOutOfBoundsException。
             2.空指针异常 : NullPointerException。
               直接输出没有问题。但是调用空指针的变量的功能就会报错!!
             3.类型转换异常:ClassCastException。
             4.迭代器遍历没有此元素异常:NoSuchElementException。
             5.数学操作异常:ArithmeticException。
             6.数字转换异常: NumberFormatException。

    小结:
        运行时异常继承了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("程序结束。。。。。");
    }
}

常见编译时异常

异常的默认处理流程

默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
直接从当前执行的异常点干掉当前程序。
后续代码没有机会执行了,因为程序已经死亡。

java基础类型哪些不可变的 java不可变集合_java_03

编译时异常的处理机制

编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过
编译时异常的处理形式有三种:
    出现异常直接抛出去给调用者,调用者也继续抛出去。
    出现异常自己捕获处理,不麻烦别人。
    前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
异常处理方式1 —— throws
throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。
抛出异常格式:
    方法 throws 异常1 ,异常2 ,异常3 ..{}
规范做法: 
    方法 throws Exception{}
代表可以抛出一切异常
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
    目标:编译时异常的处理方式一。

    编译时异常:编译阶段就会报错,一定需要程序员处理的,否则代码无法通过!!

    抛出异常格式:
        方法 throws 异常1 ,  异常2 , ..{

        }
        建议抛出异常的方式:代表可以抛出一切异常,
        方法 throws Exception{

        }

    方式一:
        在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
        JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。
        虽然可以解决代码编译时的错误,但是一旦运行时真的出现异常,程序还是会立即死亡!
        这种方式并不好!

    小结:
        方式一出现异常层层跑出给虚拟机,最终程序如果真的出现异常,程序还是立即死亡!这种方式不好!

 */
public class ExceptionDemo01 {

//    public static void main(String[] args) throws ParseException, FileNotFoundException {
//        System.out.println("程序开始。。。。。");
//        parseTime("2011-11-11 11:11:11");
//        System.out.println("程序结束。。。。。");
//    }
//
//    public static void parseTime(String date) throws ParseException, FileNotFoundException {
//        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");
//    }

    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…
监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。
格式:
    try{       
        // 监视可能出现异常的代码!
    }catch(异常类型1 变量){
       // 处理异常
    }catch(异常类型2 变量){
       // 处理异常
    }...
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
    目标:编译时异常的处理方式二。

    方式二:在出现异常的地方自己处理,谁出现谁处理。

    自己捕获异常和处理异常的格式:捕获处理
         try{
            // 监视可能出现异常的代码!
         }catch(异常类型1 变量){
            // 处理异常
         }catch(异常类型2 变量){
            // 处理异常
         }...

    监视捕获处理异常企业级写法:
         try{
             // 可能出现异常的代码!
         }catch (Exception e){
            e.printStackTrace(); // 直接打印异常栈信息
         }
         Exception可以捕获处理一切异常类型!

    小结:
        第二种方式,可以处理异常,并且出现异常后代码也不会死亡。
        这种方案还是可以的。
        但是从理论上来说,这种方式不是最好的,上层调用者不能直接知道底层的执行情况!

 */
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(); // 打印异常栈信息
        }
    }


//    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 (FileNotFoundException|ParseException e) {
//            e.printStackTrace(); // 打印异常栈信息
//        }
//    }

//    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 (FileNotFoundException e) {
//           e.printStackTrace(); // 打印异常栈信息
//        } catch (ParseException e) {
//           e.printStackTrace();
//        }
//    }

//    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);
//        } catch (ParseException e) {
//            // 解析出现问题
//            System.out.println("出现了解析时间异常哦,走点心!!");
//        }
//
//        try {
//            InputStream is = new FileInputStream("E:/meinv.jpg");
//        } catch (FileNotFoundException e) {
//            System.out.println("您的文件根本就没有啊,不要骗我哦!!");
//        }
//    }
}
异常处理方式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");
    }

}
1、异常处理的总结
在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。

实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式似乎也都是可以的。

运行时异常的处理机制

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

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

需求
    键盘录入一个合理的价格为止(必须是数值,值必须大于0)。
分析
    定义一个死循环,让用户不断的输入价格。
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,可以用异常的形式清晰的指出出错的地方。

自定义异常的分类
1、自定义编译时异常       
    定义一个异常类继承Exception.
    重写构造器。
    在出现异常的地方用throw new 自定义对象抛出,
    作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!

2、自定义运行时异常
    定义一个异常类继承RuntimeException.
    重写构造器。
    在出现异常的地方用throw new 自定义对象抛出!
    作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
/**
    目标:自定义异常(了解)

    引入:Java已经为开发中可能出现的异常都设计了一个类来代表.
        但是实际开发中,异常可能有无数种情况,Java无法为
        这个世界上所有的异常都定义一个代表类。
        假如一个企业如果想为自己认为的某种业务问题定义成一个异常
        就需要自己来自定义异常类.

    需求:认为年龄小于0岁,大于200岁就是一个异常。

    自定义异常:
        自定义编译时异常.
            a.定义一个异常类继承Exception.
            b.重写构造器。
            c.在出现异常的地方用throw new 自定义对象抛出!
            编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!

        自定义运行时异常.
            a.定义一个异常类继承RuntimeException.
            b.重写构造器。
            c.在出现异常的地方用throw new 自定义对象抛出!
            提醒不强烈,编译阶段不报错!!运行时才可能出现!!

 */
public class ExceptionDemo {
    public static void main(String[] args) {
//        try {
//            checkAge(-34);
//        } catch (ItheimaAgeIlleagalException e) {
//            e.printStackTrace();
//        }

        try {
            checkAge2(-23);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void checkAge2(int age)  {
        if(age < 0 || age > 200){
            // 抛出去一个异常对象给调用者
            // throw :在方法内部直接创建一个异常对象,并从此点抛出
            // throws : 用在方法申明上的,抛出方法内部的异常
            throw new ItheimaAgeIlleagalRuntimeException(age + " is illeagal!");
        }else {
            System.out.println("年龄合法:推荐商品给其购买~~");
        }
    }

    public static void checkAge(int age) throws ItheimaAgeIlleagalException {
        if(age < 0 || age > 200){
            // 抛出去一个异常对象给调用者
            // throw :在方法内部直接创建一个异常对象,并从此点抛出
            // throws : 用在方法申明上的,抛出方法内部的异常
            throw new ItheimaAgeIlleagalException(age + " is illeagal!");
        }else {
            System.out.println("年龄合法:推荐商品给其购买~~");
        }
    }
}
/**
    自定义的编译时异常
      1、继承Exception
      2、重写构造器
 */
public class ItheimaAgeIlleagalException extends Exception{
    public ItheimaAgeIlleagalException() {
    }

    public ItheimaAgeIlleagalException(String message) {
        super(message);
    }
}
/**
    自定义的编译时异常
      1、继承RuntimeException
      2、重写构造器
 */
public class ItheimaAgeIlleagalRuntimeException extends RuntimeException{
    public ItheimaAgeIlleagalRuntimeException() {
    }

    public ItheimaAgeIlleagalRuntimeException(String message) {
        super(message);
    }
}

日志框架

日志技术的概述

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


输出语句

日志技术

输出位置

只能是控制台

可以将日志信息写入到文件或者数据库中

取消日志

需要修改代码,灵活性比较差

不需要修改代码,灵活性比较好

多线程

性能较差

性能较好

日志技术体系结构

日志规范:一些接口,提供给日志的实现框架设计的标准。
日志框架:牛人或者第三方公司已经做好的日志记录实现代码,后来者直接可以拿去使用。
因为对Commons Logging的接口不满意,有人就搞了SLF4J。
因为对Log4j的性能不满意,有人就搞了Logback。

Logback概述

Logback日志框架
    Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好
    官方网站:https://logback.qos.ch/index.html 
    Logback是基于slf4j的日志规范实现的框架。
Logback主要分为三个技术模块:
    logback-core: logback-core 模块为其他两个模块奠定了基础,必须有。
    logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API。
    logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能

使用Logback需要使用哪几个模块,各自的作用是什么。
    slf4j-api:日志规范
    logback-core:基础模块。
    logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API

Logback快速入门

需求:导入Logback日志技术到项目中,用于纪录系统的日志信息
分析:
①:在项目下新建文件夹lib,导入Logback的相关jar包到该文件夹下,并添加到项目依赖库中去。
②:将Logback的核心配置文件logback.xml直接拷贝到src目录下(必须是src下)。
③:在代码中获取日志的对象 
public static final Logger LOGGER = LoggerFactory.getLogger("类对象");
④:使用日志对象LOGGER调用其方法输出不能的日志信息

导包

java基础类型哪些不可变的 java不可变集合_java_04

//快速搭建Logback日志对象,记录程序的执行情况到控制台、文件中


import org.slf4j.LoggerFactory;

import org.slf4j.Logger;

public class Test {
    //Logger LOGGER = LoggerFactory.getLogger("Test.class");
    //创建Logback的日志对象,代表了日志技术
    public static final Logger LOGGER = LoggerFactory.getLogger("Test.class");

    public static void main(String[] args) {
        try {
            LOGGER.debug("main方法开始执行了~~");
            LOGGER.info("开始记录第二行日志");
            int a = 10;
            int b = 0;
            LOGGER.trace("a="+ a);
            LOGGER.trace("b="+ b);
            System.out.println(a/b);
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("功能出现异常,"+e);
        }
    }
}

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

Logback日志系统的特性都是通过核心配置文件logback.xml控制的。
Logback日志输出位置、格式设置:
通过logback.xml 中的<append>标签可以设置输出位置和日志信息的详细格式。
通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中
输出到控制台的配置标志:
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
输出到系统文件的配置标志:
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">

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

日志级别
级别程度依次是:TRACE< DEBUG< INFO<WARN<ERROR  ; 
默认级别是debug(忽略大小写),对应其方法。
作用:用于控制系统中哪些日志级别是可以输出的,只输出级别不低于设定级别的日志信息。
ALL  和 OFF分别是打开全部日志信息,及关闭全部日志信息。
具体在<root level=“INFO”>标签的level属性中设置日志级别。
    <root level=“INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE" />
    </root>