Java for C++ coders, and vice versa By dplass Introduction Primitives In addition, in Java, unlike C++, primitives are not first-class objects. You cannot extend a primitive data type in Java, nor can you call methods on a primitive. Furthermore, as a result, in Java you cannot pass a primitive to a method that accepts an Object (or any class that extends Object). This becomes a problem in Java collections, which only store Objects (this will change in Java 1.5 with the addition of generics, see below). To get around these limitations, Java has "wrapper" classes with similar names to the primitives that they wrap. For example, the Integer class wraps int; you can cheaply make an Integer with the int that it wraps. The wrapper classes in Java also provide utility methods and constants that are associated with the primitive itself. For example, Integer.MAX_VALUE is the most positive int (231-1). Other very useful methods include Integer.parseInt, which parses a String into an int (aside: why doesn't it parse it into an Integer?), and Integer.toBinaryString. Strings In C++ strings are mutable and adhere to the [] style for both get and set. This is one of my pet peeves about the Java String and StringBuffer classes; you must use awkward charAt(int) and setCharAt(int, char) methods to get and set individual characters within the string. Because C++ supports operator overloading (see below), the string class defines operator[] to support this syntax. Classes and inheritance An interesting feature of Java lets you declare a class as abstract with no abstract methods. This will prevent that particular class from being instantiated. C++ has no such analogous concept; by definition in C++, an abstract class is one in which at least one abstract method is defined. Interfaces "Mix-ins" are typically single-method interfaces which allow you to use the class in a certain way. For example, the Comparable interface in Java allows you to define the compareTo method, which will allow Java utility methods and classes to sort your objects. In this case, it is equivalent to defining the operator< method in C++. "Tagging" interfaces are used to signify to the application server that it should do something special with the class. For example, in Java, the Serializable interface signifies to the container that it can write out the bytes of the object and read it back later with the same exact state. The developer does not need to actually implement anything when a class implements Serializable. Virtual methods public class Parent { void method() { System.out.println("parent"); } } public class Child extends Parent { void method() { System.out.println("child"); } } Parent c = new Child(); c.method(); // outputs "child" In C++, you must explicitly tag Parent.method as virtual for this to work. Example: class Parent { public: virtual void method() { printf("parent\n"); } void method2() { printf("parent 2\n"); } // note not virtual }; class Child: public Parent { public: void method() { printf ("child\n"); } void method2() { printf("child 2\n"); } }; int main() { Parent *c = new Child(); c->method(); // outputs "child" c->method2(); // outputs "parent 2" } This is known as the "Slicing Problem" in C++, and can wreak havoc on systems. It makes it hard to track down the behavior of a child class when the problem isn't in the child class! If the child class overrides a method defined in the parent as not virtual, you have to change the parent class to make it virtual. Java doesn't have this problem, since all methods are virtual there is no way to induce the Slicing Problem. Operator overloading class Complex { public: double real, imag; Complex & operator+(Complex &that) { this->real += that.real; this->imag += that.imag; return this; } }; Then you can write very natural code with Complex objects, e.g., Complex a; a.real = 1.0; a.imag = 2; Complex b; b.real = 3.0; b.imag = 4.0; Complex c = a + b; // very natural, and does what you think This facility is absolutely not supported in Java. All I can say is, "Why, oh why not!?" It makes C++ so much readable, especially when it comes to custom classes that represent mathematical entities (as in the Complex example above), or array-ish classes. For example, the vector template class defines the operator[] method, which allows you to write array-like code for accessing and modifying members of the vector. Standard libraries
The big difference is that Java does not support templatized data types (until recently, when Sun announced the addition of generics to the next version of the language). So, when you define a Vector in Java, it is only a Vector of Objects. In C++ you specifically declare vector<int>, and you can only put ints into the vector. When you retrieve objects out of a Java Vector, you must then cast it to the actual object that it is. Get it wrong, and you get a run-time exception (which, of course, you can catch.) As mentioned above, the C++ vector template class defines operator[], allowing you to treat vectors as if they were arrays. Even better, the C++ map class also defines operator[]. As a result, you can write efficient, and understandable, code like this: map<string, int> wordCount; // allocated on the stack wordCount["the"] = 1; The equivalent in Java would be: HashMap wordCount = new HashMap(); // no stack allocation in Java WordCount.put("the", new Integer(1)); // remember, no primitives in collections // [until Java 1.5 is released -- in 2004?] Memory allocation Java and C# support garbage collection of unreachable memory. For example: public void JavaAllocate() { Object reclaimable = new Object(); reclaimable.doSomething(); } After the method exists, reclaimable will not be reachable from any other object in memory. Eventually, the Java garbage collector, when it needs memory, or at some other time, will find this object on the heap and reclaim its memory. Garbage collection is the only way memory is reclaimed in Java. The advantage of this system is that the burden is not put onto the developer to remember to deallocate memory. The disadvantage is that you have no control over when the garbage collector will run, and it consumes system resources (i.e. CPU cycles). [Aside: real-time extensions to Java have addressed this problem.] In contrast, C++ supports explicit deallocation of unwanted memory. For example: void cpp_cleanup() { Thing *thing = new Thing(); thing->do_something(); delete(thing); } If you forget to deallocate thing, it will remain on the heap forever (or at least until your program terminates). Example: void cpp_hanging() { Thing *thing = new Thing(); thing->do_something(); } In this example, you will never be able to deallocate the memory allocated originally after this method terminates. The advantage of C++'s memory allocation model is that you are guaranteed that as soon as you deallocate the memory, it is available (in contrast, you might have to wait for Java's garbage collector to run.) Of course, the disadvantage is that you have to be extremely careful to remember to deallocate things once you're done with them. Many companies have made much money providing tools to find, detect, debug, and eliminate these kinds of memory problems in both C++ and Java. Pointers, references and handles Java, on the other hand, only uses handles to memory. This is due to the desire of the Java designers to allow the same class files (see below) to support any number of physical implementations (i.e. CPUs). Thus they could not expose something so close to the hardware as a pointer. Instead, the specific JVM that the code is running on is responsible for mapping handles to memory. The following snippet (in a method) defines a handle in Java, but doesn't actually allocate any memory: Myclass object; In C++ the above code would actually allocate an instance of the Myclass object on the stack. But in Java nothing is allocated. Only a slot, named object, in the stack frame has been defined. The value in this slot is actually undefined and the compiler will throw an error if you attempt to use object before assigning it to a value (or allocating something.) This is similar to references in C++, which always must reference an object, whether on the stack or in memory on the heap. The big difference is that C++ references must always reference an object, but handles in Java can be null, or 'undefined' as described above. Because you can allocate on the stack, and define a pointer to any allocated object, the "dangling reference" problem plagues C++. (This cannot occur in Java, because you cannot allocate objects on the stack). Example: Thing *cpp_dangling() { Thing stackThing; // calls no-arg constructor return &stackThing; // valid syntax! } void main() { Thing *bad = cpp_dangling(); bad->do_something(); // likely will CRASH } When cpp_dangling terminates, the stack memory where stackThing was allocated has been deallocated. As a result, the pointer "bad" points to a place in memory which has been deallocated (that stack frame), and possibly already overwritten by some other object. This usually results in a fatal runtime error (a.k.a. a CRASH.) Compilation and run-time environment C#, like Java, runs within a virtual machine. This is one of the selling points of Java - "Write once, run anywhere". The .class files that a Java compiler produces can (theoretically) be run on any operating system that has a JVM. Both C# and Java support further compilation (usually at run-time) to native code to improve performance. The runtime environment for both Java and C# also provide run-time checks for other potential errors, such as array out of bounds (or string access out of bounds), which C++ does not. You can catch this exception in Java, but in C++ it is much harder to trap this kind of operating-system-level exception. Conclusion References and further information Would you like to write a feature? |
|