现在大多数高级语言都支持异常处理机制,而C语言是根据返回值(一般是定义为宏的整型数,如EXIT_FAILURE)判断错误类型加上goto语言处理异常,在linux内核中有大量goto语句的出现。而java支持异常处理,异常会改变程序运行顺序,也就说改变程序顺序的有if语句(循环也算是间接的if)、函数调用(或方法调用)和return语句、异常等。首先看一段代码:
OutputStream out = ...; java.sql.Connection conn = ...; try { Statement stat = conn.createStatement(); ResultSet rs = stat.executeQuery("select uid, name from user"); while (rs.next()) { out.println(rs.getString("uid") + ": " + rs.getString("name")); } conn.close(); out.close(); } catch (Exception e) { e.printStackTrace(); }
以上代码是http://www.blogjava.net/freeman1984/archive/2013/07/26/148850.html中关于异常处理习惯的反例,不过这在大多数java程序员中经常出现的问题。
1,一个Exception 试图处理所有的异常,这是相当之不科学的,一个好的习惯是根据不同的异常类型做不同的处理,通过不同的catch块并指定具体的异常处理具体类,作不同的异常,并且catch异常类的顺序是有讲究的,一般先子类,然后再基类,在所有已知具体类都catch后,最后最好在捕获一个Exception类异常,防止可能由于疏忽丢了某些异常。如果需要同时捕获几个异常做统一处理,java7中支持catch (xx1Exception e | xx2Wxception 2 ...)的语法,可以同时对多个类型的异常做统一处理。
2,代码中并没有处理异常,而只是打印一个异常调用栈,并没有具体作处理,一般应该具体输出些日志信息,或者抛出自定义异常,并做一些工作。只要捕获了异常,就要好好处理,否则就不要捕获。
3, 资源的释放一般在finally块中处理,而不应该放在try块中,finally块不管异常是否出现,都会在整个try块退出前执行,一般执行一些清理工作,保证资源正确释放。当然在java7中如果实现了java.lang.AutoCloseable接口,则jvm会自动调用close方法进行资源释放。
4,try块不能太大,有时为了图方便,把一大片代码放入一个try块中,这样不仅出现代码混乱,而且也不利于针对具体异常作处理。
最后修改后的代码:
OutputStream out = ...; java.sql.Connection conn = ...; try { Statement stat = conn.createStatement(); ResultSet rs = stat.executeQuery("select uid, name from user"); while (rs.next()) { out.println(rs.getString("uid") + ": " + rs.getString("name")); } } catch (SQLException e) { log("..."); throw new ApplicationException("..."); } catch (IOException e) { log("..."); throw new ApplicationException("..."); } catch (Exception e) { //do something else } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { // do something else } } if (out != null) { try { out.close(); } catch (IOException e) { // do something else } } }
总之要好好利用java的异常处理机制,不能图方便,只为能正确编译,而把一大片代码放入一个块,然后捕获一个Exception类。
参考:http://www.blogjava.net/freeman1984/archive/2013/07/26/148850.html
java使用native关键字声明本地方法,和声明通常的方法没有什么区别,只有两点不同:一是必须有native关键字,二是以分号结束,没有{},因为在类中并没有实现,这和声明抽象方法类似。本地方法一般是c/c++实现。下面以一个简单的例子来实现一个类,这个类调用本地方法。
public class JNIDemo { public native String input(String prompt); public native int sum(int a, int b); public native int sum(int[] a); static { System.load("/home/fgp/program/java/jni/libutil.so"); /* 以下方法,需要设置lib库的路径 */ //System.loadLibrary("util"); } public static void main(String[] args) { JNIDemo jni = new JNIDemo(); String name = jni.input("Type your name:"); System.out.println("Your name is " + name); System.out.println(jni.sum(1, 2)); System.out.println(jni.sum(new int[]{1, 2, 3, 4, 5})); } }
我们声明了三个方法,同时使用了方法重载。这个三个方法均在main方法中被调用。
使用javac命令编译:
javac JNIDemo.java
此时如果运行java JNIDemo,则会出现异常,因为我们还没有实现本地方法。
在实现本地方法之前,最好先自动生成c头文件,使用javah:
javah -jni JNIDemo
此时会自动产生c头文件,
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class JNIDemo */ #ifndef _Included_JNIDemo #define _Included_JNIDemo #ifdef __cplusplus extern "C" { #endif /* * Class: JNIDemo * Method: input * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_JNIDemo_input (JNIEnv *, jobject, jstring); /* * Class: JNIDemo * Method: sum * Signature: (II)I */ JNIEXPORT jint JNICALL Java_JNIDemo_sum__II (JNIEnv *, jobject, jint, jint); /* * Class: JNIDemo * Method: sum * Signature: ([I)I */ JNIEXPORT jint JNICALL Java_JNIDemo_sum___3I (JNIEnv *, jobject, jintArray); #ifdef __cplusplus } #endif #endif
初次看起来与我们平常的头文件有点不同,这就是java与本地方法通信的协议,或者说规范。比如函数名必须是Java_ClassName_MethodName__Sigature(JNIEnv *, jobject, arg...)。Sigature主要解决c没有实现重载的问题。函数形参JNIEnv* 和jobject 是必须的,分别表示JNI接口指针和对象本身(类似c++ 的this指针)。
我们不难看出,java的原始数据类型与c类型一一对应,本地方法可以直接访问,java中int对应jint,float对应jfloat等。而非原始类型,则直接对应jobject,比如java中的Object对应jobject,当然为了减少程序出错,引入了jobject的子类,比如jstring,jarray,jintArray等等。
这样以上的头文件也就不难理解了。
下面实现本地方法,注意本地方法实现时需要和头文件一致。
先从input方法说起。
#include <stdio.h> #include "JNIDemo.h" JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt) { }
首先jstring并不是c中常规字符串(char *),而是自己重新封装了下,
因此直接调用printf("%s", prompt)是错误的。必须调用JNI的函数GetStringUTFChars把它转化成c字符串。同样的c字符串(char *)可以调用newStringUTF函数转化成jstring。以下就简单多了。实现如下:
JNIEXPORT jstring JNICALL Java_Prompt_input(JNIEnv *env, jobject obj, jstring prompt) { char buf[128]; const char *str = (*env)->GetStringUTFChars(env, prompt, 0); printf("%s\n", str); (*env)->ReleaseStringUTFChars(env, prompt, str); scanf("%s", buf); return (*env)->NewStringUTF(env, buf); }
注意不再使用的jstring,需要手动调用ReleaseXX释放内存。
以下两个方法实现也不难理解,最后的完整版本为:
#include <stdio.h> #include "JNIDemo.h" JNIEXPORT jstring JNICALL Java_JNIDemo_input(JNIEnv *env, jobject obj, jstring prompt) { char buf[128]; const char *str = (*env)->GetStringUTFChars(env, prompt, 0); printf("%s\n", str); /* 必须调用释放函数,否则内存泄漏 */ (*env)->ReleaseStringUTFChars(env, prompt, str); scanf("%s", buf); return (*env)->NewStringUTF(env, buf); } JNIEXPORT jint JNICALL Java_JNIDemo_sum__II(JNIEnv *env, jobject obj, jint a, jint b) { return a + b; } JNIEXPORT jint JNICALL Java_JNIDemo_sum___3I(JNIEnv *env, jobject obj, jintArray array) { int i; int sum = 0; jsize len = (*env)->GetArrayLength(env, array); jint *a = (*env)->GetIntArrayElements(env, array, 0); for (i = 0; i < len; i++) sum += a[i]; (*env)->ReleaseIntArrayElements(env, array, a, 0); return sum; }
最后编译c文件为动态链接库(windows为dll文件,unix为libXX.so文件),
#!/bin/bash gcc -I/usr/lib/jvm/java-7-openjdk-i386/include/ -I/usr/lib/jvm/java-7-openjdk-i386/include/linux/ JNIDemo.c -shared -o libutil.so
然后运行java JNIDemo,输出结果为:Type your name:
Mary Your name is Mary 3 15
另外注意System.load()必须使用绝对路径,否则会出错,如果你的动态链接库已在环境变量中可以直接调用System.loadLibrary("util");
如果出现java.lang.UnsatisfiedLinkError,则可能路径不对,jvm没有找到对应的动态链接库。
参考:http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/index.html