单元测试
导入jar包
- junit-4.12.jar
- hamcrest-core-1.3.jar
@Test
单元测试方法的注意事项
- 公共
- 无参
- 无返回
其他注解
-
Junit4.xxxx版本
- @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
- @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
- @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前执行一次。
- @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后执行一次。
-
Junit5.xxxx版本
- @BeforeEach:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
- @AfterEach:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
- @BeforeAll:用来静态修饰方法,该方法会在所有测试方法之前执行一次。
- @AfterAll:用来静态修饰方法,该方法会在所有测试方法之后执行一次。
断言
-
预习判断某个条件一定成立,如果条件不成立,则直接奔溃。
-
Assert
- Assert.assertFalse(exists);
- Assert.assertTrue(exists);
- Assert.assertEquals方法的参数 (String message, double expected, double actual)
message: 消息字符串
expected: 期望值
actual: 实际值
public class Demo03 {
@Test
public void test01() {
int result = fun(5);
// 断言
// 断言是相等的
/*
assertEquals(Object expected, // 期望
Object actual) // 实际
*/
Assert.assertEquals( 120, result);
}
public int fun(int num) {
if (num == 1) {
return 1;
}
return num * fun(num - 1);
}
// =====================================================
@Test
public void test02() {
int[] arr = {10, 20, 30, 40, 50};
boolean exists = isNumberExists(arr, 30);
// Assert.assertFalse(exists);
// Assert.assertTrue(exists);
Assert.assertEquals(true, exists);
}
public boolean isNumberExists(int[] arr, int num) {
for (int i : arr) {
if (num == i) {
return true;
}
}
// 循环结束, 才能确定是不存在, 返回false
return false;
}
}
反射
类加载器:
- 应用程序类加载器(Application ClassLoader)
- 扩展类加载器(Extension ClassLoader)(- JKD1.9之后为PlatformClassLoader)
- 启动类加载器(Bootstrap ClassLoader)
/**
* 类加载器
*/
public class Demo01 {
public static void main(String[] args) {
ClassLoader loader = Demo01.class.getClassLoader();
System.out.println(loader);
ClassLoader parent = loader.getParent();
System.out.println(parent);
ClassLoader grandParent = parent.getParent();
System.out.println(grandParent);
}
}
什么是反射
- 反射是一种机制/功能,利用该机制/功能可以在 = =程序运行= = 过程中对类进行解剖并操作类中的构造方法,成员方法,成员属性。
- 反射乃框架之灵魂
- 反射就是把Java的各种成分(字段,方法)映射成相应的Java类.
反射应用
- 开发工具中写代码时的提示
- 各种框架的设计
获取字节码对象
- 类名.class
- 对象名.getClass()
- Class.forName(“全限定类名”)
- 代码展示
/**
* 获取字节码对象的3种方式
*
* 1. 对象.getClass()
* 2. 类名.class
* 3. Class.forName()
* "com.itheima._02reflect.demo03_field.Student" -> 类的全限定类名/类的全路径类名
*/
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException {
Student s = new Student();
Class class1 = s.getClass();
Class class2 = Student.class;
Class class3 = Class.forName("com.itheima._02reflect.demo03_field.Student");
System.out.println(class1 == class2);
System.out.println(class1 == class3);
System.out.println(class1.hashCode());
System.out.println(class2.hashCode());
System.out.println(class3.hashCode());
}
}
获取构造方法
- Constructor
- getConstructor(Class … ): 参数的类型
使用构造方法创建对象
- newInstance(参数)
代码展示
/*
Class:
参数是确定 构造方法的参数列表是什么类型
不确定个数
Constructor getConstructor(Class ... a) : 获取指定的构造方法
Constructor:
newInstance(Object ... objs) : 创建对象
*/
public class Demo04 {
public static void main(String[] args) throws Exception {
Class aClass = Class.forName("com.itheima._02reflect.demo03_field.Student");
// Student() -> 空参构造
Constructor c1 = aClass.getConstructor();
System.out.println(c1);
// 通过无参构造创建对象
Student s1 = (Student) c1.newInstance();
System.out.println(s1);
// Student(String name)
Constructor c2 = aClass.getConstructor(String.class);
System.out.println(c2);
Student s2 = (Student) c2.newInstance("张三");
System.out.println(s2);
// Student(int age)
Constructor c3 = aClass.getConstructor(int.class);
System.out.println(c3);
Student s3 = (Student) c3.newInstance(23);
System.out.println(s3);
// Student(String name, int age)
Constructor c4 = aClass.getConstructor(String.class, int.class);
System.out.println(c4);
Student s4 = (Student) c4.newInstance("高圆圆", 40);
System.out.println(s4);
}
}
获取成员变量
- Field
- getField(String) : 变量名
/*
成员变量(Field)
Class:
获取指定的成员变量, 可以通过变量名, 来指定获取哪一个成员变量
Field getField(String fieldName)
Field:
set()
s.gender = "男";
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
// 获取字节码对象
Class class1 = Class.forName("com.itheima._02reflect.demo03_field.Student");
// 获取空参构造
Constructor c = class1.getConstructor();
// 通过空参构造创建对象
Student s = (Student) c.newInstance();
// 获取指定的成员变量
Field f = class1.getField("gender");
// 修改成员变量的值, 缺: 修改的对象 , 新值
/*
set(Object obj, // 对象
Object value) // 新值
*/
f.set(s, "男");
// s.gender = "男";
System.out.println(s);
}
}
修改成员变量
- set(对象, 新值)
获取成员方法
- Method
- getMethod(String name, Class …) : 方法名, 方法的参数类型
- 代码展示
/*
成员方法
Class
通过方法名, 和方法的参数列表来确定一个成员方法
方法名: 通过String来确定是什么名字
参数列表: 可变参数 Class... a
getMethod(String methodName, Class... a)
Method:
public Object invoke(Object obj, // 调用方法的对象
Object... args) // 调用方法传入的参数
s.eat()
s.eat(实参)
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
// 获取字节码对象
Class class1 = Class.forName("com.itheima._02reflect.demo03_field.Student");
// 获取空参构造
Constructor c = class1.getConstructor();
// 通过空参构造创建对象
Student s = (Student) c.newInstance();
// 获取空参的eat方法
Method eat1 = class1.getMethod("eat");
// 调用
int o = (int) eat1.invoke(s);
System.out.println(o);
Method eat2 = class1.getMethod("eat", String.class, int.class, boolean.class);
eat2.invoke(s, "烤冷面", 2, true);
}
}
调用成员方法
- invoke(对象, 参数)
暴力反射
-
getDeclaredXxx
-
setAccessible(true)
-
代码展示
/*
暴力反射
获取非公共成员变量的方法 : public Field getDeclaredField(String name)
*/
public class Demo02 {
public static void main(String[] args) throws Exception {
// 获取字节码对象
Class class1 = Class.forName("com.itheima._02reflect.demo03_field.Student");
// 获取空参构造
Constructor c = class1.getConstructor();
// 通过空参构造创建对象
Student s = (Student) c.newInstance();
// 获取私有成员变量
Field name = class1.getDeclaredField("name");
// 去除私有权限
name.setAccessible(true);
// 修改
name.set(s, "高圆圆");
System.out.println(s);
}
}
反射+多态+Properties
public class Apple extends Fruit {
@Override
public void juice() {
System.out.println("榨出了一杯苹果汁儿!~");
}
}
public abstract class Fruit {
public abstract void juice();
}
public class FruitTest {
public static void main(String[] args) throws Exception {
// 改进2: 全限定类名通过配置文件获取
Properties p = new Properties();
// 通过类加载器, 直接动态获取到src路径, 从src路径下获取到指定文件
InputStream is = FruitTest.class.getClassLoader().getResourceAsStream("fruit.properties");
// 调用加载, load方法, 方法是用来加载输入流关联配置文件中的信息
p.load(is);
// 关流
is.close();
// 获取Properties中className对应的值
String className = p.getProperty("className");
// 改进1: 通过反射创建对象
Class class1 = Class.forName(className);
// 获取空参构造并创建对象
Fruit f = (Fruit) class1.getConstructor().newInstance();
juicer(f);
}
// 下面方法用来模拟榨汁机
public static void juicer(Fruit f) {
f.juice();
}
}
//config.properties文件下的内容 用全限定类名通过配置文件获取下面内容
className=com.itheima._02reflect.test.Orange
注解
内置注解
- @Override:描述方法的重写.
- @Deprecated:标记过时
- @SuppressWarnings(all):压制警告.
- @FunctionalInterface:函数式接口
自定注解
-
@interface
- 本质上就是一个接口,接口中可以定义变量(常量)和方法(抽象),注解中的方法叫注解属性
- 语法: @interface 注解名{}
-
属性
-
属性值的范围
- 1.基本类型
- 2.String
- 3.枚举类型
- 4.注解类型
- 5.Class类型
- 6.以上类型的一维数组类型
-
注意事项
- 一旦注解有属性了,使用注解的时候,属性必须有值
-
注解属性赋值
-
元注解
- @Target
// 元注解
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
public @interface MyAnnotation {
// 属性 -> 必须赋值
// 可以指定默认值, 如果指定了默认值, 可以不在使用的时候赋值
String a() default "呵呵";
int b() default 100;
// 如果注解中只有一个属性需要赋值, 且属性名为value, 那么可以省略 `value =`
// 如果给数组赋值时, 数组中只有一个值, 那么可以省略大括号
// 给数组赋值时的格式: 数组名 = {值1, 值2 ...}
String[] value() default "哈哈";
}
// (属性名=值, 属性名=值 ... )
@MyAnnotation
public class MyClass {
@MyAnnotation
public int num;
@MyAnnotation
public MyClass() {
}
@MyAnnotation
public void fun() {
}
}
- @Retention
// 如果没有写任何保留策略, 默认保留在源文件阶段
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String a();
String b();
}
public class MyClass {
@MyAnnotation(a = "哈哈", b = "嘿嘿")
public void fun1() {
}
public void fun2() {
}
}
注解解析
public class Test01 {
@Test
public void test1() throws Exception {
// 获取到MyClass的字节码对象
Class class1 = MyClass.class;
// 反射获取fun1方法
Method fun1 = class1.getMethod("fun1");
// 判断是否有指定的注解
if (fun1.isAnnotationPresent(MyAnnotation.class)) {
// 获取指定的注解
MyAnnotation anno = fun1.getAnnotation(MyAnnotation.class);
System.out.println(anno.a());
System.out.println(anno.b());
}
}
}
MyTest
public class MyClass {
@MyTest
public void fun1() {
System.out.println("fun1()");
}
public void fun2() {
System.out.println("fun2()");
}
public void fun3() {
System.out.println("fun3()");
}
@MyTest
public void fun4() {
System.out.println("fun4()");
}
public void fun5() {
System.out.println("fun5()");
}
}
// 指定这个注解只能使用在方法上
@Target(ElementType.METHOD)
// 指定保留策略, 为所有阶段RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
public class MyTestDemo {
public static void main(String[] args) throws Exception{
// 1. 获取MyClass的字节码对象
Class class1 = MyClass.class;
// 获取空参构造
Constructor c = class1.getConstructor();
// 创建对象
MyClass mc = (MyClass) c.newInstance();
// 2. 获取所有方法
Method[] methods = class1.getMethods();
// 3. 遍历方法, 判断是否有MyTest注解
for (Method method : methods) {
// 判断是否有MyTest注解
if (method.isAnnotationPresent(MyTest.class)) {
// 4. 如果有, 调用这个方法
method.invoke(mc);
}
}
}
}
动态代理
作用:在不改变原有类的状态下,对对象功能进行增强。
静态代理
public class Demo01 {
public static void main(String[] args) {
/*DemoLogin dl = new DemoLogin();
dl.login();*/
// 创建被代理对象
DemoLogin dl = new DemoLogin();
// 创建代理类对象
LoginProxy lp = new LoginProxy();
// 向代理类注入被代理类的对象
lp.setDemoLogin(dl);
// 调用方法
lp.login();
}
}
public class DemoLogin implements LoginAble {
@Override
public void login() {
System.out.println("用户登录");
}
}
public interface LoginAble {
void login();
}
// 和被代理类实现同一个接口
public class LoginProxy implements LoginAble {
private DemoLogin demoLogin;
// 注入
public void setDemoLogin(DemoLogin demoLogin) {
this.demoLogin = demoLogin;
}
@Override
public void login() {
// 前置增强
System.out.println("前置增强");
// 原本的登录功能
demoLogin.login();
// 后置增强
System.out.println("后置增强");
}
}
动态代理
/**
*
* 动态代理的目标:
* 1. 动态代理类是动态生成的类, 就需要用到某个方法来生成
* Proxy.newProxyInstance()
* 2. 对需要增强的方法, 进行拦截, 重新指定方法的功能 -> 前置和后置增强
* InvocationHandler
*
* 动态代理的类, 和被代理的类, 一定实现同一个接口
*
*/
public class Demo02 {
public static void main(String[] args) {
// 创建被代理对象
UserServiceImpl userService = new UserServiceImpl();
// 创建间接的代理对象
TargetProxy targetProxy = new TargetProxy();
// 注入被代理对象
targetProxy.setTarget(userService);
// 获取真正的动态代理类
UserService proxy = (UserService) targetProxy.getProxy();
// 调用方法
proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}
}
/*
CRUD
C: Create
R: Retrieve
U: Update
D: Delete
*/
public interface UserService {
void add();
void delete();
void update();
void query();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("添加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
/**
* 动态代理类是JDK动态生成的类: $Proxy0
*
* TargetProxy可以理解为简介的代理类, 它不是真正的动态代理类, 只用于生成动态代理类
*
*/
// TargetProxy实现InvocationHandler
public class TargetProxy implements InvocationHandler {
// 被代理对象
private Object target;
// set方法注入
public void setTarget(Object target) {
this.target = target;
}
/**
* 此方法用于返回真正的动态代理类
*
* newProxyInstance(ClassLoader loader,
* 建议使用被代理对象获取类加载器, 但是使用其他类获取类加载器也可以
* Class<?>[] interfaces,
* 方法是用于生成真正的动态代理类, 动态代理类会实现和被代理类相同的接口
* 这里传入的就是被代理类实现的接口
* InvocationHandler h
* 动态代理类和拦截和加强的功能产生联系
*
*
*/
public Object getProxy() {
// proxy是真正的动态代理类
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
return proxy;
}
// 拦截的方法
/*
invoke(Object proxy, // 真正的动态代理类
Method method, // 被拦截的方法, 对它进行增强
Object[] args) // 被拦截方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强了方法: " + method.getName());
// 被拦截的方法由谁调用?? 被代理对象调用!!!
Object o = method.invoke(target, args);
System.out.println("后置增强");
return o;
}
}