类加载器、反射、xml、枚举、注解、单元测试、日志

本文详细介绍了Java中的类加载器,包括加载时机、过程和分类,重点讲解了双亲委派模型。接着深入探讨了反射机制,从获取Class对象到使用构造方法、成员变量和成员方法。然后,详细阐述了XML的语法和解析,以及DTD和Schema约束。此外,文章还涵盖了枚举、注解和单元测试的基础知识,以及日志系统的概念,特别是Log4J的使用和配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 类加载器

1.1 作用

  • 前期写的Java代码,属于源代码,后期要进行运行,首先需要将这个源代码进行编译,编译完成之后会形成一个.class文件,这个.class文件就是字节码文件,后期进行运行的时候,就需要将这个字节码文件加载到内存中,要把这个字节码文件加载到内存中就需要类加载器来完成
  • 也就是说,类加载器就是负责将.class文件加载到内存中

1.2 类的加载时机问题

  • 一个类什么时候被加载到内存?
    • 创建类的实例(即对象)
    • 调用类的类方法
    • 访问类或者接口的类变量,或者为该类变量赋值
    • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    • 初始化某个类的子类
    • 直接使用java.exe命令来运行某个主类

1.3 类的加载过程

  • 一个类通过类加载器,将其加载到内存是需要经过很多个阶段的。大致可以分为
    • 加载,链接,初始化
  • 加载
    • 通过包名+类名,获取这个类,准备用流进行传输
    • 在这个类加载到内存中
    • 加载完毕创建一个class对象,这个Class对象就是对这个类的字节码文件进行描述。Java语言是面向对象的语言,在Java看来万物皆对象,因此一个字节码文件Java也提供了对应的类对其进行描述,这个类就是Class
  • 链接
    • 验证
      • 确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全
    • 准备
      • 负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值
    • 解析
      • 将类的二进制数据流中的符号引用替换为直接引用
  • 初始化
    • 根据程序员通过程序制定的主观计划去初始化类变量和其他资源
  • 总结
    • 当一个类被使用的时候,才会被加载到内存
    • 类加载的过程:加载、验证、准备、解析、初始化

1.4 分类

  • 分类
    • Bootstrap class loader:启动类加载器
      • 虚拟机的内置类加载器,通常表示为null,并且没有父null
    • Platfrom class loader:平台类加载器
      • 负责加载JDK中一些特殊的模块
    • System class loader:系统类加载器
      • 负责加载用户类路径上所指定的类库
  • 类加载器存在逻辑上的继承关系
    • System的父加载器为Platform
    • Platform的父加载器为Bootstrap
  • 逻辑上的继承关系
    • 没有通过extends关键字去指定某一个类加载器对应的父类加载器,但是他们的确是存在继承关系的,这是Java中的一种的机制,在类加载器这里可以看到
  • 代码演示
public class ClassLoaderDemo1 {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        //获取系统类加载器的父加载器 --- 平台类加载器
        ClassLoader classLoader1 = systemClassLoader.getParent();

        //获取平台类加载器的父加载器 --- 启动类加载器
        ClassLoader classLoader2 = classLoader1.getParent();

        System.out.println("系统类加载器" + systemClassLoader);
        System.out.println("平台类加载器" + classLoader1);
        System.out.println("启动类加载器" + classLoader2);

    }
}

1.5 双亲委派模型

  • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

1.6 ClassLoader中的两个方法

  • Java语言的是面向对象的,万物皆对象。类加载器其实也是一个对象,在Java中就提供了一个类对其进行描述,这个类就是ClassLoader
  • 方法介绍
方法名说明
public static ClassLoader getSystemClassLoader()获取系统类加载器
public InputStream getResourceAsStream(String name)加载某一个资源文件
  • 代码演示
public class ClassLoaderDemo2 {
    public static void main(String[] args) throws IOException {
        //static ClassLoader getSystemClassLoader() 获取系统类加载器
        //InputStream getResourceAsStream(String name)  加载某一个资源文件

        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        //利用加载器去加载一个指定的文件
        //参数:文件的路径(放在src的根目录下,默认去那里加载)
        //返回值:字节流。
        InputStream is = systemClassLoader.getResourceAsStream("prop.properties");

        Properties prop = new Properties();
        prop.load(is);

        System.out.println(prop);

        is.close();
    }
}

2. 反射

2.1 概述

  • 本质上
    • 反射就是使用类的另外一种方式
  • 反射机制
    • 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
    • 对于任意一个对象,都能够调用它的任意属性和方法;
    • 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制
  • 如何我们可以获取到一个类的字节码文件对象,那么我们就可以从这个字节码文件中解析出来这个类中的成员,然后在使用成员。Java是面向对象的,万物皆对象,因此在Java看来一个类的构造方法是一对象,成员变量也是对象,成员方法也是对象。那么Java就提供了对应的类对其进行描述:Constructor、Field、Method。当我们获取到的对应的成员对象以后,就可以调用成员对象中的方法使用对应的成员
  • 反射学习思路
    1. 首先获取一个类的字节码文件对象
    2. 调用字节码文件对象(Class)的方法获取类的指定的成员对象(构造方法,成员变量,成员方法)
    3. 调用对应的成员对象的方法使用类中的成员

2.2 获取Class对象

  • 获取Class对象的三种方式
    1. 通过Class类中的静态方法forName来进行获取
    2. 通过类的class属性来获取,即类名.class
    3. 通过对象名.getClass()方法
  • 三种方式获取的是同一个Class对象,因为一个类的字节码文件只有一个,所以一个类的字节码文件对象只有一个
  • 代码演示
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void study(){
        System.out.println("学生在学习");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.Class类中的静态方法forName("全类名")
            //全类名:包名 + 类名
        Class clazz = Class.forName("com.itheima.myreflect2.Student");
        System.out.println(clazz);

        //2.通过class属性来获取
        Class clazz2 = Student.class;
        System.out.println(clazz2);

        //3.利用对象的getClass方法来获取class对象
        //getClass方法是定义在Object类中.
        Student s = new Student();
        Class clazz3 = s.getClass();
        System.out.println(clazz3);

        System.out.println(clazz == clazz2);
        System.out.println(clazz2 == clazz3);
    }
}

2.3 获取构造方法对象

  • 方法介绍
