native 是 Java 语言中的一个修饰符,它用于声明一个方法是“原生的”或“本地的”,它告诉 JVM(Java 虚拟机)这个方法的实现不是用 Java 代码编写的,而是用其他编程语言(如 C、C++ 或汇编)编写的,并且这些代码位于本地机器的动态链接库(如 Windows 的 .dll 或 Linux/Unix 的 .so 文件)中。

native 方法的基本语法
native 关键字只能用于修饰方法,不能用于修饰类或变量。
public class NativeExample {
// 这是一个 native 方法的声明
public native void sayHello();
public static void main(String[] args) {
NativeExample example = new NativeExample();
example.sayHello(); // 调用 native 方法
}
}
关键点:
- 只有声明,没有实现:
sayHello()方法在NativeExample.java文件中只有方法签名,没有方法体(即没有 代码块)。 - 实现由其他语言完成:
sayHello()的具体实现在一个外部的 C/C++ 文件中。
为什么需要 native 方法?(使用场景)
使用 native 方法通常是为了解决 Java 自身无法或难以完成的任务,主要有以下几个原因:
-
访问系统级功能:
(图片来源网络,侵删)- Java 的设计初衷是“一次编写,到处运行”,这使得它无法直接访问特定操作系统的底层功能,如硬件端口、内存地址等。
- 示例:在 Java 中直接读取 CPU 温度、操作硬件设备等,就需要通过
native方法调用 C/C++ 代码来完成。
-
性能优化:
- 对于计算密集型任务,C/C++ 等语言通常比 Java 执行得更快。
- 示例:在游戏引擎、科学计算、图像处理等领域,可以将核心算法用 C/C++ 实现,然后通过
native方法供 Java 调用,以获得更高的性能。
-
复用现有库:
- 很多成熟的、高性能的库都是用 C/C++ 编写的(OpenCV、部分加密库、数据库驱动等)。
- 示例:如果你想在一个 Java 项目中使用 OpenCV 进行图像处理,可以通过
native方法封装 OpenCV 的 C++ API,让 Java 代码可以调用它。
-
与 JVM 交互:
- 一些需要直接与 JVM 内部数据结构(如内存管理、线程管理)交互的功能,需要通过
native方法实现。
- 一些需要直接与 JVM 内部数据结构(如内存管理、线程管理)交互的功能,需要通过
如何实现一个 native 方法?(完整流程)
创建一个可用的 native 方法需要几个步骤,这个过程比较繁琐,但理解它有助于深入理解 native 的工作原理。
我们以一个经典的 "Hello, World!" 示例来演示整个过程。
步骤 1:声明 Java native 方法
在 Java 类中声明你的 native 方法。
// NativeMethodDemo.java
public class NativeMethodDemo {
// 声明 native 方法
public native void displayMessage();
public static void main(String[] args) {
// 加载包含 native 方法实现的库
System.loadLibrary("NativeMethodDemo");
NativeMethodDemo demo = new NativeMethodDemo();
demo.displayMessage(); // 调用 native 方法
}
}
注意:
System.loadLibrary("NativeMethodDemo");这行代码非常重要,它会告诉 JVM 去加载一个名为NativeMethodDemo.dll(Windows) 或libNativeMethodDemo.so(Linux) 的动态链接库。- 库的名称通常与你的 Java 类名相关,但不完全相同,具体取决于编译步骤。
步骤 2:生成 C/C++ 头文件(.h 文件)
JVM 提供了一个工具 javah(在较新版本的 JDK 中,它被集成到了 javac 中,或者可以使用 javac -h 选项),它可以根据 Java 类的 .class 文件生成一个 C/C++ 头文件,这个头文件包含了 C/C++ 函数必须遵循的签名。
前提:你需要先编译 Java 代码。
javac NativeMethodDemo.java
生成头文件: 在较新的 JDK (9+) 中,使用以下命令:
javac -h . NativeMethodDemo.java
这会在当前目录下生成一个 NativeMethodDemo.h 文件。
生成的 NativeMethodDemo.h 文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeMethodDemo */
#ifndef _Included_NativeMethodDemo
#define _Included_NativeMethodDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: NativeMethodDemo
* Method: displayMessage
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_NativeMethodDemo_displayMessage
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
解释关键部分:
#include <jni.h>:包含了 JNI (Java Native Interface) 的所有定义和数据结构。JNIEXPORT和JNICALL:是 JNI 定义的宏,用于指定函数的调用约定,确保 JVM 能正确调用。Java_NativeMethodDemo_displayMessage:这是 C 函数的命名规则,由Java_+ 包名(如果有)+ 类名 + 方法名 组成。(JNIEnv *, jobject):这是函数的参数。JNIEnv *:这是一个指向 JNI 环境的指针,是 JNI 函数的“入口点”,通过它可以在 C/C++ 代码中调用其他 JNI 函数(创建 Java 对象、调用 Java 方法等)。jobject:这是一个指向Java对象本身的指针。native方法是static的,这个参数的类型会是jclass,指向Class对象。
步骤 3:编写 C/C++ 实现文件(.c 或 .cpp 文件)
我们根据生成的头文件,编写 C 语言的实现。
// NativeMethodDemo.c
#include <stdio.h>
#include "NativeMethodDemo.h" // 包含我们刚刚生成的头文件
// 实现 Java_NativeMethodDemo_displayMessage 函数
JNIEXPORT void JNICALL Java_NativeMethodDemo_displayMessage(JNIEnv *env, jobject obj) {
printf("Hello from C code!\n");
}
这里我们只是简单地调用 C 的 printf 函数来打印一条消息。
步骤 4:编译 C/C++ 代码为动态链接库
这一步依赖于你的操作系统。
在 Windows 上(使用 MinGW 的 gcc):
gcc -IC:\path\to\jdk\include -IC:\path\to\jdk\include\win32 -shared -o NativeMethodDemo.dll NativeMethodDemo.c
-I:指定 JNI 头文件的位置。-shared:生成动态链接库(.dll)。-o:指定输出的文件名。
在 Linux 上(使用 gcc):
gcc -I/usr/lib/jvm/java-11-openjdk-amd64/include -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux -shared -o libNativeMethodDemo.so NativeMethodDemo.c
-I:指定 JNI 头文件的位置。-shared:生成共享对象(.so)。-o:指定输出的文件名。
步骤 5:运行 Java 程序
确保生成的动态链接库(.dll 或 .so)位于 Java 程序可以找到它的地方(java 命令执行的目录,或者系统 PATH 环境变量中的目录)。
java NativeMethodDemo
预期输出:
Hello from C code!
native 方法的优缺点
优点:
- 功能强大:可以访问操作系统底层和硬件,实现 Java 无法完成的功能。
- 性能提升:可以利用 C/C++ 的高性能来优化计算密集型任务。
- 代码复用:可以方便地集成现有的 C/C++ 库。
缺点:
- 丧失跨平台性:
native方法引入了平台依赖性,为 Windows 编译的.dll文件无法在 Linux 上使用,这违背了 Java “一次编写,到处运行”的核心原则。 - 性能开销:Java 和 C/C++ 代码之间的调用(称为“JNI 调用”)是有性能开销的,频繁的 JNI 调用可能会比纯 Java 代码更慢。
- 开发复杂:整个流程(生成头文件、编写 C/C++ 代码、编译、调试)比纯 Java 开发要复杂得多。
- 不安全:
native代码绕过了 Java 的安全管理器,可以执行任意操作,包括直接访问内存,可能导致程序崩溃和安全漏洞。 - 破坏封装性:它将 Java 代码与非 Java 代码紧密耦合,增加了项目的维护难度。
| 特性 | 描述 |
|---|---|
| 关键字 | native |
| 作用 | 修饰方法,表明其实现由非 Java 语言(如 C/C++)提供。 |
| 实现位置 | 外部的动态链接库(.dll, .so)。 |
| JNI | Java Native Interface,是连接 Java 代码和 native 代码的桥梁。 |
| 优点 | 访问底层、性能优化、复用现有库。 |
| 缺点 | 失去跨平台性、有调用开销、开发复杂、不安全。 |
最佳实践:应尽量避免使用 native 方法,只有在确实需要访问底层系统、进行极致性能优化或集成特定 C/C++ 库时才考虑使用,并且要谨慎处理,确保其安全性和稳定性,对于大多数应用场景,纯 Java 实现是更简单、更安全、更具可移植性的选择。
