Java高级

测试

Junit单元测试框架

Junit是使用Java语言实现的单元测试框架,它是第三方公司开源出来的,很多开发工具已经集成了Junit框架,比如IDEA

Junit框架完成单元测试:

  1. Junit框架的jar包导入到项目中(注意:IDEA集成了Junit框架,不需要我们自己手工导入了)。
  2. 编写测试类、测试类方法(注意:测试方法必须是公共的,无参数,无返回值的非静态方法)。
  3. 必须在测试方法上使用@Test注解(标注该方法是一个测试方法)。
  4. 在测试方法中,编写程序调用被测试的方法。
  5. 选中测试方法,右键选择运行,如果测试通过则是绿色;如果测试失败,则是红色。注意:如果无法执行测试方法,需要Settings->Plugins->搜索下载JUnit4 Parallel Runner这个插件即可。

Junit单元测试框架的常用注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StringUtil {
public static void printNumber(String name){
if(name == null){
System.out.println(0);
return; // 停掉方法
}
System.out.println("名字长度是:" + name.length());
}

//求字符串的最大索引
public static int getMaxIndex(String data) {
if (data == null) {
return -1;
}
return data.length() - 1;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//测试类
public class StringUtilTest {
@Before
public void test1(){//在每个测试方法执行之前都执行一次
System.out.println("---> test1 Before 执行了---------");
}

@BeforeClass
public static void test11(){//在所有测试方法执行之前只执行一次
System.out.println("---> test11 BeforeClass 执行了---------");
}

@After
public void test2(){//在每个测试方法执行之后都执行一次
System.out.println("---> test2 After 执行了---------");
}

@AfterClass
public static void test22(){//在所有测试方法执行之后只执行一次
System.out.println("---> test22 AfterClass 执行了---------");
}

@Test // 测试方法
public void testPrintNumber(){
StringUtil.printNumber("admin");
StringUtil.printNumber(null);
}

@Test // 测试方法
public void testGetMaxIndex(){
int index1 = StringUtil.getMaxIndex(null);
System.out.println(index1);

int index2 = StringUtil.getMaxIndex("admin");
System.out.println(index2);

// 断言机制:程序员可以通过预测业务方法的结果。
Assert.assertEquals("方法内部有bug!", 4, index2);
}
}

反射

反射指的是允许以编程方式访问已加载类的成分(成员变量、方法、构造器等)。

获取类:Class

反射是在运行时获取类的字节码文件对象,然后可以解析类中的全部成分。

反射的核心思想和关键:得到编译以后的class文件对象。

获取Class类的对象的三种方式:

  1. Class c1 = Class.forName(“全类名”);
  2. Class c2 = 类名.class;
  3. Class c3 = 对象.getClass();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//Student类
public class Student {
private String name;
private int age;

public Student() {
System.out.println("执行无参数构造器");
}

private Student(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception {
Class c1 = Student.class;
System.out.println(c1.getName());//全类名
System.out.println(c1.getSimpleName());//简名:Student

Class c2 = Class.forName("com.itheima.Reflect_.Student");
System.out.println(c1 == c2);// Student类的Class对象只有一份,c1和c2指向的都是同一个Class对象

Student s = new Student();
Class c3 = s.getClass();
System.out.println(c1 == c3);
}

获取类的构造器:Constructor

获取构造器的作用:初始化一个对象返回。

注意:如果是非public的构造器,需要打开权限(暴力反射):setAccessible(boolean),然后再创建对象。反射可以破坏封装性,私有的也可以执行了。

1
//Student类和上述一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Class c = Student.class;
//获取类的全部构造器
Constructor[] cons = c.getDeclaredConstructors();
for (Constructor con : cons) {
System.out.println(con.getName() + " --> " + con.getParameterCount());
}
//获取某个构造器
Constructor cons1 = c.getConstructor();//获取无参构造器
System.out.println(cons1.getName() + " --> " + cons1.getParameterCount());
Student s2 = (Student) cons1.newInstance();
//调用构造器,返回Object类型,需要强转
//如果不强转,可以在获取构造器时声明泛型Constructor<Student> cons1 = c1.getConstructor();但是构造器一般不声明泛型
System.out.println(s2);

Constructor cons2 = c.getDeclaredConstructor(String.class, int.class);//获取有参构造器
System.out.println(cons2.getName() + " --> " + cons2.getParameterCount());
cons2.setAccessible(true);//因为该构造器是private,会报错:java.lang.IllegalAccessException,需要暴力反射:禁止检查访问权限
Student s3 = (Student) cons2.newInstance("surourou", 18);
System.out.println(s3);

获取类的成员变量:Field

获取成员变量的作用:在某个对象中取值和赋值。

1
//Student类和上述一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class c = Student.class;
Field[] fields = c.getDeclaredFields();//获取类的全部成员变量
for (Field f : fields) {
System.out.println(f.getName() + " --> " + f.getType());
}
Field f = c.getDeclaredField("name");//定位某个成员变量
System.out.println(f.getName() + " --> " + f.getType());

//为对象的成员变量赋值
Student s = new Student();
f.setAccessible(true);//暴力反射,可以访问私有变量
f.set(s, "surourou");
System.out.println(s);

String name = (String) f.get(s);//取值
System.out.println(name);

获取类的成员方法:Method

获取成员方法的作用:在某个对象中进行执行此方法。

1
//Student类和上述一样
1
2
3
4
5
6
7
8
9
10
11
12
Class c = Student.class;
Method[] methods = c.getDeclaredMethods();//获取类的全部成员方法
for (Method m : methods) {
System.out.println(m.getName() + " --> " + m.getParameterCount() + " --> " + m.getReturnType());
}
Method m = c.getDeclaredMethod("setName", String.class);//获取某个方法对象
System.out.println(m.getName() + " --> " + m.getParameterCount() + " --> " + m.getReturnType());

Student s = new Student();
m.setAccessible(true);//暴力反射
Object rs = m.invoke(s, "surourou");//调用方法,无返回值则结果为null
System.out.println(s + " --> " + rs);

反射的作用:绕过编译阶段为集合添加数据

反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的。

泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义两个不同类型的集合
ArrayList<Integer> list = new ArrayList();
ArrayList<String> list2 = new ArrayList();
// 对比两个的对象地址
System.out.println(list.getClass() == list2.getClass()); // true

System.out.println("-----------------反射修改指定类型---------------");
ArrayList<Integer> list3 = new ArrayList();
list3.add(12);
list3.add(18);
// list3.add("大冶人"); 翻译阶段会报错

// 使用反射绕过编译阶段修改add()方法添加字符串
Class c = list3.getClass();
// 定位c中的方法
Method add = c.getDeclaredMethod("add", Object.class);
boolean rs = (boolean) add.invoke(list3,"我修改你");
System.out.println(list3); // [12, 18, 我修改你]

// ------- 更简单的方式
ArrayList list4 = list3;
list4.add("sada");
list4.add(true);
System.out.println(list3); // [12, 18, 我修改你, sada, true]

案例:使用反射做框架

对于任意一个对象,在不清楚对象字段的情况下,可以把对象的字段名称和对应值存储到文件中去。

1
2
3
4
5
6
7
8
9
10
11
//Student类
public class Student {
private String name;
private int age;

public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Teacher类
public class Teacher {
private String name;
private int age;
private String gender;
private String address;

public Teacher() {}
public Teacher(String name, int age, String gender, String address) {
this.name = name;
this.age = age;
this.gender = gender;
this.address = address;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ObjectFrame {
//开发框架:保存任意对象的字段和其数据到文件中去
public static void saveObject(Object obj) throws Exception {
PrintStream ps = new PrintStream(new FileOutputStream("src/Test.txt", true));
//注意:Junit测试下,文件相对路径是"src"开头,不是"项目名/src"
//obj是任意对象
Class c = obj.getClass();
String classname = c.getSimpleName();
ps.println("------------" + classname + "------------");
Field[] fields = c.getDeclaredFields();//拿到全部成员变量
for (Field field : fields) {
String name = field.getName();//拿到成员变量的名字
field.setAccessible(true);//暴力反射,禁止检查访问控制
String value = field.get(obj) + "";//拿到成员变量在对象中的数据(全都转为字符串)
ps.println(name + " = " + value);
}
}
}
1
2
3
4
5
6
7
8
//测试
@Test
public void save() throws Exception {
Student s = new Student("surourou", 18);
Teacher t = new Teacher("srr", 19, "female", "shenzhen");
ObjectFrame.saveObject(s);
ObjectFrame.saveObject(t);
}

注解

Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制。比如:@Override@Test,作用是:让其他程序根据注解信息来决定怎么执行该程序。

Java 语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。

自定义注解

格式:

1
2
3
public @interface 注解名称 {
public 属性类型 属性名() default 默认值 ;
}
1
2
3
4
5
6
//自定义注解
public @interface MyTest1 {
String aaa();
boolean bbb() default true;
String[] ccc();
}

特殊属性:value属性。如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写。但是如果有多个属性, 且多个属性没有默认值,那么value名称是不能省略的。

1
2
3
4
public @interface MyTest2{
String value(); //特殊属性
int age() default 10; //其他属性有默认值
}
1
2
3
4
5
6
@MyTest1(aaa="牛魔王", ccc={"Java", "C++"})
@MyTest2("孙悟空") //等价于 @MyTest2(value="孙悟空")
public class AnnotationTest1 {
@MyTest1(aaa="至尊宝", bbb = false, ccc = {"python"})
public void test1() {}
}

注解的本质

1.MyTest1注解本质上是接口,每一个注解接口都继承子Annotation接口。

2.MyTest1注解中的属性本质上是抽象方法

3.@MyTest1实际上是作为MyTest接口的实现类对象

4.@MyTest1(aaa="孙悟空",bbb=false,ccc={"Python","前端","Java"})里面的属性值,可以通过调用aaa()bbb()ccc()方法获取到。

元注解

元注解:修饰注解的注解。

元注解有两个:

@Target是用来声明注解只能用在那些位置,比如:类上、方法上、成员变量上等。

@Retetion是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期。

1
2
3
4
5
6
7
//声明@MyTest3注解只能用在类上和方法上
@Target({ElementType.TYPE,ElementType.METHOD})
//控制使用了@MyTest3注解的代码中,@MyTest3保留到运行时期
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest3{

}

注解的解析

注解的解析:判断类上、方法上、成员变量上是否存在注解,并把注解里的内容解析出来。

1
2
3
4
1.如果注解在类上,先获取类的字节码对象,再获取类上的注解。
2.如果注解在方法上,先获取方法对象,再获取方法上的注解。
3.如果注解在成员变量上,先获取成员变量对象,再获取变量上的注解。
总之:注解在谁身上,就先获取谁,再用谁获取谁身上的注解

1
2
3
4
5
6
7
8
9
//声明@MyTest4注解只能用在类上和方法上
@Target({ElementType.TYPE,ElementType.METHOD})
//控制使用了@MyTest4注解的代码中,@MyTest4保留到运行时期
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4{
String value();
double aaa() default 100;
String[] bbb();
}
1
2
3
4
5
6
7
@MyTest4(value="蜘蛛侠",aaa=99.9, bbb={"至尊宝","黑马"})
public class Demo{
@MyTest4(value="孙悟空",aaa=199.9, bbb={"紫霞","牛夫人"})
public void test1(){

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//测试类AnnotationTest3解析Demo类上的MyTest4注解
public class AnnotationTest3{
@Test
public void parseClass(){
//1.先获取Class对象
Class c = Demo.class;

//2.解析Demo类上的注解
if(c.isAnnotationPresent(MyTest4.class)){
//获取类上的MyTest4注解
MyTest4 myTest4 = (MyTest4)c.getDeclaredAnnotation(MyTest4.class);
//获取MyTests4注解的属性值
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(myTest4.bbb());
}
}

@Test
public void parseMethods() throws Exception {
//1.先获取Class对象
Class c = Demo.class;

//2.解析Demo类中test1方法上的注解MyTest4注解
Method m = c.getDeclaredMethod("test1");
if(m.isAnnotationPresent(MyTest4.class)){
//获取方法上的MyTest4注解
MyTest4 myTest4 = (MyTest4)m.getDeclaredAnnotation(MyTest4.class);
//获取MyTests4注解的属性值
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(myTest4.bbb());
}
}
}

案例:模拟Junit框架

需求:定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行。

1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest{

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class AnnotationTest4 {
@MyTest
public void test1(){
System.out.println("=====test1====");
}

@MyTest
public void test2(){
System.out.println("=====test2====");
}


public void test3(){
System.out.println("=====test3====");
}

public static void main(String[] args) throws Exception {
AnnotationTest4 a = new AnnotationTest4();
//1.先获取Class对象
Class c = AnnotationTest4.class;

//2.解析AnnotationTest4类中所有的方法对象
Method[] methods = c.getDeclaredMethods();
for(Method m: methods){
//3.判断方法上是否有MyTest注解,有就执行该方法
if(m.isAnnotationPresent(MyTest.class)){
m.invoke(a);
}
}
}
}

动态代理

代理思想:被代理者没有能力,或者不愿意去完成某件事情,需要找个人(代理)代替自己去完成这件事。

动态代理主要是对被代理对象的行为进行代理。

动态代理的开发步骤

1
2
3
4
5
1.必须定义接口,里面定义一些行为,用来约束被代理对象和代理对象都要完成的事情。
2.定义一个实现类实现接口,这个实现类的对象代表被代理的对象。
3.定义一个测试类,在里面创建被代理对象,然后为其创建一个代理对象返回。(重点)
4.代理对象中,需要模拟收首付款,真正触发被代理对象的行为,然后接收尾款操作。
5.通过返回的代理对象进行方法的调用,观察动态代理的执行流程。
1
2
3
4
5
//定义接口
public interface Star {
String sing(String name);
void dance();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//实现类实现接口:被代理的对象
public class BigStar implements Star {
private String name;

public BigStar(String name) {
this.name = name;
}

public String sing(String name) {
System.out.println(this.name + "正在唱歌:" + name);
return "谢谢!";
}

public void dance() {
System.out.println(this.name + "正在跳舞");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//定义ProxyUtil工具类,为BigStar对象生成代理对象
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
/* newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:用于指定一个类加载器(一般用当前类的类加载器)
参数2:指定生成的代理长什么样子,也就是有哪些方法
参数3:用来指定生成的代理对象要干什么事情
*/
// Star starProxy = ProxyUtil.createProxy(s);
// starProxy.sing("好日子") starProxy.dance()
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler() {
@Override // 回调方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy:当前的代理对象
//method:当前代理调用的方法
//args:调用方法的参数
// 代理对象要做的事情,会在这里写代码
if(method.getName().equals("sing")){
System.out.println("准备话筒,收钱20万");
}else if(method.getName().equals("dance")){
System.out.println("准备场地,收钱1000万");
}
return method.invoke(bigStar, args);//返回方法执行的结果
}
});
return starProxy;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//调用ProxyUtil工具类,为BigStar对象生成代理对象
public class Test {
public static void main(String[] args) {
BigStar s = new BigStar("surourou");
Star starProxy = ProxyUtil.createProxy(s);

String rs = starProxy.sing("倒数");
System.out.println(rs);

starProxy.dance();
}
}

案例

需求:模拟某企业用户管理业务,需包含用户登录,用户删除,用户查询功能,并要统计每个功能的耗时。

传统方法:

1
2
3
4
5
6
7
8
9
//用户业务接口
public interface UserService {
// 登录功能
void login(String loginName,String passWord) throws Exception;
// 删除用户
void deleteUsers() throws Exception;
// 查询用户,返回数组的形式。
String[] selectUsers() throws Exception;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//用户业务实现类(面向接口编程)
public class UserServiceImpl implements UserService{
@Override
public void login(String loginName, String passWord) throws Exception {
long time1 = System.currentTimeMillis();
if("admin".equals(loginName) && "123456".equals(passWord)){
System.out.println("您登录成功,欢迎光临本系统~");
}else {
System.out.println("您登录失败,用户名或密码错误~");
}
Thread.sleep(1000);
long time2 = System.currentTimeMillis();
System.out.println("login方法耗时:"+(time2-time1));
}

@Override
public void deleteUsers() throws Exception{
long time1 = System.currentTimeMillis();
System.out.println("成功删除了1万个用户~");
Thread.sleep(1500);
long time2 = System.currentTimeMillis();
System.out.println("deleteUsers方法耗时:"+(time2-time1));
}

@Override
public String[] selectUsers() throws Exception{
long time1 = System.currentTimeMillis();
System.out.println("查询出了3个用户");
String[] names = {"张全蛋", "李二狗", "牛爱花"};
Thread.sleep(500);
long time2 = System.currentTimeMillis();
System.out.println("selectUsers方法耗时:"+(time2-time1));
return names;
}
}

存在问题:每一个方法中计算耗时的代码都是重复的,且这些重复的代码并不属于UserSerivce的主要业务代码。

解决办法:把计算每一个方法的耗时操作,交给代理对象来做。

先在UserService类中把计算耗时的代码删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//用户业务实现类(面向接口编程)
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("成功删除了1万个用户~");
Thread.sleep(1500);
}

@Override
public String[] selectUsers() throws Exception{

System.out.println("查询出了3个用户");
String[] names = {"张全蛋", "李二狗", "牛爱花"};
Thread.sleep(500);

return names;
}
}

然后为UserService生成一个动态代理对象,在动态代理中调用目标方法,在调用目标方法之前和之后记录毫秒值,并计算方法运行的时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ProxyUtil2 {
public static UserService createProxy(UserService userService){
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),
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;
}
}

在测试类中为UserService创建代理对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test1 {
public static void main(String[] args) throws Exception{
// 1、创建用户业务对象。
UserService userService = ProxyUtil2.createProxy(new UserServiceImpl());

// 2、调用用户业务的功能。
// 每次用代理对象调用方法时,都会执行InvocationHandler中的invoke方法。
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("----------------------------------");
}
}

Java高级
http://surourou8.github.io/2024/10/07/Java高级/
作者
Su Rourou
发布于
2024年10月7日
许可协议