Java基础进阶篇-第五天

Java高级技术

Junit单元测试

java 复制代码
public class UserService {
    public UserService() {}
    public Integer getAge(String id) {
        if("110110200005050308".equals(id)){
            return 19;
        }else if("110110200005050328".equals(id)){
            return 24;
        }else if("110110200005050388".equals(id)){
            return 59;
        }
        return -1;
    }
    public String getGender(String id) {
        if("110110200005050308".equals(id)){
            return "男";
        }else if("110110200005050328".equals(id)){
            return "女";
        }else if("110110200005050388".equals(id)){
            return "男";
        }
        return "id不存在";
    }
}

// 测试中显示的名称
@DisplayName("用户信息测试类")
public class UserService1Test {
    
    //修饰实例方法,每一个测试方法运行之前都会执行一次
    @BeforeEach
    public void beforeEach(){
        System.out.println("beforeEach....");
    }
    //修饰实例方法,每一个测试方法运行之后都会执行一次
    @AfterEach
    public void afterEach(){
        System.out.println("afterEach....");
    }
    //修饰静态方法,所有测试方法运行之前只执行一次
    @BeforeAll
    public static void beforeAll(){
        System.out.println("beforeAll....");
    }
    //修饰静态方法,所有测试方法运行之后只执行一次
    @AfterAll
    public static void afterAll(){
        System.out.println("afterAll....");
    }

    //定义一个测试方法
    @DisplayName("测试获取年龄1")
    @Test
    public void testGetAge(){
        UserService userService = new UserService();
        System.out.println(userService.getAge("110110200005050308"));
    }

    //定义一个测试方法 - 断言 --- 110110200005050308
    @DisplayName("测试获取年龄2")
    @Test
    public void testGetAge2(){
        UserService userService = new UserService();
        // Assertions.assertEquals 判断预期结果和真实结果是否相同
        Assertions.assertEquals(24,userService.getAge("110110200005050308"),"测试失败");
    }

    //定义一个测试方法 - 断言 --- 110110200005050308
    @DisplayName("测试获取性别1")
    @Test
    public void testGetGender(){
        UserService userService = new UserService();
        Assertions.assertEquals("女",userService.getGender("110110200005050308"),"测试失败");
    }

    //参数化测试 --- 110110200005050308 110110200005050328 110110200005050388
    @DisplayName("测试获取性别2")
    // 以下两者配合使用,设置多个测试参数,多次测试
    @ParameterizedTest
    @ValueSource(strings = {"110110200005050308","110110200005050328","110110200005050388"})
    public void testAbc2(String idCard){
        UserService userService = new UserService();
        Assertions.assertEquals("女",userService.getGender(idCard),"测试失败");
    }
}

反射机制

反射:可以允许程序在运行过程中动态地获取类的信息(成员变量、方法、构造器等),还可以创建对象

反射:记载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)

获取Class对象的三种方式
  • Class c = 类名.class
  • 调用Class提供方法:public static Class forName(String package);
  • Object提供的方法: public Class getClass(); Class c3 = 对象.getClass();
java 复制代码
// 获取Class对象的三种方法
// 方式1,通过
Class c1 = Dog.class;

// 方式2,Class.forName方法(类的全限定名)
Class c2 = Class.forName(Dog.class.getName());

System.out.println(Dog.class.getName()); // 获取类的全限定名
System.out.println(Dog.class.getSimpleName()); // 获取类名

Constructor con1 = c1.getDeclaredConstructor(); // 获取Dog类中的无参构造器
Object d1 = con1.newInstance(); // 参数为
// 方式3,通过实例对象获取
Class c3 = d1.getClass();
反射获取类中构造器
方法 说明
Constructor<?>[] getConstructors() 获取全部构造器(只能获取public修饰的)
Constructor<?>[] getDeclaredConstructors() 获取全部构造器(只要存在就能拿到)
Constructor getConstructor(Class<?>... parameterTypes) 获取某个构造器(只能获取public修饰的)
Constructor getDeclaredConstructor(Class<?>... parameterTypes) 获取某个构造器(只要存在就能拿到)
Constructor提供的方法 说明
T newInstance(Object... initargs) 调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回
public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
java 复制代码
// 子类可以继承父类的非私有的方法(构造方法除外)和属性
Class c1 = Dog.class;

