天天看点

大数据JavaWe基础 ------Junit单元测试, 类加载器, 反射, 注解

演示junit单元测试

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

/*


    案例: 演示Junit单元测试的细节 和 常用注解.

   Junit单元测试简介:
        概述:
            它是Apache提供的第三方的jar包, 可以简单理解为: 就是用来替代main方法的,
            即: 代码不用放到main方法中, 也能执行.
        使用步骤:
            1. 在项目(模块)下创建: lib 文件夹.
            2. 把需要使用的jar包拷贝进来.
            3. 配置运行时环境.
                选中jar包, 右键 -> add as library....
            4. 到这里, 就能使用该jar包中的内容了.

    使用Junit单元测试的细节:
        1. Junit单元测试只针对于 无参无返回值的 非静态方法有效.
        2. @Test一般用于: 具体的测试方法,  @Before: 一般用于 初始化参数.  @After: 一把用于 释放资源.

    和Junit单元测试相关的注解:
        @Before 它标注的方法: 会在 @Test标注的方法之前, 自动被调用.
        @Test   它标注的是: 具体要进行Junit单元测试的代码.
        @After  它标注的方法: 会在 @Test标注的方法之后, 自动被调用.
 */
public class demo04junit {

    @Before
    public void before(){
        System.out.println("是在测试之前执行的.........");
    }

    @Test
    public void show01(){
        System.out.println("我是测试方法1.............");
    }

    @After
    public void after(){
        System.out.println("我只在测试之后执行的.......");
    }
}

           

类加载器

大数据JavaWe基础 ------Junit单元测试, 类加载器, 反射, 注解
/*
    案例: 讲解类加载器相关的概述.

    类加载器简介:
        概述:
            它的全称是ClassLoader, 主要负责加载 字节码文件进内存, 并为之创建一个与其对应的Class对象.
       JVM的类加载机制:
            全盘加载: 就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
            父类委托: 就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
            缓存机制: 保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
            
        分类:
            BootStrapClassLoader:   根类加载器,
                负责加载: jre/lib/rt.jar 相关,  它是通过 C语言编写的.
            ExtClassLoader:  扩展类加载器,
                负责加载: jre/lib/ext/*.jar,  JDK1.9的时候更名为: Platform ClassLoader
            ApplicationClassLoader: 应用程序类加载器
                负责加载: 用户自定义的类 和 ClassPath环境变量配置的内容. JDK1.9的时候更名为: SystemClassLoader

      
        如何获取加载当前字节码文件的类加载器:
          ClassLoader#getClassLoader();     获取负责加载此类的类加载器对象.

    总结(记忆):
        1. 类加载就是加载 字节码文件进内存, 并为之创建与其对应的 Class对象的.
        2. 类加载器的检查顺序是: App -> Ext -> BootStrap
        3. 类加载器的加载顺序是: BootStrap -> Ext -> App
 */
 
public class DemoClassloader {
    public static void main(String[] args) {
        ClassLoader lo = ClassLoader.getSystemClassLoader();
        System.out.println(lo);  //获取负责加载当前类的类加载器: AppClassLoader

        System.out.println(lo.getParent()); //获取当前类加载器的父类加载器: ExtClassLoader

          /*
            这里理论上应该是: BootStrapClassLoader, 但是因为它的底层是通过C语言编写的,
            在Java中并没有与其对应的 对象形式, 所以是: null.
         */
        System.out.println(lo.getParent().getParent());  //获取当前类加载器的父类的父类加载器. null
    }
}
           

Properties集合类详解.

