【可能是最详细的】Scala的类型擦除 /TypeTags、Manifests的用法,反射

在Scala 2.10后(包含2.10),不建议使用scala.reflect.ClassManifesthscala.reflect.Manifest,而推荐使用TypeTagClassTag,并且计划在即将发布的版本中弃用scala.reflect.Manifest。因此,建议迁移任何基于Manifest的API以使用Tag。

一、类型擦除:

Scala运行在JVM上,在JVM上有一种机制叫做类型擦除(type eraser)。
类型擦除是说:在语言的编译阶段程序中所携带的泛型信息都会被擦除,最终生成的class文件中是不包含类型信息的。所以scala 2.8中引入了Manifests类来解决构建Array时遇到的一些问题。然而引入的Manifests并不完美,对于路径依赖类型,它拿到的信息是不精确的。所以后续引入了TypeTags。

模式匹配时,Map[Int,Int]Map[_,_]是一样的,匹配不了里面的类型信息。
Array是能保留类型信息的,因为scala对Array做了特殊处理(即利用了Manifests)

exp match{
case s:String | m:Map[_,_] | a:Array[String]=> ... //带类型的模式
case _ =>  //通配模式
}

二、TypeTag、Manifest等的用法

快速概览

TypeTag 定义在:scala.reflect.api.TypeTags#TypeTag,但用时需要引用scala.reflect.runtime.universe.TypeTag
Manifest定义在scala.reflect.Manifest
ClassTag定义在scala.reflect.ClassTag
ClassManifest定义在scala.reflect.ClassManifest

获取TypeTag,scala.reflect.runtime.universe.typeTag函数

//源码
def typeTag[T](implicit ttag: TypeTag[T]) = ttag
//示例
scala> typeTag[String]
res0: reflect.runtime.universe.TypeTag[String] = TypeTag[String]

获取Manifest,mainifest函数定义在scala.Pfedef中,可直接使用:

//源码
def manifest[T](implicit m: Manifest[T])= m
//示例
scala> manifest[String]
res1: Manifest[String] = java.lang.String

获取ClassTag,scala.reflect.classTag函数

//源码
  def classTag[T](implicit ctag: ClassTag[T]) = ctag
//示例
> classTag[List[String]]
res1: scala.reflect.ClassTag[List[String]] = scala.collection.immutable.List

获取ClassManifest,classManifest函数,也定义在scala.Pfedef中

//源码
  @deprecated("use scala.reflect.classTag[T] instead", "2.10.0")
  def classManifest[T](implicit m: ClassManifest[T]) = m
 //示例
> classManifest[List[String]]  
res2: ClassManifest[List[String]] = scala.collection.immutable.List[java.lang.String]

2.1 上下文界定:

[T:Ordering]是一个上下文界定的语法,相当于可省略(implicit anyName:Ordering[T])

//标准库中定义如下方法
def implicityly[T](implicit t:T)=t
//使用上下文界定
object App2 extends App {
  def maxList[T](l:List[T])(implicit odering:Ordering[T]):T= l match {//可以看到ordering并不是必须的,可修改为等价的上下文界定形式
    case List()=> throw new IllegalArgumentException("空列表")
    case List(x) => x
    case x::rest=>
      val maxRest=maxList(rest)//这里会隐式添加ordering
      if(implicityly[Ordering[T]].gteq(x,maxRest)) x else maxRest //标准库中的方法
  }
}
//等价上下文界定
object App2 extends App {
  def maxList[T: Ordering](l:List[T]):T= l match {//等价的上下文界定形式
    case List()=> throw new IllegalArgumentException("空列表")
    case List(x) => x
    case x::rest=>
      val maxRest=maxList(rest)//这里会隐式添加隐式参数 anyName
      if(implicityly[Ordering[T]].gteq(x,maxRest)) x else maxRest //标准库中的方法
  }
}

2.2 Manifests和classManifest的用法

Manifest定义在scala.reflect.Manifest
获取类型的Manifest,mainifest函数定义在scala.Pfedef中,可直接使用。

