I am very opinionated in the technology arena. I wouldn't say that I am polarized on the issues per se. However, the reasons behind my opinions on both sides are reasons that I feel strongly about.

Below, I have discoursed on several technology issues about which I have a strong opinion. I invite people to disagree with me and show me why my reasoning is faulty or incomplete. If you would like to comment on these issues, please send me mail.

On the topic of Object Oriented Programming Languages

There are many object oriented languages used in today's industrial software environment. I have experience with several languages that tout object oriented features. Some of them have such features added as a supplement to their original functionality, such as Perl and PHP. Some are powerful object oriented systems that I have limited experience with, and therefore am not qualified to evaluate, such as Eiffel and Smalltalk. Of the object oriented languages with which I have substantial experience, I will compare C++ and Java. This seems apt, as everyone seems to want to compare C++ and Java. I have respect for both languages and the goals they are trying to accomplish. Consider this a wish list of what I wish could be added to each language.

The problems with Java

Singly Inherited Class Model

The developers of Java at Sun Microsystems, Inc. have asserted that the multiply inherited class model of many object oriented languages are complicated and confusing. They claim that by removing this functionality, programming becomes clearer and easier to maintain. This simply is not true. While many class hierarchies are adequately expressed with a singly inherited model, there are times when the best way to represent family behaviors is by combining the abstractions of more than one parent class. I have personally done this in more than one project, and the resulting class hierarchy was much simpler and more maintainable than the singly inherited version would have been.

It is argued that Java's interface mechanism nearly makes up for the lack of multiple inheritance, but this argument is weak at best. Multiple inheritance is used for much more powerful purposes than defining an interface to other classes, and most of the uses where it is applied require both an object state (variables) and behavioral implementation (defined methods), neither of which are allowed in a Java interface definition. The truth of the matter is that it was more difficult to specify the Java Virtual Machine and implement the Java compiler with multiple inheritance than it was without it.

As a design decision, this is reasonable. What is not reasonable is to take this limitation on the language and tout it as a feature. Many researchers in the object oriented design will take issue against such a claim.

Lack of Templates (and operator overloading)

The two most powerful and commonly used tools that came out of the structured programming revolution were the explicit specification (in code) of algorithms, and data structures. They are both used in virtually every nontrivial programming project today. However, many of the simple algorithms, such as sorting and searching, and simple data structures, such as linked lists, trees, graphs, and hash tables, are used over and over again and must be reimplemented for each new subproblem in each application, to fit the circumstances in which they are used. There are two classical solutions to this difficulty. First, you can use the functionality (as in classic or ANSI C) of a preprocessor and macro operations, as well as generalized library routines that act on raw memory. Both of these tools are somewhat kludgy and error prone, and neither are supported by the Java environment. Second, you can implement high level operations and data structures (as in higher level languages such as Perl or Python) at the language level. This is more elegant and prevents more errors, but it is much less extensible and can lead to inefficient code.

The better solution to the problem of abstracting algorithms and data structures is through the use of template classes and template fuctions. An algorithm or data structure can be fully specified as a template and automatically implemented by the compiler from a well developed, analyzed, and tested template specification.

Java's solution, while somewhat more elegant than the classic solutions, is still kludgy and error prone. Java implements its data structures such as containers once for the most general data type, the Object. Every other class type inherits from the Object data type, and consequently any class type can be stored in a container designed to contain Objects. However, this provides much less flexibility and performance than a template solution. Any container written for Objects can not contain native data types. There are times I want to create a linked list of integers or floating point numbers. It is very inefficient to use wrapper classes for these situations. Further, I often want to constrain my containers to contain data elements of only one data type, which I specify. The only way to do this using Java containers is by performing costly runtime type checks when accessing the contained data. In Java, I am forced to do this anyway, because when I access the objects in a container, it is necessary to perform a runtime type conversion to access the object through its class interface. Templates perform all type checking at compile time, allowing for more flexibility in optimization, and providing direct access of the contained data using their explicit class interface.

Finally, Java does not allow operator overloading, a common way to specify generic operations between classes. This is a must for a language implementing templates. When a programmer writes a template, he/she prefers to make as few assumptions about how the template will be used as possible to make it general. He/she is free, however, to assume that the data types to which the template is applied have implemented, say, the comparison operations, or the dereference operations. He/she can use these operations in his template without fear of constraining the usability of his template, knowing that the template user will supply operations that make sense.

Not Written For Performance

From the outset, Java has made no apologies for its size-optimized compiler. There is a good rationality behind this. Its target market was network application development, and over network wires, often size is speed. However, when I write programs on my own machine using Java code written by myself or provided by others, I often want it to run fast in the here and now. Nevertheless, I can forgive this shortcoming to some extent. The developers at Sun are largely correct when they assert it is only a matter of time before the hardware catches up with any application development. And with the availability of JIT VMs, running Java code can come near the performance of optimized C code (that is, C code optimized for size and with data access riddled with pointers and indirection).

