native-method.md 10.7 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1
---
沉默王二's avatar
沉默王二 已提交
2 3
title: 手把手教你用 C语言实现 Java native 本地方法
shortTitle: Java native方法
沉默王二's avatar
沉默王二 已提交
4
description: 掌握C语言实现Java native方法的技巧,提升Java程序性能,深入理解Java Native Interface (JNI)。跟随我们的手把手教程,轻松实现跨语言编程。
沉默王二's avatar
沉默王二 已提交
5 6 7 8 9 10 11
category:
  - Java 核心
tag:
  - 面向对象编程
head:
  - - meta
    - name: keywords
沉默王二's avatar
沉默王二 已提交
12
      content: Java, C语言, Native方法, JNI,native,本地方法,java native method
沉默王二's avatar
沉默王二 已提交
13 14
---

沉默王二's avatar
沉默王二 已提交
15
# 5.6 Java native方法
沉默王二's avatar
沉默王二 已提交
16

沉默王二's avatar
沉默王二 已提交
17
“三妹,之前我们学习了 Java 中的[基本方法](https://javabetter.cn/oo/method.html),其实 Java 还有一种方法,本地方法,或者叫 native 方法,它与之前的方法有很大的不同。”我放下手中的手机,扭过脸来对三妹说。
沉默王二's avatar
沉默王二 已提交
18 19 20

“听起来挺有意思的。”三妹很期待。

沉默王二's avatar
沉默王二 已提交
21
“我会教你用 C语言实现一个 native 方法。”我继续说到,“C语言是另外一种编程语言,你可以点这个[链接](https://javabetter.cn/xuexiluxian/c.html)去了解和学习。让我们开始吧”
沉默王二's avatar
沉默王二 已提交
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

类似 Thread 类中的 `private native start0()` 方法;

又或者 Object.class 类中的 getClass() 方法、hashCode()方法、clone() 方法,其中方法签名如下:

```java
public final native Class<?> getClass();
public native int hashCode();
protected native Object clone() throws CloneNotSupportedException;
```

也就是用【native】关键词修饰的方法,多数情况下不需要用 Java 语言实现。

“二哥,为什么要用 native 来修饰方法呢,这样做有什么用?”三妹很乖,但这个问题也问的很掷地有声。

“好的,三妹,我们一步步来扒拉”。

### **1、JNI:Java Native Interface**

在介绍 native 之前,我们先了解什么是 JNI。

一般情况下,我们完全可以使用 Java 语言编写程序,但某些情况下,Java 可能满足不了需求,或者不能更好的满足需求,比如:

- ①、标准的 Java 类库不支持。
- ②、我们已经用另一种语言,比如说 C/C++ 编写了一个类库,如何用 Java 代码调用呢?
- ③、某些运行次数特别多的方法,为了加快性能,需要用更接近硬件的语言(比如汇编)编写。

上面这三种需求,说到底就是如何用 Java 代码调用不同语言编写的代码。那么 JNI 应运而生了。

从 Java 1.1 开始,Java Native Interface (JNI)标准就成为 Java 平台的一部分,它允许 Java 代码和其他语言编写的代码进行交互。

JNI 一开始是为了本地已编译语言,尤其是 C 和 C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。使用 Java 与本地已编译的代码交互,通常会丧失平台可移植性,但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI 标准至少保证本地代码能工作能在任何 Java 虚拟机实现下。

沉默王二's avatar
沉默王二 已提交
55
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/oo/native-method-2a6fd1da-8b64-4fe1-bf3b-fbb117774549.png)
沉默王二's avatar
沉默王二 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

通过 JNI,我们就可以通过 Java 程序(代码)调用到操作系统相关的技术实现的库函数,从而与其他技术和系统交互;同时其他技术和系统也可以通过 JNI 提供的相应原生接口调用 Java 应用系统内部实现的功能。

“二哥,等一下,Java 不是跨平台的吗?如果用 JNI,那么程序不就失去了跨平台的优点?”不得不说,三妹这个问题起到好处。

“确实是这样的。”我掐灭了中指和无名指之间的烟头,继续娓娓道来。

JNI 的缺点:

- ①、程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。
- ②、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就降低了 Java 和 C/C++ 之间的耦合性。

目前来讲使用 JNI 的缺点相对于优点还是可以接受的,可能后面随着 Java 的技术发展,我们不在需要 JNI,但是目前 JDK 还是一直提供了对 JNI 标准的支持。

### **2、用 C 语言编写程序本地方法**

“上面讲解了什么是 JNI,接下来我们来写个例子:如何用 Java 代码调用本地的 C 程序。”我扭头对三妹说,“你注意📢看。”

>官方文档如下:[https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html)

步骤如下:    

①、编写带有 native 方法的 Java 类,生成.java 文件;

②、使用 javac 命令编译所编写的 Java 类,生成.class 文件;

③、使用 javah -jni java 类名 生成扩展名为 h 的头文件,也即生成 .h 文件;

④、使用 C/C++(或者其他编程想语言)实现本地方法,创建 .h 文件的实现,也就是创建 .cpp 文件实现.h 文件中的方法;

⑤、将 C/C++ 编写的文件生成动态连接库,生成 dll 文件;

下面我们通过一个 HelloWorld 程序的调用来完成这几个步骤。

#### 01)编写带有 native 方法的 Java 类 HelloJNI.java

`/Users/itwanger/Documents/Github/javabetter/testjni` 目录下创建 HelloJNI.java 文件,内容如下所示。

```java
public class HelloJNI {
    static {
        System.loadLibrary("hello"); // 加载名为 libhello.dylib 的动态链接库
    }

    // 定义本地方法
    private native void helloJNI();

    public static void main(String[] args) {
        new HelloJNI().helloJNI(); // 调用本地方法
    }
}
```

**PS:后面执行的命令都将在 testjni 的目录下**

解释一下这段代码:

`private native void helloJNI()`:用 native 声明的方法告知 JVM 调用该方法在外部定义,也就是我们会用 C 语言去实现。

`System.loadLibrary("hello")`:加载动态库,参数 hello 是动态库的名字。我们可以这样理解:程序中的方法 helloJNI() 在程序中没有实现,但是我们下面要调用这个方法,怎么办呢?

沉默王二's avatar
沉默王二 已提交
117
我们就需要对这个方法进行初始化,所以用了 [static 代码块进行初始化](https://javabetter.cn/oo/static.html),后面会讲到。
沉默王二's avatar
沉默王二 已提交
118

沉默王二's avatar
沉默王二 已提交
119
#### 02)编译 HelloJNI.java
沉默王二's avatar
沉默王二 已提交
120 121 122

在命令行通过 `javac HelloJNI.java` 来编译源代码。

沉默王二's avatar
沉默王二 已提交
123
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/oo/native-method-41e11e7f-31c1-4611-9b85-63ec211ff31b.png)
沉默王二's avatar
沉默王二 已提交
124 125 126

#### 03)使用 `javah -jni HelloJNI` 生成扩展名为 h 的头文件

沉默王二's avatar
沉默王二 已提交
127
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/oo/native-method-0b08bf51-7bd9-4d06-a0dc-4262c1a71fd5.png)
沉默王二's avatar
沉默王二 已提交
128 129 130 131 132

