您也許已經(jīng)見(jiàn)過(guò)這樣的報(bào)告,即一些新的 Java 語(yǔ)言變化包含易于開(kāi)發(fā)性主題。這些變化包括泛型、元數(shù)據(jù)、autoboxing、增強(qiáng)的 for 循環(huán)、枚舉類(lèi)型、靜態(tài)導(dǎo)入、C 風(fēng)格的格式化 I/O、可變參數(shù)、并發(fā)實(shí)用程序以及更簡(jiǎn)單的 RMI 接口生成。
JSR 201 包括如下四個(gè)語(yǔ)言變化:增強(qiáng)的 for 循環(huán)、枚舉類(lèi)型、靜態(tài)導(dǎo)入和 autoboxing;JSR 175 指定了新的元數(shù)據(jù)功能,而 JSR 14 則詳細(xì)說(shuō)明了泛型。
javac 編譯器執(zhí)行的默認(rèn)語(yǔ)言規(guī)范是版本 1.4。這意味著要利用以下語(yǔ)言變化的任何好處,需要向 javac 命令傳遞參數(shù) -source 1.5。
元數(shù)據(jù)
J2SE 1.5 中的元數(shù)據(jù)特性提供這樣的能力,即向 Java 類(lèi)、接口、方法和字段關(guān)聯(lián)附加的數(shù)據(jù)。這些附加的數(shù)據(jù)或者注釋?zhuān)梢员?javac 編譯器或其他工具讀取,并且根據(jù)配置不同,可以被保存在類(lèi)文件中,也可以在運(yùn)行時(shí)使用 Java 反射 API 被發(fā)現(xiàn)。
向 Java 平臺(tái)增加元數(shù)據(jù)的一個(gè)主要原因是,使得開(kāi)發(fā)工具和運(yùn)行工具有一個(gè)通用的基礎(chǔ)結(jié)構(gòu),以減少開(kāi)發(fā)和部署所需的成本。工具可以使用元數(shù)據(jù)信息生成附加的源代碼,或者在調(diào)試時(shí)提供附加信息。
下面的例子用元數(shù)據(jù)工具創(chuàng)建了一個(gè)調(diào)試元數(shù)據(jù)注釋?zhuān)@些元數(shù)據(jù)注釋然后又簡(jiǎn)單地在運(yùn)行時(shí)顯示出來(lái)。可以想像,大部分的元數(shù)據(jù)標(biāo)簽形成一個(gè)標(biāo)準(zhǔn),即一個(gè)良好規(guī)范的集合。
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @interface debug {
boolean devbuild() default false;
int counter();
}
public class MetaTest {
final boolean production=true;
@debug(devbuild=production,counter=1) public void testMethod() {
}
public static void main(String[] args) {
MetaTest mt = new MetaTest();
try {
Annotation[] a = mt.getClass().getMethod("testMethod").getAnnotations();
for (int i=0; i<a.length ; i++) {
System.out.println("a["+i+"]="+a+" ");
}
} catch(NoSuchMethodException e) {
System.out.println(e);
}
}
}
利用一個(gè)元數(shù)據(jù)處理工具,許多重復(fù)的代碼編寫(xiě)步驟可以減少成一個(gè)簡(jiǎn)練的元數(shù)據(jù)標(biāo)簽。例如,訪問(wèn)某個(gè) JAX-RPC 服務(wù)實(shí)現(xiàn)時(shí)所需的遠(yuǎn)程接口可以實(shí)現(xiàn)為:
原來(lái)(J2SE 1.5 以前版本):
public interface PingIF extends Remote {
public void ping() throws RemoteException;
}
public class Ping implements PingIF {
public void ping() {
}
}
現(xiàn)在:
public class Ping {
public @remote void ping() {
}
}
范型
范型一直是 Java 社團(tuán)所廣泛期待的,現(xiàn)在已經(jīng)是 J2SE 1.5 的一部分了。最先見(jiàn)到使用泛型的地方是在 Collections API 中。Collections API 提供可以被多個(gè) Java 類(lèi)型使用的公共功能性,比如 LinkedLists、ArrayLists 和 HashMaps。下一個(gè)例子使用 1.4.2 庫(kù)和默認(rèn)的 javac 編譯模式。
ArrayList list = new ArrayList();
list.add(0, new Integer(42));
int total = ((Integer)list.get(0)).intValue();
最后一行中的 Integer 轉(zhuǎn)換是泛型所要防止的強(qiáng)制類(lèi)型轉(zhuǎn)換問(wèn)題的一個(gè)例子。這個(gè)問(wèn)題在于,1.4.2 Collections API 使用 Object 類(lèi)來(lái)存儲(chǔ) Collection 對(duì)象,這就意味著在編譯的時(shí)候不能找出類(lèi)型匹配。問(wèn)題的第一個(gè)標(biāo)志信息是在運(yùn)行時(shí)拋出的 ClassCastException。
帶有范型化 Collections 庫(kù)的同一個(gè)例子可編寫(xiě)為:
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0, new Integer(42));
int total = list.get(0).intValue();
范型化 API 的用戶必須使用 <> 符號(hào)簡(jiǎn)單地聲明在編譯類(lèi)型中使用的類(lèi)型。不需要任何類(lèi)型轉(zhuǎn)換,在本例中試圖向一個(gè) Integer 類(lèi)型的集合中添加 String 對(duì)象將會(huì)在編譯時(shí)被捕獲。
因此,范型允許 API 設(shè)計(jì)者提供這樣的公共功能性:可以與多種數(shù)據(jù)類(lèi)型一起使用,也可以在編譯時(shí)出于類(lèi)型安全對(duì)它進(jìn)行檢查。
設(shè)計(jì)自己的 Generic API 比起只是使用它們來(lái)說(shuō)要稍微復(fù)雜一些。請(qǐng)從查看 java.util.Collection 源代碼和 API 指南開(kāi)始。
原語(yǔ)類(lèi)型的 Autoboxing 和 Auto-unboxing
像 int、boolean 以及它們的基于對(duì)象的對(duì)應(yīng)物(比如 Integer 和 Boolean)這樣的原語(yǔ)類(lèi)型之間的轉(zhuǎn)換需要大量不必要的額外編碼, 尤其是當(dāng)只是像 Collections API 這樣的方法調(diào)用需要轉(zhuǎn)換時(shí)更甚。
Java 原語(yǔ)類(lèi)型的 autoboxing 和 auto-unboxing 產(chǎn)生更加精練并更加易于理解的代碼。1.5 版本讓所需要的轉(zhuǎn)換轉(zhuǎn)變成 Integer 并轉(zhuǎn)換回編譯器。
原來(lái)
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0, new Integer(42));
int total = (list.get(0)).intValue();
現(xiàn)在
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0, 42);
int total = list.get(0);
增強(qiáng)的 for 循環(huán)
Collections API 經(jīng)常使用 Iterator 類(lèi)。Iterator 類(lèi)提供在 Collection 中順序?qū)Ш降臋C(jī)制。當(dāng)像下面一樣只是在 Collection 中遍歷時(shí),新的增強(qiáng)的 for 循環(huán)可取代 iterator。編譯器生成必要的循環(huán)代碼,因?yàn)槔梅缎停圆恍枰~外的類(lèi)型轉(zhuǎn)換。
原來(lái)
ArrayList<Integer> list = new ArrayList<Integer>();
for (Iterator i = list.iterator(); i.hasNext();) {
Integer value=(Integer)i.next();
}
現(xiàn)在
ArrayList<Integer> list = new ArrayList<Integer>();
for (Integer i : list) { ... }
枚舉類(lèi)型
當(dāng)使用 static final 型常量時(shí),該類(lèi)型提供枚舉的類(lèi)型。如果您以前在自己的應(yīng)用程序中使用過(guò)標(biāo)識(shí)符 enum,那么在利用 javac -source 1.5 編譯時(shí)需要調(diào)整源代碼。
public enum StopLight { red, amber, green };
靜態(tài)導(dǎo)入
靜態(tài)導(dǎo)入特性實(shí)現(xiàn)為“import static”,允許您從一個(gè)類(lèi)引用靜態(tài)常量,而不需要繼承這個(gè)類(lèi)。每次我們添加一個(gè)組件時(shí),不必使用 BorderLayout.CENTER,只要引用 CENTER 就可以了。
import static java.awt.BorderLayout.*;
getContentPane().add(new JPanel(), CENTER);
格式化的輸出
現(xiàn)在開(kāi)發(fā)人員可以選擇使用 printf type 功能性來(lái)生成格式化的輸出。這將有助于遷移傳統(tǒng)的 C 應(yīng)用程序,作很少的更改或者不作更改就能保留相同的文本布局。
大多數(shù)公共 C printf 格式化程序是可用的,另外,一些 Java 類(lèi)(比如 Date 和BigInteger)也具有格式化規(guī)則。更多信息請(qǐng)參見(jiàn) java.util.Formatter 類(lèi)。
System.out.printf("name count\n");
System.out.printf("%s %5d\n", user,total);
格式化的輸入
Scanner API 為從系統(tǒng)控制臺(tái)或任何數(shù)據(jù)流讀取數(shù)據(jù)提供基本的輸入功能性。下面的例子從標(biāo)準(zhǔn)輸入讀取一個(gè) String 并期待下一個(gè) int 值。
如果沒(méi)有數(shù)據(jù)可用,像 next 和 nextInt 這樣的 Scanner 方法將會(huì)阻塞。如果您需要處理更加復(fù)雜的輸入,那么從 java.util.Formatter 類(lèi)還可以得到模式匹配算法。
Scanner s=Scanner.create(System.in);
String param= s.next();
int value=s.nextInt();
s.close();
Varargs
varargs(可變參數(shù))功能性允許多個(gè)參數(shù)傳遞作為方法的參數(shù)。它需要簡(jiǎn)單的 ... 符號(hào),該符號(hào)用于接收參數(shù)列表的方法,并且它還被用于實(shí)現(xiàn) printf 所需參數(shù)的靈活數(shù)量。
void argtest(Object ... args) {
for (int i=0;i <args.length; i++) {
}
}
argtest("test", "data");
并發(fā)實(shí)用程序
并發(fā)實(shí)用程序庫(kù)由 Doug Lea 定義在 JSR-166 中,是 J2SE 1.5 平臺(tái)中流行的并發(fā)軟件包的一個(gè)特殊版本。它提供強(qiáng)大的、高級(jí)別的線程構(gòu)造,包括 executors(這是一個(gè)線程任務(wù)框架)、線程安全隊(duì)列、Timers、鎖(包括原子鎖)和其他同步原語(yǔ)。
著名的旗語(yǔ)(semaphore)是這樣一個(gè)鎖。旗語(yǔ)與現(xiàn)在使用的 wait 的使用方式相同,用于限制對(duì)一塊代碼的訪問(wèn)。旗語(yǔ)更加靈活,并且也允許許多并發(fā)的線程訪問(wèn),同時(shí)允許您在獲得一個(gè)鎖之前對(duì)它進(jìn)行測(cè)試。下面的例子使用剛好一個(gè)旗語(yǔ),也叫做二進(jìn)制旗語(yǔ)。更多信息請(qǐng)參見(jiàn) java.util.concurrent 軟件包。
final private Semaphore s= new Semaphore(1, true);
s.acquireUninterruptibly(); //for non-blocking version use s.acquire()
balance=balance+10; //protected value
s.release(); //return semaphore token
rmic —— rmi 編譯器
您不再需要使用 rmic —— rmi 編譯器工具——來(lái)生成最遠(yuǎn)程的接口存根。動(dòng)態(tài)代理的引入意味著通常由存根提供的信息可以在運(yùn)行時(shí)被發(fā)現(xiàn)。更多信息請(qǐng)參見(jiàn) RMI 版本說(shuō)明。
可擴(kuò)展性和性能
1.5 版本承諾在可擴(kuò)展性和性能方面的改進(jìn),新的重點(diǎn)在于啟動(dòng)時(shí)間和內(nèi)存占用,使它更加易于以最大的速度部署應(yīng)用程序。
最重大的一個(gè)更新是引入了 Hotspot JVM 中的類(lèi)數(shù)據(jù)共享。該技術(shù)不僅在多個(gè)正在運(yùn)行的 JVM 之間共享只讀數(shù)據(jù),而且改進(jìn)了啟動(dòng)時(shí)間,因?yàn)楹诵牡?JVM 類(lèi)都是預(yù)先打包的。
性能工效是 J2SE 1.5 中的一個(gè)新特性,這意味著如果您一直使用的是以前版本中專(zhuān)門(mén)的 JVM 運(yùn)行時(shí)選項(xiàng), 那么可能值得不用選項(xiàng)或者用很少的選項(xiàng)重新驗(yàn)證您的性能。
監(jiān)控和可管理性
監(jiān)控和可管理性是 Java 平臺(tái)中的 RAS (Reliability, Availability, Serviceability,即可*性、可用性、可服務(wù)性) 的一個(gè)關(guān)鍵組件。
JVM Monitoring & Management API (JSR-174) 指定一組全面的可以從正在運(yùn)行的 JVM 進(jìn)行監(jiān)控的 JVM internals。 該信息可通過(guò) JMX (JSR-003) MBeans 訪問(wèn)到,也可以使用 JMX 遠(yuǎn)程接口 (JSR-160) 和行業(yè)標(biāo)準(zhǔn) SNMP 工具而遠(yuǎn)程訪問(wèn)得到。
最有用的一個(gè)特性是一個(gè)低內(nèi)存檢測(cè)程序。當(dāng)超過(guò)閥值時(shí),JMX MBeans 可以通知已注冊(cè)的偵聽(tīng)程序。更多信息請(qǐng)參見(jiàn) javax.management 和 java.lang.management。
為了了解新的 API 是多么容易使用,下面報(bào)告了 Hotspot JVM 中內(nèi)存堆的詳細(xì)使用情況。
import java.lang.management.*;
import java.util.*;
import javax.management.*;
public class MemTest {
public static void main(String args[]) {
List pools =ManagementFactory.getMemoryPoolMBeans();
for(ListIterator i = pools.listIterator(); i.hasNext();) {
MemoryPoolMBean p = (MemoryPoolMBean) i.next();
System.out.println("Memory type="+p.getType()+" Memory usage="+p.getUsage());
}
}
}
新的 JVM profiling API (JSR-163)
該版本還包含一個(gè)更強(qiáng)大的本機(jī) profiling API,叫做 JVMTI。該 API 已經(jīng)在 JSR 163 中指定了,并由對(duì)改善的 profiling 接口的需求所推動(dòng)。但是,JVMTI 除了具有 profiling 功能之外,還想要涵蓋全范圍的本機(jī)內(nèi)部過(guò)程工具訪問(wèn),包括監(jiān)控工具、調(diào)試工具以及潛在的各種各樣的其他代碼分析工具。
該實(shí)現(xiàn)包含一個(gè)用于字節(jié)碼裝置(instrumentation)——Java 編程語(yǔ)言裝置服務(wù)(Java Programming Language Instrumentation Services,JPLIS)的機(jī)制。這使得分析工具只在需要的地方添加額外的配置信息(profiling)。該技術(shù)的優(yōu)點(diǎn)是,它允許更加集中的分析,并且限制了正在運(yùn)行的 JVM 上的 profiling 工具的引用。該裝置甚至可以在運(yùn)行時(shí)和類(lèi)加載時(shí)動(dòng)態(tài)地生成,并且可以作為類(lèi)文件預(yù)先處理。
下面這個(gè)例子創(chuàng)建了一個(gè)裝置鉤(instrumentation hook),它可以從磁盤(pán)加載類(lèi)文件的一個(gè)已修改的版本。要運(yùn)行該測(cè)試,可利用 java -javaagent:myBCI BCITest 啟動(dòng) JRE。
//File myBCI.java
import java.lang.instrument.Instrumentation;
public class myBCI {
private static Instrumentation instCopy;
public static void premain(String options, Instrumentation inst) {
instCopy = inst;
}
public static Instrumentation getInstrumentation() {
return instCopy;
}
}
//File BCITest.java
import java.nio.*;
import java.io.*;
import java.nio.channels.*;
import java.lang.instrument.*;
public class BCITest {
public static void main (String[] args) {
try {
OriginalClass mc = new OriginalClass();
mc.message();
FileChannel fc=new FileInputStream(new File("modified"+File.separator+"OriginalClass.class")).getChannel();
ByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, (int)fc.size());
byte[] classBuffer = new byte[buf.capacity()];
buf.get(classBuffer, 0, classBuffer.length);
myBCI.getInstrumentation().redefineClasses(new ClassDefinition[] {new ClassDefinition(mc.getClass(), classBuffer)});
mc.message();
}catch (Exception e){}
}
}
//OriginalClass.java
//Compile in current directory
//Copy source to modified directory,change message and recompile
public class OriginalClass {
public void message() {
System.out.println("OriginalClass");
}
}
改進(jìn)的診斷能力
如果沒(méi)有控制臺(tái)窗口可用,生成的 Stack 跟蹤就很笨拙。兩個(gè)新的 API —— getStackTrace 和 Thread.getAllStackTraces —— 以程序的方式提供該信息。
StackTraceElement e[]=Thread.currentThread().getStackTrace();
for (int i=0; i <e.length; i++) {
System.out.println(e);
}
System.out.println("\n"+Thread.getAllStackTraces());
Hotspot JVM 包含一個(gè)致命的錯(cuò)誤處理程序(error hander),如果 JVM 異常中斷,它可以運(yùn)行用戶提供的腳本。使用 Hotspot JVM 可服務(wù)性代理連接器,調(diào)試工具也可以連接到一個(gè)掛起的 JVM 或者核心文件。
-XX:OnError="command"
-XX:OnError="pmap %p"
-XX:OnError="gdb %p"
optional %p used as process id
桌面客戶端
Java 桌面客戶端保留有 Java 平臺(tái)的一個(gè)關(guān)鍵組件,并且這一點(diǎn)成了 J2SE 1.5 中許多改進(jìn)的焦點(diǎn)。
這個(gè) Beta 版本包含啟動(dòng)時(shí)間和內(nèi)存占用方面的一些早期改進(jìn)。該版本不僅更快,并且 Swing 工具集采用了一個(gè)暫新的叫做 Ocean 的主題。
通過(guò)建立 J2SE 1.4.2 中的更新,GTK 和 Windows XP 外觀方面有了更進(jìn)一步的改進(jìn)。
Windows XP
Click to Enlarge
Linux/Redhat
Click to Enlarge
具有最新 OpenGL 驅(qū)動(dòng)程序并且選擇了圖形卡的 Linux 和 Solaris 用戶,可以使用下面的運(yùn)行時(shí)屬性從 Java2D 獲得本機(jī)硬件加速:
java -Dsun.java2d.opengl=true -jar Java2D.
Linux 版本也具有快速的 X11 Toolkit,叫做 XAWT,默認(rèn)情況下是啟用的。如果您需要與 motif 版本進(jìn)行比較,可以使用下面的系統(tǒng)屬性:
java -Dawt.toolkit=sun.awt.motif.MToolkit -jar Notepad.jar
(X11 Toolkit 叫做 sun.awt.X11.XToolkit)
X11 Toolkit 也使用 XDnD 協(xié)議,所以您可以在 Java 和其他應(yīng)用(比如 StarOffice 或 Mozilla)之間拖放簡(jiǎn)單的組件。
其他特性
核心 XML 支持
J2SE 1.5 引入了核心 XML 平臺(tái)的幾個(gè)修訂,包括 XML 1.1 和 Namespace、XML Schema、SAX 2.0.1、XSLT 和快速 XLSTC 編譯器,以及最后的 DOM 第 3 層支持。
除了支持核心 XML 之外,未來(lái)版本的 Java Web Services Developer Pack 將交付最新的 Web 服務(wù)標(biāo)準(zhǔn):JAX-RPC & SAAJ (WSDL/SOAP)、JAXB、XML Encryption and Digital Signature,以及用于注冊(cè)的 JAXR。
輔助字符支持
32 位的輔助字符支持作為傳輸?shù)?Unicode 4.0 支持的一部分,已經(jīng)慎重地添加到該平臺(tái)。輔助字符被編碼為一對(duì)特殊的 UTF16 值,以生成一個(gè)不同的字符或者碼點(diǎn)(codepoint)。一個(gè)代理對(duì)(surrogate pair)是一個(gè)高 UTF16 值和后面的一個(gè)低 UTF16 值的組合。這些高值和低值來(lái)自一個(gè)特殊范圍的 UTF16 值。
一般來(lái)說(shuō),當(dāng)使用 String 或者字符序列時(shí),核心 API 庫(kù)將透明地為您處理新的輔助字符。但是因?yàn)?Java "char" 仍然保留為 16 位,所以非常少的一些使用 char 作為參數(shù)的方法,現(xiàn)在有了足夠的可以接受 int 值的方法,其中 int 值可以代表新的更大的值。特別是 Character 類(lèi),具有附加的方法來(lái)檢索當(dāng)前的字符和接下來(lái)的字符,以便檢索輔助的碼點(diǎn)值,如下所示:
String u="\uD840\uDC08";
System.out.println(u+"+ "+u.length());
System.out.println(Character.isHighSurrogate(u.charAt(0)));
System.out.println((int)u.charAt(1));
System.out.println((int)u.codePointAt(0));
更多信息請(qǐng)參見(jiàn) Character 中的 Unicode 部分。
JDBC RowSets
JDBC 行集支持有兩個(gè)主要的更新。CachedRowSet 包含從數(shù)據(jù)庫(kù)檢索的行的內(nèi)存中的集合。但是它們也是不連接的,這意味著以后更新可以與數(shù)據(jù)庫(kù)重新同步。
另一個(gè)組件是 WebRowSet,它使用數(shù)據(jù)庫(kù)行通過(guò) XML 來(lái)傳輸數(shù)據(jù)。
參考資料:
New Language Features for Ease of Development in the Java 2 Platform, Standard Edition 1.5: http://java.sun.com/features/2003/05/bloch_qa.html
Tiger Component JSRs
003 Java Management Extensions (JMX) Specification http://jcp.org/en/jsr/detail?id=3
013 Decimal Arithmetic Enhancement http://jcp.org/en/jsr/detail?id=13
014 Add Generic Types To The Java Programming Language http://jcp.org/en/jsr/detail?id=14
028 Java SASL Specification http://jcp.org/en/jsr/detail?id=28
114 JDBC Rowset Implementations http://jcp.org/en/jsr/detail?id=114
133 Java Memory Model and Thread Specification Revision http://jcp.org/en/jsr/detail?id=133
160 Java Management Extensions (JMX) Remote API 1.0 http://jcp.org/en/jsr/detail?id=160
163 Java Platform Profiling Architecture http://jcp.org/en/jsr/detail?id=163
166 Concurrency Utilities http://jcp.org/en/jsr/detail?id=166
174 Monitoring and Management Specification for the Java Virtual Machine http://jcp.org/en/jsr/detail?id=174
175 A Metadata Facility for the Java Programming Language http://jcp.org/en/jsr/detail?id=175
200 Network Transfer Format for Java Archives http://jcp.org/en/jsr/detail?id=200
201 Extending the Java Programming Language with Enumerations, Autoboxing, Enhanced for Loops and Static Import http://jcp.org/en/jsr/detail?id=201
204 Unicode Supplementary Character Support http://jcp.org/en/jsr/detail?id=204
206 Java API for XML Processing (JAXP) 1.3 http://jcp.org/en/jsr/detail?id=206
最近在公司里做了一個(gè)手機(jī)的項(xiàng)目,需要JAVA程序在發(fā)送短信的時(shí)候和第三方的短信服務(wù)器連接。短信接口是用C++寫(xiě)的。琢磨了三天,大致搞懂了JNI的主體部分。先將心得整理,希望各位朋友少走彎路。
首先引用一篇文章,介紹一個(gè)簡(jiǎn)單的JNI的調(diào)用的過(guò)程。
JAVA以其跨平臺(tái)的特性深受人們喜愛(ài),而又正由于它的跨平臺(tái)的目的,使得它和本地機(jī)器的各種內(nèi)部聯(lián)系變得很少,約束了它的功能。解決JAVA對(duì)本地操作的一種方法就是JNI。
JAVA通過(guò)JNI調(diào)用本地方法,而本地方法是以庫(kù)文件的形式存放的(在WINDOWS平臺(tái)上是DLL文件形式,在UNIX機(jī)器上是SO文件形式)。通過(guò)調(diào)用本地的庫(kù)文件的內(nèi)部方法,使JAVA可以實(shí)現(xiàn)和本地機(jī)器的緊密聯(lián)系,調(diào)用系統(tǒng)級(jí)的各接口方法。
簡(jiǎn)單介紹及應(yīng)用如下:
一、JAVA中所需要做的工作
在JAVA程序中,首先需要在類(lèi)中聲明所調(diào)用的庫(kù)名稱(chēng),如下:
- static {
- System.loadLibrary(“goodluck”);
- }
在這里,庫(kù)的擴(kuò)展名字可以不用寫(xiě)出來(lái),究竟是DLL還是SO,由系統(tǒng)自己判斷。
還需要對(duì)將要調(diào)用的方法做本地聲明,關(guān)鍵字為native。并且只需要聲明,而不需要具 體實(shí)現(xiàn)。如下:
public native static void set(int i);
public native static int get();
然后編譯該JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就會(huì)生成C/C++的頭文件。
例如程序testdll.java,內(nèi)容為:
- public class testdll
- {
- static
- {
- System.loadLibrary("goodluck");
- }
- public native static int get();
- public native static void set(int i);
- public static void main(String[] args)
- {
- testdll test = new testdll();
- test.set(10);
- System.out.println(test.get());
- }
- }
用javac testdll.java編譯它,會(huì)生成testdll.class。
再用javah testdll,則會(huì)在當(dāng)前目錄下生成testdll.h文件,這個(gè)文件需要被C/C++程序調(diào)用來(lái)生成所需的庫(kù)文件。
二、C/C++中所需要做的工作
對(duì)于已生成的.h頭文件,C/C++所需要做的,就是把它的各個(gè)方法具體的實(shí)現(xiàn)。然后編譯連接成庫(kù)文件即可。再把庫(kù)文件拷貝到JAVA程序的路徑下面,就可以用JAVA調(diào)用C/C++所實(shí)現(xiàn)的功能了。
接上例子。我們先看一下testdll.h文件的內(nèi)容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class testdll */
#ifndef _Included_testdll
#define _Included_testdll
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: testdll
* Method: get
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);
/*
* Class: testdll
* Method: set
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
在具體實(shí)現(xiàn)的時(shí)候,我們只關(guān)心兩個(gè)函數(shù)原型
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
這里JNIEXPORT和JNICALL都是JNI的關(guān)鍵字,表示此函數(shù)是要被JNI調(diào)用的。而jint是以JNI為中介使JAVA的int類(lèi)型與本地的int溝通的一種類(lèi)型,我們可以視而不見(jiàn),就當(dāng)做int使用。函數(shù)的名稱(chēng)是JAVA_再加上java程序的package路徑再加函數(shù)名組成的。參數(shù)中,我們也只需要關(guān)心在JAVA程序中存在的參數(shù),至于JNIEnv*和jclass我們一般沒(méi)有必要去碰它。
好,下面我們用testdll.cpp文件具體實(shí)現(xiàn)這兩個(gè)函數(shù):
#include "testdll.h"
int i = 0;
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)
{
return i;
}
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)
{
i = j;
}
編譯連接成庫(kù)文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名稱(chēng)要與JAVA中需要調(diào)用的一致,這里就是goodluck.dll 。把goodluck.dll拷貝到testdll.class的目錄下,java testdll運(yùn)行它,就可以觀察到結(jié)果了。
我的項(xiàng)目比較復(fù)雜,需要調(diào)用動(dòng)態(tài)鏈接庫(kù),這樣在JNI傳送參數(shù)到C程序時(shí),需要對(duì)參數(shù)進(jìn)行處理轉(zhuǎn)換。才可以被C程序識(shí)別。
大體程序如下:
- public class SendSMS {
- static
- {
- System.out.println(System.getProperty("java.library.path"));
- System.loadLibrary("sms");
- }
- public native static int SmsInit();
- public native static int SmsSend(byte[] mobileNo, byte[] smContent);
- }
在這里要注意的是,path里一定要包含類(lèi)庫(kù)的路徑,否則在程序運(yùn)行時(shí)會(huì)拋出異常:
java.lang.UnsatisfiedLinkError: no sms in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)
at java.lang.Runtime.loadLibrary0(Runtime.java:788)
at java.lang.System.loadLibrary(System.java:834)
at com.mobilesoft.sms.mobilesoftinfo.SendSMS.<clinit>(SendSMS.java:14)
at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
Exception in thread "main"
指引的路徑應(yīng)該到.dll文件的上一級(jí),如果指到.dll,則會(huì)報(bào):
java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)
at java.lang.Runtime.loadLibrary0(Runtime.java:788)
at java.lang.System.loadLibrary(System.java:834)
at com.mobilesoft.sms.mobilesoftinfo.SendSMS.<clinit>(SendSMS.java:14)
at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
Exception in thread "main"
通過(guò)編譯,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h頭文件。(建議使用Jbuilder進(jìn)行編譯,操作比較簡(jiǎn)單!)這個(gè)頭文件就是Java和C之間的紐帶。要特別注意的是方法中傳遞的參數(shù)jbyteArray,這在接下來(lái)的過(guò)程中會(huì)重點(diǎn)介紹。
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */
- #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
- #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS
- * Method: SmsInit
- * Signature: ()I
- */
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit
- (JNIEnv *, jclass);
- /*
- * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS
- * Method: SmsSend
- * Signature: ([B[B)I
- */
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend
- (JNIEnv *, jclass, jbyteArray, jbyteArray);
- #ifdef __cplusplus
- }
- #endif
- #endif
對(duì)于我要調(diào)用的C程序的動(dòng)態(tài)鏈接庫(kù),C程序也要提供一個(gè)頭文件,sms.h。這個(gè)文件將要調(diào)用的方法羅列了出來(lái)。
- /*
- * SMS API
- * Author: yippit
- * Date: 2004.6.8
- */
- #ifndef MCS_SMS_H
- #define MCS_SMS_H
- #define DLLEXPORT __declspec(dllexport)
- /*sms storage*/
- #define SMS_SIM 0
- #define SMS_MT 1
- /*sms states*/
- #define SMS_UNREAD 0
- #define SMS_READ 1
- /*sms type*/
- #define SMS_NOPARSE -1
- #define SMS_NORMAL 0
- #define SMS_FLASH 1
- #define SMS_MMSNOTI 2
- typedef struct tagSmsEntry {
- int index; /*index, start from 1*/
- int status; /*read, unread*/
- int type; /*-1-can't parser 0-normal, 1-flash, 2-mms*/
- int storage; /*SMS_SIM, SMS_MT*/
- char date[24];
- char number[32];
- char text[144];
- } SmsEntry;
- DLLEXPORT int SmsInit(void);
- DLLEXPORT int SmsSend(char *phonenum, char *content);
- DLLEXPORT int SmsSetSCA(char *sca);
- DLLEXPORT int SmsGetSCA(char *sca);
- DLLEXPORT int SmsSetInd(int ind);
- DLLEXPORT int SmsGetInd(void);
- DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);
- DLLEXPORT int SmsSaveFlash(int flag);
- DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);
- DLLEXPORT int SmsDelete(int storage, int index);
- DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -> read*/
- #endif
在有了這兩個(gè)頭文件之后,就可以進(jìn)行C程序的編寫(xiě)了。也就是實(shí)現(xiàn)對(duì)JNI調(diào)用的兩個(gè)方法。在網(wǎng)上的資料中,由于調(diào)用的方法實(shí)現(xiàn)的都比較簡(jiǎn)單,(大多是打印字符串等)所以避開(kāi)了JNI中最麻煩的部分,也是最關(guān)鍵的部分,參數(shù)的傳遞。由于Java和C的編碼是不同的,所以傳遞的參數(shù)是要進(jìn)行再處理,否則C程序是會(huì)對(duì)參數(shù)在編譯過(guò)程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actual parameter 2等。
Sms.c的程序如下:
- #include "sms.h"
- #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)
- {
- return SmsInit();
- }
-
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)
- {
- char * pSmscontent ;
- //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);
- jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);
- char * pMobileNo = (char *)arrayBody;
- printf("[%s]\n ", pMobileNo);
- //jsize size = (*env)->GetArrayLength(env,smscontent);
- arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);
- pSmscontent = (char *)arrayBody;
- printf("<%s>\n", pSmscontent);
- return SmsSend(pMobileNo,pSmscontent);
- }
對(duì)于C或C++,在程序上是會(huì)有稍微的不同,這可以由讀者對(duì)其進(jìn)行適當(dāng)?shù)男薷摹_@里要注意的是GetArrayLength,GetByteArrayElements等這些JNI中已經(jīng)包含的方法,這些方法是專(zhuān)門(mén)對(duì)轉(zhuǎn)換參數(shù)類(lèi)型而提供的。具體的方法有很多,在下一篇中會(huì)做專(zhuān)門(mén)的介紹。
在完成了上述的文件后,可以對(duì)sms.c進(jìn)行編譯,生成.dll文件(建議在release中編譯,這樣動(dòng)態(tài)鏈接庫(kù)的容積會(huì)比較小!)
完成.dll文件的編譯后,就可以在Java中調(diào)用C程序中的方法了。例如文件test.java
- public class test {
- public test() {
- }
- public static void main(String[] args) {
- byte[] mobileno = {
- 0x31, 0x33, 0x36, 0x36, 0x31, 0x36, 0x33, 0x30, 0x36, 0x36, 0x37, 0x00};
- String smscontentemp = "早上好";
- byte[] temp = {0};
- try {
- byte[] smscontentdb = smscontentemp.getBytes("gbk");
- byte[] smscontent = new byte[smscontentdb.length + temp.length];
- System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);
- System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);
- SendSMS sendSMS = new SendSMS();
- sendSMS.SmsInit();
- if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {
- System.out.println("chenggong !");
- }
- else {
- System.out.println("shibai !");
- }
- }catch (Exception ex) {}
- }
- }
在這個(gè)文件中要注意的有一點(diǎn),就是在傳遞字節(jié)數(shù)組到C程序中時(shí),最后的結(jié)尾一定要以0結(jié)束。這是一個(gè)偷懶的做法,不過(guò)是個(gè)有效的做法。因?yàn)榇蠖鄶?shù)情況下,接口是由第三方提供的。所以我們一般是不知道在C的方法里,具體是怎么處理參數(shù)的。而C又是要求數(shù)組是有長(zhǎng)度。所以,在Java中,如果你不想寫(xiě)程序傳數(shù)組的長(zhǎng)度,那么在數(shù)組中以0結(jié)尾就是最方便的方法了。當(dāng)然,如果有更好的方法也希望大家提出。
到這里,一個(gè)完整的Java通過(guò)JNI調(diào)用動(dòng)態(tài)鏈接庫(kù)的程序就完成了。實(shí)際上也不是很復(fù)雜。只要多注意一下細(xì)節(jié),是很容易得出來(lái)的。