/*
    案例: Properties集合类详解.

    Properties简介:
        概述:
            它是双列集合, 表示持久性的属性集, 一般是结合配置文件使用的, 键值都是String类型, 且它是Hashtable的子类,
            它是唯一一个可以直接和IO流相交互使用的集合类. 可以直接从文件中加载文件,或者直接写数据到文件中.
        涉及到的成员方法:
            public void setProperty(String key, String value);  类似于Map#put()方法, 表示添加元素.
            public String getProperty(String key);              类似于Map#get()方法, 表示根据键获取值.

            public void load(Reader r);         接收一个(字符)输入流对象, 可以直接从这个对象中读取数据, 并存储到集合中.
            public void load(InputStream is);   接收一个(字节)输入流对象, 可以直接从这个对象中读取数据, 并存储到集合中.

            public void store(Writer w, String comments);       接收一个(字符)输出流对象, 可以直接往文件中写数据, comments类似于备注信息, 一般写: null.
            public void store(OutputStream os, String comments);接收一个(字符)输出流对象, 可以直接往文件中写数据, comments类似于备注信息, 一般写: null.
 */
public class DemoProperties {
    public static void main(String[] args) throws Exception{
        //需求1: 读取my.properties文件中的数据到集合, 并打印.
        //1. 创建Properties集合对象.
        Properties pp = new Properties();
        //2. 从文件中读取数据到集合.
        pp.load(new FileInputStream("day10_bigdata/src/my.Properties"));
        //3. 打印集合信息.
        System.out.println("修改前:" + pp);  //修改前:{address=hubei, age=23, name=liuafu}


        //需求2: 修改集合信息, 然后写入到my.properties配置文件中.
        //4. 修改集合信息.
        pp.setProperty("name","liuafu");
        pp.setProperty("phone","12345678911");
        //5. 把修改后的信息重新写回到文件中.

        pp.store(new FileOutputStream("day10_bigdata/src/my.Properties"),null);
        System.out.println("修改后:" + pp);  //修改后:{phone=12345678911, address=hubei, age=23, name=liuafu}
    }
}

           

反射

反射简介:
	概述:
		就是在程序的运行期间, 通过字节码文件来操作类中的成员(成员变量, 构造方法, 成员方法).
	思路(操作步骤):
		1. 获取指定类的字节码文件对象.
		2. 获取该类的成员对象(成员变量对象, 构造器对象, 成员方法对象).
		3. 通过特定的方法来操作类中的 成员对象集合.
	获取字节码文件对象的 3 种方式:
		1. 类名.class属性.
			一般用作 锁对象.
		2. 对象名.getClass()方法
			判断两个对象是否是同一个类型的对象, 例如: 重写Object#equals()方法时.
		3. Class.forName("全类名");
			强制加载某个类的字节码文件对象, 一般应用在 反射阶段.
			//解释: 当第一次使用某个类的字节码内容, 但是该类的字节码文件对象在内存中不存在时, 
			JVM才会去加载整个字节码文件, 而此方法可以直接让JVM加载指定的字节码文件.
	细节:
		1. Class类表示 所有的字节码文件对象.
		2. 一个.java文件对应一个.class文件, 且该文件在内存中只有一份.
		
	反射涉及到的成员方法:
		Class类中的成员方法:
			public static Class forName(String classPath);				根据传入的全类名, 获取该类的字节码文件对象.
			public Object newInstance();								根据该类的公共的空参对象, 创建该类的实例(对象).
			
			public Constructor[] getConstructors();						获取该类中所有的构造方法对象(不包含私有)
			public Constructor getConstructor(Class... params);			根据传入的参数, 获取该类中指定的构造器对象(不包含私有)
			public Constructor[] getDeclaredConstructors();				获取该类中所有的构造方法对象(包含私有)
			public Constructor getDeclaredConstructor(Class... params);	根据传入的参数, 获取该类中指定的构造器对象(包含私有)
			
			public Field[] getFields();						获取该类中所有的成员变量对象(不包含私有)
			public Field getField(String name);				根据传入的成员变量名, 获取该类中指定的成员变量对象(不包含私有)
			public Field[] getDeclaredFields();				获取该类中所有的成员变量对象(包含私有)
			public Field getDeclaredField(String name);		根据传入的成员变量名, 获取该类中指定的成员变量对象(包含私有)
			
			
			public Method[] getMethods();							
				//获取该类中所有的成员方法对象(不包含私有)
			public Method getMethod(String name,Class... params);				
				//根据传入的成员方法名及参数列表, 获取该类中指定的成员方法对象(不包含私有)
			public Method[] getDeclaredMethods();				
				//获取该类中所有的成员方法对象(包含私有)
			public Method getDeclaredMethod(String name,Class... params);		
				//根据传入的成员方法名及参数列表, 获取该类中指定的成员方法对象(包含私有)
				
			Annotation[] getAnnotations();          获得当前对象及其从父类上继承的所有的注解对象。
            Annotation[] getDeclaredAnnotations();  获得当前对象上所有的注解对象,不包括父类的。

            boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有指定的注解,有则返回true,否则返回false。
            T getAnnotation(Class<T> annotationClass);          获得当前对象上指定的注解对象。
			
		
		Constructor类中的成员方法:		//构造器
			public Object newInstance(Object... params)					根据传入的参数, 创建该类的对象.
			public void setAccessible(boolean flag);					设置访问权限, 如果为true, 表示暴力反射.
			
		Field类中的成员方法:			//成员变量, 字段
			public void set(Object obj, Object value);			设置指定对象(obj)的指定属性为指定的值(value).
			public void setAccessible(boolean flag);			设置访问权限, 如果为true, 表示暴力反射.
			
		Method类中的成员方法:			//成员方法
			public Object invoke(Object obj, Object... value)	调用某个类的指定方法.
			public void setAccessible(boolean flag);			设置访问权限, 如果为true, 表示暴力反射.
           
