各位小伙伴儿大家好啊!又和各位见面了,风里雨里,小白在这里等你哟!
在前面的文章中,我们简单的介绍过一点反射的内容,没有深入,这次的反射内容会比上一次更加深刻一点!
1、动态语言
程序运行的时候,可以改变程序的结构或变量类型。典型的语言如:Python、ruby、JavaScript、c、c++,但是java不是动态语言,java可以称之为“准动态语言”,java具有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
JAVA反射机制是在运行状态中,对于任意一个类,都能够得到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.
2、基本概念
在java中,对象就是用来表示或者封装一些数据。一个类被加载之后,JVM会创建一个对应类的Class对象,类的整个结构信息会放到对应的Class对象中,这个Class对象就像一面镜子,通过这面镜子,我们可以看到这个类的全部信息。包括其中的属性,方法等等信息。我们来测试一下反射机制的内容。
第一步:我们首先自己创建一个Javabean类,作为我们测试的对象
package com.peng.test.bean;
public class User {
private int age;
private int id;
private String uname;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public User(int age, int id, String uname) {
super();
this.age = age;
this.id = id;
this.uname = uname;
}
//Javabean必须要有无参构造器
public User() {
// TODO Auto-generated constructor stub
}
}
tips:在Javabean类中,一定要有无参构造器,在后续的反射机制中,一些创建对象的方法就是根据Javabean类的无参构造进行创建对象的。
第二步:利用Javabean类,创建对象,测试反射机制
public class Demo01 {
public static void main(String[] args) {
try {
String path = "com.peng.test.bean.User";//需要使用的类的路径
//Class对象的获取方式1
Class clazz1 = Class.forName(path);
System.out.println(clazz1);
System.out.println(clazz1.hashCode());
Class clazz2 = Class.forName(path);
System.out.println(clazz2.hashCode());
//Class对象的获取方式2
Class clazz3 = String.class;
//Class对象的获取方式3
Class clazz4 = path.getClass();
System.out.println(clazz3 == clazz4);
//不同数组对象的Class对象获取
int[] arry01 = new int[10];
int[] arry02 = new int[30];
int[][] arry03 = new int[10][30];
double[] arry04 = new double[20];
System.out.println(arry01.getClass().hashCode());
System.out.println(arry02.getClass().hashCode());
System.out.println(arry03.getClass().hashCode());
System.out.println(arry04.getClass().hashCode());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
代码运行后的结果为:
tips:
(1)Class对象的3种获取方式,分别是Class.forName(),类.Class,对象.getClass(),在上面的代码中,分别对应代码“Class clazz1 = Class.forName(path);”、“Class clazz3 = String.class;”、“Class clazz4 = path.getClass();”。
(2)一个类的Class对象只会在JVM中加载一次,同一个类不论加载了多少次,也不论使用什么方式加载,所得到的Class对象都是相同的。比如在测试中,我们对同一个类User加载了两次,分别得到Class对象clazz1和clazz2对象,然后分别获取其hashcode,结果显示两者的hashcode是完全相同的,这就代表了clazz1和clazz2属于同一个对象。与此同时,clazz3是引用类String的Class对象,clazz4是字符串变量path的Class对象,但是最后打印结果也是clazz3=clazz4,这是因为变量path是String的一个实现类。以上结果均表明,在JVM中,加载得到的类的Class对象只会根据该类本身进行产生,而不会因为其加载方式和加载次数而改变。
(3)紧接着,我们又分别创建了四个数组,其长度和维度以及类型有所不同。我们通过最后的打印结果可以看出,arry01与arry02加载得到的Class对象是相同的,arry03和arry04的Class对象是不同的,也就代表着,数组对象的Class对象之间的区分,不在于其数组长度,而在于其维度以及数组的基本类型,一维数组和二维数组的反射对象不同,int和double数组的反射对象不同。
3、利用反射机制进行相应的操作
在上面的代码中,我们基本了解了反射的一些概念,下面我们使用反射API动态的操作构造器、方法、属性。
public class Demo03 {
@SuppressWarnings("all")
public static void main(String[] args) {
String path = "com.peng.test.bean.User";
try {
Class clazz = Class.forName(path);
//通过反射API调用构造方法,构造对象
User u = (User) clazz.newInstance();
System.out.println(u);
Constructor<User> c = clazz.getDeclaredConstructor(int.class,int.class,String.class);//调用带参构造
User u2 = c.newInstance(10,1001,"peng");
System.out.println(u2.getUname());
//通过反射API调用普通方法
User u3 = (User) clazz.newInstance();
Method method = clazz.getDeclaredMethod("setUname", String.class);
method.invoke(u3, "peng03");
System.out.println(u3.getUname());
//通过反射API操作属性
User u4 = (User) clazz.newInstance();
Field f = clazz.getDeclaredField("uname");
f.setAccessible(true); //不需要通过安全检查,直接设置属性值
f.set(u4, "peng04"); //通过反射来操作属性值
System.out.println(u4.getUname()); //利用对象的方法来获取属性值
System.out.println(f.get(u4)); //利用反射来获取属性值
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
查看结果:
tips:
(1)利用反射机制的前提,依旧是需要先获取一个User类的Class对象,在获取对象u的时候,我们使用的方法是“clazz.newInstance();”其实这个方法的实现原理就是调用User对象的无参构造器,所以在我们创建Javabean的时候,一定要给其加上无参构造器。
(2)我们在创建对象的时候,也可以使用带参构造器。此时我们就需要先获取User类的带参构造器,然后利用带参构造器进行创建对象u2。
(3)利用反射的API调用方法的时候效率较为低下。在给u3设置属性值“Uname”的时候,我们利用反射机制的实现为:“Method method = clazz.getDeclaredMethod("setUname", String.class);method.invoke(u3, "peng03");”,如果不使用反射,实现为:“u3.setUname(peng03);”。所以我们使用反射的时候要尽量慎重。
(4)在User类的定义中,我们会经常定义private的属性或者方法,此时,被private修饰的属性或者方法,都只能在该类的内部使用。当我们使用反射的时候,也是无法调用private修饰的方法或属性的。所以我们通过反射中的setAccessible(true)的途径,实现外部对私有内容的访问与修改,其代表的含义为:不需要通过安全检查,直接设置属性值。
(5)使用反射机制和不使用反射机制进行运行时间的对比:通过安全检查(即:setAccessible(false))的运行时间是不使用反射机制的12倍左右,不通过安全检查(即:setAccessible(true))的效率大概是不使用该语句的4倍。所以慎重使用反射,如果不得不用反射,那么尽可能将其设置为不通过安全检查(即:setAccessible(true)),这样可以在很大程度上提高运行效率。
二、动态编译
1、为什么我们需要使用动态编译
java 6.0引入的动态编译机制
静态编译:一次性编译。在编译的时候把你所有的模块都编译进去。
动态编译:按需编译。程序在运行的时候,用到那个模块就编译哪个模块。
现实中的实例:
比如开发一个阅读器,支持txt,pdf,doc三种格式。我们把读txt,读pdf,读doc定义为三个功能模块。
静态编译:我想看个txt,点击应用程序图标以后,三个功能都加载进来了。在这里,另外两个模块的作用就是占用系统资源。
动态编译:我想看个txt,点击应用程序,判断格式,只加载读txt模块,使用读txt模块。显然,动态编译速度快,节省了系统资源,利于今后拓展。
2、结合实际代码进行讲解
public class Demo01 {
public static void main(String[] args) throws IOException {
/**
* 动态编译
*/
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null, "G:\\java学习\\test\\HelloWorld.java");
System.out.println(result == 0?"编译成功":"编译失败");
//通过Runtime调用执行类
Runtime run = Runtime.getRuntime();
Process process = run.exec("java -cp G:/java学习/test HelloWorld");
//输出程序中的打印内容
InputStream in = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String info = "";
while((info = reader.readLine()) != null) {
System.out.println(info);
}
}
}
tips:在动态编译的时候,我们首先获取java编译器对象,然后对需要动态编译的java类进行编译操作,主要使用run方法。
int javax.tools.run(InputStream in,OutputStream out,OutputStream err,String... arguments) 方法参数介绍:
第一个参数:为java编译器提供参数
第二个参数:得到java编译器的输出信息
第三个参数:接收编译器的错误信息
第四个参数:可变参数(是一个String数组)能传入一个或多个java源文件
返回值:0表示编译成功,非0表示编译失败。
在动态编译源文件之后,就可以执行源文件了。我们利用IO流输出源文件“HelloWorld”中的内容。
三、脚本引擎执行JavaScript代码:
java脚本引擎是从jdk6.0之后添加的新功能。
脚本引擎介绍:
(1)使得java应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在java平台上调用各种脚本语言的目的。
(2)java脚本API是连通java平台和脚本语言的桥梁。
(3)可以把一些复杂异变的业务逻辑交给脚本语言处理,这又大大提高了开发效率。
其实简单的说,也就是通过java中的各个接口,使得JavaScript语句,可以在java的平台上得以实现。结合下面的实例进行解析:
public class Demo01 {
public static void main(String[] args) throws ScriptException, NoSuchMethodException, IOException {
//获得脚本引擎对象
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("javascript");
//定义变量,存储到引擎上下文中
engine.put("msg", "peng is a good man!");
String str = "var user = {name : 'peng' ,age:18,schools:['CTGU','UESTC']};";
str += "println(user.name);";
//执行脚本
engine.eval(str);
engine.eval("msg = 'UESTC is a good school';");//msg的值,可以被动态改变
System.out.println(engine.get("msg"));
System.out.println("#####################");
//定义函数
engine.eval("function add(a,b){var sum = a + b; return sum;}");
//取得调用接口
Invocable jsInvoke = (Invocable) engine;
//执行脚本中定义的方法
Object result1 = jsInvoke.invokeFunction("add", new Object[] {13,20});
System.out.println(result1);
//导入其他java包,使用其他包中的java类
String jscode = "importPackage(java.util);var list = Arrays.asList([\"CTGU\",\"UESTC\"]);";
engine.eval(jscode);//执行js语句
//从JavaScript中取出list变量,并将其转换为java中的list变量
List<String> list2 = (List<String>)engine.get("list");
for(String temp:list2) {
System.out.println(temp);
}
//执行一个js文件(我们将a.js置于项目的src下即可)
URL url = Demo01.class.getClassLoader().getResource("a.js");
FileReader fr = new FileReader(url.getPath());
engine.eval(fr);
fr.close();
}
}
tips:上面的代码就是使用脚本语言执行javascript代码。
(1)首先就是获取脚本引擎对象,我们利用引擎对象,选择JavaScript语言。在脚本引擎中,我们可以按照JavaScript的语法,将JavaScript语句写在字符串中,再使用脚本语言中的“eval”方法,执行字符串中的JavaScript语句。与此同时,我们也可以在eval方法中改变已经被定义的msg的值。
(2)在上面的代码中,我们主要是将js中的不同功能,使用其语法格式写入字符串中,使用脚本引擎进行执行。同时我们还可以将js中的变量(比如List)转换到java中进行使用。
在最后一段代码中,我们不再仅仅将js语言写在java中的字符串中进行执行,而是在src文件下直接编写一个a.js文件,文件内容如下所示,然后在java中直接执行此js文件。
//定义一个test方法
function test(){
var a = 3;
var b = 4;
println("invoke js file:"+(a+b));
}
//执行test方法
test();