HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Introduction
Much of the excitement about Java comes from C++ programmers looking for a better way to develop software. In this article we take an in-depth look at Java from the perspective of a C++ expert who wishes to quickly come up to speed and transfer hard-earned knowledge of C++ to the new language.

Our purpose is not to disparage any particular programming language. Contrary to what some would have you believe, Java is not the answer to all the world's problems, and every language has its place. However, Java provides an elegant solution to many problems in computing, and we feel that an objective comparison of C++ and Java will both assist C++ experts in more quickly adapting to a new programming paradigm, and identify the strengths and weaknesses of each language.

This article is divided into two parts, the first of which examines various object-oriented concepts. The second part looks at other programming language concepts and appears in the next issue. Each concept has its own section where we present the concept in commonly-used terms first. Then, given this context, we compare how ANSI C++ and Java 1.0 implement or can be used to implement the concepts. We briefly define the terminology as we use it.

Our diagrams are intended to be language-neutral, and use a variant of the Object Modeling Technique (OMT) notation summarized in Figure 1. Each class is represented by a box, with solid lines between the boxes denoting inheritance. The class name appears at the top of the box in bold, and begins with an uppercase letter. Operations and attributes appear below, their names beginning with lowercase letters. Subclasses show only newly added and overridden operations. The names of abstract classes appear in italics, and the names of pure abstract classes appear in small capitals. The names of abstract operations appear in italics as well. Pseudocode is given for some operations in a dog-eared box connected by a dashed line.

Figure 1
Figure 1

Classes
A class is a template which defines an object's interface and implementation, and every object has exactly one class; thus, an object is sometimes referred to as an instance of a class. A class is a construct to introduce user-defined types. It is the basic unit of data encapsulation and for supporting data abstraction. Access control for operations and data is also specified in a class.

An abstract class is a class whose primary purpose is to define a common interface for its subclasses. Typically, an abstract class defers the implementation of at least one of its operations to its subclasses. An abstract class may not be instantiated. Instantiating a concrete class, a class that implements all its operations, yields an object.

An interface from an object-oriented perspective is a set of operation signatures. It is possible for an object to support more than one interface; thus, an object may be of more than one type. A type is a name used to denote a particular interface. Interfaces are discussed further in the Inheritance section.

Java and C++ share the same meaning of a class in the above sense. Java offers an additional construct called interface that primarily specifies a public interface. It contains principally public operation signatures. It can optionally contain public constants that are not instance specific. An interface is an alternate way in Java to introduce user-defined types. It is a pure abstract class that is discussed further in the Inheritance and Multiple Inheritance sections.

In C++, non-nested classes are visible in namespace scope whereas in Java, classes are visible in package scope. Java does not support nested classes. Constructors can be specified in Java ˆ la C++. There is no need for destructors in Java because it does automatic garbage collection. The second part of this article will contain more information on object initialization and finalization.

Java does not support enumerated types. Java also does not support the equivalent of the C++ struct which is an artifact preserved in C++ for compatibility with C. Java makes a clean break and does not carry with it such encumbrances from the past while it attempts to build on the best features from C++, Smalltalk, and other object-oriented languages.

Inheritance
Inheritance is a key object-oriented concept which allows for an interface, implementation, or class to be defined in terms of one or more other interfaces, implementations, or classes, respectively. Inheritance is a mechanism for sharing common aspects of diverse types in a supertype.The types closer to the root of an inheritance hierarchy capture general behaviors and states. The more derived types exhibit more specialized behavior.

Interface inheritance defines one interface in terms of one or more existing interfaces. In interface inheritance, the subtype is said to inherit its interface from its supertype(s). An object that supports the interface of the subtype can be used in place of an object that supports the interface of the supertype. Implementation inheritance defines the implementation of one class in terms of the implementation of another, and is a mechanism for code and data reuse. Class inheritance combines interface and implementation inheritance; it defines one class in terms of one or more existing classes. In class inheritance, the subclass is said to inherit its interface and implementation from its superclass(es). Inheritance from a single parent is called single inheritance, while inheritance from more than one parent is called multiple inheritance.