案例1: 演示反射入门之 获取字节码文件对象的方式.
/*
    案例: 演示反射入门之 获取字节码文件对象的方式.

    反射简介:
        概述:
            反射指的就是 在程序的运行期间, 通过操作类的字节码文件对象, 从而达到操作类中 成员的目的.
            一个.java文件对应一个.class文件, 一个.class文件对应一个Class对象.
        核心步骤:
            1. 获取指定类的字节码文件对象.
            2. 根据类的字节码文件对象, 来获取类中指定的成员(变量, 方法, 构造方法)
            3. 操作类中指定的成员(变量, 方法, 构造方法)
            4. 打印结果.


    获取类的字节码文件对象的三种方式:
        方式1: 类名.class属性     Student.class
            一般当做 锁对象.
        方式2: 对象名.getClass()  new Student().getClass()
            一般用于 比较两个对象是否是同一个类型的对象.
        方式3: 强制加载某个类的字节码文件进内存,  Class#forName("全类名");
            全类名 = 包名 + 类名     一般用于 反射技术.
        因为一个.java文件对应一个.class文件, 一个.class文件对应一个Class对象, 所以只要类相同, 则上述三种方式创建的对象, 都是同一个.
 */
public class Demo01 {
    public static void main(String[] args) throws ClassNotFoundException {
        //需求: 演示获取字节码文件对象的3种方式:
        //方式1: 类名.class属性     Student.class
        Class<Student> cs1 = Student.class;

        //方式2: 对象名.getClass()  new Student().getClass()
        Class<? extends Student> cs2 = new Student().getClass();

        //方式3: 强制加载某个类的字节码文件进内存,  Class#forName("全类名");
        Class<?> cs3 = Class.forName("com.xxyl.pojo.Student");

        System.out.println(cs1);   //class com.xxylt.pojo.Student
        System.out.println(cs2);   //class com.xxyl.pojo.Student
        System.out.println(cs3);   //class com.xxyl.pojo.Student

        System.out.println(cs1 == cs2);  //true
        System.out.println(cs1 == cs3);  //true
        System.out.println(cs2 == cs3);  //true
    }
}
           
