当前位置: 首页 > article >正文

有关Java中的注解和反射

学习目标

● 了解注解相关
● 掌握元注解
● 了解反射概念
● 掌握反射常用的方法

1.注解

1.1 概念

● Java注解又称Java标注,是在 JDK5 时引入的新特性,注解(也被称为元数据)。
● Java注解它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。
● Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能

总结: 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
● 注解不是程序的一部分,可以理解为注解就是一个标签,程序可以通过这个标签来获取信息,或者执行动作。

1.2 作用

● 编写文档:生成文档这是最常见的,也是java 最早提供的注解;
● 编译检查:在编译时进行格式检查,如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;为我们的代码提供了一种规范制约,避免我们后续在代码中处理太多的代码以及功能的规范。
● 较少配置: 跟踪代码依赖性,实现替代配置文件功能,比较常见的是后期的基于注解配置,作用就是减少配置;
● 代码分析:在反射的 Class, Method, Field 等函数中,有许多于 Annotation 相关的接口,可以在反射中解析并使用 Annotation。

1.3 注解分类

在这里插入图片描述

  1. Java自带的标准注解,包括@Override、@Deprecated、@SuppressWarning等,使用这些注解后编译器就会进行检查。
    ● @Override:检测被该注解标注的方法是否是继承自父类(接口)的
    ● @Deprecated:该注解标注的内容,表示已过时
    ● @SuppressWarnings:压制警告,一般传递参数all @SuppressWarnings(“all”)
    ● @FunctionalInterface: 函数式接口。用来修饰接口。标识接口有且只有1个抽象方法,但是可以有多个非抽象的 方法。

  2. 元注解
    ● 元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented 等。
    ● 元注解也是Java自带的标准注解,只不过用于修饰注解,比较特殊。

  3. 自定义注解
    ● 用户可以根据自己的需求定义注解。

1.4 元注解

● 元注解是Java API提供的,是用于修饰注解的注解,通常用在注解的定义上
● 没有元注解解释说明注解,自定义的注解没有任何意义的。通过合理使用元注解,可以精确控制自定义注解的行为和适用范围

以下是Java元注解(用于修饰其他注解的注解)的总结表格,涵盖核心作用、可选值及使用场景:

元注解作用可选值/使用场景示例
@Target定义注解可应用的目标范围(如类、方法、字段等)通过ElementType枚举指定: TYPE(类)、METHOD(方法)、FIELD(字段)、PARAMETER(参数)、ANNOTATION_TYPE(注解)等@Target(ElementType.METHOD)表示该注解只能用于方法上
@Retention定义注解的生命周期(保留到哪个阶段)通过RetentionPolicy枚举指定: SOURCE(源码)、CLASS(类文件)、RUNTIME(运行时)@Retention(RetentionPolicy.RUNTIME)表示注解在运行时可通过反射获取
@Documented标注后,该注解会包含在Javadoc生成的文档中无需参数,直接添加即可@Documented配合其他注解使用(如@Override
@Inherited标注后,该注解会被子类继承(默认不继承)需显式声明,仅对类注解有效@Inherited父类使用该注解,子类自动继承

补充说明

  1. @Target:若未指定,默认支持所有目标类型。

  2. @Retention:默认值为RetentionPolicy.CLASS,但常用RUNTIME以实现动态处理(如反射)。

  3. 组合使用:多个元注解可同时修饰一个注解,例如:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyAnnotation { ... }
    
  4. @Inherited限制:仅对类级注解有效,接口或方法上的注解无法被继承。

1.4 自定义注解

1.4.1 语法

● 在开发中,满足具体的业务需求。
● 当我们了解了内置注解, 元注解和反射后,我们便可以开始自定义以及使用注解了。
● 创建自定义注解和创建一个接口相似,但是注解的interface关键字需要以@符号开头,我们可以为注解声明方法

//语法
[public]  @interface  注解名称{//大写驼峰
    //有很多参数
    属性数据类型  属性名1() [default  默认数据];
    属性数据类型  属性名2() [default  默认数据];
  
    //属性名称要唯一
}

1.4.2 属性类型

在注解里面 可以使用哪些数据类型作为属性的数据类型?

  1. 基本数据类型 4类八种
  2. String
  3. 枚举类型
  4. Class
  5. 以上类型的数组类型 int[] String[] Class[] DayOfWeek[]

1.4.3 属性值

● 定义了属性,在使用时需要给属性赋值:
● 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
● 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接输入值即可。
● 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略。

1.5.4 创建注解

//使用注解 注解里面有且只有1个参数  并且参数名就是value 可以省略参数名
//@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyAnnotation {

    //参数
    int id();

    String name();

    String[] hobby();

    DayOfWeek day() default DayOfWeek.MONDAY;

    Class<?> clazz() default Object.class;
    
}