方法名说明
Constructor<?>[] getConstructors()返回所有公共构造方法对象的数组
Constructor<?>[] getDeclaredConstructors()返回所有构造方法对象的数组
Constructor getConstructor(Class<?>… parameterTypes)返回单个公共构造方法对象
Constructor getDeclaredConstructor(Class<?>… parameterTypes)返回单个构造方法对象
  • 常用的是第四种
    • parameterTypes:表示的是构造方法参数对应的Class类型
  • 代码演示
public class Student {
    private String name;
    private int age;

    //私有的有参构造方法
    private Student(String name) {
        System.out.println("name的值为:" + name);
        System.out.println("private...Student...有参构造方法");
    }

    //公共的无参构造方法
    public Student() {
        System.out.println("public...Student...无参构造方法");
    }

    //公共的有参构造方法
    public Student(String name, int age) {
        System.out.println("name的值为:" + name + "age的值为:" + age);
        System.out.println("public...Student...有参构造方法");
    }
}
public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //method1();
        //method2();
        //method3();
        //method4();
    }

    private static void method4() throws ClassNotFoundException, NoSuchMethodException {
        //        Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):
//                                      返回单个构造方法对象
        //1.获取Class对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");
        Constructor constructor = clazz.getDeclaredConstructor(String.class);
        System.out.println(constructor);
    }

    private static void method3() throws ClassNotFoundException, NoSuchMethodException {
        //        Constructor<T> getConstructor(Class<?>... parameterTypes):
//                                      返回单个公共构造方法对象
        //1.获取Class对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");
        //小括号中,一定要跟构造方法的形参保持一致.
        Constructor constructor1 = clazz.getConstructor();
        System.out.println(constructor1);

        Constructor constructor2 = clazz.getConstructor(String.class, int.class);
        System.out.println(constructor2);

        //因为Student类中,没有只有一个int的构造,所以这里会报错.
        Constructor constructor3 = clazz.getConstructor(int.class);
        System.out.println(constructor3);
    }

    private static void method2() throws ClassNotFoundException {
        //        Constructor<?>[] getDeclaredConstructors():
//                                      返回所有构造方法对象的数组
        //1.获取Class对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");

        Constructor[] constructors = clazz.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
    }

    private static void method1() throws ClassNotFoundException {
        //        Constructor<?>[] getConstructors():
//                                      返回所有公共构造方法对象的数组
        //1.获取Class对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
    }
}

2.4 使用构造方法对象

  • 作用
    • 用来创建对象
  • 方法介绍
方法名说明
T newInstance(Object…initargs)根据指定的构造方法创建对象
setAccessible(boolean flag)设置为true,表示取消访问检查
  • initargs: 表示的就是给构造方法所传递的具体的数据(实际参数)
  • 代码演示
// Student类同上一个示例,这里就不在重复提供了
public class ReflectDemo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //T newInstance(Object... initargs):根据指定的构造方法创建对象
        //method1();
        //method2();
        //method3();
        //method4();

    }

    private static void method4() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        //获取一个私有的构造方法并创建对象
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");

        //2.获取一个私有化的构造方法.
        Constructor constructor = clazz.getDeclaredConstructor(String.class);

        //被private修饰的成员,不能直接使用的
        //如果用反射强行获取并使用,需要临时取消访问检查
        constructor.setAccessible(true);

        //3.直接创建对象
        Student student = (Student) constructor.newInstance("zhangsan");

        System.out.println(student);
    }

    private static void method3() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //简写格式
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");

        //2.在Class类中,有一个newInstance方法,可以利用空参直接创建一个对象
        Student student = (Student) clazz.newInstance();//这个方法现在已经过时了,了解一下

        System.out.println(student);
    }

    private static void method2() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");

        //2.获取构造方法对象
        Constructor constructor = clazz.getConstructor();

        //3.利用空参来创建Student的对象
        Student student = (Student) constructor.newInstance();

        System.out.println(student);
    }

    private static void method1() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");

        //2.获取构造方法对象
        Constructor constructor = clazz.getConstructor(String.class, int.class);

        //3.利用newInstance创建Student的对象
        Student student = (Student) constructor.newInstance("zhangsan", 23);

        System.out.println(student);
    }
}

2.5 获取成员变量对象

  • Class类中的方法
  • 方法分类
方法名说明
Field[] getFields()返回所有公共成员变量对象的数组
Field[] getDeclaredFields()返回所有成员变量对象的数组
Field getField(String name)返回单个公共成员变量对象
Field getDeclaredField(String name)返回单个成员变量对象
  • 常用的是第四种
    • name参数表示的就是成员变量的名称
  • 代码演示
public class Student {

    public String name;

    public int age;

    public String gender;

    private int money = 300;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", money=" + money +
                '}';
    }
}
public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
       // method1();
        //method2();
        //method3();
        //method4();

    }

    private static void method4() throws ClassNotFoundException, NoSuchFieldException {
        //        Field getDeclaredField(String name):返回单个成员变量对象
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect4.Student");
  
        //2.获取money成员变量
        Field field = clazz.getDeclaredField("money");
  
        //3.打印一下
        System.out.println(field);
    }
  
    private static void method3() throws ClassNotFoundException, NoSuchFieldException {
        //        Field getField(String name):返回单个公共成员变量对象
        //想要获取的成员变量必须是真实存在的
        //且必须是public修饰的.
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect4.Student");
  
        //2.获取name这个成员变量
        //Field field = clazz.getField("name");
        //Field field = clazz.getField("name1");
        Field field = clazz.getField("money");
  
        //3.打印一下
        System.out.println(field);
    }
  
    private static void method2() throws ClassNotFoundException {
        //        Field[] getDeclaredFields():返回所有成员变量对象的数组
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect4.Student");
  
        //2.获取所有的Field对象
        Field[] fields = clazz.getDeclaredFields();
  
        //3.遍历
        for (Field field : fields) {
            System.out.println(field);
        }
    }
  
    private static void method1() throws ClassNotFoundException {
        //        Field[] getFields():返回所有公共成员变量对象的数组
  
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect4.Student");
  
        //2.获取Field对象.
        Field[] fields = clazz.getFields();
  
        //3.遍历
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}

