Scala动态创建对象,scala反射

探索后端领域 Scala 的反射机制应用 在后端开发中,我们经常遇到这样的需求:写一个通用框架,能处理任意类的对象(比如JSON序列化工具);或者在运行时根据配置动态创建对象(比如依赖注入容器)。这些场景都需要程序在“运行时”获取类的信息并操作它——这就是反射机制的用武之地。用小学生能听懂的话讲清Scala反射的核心原理,通过实战案例掌握它在后端开发中的典型应用。我们会重点讨论Scala反射与Java反射的区别、TypeTag如何解决泛型“失忆症”、Mirror系统如何操作类结构,以及反射在依赖注入、ORM等后端场景中的具体用法。 阅读详情

一、动态对象创建方法

方法1–java的迁移

//可带参数
val clsFullName="你要的类名路径"//例如:com.xlt.test.Fruits
 val taskClass = if (customizedClassLoader != null) customizedClassLoader.loadClass(clsFullName)
        else this.getClass.getClassLoader.loadClass(clsFullName)
 val constructor = taskClass.getConstructor(arg1.getClass,arg2.getClass,arg3.getClas...) //为了传递参数,获取参数类型
 val cls = constructor.newInstance(arg1,arg2,arg3...).asInstanceOf[YourClass]//YourClass可为clsFullName的父类
 

方法2–scala原生

在Scala-2.10以前, 只能在Scala中利用Java的反射机制, 但是通过Java反射机制得到的是只是擦除后的类型信息, 并不包括Scala的一些特定类型信息. 从Scala-2.10起, Scala实现了自己的反射机制, 我们可以通过Scala的反射机制得到Scala的类型信息。

import scala.reflect.runtime.universe._
val clsFullName="你要的类名路径"//例如:com.xlt.test.Fruits

def getObjectInstance(className: String,arg1:T1,arg2:T2...): Any = {
  val mirror = runtimeMirror(getClass.getClassLoader)
  val classSymbol = mirror.staticClass(className)
  val classMirror = mirror.reflectClass(classSymbol)
  val constructorSymbol = classSymbol.primaryConstructor.asMethod
  val constructorMirror = classMirror.reflectConstructor(constructorSymbol)
  constructorMirror(arg1,arg2...)
}
 val cls = getObjectInstance(clsFullName).asInstanceOf[YourClass] //YourClass可为clsFullName的父类

【细节解释详见我的另一篇文章】Scala的类型擦除 和 TypeTags、Manifests的用法,反射

在这里插入图片描述

二、java类加载区别

1、由 new 关键字创建一个类的实例
在由运行时刻用 new 方法载入
如:Dog dog = new Dog();
2、调用 Class.forName() 方法
通过反射加载类型,并创建对象实例
如:Class clazz = Class.forName("Dog");
Object dog =clazz.newInstance()
3、调用某个 ClassLoader 实例的 loadClass() 方法
通过该 ClassLoader 实例的 loadClass() 方法载入。应用程序可以通过继承 ClassLoader 实现自己的类装载器。
如:Class clazz = classLoader.loadClass("Dog");
Object dog =clazz.newInstance()

三者的区别:
1和2使用的类加载器是相同的,都是当前类加载器。(即:this.getClass.getClassLoader),然后再执行classLoader.loadClass("Dog")
3由用户指定类加载器。如果需要在当前类路径以外(不同jar包?)寻找类,则只能采用第3种方式。
第3种方式加载的类与当前类分属不同的命名空间。

另外,1是静态加载,2、3是动态加载

两个异常(exception)
静态加载的时候如果在运行环境中找不到要初始化的类,抛出的是NoClassDefFoundError,它在JAVA的异常体系中是一个Error
动态态加载的时候如果在运行环境中找不到要初始化的类,抛出的是ClassNotFoundException,它在JAVA的异常体系中是一个checked异常