Manifest是在编译时捕捉的,编码了“捕捉时”所致的类型信息。然后就可以在运行时检查和使用类型信息,但是manifest只能捕捉当Manifest被查找时在隐式作用域里的类型。

利用manifest获取类型信息

    scala> class A[T]
    scala> manifest[List[A[Int]]]
    res1: Manifest[List[A[Int]]] = scala.collection.immutable.List[A[Int]]
classManifest的用法

与manifest功能类似的一个方法是classManifest,也定义在scala.Pfedef中。类型定义在scala.reflect.ClassManifest

    scala> classManifest[List[A[Int]]]
    warning: there was one deprecation warning; re-run with -deprecation for details
    res2: ClassManifest[List[A[Int]]] = scala.collection.immutable.List[A[Int]]

两个方法都可以拿到相应传入的类型信息,但是观察返回结果:res1的类型是Manifest[List[A[Int]]],res2的类型是ClassManifest[List[A[Int]]],两个东西还是有一些区别。接着再看一个例子:

    scala> manifest[List[A[_]]]
    res3: scala.reflect.Manifest[List[A[_]]] = scala.collection.immutable.List[A[_ <: Any]]
 
    scala> classManifest[List[A[_]]]
    warning: there was one deprecation warning; re-run with -deprecation for details
    res4: scala.reflect.ClassTag[List[A[_]]] = scala.collection.immutable.List[A[<?>]]

在这个例子中manifest会返回所有的类型信息,而classManifest返回了List[A]两层类型,丢失最内部的泛型信息。通过看源码注释对ClassManifest的解释,针对高阶类型它可能不会给出精确的类型信息。所以如果想要得到精确地类型信息使用manifest。

通常Manifest会以隐式参数和上下文界定的形式使用。

    def method[T](arg: T)(implicit m: Manifest[T]) = m     //隐式参数
 
    def method[T: Manifest](arg: T) = m   //上下文界定

2.3 TypeTag的用法

定义在scala.reflect.api.TypeTags#TypeTag,但用时需要引用scala.reflect.runtime.universe.TypeTag
为什么要创造TypeTag?看下面这个例子

    scala> class Foo { class Bar}
    defined class Foo
 
    scala> def m(f: Foo)(b: f.Bar)(implicit t: Manifest[f.Bar]) = t
    m: (f: Foo)(b: f.Bar)(implicit t: scala.reflect.Manifest[f.Bar])scala.reflect.Manifest[f.Bar]
 
    scala> val f = new Foo; val b = new f.Bar
    f: Foo = Foo@6fe29d36
    b: f.Bar = Foo$Bar@6bf7d9d
 
    scala> val ff = new Foo; val bb = new ff.Bar
    ff: Foo = Foo@2fdb85b8
    bb: ff.Bar = Foo$Bar@75280b93
 
    scala> m(f)(b)
    res20: scala.reflect.Manifest[f.Bar] = Foo@6fe29d36.type#Foo$Bar
 
    scala> m(ff)(bb)
    res21: scala.reflect.Manifest[ff.Bar] = Foo@2fdb85b8.type#Foo$Bar
 
    scala> res20 == res21   //not expected
    res23: Boolean = true

很明显f.Bar和ff.Bar应该是不相等,因为其依赖路径(外部实例)是不一样的,但Manifest区分不出来。
所以,scala2.10 引入了TypeTags来解决上述问题,TypeTag可以看成是Manifest的升级版。

def m2(f: Foo)(b: f.Bar)(implicit ev: TypeTag[f.Bar]) = ev
val ev3 = m2(f)(b)
val ev4 = m2(ff)(bb)
ev3 == ev4 //false

通过调用该 TypeTag 的 .tpe 方法从 TypeTag 当中抽取出其完整的类型,并以 Type 类型返回:

scala> val tt = typeTag[List[List[String]]]
tt: reflect.runtime.universe.TypeTag[List[List[String]]] = TypeTag[scala.List[scala.List[String]]]

scala> tt.tpe
res0: reflect.runtime.universe.Type = scala.List[scala.List[String]]

