在前面我們學(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ā)筆記