单元测试:Junit框架、反射、注解、动态代理

单元测试:Junit框架

单元测试

  • 单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性

目前测试方法的方式的和存在的问题

方式

  • 只有一个main方法,如果一个方法的测试失败了,其他方法测试会受到影响

问题:

  • ①无法得到测试的结果报告,需要程序员自己去观察测试是否成功

  • ②无法实现自动化测试

测试分类 :

  • 1、黑盒测试 :不需要写代码给输入值,看程序是否能够输出期望的值

  • 2、白盒测试:需要写代码的。关注程序具体的执行流程(Junit使用:白盒测试)

Junit单元测试框架

  • JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试
    (开源就是开放程序源代码。就是把程序源代码发放出来,让程序的用户可以获得)

  • 此外,几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试,JUnit目前最新版本是5

JUnit优点

  • JUnit可以灵活的选择执行哪些测试方法,也可以一键执行全部测试方法

  • Junit可以生成全部方法的测试报告,如果测试良好则是绿色;如果测试失败则是红色

  • 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试


单元测试快速入门

需求:使用单元测试进行业务方法的预期结果、正确性测试

步骤分析

①将JUnit的jar包导入到项目中

  • IDEA通常整合好了Junit框架,一般不需要导入

  • 如果IDEA没有整合好,需要自己手工导入如下2个JUnit的jar包到模块
    链接:JUnit4框架jar包

②编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法

③在测试方法上使用 @Test注解 :标注该方法是一个测试方法

④在测试方法中完成被测试方法的预期正确性测试

⑤选中测试方法,选择“JUnit运行” ,如果测试良好则是绿色,如果测试失败,则是红色

