目录
一、单元测试
概念:
就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试
Junit单元测试框架
概念:
可以用来对方法进行测试,第三方公司开源出来的(很多开发工具已经集成了Junit单元测试框架,如 Idea)
优点:
1、可以灵活的编写测试代码,可以针对某个测试方法执行测试,也支持一键完成对全部方法的自动化测试,且一键独立
2、不需要程序员去分析测试的结果会自动生成测试报告
JUnit 4注解说明
注解 | 说明 |
---|---|
@Test: | 标识一条测试用例。 (A) (expected=XXEception.class) (B) (timeout=xxx) |
@Ignore: | 忽略的测试用例。 |
@Before: | 每一个测试方法之前运行。 |
@After : | 每一个测试方法之后运行。 |
@BefreClass | 所有测试开始之前运行。 |
@AfterClass | 所有测试结果之后运行。 |
JUnit 5注解说明
注解 | 说明 |
---|---|
@Test: | 标识一条测试用例。 (A) (expected=XXEception.class) (B) (timeout=xxx) |
@Ignore: | 忽略的测试用例。 |
@BeforeEach: | 每一个测试方法之前运行。 |
@AfteEachr : | 每一个测试方法之后运行。 |
@BefreClassAll | 所有测试开始之前运行。 |
@AfterClassAll | 所有测试结果之后运行。 |
Junit框架的简单单元测试
public class StringUtil { //求名字长度 public static void printString(String name) { if(name==null){ System.out.println("name为空,请输入测试值"); }else { System.out.println("名字长度:" + name.length()); } } /* 求字符串的最大索引 */ public static int getMaxIndex(String data){ if(data==null){ return -1; } return data.length()-1; } }
public class TestStringUtil { @BeforeEach // 每一个测试方法之前运行 public void test1(){ System.out.println("------test1方法执行了-------"); } @BeforeAll //每一个测试方法之后运行 public static void test3(){ System.out.println("------test3方法执行了-------"); } @AfterEach // 所有测试开始之前运行,只执行一次 public void test2(){ System.out.println("------test2方法执行了-------"); } @AfterAll // 所有测试结果之后运行,只执行一次 public static void test4(){ System.out.println("------test4方法执行了-------"); } @Test //测试方法 public void testPrintString(){ StringUtil.printString("13231231"); StringUtil.printString(""); StringUtil.printString(null); } @Test //测试方法 public void TestGetMaxIndex(){ int index1=StringUtil.getMaxIndex("abcde"); System.out.println(index1); int index2=StringUtil.getMaxIndex(null); System.out.println(index2); //断言机制,测试员可以通过预测方法的结果 Assert.assertEquals("方法内部出现Bug",4,index1); } } //输出 /* ------test3方法执行了------- ------test1方法执行了------- 名字长度:8 名字长度:0 name为空,请输入测试值 ------test2方法执行了------- ------test1方法执行了------- 4 -1 ------test2方法执行了------- ------test4方法执行了------- */
二、反射
认识反射,获取类
反射概念:
反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量,方法,构造器)
用于调试器,解释器,对象检查器,类浏览器等应用程序,以及需要访问目标对象的公共成员(基于其运行时类)的对象序列化和JavaBean等服务
反射获取类的信息
1、反射的第一步:加载类,获取类的字节码:Class对象
2、获取类的构造器:Constructor对象
3、获取类的成员变量:Filed对象
4、获取类的成员方法:Method对象
反射获取类的三种方法
方式一: 如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取: String s = “Hello”; Class cls = s.getClass();
方式二: 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取: Class cls = Class.forName(“java.lang.String”);
方式三: 直接通过一个class的静态变量class获取: Class cls = String.class;
获取类的结构
获取类的构造器的方法
Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
public Constructor<?>[] getConstructors():该方法只能获取当前Class所表示类的public修饰的构造器 public Constructor<?>[] getDeclaredConstructors():获取当前Class所表示类的所有的构造器,和访问权限无关
public Constructor<T> getConstructor(Class<?>... parameterTypes) :获取当前Class所表示类中指定的一个public的构造器
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) :获取当前Class所表示类中指定的一个的构造器
newInstance
创建此类
对象表示的类的新实例。 该类实例化为new
带有空参数列表的表达式。 如果尚未初始化,则初始化该类。
setAccessible
方法:public void setAccessible (AccessibleObject[] array, boolean flag) 禁止检查访问权限(暴力反射)
获取类的构造器的方法
public class Cat { private String name; private String age; private Cat() { } public Cat(String name, String age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return "Cat{" + "name='" + name + '\\'' + ", age='" + age + '\\'' + '}'; } }
import org.junit.jupiter.api.Test; import org.junit.jupiter.params.shadow.com.univocity.parsers.common.processor.ObjectColumnProcessor; import java.lang.reflect.Constructor; /* * 目标:获取类的构造器,并对其进行操作 * */ public class TestClass { //获取构造器 @Test //测试方法 public void textGetConstructors(){ //1.反射第一步,必须先得到这个类的Class对象 // Class c=new Cat().getClass(); Class c= Cat.class; //2.获取类的全部构造器,只能获取public构造器 // Constructor[] constructors=c.getConstructors(); //可以获取全部的构造器 Constructor[] constructors=c.getDeclaredConstructors(); //3.遍历数组中的每个构造器对象 for (Constructor constructor : constructors) { System.out.println(constructor.getName() + "----->" + constructor.getParameterCount()); } } //对其进行操作 @Test public void testGetConstructor() throws Exception { //1.反射第一步,必须先得到这个类的Class对象 Class c= Cat.class; //2.获取某个构造器 // Constructor constructor=c.getConstructor(); //可以不管修饰符权限,获取构造器 Constructor constructor=c.getDeclaredConstructor(); constructor.setAccessible(true); //禁止检查访问权限 Cat o1 = (Cat) constructor.newInstance(); System.out.println(o1); //3.获取有参构造器 Constructor constructor1 = c.getDeclaredConstructor(String.class, String.class); System.out.println(constructor1.getName() + "----->" + constructor1.getParameterCount()); //获取对象 Cat o = (Cat) constructor1.newInstance("小多啦","12"); //进行强转 System.out.println(o); } } //输出 /* Cat{name='null', age='null'} org.example.reflect.Cat----->2 Cat{name='小多啦', age='12'} org.example.reflect.Cat----->0 org.example.reflect.Cat----->2 */
获取成员变量
获取成员变量
public class Cat { private String name; private String age; public Cat() { } public Cat(String name, String age) { this.name = name; this.age = age; } public void run(){ System.out.println("小猫在跑~"); } public void eat(String food){ System.out.println("小猫在吃"+food); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return "Cat{" + "name='" + name + '\\'' + ", age='" + age + '\\'' + '}'; } }
import org.example.homework.Cat; import org.junit.jupiter.api.Test; import java.lang.reflect.Field; public class TestFiled { @Test public void testGetFields() throws NoSuchFieldException, IllegalAccessException { //1.反射第一步,必须得到类的Class对象 Class cat = Cat.class; //2.获取类的全部成员变量 Field[] fields = cat.getDeclaredFields(); //3.遍历这个成员变量数组 for (Field field : fields) { System.out.println(field.getName()); } //定位某个成员变量 Field name = cat.getDeclaredField("name"); System.out.println(name.getName()+"------->"+name.getType().getName()); //赋值 Cat cat1=new Cat(); name.setAccessible(true); //禁止访问控制权限 name.set(cat1,"猫0"); //取值 String n =(String)name.get(cat1); System.out.println(n); } } //输出 /* name age name------->java.lang.String 猫0 */
获取成员方法
获取成员方法
public class Cat { private String name; private String age; public Cat() { } public Cat(String name, String age) { this.name = name; this.age = age; } public void run(){ System.out.println("小猫在跑~"); } public void eat(String food){ System.out.println("小猫在吃"+food); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return "Cat{" + "name='" + name + '\\'' + ", age='" + age + '\\'' + '}'; } }
import org.example.homework.Cat; import org.junit.jupiter.api.Test; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class TestMethod { @Test public void testGetMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { //反射第一步,获得类的对象 Class cat = Cat.class; //获取全部的方法 Method[] methods = cat.getDeclaredMethods(); //开始遍历方法 for (Method method : methods) { System.out.println(method.getName()+" " +method.getParameterCount()+" " +method.getReturnType()); } //获取指定的方法 Method run = cat.getDeclaredMethod("run"); System.out.println(run.getName()); Method eat = cat.getDeclaredMethod("eat", String.class); System.out.println(eat.getName()); //创建class对象 Cat cat1=new Cat(); run.setAccessible(true); run.invoke(cat1); //调用无参方法 eat.invoke(cat1,"猫粮"); //调用有参方法 } } //输出 /* getName 0 class java.lang.String run 0 void toString 0 class java.lang.String setName 1 void eat 1 void setAge 1 void getAge 0 class java.lang.String run eat 小猫在跑~ 小猫在吃猫粮 */
反射的作用和应用场景
作用:
可以得到一个类的全部成分然后进行操作
可以破坏封装性
最重要的功能:适合做Java的框架,基本上,主流的框架都会基于发射设计出一些通用的功能
框架的简单应用
public class Cat { private String name; private String age; public Cat() { } public Cat(String name, String age) { this.name = name; this.age = age; } public void run(){ System.out.println("小猫在跑~"); } public void eat(String food){ System.out.println("小猫在吃"+food); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return "Cat{" + "name='" + name + '\\'' + ", age='" + age + '\\'' + '}'; } }
public class Dog { private String name; private int age; public Dog() { } public Dog(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; } }
import java.io.FileOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; public class ObjectFrame { public static void saveObject(Object object) throws Exception { //获取打印流 PrintStream Ps = new PrintStream(new FileOutputStream("opp_Junit/src/test/java/org/example/homework/writer.txt", true), true); //获取Object的Class的对象 Class aClass = object.getClass(); //获取类名,并打印 Ps.println("------------"+aClass.getSimpleName()+"------------"); //获取全部实例变量 Field[] Fields = aClass.getDeclaredFields(); //开始遍历实例变量 for (Field field : Fields) { field.setAccessible(true); //禁止访问权限 String name = field.getName(); System.out.println(name); Object value = field.get(object); System.out.println(value); //打印name+value Ps.println(name+":"+value); } Ps.close(); } }
public class Test { public static void main(String[] args) throws Exception{ Cat cat = new Cat("咪咪", "1"); Dog dog = new Dog("旺财", 3); //调用框架 ObjectFrame.saveObject(cat); ObjectFrame.saveObject(dog); } }
-----------Cat------------
name:咪咪
age:1
-----------Dog------------
name:旺财
age:3
三、注解
注解
概念:
就是Java代码里的特殊标记,比如:@Override @Test等,作用是让其他程序根据注解信息来决定怎么执行程序
自定义注解
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}
原理
本质上是一个接口,Java中的所有注解都是继承了Annotation接口的
元注解
概念:
指的是:修饰注解的注解
@Target注解
声明注解属性
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
@Retention注解
声明注解保留周期
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}
注解的解析
概念:
就是判断类上、方法上、成员变量上是否存在注解,并把注解的内容解析出来
解析注解的常用方法
通过反射来获得注解,先得到class对象
Class cls = Student. Class;
方法一: Annotation[] annotations = cls.getAnnotations()
作用: 获取所有注解
方法二: Annotation annotation = cls.getAnnotation(MyAnnotation.class)
作用:获MyAnnotation注解,类型是Annotation
方法三: Field field = cls.getDeclaredField(“name”)
annotations = field.getAnnotations()
作用: 获取属性上的注解
方法四:annotation = field.getAnnotation(MyAnnotation.class)
作用:获取属性上的MyAnnotation注解
annotation = field.getAnnotation(MyAnnotation.class);
方法五: Method method = cls.getDeclaredMethod(“show”, int.class);
annotations = method.getAnnotations()
作用:获取方法上的注解
方法六:annotation = method.getAnnotation(MyAnnotation.class)
作用:获取方法上的MyAnnotation注解
方法七:annotation instanceof MyAnnotation
作用:判断annotation是否是MyAnnotation类型
方法八:MyAnnotation ma=(MyAnnotation) annotation
System.out.println(ma.d());
System.out.println(ma.value());
作用: 获取注解上的属性值
方法九:method.isAnnotationPresent(SuppressWarnings.class)
作用: 判断method是否使用了指定的SuppressWarnings注解
应用场景
需求:
定义若干个方法,只要加了MyTest注解,就会触发该方法执行
需求
import java.lang.annotation.*; import java.lang.reflect.Method; /* 定义一个注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest{ }
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /* * 模拟Junit框架的设计 * */ public class AnnotationTest { @MyTest public void test1(){ System.out.println("test1执行了"); } // @MyTest public void test2(){ System.out.println("test2执行了"); } @MyTest public void test3(){ System.out.println("test3执行了"); } // @MyTest public void test4(){ System.out.println("test4执行了"); } public static void main(String[] args) throws InvocationTargetException, IllegalAccessException { AnnotationTest a=new AnnotationTest(); //获取Class对象 Class at = AnnotationTest.class; //获取全部方法 Method[] methods = at.getDeclaredMethods(); //遍历方法 for (Method method : methods) { //判断方法是否存在MyTest注解 if(method.isAnnotationPresent(MyTest.class)){ method.invoke(a); //方法执行 } } } }
动态代理
如何使用java动态代理
创建java动态代理需要使用如下类
java.lang.reflect.Proxy
调用其newProxyInstance方法,例如我们需要为Map创建一个代理:
Map mapProxy = (Map) Proxy.newProxyInstance(
HashMap.class.getClassLoader(),
new Class[]{Map.class},
new InvocationHandler(){...}
);
我们接着就来分析这个方法。先查看其签名:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
ClassLoader类型的loader:被代理的类的加载器,可以认为对应4要素中的被代理的对象。
Class数组的interfaces:被代理的接口,这里其实对应的就是4要素中的被代理的行为,可以注意到,这里需要传入的是接口而不是某个具体的类,因此表示行为。
InvocationHandler接口的h:代理的具体行为,对应的是4要素中的行为的完全控制,当然也是java动态代理的核心。
最后返回的对象Object对应的是4要素中的代理对象。
接着我们来示例用java动态代理来完成记录方法执行时间戳的需求:
首先定义被代理的行为,即接口:
public interface ExecutorInterface {
void execute(int x, int y);
}
接着定义被代理的对象,即实现了接口的类:
public class Executor implements ExecutorInterface {
public void execute(int x, int y) {
if (x == 3) {
return;
}
for (int i = 0; i < 100; i++) {
if (y == 5) {
return;
}
}
return;
}
}
接着是代理的核心,即行为的控制,需要一个实现了InvocationHandler接口的类:
public class TimeLogHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
这个接口中的方法并不复杂,我们还是先分析其签名
Object类型的proxy:最终生成的代理对象
Method类型的method:被代理的方法。这里其实是2个要素的复合,即被代理的对象是如何执行被代理的行为的。因为虽然我们说要对行为完全控制,但大部分时候,我们只是对行为增添一些额外的功能,因此依然是要利用被代理对象原先的执行过程的。
Object数组的args:方法执行的参数
因为我们的目的是要记录方法的执行的时间戳,并且原方法本身还是依然要执行的,所以在TimeLogHandler的构造函数中,将一个原始对象传入,method在调用invoke方法时即可使用。
定义代理的行为如下:
public class TimeLogHandler implements InvocationHandler {
private Object target;
public TimeLogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("start:{}", System.nanoTime());
Object result = method.invoke(target, args);
log.info("end:{}", System.nanoTime());
return result;
}
}
接着我们来看Invoker如何使用代理,这里为了方便演示我们是在构造函数中实例化代理对象,在实际使用时可以采用依赖注入或者单例等方式来实例化:
public class Invoker {
private ExecutorInterface executor;
public Invoker() {
executor = (ExecutorInterface) Proxy.newProxyInstance(
Executor.class.getClassLoader(),
new Class[]{ExecutorInterface.class},
new TimeLogHandler(new Executor())
);
}
public void invoke() {
executor.execute(1, 2);
}
}
此时如果Exector新增了任何方法,那么Invoker和TimeLogHandler将不需要任何改动就可以支持新增方法的的时间戳记录,有兴趣的同学可以自己尝试一下。
另外如果有其他类也需要用到时间戳的记录,那么只需要和Executor一样,通过Proxy.newProxyInstance方法创建即可,而不需要其他的改动了。