什么是JNI?

JNI是Java本地接口,简单说就是让Java能"调用"C/C++代码的桥梁。想象一下,Java是优雅的舞者,而C/C++是力量型的拳击手,JNI就是让它们能配合表演的导演!🎯

为什么需要JNI?

  • Java在某些场景下性能不够(如复杂算法、音视频处理)
  • 需要访问底层硬件或系统功能
  • 已有C/C++库想在Java中复用
  • 提高代码安全性(反编译C/C++比Java难得多)

用JNI的完整流程

步骤1:编写Java代码(声明native方法)

public class JNIDemo {
    // 声明native方法,不写方法体
    public native int add(int a, int b);
    
    static {
        // 加载本地库,注意:不带扩展名!
        System.loadLibrary("jniDemo"); // Windows是jniDemo.dll,Linux是libjniDemo.so
    }
    
    public static void main(String[] args) {
        JNIDemo demo = new JNIDemo();
        int result = demo.add(5, 3);
        System.out.println("结果: " + result); // 应该输出8
    }
}

步骤2:编译Java类

javac JNIDemo.java

步骤3:生成C/C++头文件(关键一步!)

javac -h ./ JNIDemo.java  # JDK 10+推荐使用这个命令

注意:JDK 10+已经移除了javah命令,用javac -h代替

生成的头文件JNIDemo.h内容如下:

/* 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:    add
 * Signature: (II)I  // (两个int参数,返回int)
 */
JNIEXPORT jint JNICALL Java_JNIDemo_add(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif

步骤4:编写C/C++实现

创建JNIDemo.c文件:

#include "JNIDemo.h"

JNIEXPORT jint JNICALL Java_JNIDemo_add(JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b; // 实现加法
}

命名规则:Java_包名_类名_方法名,这里没有包,所以是Java_JNIDemo_add

步骤5:编译生成动态库

Windows (生成.dll):

cl /LD JNIDemo.c  # 生成jniDemo.dll

Linux (生成.so):

gcc -shared -o libjniDemo.so JNIDemo.c

Mac (生成.jnilib):

gcc -dynamiclib -o libjniDemo.jnilib JNIDemo.c

步骤6:运行Java程序

java JNIDemo

如果一切顺利,你会看到输出:

结果: 8

真实场景案例:字符串处理

Java代码

public class StringDemo {
    public native String reverseString(String input);
    
    static {
        System.loadLibrary("stringdemo");
    }
    
    public static void main(String[] args) {
        StringDemo demo = new StringDemo();
        String reversed = demo.reverseString("Hello JNI!");
        System.out.println("反转后: " + reversed); // !INJ olleH
    }
}

C实现

#include "StringDemo.h"
#include <jni.h>
#include <string.h>

JNIEXPORT jstring JNICALL Java_StringDemo_reverseString(JNIEnv *env, jobject obj, jstring input) {
    // 1. 将Java字符串转为C字符串
    const jchar* jchars = (*env)->GetStringChars(env, input, NULL);
    
    // 2. 获取字符串长度
    jsize len = (*env)->GetStringLength(env, input);
    
    // 3. 创建反转后的字符串
    jchar* reversed = (jchar*)malloc(len * sizeof(jchar));
    
    // 4. 反转逻辑
    for (int i = 0; i < len; i++) {
        reversed[i] = jchars[len - 1 - i];
    }
    
    // 5. 转换为Java字符串返回
    jstring result = (*env)->NewString(env, reversed, len);
    
    // 6. 释放内存
    (*env)->ReleaseStringChars(env, input, jchars);
    free(reversed);
    
    return result;
}

JNI关键点总结

  1. 命名规则:Java_包名_类名_方法名,如Java_com_example_MyClass_myMethod
  2. 参数顺序
    • 第一个参数:JNIEnv*(JNI环境指针)
    • 第二个参数:jobject(实例方法)或jclass(静态方法)
    • 后续参数:与Java方法参数一一对应
  3. 数据类型映射
    • Java int → C jint
    • Java String → C jstring
    • Java byte[] → C jbyteArray

常见问题

Q:为什么JDK 10移除了javah?
A:JEP 313移除了javah,用javac -h代替,更简洁。

Q:跨平台问题怎么解决?
A:在Java代码中加载库时,通过System.getProperty("os.name")判断平台,加载对应库。

Q:内存管理要注意什么?
A:用GetStringChars获取字符串时,必须用ReleaseStringChars释放;用NewString创建的字符串要记得管理。

我的建议

刚接触JNI时,我建议先从简单的整数计算开始,熟悉流程后再尝试字符串、数组等复杂类型。一开始可能会因为命名规则或内存管理出错,但只要多练习几次,就会越来越顺手!

小技巧:在IDE中(如IntelliJ或Eclipse),可以配置C/C++开发环境,让JNI开发更高效。

为什么值得学?

  • 能解决Java无法直接处理的问题(如访问硬件)
  • 提升性能(C/C++比Java快)
  • 保护核心代码(C/C++比Java难反编译)
  • 复用现有C/C++库

如果你正在开发高性能应用,或者需要处理底层系统功能,JNI绝对是你的得力助手!💪


Java Native Interface (JNI) 是Java平台自1.1版本起提供的标准编程接口,允许Java程序与C/C++等本地代码进行交互。它主要解决纯Java环境无法处理的场景:当需要高性能计算(如图像处理、音视频编解码)、访问特定平台功能、复用现有C/C++库或与硬件交互时,JNI充当了Java与本地代码之间的桥梁。使用JNI时,Java层通过native关键字声明方法并加载本地库,生成头文件后在C/C++层实现具体逻辑,最终编译成动态链接库(Windows的.dll或Linux的.so)。虽然JNI能显著提升性能和功能扩展性,但其开发复杂度较高,需要同时掌握Java和C/C++,且调试难度较大。