杰瑞科技汇

java native 关键字

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

java native 关键字-图1
(图片来源网络,侵删)

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 自身无法或难以完成的任务,主要有以下几个原因:

  1. 访问系统级功能

    java native 关键字-图2
    (图片来源网络,侵删)
    • Java 的设计初衷是“一次编写,到处运行”,这使得它无法直接访问特定操作系统的底层功能,如硬件端口、内存地址等。
    • 示例:在 Java 中直接读取 CPU 温度、操作硬件设备等,就需要通过 native 方法调用 C/C++ 代码来完成。
  2. 性能优化

    • 对于计算密集型任务,C/C++ 等语言通常比 Java 执行得更快。
    • 示例:在游戏引擎、科学计算、图像处理等领域,可以将核心算法用 C/C++ 实现,然后通过 native 方法供 Java 调用,以获得更高的性能。
  3. 复用现有库

    • 很多成熟的、高性能的库都是用 C/C++ 编写的(OpenCV、部分加密库、数据库驱动等)。
    • 示例:如果你想在一个 Java 项目中使用 OpenCV 进行图像处理,可以通过 native 方法封装 OpenCV 的 C++ API,让 Java 代码可以调用它。
  4. 与 JVM 交互

    • 一些需要直接与 JVM 内部数据结构(如内存管理、线程管理)交互的功能,需要通过 native 方法实现。

如何实现一个 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) 的所有定义和数据结构。
  • JNIEXPORTJNICALL:是 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 方法的优缺点

优点:

  1. 功能强大:可以访问操作系统底层和硬件,实现 Java 无法完成的功能。
  2. 性能提升:可以利用 C/C++ 的高性能来优化计算密集型任务。
  3. 代码复用:可以方便地集成现有的 C/C++ 库。

缺点:

  1. 丧失跨平台性native 方法引入了平台依赖性,为 Windows 编译的 .dll 文件无法在 Linux 上使用,这违背了 Java “一次编写,到处运行”的核心原则。
  2. 性能开销:Java 和 C/C++ 代码之间的调用(称为“JNI 调用”)是有性能开销的,频繁的 JNI 调用可能会比纯 Java 代码更慢。
  3. 开发复杂:整个流程(生成头文件、编写 C/C++ 代码、编译、调试)比纯 Java 开发要复杂得多。
  4. 不安全native 代码绕过了 Java 的安全管理器,可以执行任意操作,包括直接访问内存,可能导致程序崩溃和安全漏洞。
  5. 破坏封装性:它将 Java 代码与非 Java 代码紧密耦合,增加了项目的维护难度。

特性 描述
关键字 native
作用 修饰方法,表明其实现由非 Java 语言(如 C/C++)提供。
实现位置 外部的动态链接库(.dll, .so)。
JNI Java Native Interface,是连接 Java 代码和 native 代码的桥梁。
优点 访问底层、性能优化、复用现有库。
缺点 失去跨平台性、有调用开销、开发复杂、不安全。

最佳实践:应尽量避免使用 native 方法,只有在确实需要访问底层系统、进行极致性能优化或集成特定 C/C++ 库时才考虑使用,并且要谨慎处理,确保其安全性和稳定性,对于大多数应用场景,纯 Java 实现是更简单、更安全、更具可移植性的选择。

分享:
扫描分享到社交APP
上一篇
下一篇