先给大家一个在下面的展示代码中可能出现的JUnit中的API(断言操作:Assert.assertEquals(提醒,期望的结果,运算的结果);

方法名称说明
public static void assertEquals(String message, Object expected, Object actual)参数二:期待值(即我们所希望出现的结果);参数一:提醒(即如果结果和我们期待的不同,进行一个错误提醒);参数三:实际值

代码展示

我们首先要提供一些方法便于测试

public class ServeDemo {
    //用于对有返回值方法的测试
    public String loginService(String LoginName,String passWord){
        if (LoginName.equals("danshengou")&&passWord.equals("123456")){
            return "登陆成功";
        }else {
            return "登陆失败";
        }
    }
    //用于对静态方法的测试
    public static void singleDog1(){
        System.out.println("单身好快乐啊~~~");
    }
    //用于对实例方法的测试
    public void singleDog2(){
        //设置错误便于观察
        System.out.println(10/0);
        System.out.println("我要脱单!!!");
    }
}

方法测试

public class TestDemo {
    /**
     对有返回值方法的测试
     */
    @Test
    public void testLoginService(){
        ServeDemo sd=new ServeDemo();
        String s= sd.loginService("danshengou1","123456");
        //上面所提到的API(断言操作)
        Assert.assertEquals("你的功能可能出错","登陆成功",s);
    }

    /**
      对静态方法的测试
     */
    @Test
    public void testSingleDog1Test(){
       ServeDemo.singleDog1();
    }

    /**
     对实例方法的测试
     */
    @Test
    public void testSingleDog2Test(){
        ServeDemo sd=new ServeDemo();
        sd.singleDog2();
    }
}

JUnit测试某个方法和测试全部方法的方式(idea)

  • 测试某个方法直接右键该方法启动测试

  • 测试全部方法,可以选择类或者模块启动

根据两部分代码的比较,我们可以知道,第一个和第二个测试方法有问题,我们看结果
在这里插入图片描述
第一个方法问题是与我们预测的结果不同导致的,但代码方面并无异常,所以显示黄色打×

第二个方法正确,测试良好为绿色

第三个方法有异常,测试失败为红色

Junit常用注解(Junit 4.xxxx版本)

注解说明
@Test测试方法
@Before用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次
@After用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次
@BeforeClass用来静态修饰方法,该方法会在所有测试方法之前只执行一次
@AfterClass用来静态修饰方法,该方法会在所有测试方法之后只执行一次
  • 开始执行的方法:初始化资源

  • 执行完之后的方法:释放资源

代码展示

public class TestDemo {
    @BeforeClass
    public static void beforeClass(){
        System.out.println("BeforeClass执行一次");
    }
    @Before
    public void before(){
        System.out.println("Before执行一次");
    }
    @Test
    public void testSingleDog1Test(){
       ServeDemo.singleDog1();
    }
    @Test

    public void testSingleDog2(){
      ServeDemo sd=new ServeDemo();
      sd.singleDog2();
    }
    @After
    public void after(){
        System.out.println("After执行一次");
    }
    @AfterClass
    public static void aeforeClass(){
        System.out.println("AfterClass执行一次");
    }
}

结果

BeforeClass执行一次
Before执行一次
单身好快乐啊~~~
After执行一次
Before执行一次
我要脱单!!!
After执行一次
AfterClass执行一次

我们可以清晰地看出,BeforeClass和AfterClass分别在开头和最后只执行一次,Before和After在每个方法的前后执行一次

Junit常用注解(Junit 5.xxxx版本)

注解说明
@Test测试方法
@BeforeEach用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次
@AfterEach用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次
@BeforeAll用来静态修饰方法,该方法会在所有测试方法之前只执行一次
@AfterAll用来静态修饰方法,该方法会在所有测试方法之后只执行一次

反射

在此之前,我们需要了解一下代码在计算机中经历的三个阶段,模型图如下

在这里插入图片描述

实际上,反射位于Class类对象阶段

反射概述

  • 反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类全部成分

  • 在运行时可以直接得到这个类的构造器对象: Constructor

  • 在运行时,可以直接得到这个类的成员变量对象:Field

  • 在运行时,可以直接得到这个类的成员方法对象: Method

  • 这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制

反射的关键:

  • 反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分

HelloWorld.java ->javac -> HelloWorld.class
Class c = HelloWorld.class;

如上大概可以看出,反射可以绕过编译阶段,得到运行阶段的类对象

反射的基本作用

  • 反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分

反射的好处

  • 可以在程序运行过程中,操作这些对象(我们在使用idea的时候,输入String相关代码时会出先方法提示界面,实际上就是,我们定义了字符串,那么idea内部就会把字符串对应的字节码文件加载进内存,在内存中有一个Class类对象,Class类对象已经把所有的方法抽取出来封装进Method对象数组中,然后idea内部把数组中的数据展示出来就是我们所看到的方法提示了)

  • 可以解耦,提高程序的可扩展性

反射的关键

  • 反射的核心思想和关键就是:得到编译以后的class文件对象

反射的第一步:获取Class类的对象

Class类的都对象就是类对象,比如说学生类,学生类本身就是类对象,而类的对象是由学生类的构造器创建的对象=

简单来说,就是:
学生是类的对象,学生类是类的类对象,类的类对象是类

再简单理解就是:
类对象可以理解为人类,类的对象可以理解为人

获取类的三种方式模型图
在这里插入图片描述
获取Class类的对象的三种方式

  • 方式一: Class c= Class.forName(“全类名”);(将字节码文件加载进内存,返回class对象,为处于Source源代码阶段获取类对象的方法)
    在这里插入图片描述
    比如说,我想要在ObjectDemo类中获取Demo包下的类对象Student

代码展示

public static void main(String[] args) throws Exception {
        Class c=Class.forName("Demo.Student");
        System.out.println(c);//class Demo.Student

    }

class Demo.Student 其实就是Student.class ,只是形式不同罢了,Demo.Student
这一坨为:包名+类名,也叫全限定名 或者 全包名

我们也可以通过JUnit运行

  @Test
   public void demo() throws Exception {
        Class c=Class.forName("Demo.Student");
        System.out.println(c);//class Demo.Student

   }
  • 方式二: Class c=类名.class(通过类名的属性class获取,第二阶段获取方法:Class 类对象阶段)

(其实我们平时new 的对象就是在该阶段完成的)

   Class c= Student.class;
   System.out.println(c);//class Demo.Student
  • 方式三: Class c=类的对象.getClass();(getclass()方法在Object类中定义着,多用于对象的获取字节码的方式,第三阶段获取方法:Runtime运行时阶段)
   Student s=new Student();
   Class c=s.getClass();
   System.out.println(c); //class Demo.Student

注意:反射拿到的是同一个对象,因为编译后的class文件只有一个。


使用反射技术获取构造器对象并使用

步骤操作模型
在这里插入图片描述
第一步已经提到过了,现在需要解决的是第二步:获取构造器对象,第二步解决后,第三步就简单了

使用反射技术获取构造器对象

  • 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象

  • Class类中用于获取构造器的方法

方法名称说明
Constructor<?>[ ] getConstructors()返回所有构造器对象的数组 (只能拿public的)
Constructor<?>[ ] getDeclaredConstructors()返回所有构造器对象的数组,存在就能拿到
Constructor< T > getConstructor(Class<?>… parameterTypes)返回单个构造器对象 (只能拿public的)
Constructor< T > getDeclaredConstructor(Class<?>… parameterTypes)返回单个构造器对象,存在就能拿到(参数:构造器参数的数据类型对应的类对象,无参不填写即可)

(以上方法也就是Declared有无的区别,其实就是有Declared的可以获取任何类型的构造器,没有Declard的只能获取公开的,后面再出现的情况也一样,我只说有Declared的方法)

Constructor类的一些API(方便我们对得到的构造器对象进行一些操作)

方法名称说明
public String getName()以字符串形式返回此构造函数的名称
public int getParameterCount()返回此对象表示的可执行文件的形式参数(无论是显式声明还是隐式声明或两者都不是)的数量

我先要先提供一个类,以便获取该类的构造器对象(有参私有,无参公开,便于测试)

public class Student {
    private String name;
    private double score;
    private static int number;
    public Student(){
    }
    private Student(String name, double score) {
        this.name = name;
        this.score = score;
    }
}

获取构造器对象

public class ObjectDemo {
    public static void main(String[] args)throws Exception{
     Class c=Student.class;
     //返回所有构造器对象的数组,存在就能拿到
     Constructor[] constructors=c.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            //以字符串形式返回此构造函数的名称(我们可以由此知道该构造器是那一个类的的构造器)
            System.out.println(constructor.getName());
            //查看构造器的特征,如:public Demo.Student()========>构造器参数数量(0)
            System.out.println(constructor+"========>"+constructor.getParameterCount());
            System.out.println("--------------------");
        }
     //返回单个构造器对象,存在就能拿到   
     Constructor constructor=c.getDeclaredConstructor();
        System.out.println(constructor.getName());
        System.out.println(constructor+"========>"+constructor.getParameterCount());
        System.out.println("----------------------");
     //返回单个构造器对象,存在就能拿到(取有参构造器对象的情况下,
     //要与有参构造器的的参数对应填入其相应参数类型的类对象) 
     //如:private Student(String name, double score)  
     Constructor constructor1=c.getDeclaredConstructor(String.class,double.class);
        System.out.println(constructor1.getName());
        System.out.println(constructor1+"========>"+constructor1.getParameterCount());
    }
}