Class.forName与ClassLoader.loadClass区别
Class的装载包括3个步骤:加载(loading),连接(link),初始化(initialize).
Class.forName(className)实际上是调用Class.forName(className, true, this.getClass().getClassLoader())。第二个参数,是指Class被loading后是不是必须被初始化。
ClassLoader.loadClass(className)实际上调用的是ClassLoader.loadClass(name, false),第二个参数指Class是否被link。
Class.forName(className)装载的class已经被初始化,而ClassLoader.loadClass(className)装载的class还没有被link。一般情况下,这两个方法效果一样,都能装载Class。但如果程序依赖于Class是否被初始化,就必须用Class.forName(name)了。
例如,在JDBC编程中,常看到这样的用法,Class.forName(“com.mysql.jdbc.Driver”).
如果换成了getClass().getClassLoader().loadClass(“com.mysql.jdbc.Driver”),就不行。
com.mysql.jdbc.Driver的源代码如下:

// Register ourselves with the DriverManager
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException(“Can’t register driver!”);
}
}

原来,Driver在static块中会注册自己到java.sql.DriverManager。而static块就是在Class的初始化中被执行。
所以这个地方就只能用Class.forName(className)。

三、Scala 反射

什么是反射

我们知道, Scala是基于JVM的语言, Scala编译器会将Scala代码编译成JVM字节码, 而JVM编译过程中会擦除一些泛型信息, 这就叫类型擦除(type-erasure ).

而我们开发过程中, 可能需要在某一时刻获得类中的详细泛型信息. 并进行逻辑处理. 这时就需要用的 这个概念. 通过反射我们可以做到

  1. 获取运行时类型信息
  2. 通过类型信息实例化新对象
  3. 访问或调用对象的方法和属性等

Scala 反射的分类

Scala 的反射分为两个范畴:

  • 运行时反射
  • 编译时反射

这两者之间的区别在于Environment, 而Environment又是由universe决定的. 反射的另一个重要的部分就是一个实体集合,而这个实体集合被称为mirror,有了这个实体集合我们就可以实现对需要反射的类进行对应的操作,如属性的获取,属性值得设置,以及对反射类方法的调用(其实就是成员函数的入口地址, 但请注意, 这只是个地址)!

对于不同的反射, 我们需要引入不同的Universes

import scala.reflect.runtime.universe._      // for runtime reflection
import scala.reflect.macros.Universe._       // for compile-time reflection

编译过程

类Java程序之所以能实现跨平台, 主要得益于JVM(Java Virtual Machine)的强大. JVM为什么能实现让类Java代码可以跨平台呢? 那就要从类Java程序的整个编译、运行的过程说起.

我们平时所写的程序, 都是基于语言(第三代编程语言)范畴的. 它只能是开发者理解, 但底层硬件(如内存和cpu)并不能读懂并执行. 因此需要经历一系列的转化. 类Java的代码, 首先会经过自己特有的编辑器, 将代码转为.class, 再由ClassLoader将.class文件加载到JVM运行时数据区, 此时JVM就可以读懂.class的二进制文件, 并调用C/C++来间接操作底层硬件, 实现代码功能.

.scala => .class 过程

在这里插入图片描述、

运行时反射

Scala运行时类型信息是保存在TypeTag对象中, 编译器在编译过程中将类型信息保存到TypeTag中, 并将其携带到运行期. 我们可以通过typeTag方法获取TypeTag类型信息。

TypeTag获取类型信息(不擦除类型)

scala>  import scala.reflect.runtime.{universe => ru}
import scala.reflect.runtime.{universe=>ru}

scala>  val k = List(1,2,3)
k: List[Int] = List(1, 2, 3)

scala> def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T]
getTypeTag: [T](obj: T)(implicit evidence$1: reflect.runtime.universe.TypeTag[T])reflect.runtime.universe.TypeTag[T]

scala> val theType = getTypeTag(k).tpe
theType: reflect.runtime.universe.Type = List[Int]