>PS:Java 9 以后,javah 被弃用,取而代之的是使用 -h 选项来生成头文件,例如 `javac -h . ClassName.java`。

执行完毕后,会在 HelloJNI.java 所在目录下生成一个名为 HelloJNI.h 的头文件。打开 HelloJNI.h 文件,可以看到如下代码。  

沉默王二's avatar
沉默王二 已提交
133
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/oo/native-method-e5c34f63-84df-4a99-96e1-b45ea04929df.png)
沉默王二's avatar
沉默王二 已提交
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

看不懂没关系,无所谓,直到它是自动生成的就好。

#### 04)使用 C 语言实现本地方法 

创建一个 C 文件 HelloJNI.c,实现本地方法 sayHello。

```c
#include <stdio.h>
#include <jni.h>
#include "HelloJNI.h"

JNIEXPORT void JNICALL Java_HelloJNI_helloJNI(JNIEnv *env, jobject obj) {
    printf("Hello, JNI!\n");
    return;
}
```

注意,这里需要引入 JNI 头文件,并且实现的方法名称需要与在 Java 中声明的名称一致(`HelloJNI_helloJNI` HelloJNI 类的 helloJNI 方法)。

#### 05)编写编译脚本 compile.sh

```sh
#!/bin/bash

# 编译 HelloJNI.c 文件
gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -shared -o libhello.dylib HelloJNI.c

# 把生成的 libhello.dylib 文件拷贝到当前目录
cp libhello.dylib .
```

