Java基础进阶学习
一、Junit单元测试框架
可以用来对方法进行测试
1.1 优点
- 可以灵活的编写测试代码,可以针对某个方法进行测试,也支持一键完成对全部方法的自动化测试,且各自独立。
- 不需要程序员去分析测试结果,会自动生成测试报告出来
1.2 具体步骤
- 将Junit框架的jar包导入到项目中(IDEA集成了Junit框架,无需自己导入)
- 为需要测试的业务类,定义对应的测试方法,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
- 测试方法上必须声明 @Test注解,然后在测试方法中,编写代码调用被测试的业务方法进行测试。
- 选中测试方法,右键选择“Junit运行”,如果通过则是绿色,反之就是红色。
1.3 案例
1.3.1 先编写一个业务类StringUtil
/**
* 业务类
*/
public class StringUtil {
public static void printNum(String name){
System.out.println("名字长度是: " + name.length());
}
}
1.3.2 再编写一个对应的测试类StringUtilTest
边写完对应的测试类后,定义方法,方法名最好跟业务类的名字相对应,然后在名字前加上test,使用驼峰命名法,然后在方法里调用业务类的方法(公共、无返回值、无参),且必须在方法名上面加上test注解!
import org.junit.Test;
/**
* 测试类
*/
public class StringUtilTest {
@Test
public void testPrintNum(){
StringUtil.printNum("admin");
}
}
[!IMPORTANT]
这里只考虑到了一般情况,既然是测试,应该考虑到极端情况,以保证方法的准确性。
错误样例
当我们在测试类中调用方法,给它一个null的值进去后,会报空指针异常,这说明原来的业务类是有bug存在的。
java.lang.NullPointerException
优化业务方法
在业务方法里加入判断条件,处理异常情况
/**
* 业务类
*/
public class StringUtil {
public static void printNum(String name){
if (name == null){
System.out.println(0);
return;// 结束方法
}
System.out.println("名字长度是: " + name.length());
}
}
断言机制
程序员可以预测业务的方法的结果==Assert.assertEquals();==里面可以用到很多参数。
例:Assert.assertEquals(“发生错误时候的提示信息”,自己预测的结果,实际返回的结果);
常用注解
@Test/: 测试类中的方法必须加上这个注解,才能执行
Junit4:@Before/Junit5:@BeforeEach:用来修饰实例方法,在执行每一个测试方法之前,执行一次,例:如果有2个测试方法test1和test2,加上Before注解的方法,会在test1执行前执行1次,然后在test2执行前执行一次。
Junit4:@After/Junit5:@AfterEach:用来修饰实例方法,在执行每一个测试方法之后,执行一次,与Beofre注解相反
Junit4:@BeforeClass/Junit5:@BeforeAll:用来修饰静态方法,在方法里加上static,该方法在所有测试方法执行之前,执行一次。
Junit4:@AfterClass/Junit5:AfterAll: 用来修饰静态方法,在方法里加上static,该方法在所有测试方法执行之后,执行一次。
[!NOTE]
- 在测试方法执行前执行的方法,常用于:初始化资源
- 在测试方法执行后执行的方法,常用于:释放资源
二、反射
反射就是加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)
反射主要学习获取类的信息,并操作他们
2.1 反射的操作步骤
1. 加载类,获取类的字节码:Class对象
获取Class对象的方法
Class c1 = 类名.class
c1.getName(): // 获取全类名
c1.getSimpleName(): // 获取简名:如Student
调用Class提供的方法: Class.forName(“{全类名}”)
Object提供的方法:Class c3 = 对象.getClass();
2. 得到类的Class对象之后,获取类的构造器,并对其进行操作
Class提供了从类中获取构造器的方法
方法 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 获取全部构造器(只能获取public修饰的) |
Constructor<?>[] getDeclaredConstructors() | 获取全部构造器(只要存在就能拿到) |
Constructor getConstructor(Class<?>… parameterTypes) | 获取某个构造器(只能获取public修饰的) |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 获取某个构造器(只要存在就能拿到) |
代码案例
这里假设有一个Cat的实体类,里面有类的无参构造方法和有参(String name,int age)构造方法
import org.junit.Test;
import java.lang.reflect.Constructor;
public class Test2Constructor {
@Test
public void testGetConstructors(){
// 1.获取类的Class对象
Class c = Cat.class;
// 2.获取类的全部构造器
// Constructor[] constructors = c.getConstructors();
Constructor[] declaredConstructors = c.getDeclaredConstructors();
// 3. 遍历数组中的每个构造器对象
for (Constructor constructor : declaredConstructors) {
System.out.println(constructor.getName() + "--->" + constructor.getParameterCount());
}//getName():获取构造器的全类名
//getParameterCount():获取参数数量
}
@Test
public void testGetConstructor() throws NoSuchMethodException {
// 1.获取类的Class对象
Class c = Cat.class;
// 2.获取类的某个构造器
Constructor constructor = c.getConstructor();//无参数构造器
Constructor declaredConstructor = c.getDeclaredConstructor(String.class,int.class);
System.out.println(declaredConstructor.getName() + "--->" + declaredConstructor.getParameterCount())
}
}
获取类构造器的作用
初始化对象返回
Constructor提供的方法 | 说明 |
---|---|
T newInstance(Object… initargs) | 调用此构造器对象不表示的构造器,并传入参数,完成对象的初始化并返回 |
public void setAccessible(boolean flag) | 设置为true,表示禁止检查访问控制(暴力反射) |
当执行这条测试方法的时候,会报==java.lang.IllegalAccessException:异常,意思是因为getDeclaredConstructor()==只能获取无参的且不是私有的方法,所以当方法或被private修饰时,就会发生异常
@Test
public void testGetConstructor() throws Exception{
// 1.获取类的Class对象
Class c = Cat.class;
// 2.获取类的某个构造器
Constructor constructor = c.getDeclaredConstructor();//无参数构造器
Cat cat = (Cat) constructor.newInstance();
System.out.println(cat);
}
java.lang.IllegalAccessException: Class com.study.d2_reflect.Test2Constructor can not access a member of class com.study.d2_reflect.Cat with modifiers "private"
解决办法
可以使用setAccessible方法
@Test
public void testGetConstructor() throws Exception{
// 1.获取类的Class对象
Class c = Cat.class;
// 2.获取类的某个构造器
Constructor constructor = c.getDeclaredConstructor();//无参数构造器
// Constructor constructor = c.getDeclaredConstructor(String.class) 获取某个带有一个String类型的有参构造器
constructor.setAccessible(true);// 禁止检查访问权限
Cat cat = (Cat) constructor.newInstance();// 创建类的实例,调用无参构造方法
//Cat cat = (Cat) constructor.newInstance("aaaa"); 调用有参构造方法
System.out.println(cat);
}
3. 获取类的成员变量:Field对象
Class提供了从类中获取成员变量的方法
方法 | 说明 |
---|---|
public Field[] getFields() | 获取类的全部成员变量(只能获取public修饰的) |
public Field[] getDeclaredFields() | 获取类的全部成员变量(只要存在就能拿到) |
public Field getField(String name) | 获取类的某个成员变量(只能获取public修饰的) |
public Field getDeclaredField(String name) | 获取类的某个成员变量(只要存在就能拿到) |
代码案例
@Test
public void testGetField() throws NoSuchFieldException {
// 1.得到类的Class对象
Class c = Cat.class;
// 2.获取类的全部成员变量
Field[] fields = c.getDeclaredFields();
// 3.遍历这个数组
for (Field field : fields) {
System.out.println("name = " + field.getName() + " type = " + field.getType() );
}
// 4.定位 某个成员变量
Field fName = c.getDeclaredField("name");
System.out.println(fName.getName() + "---> " + fName.getType());
// getName 获取成员变量的名称
// getType 获取该成员变量的数据类型
}
获取到成员变量的作用
对其进行赋值和取值
方法 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值 |
public Object get(Object obj) | 取值 |
public void setAccessible(boolean flag) | 设置为true,表示禁止检查访问控制(暴力反射) |
在对成员变量进行赋值时,又报了java.lang.IllegalAccessException:的异常,这是因为,我们的成员变量也设置的是private,只需要在set方法前,调用setAccessible方法,将它设置为true,禁止访问控制权限
// 赋值
Cat cat = new Cat();
fName.set(cat,"咖啡猫");// 这里用到set方法的时候,一定得先初始化一个对象出来
System.out.println(cat);
// 取值
String name = (String) fName.get(cat);
System.out.println(name);
java.lang.IllegalAccessException: Class com.study.d2_reflect.Test3Field can not access a member of class com.study.d2_reflect.Cat with modifiers "private"
优化后代码
// 赋值
Cat cat = new Cat();
fName.setAccessible(true);
fName.set(cat,"咖啡猫");// 这里用到set方法的时候,一定得先初始化一个对象出来
System.out.println(cat);
// 取值
String name = (String) fName.get(cat);
System.out.println(name);
4. 获取类的成员方法:Method对象
Class提供了从类中获取成员方法的API
方法 | 说明 |
---|---|
public Method[] getMethods() | 获取类的全部成员方法(只能获取public修饰的) |
public Method[] getDeclaredMethods() | 获取类的全部成员方法(只要存在就能拿到) |
public Method getMethod(String name, Class<?>… parameterTypes) | 获取类的某个成员方法(只能获取public修饰的) |
public Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 获取类的某个成员方法(只要存在就能拿到) |
代码案例
// 1. 得到类
Class c = Cat.class;
// 2. 获取类的全部成语方法
c.getMethods();
Method[] methods = c.getDeclaredMethods();
// 3,遍历数组中的每个方法对象
for (Method method : methods) {
System.out.println(method.getName());
}
// 4.获取某个成员方法
Method run = c.getDeclaredMethod("run");// 拿run方法,无参数的
Method eat = c.getDeclaredMethod("eat",String.class);//当类里有两个方法的方法名一样,在后面加上String.class就可以表示指的是参数是String类型的方法名
成员方法的作用
依然是执行
Method提供的方法 | 说明 |
---|---|
public Object invoke(Object obj, Object… args) | 触发某个对象的该方法执行 |
public void setAccessible(boolean flag) | 设置为true,表示禁止检查访问控制(暴力反射) |
Cat cat = new Cat();
run.setAccessible(true);
// invoke里,第一个放的是要执行该方法的对象,第二个是可变数据,如果该方法是无参的,就无需添加
Object rs = run.invoke(cat);// 调用无参数的run方法,用cat对象触发调用的
System.out.println(rs);// 因为无返回类型,所以输出null
2.2 反射的作用和应用场景
2.2.1 反射的作用
- 基本作用:可以得到一个类的全部成分然后操作
- 可以破坏封装性
- 最重要的用途是:适合做java框架,基本上,主流的框架都会基于反射设计出一些通用的功能。
2.2.2 应用场景(案例)
需求:
对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去
实现步骤
- 定义一个方法,可以接收任意对象
- 每收到一个对象后,使用反射获取该对象的Class对象,然后获取全部的成员变量。
- 遍历成员变量,然后提取成员变量在该对象中的具体值。
- 把成员变量名、和其值,写出到文件中去即可。
代码呈现
public class Student {
private String name;
private int age;
private char sex;
private double height;
private String hobby;
}
public class Teacher {
private String name;
private double salary;
}
// 此处省略了这两个数据模型的构造方法以及get和set方法
- 创建框架类,用于保存任意对象的字段和其数据保存到文件
public class ObjectFrame {
// 目标:保存任意对象的字段和其数据保存到文件
public static void saveObject(Object obj) throws Exception {
PrintStream ps = new PrintStream(new FileOutputStream("E:\\Junit-Study\\src\\data.txt",true));
// 1.obj是任意对象,通过getClass获取任意对象的Classd对象
Class c = obj.getClass();
String cName = c.getSimpleName();
ps.println("----------------------------" + cName + "----------------------------");
// 2. 从类中提取全部成员变量
Field[] fields = c.getDeclaredFields();
// 3.遍历每个成员变量
for (Field field : fields) {
// 4. 拿到成员变量的名字
String name = field.getName();
// 5. 拿到这个成员变量在对象中在的值
field.setAccessible(true);
String value = field.get(obj) + "";
ps.println(name + " = " + value);
}
ps.close();
}
}
- 创建测试框架类
public class Test5Frame {
@Test
public void save() throws Exception {
Student s1 = new Student("浙江彭于晏",45,'男',185,"明显");
Teacher t1 = new Teacher("安可",999.9);
// 需求:把任意对象的字段名和其对应值等信息,保存到文件中去
ObjectFrame.saveObject(s1);
ObjectFrame.saveObject(t1);
}
}
生成的data.txt展示
----------------------------Student----------------------------
name = 浙江彭于晏
age = 45
sex = 男
height = 185.0
hobby = 明显
----------------------------Teacher----------------------------
name = 安可
salary = 999.9
三、注解
3.1 自定义注解
public @interface 注解名称 {
public 属性类型 属性名() default 默认值;
}
也可以在新建类的时候,选择第四个annotation,直接输入注解名称
特殊属性名:value
- 如果注解中只有一个value属性,使用注解时,value名称可以不写!!
3.2 元注解
指的是注解的注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
}
@Target
作用:声明被修饰的注解只能在哪些位置使用
@Target(ElementType.TYPE)
- TYPE,类,接口
- FIELD,成员变量
- METHOD,成员方法
- PARAMETER,方法参数
- CONSTRUCTOR,构造器
- LOCAL_VARIABLE,局部变量
@Retention
作用:声明注解的保护周期
@Retention(RetentionPolicy.RUNTIME)
1.SOURCE
- 只作用在源码阶段,字节码文件中不存在。
2. CLASS(默认值)
- 保留到字节码文件阶段,运行阶段不存在
3. RUNTIME(开发阶段)
- 一直保留到运行阶段
3.3 注解的解析
就是判断类上、方法上、成员变量上是否存在注解,并把注解的内容给解析出来
如何解析注解
- 指导思想:要解析谁上面的注解,就应该先拿到谁。
- 比如要解析类上面的注解,则应该先获取到类的Class对象,再通过Class对象解析其上面的注解。
- 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。
- Class、Method、Filed、Constructor,都实现了AnnotatedElement接口,它们都拥有解析注解的能力。
AnnotatedElement接口提供了解析注解的方法 | 说明 |
---|---|
public Annotation[] getDeclaredAnnotations() | 获取当前对象上面的注解 |
public T getDeclaredAnnotation(Class annotationClass) | 获取指定的注解对象 |
public boolean isAnnotationPresent(Class annotationClass) | 判断当前对象上是否存在某个注解 |
3.4 应用场景(案例)
模拟Junit框架
需求
- 需要若干个方法,只要加了MyTest注解,就会触发该方法执行
分析
- 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
- 定义若干个方法,部分方法加上@MyTest注解修饰,部分方法不加。
- 模拟一个Junit程序,可以触发加了@MyTest注解的方法执行
代码呈现
/**
* 自定义注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
public class AnnotationTest4 {
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{
AnnotationTest4 a = new AnnotationTest4();
// 1.得到类对象
Class c = AnnotationTest4.class;
// 2.得到类对象的方法
Method[] methods = c.getDeclaredMethods();
// 遍历
for (Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)){
// 说明当前方法存在@MyTest注解
method.invoke(a);
}
}
}
}
测试结果
====test2======
====test4======
四、动态代理
案例:使用代理优化用户管理类
场景
- 某系统有一个用户管理类,包含用户登录,删除用户,查询用户等功能,系统要求统计每个功能的执行耗时情况,以便后期观察程序性能
需求
- 现在,某个初级程序员已经开发好了该模块,请贯穿该模块代码,找出目前存在的问题,并对其进行改造。
代码呈现
/**
* 用户业务的接口
*/
public interface UserService {
// 登录功能
void login(String loginName,String password) throws Exception;
// 删除用户
void deleteUsers() throws Exception;
// 查询用户,返回数组形式
String[] selectUsers() throws Exception;
}
/**
* 用户业务实现类(面向接口编程)
*/
public class UserServiceImpl implements UserService{
@Override
public void login(String loginName, String password) throws Exception {
if("admin".equals(loginName) && "123456".equals(password)){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
Thread.sleep(1000);//登录耗时,特意让程序停了一秒钟
}
@Override
public void deleteUsers() throws Exception {
System.out.println("成功删除");
Thread.sleep(1500);
}
@Override
public String[] selectUsers() throws Exception {
System.out.println("查询出了3个用户");
String[] names = {"张三","李四","王五"};
Thread.sleep(500);
return names;
}
}
/**
* 代理类
*/
public class ProxyUtil {
public static UserService createProxy(UserService userService){
// 创建代理对象
UserService userServiceProxy=(UserService)Proxy.newProxyInstance(ProxyUtil.class.getClassLoder,
new Class[]{UserService.class},new InvocationHandler()){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("login") || method.getName().equals("deleteUsers") || method.getName().equals("selectUsers")){
long startTime = System.currentTimeMillis();
// 执行业务类的方法
Object rs = method.invoke(userService, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "方法执行耗时:" + (endTime-startTime)/1000.0 + "s");
return rs;
}
else{
Object rs = method.invoke(userService, args);
return rs;
}
}
});
return userServiceProxy;
}
}
/**
* 目标:使用动态代理解决实际问题,并掌握使用代理的好处
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1.创建用户业务对象
// 业务对象创建出代理对象,并把代理对象交给userService记住
UserService userService = ProxyUtil.createProxy(new UserServiceImpl());
// 2.调用用户业务的功能
userService.login("admin","123456");
System.out.println("-----------------------------------------------");
userService.deleteUsers();
System.out.println("-----------------------------------------------");
String[] names = userService.selectUsers();
System.out.println("查询到的用户是:" + Arrays.toString(names));
System.out.println("-----------------------------------------------");
}
}