使用反射技术使用获取的构造器对象

  • 获取构造器的作用依然是初始化一个对象返回

Constructor类中用于创建对象的方法

方法名称说明
T newlnstance(Object… initargs)根据指定的构造器创建对象(T就相当于Object,所以得到对象后,我们可以对其进行真实类型强转)【如果使用空参数构造方法创建对象,操作可以简化:类对象.newInstence】
public void setAccessible(boolean flag)设置为true,表示取消访问检查,进行暴力反射

首先我们要对上面Student类进行一些操作,方便对得到的构造器能进行一些操作(即写toString和对应getter和setter方法)

public class Student {
    private String name;
    private double score;
    public Student(){
    }
    private Student(String name, double score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

我们先对获取的公共无参的构造器操作,是没问题的

  public static void main(String[] args)throws Exception{
     Class c=Student.class;
     Constructor constructor=c.getDeclaredConstructor();
        Student s=(Student)constructor.newInstance();
        s.setName("单身狗1号");
        s.setScore(78);
        System.out.println(s);//Student{name='单身狗1号', score=78.0}
    }

但我们对我们设立的私有有参构造器进行以上类似操作时,会出现错误,我们虽然得到了私有的构造器,但不能对其操作,这是时候,我们就要对其进行暴力反射public void setAccessible(boolean flag):就是用该方法进行操作

  public static void main(String[] args)throws Exception{
     Class c=Student.class;
//     Constructor constructor=c.getDeclaredConstructor();
//        Student s=(Student)constructor.newInstance();
//        s.setName("单身狗1号");
//        s.setScore(78);
//        System.out.println(s);
//        System.out.println("----------------------");
        Constructor constructor1=c.getDeclaredConstructor(String.class,double.class);
        //暴力反射
        constructor1.setAccessible(true);
        Student s1=(Student)constructor1.newInstance("单身狗2号",80);
        System.out.println(s1);//Student{name='单身狗2号', score=80.0}
    }

如上我们可以知道,如果是非public的构造器,需要打开权限(暴力反射),然后再创建对象

  • 这就体现了:反射可以破坏封装性,私有的也可以执行了

使用反射技术获取成员变量对象并使用

步骤操作模型
在这里插入图片描述
使用反射技术获取成员变量对象

  • 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象

Class类中用于获取成员变量的方法

方法名称说明
Field[ ] getFields()返回所有成员变量对象的数组 (只能拿public的)
Field[ ] getDeclaredFields()返回所有成员变量对象的数组,存在就能拿到
Field getField(String name)返回单个成员变量对象 (只能拿public的)
Field getDeclaredField(String name)返回单个成员变量对象,存在就能拿到(参数:成员变量名)

Field类的一些API(方便我们对得到的成员变量对象进行一些操作)

方法名称说明
public String getName()返回此Field对象表示的字段的名称
public Class<?> getType()返回一个Class对象,该对象标识此对象表示的字段的声明类型 Field

常量、静态成员变量和实例成员变量都可以得到,我们在学生类中定义相关变量,以便观察

public class Student {
    private String name;
    public double score;
    private static int age;
    public static final String className="数据221";
    }

获取成员变量对象

    public static void main(String[] args)throws Exception{
     Class c=Student.class;
        //得到所有成员变量对象的数组,存在就能拿到
        Field[] fields=c.getDeclaredFields();
        for (Field field : fields) {
            //得到成员变量的名称
            System.out.println(field.getName());
            //查看成员变量的特征,如:
           //private java.lang.String Demo.Student.name 
           //=======>class java.lang.String
            System.out.println(field+"=======>"+field.getType());
            System.out.println("---------------");
        }
        //获取实例私有成员变量对象
        Field field=c.getDeclaredField("name");
        System.out.println(field.getName());
        System.out.println(field+"=======>"+field.getType());
        System.out.println("---------------");
        //获取实例公共成员变量对象
        Field field1=c.getDeclaredField("score");
        System.out.println(field1.getName());
        System.out.println(field1+"=======>"+field.getType());
        System.out.println("---------------");
        //获取静态成员变量对象
        Field field2=c.getDeclaredField("age");
        System.out.println(field2.getName());
        System.out.println(field2+"=======>"+field.getType());
        System.out.println("---------------");
        //获取常量对象
        Field field3=c.getDeclaredField("className");
        System.out.println(field3.getName());
        System.out.println(field3+"=======>"+field.getType());
        System.out.println("---------------");
    }

使用反射技术使用获取的成员变量对象

  • 获取成员变量的作用依然是在某个对象中取值、赋值
方法名称说明
void set(Object obj, Object value)赋值(参数一:我们所要赋值的成员变量的类的对象;参数二:输入我们想要输入的值)
Object get(Object obj)获取值(参数:我们所要取值的成员变量的类的对象)
public void setAccessible(boolean flag)设置为true,表示取消访问检查,进行暴力反射
public static void main(String[] args)throws Exception{
     Class c=Student.class;
     Student s=new Student();
        Field field=c.getDeclaredField("name");
        //暴力反射
        field.setAccessible(true);
        //赋值
        field.set(s,"单身狗");
        //取值
        System.out.println(field.get(s));
        System.out.println("---------------");
        
        Field field1=c.getDeclaredField("score");
        //赋值
        field1.set(s,78);
        //取值
        System.out.println(field1.get(s));
        System.out.println("---------------");

        Field field2=c.getDeclaredField("age");
        //暴力反射
        field2.setAccessible(true);
        //赋值
        field2.set(s,18);
        //取值
        System.out.println(field2.get(s));
        System.out.println("---------------");

        Field field3=c.getDeclaredField("className");
        //常量只能被赋值一次
        System.out.println(field3.get(s));
        System.out.println("---------------");
    }

使用反射技术获取方法对象并使用

步骤操作模型
在这里插入图片描述
使用反射技术获取方法对象

  • 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象

Class类中用于获取成员方法的方法

方法名称说明
Method[ ] getMethods()返回所有成员方法对象的数组(只能拿public的)
Method[ ] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象 (只能拿public的)
Method getDeclaredMethod(String name,Class<?>… parameterTypes)返回单个成员方法对象,存在就能拿到(参数:方法名+方法中参数的数据类型对应的类对象,无参只需填写方法名)

Method类的一些API(方便我们对得到的方法对象进行一些操作)

方法名称说明
public String getName()返回此Method 对象表示的方法的名称,作为String
public Class<?> getReturnType()返回一个Class对象,该对象表示此对象所表示的方法的正式返回类型Method
public int getParameterCount()返回此对象表示的可执行文件的形式参数(无论是显式声明还是隐式声明或两者都不是)的数量

首先,我们要先为Student类声明一些方法便于操作

public class Student {
    public static void singleDog(){
        System.out.println("我是单身狗~~~~");
    }
    private static void single(String name){
        System.out.println(name+"独自一人~~");
    }
    public static int dog(String name,int age){
        System.out.println(name+"是一条狗");
        return age+10;
    }
}

获取方法对象

 public static void main(String[] args) throws Exception{
        Class c=Student.class;
        //得到所有成员方法对象的数组,存在就能拿到
        Method[] methods=c.getDeclaredMethods();
        for (Method method : methods) {
            //查看方法的特征
            System.out.println(method);
            //如:single===>1===>void
            System.out.println(method.getName()+"===>"+method.getParameterCount()+"===>"+method.getReturnType());
            System.out.println("----------------");
        }
        //方法名+方法中参数的数据类型对应的类对象,取有参方法的情况下,
        //要与有参方法的的参数对应填入其相应参数类型的类对象)
        Method method=c.getDeclaredMethod("single",String.class);
        System.out.println(method);
        System.out.println(method.getName()+"===>"+method.getParameterCount()+"===>"+method.getReturnType());
        System.out.println("----------------");

        Method method1=c.getDeclaredMethod("singleDog");
        System.out.println(method1);
        System.out.println(method1.getName()+"===>"+method1.getParameterCount()+"===>"+method1.getReturnType());
        System.out.println("----------------");

        Method method2=c.getDeclaredMethod("dog",String.class,int.class);
        System.out.println(method2);
        System.out.println(method2.getName()+"===>"+method2.getParameterCount()+"===>"+method2.getReturnType());
        System.out.println("----------------");
    }

使用反射技术使用获取的方法对象

  • 获取成员方法的作用依然是在某个对象中进行执行此方法

Method类中用于触发执行的方法

方法名称说明
Object invoke(Object obj,Object… args)运行方法参数一:用obj对象调用该方法;参数二:调用方法的传递的参数(如果没有就不写);返回值:方法的返回值 (如果没有就不写)
public void setAccessible(boolean flag)设置为true,表示取消访问检查,进行暴力反射
 public static void main(String[] args) throws Exception{
        Class c=Student.class;
        Student s=new Student();
        Method method=c.getDeclaredMethod("single",String.class);
        //暴力反射
        method.setAccessible(true);
        Object obj=method.invoke(s,"单身");
        System.out.println(obj); //null
        System.out.println("----------------");

        Method method1=c.getDeclaredMethod("singleDog");
        Object obj1=method1.invoke(s);
        System.out.println(obj1); //null
        System.out.println("----------------");

        Method method2=c.getDeclaredMethod("dog",String.class,int.class);
        Object obj2=method2.invoke(s,"单身",8);
        System.out.println(obj2); //18
        System.out.println("----------------");
    }

反射的作用

绕过编译阶段为集合添加数据

  • 反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的
    例如:

ArrayList< Integer > list = new ArrayList<>();
list.add(100);
// list.add(“黑马"); //报错
list.add(99);

  • 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了

其实以上这些是因为:这是Java的历史包袱,Java1.5之前是没有泛型的,1.5之后才有泛型,为了兼容之前的1.5版本之前的代码,Java的泛型不得已只能用类型消除(type erasure)的方式实现

我们可以进行一个判断:

我们定义两个泛型不同的ArrayList集合,然后的到这两个集合对应的类对象,判断是否相同

    ArrayList<String> arrayList=new ArrayList<>();
    ArrayList<Integer> arrayList1=new ArrayList<>();
    System.out.println(arrayList.getClass()==arrayList1.getClass());//true

然后,我们尝试利用反射技术给泛型为Integer的集合添加其他类型的数据

public static void main(String[] args) throws Exception {
        ArrayList<Integer> arrayList=new ArrayList<>();
        Class c=arrayList.getClass();
        Method method=c.getDeclaredMethod("add",Object.class);
        method.invoke(arrayList,10);
        method.invoke(arrayList,20.0);
        method.invoke(arrayList,'a');
        method.invoke(arrayList,"单身狗");
        System.out.println(arrayList);//结果:[10, 20.0, a, 单身狗]
    }

经上述可知,是可行的,其实我们还有另一种添加其他类型数据的方法
如下

   public static void main(String[] args){
        ArrayList<Integer> arrayList=new ArrayList<>();
        ArrayList arrayList1=arrayList;
        arrayList1.add(10);
        arrayList1.add(20.0);
        arrayList1.add('a');
        arrayList1.add("单身狗");
        System.out.println(arrayList);//[10, 20.0, a, 单身狗]
    }

反射做通用框架

需求:给你任意一个对象,在不清楚对象字段的情况可以,可以把对象的字段名称和对应值存储到文件中去
【字段 (Field),是 Java 编程语言中类的一个成员,主要用来存储对象的状态(如同某些编程语言中的变量),所以有时也可称为成员字段或成员变量】

例如:如下数据

在这里插入图片描述
步骤分析

①定义一个方法,可以接收任意类的对象

②每次收到一个对象后,需要解析这个对象的全部成员变量名称

③使用反射获取对象的Class类对象,然后获取全部成员变量信息

④遍历成员变量信息,然后提取本成员变量在对象中的具体值

⑤存入成员变量名称和值到文件中去即可

下面代码牵涉到的一个Class类的API

方法名称说明
public String getSimpleName()返回源代码中给定的基础类的简单名称(即类名)
public class ObjectDemo {
    public static void main(String[] args)  {
        Student s = new Student("柳岩",4 ,'女',167.5 ,"女星");
        Teacher t = new Teacher("波妞",6000);
        Serve.receptionFile(s);
        Serve.receptionFile(t);
    }
}

class Serve{
    public static void receptionFile(Object obj) {
        try {
            PrintStream ps=new PrintStream(new FileOutputStream("Object/abc.txt",true));
            Class c=obj.getClass();
            Field[] fields=c.getDeclaredFields();
            ps.println("==========="+c.getSimpleName()+"==========");
            for (Field field : fields) {
                field.setAccessible(true);
                ps.println(field.getName()+"="+field.get(obj));
            }
        } catch (Exception e) {
             e.printStackTrace();

        }
    }
}

反射作用小总结

  • 可以在运行时得到一个类的全部成分然后操作

  • 可以破坏封装性(很突出)

  • 也可以破坏泛型的约束性 (很突出)

  • 更重要的用途是适合:做Java高级框架


注解

注解概述

  • Java注解(Annotation) 又称 Java 标注,是JDK5.0引入的一种注释机制

  • Java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注

  • 注解不是程序的一部分,可以理解为注解就是一个标签

  • 以后大多数时候,我们会使用注解,而不是自定义注解

JDK中预定义的一些注解

  • @Override:检测被该注解标注的方法是否是继承自父类(接口)的

  • @Deprecated:该注解标注的内容,表示已过时

  • @Suppresswarnings: 压制警告【一般传递参数“all”,放在类上,如:@suppresswarnings(“all”)】

注解的作用

  • 对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理由业务需求来决定

  • 例如:JUnit框架中,标记了注解 @Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行

注解给谁用?

  • 1、编译器
  • 2、给解析程序用

自定义注解

自定义注解就是自己做一个注解来使用

格式
在这里插入图片描述
(public为默认值,可以省略)

注解本质上就是一个接口,该接口默认继承Annotation接口(一个注解反编译后如下)

  • public interface 注解名 extends java,lang.annotation.Annotation {}

属性: 接口中的抽象方法要求

属性的返回值类型有下列取值(不包括类)

  • 基本数据类型

  • String

  • 枚举(类型为枚举类型,调用时为:枚举名.常量)

  • 注解(类型写注解名,调用时为:定义时的变量名=@注解名)

  • 以上类型的数组

定义了属性,在使用时需要给属性赋值

  • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
  • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
  • 数组赋值时,值使用 “{}” 包裹。如果数组中只有一个值,则 “{}” 省略

我们先定义一个注解

public @interface AnnotationDemo {
     String name()default "单身狗";
}

我们已经写了默认值,这样在其他地方运用注解就不需要再赋值了,如果要改名,也是可以的

@AnnotationDemo
public class ObjectDemo {
    @AnnotationDemo(name = "单身狗1")
    public static void main(
            @AnnotationDemo
            String[] args) {
        @AnnotationDemo
     String name="dog";
    }
}

此时的注解我们还没有约束,因此几乎可以用在任何地方

但如果我们没有给初始值,那么运用每次运用注解时都需要输入值(不同也行,只要符合定义的数据类型即可)

@AnnotationDemo(name = "单身狗")
public class ObjectDemo {
    @AnnotationDemo(name = "单身狗")
    public static void main(
            @AnnotationDemo(name = "单身狗")
            String[] args) {
        @AnnotationDemo(name = "单身狗")
     String name="dog";
    }
}

特殊属性

  • value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写

  • 但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的

如下

public @interface AnnotationDemo {
      String name()default "单身狗";
      String value();
}

然后,它的运用地方

@AnnotationDemo("你好")
public class ObjectDemo {
    @AnnotationDemo("你好")
    public static void main(
            @AnnotationDemo("你好")
            String[] args) {
        @AnnotationDemo("你好")
        String name="dog";
    }
}

我们可以发现,括号内是不需要写 “value=” 的,但当我们把name
的默认值去掉后

public @interface AnnotationDemo {
      String name();
      String value();
}

是必须要写 “value=”

@AnnotationDemo(value = "你好",name ="单身狗")
public class ObjectDemo {
    @AnnotationDemo(value = "你好",name ="单身狗")
    public static void main(
            @AnnotationDemo(value = "你好",name ="单身狗")
            String[] args) {
        @AnnotationDemo(value = "你好",name ="单身狗")
        String name="dog";
    }
}

元注解

元注解就是注解(动词)注解(名词)的注解(名词)

常用的元注解:

  • @Target: 约束自定义注解可以标记的范围(即自定义注解可以在哪里使用)

  • @Retention: 用来约束自定义注解的存活范围(生命周期)(如:@Retention(RetentionPolicy.RUNTIME): 当前被描述的注解,会保留到class字节码文件中,并被JVM读取到)

  • @Documented:描述注解是否被抽取到api文档中(我们可以在文件所在的文档中启动终端,然后输入 javadoc 对应的class文件,就会生成API文档了)

  • @Inherited: 描述注解是否被子类继承(注解标记此后,继承被该注解注解的类的子类也包含了该注解)

简单展示一下

@Target({ElementType.METHOD})//元注解,括号中为限制范围(此为限制注解只用用于方法)
public @interface AnnotationDemo {
}

@Target中可使用的值定义在ElementType枚举类中,常用值如下

范围
TYPE类,接口
FIELD成员变量
METHOD成员方法
PARAMETER方法参数
CONSTRUCTOR构造器
LOCAL_VARIABLE局部变量

@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下

范围
SOURCE注解只作用在源码阶段,生成的字节码文件中不存在
CLASS注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
RUNTIME注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)

两者结合格式

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AnnotationDemo {
}

注解的解析

注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容

与注解解析相关的接口