案例2: 演示反射的方式 操作类的公共的空参构造, 创建类的实例.
/*
    案例: 演示反射的方式 操作类的公共的空参构造, 创建类的实例.

    涉及到的成员方法:
        Class类中的成员方法:
            public Class forName(String class); 根据全类名, 获取该类对应的 字节码文件对象, 即: Class对象.
            public Object newInstance();    可以通过类的公共的空参构造, 直接创建该类的实例.

            public Constructor[] getConstructors();     获取类中 所有的构造方法对象(不包含私有)
            public Constructor getConstructor(Class... params); 根据参数类型, 获取类中指定的构造方法对象(不包含私有)
            public Constructor[] getDeclaredConstructors(); 获取类中 所有的构造方法对象(包含私有)
            public Constructor getDeclaredConstructor(Class... params); 根据参数类型, 获取类中指定的构造方法对象(包含私有)

       Constructor: 表示 构造方法(即: 构造器)对象
        public Object newInstance(Object... values);    根据参数值, 创建对应的 JavaBean对象.
        public void setAccessible(boolean flag);        暴力反射, 一般用于访问类中的私有成员, true: 表示开启暴力反射.

 */
public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 1. 获取指定类的字节码文件对象.
        Class<?> cs1 = Class.forName("com.xxxyl.pojo.Student");
        // 2. 根据类的字节码文件对象, 来获取类中指定的成员(变量, 方法, 构造方法)
        Constructor<?> con = cs1.getConstructor();
        // 3. 操作类中指定的成员(变量, 方法, 构造方法)
        Object obj = con.newInstance();
        //4.打印结果
        System.out.println(obj);   //Student{name='null', age=0}

        //方式2: 简写版, 只针对类中的 公共的 无参构造有效.
        // 1. 获取指定类的字节码文件对象.
        Class<?> cs2 = Class.forName("com.xxyl.pojo.Student");
        //2.通过Class#newInstance()直接创建对象.
        Object obj1 = cs2.newInstance();
        System.out.println(obj1);    //Student{name='null', age=0}

    }
}

           
案例3: 反射操作构造方法之 公共的全参构造.
/*
    案例: 反射操作构造方法之  公共的全参构造.
 */
public class Demo03 {
    public static void main(String[] args) throws Exception{
        //需求: 创建学生对象, 刘啊福, 18
        // 1. 获取指定类的字节码文件对象.
        Class<?> cs1 = Class.forName("com.xxyl.pojo.Student");
        // 2. 根据类的字节码文件对象, 来获取类中指定的成员(变量, 方法, 构造方法)
        Constructor<?> con = cs1.getConstructor(String.class, int.class);
        //3. 操作类中指定的成员(变量, 方法, 构造方法)
        Object obj = con.newInstance("刘啊福", 18);
        System.out.println(obj);  //Student{name='刘啊福', age=18}
    }
}
           
案例4: 反射操作构造方法之 私有的带参构造.
/*
    案例: 反射操作构造方法之  私有的带参构造.
 */
public class Demo04 {
    public static void main(String[] args)  throws Exception{
        // 1. 获取指定类的字节码文件对象.
        Class<?> cs = Class.forName("com.itcast.pojo.Student");
        // 2. 根据类的字节码文件对象, 来获取类中指定的成员(变量, 方法, 构造方法)
        Constructor<?> dcon = cs.getDeclaredConstructor(String.class);
        //访问私有成员之前, 必须先: 暴力反射
        dcon.setAccessible(true);
        // 3. 操作类中指定的成员(变量, 方法, 构造方法)
        System.out.println(dcon.newInstance("刘啊福"));   //Student{name='刘啊福', age=0}

    }
}

           
案例5: 反射操作成员变量之 私有的成员变量.
import java.lang.reflect.Field;

