JavaSE——反射内容大全

一、类加载

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。

不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。

类加载:

  • 将class文件读入内存,并为之创建一个java.lang.Class对象

  • 任何类被使用,系统都会为之创建一个java.lang.Class对象

类连接:

  • 验证阶段:用于检验被加载的类是否有正确的内部结果,和其他类协调一致

  • 准备阶段:负责为类的类变量分配内存,并设置默认初始化值

  • 解析阶段:将类的二进制数据中的符号引用替换为直接引用

类的初始化:

  • 主要对类变量进行初始化

类初始化的步骤:

  • 假如类还未被加载和连接,则程序先加载并连接该类

  • 假如该类的直接父类还未被初始化,则先初始化其父类(所以Java虚拟机最先初始化的是Object类)

  • 假如类中有初始化语句,则系统依次执行这些初始化语句

注意:在执行第二个步骤时,系统对直接父类的初始化步骤也要遵循上面三步

类的初始化时机:

一定是首次,因为一个类一旦被载入虚拟机,同一个类就不会再次被载入

  • 创建类的实例

  • 调用类的类方法(如静态方法)

  • 访问类或者接口的类变量,或者为该类变量赋值

  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

  • 初始化某个类的子类(因为初始化子类一定会先初始化父类)

  • 直接使用java.exe命令来运行某个主类

也可以看一下下面的文章更深入的了解一下
https://blog.csdn.net/weixin_51351637/article/details/127704624

二、类加载器

什么是类加载器?

专门负责加载类的命令/工具

负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象

JDK自带3个类加载器

  • 启动类加载器 (父加载器)

  • 扩展类加载器 (母加载器)

  • 应用类加载器

双亲委派机制(一种安全机制):

  • java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器加载,这个成为“父加载器”,

  • 当父加载器无法加载到,再从扩展类加载器中加载,这个成为"母加载器"。

  • 双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,知道加载到为止

代码在开始执行之前,会将所需要的类全部加载到JVM当中,通过类加载器加载

比如:
String s = "abc";
类加载器会找到String.class文件进行加载。
加载时首先会从启动类加载器加载: 专门加载jdk\jre\lib\rt.jar 的包,里面都是JDK最核心的类库

启动类加载器加载不到的时候。会通过扩展类加载器加载:jdk\jre\lib\ext\*.jar 的jar包

如果扩展类加载器中也加载不到的话,会通过应用类加载器加载:应用类加载器专门加载classPath的jar包

类加载机制

三、反射

高级框架底层实现原理,都采用了反射机制,挺重要的

反射的作用:可以操作字节码文件(.class文件)

java.lang. Class 代表 字节码文件,代表整个类
java.lang.reflect. Method 代表字节码中的 方法字节码,代表类中的方法
java.lang.reflect. Constructor 代表字节码中的 构造方法字节码,代表类中的构造方法
java.lang.reflect. Field 代表字节码中的 属性字节码,代表类中的成员变量

3.1 获取Class的三种方式

3.1.1 第一种方式

//      此时c1就代表java.lang.String的class文件,或者说c1代表String类型
//      静态方法
//      方法的参数是一个字符串,字符串需要一个带有包名的完成类名
        Class c1 = Class.forName("java.lang.String");

3.1.1.1 静态代码块执行Class.forName(完整类名)

如果我们只希望一个类的静态代码块执行,其它代码一律不执行,那我们可以使用Class.forName(完整类名)

public class ReflectTest04 {
    public static void main(String[] args) throws ClassNotFoundException {
//   Class.forName的执行会导致类加载,类加载的执行必然会导致静态代码块的执行
     Class.forName("com.example.reflect.MyClass");
    }
}

class MyClass {
    //  静态代码块在类加载时执行,并且只执行一次
    static {
        System.out.println("静态代码块执行了");
    }
}

3.1.2 第二种方式

//      我们的老祖宗Object类中有一个方法,getClass()
//         我们是字符串类型调用的getClass方法,那这个x指的就是String.class字节码文件或者说代表String类型
        String s = "aaa";
        Class x = s.getClass();

那我们同样都是String的字节码文件,那x和c1是否相等?

        System.out.println(x ==c1 );

内存图如下所示:

3.1.3 第三种方式

//      第三种方式,java语言中任何一个类型,包括基本数据类型,都有.class属性
//        下面的z代表String类型
        Class z = String.class;

3.2 通过反射实例化对象

通过反射机制,获取class,通过Class来实例化对象


//      通过反射机制,获取class,通过class来实例化对象
        Class c = Class.forName("com.example.reflect.User");
