Scala动态创建对象,scala反射

一、动态对象创建方法

方法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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值