/*
    案例: 反射操作成员变量之  私有的成员变量.

    涉及到的成员方法:
        Class类中的成员方法:
            public Field[] getFields();         获取类中 所有的成员变量对象(不包含私有)
            public Field getField(String name); 根据成员变量名, 获取类中指定的成员变量对象(不包含私有)
            public Field[] getDeclaredFields(); 获取类中 所有的成员变量对象(包含私有)
            public Field getDeclaredField(String name); 根据成员变量名, 获取类中指定的成员变量对象(包含私有)

       Field: 表示 成员变量(即: 字段)对象
        public void set(Object obj, Object value);   设置指定对象(obj)的指定属性为指定的值(value)
        public void setAccessible(boolean flag);     暴力反射, 一般用于访问类中的私有成员, true: 表示开启暴力反射.


    反射的核心步骤:
        1. 获取指定类的字节码文件对象.
        2. 根据类的字节码文件对象, 来获取类中指定的成员(变量, 方法, 构造方法)
        3. 操作类中指定的成员(变量, 方法, 构造方法)
        4. 打印结果.
 */
public class Demo05 {
    public static void main(String[] args) throws Exception {
        //需求: 创建姓名为 詹姆斯的 学生对象, 采用 空参构造 + 给属性赋值的方式实现.
        //1. 获取指定类的字节码文件对象.
        Class<?> cs1 = Class.forName("com.xxyl.pojo.Student");
        //2. 通过公共的空参构造, 获取Student对象.
        Object obj = cs1.newInstance();
        //3. 获取私有的 name字段.
        Field name = cs1.getDeclaredField("name");
        //4.暴力反射
        name.setAccessible(true);
        //5.设置名字
        name.set(obj,"詹姆斯");

        System.out.println(obj);  //Student{name='詹姆斯', age=0}

    }
}

           
演示反射操作类中的 成员方法之 公共的有参有返回值的方法
/*
    案例: 演示反射操作类中的 成员方法之  公共的有参有返回值的方法.

    涉及到的成员方法:
        Class类中的成员方法:
            public Method[] getMethods();     获取类中 所有的成员方法对象(不包含私有)
            public Method getMethod(String name, Class... params); 根据方法名和参数类型, 获取类中指定的成员方法对象(不包含私有)
            public Method[] getDeclaredMethods(); 获取类中 所有的成员方法对象(包含私有)
            public Method getDeclaredMethod(String name, Class... params); 根据方法名和参数类型, 获取类中指定的成员方法对象(包含私有)

       Method: 表示 成员方法 对象
        public Object invoke(Object obj, Object... values);   指定obj对象的指定方法, values是执行方法所需的实参.
        public void setAccessible(boolean flag);        暴力反射, 一般用于访问类中的私有成员, true: 表示开启暴力反射.

 */
public class Demo06 {
    public static void main(String[] args) throws Exception {
        //需求: 调用Student#study()方法
        //1. 获取指定类的字节码文件对象.
        Class<?> cs1 = Class.forName("com.xxyl.pojo.Student");
        //2. 通过公共的空参构造,创建实例.
        Object obj = cs1.newInstance();
        //3. 获取Student#getSum()方法
        Method getSum = cs1.getMethod("getSum",int.class,int.class);
        //4. 调用study方法.
        Object invoke = getSum.invoke(obj,20,30);
        //5. 打印调用结果.
        System.out.println("返回值: " + invoke);
    }
}
           

案例: 反射的两个案例,加深对反射的理解.

/*
    反射案例1: 越过泛型校验.

    需求:
        往ArrayList<Integer>集合中, 添加一个 字符串.

    细节(记忆):
        泛型只在程序的编译期间有效, 在运行时无效.
 */
public class ReflectDemo01 {
    public static void main(String[] args) throws Exception {
        //1. 获取ArrayList集合类的字节码文件对象.
        Class<?> cs = Class.forName("java.util.ArrayList");
        //2. 获取ArrayList<Integer>集合对象.
        ArrayList<Integer> list = new ArrayList<>();
        list.add(20);
        list.add(30);
        //list.add("123");  报错

        //3. 获取ArrayList#add()方法, 指定参数类型为: Object类型.
        Method m = cs.getMethod("add", Object.class);
        //4. 调用方法, 往集合中添加元素.
        m.invoke(list,"abc");
        m.invoke(list,false);
        m.invoke(list,50);

        //打印
        System.out.println(list);
    }
}
           