//      使用newInstance()创建对象,此方法会调用User这个类的无参数构造方法,完成对象的创建
        Object obj = c.newInstance();
        System.out.println(obj);

public class User {
    public User(){
        System.out.println("无参构造方法");
    }
}

此处还可以添加泛型

这样我们创建出来的对象就是User对象

//      通过反射机制,获取class,通过class来实例化对象
        Class<User> c =  (Class<User>)Class.forName("com.example.reflect.User");
//      使用newInstance()创建对象,此方法会调用User这个类的无参数构造方法,完成对象的创建
        User user = c.newInstance();

那如果我们User并没有提供无参构造器,而是提供了一个有参构造器,这样会出现什么情况呢?

出现实例化异常,没有这个无参构造

3.2.1 验证反射机制的灵活性

java代码写一遍,不改变java源代码的基础上,可以做到不同对象的实例化。

符合OCP开闭原则:对扩展开发,对修改关闭

创建配置文件

读取属性文件实例化对象

public class ReflectTest03 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//      通过IO流读取classinfo.properties
        FileReader reader = new FileReader("D:\\project\\springboot\\Swagger\\classinfo.properties");
//      创建属性类对象Map
        Properties pro = new Properties();
//      加载,此时文件中的内容就加载到map集合中了
        pro.load(reader);
//      关闭流
        reader.close();
//      通过key获取value
        String  className = pro.getProperty("className");
        System.out.println(className);

//       通过反射机制实例化对象
//         获取className的字节码文件
        Class<?> c = Class.forName(className);
//      创建对象
        Object o = c.newInstance();
        System.out.println(o);

    }
}

下面我们修改一下properties的内容

className=java.util.Date

不动java代码再输出

3.2.2获取类路径下的绝对路径

面这样写路径有一个缺点,移植性差,在IDEA中默认的当前路径是project的根

那就是只能在idea中找到对应的文件,如果我们把代码换到其他的地方,当前路径就不是project的根了,这是这个路径就无效了。

FileReader reader = new FileReader("Swagger/classinfo.properties");

下面写一种通用的路径,即使代码换位置了,这样的编写仍是通用的,但是!!!文件只能在类路径下

凡是在src目录下的都是类路径下
记住!!!!一定确定好根目录下
getResource(String name),其中name启动是从根路径开始写!!!!!一定注意,否则会出现空指针异常

这样的好处是,不论以后移植到什么环境、谁的电脑都能获取path内容,类似动态获取

    public class AboutPath {
    public static void main(String[] args) {
//      Thread.currentThread() 获取当前线程对象
//        getContextClassLoader()是线程对象的方法,可以获取到当前线程的类加载器对象(类加载器挺多的)
//        getResource(String name)方法是类加载器的方法,当前线程的类加载器默认从类的根路径下(src目录下)加载资源
        String path=Thread.currentThread().getContextClassLoader().getResource("classinfo2.properties").getPath();
        System.out.println(path);
    }
}

假如是maven项目呢? 如下所示

        String path=Thread.currentThread().getContextClassLoader().getResource("classinfo3.properties").getPath();

3.2.3 灵活获取路径进行反射

这个程序相当于将3.2.1与3.2.2结合起来了

public class IoPropertiesTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//      获取path路径
        String path=Thread.currentThread().getContextClassLoader().getResource("classinfo3.properties").getPath();

//      通过IO流读取classinfo.properties
        FileReader reader = new FileReader(path);
//      创建属性类对象Map
        Properties pro = new Properties();
//      加载,此时文件中的内容就加载到map集合中了
        pro.load(reader);
//      关闭流
        reader.close();
//      通过key获取value
        String  className = pro.getProperty("className");
        System.out.println(className);

//       通过反射机制实例化对象
//         获取className的字节码文件
        Class<?> c = Class.forName(className);
//      创建对象
        Object o = c.newInstance();
        System.out.println(o);
    }
}

但是,我们上面的程序还能再简单一点点

由两行代码编程一行

//      获取path路径
//        String path=Thread.currentThread().getContextClassLoader().getResource("classinfo3.properties").getPath();
//      通过IO流读取classinfo.properties
//        FileReader reader = new FileReader(path);
//      直接以流的形式返回,这个地方还是类路径下
        InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("classinfo3.properties");

3.2.4 补充:资源绑定器

java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容(如果是Maven工程,则是获取的resource目录下的内容),但是条件有一点苛刻,文件的扩展名必须是properties结尾