2.6 使用成员变量对象

  • 给成员变量设置值
  • 获取成员变量的值
  • 方法介绍
方法名说明
void set(Object obj, Object value)赋值
Object get(Object obj)获取值
  • obj:表示的就是一个对象
  • value:表示的就是要设置的值
  • 返回值表示的就是成员变量的值,方法参数obj表示的就是一个对象
  • 代码演示
// Student类同上一个示例,这里就不在重复提供了
public class ReflectDemo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
//        Object get(Object obj) 返回由该 Field表示的字段在指定对象上的值。
        //method1();
        //method2();

    }

    private static void method2() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect4.Student");

        //2.获取成员变量Field的对象
        Field field = clazz.getDeclaredField("money");

        //3.取消一下访问检查
        field.setAccessible(true);

        //4.调用get方法来获取值
        //4.1创建一个对象
        Student student = (Student) clazz.newInstance();
        //4.2获取指定对象的money的值
        Object o = field.get(student);

        //5.打印一下
        System.out.println(o);
    }

    private static void method1() throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        //        void set(Object obj, Object value):给obj对象的成员变量赋值为value
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect4.Student");

        //2.获取name这个Field对象
        Field field = clazz.getField("name");

        //3.利用set方法进行赋值.
        //3.1先创建一个Student对象
        Student student = (Student) clazz.newInstance();
        //3.2有了对象才可以给指定对象进行赋值
        field.set(student,"zhangsan");

        System.out.println(student);
    }
}

2.7 获取成员方法对象

  • 方法分类
方法名说明
Method[] getMethods()返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods()返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name, Class<?>… parameterTypes)返回单个公共成员方法对象
Method getDeclaredMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象
  • 常用的就是第四种
    • name:表示的是成员方法的名称
    • parameterTypes:表示的就是成员方法参数类型所对应的Class类型
  • 代码演示
public class Student {

    //私有的,无参无返回值
    private void show() {
        System.out.println("私有的show方法,无参无返回值");
    }

    //公共的,无参无返回值
    public void function1() {
        System.out.println("function1方法,无参无返回值");
    }

    //公共的,有参无返回值
    public void function2(String name) {
        System.out.println("function2方法,有参无返回值,参数为" + name);
    }

    //公共的,无参有返回值
    public String function3() {
        System.out.println("function3方法,无参有返回值");
        return "aaa";
    }

    //公共的,有参有返回值
    public String function4(String name) {
        System.out.println("function4方法,有参有返回值,参数为" + name);
        return "aaa";
    }
}
public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //method1();
        //method2();
        //method3();
        //method4();
        //method5();
    }

    private static void method5() throws ClassNotFoundException, NoSuchMethodException {
        //        Method getDeclaredMethod(String name, Class<?>... parameterTypes):
//                                返回单个成员方法对象
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect5.Student");
        //2.获取一个成员方法show
        Method method = clazz.getDeclaredMethod("show");
        //3.打印一下
        System.out.println(method);
    }
  
    private static void method4() throws ClassNotFoundException, NoSuchMethodException {
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect5.Student");
        //2.获取一个有形参的方法function2
        Method method = clazz.getMethod("function2", String.class);
        //3.打印一下
        System.out.println(method);
    }
  
    private static void method3() throws ClassNotFoundException, NoSuchMethodException {
        //        Method getMethod(String name, Class<?>... parameterTypes) :
//                                返回单个公共成员方法对象
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect5.Student");
        //2.获取成员方法function1
        Method method1 = clazz.getMethod("function1");
        //3.打印一下
        System.out.println(method1);
    }
  
    private static void method2() throws ClassNotFoundException {
        //        Method[] getDeclaredMethods():
//                                返回所有成员方法对象的数组,不包括继承的
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect5.Student");
  
        //2.获取Method对象
        Method[] methods = clazz.getDeclaredMethods();
        //3.遍历一下数组
        for (Method method : methods) {
            System.out.println(method);
        }
    }
  
    private static void method1() throws ClassNotFoundException {
        //        Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect5.Student");
        //2.获取成员方法对象
        Method[] methods = clazz.getMethods();
        //3.遍历
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

2.8 使用成员方法对象

  • 方法介绍
方法名说明
Object invoke(Object obj, Object… args)运行方法
  • 参数详解
    • 参数一: 用obj对象调用该方法
    • 参数二: 调用方法的传递的参数(如果没有就不写)
    • 返回值: 方法的返回值(如果没有就不写)
  • 代码演示
public class ReflectDemo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//        Object invoke(Object obj, Object... args):运行方法
//        参数一:用obj对象调用该方法
//        参数二:调用方法的传递的参数(如果没有就不写)
//        返回值:方法的返回值(如果没有就不写)

        //1.获取class对象
        Class clazz = Class.forName("com.itheima.myreflect5.Student");
        //2.获取里面的Method对象  function4
        Method method = clazz.getMethod("function4", String.class);
        //3.运行function4方法就可以了
        //3.1创建一个Student对象,当做方法的调用者
        Student student = (Student) clazz.newInstance();
        //3.2运行方法
        Object result = method.invoke(student, "zhangsan");
        //4.打印一下返回值
        System.out.println(result);
    }
}

3. xml

3.1 概述

  • 万维网联盟(W3C)
    • 万维网联盟(W3C)创建于1994年,又称W3C理事会。1994年10月在麻省理工学院计算机科学实验室成立。
    • 建立者: Tim Berners-Lee (蒂姆·伯纳斯·李)。
    • 是Web技术领域最具权威和影响力的国际中立性技术标准机构。
    • 到目前为止,W3C已发布了200多项影响深远的Web技术标准及实施指南,
    • 如广为业界采用的超文本标记语言HTML(标准通用标记语言下的一个应用)、
    • 可扩展标记语言XML(标准通用标记语言下的一个子集)
    • 以及帮助残障人士有效获得Web信息的无障碍指南(WCAG)等
  • xml概述
    • XML的全称为(EXtensible Markup Language),是一种可扩展的标记语言
    • 标记语言: 通过标签来描述数据的一门语言(标签有时我们也将其称之为元素)
    • 可扩展:标签的名字是可以自定义的,XML文件是由很多标签组成的,而标签名是可以自定义的
  • 配置文件
    • 用于去配置软件在运行的时候所需要的一些参数
    • 配置文件种类较多
      • properties文件
      • xml文件
      • yaml文件
  • xml 作用
    • 用于进行存储数据和传输数据
    • 作为软件的配置文件
  • 作为配置文件的优势
    • 可读性好
    • 可维护性高

