十五、Java高级
单元测试、反射、注解、动态代理。
1、单元测试
定义:就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试。
1.1 Junit单元测试框架
可以用来对方 法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了Junit框架,比如IDEA)
优点:可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键 完成对全部方法的自动化测试,且各自独立。不需要程序员去分析测试的结果,会自动生成测试报告出来。
1.2 测试案例
某个系统,有多个业务方法,请使用Junit单元测试框架, 编写测试代码,完成对这些方法的正确性测试。
具体测试步骤:
- 将Junit框架的jar包导 入到项目中(注意: IDEA集成了Junit框架,不需要我们自己手工导入了)
- 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
- 测试方法 上必须声明@Test注解,然后在测试方法中,编写代码调用被测试的业务方法进行测试。
- 开始测试: 选中测试方法,右键选择"JUnit运行",如果测试通过则是绿色;如果测试失败,则是红色。
1、只测试某个方法:直接点击该方法的左侧运行按钮运行即可
2、测试该测试类的所以方法:点击该参数类的左侧运行按钮运行即可
3、要测试该项目的所以测试类:直接右击该项目名------>点击 run All Tests
java
//工具类
public class Function {
//获取字符串长度
public static int getStringLenght(String str){
if(str==null){
return 0;
}
return str.length();
}
//获取字符串最后一个字母的索引
public static int getStringMaxIndex(String str){
if(str==null){
return -1;
}
return str.length();
}
}
java
//工具类的测试类
//点击在测试类左侧的运行按钮就测试所以的方法
public class FunctionTest {
@Test //测试类(点击左侧的运行按钮即可运行所对应的方法)
//保证方法是(public、无返回值、无形参)
public void getStringLenghtTest(){
//直接调用要测试的方法(这里写的是静态方法,直接通过类名调取)
int len= Function.getStringLenght("hello");
int len2=Function.getStringLenght(null);
System.out.println(len);
System.out.println(len2);
}
@Test
public void getStringMaxIndexTest(){
int index1=Function.getStringMaxIndex("hello");
int index2=Function.getStringMaxIndex(null);
System.out.println(index1);
System.out.println(index2); //会发现测试没有报错,但是结果可能有问题。
//可以使用结果断言,就是可以用自己的预测结果和方法得出的结果进行比较,看看是不是一样的。
//参数(有误时的提示信息,自己预测结果,方法得出的结果)
Assert.assertEquals("测试结果有误:",4,index1);
}
}
Junit单元测试常见注解 | 说明 |
---|---|
@Test | 测试类中的方法必须用它修饰才能成为测试方法,才能启动执行 |
@Before Junit5改名为 ( @BeforeEach) | 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次。 |
@After Junit5改名为 (@BeforeEach) | 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次。 |
@BeforeClass Junit5改名为(@BeforeAll ) | 用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次。 |
@AfterClass Junit5改名为(@AfterAll) | 用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次。 |
在测试方法执行前执行的方法,常用于:初始化资源。
在测试方法执行完后再执行的方法,常用于:释放资源。
2、反射
反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)。
作用:反射主要是用来做框架的
学习获取类的信息、操作它们,实现步骤:
- 反射第一步:加载类,获取类的字节码: Class对象
- 获取类的构造器:Constructor对象
- 获取类的成员变量:Field对象
- 获取类的成员方法::Method对象
2.1 反射第一步:加载类,获取类的字节码: Class对象
获取Class对象的三种方式:
- Class c1 =类名.class
- 调用Class提供方 法: public static Class forName(String package);
- Object提供的方法: public Class getClass(); Class c3 =对象.getClass();
java
public class Student {
private String name;
private String sex;
private int age;
}
java
//测试类
public class ReflectTest {
public static void main(String[] args) throws Exception {
//Class c1 =类名.class
Class c1=Student.class;
System.out.println(c1.getName()); //获取全类类名(包名加类名)
System.out.println(c1.getSimpleName()); //获取简单类名(类名)
//调用Class提供方 法: public static Class forName(String package);
Class c2=Class.forName("akc4.reflect.Student"); //参数为全类类名
System.out.println(c1==c2); //等到的都是同一个类(true)
//Object提供的方法: public Class getClass(); Class c3 =对象.getClass();
Student student=new Student();
Class c3=student.getClass();
System.out.println(c2==c3); //等到的都是同一个类(true)
}
}
2.2 获取类的构造器:Constructor对象
Class提供了从类中获取构造器的方法 | 说明 |
---|---|
Constructor<?> [ ] getConstructors() | 获取全部构造器(只能获取public修饰的) |
Constructor<?> [ ] getDeclaredConstructors() | 获取全部构造器(只要存在就能拿到) |
Constructor getConstructor(Class<?> ... parmeterTypes ) | 获取某个构造器(只能获取public修饰的) |
Constructor getDeclaredConstructor( Class<?> ... paramerTyoes ) | 获取某个构造器(只要存在就能拿到) |
java
//学生实体类
public class Student {
private String name;
private String sex;
private int age;
public Student() {
}
private Student(String name, String sex) {
this.name = name;
this.sex = sex;
}
public Student(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
}
java
//测试类
public class StudentTest {
@After //每个测试方法结束后都会执行
public void soutAfter(){
System.out.println("---------------");
}
//获取多少构造器
@Test
public void testGetConstructors(){
//1、获取到对应类的类Class对象
Class c1=Student.class;
//2、通过 getConstructors()方法获取到所有用public修饰的构造器
Constructor[] cons1=c1.getConstructors();
for (Constructor constructor : cons1) {
//获取构造器名和形参个数
System.out.println(constructor.getName()+"--->"+constructor.getParameterCount());
}
System.out.println("============");
//3、通过getDeclaredConstructors() 方法获取所有的构造器(一般用这个获取多个构造器)
Constructor[] cons2=c1.getDeclaredConstructors();
for (Constructor constructor : cons2) {
//获取构造器名和形参个数
System.out.println(constructor.getName()+"--->"+constructor.getParameterCount());
}
}
@Test
public void testGetDeclaredConstructor() throws Exception {
//1、获取对应类的Class
Class c2=Student.class;
//2、通过getConstructor(形参类型)方法获取public修饰的某个构造器
Constructor con1=c2.getConstructor(); //获取无参构造器(public修饰的)
//获取public修饰的形参为三个,且形参类型对应的构造器
Constructor cons2=c2.getConstructor(String.class,String.class,int.class);
//获取指定的任何一个构造器,不限修饰类型
Constructor cons3=c2.getDeclaredConstructor(String.class,String.class);
System.out.println(con1.getName()+" "+cons2.getName()+" "+cons3.getName());
}
}
获取类构造器的作用:依然是初始化对象返回
Constructor对象提供的方法 | 说明 |
---|---|
T newIntstance(Object...initargs) | 调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回 |
public void setAccessible( boolean flag ) | 设置为true,表示禁止检查访问控制,就是即使是私有的构造器也能访问(暴力反射) |
java
//学生实体类
public class Student {
private String name;
private String sex;
private int age;
public Student() {
System.out.println("无参构造器被调用了");
}
private Student(String name, String sex) {
this.name = name;
this.sex = sex;
}
public Student(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
java
//测试类
public class StudentTest {
@Test
public void testGetDeclaredConstructor() throws Exception {
//1、获取对应类的Class
Class c2=Student.class;
//2、通过getConstructor(形参类型)方法获取public修饰的某个构造器
Constructor con1=c2.getConstructor(); //获取无参构造器(public修饰的)
//通过调用无参构造器的newInstance方法来初始对象,并返回
Student student=(Student) con1.newInstance();
System.out.println(student);
//获取一个参数为2个的,且参数类型对应的任意一个构造器
Constructor cons3=c2.getDeclaredConstructor(String.class,String.class);
cons3.setAccessible(true); //暴力反射, 即使是private修饰的也能调用了
Student student1=(Student) cons3.newInstance("小明","男");
System.out.println(student1);
}
}
2.3 获取类的成员变量:Field对象
Class提供了从类中获取成员变量的方法
方法 | 说明 |
---|---|
public Field[ ] getFields() | 获取类id全部成员变量(只能获取public修饰的) |
public Field[ ] getDeclaredFields() | 获取类的全部成员变量(只要存在就能拿到) |
public Field getField(Sting name) | 获取类的某个成员变量(只能获取public修饰的) |
public Field getDeclaredField(String name) | 获取类的某个成员变量(只要存在就能拿到) |
获取到成员变量的作用:依然是赋值、取值。
方法 | 说明 |
---|---|
void set(Object obj , Object value) | 赋值 |
Object get( Object obj ) | 取值 |
public void setAccessible( boolean flag ) | 设置true ,表示禁止检查访问控制(暴力反射) |
java
//实体类
public class Student {
public String name;
public String id;
private String sex;
private int age;
public Student() {
}
}
java
//测试类
public class StudentTest {
@After //每个测试方法结束后都会执行
public void soutAfter(){
System.out.println("---------------");
}
@Test
public void testFields(){
//1、获取到对应类的Class对象
Class c=Student.class;
//2、获取到所有用public修饰的成员变量
Field[] fields=c.getFields();
//3、遍历拿到的成员变量
for (Field field : fields) {
//输出变量名和变量类型
System.out.println(field.getName()+"==>"+field.getType());
}
System.out.println("==========");
//2、获取全部成员变量
Field[] fields1=c.getDeclaredFields();
//3、遍历拿到的成员变量
for (Field field : fields1) {
//输出变量名和变量类型
System.out.println(field.getName()+"==>"+field.getType());
}
}
@Test
public void testField() throws Exception {
//1、获取到对应类的Class对象
Class c1=Student.class;
//2、获取public修饰的某个成员变量(成员变量名)
Field field= c1.getField("name");
System.out.println(field.getName()+"==>"+field.getType());
System.out.println("============");
//2、获取某个成员变量。不限修饰(成员变量名)
Field field1=c1.getDeclaredField("age");
System.out.println(field1.getName()+"==>"+field1.getType());
//3、为成员变量赋值
//3.1 创建一个对象
Student student=new Student();
//由于field1对应的age是私有的成员变量,所以要暴力反向
field1.setAccessible(true);
//赋值(要赋值的具体对象,符的值)
field1.set(student,20);
//获取值
int age=(int)field1.get(student);
System.out.println(age);
}
}
2.4 获取类的成员方法::Method对象
Class提供了从类中获取成员方法的API。
方法 | 说明 |
---|---|
Method[ ] getMethods() | 获取类的全部成员方法(只能获取public修饰的) |
Method[ ] getDeclaredMethods() | 获取类的全部成员方法(只要存在就能拿到) |
Method getMethod(String name, Class<?> ... parameterTypes) | 获取类的某个成员方法(只能获取public修饰的)参数:(方法名,参数类型,如:String.class) |
Method getDeclaredMethod(String name , Class<?> ... parameterType ) | 获取类的某个成员方法(只要存在就能拿到)参数:(方法名,参数类型,如:String.class) |
成员方法的作用:依然是执行
Method提供的方法 | 说明 |
---|---|
public Object invoke( Object obj , Object ... args ) | 触发某个对象的该方法执行 |
public void setAccessible( boolean flag ) | 设置true,表示禁止检查访问控制(暴力反射) |
java
//实体类
public class Student {
private String name;
private String sex;
private int age;
public Student() {
}
private String run(int num){
return "跑"+num+"圈操场!";
}
}
java
//测试类
public class StudentTest {
@Test
public void testMethod() throws Exception {
//1、创建一个类,对应的Class
Class c=Student.class;
//获取某个方法,不限修饰,参数为:要获取方法的方法名,形参列表的各个类型
Method method= c.getDeclaredMethod("run",int.class);
//2、由于run方法是私有的所以要暴力反向
method.setAccessible(true);
//3、获取某个方法,需要一个对应对象
Student student=new Student();
//4、调用该方法,并传入参数(要调用对象的对象名,调用方法的参数)
String str= (String) method.invoke(student,10);
System.out.println(str);
}
}
2.5 反射的运用场景
基本作用:可以得到一个类的全部成分然后操作。可以破坏封装性。
最重要的用途是:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能。
使用反射做一个简易版的框架
需求:对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去。
实现步骤:
①定义一个方法,可以接收任意对象。
②每收到一个对象后,使用反射获取该对象的Class对象,然后获取全部的成员变量。
③遍历成员变量,然后提取成员变量在该对象中的具体值。
④把成员变量名、和其值,写出到文件中去即可。
3、注解( Annotation )
就是Java代码里的特殊标记,比如: @Override、 @Test等,作用是:让其他程序根据注解信息来决定怎么执行该程序。
注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处。
3.1 自定义注解
java
//自定义注解格式
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}
java
//注解
public @interface MyTest {
//默认public可以不写
public String name();
//可以写默认值(写了,在赋值的时候可以不写)
int age() default 10;
String[] hobby();
}
java
//测试注解
@MyTest(name="小明",age=19,hobby={"唱","跳","篮球"})
public class AnnotationTest {
public static void main(String[] args) {
}
@MyTest(name="小红",hobby={"唱","跳","篮球"})
public static void run(){}
}
1、特殊属性名: value
如果注解中只有一个value属性,使用注解时, value名称可以不写!
2、注解的本质
注解本质是一个接口,Java中所有注解都是继承了Annotation接口的。
@注解(...):其实就是一个实现类对象,实现了该注解以及Annotation接口。
3.2 元注解
是指的是修饰注解的注解
常用的2个元注解:
@Target
和@Retention
java
@Retention( Retent ionPol icy . RUNTIME ) //元注解1
@Target({ElementType .METHOD}) //元注解2
public @interface Test {
}
3.2.1 @Target 元注解
作用:声明被修饰的注解只能在哪些位置使用。
java
//格式
@Target(ElementType.TYPE)
public @interface Test { }
- TYPE:类、接口;
- FIELD:成员变量;
- METHOD:成员方法
- PARAMETER:方法参数;
- CONSTRUCTOR:构造器;
- LOCAL_VARIABLE:局部变量;
java
//限定修饰类型为成员变量
@Target(ElementType.FIELD)
public @interface MyTest {
String value();
}
java
public class AnnotationTest {
//现在只能修饰成员变量了
@MyTest("hello")
private String name;
}
3.2.2 Retention 元注解
作用:声明注解的保留周期。
java
//格式
@Retention(RetentionPolicy.RUNTIME)
public @interface Test { }
- SOURCE:只作用在源码阶段,字节码文件中不存在。
- CLASS(默认值):保留到字节码文件阶段,运行阶段不存在。
- RUNTIME(开发常用):一直保留到运行阶段。
java
//一直保留到运行阶段
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
String value();
}
java
public class AnnotationTest {
//现在只能修饰成员变量了
@MyTest("hello")
private String name;
}
3.2.2 注解的解析
定义:就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。
指导思想:要解析谁上面的注解,就应该先拿到谁。比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解。比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。
Class、Method 、Field , Constructor.都实现了AnnotatedElement接口,它们都拥有解析注解的能力。
AnnotatedElement接口提供的解析注解方法 | 说明 |
---|---|
public Anotation[ ] getDeclaredAnnotations() | 获取当前对象上面的注解 |
public T getDeclaredAnnotation(Class annottationClass) | 获取指定的注解对象 |
public boolean isAnnotationPresent(Class annotationClass) | 判断当前对象上是否存在某个注解 |
解析注解案例:
①定义注解MyTest4, 要求如:
➢包含属性: String value()
➢包含属性: double aaa(),默认值为100
➢包含属性: String[] bbb()
➢限制注解使用的位置: 类和成员方法上
➢指定注解的有效范围: - -直到运行时
②定义一个类叫: Demo,在类中定义一个test1方法, 并在该类和其方法.上使用MyTest4注解
③定义AnnotationTest3测试类 ,解析Demo类中的全部注解。
java
//自定义注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
String value();
double aaa() default 100;
String[] bbb();
}
java
//使用注解的类
@MyTest(value = "java",aaa=120,bbb = {"python","HTML"})
public class Demo {
@MyTest(value = "JS",bbb = {"C语言","C++"})
public void test1(){}
}
java
//测试类
public class AnnotationTest {
@Test
public void testClassAnnotation(){
//1、获取到对应类的对象
Class c=Demo.class;
//2、解析类上的注解
// 判断类对象是否有对应的注解
if (c.isAnnotationPresent(MyTest.class)){
//3、获取到类上的注解对象
MyTest myTest=(MyTest) c.getDeclaredAnnotation(MyTest.class);
//4、通过类上的对象获取到对应的参数
System.out.println(myTest.value());
System.out.println(myTest.aaa());
System.out.println(Arrays.toString(myTest.bbb()));
}
}
@Test
public void testMethodAnnotation() throws Exception {
//1、获取到对应的类对象
Class c=Demo.class;
//2、获取到类的Method对象
Method m=c.getDeclaredMethod("test1");
//3、判断方法上是否有对应的注解
if(m.isAnnotationPresent(MyTest.class)){
//4、获取到注解的对象
MyTest myTest=(MyTest)m.getDeclaredAnnotation(MyTest.class);
//5、通过注解对象获取获取到方法上注解的参数
System.out.println(myTest.value());
System.out.println(myTest.aaa());
System.out.println(Arrays.toString(myTest.bbb()));
}
}
}
3.2.3 注解的运用场景
需求:定义若干个方法,只要加了MyTest注解,就会触发该方法执行。
分析:
①定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
②定义若干个方法,部分方法加.上@MyTest注解修饰,部分方法不加。
③模拟一个junit程序,可以触发加了@MyTest注解的方法执行。
java
//注解
@Target(ElementType.METHOD) //只能方法才能使用的注解
@Retention(RetentionPolicy.RUNTIME) //触发范围:一直到运行阶段
public @interface MyTest {
}
java
public class Demo {
//定义一下方法,要求只有加了注解的方法才能触发
@MyTest()
public void test1(){
System.out.println("=======test1=======");
}
@MyTest
public void test2(){
System.out.println("=======test2=======");
}
public void test3(){
System.out.println("=======test3=======");
}
@MyTest
public void test4(){
System.out.println("=======test4=======");
}
public static void main(String[] args) throws Exception{
//1、获取到类这个Class对象
Class c= Demo.class;
//2、通过类对象获取到类对象里面的所有方法
Method[] methods= c.getDeclaredMethods();
//3、通过遍历获取到里面的每一个方法
for (Method method : methods) {
//4、判断里面的每一个方法是否带有注解
if(method.isAnnotationPresent(MyTest.class)){
//5、创建一个要被执行方法的对象,将这个对象作为参数
Demo demo=new Demo();
//执行带有注解的这个方法
method.invoke(demo);
}
}
}
}
4、动态代理
代理是一种设计模式,它提供了一种机制,通过代理对象来访问目标对象,从而在不修改原始对象的基础上添加额外的功能或限制访问。
java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
java
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
/*
参数一:用于指定用哪个类加载器,去加载生成的代理类
参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
参数三:用来指定生成的代理对象要干什么事情
*/
案例:蔡徐坤他有很多的才艺(唱歌,跳舞,打篮球...),但是呢,他肯定不会白白的在大众面向做这些,肯定是要收钱的,而且还要准备舞台什么的;他又不会去自己去收钱、搭建舞蹈什么的,因为他只负责唱、跳等,自己擅长的领域,这时其它的工作就会交给代理去做,代理做好前期工作后,代理才叫他来展示他的才艺。下面是实现代码:
java
//大明星的才艺接口
public interface Star {
String sing(String singname);
void dance();
void basketball();
}
java
//大明星类
//大明星要学习这些才艺(实现接口的方法)
public class BigStar implements Star {
private String name;
public BigStar(String name) {
this.name = name;
}
//创建2个方法
public String sing(String singName){
System.out.println(this.name+" 唱完了 "+singName);
return "唱得怎么样?谢谢!谢谢!";
}
public void dance(){
System.out.println(this.name+" 跳了");
}
public void basketball(){
System.out.println(this.name+" 打篮球了");
}
}
java
//一个代理工具类(大明星可以在这里找代理)
public class ProxyUtil {
//1、创建一个静态方法,用来完成代理工作,接收一个大明星类(就是为bigStar做代理)
public static Star createProxy(BigStar bigStar){
//2、创建一个代理对象
/*Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:用于指定一个类加载器(一般就行写这个类的类加载器)
参数2:指定生成的代理长什么样子,也就是有哪些方法(是一个数组装其来的),由于这里只接收一个方法,就把它直接放到数组里去
参数3:用于指定生成的代理要做什么事情,它是一个接口,所以要不能直接创建对象,就new一个匿名内部类对象来指定代理对象做什么事情
*/
Star starProxy=(Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), new Class[]{Star.class}, new InvocationHandler() {
//invoke方法:等一下会去main方法调用这个工具类,将bigStar对象作为参数,得到一个Star对象,再通过Star对象调用sing和dance方法,调用这两个方法就会去调用invoke方法。
//sing和dance方法被调用时,也会把3个参数填到invoke方法中
//参数1:java会把当前的代理对象当作一个Object对象放到其中
//参数2:java会把,调用的方法当作Method传进来,如果外面在调用sing方法,那么method就是sing方法
//参数3:被调用方法的参数,java会把被调用的方法的产生传到args数组中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理对象要做的事情,会在这里写代码
//1、通过method来判断,被调用的是哪个方法
if(method.getName().equals("sing")){
System.out.println("准备话筒,收100块!");
//2、执行被代理发方法(参数为:被代理的对象,被调用方法的参数),有返回值的直接返回
return method.invoke(bigStar,args);
}else if(method.getName().equals("dance")){
System.out.println("准备一个舞台!收20块!");
//2、执行被代理发方法(参数为:被代理的对象,被调用方法的参数)
return method.invoke(bigStar,args);
}else {
//其它方法
return method.invoke(bigStar,args);
}
}
});
//将代理对象返回出去
return starProxy;
}
}
java
//测试类(大明星展示才艺类)
public class Test {
public static void main(String[] args) {
//通过有参构造器创建一个需要被代理的对象(创建了蔡徐坤大明星)
BigStar bigStar=new BigStar("蔡徐坤");
//调用代理工具类静态方法,创建一个代理对象,将需要被代理的对象传过去(蔡徐坤找来一个代理)
Star star=ProxyUtil.createProxy(bigStar);
//通过代理对象,调用代理方法
String str = star.sing("鸡你太美");
System.out.println(str);
//通过代理对象,调用代理方法
star.dance();
//通过代理对象,调用代理方法
star.basketball();
}
}