Java 运行在虚拟机中,若要调用操作系统动态库,需声明 native 方法,再由 C/C++ 实现。Java 调用这些 native 方法,等价于调用本地代码中的函数。这套机制称为 JNI(Java Native Interface,Java 本地编程接口)。

Android NDK 开发同样建立在 JNI 之上。下文在纯 Java 环境下编译 C 动态库并用 Java 调用,由浅入深给出四个示例。原文参考:CSDN - u_xtian。

相关:System.load 与 System.loadLibrary、JNI 类型签名 [B [C 等。JDK 22 起可用 Project Panama / FFM API 替代多数 JNI 场景;版本变更见 JDK 21 升级到 JDK 25 的变更汇总。

示例一:Hello World

Java:HelloJni.java

public class HelloJni {

    static {
        System.loadLibrary("hello");
    }

    public static native void sayHello();

    public static void main(String[] args) {
        HelloJni.sayHello();
    }
}

生成头文件

javac HelloJni.java
javah -jni HelloJni   # 旧版工具;JDK 8+ 可用 javac -h . HelloJni.java

生成的 HelloJni.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>

#ifndef _Included_HelloJni
#define _Included_HelloJni
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT void JNICALL Java_HelloJni_sayHello(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

C++ 实现:HelloJni.cpp

#include <stdio.h>
#include "HelloJni.h"

JNIEXPORT void JNICALL Java_HelloJni_sayHello(JNIEnv *env, jclass cls) {
    printf("HelloWorld\n");
}

编译动态库

g++ -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \
    HelloJni.cpp -shared -o libhello.so

参数说明:

  • -I:引入 JVM 头文件路径(macOS 将 linux 换为 darwin)
  • -shared:编译为动态链接库
  • -o:输出文件名;Linux 命名惯例为 lib + 库名 + .so,loadLibrary 时不要写 lib 前缀和 .so 后缀

若 .so 不在系统默认搜索路径,需设置 LD_LIBRARY_PATH 指向其所在目录后再运行 java HelloJni。

示例二:传递 int

C/C++ 对 Java 传入的整数加 1 后返回。

Java:HelloJni.java

public class HelloJni {

    static {
        System.loadLibrary("hello");
    }

    public static native void sayHello();
    public native int getInt();
    public native void setInt(int i);

    public static void main(String[] args) {
        HelloJni hello = new HelloJni();
        hello.setInt(2);
        System.out.println(hello.getInt()); // 输出 3
    }
}

头文件 HelloJni.h(节选)

JNIEXPORT void JNICALL Java_HelloJni_sayHello(JNIEnv *, jclass);
JNIEXPORT jint JNICALL Java_HelloJni_getInt(JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_HelloJni_setInt(JNIEnv *, jobject, jint);

C++ 实现

#include <stdio.h>
#include "HelloJni.h"

static int i = 1;

JNIEXPORT void JNICALL Java_HelloJni_sayHello(JNIEnv *env, jclass cls) {
    printf("HelloWorld\n");
}

JNIEXPORT jint JNICALL Java_HelloJni_getInt(JNIEnv *env, jobject thiz) {
    return i;
}

JNIEXPORT void JNICALL Java_HelloJni_setInt(JNIEnv *env, jobject thiz, jint ji) {
    i = ji + 1;
}

示例三:C++ 构造 Java 对象

POJO:User.java

public class User {

    private long id;
    private String userName;
    private boolean isMan;
    private int age;

    public User() {}

    public User(long id, String userName, boolean isMan, int age) {
        this.id = id;
        this.userName = userName;
        this.isMan = isMan;
        this.age = age;
    }

    public long getId() { return id; }
    public void setId(long id) { this.id = id; }
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }
    public boolean isMan() { return isMan; }
    public void setMan(boolean isMan) { this.isMan = isMan; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

Java:HelloJni.java

public class HelloJni {

    static {
        System.loadLibrary("userbean");
    }

    public native void setUser(String userName);
    public native User getUser();

    public static void main(String[] args) {
        HelloJni hello = new HelloJni();
        hello.setUser("LiangYaotian");
        User user = hello.getUser();
        System.out.println("user from c/c++");
        System.out.println("name:" + user.getUserName());
        System.out.println("isMan?:" + user.isMan());
        System.out.println("age:" + user.getAge());
    }
}

头文件(新增方法)

JNIEXPORT void JNICALL Java_HelloJni_setUser(JNIEnv *, jobject, jstring);
JNIEXPORT jobject JNICALL Java_HelloJni_getUser(JNIEnv *, jobject);

C++ 实现(核心逻辑)

#include <iostream>
#include <stdio.h>
#include "HelloJni.h"

using namespace std;

static jobject user;

JNIEXPORT void JNICALL Java_HelloJni_setUser(JNIEnv *env, jobject thiz, jstring name) {
    jclass userClass = env->FindClass("User");
    jmethodID userMethod = env->GetMethodID(userClass, "<init>", "()V");
    jfieldID mId = env->GetFieldID(userClass, "id", "J");
    jfieldID mUserName = env->GetFieldID(userClass, "userName", "Ljava/lang/String;");
    jfieldID mIsMan = env->GetFieldID(userClass, "isMan", "Z");
    jfieldID mAge = env->GetFieldID(userClass, "age", "I");

    jobject userObject = env->NewObject(userClass, userMethod);
    env->SetObjectField(userObject, mUserName, name);
    env->SetLongField(userObject, mId, 1001);
    env->SetBooleanField(userObject, mIsMan, JNI_TRUE);
    env->SetIntField(userObject, mAge, 21);
    user = userObject;
}

JNIEXPORT jobject JNICALL Java_HelloJni_getUser(JNIEnv *env, jobject thiz) {
    return user;
}

要点:FindClass 使用类名 User(无包名时);字段/方法签名需与 Java 类型一致,如 String 为 Ljava/lang/String;,boolean 为 Z。

示例四:互传 ArrayList<User>

Java 将 ArrayList<User> 传入 C++,C++ 遍历打印后再构造新的 ArrayList 返回。

Java:HelloJni.java

import java.util.ArrayList;

public class HelloJni {

    static {
        System.loadLibrary("userbean");
    }

    public native void setUserList(ArrayList<User> userList);
    public native ArrayList<User> getUserList();

    public static void main(String[] args) {
        HelloJni hello = new HelloJni();

        ArrayList<User> userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            userList.add(new User(1000L + i, "LiangYaotian" + i, true, 21));
        }

        hello.setUserList(userList);
        userList = hello.getUserList();

        System.out.println("ArrayList<User> construct from C++, then Java print it.....");
        for (User u : userList) {
            System.out.println("id:" + u.getId() + "; name:" + u.getUserName());
        }
    }
}

C++:setUserList — 遍历 Java 列表

JNIEXPORT void JNICALL Java_HelloJni_setUserList(JNIEnv *env, jobject thiz, jobject userList) {
    jclass cls_arraylist = env->GetObjectClass(userList);
    jmethodID arraylist_get = env->GetMethodID(cls_arraylist, "get", "(I)Ljava/lang/Object;");
    jmethodID arraylist_size = env->GetMethodID(cls_arraylist, "size", "()I");
    jint len = env->CallIntMethod(userList, arraylist_size);

    printf("get java ArrayList<User> object by C++, then print it.....\n");
    for (jint i = 0; i < len; i++) {
        jobject obj_user = env->CallObjectMethod(userList, arraylist_get, i);
        jclass cls_user = env->GetObjectClass(obj_user);

        jmethodID user_getId = env->GetMethodID(cls_user, "getId", "()J");
        jmethodID user_getUserName = env->GetMethodID(cls_user, "getUserName", "()Ljava/lang/String;");
        jmethodID user_isMan = env->GetMethodID(cls_user, "isMan", "()Z");
        jmethodID user_getAge = env->GetMethodID(cls_user, "getAge", "()I");

        jstring name = (jstring) env->CallObjectMethod(obj_user, user_getUserName);
        jboolean isCopy;
        const char *namePtr = env->GetStringUTFChars(name, &isCopy);

        jlong id = env->CallLongMethod(obj_user, user_getId);
        jboolean sex = env->CallBooleanMethod(obj_user, user_isMan);
        jint age = env->CallIntMethod(obj_user, user_getAge);

        printf("Id:%lld; Name:%s; isMan? %d; Age:%d\n", (long long) id, namePtr, sex, age);
        env->ReleaseStringUTFChars(name, namePtr);
    }
}

C++:getUserList — 构造 Java 列表返回

JNIEXPORT jobject JNICALL Java_HelloJni_getUserList(JNIEnv *env, jobject thiz) {
    jclass cls_ArrayList = env->FindClass("java/util/ArrayList");
    jmethodID construct = env->GetMethodID(cls_ArrayList, "<init>", "()V");
    jobject obj_ArrayList = env->NewObject(cls_ArrayList, construct);
    jmethodID arrayList_add = env->GetMethodID(cls_ArrayList, "add", "(Ljava/lang/Object;)Z");

    jclass cls_user = env->FindClass("User");
    jmethodID construct_user = env->GetMethodID(cls_user, "<init>", "()V");

    for (int i = 0; i < 10; i++) {
        jobject obj_user = env->NewObject(cls_user, construct_user);
        jfieldID user_id = env->GetFieldID(cls_user, "id", "J");
        jfieldID user_name = env->GetFieldID(cls_user, "userName", "Ljava/lang/String;");
        jfieldID user_isMan = env->GetFieldID(cls_user, "isMan", "Z");
        jfieldID user_age = env->GetFieldID(cls_user, "age", "I");

        env->SetLongField(obj_user, user_id, i);
        env->SetObjectField(obj_user, user_name, env->NewStringUTF("LiangYaoTian"));
        env->SetBooleanField(obj_user, user_isMan, JNI_TRUE);
        env->SetIntField(obj_user, user_age, 21);
        env->CallBooleanMethod(obj_ArrayList, arrayList_add, obj_user);
    }
    return obj_ArrayList;
}

常见问题

问题 说明
UnsatisfiedLinkError 库名不对、.so 不在 java.library.path / LD_LIBRARY_PATH
NoSuchMethodError / 字段找不到 方法签名或字段类型与 Java 不一致
FindClass 失败 有包名时需写 com/example/User;注意类加载器与线程上下文
全局 jobject 引用 示例三用全局变量保存对象,生产代码应使用 NewGlobalRef / DeleteGlobalRef 管理生命周期

参考

维护记录

时间 修改内容 原因
2026-06-19 重组章节结构;代码块格式化并添加语法高亮;修正 \n、编译命令空格等笔误;更新 categories/tags;补充站内互链与维护记录 原文代码无高亮、排版混乱,不便阅读
2026-06-19 概述中 FFM 链接改为指向 project-panama.md 新增 Panama 专题文档