3.2 标签的规则

  • 标签由一对尖括号和合法标识符组成
 <student>
  • 标签必须成对出现
<student> </student>
<!--前边的是开始标签,后边的是结束标签-->
  • 特殊的标签可以不成对,但是必须有结束标记
 <address/>
  • 标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来
 <student id="1"> </student>
  • 标签需要正确的嵌套
 这是正确的: <student id="1"> <name>张三</name> </student>
 这是错误的: <student id="1"><name>张三</student></name>

3.3 xml 语法规则

  • 语法规则
    • XML文件的后缀名为:xml
    • 文档声明必须是第一行第一列
    • 必须存在一个根标签,有且只能有一个
    • XML文件中可以定义注释信息
    • XML文件中可以存在以下特殊字符
    • XML文件中可以存在CDATA区
  • 文档声明属性说明
    • version : 该属性是必须的,声明当前xml文件的版本。一般我们使用的都是1.0。
    • encoding: 该属性不是必须的,告知浏览器在打开该xml文件的时候应该是使用什么字符编码表(一般取值都是UTF-8)。
    • standalone: 该属性不是必须的,描述XML文档是否是孤独的(是否不需要依赖其他的xml文件),取值为yes/no。
  文档声明示例
  <?xml version="1.0" encoding="UTF-8" standalone="yes”?>

    特殊符号如下:
    &lt; < 小于
    &gt; > 大于
    &amp; & 和号
    &apos; ' 单引号
    &quot; " 引号

    CDATA区示例
    <![CDATA[ …内容… ]]>
  • 示例代码
  <?xml version="1.0" encoding="UTF-8" ?>
  <!--注释的内容-->
  <!--本xml文件用来描述多个学生信息-->
  <students>
  
      <!--第一个学生信息-->
      <student id="1">
          <name>张三</name>
          <age>23</age>
          <info>学生&lt; &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;的信息</info>
          <message> <![CDATA[内容 <<<<<< >>>>>> ]]]></message>
      </student>
  
      <!--第二个学生信息-->
      <student id="2">
          <name>李四</name>
          <age>24</age>
      </student>
  
  </students>
  • 如何保证我们编写的xml文件是否正确?
    • 通过idea开发工具进行校验,如果xml编写有误,idea开发工具会有指定的错误提示信息
    • 使用浏览器打开,如果可以正常显示xml文件的内容,那么说明没有问题,如果存在问题,那么在使用浏览器打开的时候就会出错

