JNI 調(diào)用構(gòu)造方法和父類實(shí)例方法

在前面我們學(xué)習(xí)到了在 Native 層如何調(diào)用 Java 靜態(tài)方法和實(shí)例方法篙挽,其中調(diào)用實(shí)例方法的示例代碼中也提到了調(diào)用構(gòu)造函數(shù)來實(shí)始化一個對象,但沒有詳細(xì)介紹漂佩,一帶而過了许蓖。還沒有閱讀過的同學(xué)請移步《JNI——C/C++ 訪問 Java 實(shí)例方法和靜態(tài)方法》閱讀。這章詳細(xì)來介紹下初始一個對象的兩種方式憎瘸,以及如何調(diào)用子類對象重寫的父類實(shí)例方法入篮。
構(gòu)造方法和父類實(shí)例方法
我們先回過一下,在 Java 中實(shí)例化一個對象和調(diào)用父類實(shí)例方法的流程幌甘。先看一段代碼:

package com.study.jnilearn;
public class Animal {
    public void run() {
        System.out.println("Animal.run...");
    }
}

package com.study.jnilearn;
public class Cat extends Animal {
    @Override
    public void run() {
        System.out.println(name + " Cat.run...");
    }
}

public static void main(String[] args) {
    Animal cat = new Cat("湯姆");
    cat.run();
}

正如你所看到的那樣潮售,上面這段代碼非常簡單,有兩個類 Animal 和 Cat锅风,Animal 類中定義了 run 和 getName 兩個方法酥诽,Cat 繼承自 Animal,并重寫了父類的 run 方法皱埠。在 main 方法中肮帐,首先定義了一個 Animal 類型的變量 cat,并指向了 Cat 類的實(shí)例對象边器,然后調(diào)用了它的 run 方法训枢。在執(zhí)行 new Cat(“湯姆”)這段代碼時,會先為 Cat 類分配內(nèi)存空間(所分配的內(nèi)存空間大小由 Cat 類的成員變量數(shù)量決定)忘巧,然后調(diào)用 Cat 的帶參構(gòu)造方法初始化對象恒界。 cat 是 Animal 類型,但它指向的是 Cat 實(shí)例對象的引用砚嘴,而且 Cat 重寫了父類的 run 方法十酣,因?yàn)檎{(diào)用 run 方法時有多態(tài)存在涩拙,所以訪問的是 Cat 的 run 而非 Animal 的 run,運(yùn)行后打印的結(jié)果為:湯姆 Cat.run…

如果要調(diào)用父類的 run 方法耸采,只需在 Cat 的 run 方法中調(diào)用 super.run() 即可兴泥,相當(dāng)?shù)暮唵巍?br> 寫過 C 或 C++ 的同學(xué)應(yīng)該都有一個很深刻的內(nèi)存管理概念,椣河睿空間和堆空間搓彻,棧空間的內(nèi)存大小受操作系統(tǒng)限制嘱朽,由操作系統(tǒng)自動來管理好唯,速度較快,所以在函數(shù)中定義的局部變量燥翅、函數(shù)形參變量都存儲在棧空間蜕提。操作系統(tǒng)沒有限制堆空間的內(nèi)存大小森书,只受物理內(nèi)存的限制,內(nèi)存需要程序員自己管理谎势。在 C 語言中用 malloc 關(guān)鍵字動態(tài)分配的內(nèi)存和在 C++ 中用 new 創(chuàng)建的對象所分配內(nèi)存都存儲在堆空間凛膏,內(nèi)存使用完之后分別用free
或delete/delete[]
釋放。這里不過多的討論 C/C++ 內(nèi)存管理方面的知識脏榆,有興趣的同學(xué)請自行百度猖毫。做 Java 的童鞋眾所周知,寫 Java 程序是不需要手動來管理內(nèi)存的须喂,內(nèi)存管理那些煩鎖的事情全都交由一個叫 GC 的線程來管理(當(dāng)一個對象沒有被其它對象所引用時吁断,該對象就會被 GC 釋放)。但我覺得 Java 內(nèi)部的內(nèi)存管理原理和 C/C++ 是非常相似的坞生,上例中仔役,Animal cat = new Cat(“湯姆”);
局部變量 cat 存放在棧空間上是己,new Cat (“湯姆”);創(chuàng)建的實(shí)例對象存放在堆空間又兵,返回一個內(nèi)存地址的引用,存儲在 cat 變量中卒废。這樣就可以通過 cat 變量所指向的引用訪問 Cat 實(shí)例當(dāng)中所有可見的成員了沛厨。
所以創(chuàng)建一個對象分為 2 步:
為對象分配內(nèi)存空間
初始化對象(調(diào)用對象的構(gòu)造方法)

