【深入Java虚拟机】之八动态类型语言支持

编译型语言和解释型语言

1、编译型语言

需通过编译器(compiler)将源代码编译成机器码,之后才能执行的语言。一般需经过编译(compile)、链接(linker)这两个步骤

编译是把源代码编译成机器码,

链接是把各个模块的机器码和依赖库串连起来生成可执行文件。

优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高。可以脱离语言环境独立运行。
缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
代表语言:C、C++、Pascal、Object-C以及最近很火的苹果新语言swift

2、解释型语言

解释性语言的程序不需要编译,相比编译型语言省了道工序,解释性语言在运行程序的时候才逐行翻译。
优点:有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(虚拟机)。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。
代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby
 

3、混合型语言

既然编译型和解释型各有缺点就会有人想到把两种类型整合起来,取其精华去其糟粕。就出现了半编译型语言。比如C#,C#在编译的时候不是直接编译成机器码而是中间码,.NET平台提供了中间语言运行库运行中间码,中间语言运行库类似于Java虚拟机。.net在编译成IL代码后,保存在dll中,首次运行时由JIT在编译成机器码缓存在内存中,下次直接执行。Java先生成字节码再在Java虚拟机中解释执行。严格来说混合型语言属于解释型语言。C#更接近编译型语言。

 

 

动态语言和静态语言

1、动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

 

2、静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

 

有三个名词容易混淆:
Dynamic Programming Language (动态语言或动态编程语言)
Dynamically Typed Language (动态类型语言)
Statically Typed Language (静态类型语言)

动态类型语言和静态类型语言

1、动态类型语言

动态类型语言和动态语言是完全不同的两个概念。

动态类型语言:是指在运行期间才去做数据类型检查的语言,说的是数据类型,

动态语言:说的是运行是改变结构,说的是代码结构。
动态类型语言的数据类型不是在编译阶段决定的,而是把类型绑定延后到了运行阶段。
主要语言:Python、Ruby、Erlang、JavaScript、swift、PHP、Perl。

2、静态类型语言

静态语言的数据类型是在编译期间(或运行之前)确定的,编写代码的时候要明确确定变量的数据类型。
主要语言:C、C++、C#、Java、Object-C。

(一)什么是动态类型语言

在讲解java虚拟机对动态类型语言支持之前,我们首先要弄明白动态类型语言是什么?它与java语言、java虚拟机有什么关系?

那么接下来先回答第一个问题,什么是动态类型语言:动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译器,

满足这个特征的语言有很多,常用的包括: APL、JavaScript、python、Ruby、Groovy等。相对的在编译期就进行类型检查过程的

语言(C++和Java等)就是最常用的静态类型语言。大家可能对上面定义过于概念化,那我们不妨通过几个例子以最浅显的方式说明

什么是“在编译器/运行期进行”和什么是“类型检查”。首先看下面这段简单的Java代码:它是否能正常编译和运行呢?


 
 
  1. public static void main(String[] args) {
  2. int i = 10;
  3. int j = 0;
  4. int v = i/j;
  5. }

这段代码相信大家再熟悉不过了,它可以正常编译,但是会运行时会报ArithmeticException异常。在Java虚拟机中规范中明确规定了

ArithmeticException是一个运行异常,通俗一点说,运行时异常只要代码不运行到这一行就不会有问题。与运行异常相对应的是编译时

异常,接下来看一下编译时异常的例子:


 
 
  1. public static void main(String[] args) {
  2. FileInputStream fis = null;
  3. fis = new FileInputStream( "test.txt");
  4. }

上面这个例子中 fis = new FileInputStream("test.txt")会抛出IOException异常,这是一个编译时异常,如果不做try-catch处理,

编译都通不过。通过上面两个例子就是想说明有些检查是在运行期进行的,有些检查是在编译器进行的。

接下来再举一个例子来解释“类型检查”,例如下面这一句非常简单的代码:


 
 
  1. obj = Demo();
  2. 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包出现要解决的问题。

(1)java.lang.invoke包

JDK1.7中新加入的java.lang.invoke包的主要目的就是在之前单纯依靠符号引用来确定的目标方法这种方式以外,提供一种新的

动态确定目标方法的机制,称之为MethodHandle。其实MethodHandle就是类似C/C++中的函数指针,或者C#中的委托。

举个例子,如果我们要实现一个带有函数参数的排序函数,用函数指针的方如下:

 

void sort(int list[], const int size , int (*compare)(int, int))
 
 


Java语言就做不到这点,即没有办法把一个函数作为参数进行传递。普遍的做法是设计一个带有compare()方法的Comparator接口,

以实现了这个接口的对象作为参数。不过,在拥有Method Handle之后,Java语言也可以拥有类似于函数指针或者委托的方法别名的

工具了。如下代码演示了MethodHandle的基本用法,无论obj是何种类型,都可以正确的调用到println()方法。

 

 
 
  1. package demo;
  2. import java.lang.invoke.MethodHandle;
  3. import java.lang.invoke.MethodType;
  4. import static java.lang.invoke.MethodHandles.lookup;
  5. public class MethodHandleTest {
  6. static class ClassA{
  7. public void println(String s){
  8. System.out.println(s);
  9. }
  10. }
  11. public static void main(String[] args) throws Throwable {
  12. Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
  13. //无论obj最终是哪个实现类,下面这句都能正确调用到println方法
  14. getPrintlnMH(obj).invokeExact( "test");
  15. }
  16. private static MethodHandle getPrintlnMH(Object reveiver) throws Throwable{
  17. //MethodType: 代表"方法类型",包含了方法的返回值(methodType()的第一个参数)和具体参数(methodType()第二个及以后的参数)
  18. MethodType mt = MethodType.methodType( void.class,String.class);
  19. //lookup()方法来自于MethodHandles.lookup,这句的作用是在指定类中查找符合给定的方法名称、方法类型、并且符合调用权限的方法语柄
  20. /*因为这里调用的是一个虚方法,按照Java语言的规则,方法第一个参数是隐式的,代表该方法的接受者,也即是this指向的对象,这个参数以前是放在
  21. 参数列表中进行传递的,而现在提供了bindTo方法来完成这件事情*/
  22. return lookup().findVirtual(reveiver.getClass(), "println",mt).bindTo(reveiver);
  23. }
  24. }

实际上,方法getPrintlnMH()中模拟了invokevirt指令的执行过程,只不过它的分派逻辑并非固化在Class字节码上,而是通过一个具体

方法来实现。在这里本人仅仅举了MethodHandle来实现java对动态类型的支持。但是还有其他的方法,比如反射、invokedynamic指令,

在这里就不再一一细说,这篇博客主要介绍了什么叫做动态类型语言,以及Java对动态类型支持所做的努力。希望能让大家有一个总体印象!

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值