java 异常处理

现在大多数高级语言都支持异常处理机制,而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

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

java 本地方法调用(JNI)

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

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!