Even though Java is catching on pretty rapidly as a powerful new language with a lot of potential, there is a significant problem in dealing with the large existing C and C++ code base. As a Java programmer, you have two solutions to this problem: convert your existing C/C++ code to Java or interface your Java code to the C/C++ code. Although the latter solution is certainly a viable option, especially in terms of development resources, this chapter focuses on the former. If you're really bent on keeping your C/C++ code as it is, check out Chapter 32, "Interfacing to Existing C and C++ Libraries."
The prospect of moving C/C++ code to Java might seem somewhat daunting at first. However, the similarities in syntax between the languages alone makes matters a little easier to handle. In reality, you can isolate the differences between the languages and form a strategy aimed at fixing trouble spots in the code.
This chapter takes on the challenge of converting C/C++ code to Java code an issue at a time. You learn not only how to convert C/C++ code to Java, but also why Java is different and requires the changes in the first place. If you are following along attempting to port your own code, try to make incremental changes as you cover each section. You'll probably find that the task isn't as bad as you originally thought. With that in mind, let's get started!
Before you even begin changing any source code, it's important to understand a fundamental difference between source files in Java and C/C++. In C and C++, most source files come in pairs consisting of a header file (.h) and an implementation file (.c or .cpp). The purpose of this file structure is to separate the declarations of the functions and classes from the definitions. This enables other programmers to understand code by viewing header files, while keeping them out of the specific implementation details found in the implementation files.
In Java, there is only one source file (.java) per logical code structure. Java classes contain class declaration information that can be easily extracted using the javap class file disassembler tool that comes with the JDK. Because of this, there is no need to maintain a header file with class declaration information. All the code for a Java class goes in the .java source file.
What does this mean to you? Well, it means you should get ready to merge all your header and implementation files into .java files. Once you've done that, you can move into modifying the code itself.
All C/C++ compilers implement a stage of compilation known as the preprocessor. The C++ preprocessor basically performs an intelligent search and replace on identifiers that have been declared using the #define directive or typedef. Although most advocators of C++ discourage the use of the preprocessor, which was inherited from C, it is still widely used by most C++ programmers. Most of the processor definitions in C++ are stored in header files, which complement the actual source code (implementation) files.
The problem with the preprocessor approach is that it provides an easy way for programmers inadvertently to add unnecessary complexity to a program. Many programmers using #define and typedef end up inventing their own sublanguage within the confines of a particular project. This results in other programmers having to go through the header files and sort out all the #define and typedef information to understand a program, which makes code maintenance and reuse almost impossible. An additional problem with the preprocessor approach is that it is very weak when it comes to type checking and validation.
Java does not have a preprocessor. It provides similar functionality (#define, typedef, and so forth) to that provided by the C++ preprocessor, but with far more control. Constant data members are used in place of the #define directive, and class definitions are used in lieu of typedef. The end result is that Java source code is much more consistent and easier to read than C++ source code. Additionally, as you learned earlier, Java programs don't use header files; the Java compiler builds class declarations directly from the source code files, which contain both class declarations and method implementations.
Let's look at an example; Listing 28.1 shows a C++ header file for a ball class.
Listing 28.1. The C++ ball class.
#define COLOR_RED 1
#define COLOR_YELLOW 2
#define COLOR_BLUE 3
#define MATERIAL_RUBBER 1
#define MATERIAL_LEATHER 2
class ball {
float diameter;
int color;
int material;
};
To move this code to Java, the only change is to get rid of the preprocessor #define directives and the semicolon at the end of the class declaration. You get rid of the #define directives by declaring Java class members that are static and final. For data members in Java, the static keyword means there is only one copy for the entire class, and the final keyword means that they are constant, which is usually the motive for using #define in C/C++ code. Listing 28.2 shows the resulting Java version of this class. Keep in mind that the Java version is not stored in a header file, because Java doesn't support header files; in Java, definitions and declarations are combined in one place, the .java source file.
Listing 28.2. The Java ball class.
class ball {
// Constants
static final int COLOR_RED = 1;
static final int COLOR_YELLOW = 2;
static final int COLOR_BLUE = 3;
static final int MATERIAL_RUBBER = 1;
static final int MATERIAL_LEATHER = 2;
// Variables
float diameter;
int color;
int material;
}
The Java version of ball pulls all the constants inside the class definition as static final integers. Within this class, you would then refer to them just as you would the previous C++ versions. However, outside this class they are inaccessible, because they have been left at their default access type. To make them visible by other classes, you simply declare their access type as public, as shown in Listing 28.3. The statement about default access isn't entirely true; you learn the whole scoop about access types later in this chapter.
Listing 28.3. The Java ball class with public constants.
class ball {
// Constants
public static final int COLOR_RED = 1;
public static final int COLOR_YELLOW = 2;
public static final int COLOR_BLUE = 3;
public static final int MATERIAL_RUBBER = 1;
public static final int MATERIAL_LEATHER = 2;
// Variables
float diameter;
int color;
int material;
}
In this version of ball, the constants are readily available for other classes to use. However, those classes must explicitly refer to the constants using the ball class name:
int color = ball.COLOR_YELLOW;
There are three types of complex data types in C/C++: classes, structures (structs), and unions. Java supports only one of these data types, classes. Java forces programmers to use classes when the functionality of structures and unions is desired. Although this sounds like more work for the programmer, it actually ends up being more consistent, because classes can imitate structures and unions with ease. Furthermore, supporting structs and unions would have put a major hole in the whole concept of the Java language being object-oriented. The Java designers really wanted to keep the language simple, so it only made sense to eliminate aspects of the language that overlapped.
Converting structs and unions to Java classes is pretty easy. Take a look at Listing 28.4, which contains a polar coordinate C struct.
Listing 28.4. The C polar struct.
typedef struct polar {
float angle;
float magnitude
} POLAR;
Notice that this struct uses a typedef to establish the polar type. As you learned earlier, typedefs aren't necessary in Java, because everything is an object with a unique type. Java doesn't support the concept of a struct either. Listing 28.5 contains the Java version of the polar class.
Listing 28.5. The Java polar class.
class polar {
float angle;
float magnitude
}
In addition to changing the typedef struct declaration to class, notice that the Java polar class isn't followed by a semicolon. This is a small, but often overlooked, difference between Java and C++; semicolons aren't necessary in Java class definitions.
In C, code is organized into functions, which are global subroutines accessible to a program. C++ added classes and in doing so provided class methods, which are functions that are connected to classes. C++ class methods are very similar to Java class methods. However, because C++ still supports C, there is nothing keeping C++ programmers from using functions. This results in a mixture of function and method use that makes for confusing programs.
Java has no functions. Being a purer object-oriented language than C++, Java forces programmers to bundle all subroutines into class methods. There is no limitation imposed by forcing programmers to use methods instead of functions. As a matter of fact, implementing subroutines as methods encourages programmers to organize code better. Keep in mind that, strictly speaking, there is nothing wrong with the procedural approach of using functions; it just doesn't mix well with the object-oriented paradigm that defines the core of Java.
Because almost all C/C++ code contains some degree of function use, this is a particularly important issue when porting C/C++ code to Java. Fortunately, it's mostly an organizational change. The whole point of functions is to move code into a logically separate procedure that can be called from the main program or other functions. You can easily recreate this scenario in Java without having to "objectify" the code completely. The solution is to move global C/C++ functions into method-only organizational Java classes. Check out Listing 28.6, which contains a series of string encryption/decryption function prototypes.
Listing 28.6. The C string encryption/decryption function prototypes.
char EncryptChar(char c, int key);
char DecryptChar(char c, int key);
char* EncryptString(const char* s, int key);
char* DecryptString(const char* s, int key);
These functions are global C functions that encrypt and decrypt characters and strings. Of course, in C/C++ there is no pure concept of a string; an array of characters is the best you get (more on that later in this chapter). A straight function-to-method port of these functions in Java is shown in Listing 28.7.
Listing 28.7. The Java string encryption/decryption methods encapsulated within the crypt class.
class crypt {
public static char encryptChar(char c, int key) {
// character encryption code
}
public static char decryptChar(char c, int key) {
// character decryption code
}
public static String encryptString(String s, int key) {
// string encryption code
}
public static String decryptString(String s, int key) {
// string decryption code
}
}
In Java, you have to package the functions as methods in a class, crypt. By declaring them as public static, you make them readily available to the entire Java system. The key aspect of the Java version of the functions is that their implementations are defined in a Java class because Java doesn't support the header/source file organization. All class information goes directly into the class definition. Notice that the standard naming convention for Java methods is to begin each method name with a lowercase character. To use the Java methods, you have to reference them with the crypt class name:
char c = crypt.encryptChar('a', 7);
The only other change to the C functions is the usage of String objects rather than char pointers because Java doesn't support pointers. You get into more details surrounding strings and pointers a little later in this chapter.
Although the Java crypt class provides working Java versions of the procedural C functions, performing this type of conversion isn't always enough. The crypt class provides a good example of how you can maintain a procedural feel within a Java class. Java is an object-oriented language, however, and you should design your code to fit into the object-oriented paradigm whenever possible. The crypt class is no exception to this rule.
Examining the crypt class methods, it is apparent that some things could be modified to make the class fit into a more object-oriented design. Listing 28.8 contains the source code for the revised, objectified crypt class.
Listing 28.8. The revised Java >crypt class.
class crypt {
int key;
crypt(int k) {
key = k;
}
void setKey(int k) {
key = k;
}
int getKey() {
return key;
}
char encryptChar(char c) {
// character encryption code
}
char decryptChar(char c) {
// character decryption code
}
String encryptString(String s) {
// string encryption code
}
String decryptString(String s) {
// string decryption code
}
}
In this version of crypt, the encryption key has been moved from a method parameter to a data member of the class, key. A constructor was added that accepts an encryption key when the object is created, along with access methods for getting and setting the key. The public static declarations for the encrypt/decrypt methods have also been removed, which requires you to have an instance of the crypt class to use the methods. This makes sense, because the class now has a data member (key) that affects the methods.
This revised design of the crypt class makes much more sense from a Java programming perspective. Of course, it won't run any faster or perform better encryption, but that's not the point. The point is that object-oriented design practices are a fundamental part of the Java language and should be followed whenever possible.
Operator overloading, which is considered a prominent feature in C++, is not supported in Java. Although roughly the same functionality can be implemented as methods in Java classes, the syntactic convenience of operator overloading is still missing. However, in defense of Java, operator overloading can sometimes get very tricky. The Java developers decided not to support operator overloading to keep the Java language as simple as possible.
Although operator overloading is a pretty useful feature in C++, its usage is highly dependent on the types of C++ classes with which you're dealing. For example, more fundamental C++ data structure classes, such as string classes, make heavy use of operator overloading, whereas others may not use it all. The amount of porting work you have in front of you depends on how much your C++ code relies on operator overloading.
The only way to convert overloaded C++ operators to Java is to create equivalent Java methods with the same functionality. Keep in mind that the Java methods will be called differently than the C++ overloaded operators. This means you have to dissect your code carefully to find out where each operator is used and then convert the code to a method call.
Let's look at an example; Listing 28.9 contains a complex number class with overloaded operators.
Listing 28.9. The C++ Complex class with overloaded operators.
class Complex {
float real;
float imaginary;
Complex(float r, float i);
Complex operator+(const Complex& c) const {
return Complex(real + c.real, imaginary + c.imaginary);
}
Complex operator-(const Complex& c) const {
return Complex(real - c.real, imaginary - c.imaginary);
}
};
The C++ Complex number class supports overloaded operators for addition and subtraction. The following is an example of how this class is used:
Complex c1(3.0, 4.0);
Complex c2(5.0, 2.5);
Complex c3 = c2 - c1;
The subtraction of the Complex objects is syntactically the same as subtracting two fundamental data types. However, this capability adds a significant amount of complexity to C++ that the Java architects wanted to avoid. So, although you can't provide the same syntactic approach in Java, you can provide methods with similar functionality. Listing 28.10 contains the Java version of the Complex class, complete with method versions of the overloaded operators.
Listing 28.10. The Java Complex class.
class Complex {
float real;
float imaginary;
Complex(float r, float i) {
real = r;
imaginary = i;
}
Complex add(Complex c) {
return (new Complex(real + c.real, imaginary + c.imaginary));
}
Complex subtract(Complex c) {
return (new Complex(real - c.real, imaginary - c.imaginary));
}
}
The most obvious change in the Java version of Complex is the renaming of the operator overloaded methods to add and subtract. The Java Complex class is used like this:
Complex c1 = new Complex(3.0, 4.0);
Complex c2 = new Complex(5.0, 2.5);
Complex c3 = c2.subtract(c1);
You can see how the subtraction operation isn't quite as intuitive using the Java approach. Nevertheless, it does work. Notice also that the Complex objects are created using the new operator. This is a result of the differences between memory management in Java and C++, which you learn about when you get into pointers a little later in this chapter.
Automatic coercion refers to the implicit casting of data types that sometimes occurs in C and C++. For example, in C++ you are allowed to assign a float value to an int variable, which can result in a loss of information. Java does not support C++ style automatic coercions. In Java, if a coercion will result in a loss of data, you must always explicitly cast the data element to the new type.
The following is an example of an automatic coercion in C++:
float f = 3.1412;
int i = f;
Some C++ compilers may actually generate a warning for this code, but it's not considered an error. In Java, on the other hand, this code results in a compile error. It is easily fixed with an explicit cast, such as this:
float f = 3.1412;
int i = (int)f;
The command-line arguments passed from the system into a Java program differ in a couple of ways from the command-line arguments passed into a C++ program. First, the number of parameters passed differs between the two languages. In C and C++, the system passes two arguments to a program: argc and argv. argc specifies the number of arguments stored in argv. argv is a pointer to an array of character pointers containing the actual arguments. In Java, the system passes a single value to a program: args. args is an array of String objects that contains the command-line arguments.
In C and C++, the command-line arguments passed into a program include the name used to invoke the program. This name always appears as the first argument, and it is rarely used. In Java, you already know the name of the program because it is the same name as the class, so there is no need to pass this information as a command-line argument. Therefore, the Java runtime system passes only the arguments following the name that invoked the program.
Listing 28.11 contains a simple C++ program that prints out the argument list.
Listing 28.11. A C++ program to print the argument list.
#include <iostream.h>
#include <string.h>
void main(int argc, char* argv[])
{
for(int i = 1; i < argc; i++)
cout << argv[i] << "\n";
}
This program simply iterates through each argument using a for loop, printing each to the standard output stream. Notice that the loop starts at 1 to avoid printing the name of the program itself (the first argument). Listing 28.12 contains the Java equivalent, the ArgPrint application class.
Listing 28.12. The Java ArgPrint class.
public class ArgPrint {
public static void main(String[] args) {
for (int i = 0; i < args.length; iI++)
System.out.println(args[i]);
}
}
The ArgPrint class contains one method, the main method. This is the Java equivalent of the C/C++ main function; it is called when the Java program is executed. Notice that the main method takes an array of String objects as parameters. The usage of this array highlights another interesting difference between Java and C++, arrays. All Java arrays have a data member called length that can be used to determine how many elements an array contains. In this case, length is used to iterate through the array of String arguments.
You probably noticed the absence of the cout standard output stream object in the Java ArgPrint class. There's a good reason for this: Java doesn't implement any of the standard C++ stream objects. However, it does contain similar equivalents. The Java System class implements three stream objects that are very similar to the C++ standard stream objects: in, out, and err. You access these stream objects with the System class.
The ArgPrint class showed how to use the out stream object to output text. The out object is of type OutputStream, and it contains a number of methods, such as println, for outputting data to the standard output stream. Similarly, the in and err objects contain a variety of methods for performing stream input and output. In most cases, you can convert standard C++ stream I/O code to Java stream I/O code by simply changing the references from cin to System.in, and so forth. Because Java doesn't support operator overloading, you also need to change any << or >> operations to the equivalent Java method calls.
C and C++ have no built-in support for text strings. The standard technique adopted among C and C++ programmers is that of using null-terminated arrays of characters to represent strings. In Java, strings are implemented as first class objects (String and StringBuffer), meaning that they are at the core of the Java language. Java's implementation of strings as objects provides several advantages:
Although it's easy to see the advantages of Java strings, converting C++ code to Java that makes heavy use of null-terminated character arrays is another issue. Next to pointers, string code is probably the most troublesome code to convert to Java. You simply must get dirty with the details and figure out exactly what is happening to be able to change the code to use Java string objects. The other problem is that character arrays are used extensively in almost every C/C++ program.
Listing 28.13 contains a simple C++ function that manipulates null-terminated character strings, ReverseIt.
Listing 28.13. The C++ ReverseIt function.
char* ReverseIt(const char* szText) {
int len = strlen(szText);
char* dest = new char[len];
for (i = (len - 1); i ><= 0; i--)
dest[len - i - 1] = szText[i];
return dest;
}
The ReverseIt function takes an array of characters and returns an array of characters that is the reverse of the original. It simply creates a new array and copies each character from the original array in reverse order. Notice, however, that there is nothing stopping you from writing code that overruns an array bound, or even from manipulating the character pointers. Even though this code works fine, the very nature of C++ makes it prone to potentially dangerous pitfalls implemented at the programmer's discretion. Take a look at the Java version of the same function, reverseIt, which is now a method in the Reverse class in Listing 28.14.
Listing 28.14. The Java Reverse class.
class Reverse {
String reverseIt(String s) {
int i, len = s.length();
StringBuffer dest = new StringBuffer(len);
for (i = (len - 1); i >= 0; i--)
dest.append(s.charAt(i));
return dest.toString();
}
}
The Java reverseIt method has no mention or use of arrays. In Java, strings are first class citizens implemented by the String and StringBuffer classes; they are genuine data types that can be manipulated like integers or floating-point numbers. All modifications to Java strings must take place through the class methods defined in the String and StringBuffer classes, as you can see in the reverseIt method.
Most developers agree that the misuse of pointers causes the majority of bugs in C/C++ programming. Put simply, when you have pointers, you have the ability to trash memory. C++ programmers regularly use complex pointer arithmetic to create and maintain dynamic data structures. In return, C++ programmers spend a lot of time hunting down complex bugs caused by their complex pointer arithmetic.
The Java language does not support pointers. Java provides similar functionality by making heavy use of references. Java passes all arrays and objects by reference. This approach prevents common errors due to pointer mismanagement. It also makes programming easier in a lot of ways, because the correct usage of pointers is easily misunderstood by all but the most seasoned programmers.
You may be thinking that the lack of pointers in Java will keep you from being able to implement many data structures, such as dynamically growable arrays. The reality is that any pointer task can be carried out just as easily and more reliably with objects and references. You then benefit from the security provided by the Java runtime system; it performs boundary checking on all array indexing operations.
Pointers are no doubt the most difficult aspect of moving C/C++ code to Java, because most C/C++ programs are riddled with pointer use. The first line of attack in converting pointer code is to convert all character arrays to Java strings. Once you do this, you'll probably be surprised at how much pointer-dependent code was weeded out.
The next phase of pointer conversion is object creation/destruction. In C++, the typical way objects are used is to create a pointer to an object variable and then use the new operator to create a new object and assign it to the variable. Once you finish with the object, you call delete on the pointer variable to clean up things. This procedure isn't all that different in Java; it's just missing the final step.
In Java, objects no longer being used are automatically cleaned up by the Java garbage collection system, which is typically implemented as a low-priority system thread. Because the Java system itself handles cleaning up unused objects, you don't have to worry about cleaning up after yourself. This may be your one opportunity to make a mess and not have to clean up after yourself, so take advantage of it! To better understand the differences between working with objects and pointers in C++ and Java, let's look at an example. Listing 28.15 contains a C++ class with a member object pointer.
Listing 28.15. The C++ Rocket class.
class Rocket {
Booster* booster = 0;
Rocket() {
booster = new Booster();
}
~Rocket() {
if (booster != 0) {
delete booster;
booster = 0;
}
}
};
The constructor for Rocket initializes the booster member variable by creating a new object. Then the destructor for Rocket cleans up the booster member by deleting it and setting it to 0. This is a painfully simple example, but it helps to learn things in small doses. Listing 28.16 contains the Java version of this same class.
Listing 28.16. The Java Rocket class.
class Rocket {
Booster booster;
Rocket() {
booster = new Booster();
}
}
The Java code is much more simpler, even in this case where there
is relatively little happening with the C++ pointers. Notice that
the booster member variable
isn't initialized to 0 in
the Java version of Rocket.
This highlights a subtle feature in Java; all member variables
are set to 0 or null
if they are created without being initialized. Notice also the
absence of a destructor in the Java code; the Java garbage collector
takes care of cleaning up the booster
object when it is no longer in use.
Note |
Java actually supports a method very similar to a C++ destructor, the finalize method. The finalize method is called whenever an object is destroyed by the garbage collector. However, due to the nature of the garbage collector itself, Java does not guarantee that the finalize method will get called for all objects. In other words, don't rely on it getting called in your code! |
If you're concerned about how Java gets by without using pointers, keep in mind that the Java system is certainly using pointers under the hood. The trick is that you are forced to do everything under the guise of references. This may seem like a pretty big limitation at first, but once you get in the habit of using references you'll see that you aren't really missing anything. Knowing this, another phase of porting C++ code to Java is converting pointers to references in the C++ code before you even start fooling with Java. Try converting some C++ code to being entirely reference-based and then take a stab at moving it to Java. You'll be in for a much smaller headache if you take this route.
Multiple inheritance is a feature of C++ that enables you to derive a class from multiple parent classes. Although multiple inheritance is indeed powerful, it is complicated to use correctly and causes lots of problems otherwise. It is also very complicated to implement from the compiler perspective.
Java takes the high road and provides no direct support for multiple inheritance. You can implement functionality similar to multiple inheritance by using interfaces in Java. Java interfaces provide object method descriptions, but contain no implementations. Let's take a look at an example of C++ code that uses multiple inheritance:
class InputDevice : public Clickable, public Draggable {
// class definition
};
The C++ InputDevice class is derived both from the Clickable and Draggable classes. This means that InputDevice inherits all the data members and methods implemented in both of these classes. The closest you can get to this in Java is to make Clickable and Draggable interfaces, which can contain method definitions but no actual method code or data members. The InputDevice class can implement these interfaces using the implements keyword:
class InputDevice implements Clickable, Draggable {
// class definition
}
As you can see, you may have your work cut out for you if you are trying to move C++ code to Java that relies on lots of multiply inherited classes. Even so, the Java interface approach is not all that bad; you just have to juggle the actual method bodies and possibly implement more derived classes to contain them. Nevertheless, the primary goal of uniting separate logical organizations into a more derived one will still be attained.
The multiple inheritance example brings up another important difference between C++ and Java: inheritance syntax. In C++, you specify inheritance by using a colon after the newly derived class, followed by the parent class or classes:
class B : public A {
// class definition
};
In Java, the extends and implements keywords are used to indicate inheritance:
class B extends A {
// class definition
}
Fortunately, this change can be made in your C++ code as a simple search and replace, for the most part. The only hangup will be changing the syntax for classes using multiple inheritance, in which case you have to do some real work anyway.
Access modifiers are supported in both C++ and Java, but the methods of declaring them are different in each. In C++, you declare access modifiers as a label above a group of class members:
class A {
public:
int x, y;
private:
float v;
};
In Java, you can do the same thing, but you apply the access modifiers a little differently. You set them for each individual declaration:
class A {
public int x, y;
private float v;
}
In this way, Java access modifiers aren't labels at all; they really are modifiers. Converting access modifiers in C++ code is pretty simple; just go through and remove each label, adding the appropriate modifier to each variable declaration or method following the original label.
Java has no friends! To prove it to you, Java doesn't have any concept of a friend class, whereas C++ does. A friend class in C++ is one that has access to all the data and methods in another class, regardless of the visibility of the member data. Java has no exact equivalent to the friend concept, but it does have an access type that enables a specific group of classes access to member data, which is somewhat similar to the friend idea.
This Java access type is often referred to as the default type, because there is no keyword used to apply it. You may also see it referred to as the friendly access modifier. It has the effect of giving classes in the same package as the class in question access to the member variable or method declared with default access. To give a member variable or method default access, you simply don't use an access modifier, like this:
class A {
public int k;
private int h;
int i, j;
}
In this example code, k and h take on the specific access modifiers they are declared with, and i and j assume default, or package, access. To convert friendly C++ code to Java, you should be able to combine friend classes together in the same package and declare many of their shared members with default access.
In C++, there is no real boolean type; boolean values (true and false) are usually implemented as integers, with zero being interpreted as false and nonzero being interpreted as true. This usage resulted in somewhat of a half-hearted attempt by C/C++ programmers to use booleans in their code. Take a look at this example:
if (--numLives)
isDead = 1;
There are a couple of assumptions being made here that don't sit well from a purist programming perspective. First, if statements should always be based on a boolean value. However, as this example shows, programmers often exploit the fact that C/C++ if statements (and other conditional statements) operate on values either being zero or nonzero, rather than true or false. The isDead variable, which clearly has a boolean meaning, uses 1 to represent true.
Unlike C/C++, Java supports boolean values as a type all their own. As a matter of fact, this example wouldn't work in Java because Java if statements fully expect boolean types, not integers parading as booleans. The Java version would look like this:
if (--numLives <= 0)
isDead = true;
The if statement has been changed to result in a boolean outcome. This outcome was implied in the C++ code, but it is made clearer and more consistent in the Java version. The isDead variable has been changed to a boolean type. Notice that true and false are legitimate Java keywords (as is null).
Most C/C++ code contains this "integer as boolean" approach and will therefore have to be fixed in a Java port. However, these changes are pretty simple and shouldn't take too much time.
In this chapter, you learned about the different challenges you are faced with when porting C and C++ code to Java. Even though Java is a close cousin to C++, you saw how there are enough differences to make any conversion process a decent amount of work. On the other hand, the final benefits of moving your existing code base to Java can far outweigh the potential difficulties you encounter while moving it.
The goal of this chapter isn't to bog you down with hundreds of detailed examples of complex code conversions, but rather to highlight the primary problem areas where the bulk of the code conversion will take place. If you take it a step at a time, you'll find that moving C/C++ code to Java isn't all that hard. (If it makes you feel any better, I was able to convert a complex set of sprite classes from C++ to Java in a couple of days.) Just be patient and think about how cool your code will be running live on the Web!