一、动态对象创建方法
方法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 ).
而我们开发过程中, 可能需要在某一时刻获得类中的详细泛型信息. 并进行逻辑处理. 这时就需要用的 这个概念. 通过反射我们可以做到
- 获取运行时类型信息
- 通过类型信息实例化新对象
- 访问或调用对象的方法和属性等
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)
类名可通过参数传递
// 类名可配置
//在另一个文件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
- ClassMirror ( => 类)
参考
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