1.5 使用注解

  • 在哪里使用,要看注解上的元注解@Target
@MyAnnotation(id = 1001, name = "张三", hobby = {"code", "music"}, clazz = Student.class)
public class Student {


    @MyAnnotation(id = 1002, name = "李四", day = DayOfWeek.SUNDAY)
    public void info() {

    }
}

2.反射

2.1 基本概念

● Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
● 核心功能: 动态获取信息以及动态调用对象方法.

● Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。这种“看透class”的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

2.2 优缺点

类别优点/缺点详细描述
优点灵活性与扩展性提高程序灵活性和扩展性,降低模块耦合性,适应不同场景需求。
动态对象管理允许程序在运行时动态创建、控制任意类的对象,无需提前硬编码目标类。
运行时操作能力支持运行时构造对象、判断类的成员变量和方法、调用方法,增强动态处理能力。
框架开发基础是构建框架(如Spring)的核心技术,通过反射实现依赖注入、动态代理等,避免硬编码,提升框架通用性。
多态与抽象能力强化多态特性,提升面向对象编程的抽象能力,支持动态编译,激发编程语言灵活性。
缺点性能问题反射操作涉及动态类型解析和JVM优化绕过,执行效率低于直接代码调用,频繁使用可能影响性能。
安全限制反射可绕过访问权限(如访问私有成员),可能破坏封装性,需额外安全管理(如配置SecurityManager)。
程序健壮性反射代码在编译期不易检测错误(如方法名拼写错误),需通过异常处理保障运行稳定性,增加调试和维护难度。

补充说明

  • 性能优化建议:对高频调用的反射代码可缓存ClassMethod对象,减少重复解析开销。
  • 安全实践:限制反射权限(如通过安全策略文件),避免滥用导致的安全漏洞。
  • 框架中的应用:Spring通过反射实现依赖注入(如@Autowired),动态代理(AOP)则结合反射和字节码增强技术。

● Java程序的运行过程: .java源文件 --> 经过编译器 编译成 .class(字节码文件,是一个二进制文件) ->被类加载器加载到 JVM内存(加载到方法区),在Java中万事万物皆对象,那么字节码文件对应的也有一个对象,这个对象是Class类{模板 属性,方法 }的对象。把这个类对象放大来看,里面的每一个组成成分都是对象 属性对象(Filed类型) 方法对象(Method类型) 构造器对象(Constructor类型)
● 要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就要使用如下相关API:
○ java.lang.Class;
○ java.lang.reflect. *;

2.3 获取类对象

  1. 类型名.class,要求编译期间已知类型 .
  2. 对象.getClass(),获取对象的运行时类型.
  3. Class.forName(类型全名称),可以获取编译期间未知的类型
  /**
     * 1,获取 类对象
     */
    public static void method1() throws ClassNotFoundException {
        //1,类名.class
        Class<UserInfo> stuClass = UserInfo.class;
        //2 对象名.getClass
        UserInfo user = new UserInfo();
        Class<? extends UserInfo> aClass = user.getClass();
        // 3, Class.forName(类的全路径) 最常用
        Class<?> aClass1 = Class.forName("com.java.reflect.UserInfo");
        System.out.println(stuClass == aClass);
        System.out.println(aClass == aClass1);
    }

2.4 Class常用方法

● 反射相关的API在java.lang包和java.lang.reflect包。