Both Java and C++ support the notion of inheritance. A potential source of confusion is that often the term inheritance is used to mean class inheritance specifically. This came about because class inheritance is the only kind of inheritance C++ supports. In this article, we are careful to use the term inheritance in its generic sense. We say that both C++ and Java support multiple inheritance. C++ supports multiple class inheritance only, while Java does not support multiple class inheritance, but does enable multiple interface inheritance through the use of its interface construct.

Because an object may inherit more than one interface through interface inheritance, its total interface may actually be made up of more than one interface, each of which has a name and thus is a type. Therefore, as mentioned earlier, an object may be of more than one type. The ability to transparently substitute one object for another, because they have an interface in common (hence having a type in common), is another key object-oriented concept known as polymorphism.

Java supports interface inheritance and class inheritance. For interface inheritance, a class may implement zero or more interfaces via the implements keyword. Additionally, an interface itself extends zero or more interfaces. For class inheritance, a class may explicitly extend a single class via the extends keyword. If no extends is specified, it implicitly extends Object. Thus, every class and interface extends zero or more interfaces, and every class except Object extends exactly one other class, allowing single class inheritance, but multiple interface inheritance.

Interface and Implementation Inheritance
Table 1 summarizes the inheritance mechanisms in C++ and Java, and the effects of those mechanisms on the subtype. In particular, for each inheritance mechanism, the table shows whether the interface is inherited and whether the implementation is inherited by the subtype.

Table 1

The equivalent of C++ private inheritance is not supported in Java. Instead, to reuse the implementation of another type in Java without subtyping, one can use composition to hold an object of the type whose implementation is to be used. Note that if a Java interface extends another interface, there is no implementation to inherit.

Abstract Classes
As mentioned, an abstract class primarily defines a common interface for its subclasses, and generally defers part of its implementation to them. An abstract class may not be instantiated. A pure abstract class is an abstract class with no implementation for any of its operations, and no state.

For a class to be abstract in Java, the class must contain at least one operation that is not implemented (an abstract operation) or alternatively a class can be explicitly declared to be abstract with the abstract keyword. In addition to the class construct which is common to C++ and Java, Java provides the abstract modifier and the interface construct to capture the notion of an abstract class and a pure abstract class, respectively. An abstract class in C++ is a class with at least one pure virtual function, while a pure abstract class is a class with only pure virtual functions and no state. In Java, the abstract modifier is implied for an interface and is optional in the declaration. The only kind of data that an interface can have is public, static and final data. It is useful to use interfaces when designing class libraries to allow for maximum flexibility for future modifications and reuse, as explained below.

The preferred way to capture a set of operations that specify a protocol is via an interfacein Java and via a class containing only pure virtual operations in C++. Using a C++ class requires more discipline, since the compiler does not require one to have only pure virtual operations in such a class. The Java interface,on the other hand, captures the designer's intent of having only abstract operations in the class. Any attempt to add data or operation implementation results in a compile error. As the application changes over a period of time, the interfaceconstruct ensures that one does not inadvertently forget the original intent of the designer and add a non-abstract method. This is a possibility in C++ where one may add a function that is not pure virtual to a pure abstract class.

A pure abstract class that implements a protocol provides good insulation between classes that depend on it since the compilation unit that defines a class need only import the pure abstract class itself and no additional baggage in the form of implementation and dependencies thereof. Insulation reduces compilation dependencies. This reduced coupling results in faster compilation and improved testability of the design since modules are better separated. When a Java interfaceis used to implement a protocol, it ensures that the benefit obtained from insulation is preserved as the application evolves, since a Java interfacewill stay as such.

To view these concepts in an example, consider the type hierarchy as shown in Figure 2. Animaland TerrestrialAnimalare pure abstract classes that represent animals and terrestrial animals, respectively. Reptileis an abstract class subclassing from TerrestrialAnimal.Alligator is a concrete class that implements all the operations that it specifies and inherits. The most general and abstract behaviors are captured in interfaces and classes close to the root of the hierarchy and the more specific and concrete classes are towards the bottom of the hierarchy.

Figure 2
Figure 2

