Accessing Fields in Java

All the native methods you saw so far were static methods with number and string parameters. We’ll now consider native methods that operate on objects. As an exercise, we will reimplement as native a method of the Employee class that was introduced in Volume I, Chapter 4. Again, this is not something you would normally want to do, but it does illustrate how to access fields from a native method when you need to do so.

1. Accessing Instance Fields

To see how to access instance fields from a native method, we will reimplement the raiseSalary method. Here is the code in Java:

public void raiseSalary(double byPercent)

{

salary *= 1 + byPercent / 100;

}

Let us rewrite this as a native method. Unlike the previous examples of native methods, this is not a static method. Running javac -h gives the following prototype:

JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv *, jobject, jdoubte);

Note the second argument. It is no longer of type jctass but of type jobject. In fact, it is an equivalent of the this reference. Static methods obtain a reference to the class, whereas nonstatic methods obtain a reference to the implicit this argument object.

Now we access the salary field of the implicit argument. In the “raw” Java-to-C binding of Java 1.0, this was easy—a programmer could directly access object data fields. However, direct access requires all virtual machines to expose their internal data layout. For that reason, the JNI requires programmers to get and set the values of data fields by calling special JNI functions.

In our case, we need to use the GetDoubleField and SetDoubleField functions because the type of salary is double. There are other functions—GetIntField/SetIntField, GetObjectField/SetObjectField, and so on for other field types. The general syntax is:

x = (*env)->GetXxxField(env, this_obj, fieldID);

(*env)->SetXxxField(env, this_obj, fieldID, x);

Here, fieldID is a value of a special type, jfieldID, that identifies a field in a structure, and Xxx represents a Java data type (Object, Boolean, Byte, and so on). To obtain the fieldID, you must first get a value representing the class, which you can do in one of two ways. The GetObjectClass function returns the class of any object. For example:

jclass class_Employee = (*env)->GetObjectClass(env, this_obj);

The FindClass function lets you specify the class name as a string (curiously, with / characters instead of periods as package name separators).

jclass class_String = (*env)->FindClass(env, “java/lang/String”);

Use the GetFieldID function to obtain the fieldID. You must supply the name of the field and its signature, an encoding of its type. For example, here is the code to obtain the field ID of the salary field:

jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, “salary”, “D”);

The string “D” denotes the type double. You’ll learn the complete rules for encoding signatures in the next section.

You might be thinking that accessing a data field is quite convoluted. The designers of the JNI did not want to expose the data fields directly, so they had to supply functions for getting and setting field values. To minimize the cost of these functions, computing the field ID from the field name—which is the most expensive step—is factored out into a separate step. That is, if you repeatedly get and set the value of a particular field, you can incur the cost of computing the field identifier only once.

Let us put all the pieces together. The following code reimplements the raiseSatary method as a native method:

JNIEXPORT void JNICALL Java_Emptoyee_raiseSatary(JNIEnv* env, jobject this_obj, jdoubte byPercent)

{

/* get the class */

jclass class_Employee = (*env)->GetObjectCtass(env, this_obj);

/* get the field ID */

jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, “salary”, “D”);

/* get the field value */

jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary); salary *= 1 + byPercent / 100;

/* set the field value */

(*env)->SetDoubleField(env, this_obj, id_salary, salary);

}

static jclass dass_X = 0; static jfieldID id_a;

if (class_X == 0)

{

jclass cx = (*env)->GetObjectCtass(env, obj);

class_X = (*env)->NewGtobatRef(env, cx);

id_a = (♦envl^GetFieldIDfenv, cls, “a”, “. . .”);

}

Listings 12.11 and 12.12 show the Java code for a test program and the Employee class. Listing 12.13 contains the C code for the native raiseSalary method.

2. Accessing Static Fields

Accessing static fields is similar to accessing nonstatic fields. Use the GetStaticFieldID and GetStaticXrxField/SetStaticXrxField functions that work almost identically to their nonstatic counterparts, with two differences:

  • Since you have no object, you must use FindClass instead of GetObjectCtass to obtain the class reference.
  • You have to supply the class, not the instance object, when accessing the field.

For example, here is how you can get a reference to System.out:

/* get the class */

jclass class_System = (*env)->FindClass(env, “java/lang/System”);

/* get the field ID */

jfieldID id_out = (*env)->GetStaticFieldID(env, class_System, “out”,

“Ljava/io/PrintStream;”);

/* get the field value */

jobject obj_out = (*env)->GetStaticObjectField(env, class_System, id_out);

Source: Horstmann Cay S. (2019), Core Java. Volume II – Advanced Features, Pearson; 11th edition.

Leave a Reply

Your email address will not be published. Required fields are marked *