|
SYS-CON.TV Webcasts
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
Top Links You Must Click On
General Java Java through the Eyes of a C++ Programmer
Java through the Eyes of a C++ Programmer
By: Suchitra Gupta
Jan. 1, 1997 12:00 AM
Introduction 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.
Classes 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 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 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 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 interface in 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 interface construct 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 interface is used to implement a protocol, it ensures that the benefit obtained from insulation is preserved as the application evolves, since a Java interface will stay as such. To view these concepts in an example, consider the type hierarchy as shown in Figure 2. Animal and TerrestrialAnimal are pure abstract classes that represent animals and terrestrial animals, respectively. Reptile is 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. In Java, Animal and TerrestrialAnimal could be implemented using the abstract class construct or the interface construct. Using the interface construct has the advantage that if, at a later time, a separate class such as Cockroach that derives from Arthropod, an abstract class, was required to also support the operations of TerrestrialAnimal, it could subclass from TerrestrialAnimal since TerrestrialAnimal is an interface. If TerrestrialAnimal had 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 interface instead of as a Java abstract class leaves the door open for future inheritance from that interface. The Java interface construct thus enables reuse and extensibility.
Casting Java:
TerrestrialAnimal t = new
TerrestrialAnimal *t = new Frog(); 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 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: 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 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 For example, given the inheritance hierarchy shown in Figure 4, consider the following C++ code fragment:
A *a = new B(); 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();
Operator Overloading 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 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
Class Operations
Final Operations
Parameter Passing 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
Conclusion
References Reader Feedback: Page 1 of 1
Enterprise Open Source Magazine Latest Stories . . .
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||