或者直接调用 scala.reflect.runtime.universe.typeOf 方法获取一个完整类型,以 Type 类型返回:

scala> typeOf[List[List[String]]]
res1: reflect.runtime.universe.Type = scala.List[scala.List[String]]
//源码
def typeOf[T](implicit ttag: TypeTag[T]): Type = ttag.tpe

TypeTag的使用场景
1:获取类型名称
2:模式匹配

  V.tpe match {
        case vtp if vtp =:= typeOf[String] => println("value's type is String!")
        case vtp if vtp =:= typeOf[Int] => println("value's type is Int!")
        case _ => println("value is neither String or Int.")
    }

  1. 当做类型参数(还没找到如何使用的方法)
val type1=V.tpe
 def test(m:Map[String,t1])={}//报错
 

2.7 Type类型的相等性

设 A 和 B 都属于 Type 类型:

A =:= B ,表示 A 与 B 是同一种类型(路径也要相等),包含它们的类型参数。注意和==的差异。

// 当使用==比较两个类型时,类型别名与实际类型被认为是不同类型,而=:=会用最根本的类型去比较。
type PersonName=String
val t1= typeOf[PersonName]
val t2= typeOf[String]
t1 == t2 //false
t1 =:= t2 //true

A <:< B ,表示 A 是 B 的子类型,或者是同一种类型。

    println(ev3==ev4)
	//false
    println(ev3==ev4)
    //false
    println(ev3.tpe =:= ev4.tpe )
    //false
    println(ev3.tpe == ev4.tpe )
    //false

2.4 ClassTag的使用

scala.reflect.ClassTag
和TypeTag一同引入的还有ClassTag和WeakTypeTag。ClassTag和ClassManifest一样,也不会返回完整的类型信息。但是它和classManifiest还是有区别的。

scala> classTag[List[_]]
res9: scala.reflect.ClassTag[List[_]] = scala.collection.immutable.List
 
scala> classManifest[List[_]]
warning: there was one deprecation warning; re-run with -deprecation for details
res10: scala.reflect.ClassTag[List[_]] = scala.collection.immutable.List[<?>]
 
scala> classTag[List[List[_]]]
res11: scala.reflect.ClassTag[List[List[_]]] = scala.collection.immutable.List
 
scala> classManifest[List[List[_]]]
warning: there was one deprecation warning; re-run with -deprecation for details
res12: scala.reflect.ClassTag[List[List[_]]] = scala.collection.immutable.List[scala.collection.immutable.List[<?>]]

classTag只拿到了基础类型信息,没有尝试去拿任何泛型信息(即ClassTag[T]保存了被泛型擦除后的原始类型T),而classManifest拿到了所有已知的类型信息。WeakTypeTag相较TypeTag可以处理带有泛型的参数。看下边的例子:

scala> :pas
// Entering paste mode (ctrl-D to finish)
 
def weakParamInfo[T](x: T)(implicit tag: WeakTypeTag[T]): Unit = {
  val targs = tag.tpe match { case TypeRef(_, _, args) => args }
  println(s"type of $x has type arguments $targs")
}
 
// Exiting paste mode, now interpreting.
 
weakParamInfo: [T](x: T)(implicit tag: reflect.runtime.universe.WeakTypeTag[T])Unit
 
scala> def foo[T] = weakParamInfo(List[T]())
foo: [T]=> Unit
 
scala> foo[Int]
type of List() has type arguments List(T)

2.5 也提供了一种从Manifest获取TypeTag和ClassTag的途径

但建议不要转换,直接获取,除非是只提供了Manifest的特定场景。

String———->Calss————–>Manifest——–>TypeTag
Class.forName 得到 Calss
ManifestFactory.classType 得到 Manifest
manifestToTypeTag 得到 TypeTag

//Scala 2.10.x 以后得到 TypeTag
import scala.reflect.runtime.universe._
mport compat._
才能使用manifestToTypeTag

//Scala 2.11.0以后
scala.reflect.runtime.universe.internal.manifestToTypeTag

import scala.reflect.runtime.universe
import scala.reflect.ManifestFactory
import scala.reflect.runtime.universe.internal.manifestToTypeTag

