条件介绍

本节内容介绍Java 7的一个重要新特性,它对Java虚拟机规范进行了修正,而非Java言语规范。比较之条件到的Java 7的新特性,这个修正更为杂乱,对Java渠道的影响也更深远。

反射才能的增强

Java虚拟机中办法调用的支撑得到了增强,这一改动尽管最初是为了更好地支撑动态言语编译器,但它对普通应用程序的影响也是极为重要的。这一改动为咱们供给了比反射API更为强壮的动态办法调用才能。本节将详细介绍JSR 292 (Supporting Dynamically Typed Language) 中的Java 7重要特性,包括Java虚拟机中新的调用指令 invoke dynamic,以及Java SE 7核心库中的java.lang.invoke包。

Java虚拟机与Java源码

在介绍Java虚拟机的新特性之前,需求先简略介绍一下它的作业原理。Java虚拟机自身并不知道Java言语的存在,它只能理解Java字节代码格局,即class文件中包括的指令和符号表。Java虚拟机的主要职责是履行class文件中的指令,这些文件能够由Java言语的编译器生成,也能够由其他编程言语的编译器生成,甚至能够经过手动东西生成。只需class文件格局符合规范,虚拟机就能正确地履行它们。

Java虚拟机才能支撑

Java虚拟机实践上将操作体系和应用程序之间增加了一个新的笼统层次。传统上,一种编程言语需求将源代码直接编译成方针渠道上的机器代码以取得最高的功率。但是,这种办法存在一些问题,例如生成的二进制代码无法兼容不同渠道,完结的杂乱度较高等。运用虚拟机来处理这些问题会愈加简略和高效。

  1. 虚拟机供给一个笼统层次,屏蔽了底层体系的差异,其所露出的接口是规范和一致的,能够完结“编写一次,到处运转”的方针。

  2. 虚拟机供给了需求的运转时支撑才能,包括内存管理、安全机制、并发操控、规范库和东西等。

  3. 运用现有的虚拟机作为运转渠道,编程言语运用者能够复用已有的相关财物,包括相关东西、集成开发环境和开发经历。这有利于编程言语自身的推行和遍及。

Java的多言语支撑

许多编程言语都支撑Java虚拟机作为方针运转渠道。这些言语的编译器能够将源代码编译成Java字节码。流行的言语包括Java、Scala、JRuby、Groovy、Jython、PHP、C#、JavaScript、Tcl和Lisp等。其间,Java言语自身是最流行的。

Java动态性的局限性

尽管Java虚拟机不关心字节代码的编写言语,但Java言语作为虚拟机上最重要的言语,对Java虚拟机规范产生了最大的影响,许多特性都是为了配合Java言语而产生的。由于Java言语是一门静态类型的编程言语,所以对Java虚拟机的动态性也造成了影响。尽管越来越多的动态类型编程言语采用Java虚拟机作为运转渠道,但Java虚拟机自身对动态性的支撑缺乏,导致这些动态类型言语在完结时会遇到一些阻止。但是,动态类型言语的完结者总是能够找到办法躲避Java虚拟机的限制。

办法句柄处理操作

Java7引入了动态言语支撑,对Java虚拟机规范进行了修正,使得Java虚拟机愈加友爱而且性能更好。动态言语支撑涉及到应用程序中最常见的办法调用,主要包括Java规范库中新的办法调用API和Java虚拟机规范中新的invokedynamic指令。办法句柄是这一部分的起点,而Java API则是开发者最常用的部分。接下来,将介绍办法句柄以及invokedynamic指令。

办法句柄

办法句柄是JSR292中引入的概念,它是Java中办法、构造办法和域的一个强类型可履行引证,句柄即为其含义。运用办法句柄能够直接调用底层办法。办法句柄相当于反射API中的Method类,但愈加强壮、灵敏、高效。在Java规范库中,办法句柄运用java.lang.invoke.MethodHandle类表明。办法句柄和反射API也能够协同运用。

