Java基础进阶学习

Java基础进阶学习

一、Junit单元测试框架

可以用来对方法进行测试

1.1 优点

  • 可以灵活的编写测试代码,可以针对某个方法进行测试,也支持一键完成对全部方法的自动化测试,且各自独立。
  • 不需要程序员去分析测试结果,会自动生成测试报告出来

1.2 具体步骤

  1. 将Junit框架的jar包导入到项目中(IDEA集成了Junit框架,无需自己导入)
  2. 为需要测试的业务类,定义对应的测试方法,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
  3. 测试方法上必须声明 @Test注解,然后在测试方法中,编写代码调用被测试的业务方法进行测试。
  4. 选中测试方法,右键选择“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 应用场景(案例)

需求:

对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去

实现步骤

  1. 定义一个方法,可以接收任意对象
  2. 每收到一个对象后,使用反射获取该对象的Class对象,然后获取全部的成员变量。
  3. 遍历成员变量,然后提取成员变量在该对象中的具体值。
  4. 把成员变量名、和其值,写出到文件中去即可。

代码呈现

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)

  1. TYPE,类,接口
  2. FIELD,成员变量
  3. METHOD,成员方法
  4. PARAMETER,方法参数
  5. CONSTRUCTOR,构造器
  6. 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注解,就会触发该方法执行

分析

  1. 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
  2. 定义若干个方法,部分方法加上@MyTest注解修饰,部分方法不加。
  3. 模拟一个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("-----------------------------------------------");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值