深入Java7的一些新特性以及对脚本语言支持API的介绍
本文导语: 1.switch条件语句中可以加入字符串了,实现方法是利用了字符串的hashcode()值作业真正的值2.增加了一种可以在字面量中使用的进制,二进制,通过在数字前面加“0b”或“0B”3.在数字字面量中使用下划线来分隔数字方便阅读,...
1.switch条件语句中可以加入字符串了,实现方法是利用了字符串的hashcode()值作业真正的值
2.增加了一种可以在字面量中使用的进制,二进制,通过在数字前面加“0b”或“0B”
3.在数字字面量中使用下划线来分隔数字方便阅读,不影响数值大小。基本原则是前后都是数字的才可以出现下划线
4.java7对异常做了两个改动:
4.1.支持在一个catch子句中同时捕获多个异常,另外一个是在捕获并重新抛出异常时的异常类型更加精确。java7中Throwable类增加addSuppressed方法,当一个异常被抛出的时候,可能有其他异常因为该异常而被抑制住,从而无法正常抛出。这时可以通过addSuppressed方法把这些被抑制的方法记录下来,被抑制的异常会出现在抛出的异常的堆栈信息中,也可以通过getSuppressed方法来获取这些异常。这样做的好处是不会丢失任何异常,方便开发人员测试。
public class ReadFile(){
public void read(String filename) throws IOException {
FileInputStream input = null;
IOException readException = null;
try {
input = new FileInputStream(filename);
} catch (IOException ex) {
readException = ex;
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ex) {
if (readException != null) {
readException.addSuppressed(ex);
} else {
readException = ex;
}
}
}
if (readException != null) {
throw readException;
}
}
}
}
这种做法的关键在于把finally语句中产生的异常通过addSuppressed方法加到try语句产生的异常中。
java7改进了catch子句的语法,允许在其中指定多种异常,每个异常类型之间使用“|”来分隔。需要注意的是在catch子句中声明捕获的这些异常,不能出现重复的类型,也不允许其中的某个异常是另外一个异常参数的子类,否则会出现编译错误(从小到大写就没问题)。如果在catch子句中声明了多个异常,那么异常参数的具体类型是所有这些异常类型的最小上界。
4.2使用try(申请资源){业务处理}来自动释放资源,能够被try语句所管理的资源需要满足一个条件,那就是其java类要实现java.lang.AutoCloseable接口,否则会出现编译错误。当需要释放资源的时候该接口的close方法会被自动调用。
5. 优化变长参数的方法调用:
j2se5.0中引入的一个新特性就是允许在方法声明中使用可变长度的参数。一个方法的最后一个形式参数可以被指定为代表任意多个相同类型的参数。在调用的时候,这些参数是以数组的形式来传递的。在方法体中也可以按照数组的方式来引用这些参数。
6. java7引入了一个新的注解@SafeVarargs.如果开发人员确信某个使用了可变长度参数的方法,在与泛型类一起使用进不会出现类似的情况,就可以用这个注解进行声明。@SafeVarargs注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。一个方法使用@SafeVarargs注解的前提是,开发人员必须确保这个方法的实现中对泛型类型参数的处理不会引发类型安全问题。
7.java中在java虚拟机中支持一些脚本语言是通过脚本引擎。实际上脚本引擎管理器共支持三种查找引擎方式,分别通过名称,文件扩展名和MIME类型来完成。如
public void greet() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
if (engine == null) {
throw new RuntimeException("找不到JavaScript语言执行引擎");
}
engine.eval("println('hello');");
}
还可以通过getEngingByExtension("js")和getEngineByMimeType("text/javascript")来查找到。得到脚本引擎ScriptEngine的对象后,通过其eval方法可以执行一段代码,并返回这段代码的执行结果。
7.1 语言绑定:
脚本语言支持api的一个很大优势在于它规范了java语言与脚本语言之间的交互方式,使java语言编写的程序可以与脚本之间进行双向的方法调用和数据传递。数据传递是通过语言绑定对象来完成的。所谓的语言绑定对象就是一个简单的哈希表,用来存放和获取需要共享的数据。所有数据都对应这个哈希表中的一个条目,是简单的名值对。接口javax.script.Bingings定义了语言绑定对象的接口,它继承自java.util.Map接口。一个脚本引擎在执行过程中可能会使用多个语言绑定对象。不同语言绑定对象的作用域不同。在默认情况下,脚本引擎会提供多个语言绑定对象,用来存放在执行过程中产生的全局对象等。ScriptEnging类提供了put和get方法对脚本引擎中特定使用哉的默认语言绑定对象进行操作。程序可以直接使用这个默认的语言绑定对象,也可以使用自己的语言绑定对象。在脚本执行过程中,可以将语言绑定对象看成是一个额外的变量映射表。在解析变量值的时候,语言绑定对象中的名称也会被考虑在内。脚本 执行过程中产生的全局变量等内容,会出现在语言绑定对象中。通过这种方式,就完成了Java与脚本语言之间的双向数据传递。
如通过ScriptEngine的put方法向脚本引擎默认的语言绑定对象中添加了一个名为“name”的字符串,接着在脚本中直接根据名称来引用这个对象。同样,在脚本中创建的全局变量“message”也可以通过ScriptEnging的get方法来获取。这样就实现了Java程序与脚本之间的双向数据传递。数据传递过程中的类型转换是由脚本引擎来完成的,转换规则取决于具体的语言的语法
public void useDefaultBinging() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
// ScriptEngine engine = getJavaScriptEnging();
engine.put("name", "World");
engine.eval("var message = 'hello,'+name;");
engine.eval("println(message)");
Object obj = engine.get("message");
System.out.println(obj);
}
在大多数情况下,使用ScriptEnging的put和get方法就足够了。如果仅使用put和get方法,语言绑定对象本身对于开发人员来说是透明的。在某些情况下,需要使用程序自己的语言绑定对象,比如语言绑定对象中包含了程序自己独有的数据。如果希望使用自己的语言绑定对象,可以调用脚本引擎的creatBingings方法或创建一个javax.script.SimpleBingings对象,并传递给脚本引擎的eval方法如:
public void useCustomBinding()throws ScriptException
{
ScriptEngine engine = getJavaScriptEngine();
Bindings bindings = new SimpleBindings();
bindings.put("hobby","play games");
engine.eval("println('I like'+hobby);",bindings);
}
通过eval方法传递的语言绑定对象,仅在当前eval调用中生效,并不会改变引擎默认的语言绑定对象
7.2 脚本执行上下文
与脚本引擎执行相关的另外一个重要接口是javax.script.ScriptContext,其中包含脚本引擎执行过程中的相关上下文信息,可以与JavaEE中servlet规范中的javax.servlet.ServletContext接口来进行类比。脚本引擎通过引上下文对象来获取与脚本执行相关的信息,也允许开发人员通过此对象来配置脚本引擎的行为。该上下对象中主要包含以下3类信息。
7.2.1 输入与输出
首先介绍与脚本输入和输出相关的配置信息,其中包括脚本在执行中用来读取数据输入的java.io.Reader对象以及输出正确内容和出错信息的java.io.Writer对象。在默认情况下,脚本的输入输出都发生在标准控制台中,如果希望把脚本的输出写入到文件 中,可以使用如下代码。通过setWriter方法把脚本的输出重定向到一个文件 中。通过ScriptContext的setReader和setErrorWriter方法可以分别设置脚本执行时的数据输入来源和产生错误时出错信息的输出 目的。
public void scriptToFile()throws IOException,ScriptException{
ScriptEngine engine = getJavaScriptEngine();
ScriptContext context = engine.getContext();
context.setWriter(new FileWriter("output.txt"));
engine.eval("println('hello world!');");
}
7.2.2 自定义属性
ScriptContext中也有与ServletContext中类似的获取和设置属性的方法,即setAttribute和getAttribute.所不同的是,ScriptContext中的属性是有作用域之分的。不同作用域的区别在于查找时的顺序不同,每个作用域都以一个对应的整数表示其查找顺序。该整数值越小,说明查找时的顺序越优先。优先级高的作用域中的属性会隐藏优先级低中的同名属性。因此,设置属性时需要显式指定所在的作用域。在获取属性的时候,即可选择指定的作用域中查找,也可以选择根据作用域优先级自动进行查找。
不过脚本执行上下文实现 中包含的作用域是固定的。开发人员不能随意定义自己的作用域。通过ScriptContext的getScopes方法可以得到所有可用的作用域列表。SciptContext中预先定义了两个作用域:常量ScriptContext.ENGINE_SCOPE表示的作用域对应的是当前的脚本引擎,而ScriptContext.GLOBAL_SCOPE表示的作用域对应的是从同一引擎工厂中创建出来的所有脚本引擎对象。前者的优先级较高。如下例:
public void scriptContextAttribute(){
ScriptEngine engine = getJavaScriptEnging();
ScriptContext context = engine.getContext();
context.setAttribute("name","World",ScriptContext.GLOBAL_SCOPE);
context.setAttribute("name","Bob",ScriptContext.ENGINE_SCOPE);
context.getAttribute("name");//值为Bob
}
7.2.3 语言绑定对象
脚本执行上下文中的最后一类信息是语言绑定对象。语言绑定对象也是与作用域相对应的。同样的作用域优先级顺序对语言绑定对象也适用。这样的优先级顺序会对脚本执行时的变量解析产生影响。如下例:
public void scriptContextBindings()throws ScriptException
{
ScriptEngine engine = getJavaScriptEnging();
ScriptContext context = engine.getContext();
Bindings bindings1 = engine.createBindings();
bindings1.put("name","World");
context.setBindings(bindings1,ScriptContext.GLOBAL_SCOPE);
Bindings bindings2 = engine.createBindings();
bindings2.put("name","Bob");
context.setBindings(bindings2,ScriptContext.ENGINE_SCOPE);
engine.eval("println(name);");//结果为Bob
}
还可以Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("name","World")
engine.eval("println(name);");
7.3 脚本的编译:
脚本语言一般是解释执行的。脚本引擎在运行时需要先解析脚本之后再执行。一般来说,通过解释执行的方式运行脚本的速度比编译之后再运行会慢一些。当一段脚本需要被多次重复执行时,可以先对脚本进行编译。编译之后的脚本执行时不需要重复解析,可以提高执行效率。不是所有脚本引擎都支持对脚本进行编译。如果脚本引擎支持这一特性,它会实现javax.script.Compilable接口来声明这一点。脚本引擎的使用者可以利用这个能力来提高需要多次执行的脚本的运行效率。Java SE中自带的JavaScript脚本引擎是支持对脚本进行编译的。
如下代码中,Compilable接口的compile方法用来对脚本代码进行编译,编译的结果用javax.script.CompiledScript来表示。由于不是所有的脚本引擎都支持Compilable接口,因此这里需要用instanceof进行判断。在run方法中,通过CompiledScript的eval方法就可以执行脚本。代码中把一段脚本重复执行了100次,以此说明编译完的脚本在重复执行时的性能优势。
public CompiledScript compile(String scriptText) throws ScriptException {
ScriptEngine engine = getJavaScriptEngine();
if (engine instanceof Compilable) {
CompiledScript script = ((Compilable) engine).compile(scriptText);
}
return null;
}
public void run(String scriptText) throws ScriptException {
CompiledScript script = compile(scriptText);
if (script == null) {
return;
}
for (int i = 0; i < 100; i++) {
script.eval();
}
}
CompiledScript的eval方法所接受的参数与ScriptEngine的eval方法是相同的。
7.4 脚本中方法调用
在脚本中,最常见的和最实用的就是方法。有些脚本引擎允许使用者单独调用脚本中某个方法。支持这种方法调用方式的脚本引擎可以实现javax.script.Invocable接口。通过Invocable接口可以调用脚本中的顶层方法,也可以调用对象中的成员方法。如果脚本中顶层方法或对象中的成员方法实现了Java中的接口,可以通过Invocable接口中的方法来获取脚本中相应的Java接口的实现对象。这样就可以在Java语言中定义接口,在脚本中实现接口。程序中使用该接口的其他部分代码并不知道接口是由脚本来实现的。与Compilable接口一样,ScriptEngine对于Invocable接口的实现也是可选的。
下面代码通过Invocable接口的invokeFunction来调用脚本中的顶层方法,调用时的参数会被传递给脚本中的方法。因为JavaSE自带的JavaScript脚本引擎实现了Invocable接口,所以这里省去了对引擎是否实现了Invocalbe接口的判断
在java中调用脚本顶层方法的示例:
public void invokeFunction()throws ScriptException,NoSuchMethodException{
ScriptEngine engine = getJavaScriptEngine();
String scriptText ="function greet(name) { println('hello,'+name );}";
engine.eval(scriptText);
Invocable invocable=(Invocable)engine;
invocable.invokeFunction("greet","World");
}
如果被调用方法是脚本中对象的成员方法,就需要使用invokeMethod方法,如下面代码中所示,代码中getGreeting方法是属于对象obj的,在调用的时候需要把这个对象作为参数传递进去。
//在Java中调用脚本对象的成员方法的示例
public void inokeMethod()throws ScriptException,NoSuchMethodException
{
ScriptEngine engine = getJavaScriptEngine();
String scriptText = "var obj={ getGreeting:function(name){return 'Hello,'+name;}};";
engine.eval(scriptText);
Invocable invocable = (Invocable)engine;
Object scope = engine.get("obj");
Object result = invocable.invokeMethod(scope,"getGreeting","Alxx");
System.out.println(result);
}
方法invokeMethod与方法invokeFunction用法差不多,区别在于invokeMethod要指定包含待调用方法的对象。
7.5 脚本中实现java接口
在有些脚本引擎中,可以在Java语言中定义接口,并在脚本中编写接口的实现,这样程序中的其他部分可以只同Java接口交互,并不需要关心接口是由什么方式来实现的。在下面代码中Greet是用Java定义的接口,其中包含一个getGreeting方法。在脚本中实现这个接口,通过getInterface方法可以得到由脚本实现的这个接口的对象,并调用其中的方法。
public void useInterface() throws ScriptException {
ScriptEngine engine = getJavaScriptEngine();
String scriptText = "function getGreeting(name){return 'Hello,'+name;}";
engine.eval(scriptText);
Invocable invocable = (Invocable) engine;
Greet greet = invocable.getInterface(Greet.class);
System.out.println(greet.getGreeting("World"));
}
上面中的接口的实现是由脚本中的顶层方法来完成的。同样的,也可以由脚本中对象的成员方法来实现。对于这种情况,getInterface方法另外一种重载形式可以接受一个额外的参数来指定接口实现所在的对象。
由于脚本语言的语法简单和灵活,非常适用于没有或只有少量编程背景的用户来使用,这些用户可以通过脚本语言来定制程序的业务逻辑和用户界面等,通过脚本语言可以在程序的易用性和灵活性之间达到一个比较好的平衡。比如脚本语言Lua就被广泛应用在游戏开发中,用来对游戏的内部行为和用户界面进行定制。
8. 反射API在为Java程序带来灵活性的同时,也产生了额外的性能代价,由于反射API的实现机制,对于相同的操作,比如调用一个方法,用反射API来动态实现比直接在源代码中编写的方式大概慢一到两个数量级。随着Java虚拟机实现的改进,反射API的性能已经有了非常大的提升。但是这种性能的差距是客观存在的,因此,在某些对性能要求比较高的应用中,要慎用反射API。