//一旦我们获得了所需的类型实例,我们就可以将它解析出来,例如:
scala> val decls = theType.decls.take(10)
decls: Iterable[ru.Symbol] = List(constructor List, method companion, method isEmpty, method head, method tail, method ::, method :::, method reverse_:::, method mapConserve, method ++)

def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T]中的T: ru.TypeTag叫做上下文界定(或上下文边界)【详见 第21章第4节 以及第19章的泛型】,等价于 getTypeTag[T](obj:T)(implicit tmp:TypeTag[T])。

Scala运行时类型信息是保存在TypeTag对象中, 编译器在编译过程中将类型信息保存到TypeTag中, 并将其携带到运行期. 也就是说, Scala编译器会自动为我们生成一个T => TypeTag[T](即List[Int] => TypeTag[List[Int]])的转换。

ClassTag获取类型信息(擦除类型)

而如果想要获得擦除后的类型信息, 可以使用ClassTag。
注意,classTag 在包scala.reflect._下

scala> import scala.reflect._
import scala.reflect._

scala>  def geteClassTag[T: ClassTag](obj: T) = classTag[T]
geteClassTag: [T](obj: T)(implicit evidence$1: scala.reflect.ClassTag[T])scala.reflect.ClassTag[T]

scala>  val k = List(1,2,3)
k: List[Int] = List(1, 2, 3)

scala> getTypeTag(k)
res0: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[List[Int]]

scala>  geteClassTag(k).runtimeClass
res1: Class[_] = class scala.collection.immutable.List

scala> classOf[List[Int]]
res2: Class[List[Int]] = class scala.collection.immutable.List

实例化一个类型

通过反射获得的类型,可以通过使用适当的“调用器”镜像调用它们的构造函数来实例化(镜像mirros的概念在后续文档中说明)。

scala> case class Fruits(id: Int, name: String)
defined class Fruits

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

// 获得当前JVM中的所有类镜像
scala> val rm = runtimeMirror(getClass.getClassLoader)
rm: reflect.runtime.universe.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@566edb2e of .....

// 获得`Fruits`的类型符号, 并指定为class类型
scala> val classFruits = typeOf[Fruits].typeSymbol.asClass
classFruits: reflect.runtime.universe.ClassSymbol = class Fruits

// 根据上一步的符号, 从所有的类镜像中, 取出`Fruits`的类镜像
val cm = rm.reflectClass(classFruits)
cm: reflect.runtime.universe.ClassMirror = class mirror for Fruits (bound to null)

// 获得`Fruits`的构造函数, 并指定为asMethod类型
scala> val ctor = typeOf[Fruits].declaration(nme.CONSTRUCTOR).asMethod
ctor: reflect.runtime.universe.MethodSymbol = constructor Fruits

// 根据上一步的符号, 从`Fruits`的类镜像中, 取出一个方法(也就是构造函数)
scala> val ctorm = cm.reflectConstructor(ctor)

// 调用构造函数, 反射生成类实例, 完成
scala> ctorm(1, "apple")
res2: Any = Fruits(1,apple)

类名可通过参数传递

staticClass要求是类的全路径

// 类名可配置
//在另一个文件Fruits.scala 中编辑如下代码
package com.xlt.test
case class Fruits(id: Int, name: String)

不放到单独文件中做成静态类的话,会报如下错误:
scala.ScalaReflectionException: class Fruits not found.
  at scala.reflect.internal.Mirrors$RootsBase.staticClass(Mirrors.scala:141)
  at scala.reflect.internal.Mirrors$RootsBase.staticClass(Mirrors.scala:29)
  at .getObjectInstance(<console>:21)
  ... 28 elided
  
// 新文件中引用
import scala.reflect.runtime.universe._

