首页
看点啥
插画图片
首页 科技看点 深入Java7的一些新特性以及对脚本语言支持API的介绍实用指南

深入Java7的一些新特性以及对脚本语言支持API的介绍实用指南

2026-08-25 0

平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“深入Java7的一些新特性以及对脚本语言支持API的介绍”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。

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。

您可能感兴趣的文章:
  • C++、C语言和JAVA开发的区别
  • Java程序与C语言的区别浅析
  • C语言与JAVA的区别是什么(建议)
  • Java语言简介(动力节点Java学院整理)
  • 8个轻松部分开启Java语言学习之路 附java学习书单
  • PHP和Java的主要区别有哪些?哪个最适合Web开发语言?
  • JAVA语言编程格式高级规范
  • Java基础知识之Java语言概述
  • Java老矣 尚能饭否?
喜欢(0)

上一篇

PS渐变工具如何使用

PS渐变工具如何使用

下一篇

支付宝被限制收款如何办

支付宝被限制收款如何办
猜你喜欢