In Java, Animaland TerrestrialAnimalcould be implemented using the abstract class construct or the interfaceconstruct. Using the interfaceconstruct has the advantage that if, at a later time, a separate class such as Cockroachthat derives from Arthropod,an abstract class, was required to also support the operations of TerrestrialAnimal,it could subclass from TerrestrialAnimalsince TerrestrialAnimalis an interface.If TerrestrialAnimalhad been an abstract or concrete class, this subclassing would not be possible since Java allows one to subclass from exactly one class and zero or more interfaces.Thus having a pure abstract class implemented as a Java interfaceinstead of as a Java abstract class leaves the door open for future inheritance from that interface.The Java interfaceconstruct thus enables reuse and extensibility.

Casting
A subtype can be cast to a supertype in both Java and C++. Both languages thus support type-safe casting, in which the supertype can be checked at runtime before casting it to the subtype. The example below illustrates how this is done in both languages.

Java:

TerrestrialAnimal t = new
Frog();
if (t instanceof Frog)
Frog f = (Frog) t;

C++:

TerrestrialAnimal *t = new Frog();
Frog *f = dynamic_cast t;
if (!f) {
// cast failed
}

In Java, an exception is thrown if a cast is bad, whereas in C++ a zero is returned by the cast operator.

The draft C++ standard specifies other type cast operators in addition to dynamic_cast. The second part of this article will contain more on dynamic type information.

Java objects have significantly more run-time type information available. For example, given an object, it is possible to determine its parent class.

Multiple Inheritance
Multiple inheritance is inheritance from multiple sources. Multiple class inheritance is useful when a class needs to inherit data and behavior from two or more classes. Multiple interface inheritance occurs when a type inherits interface only from two or more types.

Multiple inheritance has acquired something of a bad reputation because of C++ support for only multiple class inheritance and not multiple interface-only inheritance. The notoriety of C++ multiple class inheritance stems from the complexity it introduces in the form of ambiguity in the access of derived data and methods from within a class; the possibility of having multiple copies of an ancestor class in a subclass if there is more than one way of reaching the ancestor class by traversing the inheritance hierarchy; and the additional performance penalty for virtual functions.

Java has made things simpler by disallowing multiple class inheritance. Instead, it allows inheritance from exactly one class and zero or more interfaces. This leaves the door open for a class to implement functionality from multiple interfaces. Java thus supports multiple interface inheritance.

Figure 3 demonstrates two common ways of using multiple inheritance:

  • To provide mixin behavior by derivation from a mixin class, which is an abstract class whose role is to provide an optional interface or functionality to other classes. A subtype is predominantly of a certain base type from which it is derived, but also exhibits optional behavior that is specified in a mixin type. For example, Amphibian derives from Nocturnal which is a mixin type since it specifies behavior that is optional for an Amphibian. This means that the intrinsic characteristics of Amphibian are not affected by virtue of deriving from Nocturnal. Once Amphibian derives from Nocturnal, all instances of type Amphibian will support the Nocturnal behavior interface. Mixin types can be shared with other type hierarchies since they are not tightly coupled to a single type hierarchy.
  • To provide behavior essential to a type, parts of which are distributed across two or more base types. For example, Amphibian derives from Terrestrial Animal and AquaticAnimal since an Amphibian is both, equally.

Figure 3
Figure 3

If the example shown in Figure 3 were implemented in C++, Animal TerrestrialAnimal and AquaticAnimal would be abstract classes. If the Animal class were to declare attributes and was not declared as a virtual class, then these attributes would show up twice in Amphibian. If TerrestrialAnimal and AquaticAnimal each had an additional attribute with the same name, accessing them in Amphibian would result in ambiguity as well. Neither problem occurs in Java, because Animal, TerrestrialAnimal, and AquaticAnimal would be interfaces and having attributes in them would be prohibited by the compiler. By disallowing the presence of attributes in an interface and by preventing inheritance from more than one class, Java provides a simpler model while slightly limiting some possibilities that have proven to be more problematic than useful with C++. In Java, multiple interface inheritance is possible, but not multiple class inheritance.

If both TerrestrialAnimal and AquaticAnimal have an operation that is identical, then Amphibian or one of its derived types can implement such a method just once and satisfy both interfaces. Only if there is a semantic difference in the two interface operations will there be a problem. However this is not a new problem introduced in Java.