办法句柄类型

办法句柄的类型选择

一个办法句柄的类型只与其参数类型和回来值类型相关,与其所引证的底层办法名称和所在的类无关

例如,引证String类的length办法和Integer类的intValue办法的办法句柄类型相同,由于两者都没有参数且回来类型都是int。

MethodType办法类型

在获取办法句柄(即MethodHandle类的方针)后,能够经过其type办法检查其类型。该办法回来一个java.lang.invoke.MethodType类的方针。

MethodType类的一切实例都是不可变的,类似于String类。对MethodType类方针的任何修正都会生成一个新的MethodType类方针。MethodType类方针是否持平取决于它们所包括的参数类型和回来值类型是否完全一致。

MethodType的创立办法

MethodType类的实例只能经过MethodType类中的静态工厂办法来创立。这些工厂办法分为三类。

第一类工厂办法是经过指定参数和回来值类型来创立MethodType,主要是运用methodType办法的多个重载办法。在运用这些办法时,必须至少指定回来值类型,而参数类型能够是0个至多个。

回来值类型总是呈现在methodType办法参数列表的第一个方位,后边是0个至多个参数类型。类型由Class类的方针指定。假如回来值类型是void,能够运用void.class或java.lang.Void.class进行声明。

代码示例如下
   MethodType type1 = MethodType.methodType(void.class);  // 对应 String voidMethod()
   MethodType type2 = MethodType.methodType(int.class);   // 对应 String length()
   MethodType type3 = MethodType.methodType(String.class, int.class); // 对应 String substring(int)
   MethodType type4 = MethodType.methodType(String.class, CharSequence.class); // 对应 String concat(CharSequence)
   MethodType type5 = MethodType.methodType(type1, String.class); // 在 type5 中运用另一个 MethodType 的参数类型作为当时类型的参数类型
直接办法进行获取办法句柄

值得留意的是,在最终一个methodType办法调用中,运用另一个MethodType的参数类型作为当时MethodType方针的参数类型。

public void generateMethodTypes(){
	//String.length (
	MethodType mt1 = MethodType.methodType (int.class);
	//String.concat(String str)
	MethodType mt2 = MethodType.methodType(String.class,String.class);
	//String.getChars (int srcBegin,int srcEnd,char[]dst,int dstBegin)
	MethodType mt3 = MethodType.methodType(void.class,int.class,int.class,
	char[].class,int.class);
	//String.startswith (String prefix)
	MethodType mt4 = MethodType.methodType (boolean.class,mt2);
}
引证办法进行获取办法句柄(genericMethodType)

除了显式地指定回来值和参数类型之外,还能够创立通用的MethodType类型,其间回来值和一切参数的类型都是Object类。

能够运用静态工厂办法genericMethodType来创立。办法genericMethodType有两种重载办法:

  • 第一种办法只需求指明办法类型中包括的Object类型的参数个数即可。
  • 第二种办法能够供给一个额外的参数来阐明是否在参数列表的最终增加一个Object类型的参数。
生成通用MethodType类型的示例

例如,mt1有3个类型为Object的参数,而mt2有2个类型为Object的参数和后边的Object类型参数。

	// 回来值和参数都是Object类型,其间有3个Object类型的参数
   MethodType mt1 = MethodType.genericMethodType(3);  
	// 回来值和参数都是Object类型,其间有2个Object类型的参数和一个后边的Object类型的参数(即参数列表的最终一个参数为Object类型)   
   MethodType mt2 = MethodType.genericMethodType(2, true); 

fromMethodDescriptorString

介绍的另一个工厂办法是fromMethodDescriptorString,这个办法答应开发人员指定办法类型在字节码中的表明办法。办法的参数是一个描述符字符串,它描述了回来值和参数类型。描述符字符串的格局如下:

  (<参数类型1><参数类型2>...)<回来值类型>

其间,参数类型能够是恣意的根本类型(例如I表明整型,D表明双精度浮点类型等等),也能够是引证类型的全限定名(例如Ljava/lang/String;表明String类型)。回来值类型也能够是恣意的根本类型和引证类型的全限定名。

运用办法类型在字节代码中的表明办法来创立Method Type

例如,String.getChars办法的类型在字节码中的表明办法为“(II[CI)V”,其间“(II[CI)”表明三个参数的类型,分别是int、int、char[]和int,而“V”表明回来值类型为void。这种格局比逐一声明回来值和参数类型要更简洁,适合于对Java字节码格局比较了解的开发人员。

public void generateMethodTypesFromDescriptor(){
	classLoader cl = this.getclass()getclassLoader();
	String descriptor = "(Ljava/lang/String;)Ljava/lang/String;";
	MethodType mt1 = MethodType.fromMethodDescriptorstring(descriptor,cl);
}

“(Ljava/lang/String;)Ljava/lang/String;” 所表明的办法类型是回来值和参数类型都是java.lang.String,相当于运用MethodType.methodType(String.class, String.class)

fromMethodDescriptorString的类加载器

在运用fromMethodDescriptorString办法的时分,需求指定一个类加载器来加载办法类型表达式中呈现的Java类,假如不指定,默许运用体系类加载器。

对MethodType中的回来值和参数类型进行修正的示例

创立出MethodType方针实例之后,能够对其进行进一步的修正,包括改动回来值类型、增加和刺进新参数、删去已有参数和修正已有参数的类型等。这些修正操作对应的办法会回来一个新的MethodType方针。

public void changeMethodType(){
	//(int,int)string
	MethodType mt MethodType.methodType(String.class,int.class,int.class);
	//(int,int,float)string
	mtmt.appendParameterTypes (float.class);
	//(int,double,long,int,float)string
	mtmt.insertParameterTypes (1,double.class,long.class);
	//(int,double,int,float)string
	mt mt.dropParameterTypes(2,3);
	//(int,double,String,float)string
	mt =mt.changeParameterType(2,String.class);
	//(int,double,String,float)void
	mt mt.changeReturnType (void.class);
}

修正回来值和参数类型的示例代码。在每个修正办法的注释中,都给出了修正之后的类型,其间括号内是参数类型列表,而括号外是回来值类型。

一次性修正MethodType中的回来值和一切参数的类型的示例

除了上面说到的精确修正回来值和参数类型的办法,MethodType还有一些办法能够一次性处理回来值和一切参数的类型。

这几个办法的示例:wrap和unwrap用于根本类型与包装类型之间的转化;generic办法会将回来值和参数类型都转化为Object类型;erase办法只会将引证类型转化为Object类型,而不作处理根本类型。以下是修正之后的办法类型:

public void wrapAndGeneric(){
	//(int,double)Integer
	MethodType mt = MethodType.methodType(Integer.class,int.class,double.class);
	//(Integer,Double)Integer
	MethodType wrapped = mt.wrap();
	//(int,double)int
	MethodType unwrapped = mt.unwrap();
	//(object,Object)object
	MethodType generic = mt.generic();
	//(int,double)object
	MethodType erased = mt.erase ()
}

由于每个对MethodType方针进行修正的办法都会回来一个新的MethodType方针,所以能够运用办法级联来简化代码。

办法句柄的调用

办法句柄供给了一种灵敏的调用办法,类似于反射API中的Method类。能够经过获取办法句柄来直接调用底层办法,最直接的办法就是运用invokeExact办法

invokeExact办法接纳两个参数,第一个是作为办法接纳者的方针,第二个是调用时的实践参数列表。

运用开发案例

举个比如,假定咱们获取了String类中substring办法的办法句柄,在代码中能够经过invokeExact来直接调用该办法,就相当于直接调用”Hello World”.substring(1,3)。

运用invokeExact办法调用办法句柄

public void invokeExact ()throws Throwable{
	MethodHandles.Lookuplookup = MethodHandles.lookup ();
	MethodType type = MethodType.methodType(String.class,int.class,int.class);
	MethodHandle mh = lookup.findvirtual(String.class,"substring",type);
	String str = (String)mh.invokeExact ("Hello World",1,3);
	System.out.printin(str);
}

着重一下静态办法和一般办法之间的区别,静态办法在调用时不需求指定办法的接纳方针,而一般的办法则需求指定接纳方针。假如办法句柄引证的是java.lang.Math类中的静态办法min,那么能够直接经过mh.invokeExact(3, 4)来调用该办法。

留意,运用invokeExact办法调用办法时,要求严厉匹配办法的参数类型和回来值类型。上面代码中办法句柄引证的substring办法的回来类型是String。因而,在运用invokeExact办法进行调用时,需求在调用表达式前面加上强制类型转化,以声明回来值的类型。假如省掉了类型转化并直接将回来值赋值给Object类型的变量,在调用时会抛出反常,由于invokeExact会默许办法回来值类型为Object类型。同样,省掉类型转化而不进行赋值操作也是过错的,由于invokeExact会将办法回来值类型视为void类型,而不是办法句柄所要求的String类型。

运用invoke办法调用办法句柄

与invokeExact办法要求严厉匹配的类型不同,invoke办法答应运用愈加宽松的类型。

invoke办法的完结原理

在调用时,它会测验转化回来值和参数的类型。这是经过MethodHandle类的asType办法来完结的。asType办法将当时的办法句柄适配到新的MethodType上,并生成一个新的办法句柄。

假如办法句柄在调用时的类型与其声明的类型完全一致,调用invoke就等同于调用invokeExact;不然,invoke会先调用asType办法来测验适配到调用时的类型。

假如适配成功,调用将继续履行;不然会抛出相关的反常。这种灵敏的适配机制使得invoke办法成为在绝大多数情况下都应该运用的办法句柄调用办法。

进行类型适配时,根本的规矩是比较回来值类型和每个参数的类型是否都能够彼此匹配。只需回来值类型或某个参数的类型无法完结匹配,整个适配过程就会失败。

待转化的源类型S到方针类型T匹配成功的根本原则

  • 假如源类型S和方针类型T相同,则匹配成功;
  • 假如源类型S是方针类型T的子类型,则匹配成功;
  • 假如源类型S和方针类型T都是原始类型,则根据Java的原始类型转化规矩来匹配;
  • 假如源类型S和方针类型T都是引证类型,则根据Java的引证类型转化规矩来匹配;
  • 假如源类型S是一个原始类型,且方针类型T是一个对应的包装类型,或反之亦然,则匹配成功;
  • 假如源类型S能够经过拆箱操作转化为一个根本类型,且该根本类型能够经过装箱操作转化为方针类型T,则匹配成功;
  • 在上述情况下都无法匹配成功时,就会抛出NoSuchMethodError或IllegalArgumentException反常。

转化两个办法类型的规矩能够简述为:只需源类型中的回来值类型和参数类型都能够分别对应到方针类型中的回来值类型和参数类型,那么就能够进行类型转化。运用invoke办法时,只需求将上面的代码中的invokeExact办法替换成invoke办法即可,不需求做太多的介绍。

invokeWithArguments

运用invokeWithArguments办法。该办法在调用时能够指定恣意多个Object类型的参数。

具体办法是先根据传入的实践参数个数,运用MethodType的genericMethodType办法得到一个回来值和参数类型都是Object的新办法类型。然后将原始的办法句柄经过asType办法转化成新的办法句柄。

最终经过新办法句柄的invokeExact办法来完结调用。相对于invokeExact和invoke办法,invokeWithArguments办法的优势在于,它能够经过Java反射API被正常获取和调用,而invokeExact和invoke办法则不能这样运用。因而,invokeWithArguments办法能够作为反射API和办法句柄之间的桥梁。