java虛擬機動態類型語言支持

這篇博客是根據《深入理解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包出現要解決的問題。

(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()方法。
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);
		
	}
}
實際上,方法getPrintlnMH()中模擬了invokevirt指令的執行過程,只不過它的分派邏輯並非固化在Class字節碼上,而是通過一個具體
方法來實現。在這裏本人僅僅舉了MethodHandle來實現java對動態類型的支持。但是還有其他的方法,比如反射、invokedynamic指令,
在這裏就不再一一細說,這篇博客主要介紹了什麼叫做動態類型語言,以及Java對動態類型支持所做的努力。希望能讓大家有一個總體印象!




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章