Operations
Operations, also known as methods or member functions, are the behavioral part of an object. Every operation has a signature, which specifies its external interface, and an implementation. Instance operations are operations on objects, while class operations are operations on classes. An instance operation can be invoked only through a particular object, but a class operation can be invoked either through an object or its class. In fact, class operations may be invoked even when no instances of the class exist.

Operations may be overridden by subclasses, which means that a subclass can replace the implementation of its superclass's operation with its own. An abstract operation is one without any implementation; hence, subclasses of the class containing the abstract operation that wish to be instantiable must provide an implementation.

Operations may also be overloaded, which means that there may be more than one operation with the same name but different signatures in the same class.

Operation Overriding
An operation in C++ may be marked virtual to indicate that it can be overridden. Virtual operations are bound at runtime. The actual operation implementation invoked is determined by the class that was instantiated to create the object being referenced. If a nonvirtual operation is redefined, there is no dynamic binding, so if an object of one type is assigned to a variable which has been declared with the type of one of its superclasses, the implementation of the superclass will be invoked.

For example, given the inheritance hierarchy shown in Figure 4, consider the following C++ code fragment:

A *a = new B();
a.foo();

Figure 4
Figure 4

If foo is a virtual operation, A's implementation of foo is invoked. If foo is nonvirtual, B's implementation is invoked. Notice for virtual operations, the implementation invoked is determined by the type of the identifier through which object is referenced, and for nonvirtual operations it is determined by the class from which the object is instantiated.

C++ provides this level of control primarily because virtual operations are less efficient, so savings may be realized by not using them where their dynamic behavior is not needed. All Java instance operations behave like C++ virtual operations.

Both C++ and Java provide a way to invoke the implementation provided by a superclass which has been overridden. Java provides the super variable for this purpose. In the above example, in the above class hierarchy, A's implementation may be invoked in B as super.foo(). In C++, the operation can be qualified by its class name. If foo is virtual then A's implementation of foo may be invoked in B as A::foo(). In fact, in C++ the superclass's operation may be invoked from anywhere, not just a subclass, as follows:

B *b = new B();
b->A::foo();

To achieve a similar effect in Java, an operation would need to be added to B, say superFoo, to invoke the superclass's operation using super. Although this is an intrusive technique that requires modification of the subclass, it does have the advantage of not requiring knowledge of the superclass name as in C++.

Operator Overloading
One nice feature of C++ is operator overloading, whereby the standard operators (+, *, %, <<, and so forth) can be overloaded to have a different meaning when applied to a class than to a primitive data type. Although this feature can certainly be abused, it makes numerical programming very convenient. Consider the C++ example in Listing 1; only the second technique shown there would be legal in Java.

The first technique is more natural and convenient, because it uses the standard arithmetic operators for performing three different types of matrix multiplication. Unfortunately, Java does not allow operator overloading, although the designers of Java used the + operator themselves to allow string concatenation.

Operation Chaining
Operation chaining provides the ability for a subclass to override an operation defined by its superclass, augmenting the superclass's implementation instead of totally replacing it. For example, consider a hierarchy of persistent classes, shown in Figure 5. Inheriting from mixin interface Persistent, class A provides its own implementation of the write operation. Class B overrides this operation and does backward chaining by calling its superclass's write. Class C does not define any new persistent attributes and relies on the write operation implemented by B. Class D overrides the operation and also does backward chaining by calling its superclass's write. Since C does not provide its own implementation of write, when D calls write, the implementation provided by B is invoked.

Figure 5
Figure 5

In Java, B and D can invoke their superclass's implementation as super.write(). In C++, the same can be accomplished as B::write() and D::write(), respectively.

In both C++ and Java, constructors are backward chained. In Java, a constructor can explicitly invoke the constructor of its superclass by using super() as its first statement. If not explicitly invoked, Java implicitly inserts a call to the default constructor of the superclass. In C++, the constructor of the superclass can be explicitly specified in the initialization list; if not specified explicitly, the compiler implicitly invokes the default constructor. Because of multiple class inheritance and virtual derivation rules, determination of constructor chaining order is more involved in C++. In Java, these issues do not exist because multiple class inheritance is not supported.