public class ResourceBundleTest {
    public static void main(String[] args) {
//      前提:类路径下,扩展名必须是properties,并且在写路径的时候,不写文件的扩展名
        ResourceBundle bundle = ResourceBundle.getBundle("classinfo3");
        String className = bundle.getString("className");
        System.out.println(className);
    }
}


再补充: 和上面没有关系
当我们接触springboot之后很少用这种文件,而是用yaml形式,如下文章所示
https://blog.csdn.net/weixin_51351637/article/details/124048275

或者使用下面的Environment对象进行获取yaml结尾的文件

    //import org.springframework.core.env.Environment;
    @Autowired
    private Environment environment;
    @Test
    public void methodTest(){
        String personName = environment.getProperty("person.name");
        String hobby = environment.getProperty("person.hobby[2]");
        System.out.println("name = " + name);
        System.out.println("hobby = " + hobby);
    }

3.3 获取Field—反射属性

3.3.1创建实体类并获取

public class Student {
//  4个Field采用不同的访问权限控制符
    public int no;
    private String name;
    protected int age;
    boolean sex;

    private static final double MATH_PI = 3.1415926;
}

获取整个类

//      获取整个类
        Class studentClass = Class.forName("com.example.reflect.Student");
        System.out.println("完整类名:"+studentClass.getName());
        System.out.println("简类名:"+studentClass.getSimpleName());

3.3.2 获取public 修饰的Field

        Field[] fields = studentClass.getFields();
        System.out.println("studentClass.getFields() 获取的数组长度:"+fields.length);  //长度是2,说明只有2个元素
        Field f = fields[0];
        System.out.println(f.getName());

3.3.3 获取所有Field字段并进行解析

      //获取所有的Field 
       Field[] declaredField = studentClass.getDeclaredFields();
       
       System.out.println("studentClass.getDeclaredFields() 获取的数组长度:"+declaredField.length); //5

//      对所有的Field挨个解析        
        for (Field field : declaredField){
//          修饰符列表可能有多个,比如 public static final double MATH_PI = 3.1415926 中   public static final 就是修饰符列表
            int modifiers = field.getModifiers(); //返回的修饰符是一个数字,每个数字是修饰符的代号
//            可以将这个数字转换成字符串嘛?可以,如下所示
            System.out.println("获取属性field的修饰符列表"+ Modifier.toString(modifiers));
            System.out.println("获取属性field的类型"+field.getType());
            System.out.println("获取属性field的名字:"+field.getName());
            System.out.println("************************");
         }

3.3.4 获得具体的某个字段

public class ReflectTest07 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//      获取整个类
        Class studentClass = Class.forName("com.example.reflect.Student");

//      获取对象
        Object obj = studentClass.newInstance();
//      获取name属性
        Field noField = studentClass.getDeclaredField("name");
        System.out.println(noField);
    }
}

3.3.5 反编译Field

将下面这个类反编译出来,看一下反射机制的威力

public class Student {
//  4个Field采用不同的访问权限控制符
    public int no;
    private String name;
    protected int age;
    boolean sex;

    private static final double MATH_PI = 3.1415926;
}

public class ReflectTest06 {
    public static void main(String[] args) throws ClassNotFoundException {
//      获取整个类
        Class studentClass = Class.forName("com.example.reflect.Student");
//      用于下面的字符串拼接
        StringBuilder stringBuilder = new StringBuilder();

//      类名开始
        stringBuilder.append("public class " + studentClass.getSimpleName() + " {\n");

//      获取所有字段
        Field[] fields = studentClass.getDeclaredFields();

//      解析所有字段
        for (Field field : fields) {
//          修饰符列表
            stringBuilder.append(Modifier.toString(field.getModifiers())+" ");
//          类型
            stringBuilder.append(field.getType().getSimpleName()+" ");
//          字段名
            stringBuilder.append(field.getName()+";\n");
        }
//     类的结束
        stringBuilder.append("\n}");
        System.out.println(stringBuilder);
    }
}

很完美啊!!!!!!!相当于获取源代码

3.3.6 通过反射机制访问对象属性

public class ReflectTest07 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//      获取整个类
        Class studentClass = Class.forName("com.example.reflect.Student");

//      虽然我们不是直接new对象,但是三要素还是缺一不可:obj对象、属性、字段值
//      获取对象
        Object obj = studentClass.newInstance();
//      获取no属性,如果这个属性是private修饰的,将访问不到,依然是按照之前的访问原则进行
        Field noField = studentClass.getDeclaredField("no");
//      给no属性赋值   这段话的意思是给obj对象的no属性赋值为2222
        noField.set(obj,2222);