// 获取所有构造器
Constructor[] cons = c1.getDeclaredConstructors();
for (Constructor con : cons) {
    // 获取构造器的名称和参数类型
    System.out.println(con.getName()+" "+ Arrays.toString(con.getParameterTypes()));
}

// getDeclaredConstructor用于获取或声明指定参数类型的构造函数
// getDeclaredConstructor的参数类型列表 对应 构造器中的参数类型列表
Constructor con1 = c1.getDeclaredConstructor(); // 获取无参构造
Constructor con2 = c1.getDeclaredConstructor(String.class); // 获取参数为String类型且数量为1的构造器
Dog d1 = (Dog) con1.newInstance(); // 创建类实例,参数为构造函数中需要的参数

// 暴力反射,禁止检查访问权限
con2.setAccessible(true);
Dog d2 = (Dog) con2.newInstance("旺财"); // 使用类中的私有函数构造实例
反射获取类中成员
方法 说明
public Field[] getFields() 获取类的全部成员变量(只能获取public修饰的,包含父类)
public Field[] getDeclaredFields() 获取类的全部成员变量(只要存在就能拿到,不包含父类)
public Field getField(String name) 获取类的某个成员变量(只能获取public修饰的,包含父类)
public Field getDeclaredField(String name) 获取类的某个成员变量(只要存在就能拿到,不包含父类)
方法 说明
void set(Object obj, Object value): 赋值
Object get(Object obj) 取值
public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
java 复制代码
Class c = Dog.class;
Field[] fields = c.getDeclaredFields(); // 获取类中的所有成员
for (Field f : fields) {
    System.out.println(f);
}

Field fId = c.getDeclaredField("id"); // 获取某个成员变量
Field fName = c.getDeclaredField("name");

Dog dog = (Dog) c.getDeclaredConstructor(Integer.class, String.class).newInstance(11, "旺财");
fId.setAccessible(true); // 暴力反射
fId.set(dog, 111); // 对dog对象的id属性赋值
fName.setAccessible(true);fName.set(dog, "阿强");
System.out.println(fId.get(dog)); // 获取dog对象中的id属性
System.out.println(fName.get(dog));

Method toString = c.getDeclaredMethod("toString"); // 获取某个特定方法
Object str = toString.invoke(dog); // 触发dog对象中的toString方法
System.out.println(str);

// 通过这种方式获取父类的私有成员
Class sc = c.getSuperclass(); // 获取父类Class
Field[] sf1 = sc.getDeclaredFields();

父类和子类中有同名同类型的属性情况

java 复制代码
public class one {
    public String name;
}

public class two extends one{
    public String name;
}

Field[] fields = c.getFields();
for (Field field : fields) {
    System.out.println(field);
}

// 结果,属性不会覆盖,属性全限定名不同
public java.lang.String com.itheima.annotation.two.name
public java.lang.String com.itheima.annotation.one.name
反射获取类中成员方法
方法 说明
Method[] getMethods() 获取类的全部成员方法(只能获取public修饰的,包含父类)
Method[] getDeclaredMethods() 获取类的全部成员方法(只要存在就能拿到,不包含父类)
Method getMethod(String name, Class<?>... parameterTypes) 获取类的某个成员方法(只能获取public修饰的,包含父类)
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取类的某个成员方法(只要存在就能拿到,不包含父类)
Method提供的方法 说明
public Object invoke(Object obj, Object... args) 触发某个对象的该方法执行。
public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)v
java 复制代码
Class c = Dog.class;
Method[] m = c.getDeclaredMethods();
for (Method md : m) {
    System.out.println(md.getName()+" "+ Arrays.toString(md.getParameters())+" "+md.getReturnType());
}