下面通過一個示例來了解在 JNI 中是如何調(diào)用對象構(gòu)造方法和父類實(shí)例方法的。為了讓示例能清晰的體現(xiàn)構(gòu)造方法和父類實(shí)例方法的調(diào)用流程摔认,定義了 Animal 和 Cat 兩個類逆皮,Animal 定義了一個 String 形參的構(gòu)造方法,一個成員變量 name级野、兩個成員函數(shù) run 和 getName页屠,Cat 繼承自 Animal粹胯,并重寫了 run 方法。在 JNI 中實(shí)現(xiàn)創(chuàng)建 Cat 對象的實(shí)例辰企,調(diào)用 Animal 類的 run 和 getName 方法风纠。代碼如下所示。

// Animal.java
package com.study.jnilearn;
public class Animal {

    protected String name;

    public Animal(String name) {
        this.name = name;
        System.out.println("Animal Construct call...");
    }

    public String getName() {
        System.out.println("Animal.getName Call...");
        return this.name;
    }

    public void run() {
        System.out.println("Animal.run...");
    }   
}

// Cat.java
package com.study.jnilearn;
public class Cat extends Animal {

    public Cat(String name) {
        super(name);
        System.out.println("Cat Construct call....");
    }

    @Override
    public String getName() {
        return "My name is " + this.name;
    }

    @Override
    public void run() {
        System.out.println(name + " Cat.run...");
    }
}

// AccessSuperMethod.java
package com.study.jnilearn;
public class AccessSuperMethod {

    public native static void callSuperInstanceMethod(); 

