单元测试: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.classClass 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;
}
});
}
标签:对象,单元测试,System,public,println,注解,Junit,out
From: https://blog.51cto.com/u_16078425/8681733