scala> val className = "java.lang.String"
className: String = java.lang.String

scala> val mirror = universe.runtimeMirror(getClass.getClassLoader)
mirror: reflect.runtime.universe.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@5f2bd6d9 of type class scala.tools.nsc.interpreter.IMain$TranslatingClassLoader with classpath [(memory)] and parent being scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@54286339 of type class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader with classpath [file:/D:/softwares/Java/jdk1.8.0_131/jre/lib/resources.jar,file:/D:/softwares/Java/jdk1.8.0_131/jre/lib/rt.jar,file:/D:/softwares/Java/jdk1.8.0_131/jre/lib/jsse.jar,file:/D:/softwares/Java/jdk1.8.0_131/jre/lib/jce.jar,file:/D:/softwares/Java/jdk1.8.0_131/jre/lib/charsets.jar,file:/D:/softwares/Java/jdk1.8.0_131/jre/lib/jfr.jar,file:/D:/softwares/Java/jdk1.8.0_131/jre/lib/ext/access-bridge-64.jar,f...

scala> val cls = Class.forName(className)
cls: Class[_] = class java.lang.String

scala> ManifestFactory.classType(cls)
res0: scala.reflect.Manifest[Nothing] = java.lang.String

scala> val t = manifestToTypeTag(mirror, ManifestFactory.classType(cls))
t: scala.reflect.api.Universe#TypeTag[Nothing] = TypeTag[String]

2.6 classOf与getClass方法的差异

getClass 方法得到的是 Class[A]的某个子类,而 classOf[A] 得到是正确的 Class[A],但是去比较的话,这两个类型是equals为true的
classOf获取运行时的类型。classOf[T] 相当于 java中的T.class; 而getClass:

val listClass = classOf[List[_]]
   * // listClass is java.lang.Class[List[_]] = class scala.collection.immutable.List
val mapIntString = classOf[Map[Int,String]]
   * // mapIntString is java.lang.Class[Map[Int,String]] = interface scala.collection.immutable.Map
   * }}}

scala> class  A
scala> val a = new A

scala> a.getClass
res2: Class[_ <: A] = class A

scala> classOf[A]
res3: Class[A] = class A

scala> a.getClass  == classOf[A]
res13: Boolean = true

这种细微的差别,体现在类型赋值时,因为java里的 Class[T]是不支持协变的,所以无法把一个 Class[_ < : A] 赋值给一个 Class[A]

scala> val c:Class[A] = a.getClass
<console>:9: error: type mismatch;

类(class)与类型(type)是两个不一样的概念

(在java里因为早期一直使用class表达type,并且现在也延续这样的习惯);类型(type)比类(class)更”具体”,任何数据都有类型。类是面向对象系统里对同一类数据的抽象,在没有泛型之前,类型系统不存在高阶概念,直接与类一一映射,而泛型出现之后,就不在一一映射了。比如定义class List[T] {}, 可以有List[Int] 和 List[String]等具体类型,它们的类是同一个List,但类型则根据不同的构造参数类型而不同。

类型一致的对象它们的类也是一致的,反过来,类一致的,其类型不一定一致。

scala> classOf[List[Int]] == classOf[List[String]]
res16: Boolean = true

scala> typeOf[List[Int]] == typeOf[List[String]]
res17: Boolean = false

三、 使用样例

3.1 json4s源码中使用Manifest

jValue.extract[T]

def extract[A](implicit formats: Formats, mf: scala.reflect.Manifest[A]): A =
    Extraction.extract(jv)(formats, mf)