Dog dog = (Dog) c.getDeclaredConstructor().newInstance();
Method m2 = c.getDeclaredMethod("setName", String.class);
m2.invoke(dog,"你好");

Method toString = c.getDeclaredMethod("toString");
Object result = toString.invoke(dog);
System.out.println(result);

// 通过这种方式获取父类的私有方法
Class sc = c.getSuperclass(); // 获取父类Class
Method[] sm = sc.getDeclaredMethods();

父类和子类中有同名同类型(重写关系)的属性情况

java 复制代码
public class Dog{
    public void hello(){}
}

public class DogPlus extends Dog{
    @Override
    public void hello() {
        System.out.println("DogPlus");
    }
}

Class sc = c.getSuperclass();
Method[] sm = sc.getDeclaredMethods();
System.out.println("============");
Class c3 = DogPlus.class;
Method[] m3 = c3.getDeclaredMethods();
for (Method md : m3) {
    System.out.println(md.getName()+" "+ Arrays.toString(md.getParameters())+" "+md.getReturnType());
}
// 结果,无法获取父类中的相同的方法
hello [] void
反射的作用
  • 基础作用:可以得到一个类中的全部成分然后操作
  • 可以破坏封装性
  • 可以绕过泛型的约束
  • 最重要的用途是:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能
简易版的框架
java 复制代码
// 将任意类的对象实例中的属性名和值打印到data文件中
public class JFrame {
    public static void main(String[] args) throws Exception{
        Dog dog = new Dog(123,"旺财");
        printClassFields(dog);
        Student student = new Student(1,"小明",18,"男","北京");
        printClassFields(student);
    }
    public static void printClassFields(Object obj) throws Exception{
        PrintStream ps = new PrintStream(new FileOutputStream("data.txt",true));
        Class c = obj.getClass();
        ps.println("====="+ c.getSimpleName() +"=====");
        Field[] fs = c.getDeclaredFields();
        for (Field f : fs) {
            f.setAccessible(true);
            String name = f.getName();
            String val = f.get(obj) + "";
            ps.println(name + "=" + val);
        }
        ps.close();
    }
}

注解

注解:Java代码里的特殊标记,比如:@Override等,作用是:让其他程序根据注解信息来决定怎么执行该程序

自定义注解public @interface 注解名称 { public 属性类型 属性名() default 默认值 ; }

特殊属性名 value:如果注解中只有一个value属性,使用注解时,value名称可以不写!

注解的本质

  • 注解本质是一个接口,Java中所有注解都是继承了Annotation接口的
  • @注解(...):其实就是一个实现类对象,实现了该注解以及Annotation接口
java 复制代码
public @interface MyTest{
    String value(); //每个注解都有的特殊属性,在使用时可省略
    int age() default 10;//指定默认值,相当于抽象方法,返回值是int,默认值是10
}
//反编译
public interface Mytest extend Annotation{
    public abstract String value();
    public abstract int age();
}

注解的作用:对Java中类、方法、成员变量做标记,然后进行特殊处理。

元注解:注解注解的注解

注解的解析

AnnotatedElement接口提供了解析注解的方法 说明
public Annotation[] getDeclaredAnnotations() 获取当前对象上面的注解(不包含父类)
public T getDeclaredAnnotation(Class annotationClass) 获取指定的注解对象(不包含父类)
public boolean isAnnotationPresent(Class annotationClass) 判断当前对象上是否存在某个注解
public Annotation[] getAnnotations() 获取当前对象上面的所有注解,配合@lnherited注解一起使用,即可获取父类中的注解(只针对类上的获取注解行为)
public T getAnnotation(Class annotationClass) 获取指定的注解对象,配合@inherited注解一起使用,即可获取父类中的注解(只针对类上的获取注解行为)