def getObjectInstance(className: String,arg1:Int,arg2:String): Any = {
  val mirror = runtimeMirror(getClass.getClassLoader)
  val classSymbol = mirror.staticClass(className)
  val classMirror = mirror.reflectClass(classSymbol)
  val constructorSymbol = classSymbol.primaryConstructor.asMethod
  val constructorMirror = classMirror.reflectConstructor(constructorSymbol)
  constructorMirror(arg1,arg2...)
}
 val cls = getObjectInstance("com.xlt.test.Fruits",1,"apple").asInstanceOf[Fruits]

Mirror

Mirror是按层级划分的,有

  • ClassLoaderMirror
    • ClassMirror ( => 类)
      • MethodMirror ( => 方法)
      • FieldMirror ( => 成员)
    • InstanceMirror ( => 实例)
      • MethodMirror
      • FieldMirror
    • ModuleMirror ( => Object)
      • MethodMirror
      • FieldMirror

参考

Scala 通过字符串名字在Scala中获取对象实例
Scala 反射动态创建方法的骚操作
Scala的反射API(runtime.universe._)与Java的有哪些不同?
java 动态实例化对象,Scala:如何动态实例化对象并使用反射调用方法?
在Java的反射中,Class.forName 和 this.getClass().getClassLoader().loadClass(“com.test.mytest.ClassFor”)的区别
java类加载的三种方式比较
java 反射机制 之 getConstructor获取有参数构造函数 然后newInstance执行有参数的构造函数
一篇入门 – Scala 反射
【官方】-scala反射
Scala通过类名称字符串构建实例

https://stackoverflow.com/questions/53210198/dynamic-instantiation-of-objects-in-scala-with-arguments-asinstanceof-is-not-ac
https://stackoverflow.com/questions/39134803/how-to-invoke-method-on-companion-object-via-reflection/39135220#39135220

Scala 反射:动态探索和操作代码的力量 在计算机科学中,反射是指在运行时检查、探索和修改程序结构的能力。Scala 反射允许我们在程序运行时获取有关类、对象、方法和字段的信息,甚至可以动态地创建对象、调用方法和访问字段。这种能力使得我们可以编写更加灵活和动态的代码,适应不同的运行时需求。Scala 反射提供了一种强大的机制,使我们能够在运行时动态地探索和操作代码。通过使用反射,我们可以获取类的信息,创建对象,调用方法和访问字段。这种能力为我们提供了更大的灵活性和动态性,使得我们能够根据不同的运行时需求编写更加智能和适应性强的代码。 阅读详情

相关推荐

利用ArcGIS的模型构建器实现批量投影栅格和批量克里金插值

博主要批量投影几百个栅格,但是ArcGIS中的投影栅格的批处理工具需要手动一条条设置参数,还是麻烦,因此学习了使用模型构建器(ModelBuilder)来批量投影栅格,可以导入需投影栅格的文件夹,然后迭代投影,并按栅格原名逐个输出到指定目录。 1.在工具箱里构建模型 在arctool box里右键点击空白处,选择【添加工具箱】,点击ArcGIS默认工作目录的工具箱进行添加,然后Toolbox就会被添加到ArcToolbox页面,右键点击Toolbox,选择“新建”-“模型” 2.编写模型 (1).

qq_36958801的博客 1万+

Scala中的类和对象:定义、创建和使用

在Scala中,我们使用class关键字来定义类,并使用new关键字来创建类的对象。nameage上述代码中,我们定义了一个名为Personname和age。greet方法用于打印出一个问候语,其中包含了对象的name和age属性。然后,我们使用new关键字创建了一个Person对象,并将其赋值给person变量。最后,我们调用person对象的greet方法来打印问候语。在上述代码中,我们创建了一个名为Person的类,并传递了两个参数name和age。然后,我们创建了一个名为person的。

jakelihua 1108

SQL 通过sum() over(order by ,rownum)实现财务现金日记账实例