/*
    反射案例: 模拟框架读取配置文件的操作.

    需求:
        已知模块下的 src文件夹下的 config.properties文件中记录的是类名和方法名.
        请根据配置文件中的信息, 调用指定类的指定方法.
 */
public class ReflectDemo02 {
    public static void main(String[] args) throws Exception {
        //1. 读取配置文件中的信息, 获取全类名和方法名.
        //1.1 创建Properties集合类.
        Properties pp = new Properties();
        //1.2 从配置文件中加载数据到集合中.
        pp.load(new FileReader("day10_bigdata/src/config.properties"));
        //1.3 获取具体的数据. 全类名和方法名.
        String className = pp.getProperty("className");
        String methodName = pp.getProperty("methodName");

        //2. 根据全类名, 获取其对应的字节码文件对象.
        //Class<?> clazz = Class.forName("com.xxyl.pojo.Teacher");
        Class<?> clazz = Class.forName(className);

        //3. 根据该类的空参构造,创建该类的实例对象.
        Object obj = clazz.newInstance();
        //4. 根据方法名, 获取指定的方法对象.
        //Method eat = clazz.getMethod("eat");
        Method eat = clazz.getMethod(methodName);

        //5. 执行指定对象
        eat.invoke(obj);
    }
}


className=com.xxyl.pojo.Student
methodName=sleep
           

注解

/*
    案例: 注解相关概述讲解  入门

    注解简介:
        概述:
            也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。
            它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
        作用:
            编写文档:
                通过代码里标识的注解生成文档【例如,生成文档doc文档】
            代码分析:
                通过代码里标识的注解对代码进行分析【例如,注解的反射】
                重点: 因为在框架中通常会采用注解的方式来代替配置文件,再通过反射获取注解中配置的信息.
            编译检查:
                通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override】
        常用的注解:
            @Deprecated         标记 该成员 已过时.
            @SuppressWarning    压制警告, 可以理解为: 就是忽略警告.
            @Override           表示方法重写.
*/
@SuppressWarnings("all")
public class Demo01 {
    public static void main(String[] args) {
        System.out.println(new Date().toLocaleString());  //2020-11-16 19:54:49

        show();
    }

    //自定义的show()方法
    @Deprecated
    private static void show() {
        System.out.println("开始学习注解啦!!!");
    }
}

           
/*
    案例: 演示自定义注解的定义.

    自定义注解简介:
        概述:
            所谓的注解就是一个 继承了 Annotation 接口的子接口.
        格式1:
            public interface 注解名 extends Annotation {
            }
        格式2:  推荐
            public @interface 注解名 {
                public 数据类型 变量名() default 默认值;
                public 数据类型 变量名() default 默认值;
                public 数据类型 变量名() default 默认值;
            }
        细节:
            1. 注解中所有字段(成员变量)的默认修饰符都是: public, 所以可以省略不写.
            2. 注解中的字段是可以有默认值的, 即: default 默认值,  它也可以省略不写.
            3. 注解中字段的数据类型可以是:
                A. 所有的基本类型.
                B. String类型, Class类型, 枚举类型, 注解类型.
                C. 以上所有类型的数组形式.
 */
public @interface MyselfAnnotion {
    //姓名
    String name();
    //年龄
    int age() default  23;
    //测试 注解的成员变量的类型是 Class类型
    Class cl() default String.class;
    //测试枚举类型,  枚举: 简单理解: 统一规范.
    Gender gender() default Gender.男;
    //作者
    String[] authors() default {"阿福", "阿福哥哥","大帅哥"};
}
           