  • Annotation:注解的顶级接口,注解都是Annotation类型的对象

  • AnnotatedElement:该接口定义了与注解解析相关的解析方法

方法说明
Annotation[ ] getDeclaredAnnotations()获得当前对象上使用的所有注解,返回注解数组
T getDeclaredAnnotation(Class< T > annotationClass)根据注解类型获得对应注解对象 (其实就是在内存中生成了一个该注解接口的子类实现对象)(上述获取数组的同理)
boolean isAnnotationPresent(Class< Annotation > annotationClass)判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false

所有的类成分Class,Method,Field,Constructor,都实现了AnnotatedElement接口,他们都拥有解析注解的能力

解析注解的技巧:注解在哪个成分上,我们就先拿哪个成分对象

  • 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解

  • 比如注解作用在类上,则要该类的Class对象,再来拿上面的注解

  • 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解

我们用一个案例来理解这些技巧和方法的使用

需求:注解解析的案例

步骤分析

①定义注解Book,要求如下:

  • 包含属性: String value() 书名

  • 包含属性: double price() 价格,默认值为 100

  • 包含属性: String[ ] authors()多位作者

  • 限制注解使用的位置:类和成员方法上

  • 指定注解的有效范围:RUNTIME

② 定义BookStore类,在类和成员方法上使用Book注解

③定义AnnotationDemo测试类获取Book注解上的数据

代码展示

注解Book代码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface Book {
    String value();
    double price()default 100;
    String[] authors();
}

BookStore类代码

@Book(value = "《我真没想重生啊》",price = 30,authors = {"柳暗花又明"})
public class BookStore {
    @Book(value = "《我真没想重生啊》漫画版",price = 30,authors = {"噼咔噼"})
    public static void buyBook(){
        System.out.println("恭喜你,和渣男陈汉升约会吧");
    }
}

AnnotationDemo测试类代码

public class AnnotationDemo {
    @Test
    public void testBookStore() throws Exception {
       //解析类上的注释
        Class c = BookStore.class;
        if (c.isAnnotationPresent(Book.class)) {
            Book book = (Book) c.getDeclaredAnnotation(Book.class);
            System.out.println(book.value());
            System.out.println(book.price());
            System.out.println(Arrays.toString(book.authors()));
        }
        //解析方法上的注释
        Method m = c.getDeclaredMethod("buyBook");
        if (m.isAnnotationPresent(Book.class)) {
            Book book = m.getDeclaredAnnotation(Book.class);
            System.out.println(book.value());
            System.out.println(book.price());
            System.out.println(Arrays.toString(book.authors()));
        }
    }
}

模拟Junit框架

需求:定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行

步骤分析