//Extraction中
def extract[A](json: JValue)(implicit formats: Formats, mf: Manifest[A]): A = {
    try {
      extract(json, Reflector.scalaTypeOf[A]).asInstanceOf[A]
    }
//Reflector中
 def scalaTypeOf[T](implicit mf: Manifest[T]): ScalaType = ScalaType(mf)

//ScalaType中
  def apply[T](mf: Manifest[T]): ScalaType = {
    /* optimization */
    if (mf.runtimeClass == classOf[Int] || mf.runtimeClass == classOf[java.lang.Integer]) ScalaType.IntType
    else if (mf.runtimeClass == classOf[Long] || mf.runtimeClass == classOf[java.lang.Long]) ScalaType.LongType
    else if (mf.runtimeClass == classOf[Byte] || mf.runtimeClass == classOf[java.lang.Byte]) ScalaType.ByteType
    ...

由上可知,隐式变量mf会有具体的函数做处理,针对不同的类型,得到不同的返回。

3.2 json4s的增强优化

    /**
     * 更新或新增某个字段,不存在则新增
     * 这里的更新是指:简单类型替换(如JString,JInt),复杂类型合并(如JArray)
     *
     * @param field
     * @param value
     * @tparam T
     * @return
     */
    def update[T](field: String, value: T)(implicit ttag:ru.TypeTag[T]): JValue = {
      implicit val formats = DefaultFormats

      ttag.tpe match {
        case x if x =:= ru.typeOf[String] => json merge render(org.json4s.JsonDSL.pair2jvalue((field, value.asInstanceOf[String])))
        case x if x =:= ru.typeOf[Int] => json merge render(org.json4s.JsonDSL.pair2jvalue(field, value.asInstanceOf[Int]))
        case x if x =:= ru.typeOf[JValue] => json merge render(org.json4s.JsonDSL.pair2jvalue(field, value.asInstanceOf[JValue]))
        case x if x =:= ru.typeOf[Map[String,Int]] => json merge render(org.json4s.JsonDSL.pair2jvalue(field, value.asInstanceOf[Map[String,Int]]))
        case _ => throw new Exception(s"Wrong Type value: ${value.getClass()}")
      }

    }

    //不采用typeTag也可以实现一个版本,但是由于类型擦擦的原因,无法处理Map[String,Int]这种类型
    def update2[T](field: String, value: T): JValue = {
      implicit val formats = DefaultFormats

      value match {
        case x if x.isInstanceOf[String] => json merge render(org.json4s.JsonDSL.pair2jvalue((field, value.asInstanceOf[String])))
        case x if x.isInstanceOf[Int] => json merge render(org.json4s.JsonDSL.pair2jvalue(field, value.asInstanceOf[Int])) //value处包含隐式转换 ((x: Int) => org.json4s.JsonDSL.int2jvalue(x))
        case x if x.isInstanceOf[JValue] => json merge render(org.json4s.JsonDSL.pair2jvalue(field, value.asInstanceOf[JValue]))
        case _ => throw new Exception(s"Wrong Type value: ${value.getClass()}")
      }

    }
var json = parse("{\"name\":\"joe\",\"age\":35}")
json= json.update("age",Map("fake"->18,"real"->33))
println(compact(render(json)))
//{"name":"joe","age":{"fake":18,"real":33}}

四、 反射

4.1 scala原生反射方案

在Scala-2.10以前, 只能在Scala中利用Java的反射机制, 但是通过Java反射机制得到的是只是擦除后的类型信息, 并不包括Scala的一些特定类型信息. 从Scala-2.10起, Scala实现了自己的反射机制, 我们可以通过Scala的反射机制得到Scala的类型信息。
在这里插入图片描述
Person.scala文件中:

import Person.greet

trait PersonBase{
  def greet
}

class Person(val name: String, val age: Int){
  private def unsafe(): Unit = println("private method.")

  case class Son(val name: String)

  object InnerObject {
    def x = 2
  }

}

object Person extends PersonBase {
  def greet(): Unit = println("hello!")
}

AObject.scala文件中

object AObject {
  def x = 2
}

Test.scala的main中:

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


    val mirror = ru.runtimeMirror(getClass.getClassLoader) //getClass定义在java.lang中,直接可用

    //反射实例
    val im = mirror.reflect(new Person("Wangfang", 20))

    //取实例字段
    val ruType = ru.typeOf[Person]
    val symbol = ruType.decl(ru.TermName("name"))
    val termSyb = symbol.asTerm
    //或等价的
    //val termSyb =im.symbol.info.member(ru.TermName("name")).asTerm
    val fieldMirror = im.reflectField(termSyb)

    println(s"实例字段值为: = ${fieldMirror.get}")

    //取实例方法
    val methodSyb = ruType.decl(ru.TermName("unsafe"))
    val termMth = methodSyb.asMethod
    //或等价的
    //val termMth = im.symbol.info.member(ru.TermName("unsafe")).asMethod
    val methodMirror = im.reflectMethod(termMth)//通过反射调用此方法,apply 可以接收该方法所需要的入参。
    println(s"方法输出: ${methodMirror.apply()}")
    //取伴生方法--报错,因为im只是类的反射,不包含伴生对象成员
    //val termMthO = im.symbol.info.member(ru.TermName("greet")).asMethod
    //val methodMirrorO = im.reflectMethod(termMthO)
    //println(s"伴生对象方法输出: ${methodMirrorO.apply()}")

    //取伴生方法
    val moduleSybO = mirror.staticModule("Person")
    val moduleMirrorO = mirror.reflectModule(moduleSybO)
    val objO = moduleMirrorO.instance
    println(s"伴生对象:${objO}")
    val objO2= objO.asInstanceOf[PersonBase]//若不构建PersonBase,楼主目前没找到方法调用greet
    println(s"伴生对象方法输出: ${objO2.greet}")
    
    //取内部类构造器
    val classSyb = im.symbol.info.member(ru.TypeName("Son")).asClass
    val classMirror = im.reflectClass(classSyb)
    val ctorMethodSyb = classMirror.symbol.info.member(ru.termNames.CONSTRUCTOR).asMethod
    val ctorMethodMirror = classMirror.reflectConstructor(ctorMethodSyb)
    val son1=ctorMethodMirror("son1")
    println(s"内部类:${son1}")

    //反射内部对象
    val moduleSyb = im.symbol.info.member(ru.TermName("InnerObject")).asModule
    val moduleMirror = im.reflectModule(moduleSyb)
    val obj = moduleMirror.instance
    println(s"内部对象:${obj}")



    // 反射静态类构造器
    val classSyb2 = ruType.typeSymbol.asClass
    val classMirror2 = mirror.reflectClass(classSyb2)
    val ctorMethodSyb2= ruType.decl(ru.termNames.CONSTRUCTOR).asMethod
    val ctorMethodMirror2 = classMirror2.reflectConstructor(ctorMethodSyb2)
    val person = ctorMethodMirror2("person",30)
    println(s"静态类实例:${person}")
    val person1=person.asInstanceOf[Person]//要告诉编译器,这是个Person类,才能调用它的成员
    println(s"静态类实例-程序内可用:${person1.name}")
   
    
    
    //反射单例对象
    val moduleSyb2 = ru.typeOf[AObject.type].termSymbol.asModule
    val moduleMirror2 = mirror.reflectModule(moduleSyb2)
    val obj2 = moduleMirror2.instance
    println(s"单例对象:${obj2}")
    
实例字段值为: = Wangfang
private method.
方法输出: ()
伴生对象:Person$@7fc44dec
hello!
伴生对象方法输出: ()
内部类:Son(son1)
内部对象:Person$InnerObject$@4c51bb7
静态类实例:Person@773cbf4f
静态类实例-程序内可用:person
单例对象:AObject$@2756c0a7

注意,对于静态类和对象的方法staticModulestaticClass,参数需要是fullName,即,如果是在某个包中的类,则需要 packageName.类名

4.2 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的父类

参考:

【官方】TYPETAGS 和 MANIFESTS
【挺好】谈谈 Scala 的运行时反射
【官方】反射
https://blog.youkuaiyun.com/hellojoy/article/details/81064603
https://docs.scala-lang.org/zh-cn/overviews/reflection/typetags-manifests.html
http://slamke.github.io/2016/11/29/Scala%E7%9A%84%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6/
https://masterwangzx.com/2020/08/11/scala-reflection/

https://github.com/scala/docs.scala-lang/blob/main/_overviews/macros/changelog211.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值