注意事项:

- `$JAVA_HOME` 是 JDK 的安装路径,需要根据实际情况修改。
- 在 macOS 上,动态链接库(hello)的后缀是 .dylib,而不是 Linux 上的 .so。


这里的 -I 选项是为了告诉编译器头文件的位置,`$JAVA_HOME` 是 Java 安装目录的路径。


#### 06)执行编译脚本

```
sh compile.sh
```

执行完毕后,会在当前目录下生成一个名为 libhello.dylib 的动态链接库。

沉默王二's avatar
沉默王二 已提交
183
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/oo/native-method-e93c8fa8-6e33-4374-81da-8bd9360d1bb4.png)
沉默王二's avatar
沉默王二 已提交
184 185 186 187 188

#### 07)运行 HelloJNI

执行`java HelloJNI`命令运行 HelloJNI,如果一切正常,就会在终端上输出 Hello, JNI!。

沉默王二's avatar
沉默王二 已提交
189
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/oo/native-method-34beba0f-8fe8-48d0-aa48-b25c1b504b59.png)
沉默王二's avatar
沉默王二 已提交
190 191 192

### **3、JNI 调用 C 的流程图**

沉默王二's avatar
沉默王二 已提交
193
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/oo/native-method-6673cf73-c4dd-4434-b821-0d705f756a73.png)
沉默王二's avatar
沉默王二 已提交
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216


### **4、native 关键字**

“三妹,现在应该知道什么是 native 了吧?”我问三妹。

“嗯嗯,我来简述一下,二哥你看看我说的是否正确。”

native 用来修饰方法,用 native 声明的方法表示该方法的实现在外部定义,可以用任何语言去实现它,比如说 C/C++。 简单地讲,一个 native Method 就是一个 Java 调用非 Java 代码的接口。

native 语法:

- ①、修饰方法的位置必须在返回类型之前,和其余的方法控制符前后关系不受限制。
- ②、不能用 abstract 修饰,也没有方法体,也没有左右大括号。
- ③、返回值可以是任意类型

“三妹,你学的不错嘛。”我对三妹的学习能力感到非常的欣慰,“**我们在日常编程中看到 native 修饰的方法,只需要知道这个方法的作用是什么,至于别的就不用管了,操作系统会给我们实现,初学的时候也不需要太过深入**。”

>- Windows 下安装 gcc 教程:[http://blog.csdn.net/altland/article/details/63252757](https://blog.csdn.net/altland/article/details/63252757)
>- native 参考链接:[https://www.zhihu.com/question/28001771/answer/2049534464](https://www.zhihu.com/question/28001771/answer/2049534464)

---

沉默王二's avatar
沉默王二 已提交
217
最近整理了一份牛逼的学习资料,包括但不限于 Java 基础部分(JVM、Java 集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类 Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是 2022 年全网最全的学习和找工作的 PDF 资源了](https://javabetter.cn/pdf/programmer-111.html)
沉默王二's avatar
沉默王二 已提交
218

沉默王二's avatar
沉默王二 已提交
219
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
沉默王二's avatar
沉默王二 已提交
220 221

![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)