getDeclaredAnnotations/getAnnotations

getDeclaredAnnotation/getAnnotation

两类方法获取注解的方法

  • 若是在获取属性或方法上的注解,两者行为是一致的(属性和方法上的注解不会被继承)
  • 若是获取类上的注解,则两者不同
    • getDeclared类型的方法,只获取本类上的注解
    • get类型的方法,配合元注解@inherited(声明注解可继承),即可获取父类上的的注解
    • 若使用get方法时候,父子类上有相同注解,则获取子类上的同名注解
java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Inherited
public @interface MyAnnotation2 {
    String value();
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 表示该注解可以继承
public @interface MyTest {
    String value();
    String name() default "lxq";
    String age() default "18";
}

@MyAnnotation2("123")
@MyTest(value = "1",name = "1",age = "1")
public class one {}

@MyTest(value = "2",name = "2",age = "2")
public class two extends one{}

public static void main(String[] args) {
    Class c = two.class;
    // 直接获取类上的 MyAnnotation2.class 类型注解
    if(c.isAnnotationPresent(MyAnnotation2.class)){
        MyAnnotation2 d2 = (MyAnnotation2)c.getDeclaredAnnotation(MyAnnotation2.class);
    }
    Annotation[] as = c.getAnnotations();
    for (Annotation a : as) {
        System.out.println(a);
    }
}
// 打印结果
@com.itheima.annotation.MyAnnotation2("123")
@com.itheima.annotation.MyTest(name="2", age="2", value="2")

简易版的Junit框架

java 复制代码
@Target({ElementType.METHOD, ElementType.TYPE}) // 注解只能在类上和方法上
@Retention(RetentionPolicy.RUNTIME) // 一直保留到运行阶段
public @interface MyTest {
    String value();
    String name() default "lxq";
    // 注解定义中不能存在基础类型的包装类
    // Integer age() default 18;
    // 若是使用int基础类型,无法通过反射d.invoke(myTest)获取注解中的值,因为invoke返回值为Object类型,Object类是所有引用类型的父类,无法接收基础类型。但是 @注解(...) 相当于实现类,直接通过示例方法获取内部属性即可(方式二)。
    // int age() default 18;
    String age() default "18";
}

public class Test {
    @MyTest("你好")
    public void testOne(){
        System.out.println("======testOne======");
    }
    @MyTest(value = "你好",name = "123",age = "22")
    public void testTwo(){
        System.out.println("======testTwo======");
    }
    public static void main(String[] args) throws Exception {
        Test t = new Test();
        Class c = Test.class;
        Method[] ms = c.getDeclaredMethods();
        for (Method m : ms) {
            if(m.isAnnotationPresent(MyTest.class)){
                m.invoke(t);
                MyTest myTest = m.getDeclaredAnnotation(MyTest.class);
                // 方式一
                Class c1 = myTest.getClass();
                Method[] ds = c1.getDeclaredMethods(); // 注解的实现类,通过反射可以获取到Annotation
                for (Method d : ds) {
                    System.out.println("函数名称: "+d.getName());
                }
                for (Method d : ds) {
                    if(d.getName().equals("value")||d.getName().equals("age")||d.getName().equals("name")){
                        Object o = d.invoke(myTest);
                        System.out.println(o);
                    }
                }
                // 方式二
                System.out.println(myTest.value());
                System.out.println(myTest.age());
                System.out.println(myTest.name());
            }
        }
    }
}

代理

