前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第15次文章:反射+动态编译+脚本引擎

第15次文章:反射+动态编译+脚本引擎

作者头像
鹏-程-万-里
发布2019-09-27 12:13:00
7930
发布2019-09-27 12:13:00
举报

各位小伙伴儿大家好啊!又和各位见面了,风里雨里,小白在这里等你哟!

一、反射

在前面的文章中,我们简单的介绍过一点反射的内容,没有深入,这次的反射内容会比上一次更加深刻一点!

1、动态语言

程序运行的时候,可以改变程序的结构或变量类型。典型的语言如:Python、ruby、JavaScript、c、c++,但是java不是动态语言,java可以称之为“准动态语言”,java具有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。

JAVA反射机制是在运行状态中,对于任意一个类,都能够得到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.

2、基本概念

在java中,对象就是用来表示或者封装一些数据。一个类被加载之后,JVM会创建一个对应类的Class对象,类的整个结构信息会放到对应的Class对象中,这个Class对象就像一面镜子,通过这面镜子,我们可以看到这个类的全部信息。包括其中的属性,方法等等信息。我们来测试一下反射机制的内容。

第一步:我们首先自己创建一个Javabean类,作为我们测试的对象

代码语言:javascript
复制
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类,创建对象,测试反射机制

代码语言:javascript
复制
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动态的操作构造器、方法、属性。

代码语言:javascript
复制
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、结合实际代码进行讲解

代码语言:javascript
复制
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的平台上得以实现。结合下面的实例进行解析:

代码语言:javascript
复制
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文件。

代码语言:javascript
复制
//定义一个test方法
function test(){
  var a = 3;
  var b = 4;
  println("invoke js file:"+(a+b));
}
//执行test方法
test();

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java小白成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档