    public static void main(String[] args) {
        callSuperInstanceMethod();
    }

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

AccessSuperMethod 類是程序的入口牢贸,其中定義了一個 native 方法 callSuperInstanceMethod竹观。用 javah 生成的 jni 函數(shù)原型如下。

/* Header for class com_study_jnilearn_AccessSuperMethod */

#ifndef _Included_com_study_jnilearn_AccessSuperMethod
#define _Included_com_study_jnilearn_AccessSuperMethod
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_study_jnilearn_AccessSuperMethod
 * Method:    callSuperInstanceMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

實(shí)現(xiàn) Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod 函數(shù)潜索,如下所示臭增。

/ AccessSuperMethod.c

#include "com_study_jnilearn_AccessSuperMethod.h"

JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod
  (JNIEnv *env, jclass cls)
{
    jclass cls_cat;
    jclass cls_animal;
    jmethodID mid_cat_init;
    jmethodID mid_run;
    jmethodID mid_getName;
    jstring c_str_name;
    jobject obj_cat;
    const char *name = NULL;

    // 1、獲取Cat類的class引用
    cls_cat = (*env)->FindClass(env, "com/study/jnilearn/Cat");
    if (cls_cat == NULL) {
        return;
    }

    // 2竹习、獲取Cat的構(gòu)造方法ID(構(gòu)造方法的名統(tǒng)一為:<init>)
    mid_cat_init = (*env)->GetMethodID(env, cls_cat, "<init>", "(Ljava/lang/String;)V");
    if (mid_cat_init == NULL) {
        return; // 沒有找到只有一個參數(shù)為String的構(gòu)造方法
    }

    // 3誊抛、創(chuàng)建一個String對象,作為構(gòu)造方法的參數(shù)
    c_str_name = (*env)->NewStringUTF(env, "湯姆貓");
    if (c_str_name == NULL) {
        return; // 創(chuàng)建字符串失斦啊(內(nèi)存不夠)
    }

    //  4拗窃、創(chuàng)建Cat對象的實(shí)例(調(diào)用對象的構(gòu)造方法并初始化對象)
    obj_cat = (*env)->NewObject(env,cls_cat, mid_cat_init,c_str_name);
    if (obj_cat == NULL) {
        return;
    }

    //-------------- 5、調(diào)用Cat父類Animal的run和getName方法 --------------
    cls_animal = (*env)->FindClass(env, "com/study/jnilearn/Animal");
    if (cls_animal == NULL) {
        return;
    }

    // 例1: 調(diào)用父類的run方法
    mid_run = (*env)->GetMethodID(env, cls_animal, "run", "()V");    // 獲取父類Animal中run方法的id
    if (mid_run == NULL) {
        return;
    }

    // 注意:obj_cat是Cat的實(shí)例泌辫,cls_animal是Animal的Class引用随夸,mid_run是Animal類中的方法ID
    (*env)->CallNonvirtualVoidMethod(env, obj_cat, cls_animal, mid_run);

    // 例2:調(diào)用父類的getName方法
    // 獲取父類Animal中g(shù)etName方法的id
    mid_getName = (*env)->GetMethodID(env, cls_animal, "getName", "()Ljava/lang/String;");
    if (mid_getName == NULL) {
        return;
    }

    c_str_name = (*env)->CallNonvirtualObjectMethod(env, obj_cat, cls_animal, mid_getName);
    name = (*env)->GetStringUTFChars(env, c_str_name, NULL);
    printf("In C: Animal Name is %s\n", name);

    // 釋放從java層獲取到的字符串所分配的內(nèi)存
    (*env)->ReleaseStringUTFChars(env, c_str_name, name);

quit:
    // 刪除局部引用(jobject或jobject的子類才屬于引用變量),允許VM釋放被局部變量所引用的資源
    (*env)->DeleteLocalRef(env, cls_cat);
    (*env)->DeleteLocalRef(env, cls_animal);
    (*env)->DeleteLocalRef(env, c_str_name);
    (*env)->DeleteLocalRef(env, obj_cat);
}

運(yùn)行結(jié)果



代碼講解 - 調(diào)用構(gòu)造方法
調(diào)用構(gòu)造方法和調(diào)用對象的實(shí)例方法方式是相似的震放,傳入”< init >”作為方法名查找類的構(gòu)造方法ID宾毒,然后調(diào)用JNI函數(shù)NewObject調(diào)用對象的構(gòu)造函數(shù)初始化對象。如下代碼所示殿遂。
obj_cat = (*env)->NewObject(env,cls_cat,mid_cat_init,c_str_name);

上述這段代碼調(diào)用了 JNI 函數(shù) NewObject 創(chuàng)建了 Class 引用的一個實(shí)例對象诈铛。這個函數(shù)做了 2 件事情
創(chuàng)建一個未初始化的對象并分配內(nèi)存空間
調(diào)用對象的構(gòu)造函數(shù)初始化對象。這兩步也可以分開進(jìn)行勉躺,為對象分配內(nèi)存癌瘾,然后再初始化對象,如下代碼所示:

// 1饵溅、創(chuàng)建一個未初始化的對象妨退,并分配內(nèi)存 obj_cat = (env)->AllocObject(env, cls_cat); if (obj_cat) { // 2、調(diào)用對象的構(gòu)造函數(shù)初始化對象 (env)->CallNonvirtualVoidMethod(env,obj_cat, cls_cat, mid_cat_init, c_str_name); if ((*env)->ExceptionCheck(env)) { // 檢查異常 goto quit; } }

AllocObject 函數(shù)創(chuàng)建的是一個未初始化的對象蜕企,后面在用這個對象之前咬荷,必須調(diào)用CallNonvirtualVoidMethod 調(diào)用對象的構(gòu)造函數(shù)初始化該對象。而且在使用時一定要非常小心轻掩,確保在一個對象上面幸乒,構(gòu)造函數(shù)最多被調(diào)用一次。有時唇牧,先創(chuàng)建一個初始化的對象罕扎,然后在合適的時間再調(diào)用構(gòu)造函數(shù)的方式是很有用的聚唐。盡管如此,大部分情況下腔召,應(yīng)該使用 NewObject杆查,盡量避免使用容易出錯的 AllocObject/CallNonvirtualVoidMethod 函數(shù)。
代碼講解 - 調(diào)用父類實(shí)例方法
如果一個方法被定義在父類中臀蛛,在子類中被覆蓋亲桦,也可以調(diào)用父類中的這個實(shí)例方法。JNI 提供了一系列函數(shù)CallNonvirtualXXXMethod 來支持調(diào)用各種返回值類型的實(shí)例方法浊仆。調(diào)用一個定義在父類中的實(shí)例方法客峭,須遵循下面的步驟。
使用 GetMethodID 函數(shù)從一個指向父類的 Class 引用當(dāng)中獲取方法 ID抡柿。

cls_animal = (*env)->FindClass(env, "com/study/jnilearn/Animal");
if (cls_animal == NULL) {
    return;
}

//例1: 調(diào)用父類的run方法
mid_run = (*env)->GetMethodID(env, cls_animal, "run", "()V");    // 獲取父類Animal中run方法的id
if (mid_run == NULL) {
    return;
}

傳入子類對象舔琅、父類 Class 引用、父類方法 ID 和參數(shù)洲劣,并調(diào)用 CallNonvirtualVoidMethod搏明、 CallNonvirtualBooleanMethod、CallNonvirtualIntMethod 等一系列函數(shù)中的一個闪檬。其中CallNonvirtualVoidMethod 也可以被用來調(diào)用父類的構(gòu)造函數(shù)。

// 注意:obj_cat是Cat的實(shí)例购笆,cls_animal是Animal的Class引用粗悯,mid_run是Animal類中的方法ID
(*env)->CallNonvirtualVoidMethod(env, obj_cat, cls_animal, mid_run);

其實(shí)在開發(fā)當(dāng)中,這種調(diào)用父類實(shí)例方法的情況是很少遇到的同欠,通常在 JAVA 中可以很簡單地做到: super.func()
;但有些特殊需求也可能會用到样傍,所以知道有這么回事還是很有必要的。

NdkDemo代碼已上傳至Github

如喜歡此文,歡迎star和關(guān)注铺遂,不正支出,歡迎留言交流衫哥!
我的GitHub
我的CSDN
我的簡書
開發(fā)筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市襟锐,隨后出現(xiàn)的幾起案子撤逢,更是在濱河造成了極大的恐慌,老刑警劉巖粮坞,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚊荣,死亡現(xiàn)場離奇詭異,居然都是意外死亡莫杈,警方通過查閱死者的電腦和手機(jī)互例,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筝闹,“玉大人媳叨,你說我怎么就攤上這事腥光。” “怎么了糊秆?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵武福,是天一觀的道長。 經(jīng)常有香客問我扩然,道長艘儒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任夫偶,我火速辦了婚禮界睁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兵拢。我一直安慰自己翻斟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布说铃。 她就那樣靜靜地躺著访惜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腻扇。 梳的紋絲不亂的頭發(fā)上债热,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音幼苛,去河邊找鬼窒篱。 笑死,一個胖子當(dāng)著我的面吹牛舶沿,可吹牛的內(nèi)容都是我干的墙杯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼括荡,長吁一口氣:“原來是場噩夢啊……” “哼高镐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起畸冲,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嫉髓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邑闲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岩喷,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年监憎,在試婚紗的時候發(fā)現(xiàn)自己被綠了纱意。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡鲸阔,死狀恐怖偷霉,靈堂內(nèi)的尸體忽然破棺而出迄委,到底是詐尸還是另有隱情,我是刑警寧澤类少,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布叙身,位于F島的核電站,受9級特大地震影響硫狞,放射性物質(zhì)發(fā)生泄漏信轿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一残吩、第九天 我趴在偏房一處隱蔽的房頂上張望财忽。 院中可真熱鬧,春花似錦泣侮、人聲如沸即彪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隶校。三九已至,卻和暖如春蛹锰,著一層夾襖步出監(jiān)牢的瞬間深胳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工铜犬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稠屠,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓翎苫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親榨了。 傳聞我的和親對象是個殘疾皇子煎谍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容