您的位置:首页 > 其它

使用forName动态加载类文件,动态加载不适合数组,动态加载数组的方法

2015-11-06 10:01 357 查看
动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,对Java程序来 说,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以 在运行时再决定是否要加载一个类,比如从Web上接收一个String参数作为类名,然后在 JVM中加载并初始化,这就是动态加载,此动态加载通常是通过Class.forName(String)实现的,只是这个forName方法到底是什么意思呢?

我们知道一个类文件只有在被加载到内存中后才可能生成实例对象,也就是说一个对象的生成必然会经过以下两个步骤:
□加载到内存中生成Class的实例对象。
□通过new关键字生成实例对象。
如果我们使用的是import关键字产生的依赖包,JVM在启动时会自动加载所有依赖包 下的类文件,这没有什么问题,如果要动态加载类文件,就要使用forName方法了,但问题 是我们为什么要使用forName方法动态加载一个类文件呢?那是因为我们不知道生成的实例 对象是什么类型(如臬知道就不用动态加载),而且方法和属性都不可访问呀。问题又来了 : 动态加载的意义在什么地方呢?
意义在于:加载一个类即表示要初始化该类的static变量,特别是static代码块,在这 里我们可以做大量的工作,比如注册自己,初始化环境等,这才是我们要重点关注的逻辑, 例如如下代码:
class Utils{
//静态代码块
static{

System.out.printIn("Do Something");
}
}

public class Client {

public static voidmain(String[] args) throws Exception{ //动态加载

Class.forName("Utils");
}
}



注意看Client类,我们并没有对Utils做任何初始化,只是通过forName方法加载了Utils类,但是却产生了一个“Do Something”的输出,这就是因为Utils类被加载后,JVM 会自动初始化其static变量和static代码块,这是类加载机制所决定的。
对于此种动态加载,最经典的应用就是数据库驱动程序的加载片段,代码如下:
//加栽驱动

Class. forName ("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/db?user=&password=";
Connectionconn=DriverManager.getConnection(url);
Statementstmt=conn.createStatement();

在没有Hibernate和Ibatis等ORM框架的情况下,基本上每个系统都会有这么一 个JDBC连接类,然后提供诸如Query、Delete等的方法,大家有没有想过为什么要加上
forName这句话呢?没有任何的输出呀,要它干什么用呢?事实上非常有用,我们看一下 Driver类的源码:
public class Driverextends NonRegisteringDriver implements java.sql.Driver {
//静态代码块
 static {
try {

//把自已注册到DriverManager中

java.sql.DriverManager.registerDriver(new Driver());

} catch (SQLException E) {

//异常处理
}

 //构造函数
public Driver() throws SQLException {

}

}


该程序的逻辑是这样的:数据库的驱动程序已经由NonRegisteringDriver实现了,Driver 类只是负责把自己注册到DriverManager中。当程序动态加载该驱动时,也就是执行到 Class.forName("com.mysql.jdbc.Driver")时,Driver 类会被加载到内存中,于是 static 代码块 开始执行,也就是把自己注册到DriverManager中。
需要说明的是,forName只是把一个类加载到内存中,并不保证由此产生一个实例对象,也不会执行任何方法,之所以会初始化static代码,那是由类加载机制所决定的,而不 是forName方法决定的。也就是说,如果没有static属性或static代码块,forName就只是加 载类,没有任何的执行行为。
注意forName只是加载类,并不执行任何代码。

那么哪些地方不适合使用动态加 载?如果forName要加载一个类,那它首先必须是一个类——8个基本类型排除在外,它们不 是一个具体的类;其次,它必须具有可追索的类路径,否则就会报ClassNotFoundException。
在Java中,数组是一个非常特殊的类,虽然它是一个类,但没有定义类路径,例如这样的代码:
public static void main(String[] args) throws Exception{
String [] strs = new String [10];
Class.forName("java.lang.String[]");
}


String[]是一个类型声明,它作为forName的参数应该也是可行的吧!但是非常遗憾, 其运行结果如下:
Exception in thread "main”java.lang.ClassNotFoundExcoptions: java/lang/String[]

atjava.lang.Class.forNameO(Native Method) atjava.lang.Class.forName(Class.java:169) at Client.main(Client.java:6)
产生ClassNotFoundException异常的原因是数组虽然是一个类,在声明时可以定义为 String[],但编译器编译后会为不同的数组类型生成不同的类,具体如表7-2所示。
表7-2数组编译对应关系表

元素类型
编译后的类型
byte口
[B
char[]
[C
Double[]
[D
Float[]
[F
Iut[]
[I
Long[]
[J
Short[]
[S
Boolean
[Z
引用类型(如String[])
[L 引用类型(如:[Ljava.lang.String;)
在编码期,我们可以声明一个变量为String[],但是经过编译器编译后就成为了 [Ljava. lang.String。明白了这一点,再根据以上的表格可知,动态加载一个对象数组只要加载编译 后的数组对象就可以了,代码如下:
   //加载一个String數组
  Class.forName{"[Ljava.lang.String;");
  //加载一个long数组

  Class.forName("[J");
虽然以上代码可以动态加载一个数组类,但是这没有任何意义,因为它不能生成一个数组对象,也就是说以上代码只是把一个String类型的数组类和long类型的数组类加载到了内 存中(如果内存中没有该类的话),并不能通过newlnstance方法生成一个实例对象,因为它 没有定义数组的长度,在Java中数组是定长的,没有长度的数组是不允许存在的。
既然反射不能定义一个数组,那问题就来了:如何动态加载一个数组呢?比如依据输入 动态产生一个数组。其实可以使用Array数组反射类来动态加载,代码如下:
//动态创建数组

String[] strs = (String[]) Array.newlnstance(String.class, 8);

//创建一个多维数组

int [] [] ints = (int [] [] ) Array. newlnstance (int. class ,2,3);


因为数组比较特殊,要想动态创建和访问数组,基本的反射是无法实现的,“上帝对你 关闭一扇门,同时会为你打开另外一扇窗",于是Java就专门定义了一个Array数组反射工 具类来实现动态探知数组的功能。
注意通过反射操作数组使用Array类,不要采用通用的反射处理API。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: