这篇博客是根据《深入理解java虚拟机》的讲解和本人对动态类型语言的一些认识,来深度剖析一下java虚拟机对动态类型语言的支持!
什么是动态类型语言
在讲解java虚拟机对动态类型语言支持之前,我们首先要弄明白动态类型语言是什么?它与java语言、java虚拟机有什么关系?那么接下来先回答第一个问题,什么是动态类型语言:动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译器,满足这个特征的语言有很多,常用的包括: APL、JavaScript、python、Ruby、Groovy等。相对的在编译期就进行类型检查过程的语言(C++和Java等)就是最常用的静态类型语言。大家可能对上面定义过于概念化,那我们不妨通过几个例子以最浅显的方式说明什么是“在编译器/运行期进行”和什么是“类型检查”。首先看下面这段简单的Java代码:它是否能正常编译和运行呢?
public static void main(String[] args) {
int i = 10;
int j = 0;
int v = i/j;
}
这段代码相信大家再熟悉不过了,它可以正常编译,但是会运行时会报ArithmeticException异常。在Java虚拟机中规范中明确规定了ArithmeticException是一个运行异常,通俗一点说,运行时异常只要代码不运行到这一行就不会有问题。与运行异常相对应的是编译时异常,接下来看一下编译时异常的例子:
public static void main(String[] args) {
FileInputStream fis = null;
fis = new FileInputStream("test.txt");
}
上面这个例子中 fis = new FileInputStream("test.txt")会抛出IOException异常,这是一个编译时异常,如果不做try-catch处理,编译都通不过。通过上面两个例子就是想说明有些检查是在运行期进行的,有些检查是在编译器进行的。接下来再举一个例子来解释“类型检查”,例如下面这一句非常简单的代码:
obj = Demo();
obj.function();
上面代码中假设Demo是一个类,且里面有function方法,这两行对于Java说,相信大家都知道是无法编译的更别提执行了。但是类似的代码在JavaScript或者Python中情况则不一样,是可以编译并且可以执行的。这种差别产生的原因在于动态类型语言中,变量obj本身是没有类型的,变量obj的值才具有类型,这是因为动态类型语言在运行期确定类型的,而Java或者静态类型语言是编译器确定类型的。孰好孰坏不知道,应该是各有所长吧。
JDK1.7与动态类型
在介绍完动态类型,回到Java语言、虚拟机和动态类型语言之间的关系。其实Java虚拟机层面对动态类型语言的支持一直都有所欠缺,主要表现在方法调用方面:JDK1.7以前的字节码指令集中,4条方法调用指令(invokevirtual , invokespecial , invokestatic ,invokeinterface)的第一条参数都是被调用的方法的符号引用,前面已经提到过,方法的符号引用在编译时产生,而动态类型语言是在动态运行期才能确定接受者的类型。因此这也就是JDK1.7中invokedynamic指令以及java.lang.invoke包出现要解决的问题。
java.lang.invoke包
JDK1.7中新加入的java.lang.invoke包的主要目的就是在之前单纯依靠符号引用来确定的目标方法这种方式以外,提供一种新的动态确定目标方法的机制,称之为MethodHandle。其实MethodHandle就是类似C/C++中的函数指针,或者C#中的委托。举个例子,如果我们要实现一个带有函数参数的排序函数,用函数指针的方如下:
void sort(int list[], const int size , int (*compare)(int, int))
int (*compare)(int, int))
但Java语言就做不到这点,即没有办法把一个函数作为参数进行传递。普遍的做法是设计一个带有compare()方法的Comparator接口,以实现了这个接口的对象作为参数。不过,在拥有Method Handle之后,Java语言也可以拥有类似于函数指针或者委托的方法别名的工具了。如下代码演示了MethodHandle的基本用法,无论obj是何种类型,都可以正确的调用到println()方法。
package demo;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import static java.lang.invoke.MethodHandles.lookup;
public class MethodHandleTest {
static class ClassA{
public void println(String s){
System.out.println(s);
}
}
public static void main(String[] args) throws Throwable {
Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
//无论obj最终是哪个实现类,下面这句都能正确调用到println方法
getPrintlnMH(obj).invokeExact("test");
}
private static MethodHandle getPrintlnMH(Object reveiver) throws Throwable{
//MethodType: 代表"方法类型",包含了方法的返回值(methodType()的第一个参数)和具体参数(methodType()第二个及以后的参数)
MethodType mt = MethodType.methodType(void.class,String.class);
//lookup()方法来自于MethodHandles.lookup,这句的作用是在指定类中查找符合给定的方法名称、方法类型、并且符合调用权限的方法语柄
/*因为这里调用的是一个虚方法,按照Java语言的规则,方法第一个参数是隐式的,代表该方法的接受者,也即是this指向的对象,这个参数以前是放在
参数列表中进行传递的,而现在提供了bindTo方法来完成这件事情*/
return lookup().findVirtual(reveiver.getClass(),"println",mt).bindTo(reveiver);
}
}