  • ①定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在

  • 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行

由于我们模拟的没有运行键,我们用main方法模拟

代码展示

MyTest注解代码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}

测试代码

public class Test {
    @MyTest
    public void test(){
        System.out.println("====test=====");
    }
    public void test1(){
        System.out.println("====test1=====");
    }

    public void test2(){
        System.out.println("====test2=====");
    }
    @MyTest
    public static void main(String[] args) throws Exception {
        Test test=new Test();
        Class c= Test.class;
        Method[] methods=c.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyTest.class)) {
                method.invoke(test);
            }
        }
    }
}

动态代理

代理概述

  • 代理指:某些场景下对象会找一个代理对象,来辅助自己完成一些工作,如:歌星(经济人),买房的人(房产中介)

  • 代理主要是对对象的行为额外做一些辅助操作

创建代理对象

  • Java中代理的代表类是:java.lang.reflect.Proxy

  • Proxy提供了一个静态方法,用于为对象产生一个代理对象返回

创建代理对象API

方法名称说明
public static Object newProxyInstance(ClassLoader loader, Class<?>[ ] interfaces, InvocationHandler h)返回指定接口的代理实例,该接口将方法调用分派给指定的调用处理程序
  • 参数一:定义代理类的类加载器

  • 参数二:代理类要实现的接口列表

  • 参数三:将方法调用分派到的处理程序。 (代理对象的核心处理程序)

