如何通过JNI在C++中遍历Java返回的Map<String, List<ErrorLogger.ErrorObject>>
嘿,我刚好处理过类似的JNI遍历Java集合的场景,咱们一步步来拆解这个Map<String, List<ErrorLogger.ErrorObject>>的遍历问题,其实核心就是通过JNI调用Java集合类的标准API来逐层获取数据:
第一步:先拿到Map的所有键值对集合(EntrySet)
Java里的Map要遍历键值对,通常先调用entrySet()拿到Set集合,JNI里也一样:
// 获取Java Map类的引用 jclass mapClass = env->GetObjectClass(mapObj); // 拿到entrySet()方法的ID,方法签名是"()Ljava/util/Set;" jmethodID entrySetMethod = env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;"); // 调用方法得到EntrySet对象 jobject entrySetObj = env->CallObjectMethod(mapObj, entrySetMethod); // 用完Map类引用可以先释放(如果是局部引用的话) env->DeleteLocalRef(mapClass);
第二步:用迭代器遍历EntrySet
接下来要通过迭代器逐个取出Map.Entry对象:
// 获取Set类的引用 jclass setClass = env->GetObjectClass(entrySetObj); // 拿到iterator()方法ID jmethodID iteratorMethod = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;"); // 获取迭代器对象 jobject iteratorObj = env->CallObjectMethod(entrySetObj, iteratorMethod); // 获取Iterator类的引用,以及hasNext()、next()方法ID jclass iteratorClass = env->GetObjectClass(iteratorObj); jmethodID hasNextMethod = env->GetMethodID(iteratorClass, "hasNext", "()Z"); jmethodID nextMethod = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;"); // 释放暂时不用的局部引用 env->DeleteLocalRef(setClass); env->DeleteLocalRef(iteratorClass);
然后就可以用循环遍历每个Entry了:
while (env->CallBooleanMethod(iteratorObj, hasNextMethod)) { // 取出一个Map.Entry对象 jobject entryObj = env->CallObjectMethod(iteratorObj, nextMethod); // 接下来处理这个Entry的key和value... }
第三步:获取Entry的Key(String类型)
对于每个Entry,调用getKey()拿到字符串键,再转成C++可用的字符串:
// 获取Map.Entry类的引用 jclass entryClass = env->GetObjectClass(entryObj); // 拿到getKey()方法ID jmethodID getKeyMethod = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;"); // 调用方法得到jstring类型的key jstring keyStr = (jstring)env->CallObjectMethod(entryObj, getKeyMethod); // 转成C++的const char* const char* keyCStr = env->GetStringUTFChars(keyStr, nullptr); // 这里就可以用keyCStr做你需要的操作了,比如打印、存储 printf("Key: %s\n", keyCStr); // 记得释放资源!非常重要 env->ReleaseStringUTFChars(keyStr, keyCStr); env->DeleteLocalRef(keyStr);
第四步:处理Entry的Value(List<ErrorLogger.ErrorObject>)
接下来是处理List类型的值,同样要调用Java List的API来遍历:
// 拿到getValue()方法ID jmethodID getValueMethod = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;"); // 调用方法得到List对象 jobject listObj = env->CallObjectMethod(entryObj, getValueMethod); // 获取List类的引用,以及size()、get(int)方法ID jclass listClass = env->GetObjectClass(listObj); jmethodID listSizeMethod = env->GetMethodID(listClass, "size", "()I"); jmethodID listGetMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); // 获取List的大小 int listSize = env->CallIntMethod(listObj, listSizeMethod); // 遍历List里的每个ErrorObject for (int i = 0; i < listSize; ++i) { jobject errorObj = env->CallObjectMethod(listObj, listGetMethod, i); // 接下来处理ErrorObject的内容... } // 释放List相关的局部引用 env->DeleteLocalRef(listClass); env->DeleteLocalRef(listObj); env->DeleteLocalRef(entryClass); env->DeleteLocalRef(entryObj);
第五步:访问ErrorLogger.ErrorObject的具体内容
假设你的ErrorLogger.ErrorObject有一些公开方法,比如getErrorCode()返回int,getMessage()返回String,那你需要获取这些方法的ID来调用:
// 注意:内部类的全限定名要用$分隔,比如你的包名是com.example,那就是"com/example/ErrorLogger$ErrorObject" jclass errorObjClass = env->FindClass("com/example/ErrorLogger$ErrorObject"); // 拿到getErrorCode()方法ID,方法签名是"()I" jmethodID getErrorCodeMethod = env->GetMethodID(errorObjClass, "getErrorCode", "()I"); // 拿到getMessage()方法ID,方法签名是"()Ljava/lang/String;" jmethodID getMessageMethod = env->GetMethodID(errorObjClass, "getMessage", "()Ljava/lang/String;"); // 调用方法获取值 int errorCode = env->CallIntMethod(errorObj, getErrorCodeMethod); jstring errorMsgStr = (jstring)env->CallObjectMethod(errorObj, getMessageMethod); const char* errorMsgCStr = env->GetStringUTFChars(errorMsgStr, nullptr); // 打印或者使用这些值 printf(" Error Code: %d, Message: %s\n", errorCode, errorMsgCStr); // 释放资源 env->ReleaseStringUTFChars(errorMsgStr, errorMsgCStr); env->DeleteLocalRef(errorMsgStr); env->DeleteLocalRef(errorObj); env->DeleteLocalRef(errorObjClass);
几个必须注意的坑
- 局部引用管理:JNI的局部引用数量有限,循环里创建的每个对象(比如entryObj、errorObj)一定要用
DeleteLocalRef释放,不然会导致局部引用溢出崩溃。 - 方法签名要准确:JNI对方法签名的要求非常严格,写错一个字符都会找不到方法。你可以用
javap -s命令生成类的方法签名,比如在命令行执行javap -s com.example.ErrorLogger$ErrorObject就能看到所有方法的签名。 - 异常检查:每次调用JNI方法后,最好检查是否有Java异常抛出,比如:
if (env->ExceptionCheck()) { env->ExceptionClear(); // 这里可以加错误处理逻辑,比如跳出循环 break; }
避免异常导致后续代码执行出错。
内容的提问来源于stack exchange,提问作者Abhi9
相关产品推荐
相关产品推荐