/*
    案例: 演示自定义注解的定义和使用, 及注解解析.

    需求:
        1. 自定义注解Book, 该注解有三个属性, 分别表示: 书名, 价格, 作者.
        2. 自定义类BookShelf, 表示书架, 该类中有一个showBook()方法, 表示用来展示图书.
        3. 在BookShelf类 和 showBook()方法上, 分别定义Book注解, 用来对它们的功能进行增强.
        4. 在测试类中, 通过反射的技术, 来读取 BookShelf类 和 showBook()方法上的注解信息.  (即: 注解解析)

    注解解析:
        概述:
            所谓的注解解析就是通过Java代码来读取类上, 方法上, 变量上等的注解信息的操作.
        和注解解析相关的成员方法:  以下都是 Class类中的成员方法.
            boolean isAnnotationPresent(Class annotationClass);
                判断当前对象是否有指定的注解,有则返回true,否则返回false。
            T getAnnotation(Class<T> annotationClass);
                获得当前对象上指定的注解对象。

            Annotation[] getAnnotations();
                获得当前对象及其从父类上继承的所有的注解对象。
            Annotation[] getDeclaredAnnotations();
                获得当前对象上所有的注解对象,不包括父类的。


    细节(记忆):
        1. 使用注解时, 如果注解中的属性没有默认值, 则我们必须手动给值.
        2. 如果注解的属性有默认值, 则我们定义注解时, 可以选择不给该属性赋值.
        3. 当注解只有一个属性, 且属性名叫value的时候, 在使用注解时, 可以省略属性名.
           不管该属性是否是数组类型, 都满足上述的这一点.
        4. 如果一个注解要想进行注解解析, 则该注解必须定义元注解.
        5. 常用的元注解主要是: @Retention, @Target
            @Target:    指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
            @Retention: 指明此注解的生命周期是什么, 即在什么实机(编译, 测试, 运行)有效.

 */
public class TestBook {
    public static void main(String[] args) throws Exception {
       // method01();

        //需求2: 读取BookShelf#showBook()方法上的注解信息.
        //1. 获取BookShelf类的字节码文件对象.
        Class<BookShelf> cl = BookShelf.class;
        //2. 获取BookShelf#showBook()方法对象.
        Method showBook = cl.getMethod("showBook");
        //3. 判断BookShelf类上是否有@Book注解.
        if (cl.isAnnotationPresent(Book.class)){
            //4. 如果有就获取该注解对象.
            Book book = showBook.getAnnotation(Book.class);
            //5. 从注解对象中, 获取该注解对象的各个属性值, 并打印.
            System.out.println(book.name());
            System.out.println(book.price());
            System.out.println(Arrays.toString(book.authors()));
        }
    }

    public static void method01() {
        //需求1: 读取BookShelf类上的@Book注解信息, 并打印.
        //1. 获取BookShelf类的字节码文件对象.
        Class<BookShelf> cl = BookShelf.class;
        //2. 判断BookShelf类上是否有@Book注解.
        if (cl.isAnnotationPresent(Book.class)){
            //3. 如果有就获取该注解对象.
            Book book = cl.getAnnotation(Book.class);
            //4. 从注解对象中, 获取该注解对象的各个属性值, 并打印.
            System.out.println(book.name());
            System.out.println(book.price());
            System.out.println(Arrays.toString(book.authors()));
        }
    }
}


/**
 * 自定义注解  书
 */
//元注解  必须写
@Target({ElementType.TYPE,ElementType.METHOD})  // //表示该注解只能被定义到: 类上, 方法上
@Retention(RetentionPolicy.RUNTIME)  表示该注解在什么实机有效. Source: 源码阶段有效, Class: 源码, 字节码时机有效. Runtime: 源码, 字节码, 运行时都有效.
public @interface Book {
    //书名
    String name();
    //价格
    double price() default 33.3;
    //作者
    String[] authors();
}


//自定义类 表示书架
@Book(name = "水浒传",authors = {"武松","林冲"})
public class BookShelf {

    //该类中有一个showBook()方法  用来展示图书
    @Book(name = "三国演义", price = 55.0, authors = {"诸葛亮","赵云","庞统"})
    public void showBook(){
    }
}