//     读取属性的值
        System.out.println(noField.get(obj));
    }
}

如果我们访问一下私有的属性呢?

很显然是出现异常,private修饰的访问不到

        Field nameField = studentClass.getDeclaredField("name");
        nameField.set(obj,"zhangingqi");

那还有没有其他的办法?

有!!打破封装!但是打破封装可能会给不法分子留下机会(反射的缺点)

        Field nameField = studentClass.getDeclaredField("name");
//      打破封装
        nameField.setAccessible(true);
        nameField.set(obj,"zhangingqi");
        System.out.println(nameField.get(obj));

3.5 反射Method及反编译Method

public class ReflectTest08 {
    public static void main(String[] args) throws ClassNotFoundException {
//      获取整个类
        Class userServiceClass = Class.forName("com.example.reflect.UserService");

//      这个语句获取了所有的方法,包括Object类的
//      Method[] methods = userServiceClass.getMethods();

//      获取所有的Method(包括私有的)
        Method[] methods = userServiceClass.getDeclaredMethods();
        System.out.println(methods.length);
        for (int i=0 ; i<methods.length;i++){
//          获取形式参数列表
            int modifiers = methods[i].getModifiers();

            System.out.print(Modifier.toString(modifiers)+" ");
//          获取返回值类型
            System.out.print(methods[i].getReturnType()+" ");
//          获取方法名
            System.out.print(methods[i].getName()+" ");
//          获取修饰符列表(一个方法的参数可能会有多个)
            Class<?>[] parameterTypes = methods[i].getParameterTypes();
            for (Class parameterType: parameterTypes){
                System.out.print(parameterType.getSimpleName()+" ");
            }
            System.out.println();
        }
    }
}

3.5.1 通过反射机制调用方法(反射机制中最重要的)

public class ReflectTest09 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        //      获取整个类
        Class userServiceClass = Class.forName("com.example.reflect.UserService");

//      获取对象
        Object obj = userServiceClass.newInstance();
        Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
//      给形式参数列表赋值
        Object retValue = loginMethod.invoke(obj,"admin", "123");
        System.out.println(retValue);
    }
}
public class UserService {
    int no;
    int age;
    //登录
    public boolean login(String name,String password){
        if("admin".equals(name) && "123".equals(password)){
            return true;
        }
        return false;
    }
    //退出
    public void logout(){
        System.out.println("系统已经安全退出");
    }
}

3.6 反射Constructor

3.6.1 反编译Constructor

public class ReflectTest11 {
    public static void main(String[] args) throws ClassNotFoundException {
        StringBuilder s = new StringBuilder();
        Class<?> vipClass = Class.forName("com.example.reflect.Vip");
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class");
        s.append(" " + vipClass.getSimpleName() + " {\n");

//      拼接构造方法
        Constructor<?>[] declaredConstructors = vipClass.getDeclaredConstructors();
        for (Constructor constructor : declaredConstructors) {
//        修饰符列表
            s.append(Modifier.toString(constructor.getModifiers()) + " ");
//        构造器名
            s.append(vipClass.getSimpleName());
            s.append("(");
//          拼接形式参数列表
            Class[] parameterTypes = constructor.getParameterTypes();
            for (Class parameterType : parameterTypes) {
                s.append(parameterType.getSimpleName() + ",");
            }
//          去掉最后一个逗号
            if (declaredConstructors.length > 0) {
                s.deleteCharAt(s.length() - 1);
            }
            s.append("){}\n");
        }
        s.append("\n}");
        System.out.println(s);
    }
}

3.6.2 反射机制调用构造方法

public class ReflectTest12 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> c = Class.forName("com.example.reflect.Vip");
//      调用无参构造方法
        Object obj = c.newInstance();
//      调用有参数构造方法
//        第一步:先获取有参数构造方法
        Constructor<?> con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
//        第二步:调用构造方法new对象
        con.newInstance(1,"2222","22333",false);

//      另一种方式无参构造方法
        Constructor<?> con2 = c.getDeclaredConstructor();
        con2.newInstance();
    }
}

3.7 获取父类和父接口

public class ReflectTest13 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> stringClass = Class.forName("java.lang.String");
//      获取String的父类
        Class<?> superclass = stringClass.getSuperclass();
//      拿到父类
        System.out.println(superclass.getName());

//      获取String类实现的所有接口
        Class<?>[] interfaces = stringClass.getInterfaces();
        for(Class interfaceClass : interfaces){
            System.out.println(interfaceClass.getName());
        }
    }
}