Class类中的一个API

方法名称说明
public ClassLoader getClassLoader()返回类的类加载器(参数一)
public Class<?>[ ] getInterfaces()Class返回由该对象表示的类或接口直接实现的接口(参数二)

参数三直接new然后回车有idea帮忙建

我们一般用方法封装生成代理对象的方法,这样,方便被代理对象获取代理对象

获取代理对象

在Java中实现动态代理

  • 必须存在接口

  • 被代理对象需要实现接口

  • 使用Proxy类提供的方法,得到目标对象的代理对象

步骤分析

①我们需要一个被代理人,比如一个明星的经纪人,这个明星会唱歌跳舞,这个经纪人,也就是代理人要知道这个明星会唱歌跳舞

②定义一个Skill接口,写入唱歌跳舞抽象方法(经纪人和明星都要实现的事情一般定义为接口)

③定义明星Star类,实现Skill接口

④获取代理人对象

代码展示

Skill接口

public interface Skill {
    void dance();
    void sing();
}

Star类,实现Skill接口

public class Star implements Skill{
    private String name;
    private int age;
    private char sex;
    public Star(){
    }

    public Star(String name, int age, char sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    @Override
    public void dance() {
        System.out.println(name+"跳得很棒");
    }

    @Override
    public void sing() {
        System.out.println(name+"唱歌真好听~~");
    }
}

实践代码

public class ProxyStarDemo {
    public static void main(String[] args) {
        Star s=new Star("咻咻满",25,'女');
        Skill skill=getProxy(s);
        skill.dance();
        skill.sing();
    }
    //获取代理人
    //我们声明的Skill类型的返回值时因为我们得到的代理对象也实现类Skill接口,
    //它是由内部实现的,我们不需要过多考虑
    public static Skill getProxy(Object obj){
     //我们要实现方法的调用,必须要强转一下
        return (Skill) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
            @Override
                               //代理类        待调用的方法对象 方法中的参数
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("经纪人收到钱了");
                Object object=method.invoke(obj,args);
                System.out.println("经纪人收完尾款了");
                return object;
            }
        });
    }
}

Java中如何生成代理,并指定代理干什么事模型图
在这里插入图片描述
结合代码的模型图
在这里插入图片描述
通过代理对象调用方法执行流程大概为

  • 先走向代理

  • 代理可以为方法额外做一些辅助工作

  • 开发真正触发对象的方法的执行

  • 回到代理中,由代理负责返回结果给方法的调用者

动态代理的优点

  • 可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用

  • 简化了编程工作、提高了开发效率,同时提高了软件系统的可扩展性

  • 可以为被代理对象的所有方法做代理

  • 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理

我们想要实现 支持任意接口类型的实现类对象做代理 这一功能的话可以对上述代码进行如下操作

public static<T> T getProxy(T obj){
        return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("经纪人收到钱了");
                Object object=method.invoke(obj,args);
                System.out.println("经纪人收完尾款了");
                return object;
            }
        });
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值