方法描述
public static Class<?> forName(String className)throws ClassNotFoundException返回与带有给定字符串名的类或接口相关联的 Class 对象。
public Package getPackage()获取此类的包。
public int getModifiers()返回此类或接口以整数编码的 Java 语言修饰符。
public String getName()以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
public Class<? super T> getSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。
public Type getGenericSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。如果超类是参数化类型,则返回的 Type 对象必须准确反映源代码中所使用的实际类型参数。
public Class<?>[] getInterfaces()确定此对象所表示的类或接口实现的接口。如果此对象表示一个类,则返回值是一个数组,它包含了表示该类所实现的所有接口的对象。
public Type[] getGenericInterfaces()返回表示某些接口的 Type,这些接口由此对象所表示的类或接口直接实现。
public Field[] getFields() throws SecurityException返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。返回数组中的元素没有排序,也没有任何特定的顺序。
public Field getField(String name) throws NoSuchFieldException, SecurityException返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。name 参数是一个 String,用于指定所需字段的简称。
public Field[] getDeclaredFields() throws SecurityException返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。包括公共、保护、默认(包)访问和私有字段,但不包括继承的字段。返回数组中的元素没有排序,也没有任何特定的顺序。
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。name 参数是一个 String,它指定所需字段的简称。
public Constructor<?>[] getConstructors() throws SecurityException返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
public Constructor getConstructor(Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。parameterTypes 参数是 Class 对象的一个数组,这些 Class 对象按声明顺序标识构造方法的形参类型。 如果此 Class 对象表示非静态上下文中声明的内部类,则形参类型作为第一个参数包括显示封闭的实例。
public Constructor<?>[] getDeclaredConstructors() throws SecurityException返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。它们是公共、保护、默认(包)访问和私有构造方法。
public Constructor getDeclaredConstructor(Class<?>… parameterTypes)throws NoSuchMethodException, SecurityException返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。parameterTypes 参数是 Class 对象的一个数组,它按声明顺序标识构造方法的形参类型。 如果此 Class 对象表示非静态上下文中声明的内部类,则形参类型作为第一个参数包括显示封闭的实例。
public Method[] getMethods()throws SecurityException返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
public Method getMethod(String name, Class<?>… parameterTypes)throws NoSuchMethodException, SecurityException返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。name 参数是一个 String,用于指定所需方法的简称。parameterTypes 参数是按声明顺序标识该方法形参类型的 Class 对象的一个数组。
public Method[] getDeclaredMethods() throws SecurityException返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。返回数组中的元素没有排序,也没有任何特定的顺序。
public Method getDeclaredMethod(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。name 参数是一个 String,它指定所需方法的简称,parameterTypes 参数是 Class 对象的一个数组,它按声明顺序标识该方法的形参类型。
public Class<?>[] getClasses()返回一个包含某些 Class 对象的数组,这些对象表示属于此 Class 对象所表示的类的成员的所有公共类和接口。(即内部类)
public Class<?>[] getDeclaredClasses() throws SecurityException返回 Class 对象的一个数组,这些对象反映声明为此 Class 对象所表示的类的成员的所有类和接口。包括该类所声明的公共、保护、默认(包)访问及私有类和接口,但不包括继承的类和接口。
public Class<?> getDeclaringClass()如果此 Class 对象所表示的类或接口是另一个类的成员,则返回的 Class 对象表示该对象的声明类。如果该类或接口不是其他类的成员,则此方法返回 null。(即外部类)
public boolean isAnonymousClass()是否是匿名类
public boolean isLocalClass()是否是局部内部类
public boolean isMemberClass()是否是成员内部类
public boolean isArray()是否是数组类型
public boolean isPrimitive()是否是基本数据类型或void
public boolean isInterface()是否是接口类型
public boolean isEnum()是否是枚举类型
public boolean isAnnotation()是否是注解类型
public T newInstance() throws InstantiationException, IllegalAccessException创建此 Class 对象所表示的类的一个新实例。要求该类必须有一个公共的无参构造。
public ClassLoader getClassLoader()返回该类的类加载器。如果该类由引导类加载器加载,则此方法在这类实现中将返回 null。

2.5 Field常用方法

  • 获取属性信息
方法描述
public Annotation[] getDeclaredAnnotations()返回字段上的所有注解类型
public T getAnnotation(Class annotationClass)返回字段上指定的注解类型
public int getModifiers()以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符。应该使用 Modifier 类对这些修饰符进行解码。
public Class<?> getType()返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
public String getName()返回此 Field 对象表示的字段的名称。
public void setAccessible(boolean flag)throws SecurityException将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。
public void set(Object obj,Object value) throws IllegalArgumentException, IllegalAccessException将指定对象变量上此 Field 对象表示的字段设置为指定的新值。如果底层字段是静态字段,则忽略 obj 变量;它可能为 null。
public Object get(Object obj)throws IllegalArgumentException,IllegalAccessException返回指定对象上此 Field 表示的字段的值。如果底层字段是一个静态字段,则忽略 obj 变量;它可能为 null。

2.6 Method常用方法

  • 获取方法对象
方法描述
public Annotation[] getDeclaredAnnotations()返回某方法上的所有注解类型
public T getAnnotation(Class annotationClass)返回某方法上指定的注解类型
public int getModifiers()以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符。应该使用 Modifier 类对这些修饰符进行解码。
public Class<?> getReturnType()返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。
public String getName()返回此 Field 对象表示的字段的名称。
public Class<?>[] getParameterTypes()按照声明顺序返回一组 Class 对象,这些对象表示此Method对象所表示方法的形参类型。
public Class<?>[] getExceptionTypes()回一组表示声明throws的异常类型的 Class 对象
public void setAccessible(boolean flag) throws SecurityException将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。
public Object invoke(Object obj,Object… args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。

2.7 Constructor常用方法

  • 获取构造器 创建实例
方法描述
public Annotation[] getDeclaredAnnotations()返回构造器上的所有注解类型
public T getAnnotation(Class annotationClass)返回构造器上指定的注解类型
public int getModifiers()以整数形式返回此 Constructor 对象所表示构造方法的 Java 语言修饰符。应该使用 Modifier 类对这些修饰符进行解码。
public String getName()以字符串形式返回此构造方法的名称。它总是与构造方法的声明类的简单名称相同。
public Class<?>[] getParameterTypes()按照声明顺序返回一组 Class 对象,这些对象表示此 Constructor 对象所表示构造方法的形参类型。
public Class<?>[] getExceptionTypes()回一组表示声明throws的异常类型的 Class 对象
public T newInstance(Object… initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
public void setAccessible(boolean flag) throws SecurityException将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。

2.8 基本操作

2.8.1 操作属性

   private static void demo4() throws Exception {
       // SysUser sysUser = new SysUser().setUsername("张三");
        Class<SysUser> sysUserClass = SysUser.class;
        //对属性取值/赋值
        //public修饰的属性
       Field[] fields = sysUserClass.getFields();
        System.out.println(Arrays.toString(fields));

        //获得类中所有的属性
        fields = sysUserClass.getDeclaredFields();
        System.out.println(Arrays.toString(fields));

        Field field = sysUserClass.getDeclaredField("username");
        //Object get(Object obj)
        field.setAccessible(true);//属性private修饰 默认无法访问 需要开启权限
        Object name = field.get(sysUser);
        System.out.println(name);

        field.set(sysUser, "李四");
        System.out.println(field.getName());
        System.out.println(field.getType());
        System.out.println(field.getModifiers());
        System.out.println(sysUser.getUsername());
    }

2.8.2 操作方法

private static void demo5() throws Exception {
    Class<SysUser> sysUserClass = SysUser.class;
    SysUser sysUser = new SysUser();
    //反射操作方法----->执行方法体逻辑
    //本类/父级public
    Method[] methods = sysUserClass.getMethods();
    System.out.println(Arrays.toString(methods));

    //本类中声明的所有的方法
    methods = sysUserClass.getDeclaredMethods();
    System.out.println(Arrays.toString(methods));

    //执行b方法的方法体逻辑
    Method method = sysUserClass.getMethod("b", String.class, int.class);
    method.setAccessible(true);
    Object returnValue = method.invoke(sysUser, "hello", 200);
    System.out.println("returnValue:"+returnValue);
}

2.8.3 操作构造

private static void test3() {
    Class<SysUser> sysUserClass = SysUser.class;
    //SysUser sysUser = sysUserClass.newInstance();

    //获得类中public修饰的构造方法
    Constructor<?>[] constructors = sysUserClass.getConstructors();
    System.out.println(Arrays.toString(constructors));

    //获得类中声明的所有构造方法
    constructors = sysUserClass.getDeclaredConstructors();
    System.out.println(Arrays.toString(constructors));

    //创建对象
    //获得一个构造
    try {
            Constructor<SysUser> constructor = sysUserClass.getDeclaredConstructor(int.class, int.class);
            constructor.setAccessible(true);
            SysUser sysUser = constructor.newInstance(1, 2);
            System.out.println(sysUser);
        } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    SysUser sysUser = sysUserClass.getConstructor().newInstance();

}

2.8.4 操作注解

// @Target(value = {ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
// 前提: 注解参数只有1个 参数名value的时候 value可以省略的
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno {
    //注解的组成部分
    //属性/参数
    //数据类型 属性名称() [default 默认的数据];   看成了抽象方法
    //数据类型: 1. 字面量数据类型(基本+String)  2. 枚举类型  3. Class类型 4. 以上类型的数组类型
    //模拟注册用户
    int id();

    String name();

    double balance() default 100;

    String[] hobby() default {"code", "sleep"};

    Month birthday() default Month.JANUARY;

    Class<?> clazz() default SysUser.class;
}
@MyAnno(id = 1001,name = "张三",birthday = Month.APRIL)
@Setter
@Getter
public class Demo {
    private int num;

    @MyAnno(id=1002,name = "李四",hobby = {"music","game"},clazz = Object.class)
    public void info(){
    }
}
public static void main(String[] args) throws Exception {
        //使用注解: 获得注解里面参数数据
        //程序只要启动,批量的扫描所有的注解,存储的容器中。
        //反射---->Class
        //获得Demo类上的注解  获得注解里面的参数
        Class<Demo> demoClass = Demo.class;
        //RetentionPolicy.RUNTIME
        Annotation[] annotations = demoClass.getAnnotations();
        System.out.println(Arrays.toString(annotations));

        annotations =  demoClass.getDeclaredAnnotations();
        System.out.println(Arrays.toString(annotations));*/

        MyAnno annotation = demoClass.getAnnotation(MyAnno.class);
        System.out.println(annotation.id());
        System.out.println(annotation.name());
        System.out.println(Arrays.toString(annotation.hobby()));
        System.out.println(annotation.birthday());
        System.out.println(annotation.balance());
        System.out.println(annotation.clazz());

        Method method = demoClass.getMethod("info");
        MyAnno annotation = method.getAnnotation(MyAnno.class);
        System.out.println(annotation.id());
        System.out.println(annotation.name());
        System.out.println(Arrays.toString(annotation.hobby()));
        System.out.println(annotation.birthday());
        System.out.println(annotation.balance());
        System.out.println(annotation.clazz());
    }

http://www.kler.cn/a/585159.html

相关文章:

  • 详解数据库范式
  • 《Java对象“比武场“:Comparable与Comparator的巅峰对决》
  • app.config.globalProperties
  • 语言识别模型whisper学习笔记
  • Odoo Http鉴权+调用后端接口
  • 爱普生车规级晶振SG2520CAA智能汽车电子系统的应用
  • JavaScript 函数基础
  • 【区块链】btc
  • HTTP 各版本协议简介
  • 交易所开发:数字市场的核心动力
  • HDU 学数数导致的
  • pjsip pjsua_media_config 结构体说明
  • 【MySQL】表的约束(上)
  • 如何筛选能实现共享自助健身房“灵活性”的物联网框架?
  • Java8的新特性
  • mov格式视频如何转换mp4?
  • C++ 左值(lvalue)和右值(rvalue)
  • DSTTN
  • Kafka×DeepSeek:智能决策破取经八十一难!
  • 批量压缩与优化 Excel 文档,减少 Excel 文档大小