In C++, a destructor must be declared virtual to be backward chained. In Java, every finalize method must explicitly call its superclass's finalize for backward chaining to occur.

Abstract Operations
An abstract or "pure virtual" operation may be created in C++ by appending "= 0" to the operation declaration. Java provides the keyword abstract for the same purpose. Any class with one or more abstract operations is automatically an abstract class. In Java, the abstract keyword may optionally be used for a class to clarify that notion. In addition, Java allows a class with implementations for all its operations to be declared abstract, which prevents it from being instantiated.

Class Operations
Both C++ and Java provide class operations by using the keyword static. In both languages, when a subclass redefines an operation defined by a parent class, no dynamic binding is performed. The behavior is the same as for C++ nonvirtual operations, as described in the Operation Overriding section.

Final Operations
Java provides the keyword final as a way for an instance operation to declare that it may not be overridden. This is enforced by the compiler. C++ provides no such mechanism. In addition to the added semantics final provides, the compiler may inline final methods for greater efficiency.

Parameter Passing
All parameters in C++, objects as well as primitive types, may be passed by value or reference.

One of the limitations of Java is that primitive types may only be passed by value, and objects may only be passed by reference. To achieve the semantics of passing an object by value, the object must be cloned before manipulating it in the operation it is passed to. There is no simple way to achieve reference semantics for primitive types, which is somewhat annoying because there are times when it is useful to pass a primitive type by reference. The only workaround is to use the class that wraps each primitive, such as Integer forint, and pass an object of that class instead of the primitive type.

In C++, but not Java, a parameter may have a default value, so that all possible parameters need not be specified by the caller. This allows for flexible operations whose more esoteric parameters may be learned as needed. A similar effect may be achieved in Java (and C++) by overloading an operation with progressively more parameters.

Return Types
In both Java and C++, the void keyword may be used for the return type to indicate that an operation has no return value. In C++, if no return type is specified, int is assumed. However, in Java the return type must be specified.

Conclusion
In this first part of the article, we have looked at how Java and C++ support key object-oriented concepts. In the second part of this article, which appears in the next issue, we examine other programming language concepts and compare how Java and C++ support them.

References

  1. Flanagan, D. "Java in a Nutshell," O'Reilly & Associates, Inc., 1996.
  2. Gamma, E., R. Helm, R. Johnson, and J. Vlissides. "Design Patterns: Elements of Reusable Object-Oriented Software," Addison-Wesley, 1995.
  3. Gosling, J. and H. McGilton. "The Java Language Environment: A White Paper," Sun Microsystems, 1995.
  4. Gosling, J., B. Joy, and G. Steele. "The Java Language Specification," The Java Series, Addison-Wesley, 1996.
  5. ISO/ANSI C++ Draft X3J16/95-0087, April 1995.
  6. Stroustrup, B. "The C++ Programming Language," Second Edition, Addison-Wesley, 1991.

About the Authors
Suchitra Gupta received his Ph.D. from the State University of New York at Stony Brook. Currently, he works at U S WEST Communications, focusing on design and development of distributed applications using object-oriented techniques. He can be reached at [email protected]

Jeffrey M. Hartkopf received his B.S. and M.S. in Computer Science from the University of Colorado at Boulder. He works at U S WEST Communications on a groundbreaking project in computing systems management. He is also participating in the development of an enterprise platform for large scale client/server applications. He can be reached at [email protected]

Suresh Ramaswamy received his B.S. in Electrical Engineering from the University of Bombay and his M.S. in Electrical Engineering from the University of Southern California. Currently, he works at U S WEST Communications, where he has been working on a development platform for enterprise class client/server applications. He can be reached at [email protected]

	

Listing 1: Operator Overloading

// instantiate n by n matrices A, B, and C
...

// technique 1: legal only in C++
C = A * B; // matrix multiplication returning new matrix
C *= B;	 // in-place matrix multiplication
C *= 2;	// in-place multiplication by a scalar


// technique 2: legal in C++ and Java
C = A.mul(B); // matrix multiplication returning new matrix
C.mulBy(B);	// in-place matrix multiplication
C.mulBy(2); // in-place multiplication by a scalar

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.