3.4 xml解析

  • 概述
    • xml解析就是从xml中获取到数据
  • 常见的解析思想
    • DOM(Document Object Model)文档对象模型
      • 把文档的各个组成部分看做成对应的对象
      • 会把xml文件全部加载到内存,在内存中形成一个树形结构,再获取对应的值
    • 作为一个xml文件有哪些组成部分?
      1. xml文档本身 ----- Document
      2. 标签 ----- Element
      3. 属性 ----- Attribute
      4. 注释 ----- Comment
      5. 标签体文本内容 ----- Text

  • Element、Attribute、Text它们之前是存在共性的内容,因此可以向上进行抽取,最终形成了一个父类,这个父类就是Node
  • 常见的解析工具
    • JAXP: SUN公司提供的一套XML的解析的API
    • JDOM: 开源组织提供了一套XML的解析的API-jdom
    • DOM4J: 开源组织提供了一套XML的解析的API-dom4j,全称:Dom For Java
    • pull: 主要应用在Android手机端解析XML
  • 解析的准备工作
    1. 我们可以通过网站:https://dom4j.github.io/ 去下载dom4j
    2. 将提供好的dom4j-1.6.1.zip解压,找到里面的dom4j-1.6.1.jar
    3. 在idea中当前模块下新建一个libs文件夹,将jar包复制到文件夹中
    4. 选中jar包 -> 右键 -> 选择add as library即可
  • 需求
    • 解析提供好的xml文件
    • 将解析到的数据封装到学生对象中
    • 并将学生对象存储到ArrayList集合中
    • 遍历集合
  • 代码实现
    <?xml version="1.0" encoding="UTF-8" ?>
    <!--注释的内容-->
    <!--本xml文件用来描述多个学生信息-->
    <students>
    
        <!--第一个学生信息-->
        <student id="1">
            <name>张三</name>
            <age>23</age>
        </student>
    
        <!--第二个学生信息-->
        <student id="2">
            <name>李四</name>
            <age>24</age>
        </student>
    
    </students>
    
    // 上边是已经准备好的student.xml文件
    public class Student {
        private String id;
        private String name;
        private int age;
    
        public Student() {
        }
    
        public Student(String id, String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    /**
     * 利用dom4j解析xml文件
     */
    public class XmlParse {
        public static void main(String[] args) throws DocumentException {
            //1.获取一个解析器对象
            SAXReader saxReader = new SAXReader();
            //2.利用解析器把xml文件加载到内存中,并返回一个文档对象
            Document document = saxReader.read(new File("myxml\\xml\\student.xml"));
            //3.获取到根标签
            Element rootElement = document.getRootElement();
            //4.通过根标签来获取student标签
            //elements():可以获取调用者所有的子标签.会把这些子标签放到一个集合中返回.
            //elements("标签名"):可以获取调用者所有的指定的子标签,会把这些子标签放到一个集合中并返回
            //List list = rootElement.elements();
            List<Element> studentElements = rootElement.elements("student");
            //System.out.println(list.size());
    
            //用来装学生对象
            ArrayList<Student> list = new ArrayList<>();
    
            //5.遍历集合,得到每一个student标签
            for (Element element : studentElements) {
                //element依次表示每一个student标签
      
                //获取id这个属性
                Attribute attribute = element.attribute("id");
                //获取id的属性值
                String id = attribute.getValue();
    
                //获取name标签
                //element("标签名"):获取调用者指定的子标签
                Element nameElement = element.element("name");
                //获取这个标签的标签体内容
                String name = nameElement.getText();
    
                //获取age标签
                Element ageElement = element.element("age");
                //获取age标签的标签体内容
                String age = ageElement.getText();
    
    //            System.out.println(id);
    //            System.out.println(name);
    //            System.out.println(age);
    
                Student s = new Student(id,name,Integer.parseInt(age));
                list.add(s);
            }
            //遍历操作
            for (Student student : list) {
                System.out.println(student);
            }
        }
    }
    

3.5 DTD约束

3.5.1 编写
  • 什么是约束
    • 用来限定xml文件中可使用的标签以及属性
  • 约束的分类
    • DTD
    • schema
  • 编写DTD约束
    1. 创建一个文件,这个文件的后缀名为.dtd
    2. 看xml文件中使用了哪些元素, <!ELEMENT> 可以定义元素
    3. 判断元素是简单元素还是复杂元素
      • 简单元素:没有子元素。
      • 复杂元素:有子元素的元素;
    • 入门案例代码演示
<!ELEMENT persons (person)>
<!ELEMENT person (name,age)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
3.5.2 引入
  • 引入DTD约束
    • 引入DTD约束的三种方法
      • 引入本地dtd : <!DOCTYPE 根元素名称 SYSTEM ‘DTD文件的路径'>
      • 在xml文件内部引入:<!DOCTYPE 根元素名称 [ dtd文件内容 ]>
      • 引入网络dtd:<!DOCTYPE 根元素的名称 PUBLIC "DTD文件名称" "DTD文档的URL">
    • 代码实现
      • 引入本地DTD约束
        // 这是persondtd.dtd文件中的内容,已经提前写好
        <!ELEMENT persons (person)>
        <!ELEMENT person (name,age)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>
        
        // 在person1.xml文件中引入persondtd.dtd约束
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE persons SYSTEM 'persondtd.dtd'>
        
        <persons>
            <person>
                <name>张三</name>
                <age>23</age>
            </person>
        
        </persons>
    
    • 在xml文件内部引入
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE persons [
              <!ELEMENT persons (person)>
              <!ELEMENT person (name,age)>
              <!ELEMENT name (#PCDATA)>
              <!ELEMENT age (#PCDATA)>
              ]>
      
      <persons>
          <person>
              <name>张三</name>
              <age>23</age>
          </person>
      
      </persons>
    
    • 引入网络dtd
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE persons PUBLIC "dtd文件的名称" "dtd文档的URL">
      
      <persons>
          <person>
              <name>张三</name>
              <age>23</age>
          </person>
      
      </persons>
    
3.5.3 DTD语法
  • 定义元素
    • 定义一个元素的格式为:<!ELEMENT 元素名 元素类型>
    • 简单元素
      ​ EMPTY: 表示标签体为空
      ANY: 表示标签体可以为空也可以不为空
      ​ PCDATA: 表示该元素的内容部分为字符串
    • 复杂元素
      ​ 直接写子元素名称. 多个子元素可以使用",“或者”|"隔开;
      ​ ","表示定义子元素的顺序 ; “|”: 表示子元素只能出现任意一个
      ​ "?"零次或一次, "+"一次或多次, "*"零次或多次;如果不写则表示出现一次
  • 定义属性
    • 定义一个属性的格式为
      • <!ATTLIST 元素名称 属性名称 属性的类型 属性的约束>
    • 属性的类型
      • CDATA类型:普通的字符串
    • 属性的约束
      • // #REQUIRED: 必须的
      • // #IMPLIED: 属性不是必需的
      • // #FIXED value:属性值是固定的
  • 代码实现
    <!ELEMENT persons (person+)>
    <!ELEMENT person (name,age)>
    <!ELEMENT name (#PCDATA)>
    <!ELEMENT age (#PCDATA)>
    <!ATTLIST person id CDATA #REQUIRED>
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE persons SYSTEM 'persondtd.dtd'>
    
    <persons>
        <person id="001">
            <name>张三</name>
            <age>23</age>
        </person>
    
        <person id = "002">
            <name>张三</name>
            <age>23</age>
        </person>
    
    </persons>
    ```
    

3.6 schema约束

3.6.1 schema和dtd的区别
  1. schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
  2. 一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
  3. dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
  4. schema 语法更加的复杂
3.6.2 编写schema约束
1. 创建一个文件,这个文件的后缀名为.xsd
2. 定义文档声明
3. schema文件的根标签为: `<schema>`
4. 在`<schema>`中定义属性:`xmlns=http://www.w3.org/2001/XMLSchema`
5. 在`<schema>`中定义属性 :targetNamespace =唯一的url地址,指定当前这个schema文件的名称空间
6. 在`<schema>`中定义属性 :elementFormDefault="qualified“,表示当前schema文件是一个质量良好的文件。
7. 通过element定义元素
8. 判断当前元素是简单元素还是复杂元素
  • 代码实现
 <?xml version="1.0" encoding="UTF-8" ?>
 <schema
     xmlns="http://www.w3.org/2001/XMLSchema"
     targetNamespace="http://www.itheima.cn/javase"
     elementFormDefault="qualified"
 >
 
     <!--定义persons复杂元素-->
     <element name="persons">
         <complexType>
             <sequence>
                 <!--定义person复杂元素-->
                 <element name = "person">
                     <complexType>
                         <sequence>
                             <!--定义name和age简单元素-->
                             <element name = "name" type = "string"></element>
                             <element name = "age" type = "string"></element>
                         </sequence>      
                     </complexType>
                 </element>
             </sequence>
         </complexType>
     </element>
 </schema>
3.6.3 引入schema约束
1. 在根标签上定义属性xmlns="http://www.w3.org/2001/XMLSchema-instance"
2. 通过xmlns引入约束文件的名称空间
3. 给某一个xmlns属性添加一个标识,用于区分不同的名称空间,格式为: xmlns:标识=“名称空间地址” ,标识可以是任意的,但是一般取值都是xsi
4. 通过xsi:schemaLocation指定名称空间所对应的约束文件路径,格式为:xsi:schemaLocation = "名称空间url 文件路径“
  • 代码实现
 <?xml version="1.0" encoding="UTF-8" ?>
 
 <persons
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://www.itheima.cn/javase"
     xsi:schemaLocation="http://www.itheima.cn/javase person.xsd"
 >
     <person>
         <name>张三</name>
         <age>23</age>
     </person>
 </persons>
 ```
3.6.4 schema约束定义属性
  • 代码示例
 <?xml version="1.0" encoding="UTF-8" ?>
 <schema
     xmlns="http://www.w3.org/2001/XMLSchema"
     targetNamespace="http://www.itheima.cn/javase"
     elementFormDefault="qualified"
 >
 
     <!--定义persons复杂元素-->
     <element name="persons">
         <complexType>
             <sequence>
                 <!--定义person复杂元素-->
                 <element name = "person">
                     <complexType>
                         <sequence>
                             <!--定义name和age简单元素-->
                             <element name = "name" type = "string"></element>
                             <element name = "age" type = "string"></element>
                         </sequence>
                         
                         <!--定义属性,required( 必须的)/optional( 可选的)-->
                         <attribute name="id" type="string" use="required"></attribute>
                     </complexType>
                     
                 </element>
             </sequence>
         </complexType>
     </element>
     
 </schema>
 
 <?xml version="1.0" encoding="UTF-8" ?>
 <persons
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://www.itheima.cn/javase"
     xsi:schemaLocation="http://www.itheima.cn/javase person.xsd"
 >
     <person id="001">
         <name>张三</name>
         <age>23</age>
     </person>
 
 </persons>
 ```

4. 枚举

4.1 概述

  • 为了间接的表示一些固定的值,Java就给我们提供了枚举
  • 是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内

4.2 定义格式

  • 格式
    public enum s {   
    	枚举项1,枚举项2,枚举项3;
    }
    注意: 定义枚举类要用关键字enum
    
  • 示例代码
    // 定义一个枚举类,用来表示春,夏,秋,冬这四个固定值
    public enum Season {
        SPRING,SUMMER,AUTUMN,WINTER;
    }
    

4.3 枚举的特点

  • 特点
    • 所有枚举类都是Enum的子类
    • 我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
    • 每一个枚举项其实就是该枚举的一个对象
    • 枚举也是一个类,也可以去定义成员变量
    • 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
    • 枚举类可以有构造器,但必须是private的,它默认的也是private的。
    • 枚举项的用法比较特殊:枚举("");
    • 枚举类也可以有抽象方法,但是枚举项必须重写该方法
  • 示例代码
    public enum Season {
    
        SPRING("春"){
    
            //如果枚举类中有抽象方法
            //那么在枚举项中必须要全部重写
            @Override
            public void show() {
                System.out.println(this.name);
            }
    
        },
    
        SUMMER("夏"){
            @Override
            public void show() {
                System.out.println(this.name);
            }
        },
    
        AUTUMN("秋"){
            @Override
            public void show() {
                System.out.println(this.name);
            }
        },
    
        WINTER("冬"){
            @Override
            public void show() {
                System.out.println(this.name);
            }
        };
    
        public String name;
    
        //空参构造
        //private Season(){}
      
        //有参构造
        private Season(String name){
            this.name = name;
        }
      
        //抽象方法
        public abstract void show();
    }
    
    public class EnumDemo {
        public static void main(String[] args) {
            /*
            1.所有枚举类都是Enum的子类
            2.我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
            3.每一个枚举项其实就是该枚举的一个对象
            4.枚举也是一个类,也可以去定义成员变量
            5.枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,
              但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
            6.枚举类可以有构造器,但必须是private的,它默认的也是private的。
              枚举项的用法比较特殊:枚举("");
            7.枚举类也可以有抽象方法,但是枚举项必须重写该方法
        */
      
            //第二个特点的演示
            //我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
            System.out.println(Season.SPRING);
            System.out.println(Season.SUMMER);
            System.out.println(Season.AUTUMN);
            System.out.println(Season.WINTER);
      
            //第三个特点的演示
            //每一个枚举项其实就是该枚举的一个对象
            Season spring = Season.SPRING;
        }
    }
    

4.4 枚举的方法

  • 方法介绍

    方法名说明
    String name()获取枚举项的名称
    int ordinal()返回枚举项在枚举类中的索引值
    int compareTo(E o)比较两个枚举项,返回的是索引值的差值
    String toString()返回枚举常量的名称
    static T valueOf(Class type,String name)获取指定枚举类中的指定名称的枚举值
    values()获得所有的枚举项
  • 示例代码

    public enum Season {
        SPRING,SUMMER,AUTUMN,WINTER;
    }
    
    public class EnumDemo {
        public static void main(String[] args) {
    //        String name() 获取枚举项的名称
            String name = Season.SPRING.name();
            System.out.println(name);
            System.out.println("-----------------------------");
    
    //        int ordinal() 返回枚举项在枚举类中的索引值
            int index1 = Season.SPRING.ordinal();
            int index2 = Season.SUMMER.ordinal();
            int index3 = Season.AUTUMN.ordinal();
            int index4 = Season.WINTER.ordinal();
            System.out.println(index1);
            System.out.println(index2);
            System.out.println(index3);
            System.out.println(index4);
            System.out.println("-----------------------------");
    
    //        int compareTo(E o) 比较两个枚举项,返回的是索引值的差值
            int result = Season.SPRING.compareTo(Season.WINTER);
            System.out.println(result);//-3
            System.out.println("-----------------------------");
    
    //        String toString()   返回枚举常量的名称
            String s = Season.SPRING.toString();
            System.out.println(s);
            System.out.println("-----------------------------");
    
    //        static <T> T valueOf(Class<T> type,String name)
    //        获取指定枚举类中的指定名称的枚举值
            Season spring = Enum.valueOf(Season.class, "SPRING");
            System.out.println(spring);
            System.out.println(Season.SPRING == spring);
            System.out.println("-----------------------------");
    
    //        values()       获得所有的枚举项
            Season[] values = Season.values();
            for (Season value : values) {
                System.out.println(value);
            }
        }
    }
    

5. 注解

5.1 概述

  • 对我们的程序进行标注和解释
  • 注解和注释的区别
    • 注释: 给程序员看的
    • 注解: 给编译器看的
  • 使用注解进行配置配置的优势
    • 代码更加简洁,方便

5.2 JDK中的注解

注解作用
@Override描述子类重写父类的方法
@SuppressWarnings压制警告
@Deprecated描述方法过时
  • 代码演示
@SuppressWarnings(value = "all")
//表示压制了本类中所有的警告
public class Zi extends Fu {

    @Override//告诉我们得编译器这个方法是重写了父类中的show方法
                //那么我们在进行方法定义的时候,就需要按照重写方法的要求进行方法定义。
    public void show() {
        System.out.println("子类的方法");
    }

    //表示这是一个过时的方法
    @Deprecated
    public void method(){
        System.out.println("method.......");
    }

    public void function2(){
        int a = 10;
    }

    @SuppressWarnings(value = "all")
    //表示压制本方法中所有的警告
    public void function(){
        int a = 10;
        int b = 20;
        //System.out.println(a);
        //System.out.println(b);
    }
}

5.3 自定义注解

  • 格式
    public @interface 注解名称 {public 属性类型 属性名() default 默认值 ;
    }
    
    • 在定义属性的时候,可以通过default子句去给该属性指定默认值
  • 属性类型
    • 基本数据类型
    • String类型
    • Class类型
    • 注解类型
    • 枚举类型
    • 以上类型的一维数组
  • 代码演示
    public @interface Anno1 {
    
        //定义一个基本类型的属性
        int a () default 23;
    
        //定义一个String类型的属性
        public String name() default "itheima";
    
        //定义一个Class类型的属性
        public Class clazz() default Anno2.class;
    
        //定义一个注解类型的属性
        public Anno2 anno() default @Anno2;
    
        //定义一个枚举类型的属性
        public Season season() default Season.SPRING;
    
        //以上类型的一维数组
        //int数组
        public int[] arr() default {1,2,3,4,5};
    
        //枚举数组
        public Season[] seasons() default {Season.SPRING,Season.SUMMER};
    
        //value。后期我们在使用注解的时候,如果我们只需要给注解的value属性赋值。
        //那么value就可以省略
        public String value();
    
    }
    
    //在使用注解的时候如果注解里面的属性没有指定默认值。
    //那么我们就需要手动给出注解属性的设置值。
    //@Anno1(name = "itheima")
    @Anno1("abc")
    public class AnnoDemo {
    }
    
  • 注意
    • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可

5.4 自定义注解案例

  • 方法
方法说明
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)如果此元素上 存在指定类型的注释,则返回true,否则返回false
  • 需求
    • 自定义一个注解@Test,用于指定类的方法上,如果某一个类的方法上使用了该注解,就执行该方法
  • 实现步骤
    1. 自定义一个注解Test,并在类中的某几个方法上加上注解
    2. 在测试类中,获取注解所在的类的Class对象
    3. 获取类中所有的方法对象
    4. 遍历每一个方法对象,判断是否有对应的注解
  • 代码实现
    //表示Test这个注解的存活时间
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface Test {
    }
    
    public class UseTest {
    
        //没有使用Test注解
        public void show(){
            System.out.println("UseTest....show....");
        }
    
        //使用Test注解
        @Test
        public void method(){
            System.out.println("UseTest....method....");
        }
    
        //没有使用Test注解
        @Test
        public void function(){
            System.out.println("UseTest....function....");
        }
    }
    
    public class AnnoDemo {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
            //1.通过反射获取UseTest类的字节码文件对象
            Class clazz = Class.forName("com.itheima.myanno3.UseTest");
    
            //创建对象
            UseTest useTest = (UseTest) clazz.newInstance();
    
            //2.通过反射获取这个类里面所有的方法对象
            Method[] methods = clazz.getDeclaredMethods();
    
            //3.遍历数组,得到每一个方法对象
            for (Method method : methods) {
                //method依次表示每一个方法对象。
                //isAnnotationPresent(Class<? extends Annotation> annotationClass)
                //判断当前方法上是否有指定的注解。
                //参数:注解的字节码文件对象
                //返回值:布尔结果。  true 存在  false 不存在
                if(method.isAnnotationPresent(Test.class)){
                    method.invoke(useTest);
                }
            }
        }
    }

5.5 元注解

  • 概述

    • 元注解就是描述注解的注解
  • 元注解介绍

    元注解名说明
    @Target指定了注解能在哪里使用
    @Retention可以理解为保留时间(生命周期)
    @Inherited表示修饰的自定义注解可以被子类继承
    @Documented表示该自定义注解,会出现在API文档里面。
  • 示例代码

 @Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})  //指定注解使用的位置(成员变量,类,方法)
 @Retention(RetentionPolicy.RUNTIME) //指定该注解的存活时间
 //@Inherited //指定该注解可以被继承
 public @interface Anno {
 }
 
 @Anno
 public class Person {
 }
 
 public class Student extends Person {
     public void show(){
         System.out.println("student.......show..........");
     }
 }
 
 public class StudentDemo {
     public static void main(String[] args) throws ClassNotFoundException {
         //获取到Student类的字节码文件对象
         Class clazz = Class.forName("com.itheima.myanno4.Student");
 
         //获取注解。
         boolean result = clazz.isAnnotationPresent(Anno.class);
         System.out.println(result);
     }
 }

6. 单元测试

6.1 概述

  • 单元测试
    • 就是把我们的程序分单元进行测试
  • 常见的单元测试工具:Junit
  • JUnit是一个 Java 编程语言的单元测试工具,是一个非常重要的测试工具

6.2 Junit 特点

  • JUnit是一个开放源代码的测试工具。
  • 提供注解来识别测试方法。
  • JUnit测试可以让你编写代码更快,并能提高质量。
  • JUnit优雅简洁。没那么复杂,花费时间较少。
  • JUnit在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。

6.3 使用步骤

  1. 将junit的jar包导入到工程中 junit-4.9.jar
  2. 编写测试方法该测试方法必须是公共的无参数无返回值的非静态方法
  3. 在测试方法上使用@Test注解标注该方法是一个测试方法
  4. 选中测试方法右键通过junit运行该方法
  • 代码示例

    public class JunitDemo1 {
        @Test
        public void add() {
            System.out.println(2 / 0);
            int a = 10;
            int b = 20;
            int sum = a + b;
            System.out.println(sum);
        }
    }
    

6.4 相关注解

  • 注解说明
    注解含义
    @Test表示测试该方法
    @Before在测试的方法前运行
    @After在测试的方法后运行
  • 代码示例
    public class JunitDemo2 {
        @Before
        public void before() {
          	// 在执行测试代码之前执行,一般用于初始化操作
            System.out.println("before");
        }
        @Test
        public void test() {
          	// 要执行的测试代码
            System.out.println("test");
        }
        @After
        public void after() {
          	// 在执行测试代码之后执行,一般用于释放资源
            System.out.println("after");
        }
    }
    

7. 日志

7.1 概述

  • 如果我们想清楚的知道程序在运行时候的一些详细信息,该怎么是实现
  • 程序中的日志可以用来记录程序在运行的时候点点滴滴。并可以进行永久存储。
  • 日志与输出语句的区别
    输出语句日志技术
    取消日志需要修改代码,灵活性比较差不需要修改代码,灵活性比较好
    输出位置只能是控制台可以将日志信息写入到文件或者数据库中
    多线程和业务代码处于一个线程中多线程方式记录日志,不影响业务代码的性能

7.2 日志体系结构和Log4J

  • 体系结构
  • 常见的日志接口有两个:JCL,slf4j
    • JCL是sun公司提供的一套日志接口,但是这套日志在使用的时候是存在问题的,因此后来就出现了第三方的日志接口:slf4j
    • slf4j是一套日志接口,是由很多的抽象类和接口组成,不能真正的干活,因此后期针对这套接口又提供了很多的实现类。目前市面上使用最多的就是log4j,logback。这里介绍比较简单的log4j
  • Log4J
    • Log4j是Apache的一个开源项目。
    • 通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件等位置。
    • 我们也可以控制每一条日志的输出格式。
    • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
    • 最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

7.3 入门案例

  • 使用步骤
    1. 导入log4j的相关jar包
    2. 编写log4j配置文件
    3. 在代码中获取日志的对象,即创建一个日志记录器对象(这个Logger对象是slf4j里面的)
    4. 按照级别设置记录日志信息
  • 代码示例
// log4j的配置文件,名字为log4j.properties, 放在src根目录下
  log4j.rootLogger=debug,my,fileAppender
 
  ### direct log messages to my ###
  log4j.appender.my=org.apache.log4j.ConsoleAppender
  log4j.appender.my.ImmediateFlush = true
  log4j.appender.my.Target=System.out
  log4j.appender.my.layout=org.apache.log4j.PatternLayout
  log4j.appender.my.layout.ConversionPattern=%d %t %5p %c{1}:%L - %m%n
  
  # fileAppender演示
  log4j.appender.fileAppender=org.apache.log4j.FileAppender
  log4j.appender.fileAppender.ImmediateFlush = true
  log4j.appender.fileAppender.Append=true
  log4j.appender.fileAppender.File=D:/log4j-log.log
  log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
  log4j.appender.fileAppender.layout.ConversionPattern=%d %5p %c{1}:%L - %m%n
// 测试类
public class Log4JTest01 {

    //使用log4j的api来获取日志的对象
    //弊端:如果以后我们更换日志的实现类,那么下面的代码就需要跟着改
    //不推荐使用
    //private static final Logger LOGGER = Logger.getLogger(Log4JTest01.class);

    //使用slf4j里面的api来获取日志的对象
    //好处:如果以后我们更换日志的实现类,那么下面的代码不需要跟着修改
    //推荐使用
    private static  final Logger LOGGER = LoggerFactory.getLogger(Log4JTest01.class);

    public static void main(String[] args) {
        //1.导入jar包
        //2.编写配置文件
        //3.在代码中获取日志的对象
        //4.按照日志级别设置日志信息
        LOGGER.debug("debug级别的日志");
        LOGGER.info("info级别的日志");
        LOGGER.warn("warn级别的日志");
        LOGGER.error("error级别的日志");
    }
}

7.4 配置文件详解

  • 三个核心
    • Loggers(记录器) :日志的级别
      • Loggers组件在此系统中常见的五个级别:DEBUG、INFO、WARN、ERROR 和 FATAL。
      • DEBUG < INFO < WARN < ERROR < FATAL。
      • DEBUG这个级别是用来记录基本的信息
      • INFO这个级别是用来记录比较重要的信息
      • WARN这个级别是用来记录一些警告信息,告知程序员可能会出现问题
      • ERROR这个级别是用来记录一些错误信息,但是不会影响程序的正常运行
      • FATAL这个级别是用来记录一些重大错误信息,会终止程序的运行
      • Log4j有一个规则:只输出级别不低于设定级别的日志信息。(在配置文件中进行设定)
    • Appenders(输出源) :日志要输出的地方
      • 把日志输出到不同的地方,如控制台(Console)、文件(Files)、数据库等。
      • org.apache.log4j.ConsoleAppender(控制台)
      • org.apache.log4j.FileAppender(文件)
    • Layouts(布局) :日志输出的格式
      • 可以根据自己的喜好规定日志输出的格式
      • 常用的布局管理器:
        ​ org.apache.log4j.PatternLayout(可以灵活地指定布局模式)常用
        ​ org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
        org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)
  • 配置根Logger
    • 格式
      • log4j.rootLogger=日志级别,appenderName1,appenderName2,…
    • 日志级别
      • OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别。
    • appenderName1
      • 就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开。
      • 例如:log4j.rootLogger=INFO,ca,fa
  • ConsoleAppender常用的选项
    • ImmediateFlush=true
      • 表示所有消息都会被立即输出,设为false则不输出,默认值是true。
    • Target=System.err
      • 默认值是System.out。
  • FileAppender常用的选项
    • ImmediateFlush=true
      • 表示所有消息都会被立即输出。设为false则不输出,默认值是true
    • Append=false
      • true表示将消息添加到指定文件中,原来的消息不覆盖。
      • false则将消息覆盖指定的文件内容,默认值是true。
    • File=D:/logs/logging.log4j
      • 指定消息输出到logging.log4j文件中
  • PatternLayout常用的选项
    • ConversionPattern=%m%n
      • 设定以怎样的格式显示消息

7.5 在项目中的应用

  1. 导入相关所需要的jar包
  2. 将properties配置文件复制到src目录下
  3. 在代码中获取日志的对象
  4. 按照级别设置记录日志信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值