  • 目标对象:需要被增强的对象(方法)
  • 目标类:需要被增强的对象所在的类
  • 代理对象:增强之后的对象(方法),分为静态和动态代理对象
  • 代理类:增强之后的对象所在的类,分为静态和动态代理类
静态代理

两种方式

继承父类:通过直接在工程中创建子类继承目标类作为父类,这个子类可以称为静态代理类,然后在子类中重写父类方法进行增强,同时子类会持有对目标对象的引用,通过静态代理类创建处理的对象称为静态代理对象

java 复制代码
public class UserServiceImpl {
    public void add() {
        System.out.println("UserServiceImpl.add() 方法被调用");
    }
}
public class UserServiceProxy extends UserServiceImpl {
    @Override
    public void add() {
        // 添加额外的逻辑:日志记录
        System.out.println("日志:开始执行 add 方法");
        super.add(); // 调用父类的方法
        System.out.println("日志:结束执行 add 方法");
    }
}
public class Main {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceProxy();
        userService.add();
    }
}
// 结果
日志:开始执行 add 方法
UserServiceImpl.add() 方法被调用
日志:结束执行 add 方法

实现接口:目标类如果有实现接口的情况下,而且需要被增强的方法就是接口定义的方法,这种情况下,可以新建一个实现类来实现和目标类相同的接口,然后同时新的实现类内部持有目标对象的引用

java 复制代码
public interface UserService {
    void add();
}
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("UserServiceImpl.add() 方法被调用");
    }
}
public class UserServiceProxy implements UserService {
    private UserService userService;
    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }
    @Override
    public void add() {
        // 添加额外的逻辑:日志记录
        System.out.println("日志:开始执行 add 方法");
        userService.add(); // 调用目标类的方法
        System.out.println("日志:结束执行 add 方法");
    }
}
// 测试
public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userService);
        proxy.add();
    }
}
// 结果
日志:开始执行 add 方法
UserServiceImpl.add() 方法被调用
日志:结束执行 add 方法

缺点:针对每一个需要被增强的目标类都需要自己在项目中手写一个子类或者实现类进行增强,如果有很多个目标类都需要增强同一个逻辑,会非常麻烦

动态代理

Java中的动态代理是一种非常强大的功能,主要用于在运行时动态地创建代理对象,从而在不修改原始类代码的情况下,为对象的方法调用添加额外的逻辑。动态代理广泛应用于各种框架和库中,如Spring的AOP(面向切面编程)、事务管理、日志记录、权限控制等。

java 复制代码
// 所需代理对象的接口类,规定代理所需代理哪些方法
public interface Work {
    String playGame(String str);
    String dance(String str);
}
// 所需代理的对象,实现Work方法
public class User implements Work{
    @Override
    public String playGame(String str) {
        System.out.println("User PlayGame");
        return "User PlayGame";
    }

    @Override
    public String dance(String str) {
        System.out.println("User Dance");
        return "User Dance";
    }
}
public class Demo {
    public static void main(String[] args) {
        User u = new User();
        // newProxyInstance返回Object类型,需要调用Work的独有方法,强制类型转换。
        // 参数1:类加载器
        // 参数2:所需代理的接口类的Class数组
        // 参数3:InvocationHandler
        Work proxy =  (Work)Proxy.newProxyInstance(Demo.class.getClassLoader(), new Class[]{ Work.class }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 参数1:代理对象,当使用proxy时候,会调用invoke方法,则会不断递归(不能使用)
                // 参数2:代理调用的Method对象
                // 参数3:代理调用的方法,所传递进来的参数列表
                // System.out.println(Arrays.toString(args));
                System.out.println("++代理功能++");
                Object str = method.invoke(u, args);
                System.out.println("--代理功能--");
                return str; // 若是method无法返回值,则该str值proxy无法获取
            }
        });
        System.out.println("返回的值: " + proxy.playGame("我是代理") + '\n');
        System.out.println("返回的值: " + proxy.dance("我是代理") + '\n');
    }
}
// 第二种写法,自定义类实现 InvocationHandler 接口
public class UserProxy implements InvocationHandler {
    private Work target;
    public UserProxy(Work target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("++代理功能++");
        Object str = method.invoke(target, args);
        System.out.println("--代理功能--");
        return str; // 若是method无法返回值,则该str值proxy无法获取
    }
}
public class Demo {
    public static void main(String[] args) {
        UserProxy userProxy = new UserProxy(user);
        Work proxy2 =  (Work)Proxy.newProxyInstance(Demo.class.getClassLoader(), new Class[]{ Work.class }, userProxy);
        System.out.println("返回的值: " + proxy2.playGame("我是代理") + '\n');
        System.out.println("返回的值: " + proxy2.dance("我是代理") + '\n');
    }
}