What I find it harder to forgive is the fact that I am isolated from writing code using basic principles of performance awareness. For example, I am completely unable to aggregate data structures without using indirection. Every member of my class, and every local variable, that is an instance of a class is another pointer (or reference) I must follow to access data. Every array I declare is yet another level of indirection I must traverse. In many or most cases these objects could be declared in static global memory, quickly allocated on the system stack, or aggregated with another object to consolidate memory requests. Instead, all requests must go to the system heap. This all but destroys data locality, increasing cache misses, and requiring space allocated for many extraneous pointers. (And incidentally, I often want to explicitly pass a copy of an object and keep the original intact, but Java has no explicit mechanism for this. It doesn't even have a mechanism like C++'s const directive to prevent inadvertent modification or make explicit which methods change the state of an object. Even if I manually copy an instance, any instance members may still be changed through the copy.)

Also, it is frustrating that every method must be polymorphic. There are many methods I know I will never override, and for speed reasons, I would prefer that they be statically linked at compile time. Often I want more. I want the compiler to inline them directly into the calling code. This is especially true of access methods provided only to provide an interface to private members. And I don't necessarily want to declare them as final, because I may want to override them in a derived class later on, but only access them through a nonpolymorphic interface. Also, there is the rare time when I actually want the method used to depend on the type of pointer I use to access the object. This is rare, but when I want it I brook no opposition. Finally, it is a waste of memory to allocate storage for VTable pointers in small record-like classes that don't have methods or subclasses.

Write Once, Run Anywhere Does Not Work

It is widely known that one of the most ambitious goals of the Java project is to create code that can be run on multiple platforms and in multiple environments. It is almost as widely known that it doesn't really work. The catch phrase "Write Once, Run Anywhere" (WORA) is often spoofed with the phrase "Write Once, Test Everywhere" (WOTE). Some will take issue with me on this, but I assert that as worthy and desirable athe goal of WORA is, it plainly has not happened.

My conviction of this was made sure when my brother, an engineering student with a computer emphasis, had difficulty moving his Java program from the VM on Windows 95 where he developed it to the VM on Windows NT (both VMs were from the JDK by Sun). There are programs written in hardware assembly that are easier to port.

The problems with C++

Roots In An Arcane Language

There are many good things said about C, but there are arguably more bad. C has its roots in systems programming. Consequently it has a lot of low level functionality built in. Memory can be reinterpreted at will, often unintentionally. Operators have a way of performing unintuitively, because they were easier to implement in hardware. For instance, 3/4 is obviously 0, but 3/4.0 is another story. And don't get me started with 3/4*4. Also, it is not uncommon to want to constrain a value to be within a specific range by using the modulus operator (%), such as oArray[​rand()%​nArraySize], but try oArray[​(rand()-1)%​nArraySize]. The C modulus operator doesn't take kindly to negative operands. (Incidentally, using modulus with rand() is generally a bad idea, since the high order bits of rand() are in most implementations more random than the low order bits.)

C is a product of the UNIX community, a community with a tradition of cryptic expression. It is not intuitively obvious to the lay person that "{" means "begin code," that the operator "||" means "or," or that the operator "=" isn't used to test for equality. And while we're on the subject, every C programmer has used the assignment operator instead of the equality operator in a loop or branch condition more often than they would admit, and they admit it regularly. Inline assignments, compound assignments, and post-increments/decrements (all of which I secretly love and use regularly) are a barrier to readability and an invitation for bugs, and the lack of an explicit boolean type in the C language prevents a compiler from being able to determine the intent of the programmer and detect many of these errors. (If the assignment operator were simply replaced with ":=", it would solve 95% of such bugs.)

All this and other arcane C features are the legacy of C++ (and incidentally, many of them are inherited by Java as well).

Semantic Errors Easily Made

Often when I'm designing a class hierarchy I'll create a member function (method) that abstracts the essence of a specific task. After creating several classes in the hierarchy I'll reconsider my design and change the calling interface of the function. I'll change a call by pointer to call by reference, change part of the const specification of a virtual function, or make some other minor change in the method's prototype. Later when I test the code, I'll wonder why my program isn't exhibiting polymorphic behavior. Or I'll wonder why a static call to a derived class's (nonpolymorphic) member function through a derived reference will go to the base class instead. After stepping through the program in the debugger half a dozen times and scratching my head in bafflement, I will notice that when I changed the prototype I failed to change it in the derived class. The prototype was similar, but different enough to be a new overload of the function instead of an override of the base behavior. This is especially true of the const interface (which I admire greatly, despite the headache it occasionally causes).

Pure virtual (abstract) functions do some to alleviate such errors, since the compiler will refuse to instantiate a class that has not properly overriden its bases's abstract members, and I always declare a function to be pure virtual if I expect it to be overridden in all base classes, even if I provide a default implementation. However, this is insufficient for errors committed while overriding default behavior selectively. Such errors could be detected and signalled as a warning by the compiler. However, there are cases when I actually do overload on such minor differences. A better solution would be to require a keyword that specifies that a function is overriding a base member. This would prevent such accidental overrides and overloads.

Conclusion: Bottom Line

My statements above clearly show that I personally prefer C++ to Java. There are many things I admire about Java, and I continue to use Java for some tasks. But I refrain from advocating it in general until it has resolved the deficiencies I mentioned above. I do not feel perfect love and admiration for C++ either, but until something better comes along I will continue to use and recommend it.