关于一些基础的Java问题的解答(八)

上一篇文章的传送门:关于一些基础的Java问题的解答(七)

JNI的使用

先简单介绍一下JNI,JNI即Java Native Interface的缩写,中文译为“Java本地调用”。通俗的说,JNI是一种实现Java层与Native层(C/C++)交互的技术。有时为了追求效率问题,或者是使用用native代码编写的函数库,我们就不得不使用JNI接口。
以下是一个JNI的小例子:

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) throws Exception {
// 动态库名字,windows平台自动拓展成makeStr_jni.dll
System.loadLibrary("makeStr_jni");
// 打印字符串
printString("Java World!");
}
// native关键字表示为本地方法
public static native void printString(String str);
}

我们在Java中使用JNI接口只需要两步:

  1. 使用native关键字声明某方法为本地方法
  2. 使用System.loadLibrary加载由C/C++编写成的动态链接库(我们只需要写出库名字即可,Java会根据平台补充库的后缀名windows:dll,linux:so)

接下来我们来看看如何编写Native层的cpp文件(以下注册Native函数方法为静态注册,动态注册本文不提及):
为了让Java层中的函数与Native层中的函数一一对应,JNI规定了一套复杂的命名体系。在此本文就不深入介绍该命名方法了,我们使用JDK提供的javah工具生成对应的.h头文件:
首先我们把写好的Java代码编译成.class文件:

JNI_pic1

然后我们使用javah工具生成对应的.h头文件(-o后面第一个参数为.h的文件名,第二个参数为.class的文件名):

jni2_pic2

然后我们就能看到生成的.h文件了:

jni3_pic3

以下是.h文件中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class Main */

#ifndef _Included_Main
#define _Included_Main
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Main 方法所在的类
* Method: printString 方法名
* Signature: (Ljava/lang/String;)V 签名
*/
JNIEXPORT void JNICALL Java_Main_printString
(JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

可以看到我们在Java层定义的printString方法对应成了Native层的Java_Main_printString方法,方法上面有javah给我们生成的注释,它提供了以下信息:

  • Class:方法所属于的类
  • Method:方法的名称
  • Signature:方法的签名。签名是由于Java中的方法允许重载,仅仅通过类与名称并不能确定该Native方法所对应的Java方法,因此JNI技术中就将参数类型和返回值的组合作为了一个函数的签名信息,有了签名和函数名我们才能顺利找到Javac层中对应的函数

接下来我们只要写一个.cpp文件实现该方法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
#include"_Main.h"

using namespace std;

/*
* JNIEnv : env JNI环境,一个提供JNI系统函数的结构体
* jclass : clazz 代表Java层的Class对象,由于printString方法是一个静态方法,故传入Class对象
* jstring : s 代表Java层的String对象,表示传入的参数
*/
JNIEXPORT void JNICALL Java_Main_printString
(JNIEnv * env, jclass clazz, jstring s) {
jboolean iscopy;
// 通过jstring对象生成本地字符串
const char *charData = env->GetStringUTFChars(s, &iscopy);
// 打印字符串
cout << "A message from Native World: " << charData << endl;
// 释放资源
env->ReleaseStringUTFChars(s, charData);
}

以上代码相信注释已解释的非常清楚,故此处不再赘述。值得一提的是在Native层中有多种与Java层中相对应的数据结构,如:jclass代表Class对象,jstring代表String对象,jboolean代表boolean基本类型等。有兴趣的童鞋可以自行去了解下。
写完该.cpp文件后,我们编译工程生成dll文件(Windows平台),并把文件加入我们Java工程的引用库中:
JNI4_pic4

然后运行我们的Java代码,我们就可以看到使用JNI技术后来自Native层的问候:
JNI5_pic5

String拼接的两种方法,concat与+

总的来说,区别有以下几点:

  1. concat是String方法,而“+”是String重载的操作符
  2. concat只能连接String字符串,而“+”可以连接任何Object
  3. 由于String是不变的对象,concat底层是通过创建新的String对象实现拼接,而“+”使用的是StringBuilder工具实现拼接

Java元注解(Annotation)

自Java5.0版本引入注解之后,它就成为了Java平台中非常重要的一部分。开发过程中,我们也时常在应用代码中会看到诸如@Override,@Deprecated这样的注解

用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据

当我们使用自定义的注解时,我们就会使用到Java提供的元注解。元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明,它们均被存放在java.lang.annotation包下面,分别是:

  • @Target
  • @Retention
  • @Documented
  • @Inherited

@Target

@Target说明了Annotation所修饰的对象范围,它的取值(ElementType)有:

  • CONSTRUCTOR:用于描述构造器
  • FIELD:用于描述域
  • LOCAL_VARIABLE:用于描述局部变量
  • METHOD:用于描述方法
  • PACKAGE:用于描述包
  • PARAMETER:用于描述参数
  • TYPE:用于描述类、接口(包括注解类型)或enum声明

例子:

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
public @interface A {
}

@Target(ElementType.FIELD)
public @interface B {

}

注解A可以用于注解类、接口(包括注解类型) 或enum声明,而注解B仅可用于注解类的成员变量

@Retention

@Retention定义了该Annotation被保留的时间长短,表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)有:

  • SOURCE:在源文件中有效(即源文件保留)
  • CLASS:在class文件中有效(即class保留)
  • RUNTIME:在运行时有效(即运行时保留,可以通过反射获取)

@Documented

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,表明这个注解应该被 javadoc工具记录, 默认情况下,javadoc是不包括注解的。但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理,所以注解类型信息也会被包括在生成的文档中。@Documented是一个标记注解,没有成员

@Inherited

@Inherited作用是,使用此注解声明出来的自定义注解时,如果自定义注解在类上面时,子类会自动继承此注解,否则的话,子类不会继承此注解。这里一定要记住,使用@Inherited声明出来的注解,只有在类上使用时才会有效,对方法,属性等其他无效。

具体的例子可以查看这篇博客:http://blog.csdn.net/snow_crazy/article/details/39381695

自定义注解(Annotation)

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。通过使用元注解来标明自定义注解的某些功能。

定义注解格式:

public @interface 注解名 {定义体}

注解参数的可支持数据类型如下:

  • 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
  • String
  • Class
  • enum(枚举)
  • Annotation(注解)
  • 以上所有类型的数组

例子如下:

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DatabaseTable {

String tableName() default "";
}

上面的例子定义了一个用于描述类、接口(包括注解类型)或enum声明的、在运行时保留的(即可以反射获取的)注解,该注解拥有属性值tableName,默认值为空字符串,使用方法如下:

1
2
@DatabaseTable(tableName = "tableBean")
public class TableBean {}

Java反射中getXXX与getDeclaredXXX方法的区别

对于Fields:

  • getField:只能获取类的public字段、但可以获取从父类继承的public字段
  • getDeclaredField:能获取当前类的所有字段、但不能获取从父类继承的字段

对于Methods:

  • getMethods:只能获取类的public方法、但可以获取从父类继承的public方法
  • getDeclaredMethods:能获取当前类的所有方法、但不能获取从父类继承的方法

对于Constructors:

  • getConstructors:返回所有的public构造器
  • getDeclaredConstructors:返回所有的构造器

对于Annotations:

  • getAnnotations:对于Class(类),返回所有注解,包括@Inherited继承的
  • getDeclaredAnnotations:对于Class(类),返回当前类拥有注解,不包括@Inherited继承的