newProxyInstance方法

  1. Java 动态代理通过 java.lang.reflect.Proxy 类在运行时直接生成代理类的字节码(生成的字节码文件和静态代理手动编写生成的字节码文件类似),无需编写 .java 源文件
  2. ClassLoader类加载器:字节码生成后会被 JVM 动态加载,开发者无需手动编译或管理代理类的源码
  3. 加载到内之后通过反射获取代理类的构造器并创建对象,最后返回一个代理类的实例对象
  4. 代理类的实例对象调用的方法,即为增强功能之后的目标方法

其他补充

时间相关的获取方案

LocalDate:代表本地日期(年、月、日、星期)

LocalTime:代表本地时间(时、分、秒、纳秒)

LocalDateTime:代表本地日期、时间(年、月、日、星期、时、分、秒、纳秒)

java 复制代码
long start = System.currentTimeMillis();
// 处理业务
long end = System.currentTimeMillis();
// 处理业务时间差值
System.out.println((start-end)/1000+"s");

StringBuilder

StringBuilder比String更适合做字符串的修改操作,效率会更高,代码也会更简洁。

构造器 说明
public StringBuilder() 创建一个空白的可变的字符串对象,不包含任何内容
public StringBuilder(String str) 创建一个指定字符串内容的可变字符串对象
方法名称 说明
public StringBuilder append(任意类型) 添加数据并返回StringBuilder对象本身
public StringBuilder reverse() 将对象的内容反转
public int length() 返回对象内容长度
public String toString() 通过toString()就可以实现把StringBuilder转换为String

BigDecimal

用于解决浮点型运算时,出现结果失真的问题。

构造器 说明
public BigDecimal(double val) 注意:不推荐使用这个 将 double转换为 BigDecimal
public BigDecimal(String val) 把String转成BigDecimal
方法名 说明
public static BigDecimal valueOf(double val) 转换一个 double成 BigDecimal
public BigDecimal add(BigDecimal b) 加法
public BigDecimal subtract(BigDecimal b) 减法
public BigDecimal multiply(BigDecimal b) 乘法
public BigDecimal divide(BigDecimal b) 除法
public BigDecimal divide (另一个BigDecimal对象,精确几位,舍入模式) 除法、可以控制精确到小数几位
public double doubleValue() 将BigDecimal转换为double
相关推荐
&白帝&14 分钟前
java HttpServletRequest 和 HttpServletResponse
java·开发语言
阿杆40 分钟前
🤯我写了一套无敌的参数校验组件④ | 现已支持 i18n
java·spring
小样vvv40 分钟前
【微服务管理】注册中心:分布式系统的基石
java·数据库·微服务
amagi60044 分钟前
Java中的正则表达式(Regular Expression)
java
喵手1 小时前
如何快速掌握 Java 反射之获取类的字段?
java·后端·java ee
AronTing1 小时前
06- 服务网格实战:从 Istio 核心原理到微服务治理升级
java·后端·架构
奋进的小暄1 小时前
贪心算法(18)(java)距离相等的条形码
java·开发语言·贪心算法
雷渊1 小时前
Elasticsearch查询为什么这么快
java
雷渊1 小时前
RocketMQ和kafka一样有重平衡的问题吗?
java·后端·面试
码农周1 小时前
Spring Boot 启动后自动执行 Service 方法终极指南
java·spring boot·后端