SELECT 类型,to_char(发生日期,'yyyy-MM-dd') 发生日期,期初余额,借方金额,贷方金额, (CASE WHEN 借方金额>0 THEN '借' WHEN 贷方金额>0 THEN '贷' ELSE '平' END) 方向, sum(收支金额) over(order by 发生日期, rownum) 余额 FROM( SELECT LX 类型,账户,账号,摘要,发生日期,单据ID, ...

qq_41451794的博客 1023

Scala实战专栏 (四) ——— 类和属性

scala-basic 基础篇 @author 鲁伟林 Scala基础知识介绍,主要包括《Scala编程实战》的基础章节 GitHub地址: https://github.com/thinkingfioa/scala-in-action 本人博客地址: http://blog.youkuaiyun.com/thinking_fioa/article/details/78265745 4. 类和属性 ...

thinking_fioa的博客 4807

(Touch™)搭建 Windows 开发环境

> OpenHaptics 开发包参考文档。

置心一处,便无事不办 1500

win2016 php mysql_Windows Server 2016 IIS10.0+PHP(FastCGI)+MySQL环境搭建教程 | 系统运维...

准备篇一、环境说明:操作系统:Windows Server 2016PHP版本:php 7.1.0MySQL版本:MySQL 5.7.17.0二、相关软件下载:1、PHP下载地址:2、MySQL下载地址:3、Microsoft Visual C++ 2010 可再发行组件包(安装MySQL需要此插件)4、Visual C++ Redistributable for Visual Studio 20...

weixin_34794420的博客 1377

自定义类加载器

/** * */ package com.bzu.csh; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; /** * * 项目名称:Test1 类名称:Test2 类描述: 创建人:admin

只作为个人备忘录 329

【数据集8】全球人口数据WorldPop详解

全球人口数据WorldPop详解

WW、forever的博客 7880

Scala中如何创建类和对象,并且运行

println(s"我是$name,$age 岁,性别 :$gender,唱歌 $songName")//private:在类的外部不可以访问,在类的里面可以访问,不可以修改。// gender="男"(在类的里面可以访问private)//定义属性的修饰符有三种:val,var,private。private var gender="女"//在类的内部,可以直接访问属性。//p1.name="小花花"//var:可以访问,可以修改。//val:可以访问,不可修改。//class 类名。

2301_80781830的博客 428

AIGC游戏开发:如何评估生成内容的玩家满意度?

如何量化评估生成内容对玩家体验的影响?本文聚焦AIGC生成的游戏内容(包括但不限于剧情、关卡、NPC对话、道具),系统探讨玩家满意度的评估方法论,覆盖主观与客观评估、静态与动态内容、短期与长期体验等场景。核心概念:定义AIGC与玩家满意度的关联维度;评估方法:主观(问卷/访谈)与客观(行为/生理数据)评估的技术细节;数学模型:构建多指标融合的评估模型;实战案例:以AIGC生成关卡为例,演示从数据采集到模型落地的全流程;工具资源:推荐数据采集、分析与建模的工具链;

AI天才研究院 1224

继电保护原理1.2-反时限过流保护

反时限过流保护

让你爱上电路设计 1万+

Scala 反射动态创建方法的骚操作

有时候我们想定义一个字符串的方法,然后通过scala的动态创建class,然后反射调用方法,在很多情景下是在学有用的,比较动态自定义spark的mapParations,当然了,每个人的需求都不一样,但是底层原理是一样的。 先画饼 运行 object CreateTest{ def main(args: Array[String]): Unit = { val cim = Cla...

多一份贡献,多一份环保 2625

【AUTOSAR-RTE】-1-聊一聊RTE(Run-Time Environment)

RTE(Run-Time Environment)是AUTOSAR ECU架构的核心。

「汽车电子助手」的博客 6502

Scala 创建对象

通过 scala.reflect 包中的 classOf 方法获取类的 Class 对象,然后使用 newInstance 方法创建对象。如果类有伴生对象,并且伴生对象中定义了 apply 方法,可以通过该方法来创建对象。例如,val person = Person() 将调用伴生对象中的 apply 方法来创建一个 Person 对象。特质,可以使用 clone 方法来创建对象。例如,val person = new Person("Ziyu") 将创建一个名为 person 的新 Person 对象。

2401_87076421的博客 314

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Scala的创建对象

通过 scala.reflect 包中的 classOf 方法获取类的 Class 对象,然后使用 newInstance 方法创建对象。如果类有伴生对象,并且伴生对象中定义了 apply 方法,可以通过该方法来创建对象。例如,val person = Person() 将调用伴生对象中的 apply 方法来创建一个 Person 对象。例如,val person = new Person("Ziyu") 将创建一个名为 person 的新 Person 对象。这种方式常用于网络通信或持久化存储。

Wessys的博客 173

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

如何用Scala反射实现运行时动态逻辑?5个真实工程示例告诉你答案

掌握Scala反射技巧,轻松实现运行时动态逻辑。本文提供5个真实工程中的Scala反射示例,涵盖对象创建、方法调用与字段访问,适用于插件系统与配置驱动场景,提升代码灵活性与扩展性,值得收藏

FuncTide的博客 866

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

WebViewJavascriptBridge 通信机制拆解:一条消息的完整路径与四道防线

WebViewJavascriptBridge 是一座架在 iOS/OSX WebView 与原生代码之间的消息桥:网页里的 JavaScript 能发起对原生能力的调用,原生侧也能把数据推进页面、并拿到 JS 的返回结果。下面跟着一次真实的 `callHandler` 调用走完全程,把通信机制里每个零件的位置交代清楚。 ## 网页想碰原生能力,卡在哪 WebView 里的 JS 被沙在页面里

gitblog_01082的博客 493

Spark系列---SparkSQL(四)动态与反射方式创建DataFrame(Java、Scala版本)

1.动态创建Scheme将非json格式RDD转换成DataFrame,推荐 scala版本 package com.kevin.scala.dataframe import org.apache.spark.sql.types._ import org.apache.spark.sql.{RowFactory, SQLContext} import org.apache.spark.{S...

Mr.Cao 767

Java注解和反射 03 反射概念&&获得反射对象

2.1 反射概念(Reflection) 反射 让 Java 从 “静态语言” 转变为了 “动态语言”,是非常非常重要的 机制! Java反射机制概述 理解 Class 类 并获取 Class 实例 类的加载 与 ClassLoader 创建运行时类的对象(以前是 new、对象.clone,但反射机制也可以创建对象,而且是在运行的时候创建一个对象!!) 比如游戏内存外挂,在运行的时候,为什么你写一段代码,就可以注入到游戏里呢?如果是 Java 的话,就可以用 反射 做到这样的操作。当我们 注入到游戏

小哞^同^学的技术博客 173

从零构建DSL:利用Scala反射实现领域特定语言(附完整源码示例)

掌握Scala反射技巧,轻松构建高效DSL。本文通过一个完整示例展示如何利用Scala反射实现领域特定语言,涵盖语法解析、动态调用等核心方法,提升代码灵活性与可维护性,适用于配置解析、规则引擎等场景,Scala反射示例值得收藏。

QuickCode的博客 677

为什么你的框架需要Scala反射?3大关键用例揭示其不可替代性

掌握Scala反射核心技巧,解决动态类型处理难题。通过Scala反射示例解析类加载、方法调用与注解处理三大场景,揭示其在框架设计中的关键作用。支持运行时元数据操作,提升灵活性与扩展性,值得收藏。

LiteCode的博客 636

动态调用与类型检查全解析,深度解读Scala反射在实际项目中的应用

掌握Scala反射机制的实用技巧,本文通过典型应用场景解析动态调用与类型检查实现方法,提供可运行的Scala反射示例,涵盖invoke、ClassTag等核心API,提升代码灵活性与扩展性,值得收藏。

BytePerch的博客 658
上一篇: 最详细的Log4j使用教程
下一篇: Elasticsearch 时间范围查询
Code_LT
博客等级 码龄10年 307粉丝 182原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值