3.8 反射注解

//只允许该注解可以标注类、方法
@Target({ElementType.TYPE,ElementType.METHOD})
//希望这个注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

}

@MyAnnotation
public class MyAnnotationTest {
   int i;

   @MyAnnotation
   public void doSome(){
   }

    public MyAnnotationTest() {
    }
}

public class ReflectAnnotationTest {
    public static void main(String[] args) throws ClassNotFoundException {
//      获取类
        Class<?> c = Class.forName("com.example.annotation2.MyAnnotationTest");
//      判断类上是否有@MyAnnotation
        boolean isHaveMyAnnotation = c.isAnnotationPresent(MyAnnotation.class);

        if (isHaveMyAnnotation) {
//           获取该注解
            MyAnnotation myAnnotation = c.getAnnotation(MyAnnotation.class);
            System.out.println("类上面的注解对象:" + myAnnotation);
//            获取注解对象的属性
            System.out.println("注解对象的属性value:"+myAnnotation.value());
        }
    }
}

3.8.1 通过反射获取注解对象属性的值

四、可变长参数

  • int... args 这就是可变长度参数

  • 语法: 类型...

    public static void m(int... args) {
    }

  • 可变长度参数要求的参数个数是:0~N个

    public static void main(String[] args) {
        m();
        m(10);
        m(10, 20, 30);
    }

    //可变长度参数,类型后面加三个点
    //可变长度参数要求的参数个数是:0~N个
    public static void m(int... args) {
        System.out.println("m方法执行了!");
    }

  • 可变长度参数在参数列表中必须在最后一个位置上,并且可变长度参数只能有一个

//    下面这样写会出现错误,并且说明,可变长参数必须是在列表的最后一个
//    public static  void m2(String... args1,int... args2){
//    }

//  这样写是完全没有问题的
    public static void m3(String m ,int... args){

    }

  • 访问可变长度参数

    public static void main(String[] args) {
        m(10, 20, 30);
    }

    //可变长度参数,类型后面加三个点
    //可变长度参数要求的参数个数是:0~N个
    public static void m(int... args) {
        for(int i=0;i< args.length;i++){
            System.out.println(args[i]);
        }
    }

五、注解 Annotation

5.1 注解介绍

  • 注解Annotation 是一种引用数据类型,编译之后也生成xxx.class文件

  • 自定义注解的语法格式

修饰符列表 @interface 注解类型名{}

//自定义注解
public @interface MyAnnotation {
}

  • 注解使用语法格式

@注解类型名

注解可以出现在类上、属性上、方法上、变量上等....

注解还可以出现在注解类型上

@MyAnnotation
public class AnnotationTest01 {
    @MyAnnotation
    private int no;

    @MyAnnotation
    public void m(){
        @MyAnnotation
        int i =100;
    }
}

5.2 元注解

用来标注"注解类型"的注解,成为元注解

常见的元注解:@Target、@Retention,如下所示

  • @Target注解用来标注“被标注的注解”可以出现在哪些位置上

@Target(ElementType.METHOD)表示被标注的注解出现在方法上

  • @Retention用来标注“被标注的注解”最终可以保存在哪

@Retention(RetentionPolicy.SOURCE): 该注解只被保留在java源文件中

@Retention(RetentionPolicy.CLASS): 表示该注解被保存在class文件中

@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

5.3 @Deprecated注解

但是不鼓励这么使用,因为它很危险或者存在更好的选择

如果用在方法上表示这个方法已经过时

public class AnnotationTest03 {
    public static void main(String[] args) {
        AnnotationTest03.doSome();
    }

    @Deprecated
    public static void doSome() {
        System.out.println("do something....");
    }

    public static void doOther() {
        System.out.println("do other....");
    }
}

5.4 注解中的属性

5.4.1 定义属性并使用

//自定义注解
public @interface MyAnnotation {
    /**
     *  我们通常在注解当中定义属性,以下是name属性
     *      看着像方法但其实不是
     * @return
     */
    String name() default "";

//  如果我们没有给age属性赋值,则默认为25
    int age() default 25;
}
public class MyAnnotationTest {
    public static void main(String[] args) {

    }
//  如果一个注解当中有属性,必须给属性赋值
    @MyAnnotation(name= "zhangingqi")
    public void doSome(){
    }
}

5.4.2 属性是value时可以省略

public @interface MyAnnotation {

    String value() default "";
}

5.4.3 属性支持的类型

byte、short、int、long、float、double、boolean、char、String、Class、枚举类型以及以上每一种数组形式

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱布朗熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值