Java as a Better C++ Joseph Bergin, 1996 I have recently been learning Java and have been impressed with its features for education. I think it is too early to adopt due to lack of materials, though I am attempting to address that. It is worth a look, however, as a long term possible replacement of C++. One of the great difficulties educators have with C++ is that it is such a complex language. Another is that it contains a large number of very low-level constructs that must be mastered. While it is possible to build high-level constructs in C++, the language doesn't require it, nor does it isolate you from low level details while you are building high level abstractions. At some level of your project, you need to get down into the pointer morass and wade about. Some reports from industry are that on large projects, half of the programming effort is spent in getting memory management right. Java was developed at Sun Microsystems, primarily as an "internet appliance" programming language. Internet appliances are simpler than general purpose computers and depend on the internet for storage of information. Java is, however, an industrial strength computing language. In Java, one can build Applets, little applications that run in the context of, for example, a Java enabled browser, such as HotJava or Netscape 2. As an Applet, the program has a small part of a larger window in which to display its wares. It is possible to build full applications in Java as well. Indeed, HotJava itself is written in Java. The designers of Java describe the language as C++ without guns, knives, or clubs. They mean that the difficult parts of the language, that bedevil programmers, as well as learners, have been removed from the language itself and are implemented within the underlying layer. For example, pointers are a major difficulty both in learning and in use in C++. They are difficult in use, since they are as easy to use wrong as they are to use right. In Java there are no pointers. Java does have recursive data structures, however, just as Lisp and ML do, but you don't refer to them via pointers. Similarly, you don't need to resolve the memory management problem (destructors-deletions) in every project, since the language requires a garbage collector to manage memory. See for example, the List class presented below. This class implements a lisp like, non-updatable list. Non-updatability assures that it is OK for two lists to share nodes. Note that node deletion is handled by the system. Arrays in C++ are troublesome, as they are a very low level construct, interacting in fundamental ways with pointers. It is easy (required?) to make subscript bounds errors in C++, and while you can certainly build a safe array class in C++, you need to get the bits right while doing so or the problem has just been transferred to a different level. In Java, Arrays are a true type and are always bounds checked. Array limits are determined at run time, as in C++, rather than at compile time (Pascal). Strings are also first class objects in Java. They are not null terminated arrays. Strings are defined in a provided class. They are not updatable. There is a library of manipulation routines. They are bounds checked. There is another class called StringBuffer that can be used when you really need to update strings. Boolean is a real type, not confused with int. One of the best features of Java is that it has elevated interfaces to first class status. An interface is a collection of constants and procedure prototypes that define some functionality (Stack). An interface is a type and you can define variables to have such a type. Interfaces are implemented with classes. And an interface can be implemented several times with different classes. A variable of interface type can hold a reference to an object of any of the classes that implement that interface. For example, you can define a Stack interface and have several different classes implement it. If a function requires that a Stack be passed as an argument, then an object of any class implementing the interface will suffice. This is a lot like the situation in C++ where a template parameter would require the operations of a Stack. It is not quite the same, however, since a class must advertise that it implements an interface. It isn't implicit. Java code is collected into packages. A package may span several files. The purpose of a package is to export a collection of classes and interfaces that define and implement some specific kind of functionality. Input/output in java is provided by a package, as is the windows toolkit. There is no "friend" notion in Java, but classes in a package may share information quite freely unless it is specifically made private. While Java defines a single inheritance model for classes, with "Object" as the root, it permits multiple inheritance of interfaces. Since there is no code associated with interfaces, this is both safer and easier to grasp than C++ implementation inheritance. This matches the biological model of multiple inheritance quite well. One of the reasons that multiple inheritance is treated as "natural" is the multiple classification schemes that biologists often use. In reality however, there are no "vertibrates". There are only tigers and humans... That is to say, the classifications define abstractions, that are not themselves instantiated. Only the "leaves" of the classification system have instances. Interfaces model these relationships quite well, and classes implement the leaves. Another major advantage of Java is that it is architecture neutral, runs on many platforms including Macintosh, Sun Workstations, and Windows 95/NT, and defines a platform independent graphics model and a platform independent window-button-mouse based GUI. Java also has exceptions and is multi-threaded. One feature of Java is that every variable is encapsulated within either an object or a function, and every function is encapsulated within a class. There are no globals of any kind. All programming is object- oriented. Even the main function of an application (applets don't use main), is a method of some class, but interestingly, it could be the method of any class in your library. In Java, all methods are virtual, though they may be declared "final" preventing their being overridden. There is only one object model, not the two (dynamic vs. automatic) of C++. Polymorphism is automatic and ubiquitous. Java has more levels of information hiding than C++, not just public, protected, and private. Since Java uses a garbage collector, there are no destructors as such, though it does provide a finalization mechanism that the programmer can guarantee will be executed before an object is actually deleted. This is seldom needed, except when an object holds a reference to an external entity such as a file that must be closed. Java takes its syntax from C++, it borrows single inheritance of classes, together with a run-time instantiation of classes from Smalltalk. It borrows a strong typing model from Modula-3, and a distributed idea of what constitutes an application from Oberon. Reference variables (rather than pointers) are similar to what is in Smalltalk, M3, and Oberon, as well as Lisp. Java is free and distributed via the internet. It is supported by a data structures library and a GUI library. The documentation is also available on line and comes in the form of hypertext documents viewed with any WWW browser. They can be viewed on or offline. Surf to http://java.sun.com The sad parts. In programming Java, there are some bits that I miss from C++. The three things I find very useful in C++ that are missing from Java are templates, overloaded operators, and user defined casts. These are useful in C++ because they can make a user defined data type behave exactly as a built in datatype, both in semantics and with the same syntax. Java programs are a bit wordier in a few places. For example, it is possible in C++ to build a dynamic array class, that can grow as needed at run time, and the user manipulates an array using completely familiar operators. In Java, the convenient use of [ ] must be replaced by method calls. The left and right hand usages of [ ] actually require different methods in Java such as getFrom(n) and putInto(n, a). Another small difficulty with Java is that it is not possible to separate a class definition from the definitions of its methods. This is primarily a presentation problem and is partly ameliorated by the fact that you can define an interface giving the protocols and then implement that interface using a class. The interface isn't required though. Java doesn't have structs -- use classes unions -- use inheritance pointers -- use references to objects goto -- use if, while, for, switch... typedef -- it is automatic, actually unitialized data. All variables are always guaranteed to hold a valid value. templates overloaded operators automatically applied, user defined casts Java does: have C++ like syntax have constructors have streams have low level data byte, char, int, long, float, double These are fully defined in Java--not platform dependent. have interfaces support exceptions (Similar to C++, but stronger) support persistent data support multi-threaded programming support distributed applications (internet distributed) have better integration of multi-file projects than C++ have a data structures library including (classes) BitSet Date Dictionary Hashtable Observable Properties Random Stack StringTokenizer Vector Enumeration, which is called Iterator in other languages. At this point, Java needs more and better libraries, and if it is to be used in education, extensive curricular materials. There is an abstract windows toolkit, but no application builder library as yet. A number of things must be done by hand that could be handled by a somewhat higher level interface library. Fortunately, the nature of the language and its intended use imply that advances will be quickly and widely distributed. Teaching with Java. I think that it would be possible to teach very effectively with Java for the first two years of the undergraduate curriculum. I don't think anything would necessarily be given up by a student who eventually wanted to be proficient in C++. Using Java first would force the student to focus on higher levels of abstraction than is possible in C++ while they are learning problem solving. Certain large classes of errors simply cant be made, or are caught early on. Students could then later pick up C++ by delving into the lower level details and the more complex stuff. I think they would be better C++ programmers as a result, as they would approach programming from a somewhat more abstract level. If Java were adopted, instructors would be able to spend much less class time solving low-level problems for students who have fallen into one of C++'s pot-holes. They would naturally be led to think at a higher level of abstraction than is normal when learning C++. The advanced features of Java, such as threads would also be very useful in later courses such as operating systems and networking. The object- oriented nature would be useful in compilers, and the graphics/gui stuff useful in the graphics course. Also, interestingly, it would be possible for faculty and students to make their work widely available for use via the web. Additional Java code may be found in many places. Java is available from http://javasoft.sun.com. Two examples of applets in Java that could be used in early CS courses may be found by starting at my home page: http://csis/pace.edu/csis/admin/bergin/. Appendix A. A non-updatable array class (Lisp like) implemented in C++. // © Copyright 1996. Joseph Bergin. All rights reserved. #ifndef __List__ #define __List__ #include #include "Error.h" #include "Boolean.h" //********************* NODE ************************** template class ListNode { // no public: ListNode(const E& e):_next(NULL), _value(e){}; ListNode(const ListNode& n): _next(n._next), _value(n._value){}; ~ListNode(){}; ListNode& operator = (const ListNode &n) { if(this != &n) { _next = n._next; _value = n._value; } return *this; } private: ListNode* _next; E _value; ListNode* copyAll() { ListNode *result; result = new ListNode(_value); FAILNULL(result); if(_next != NULL) result->_next = _next->copyAll(); return result; } friend class List; friend ostream & operator<<(ostream & os, const List& L); }; // ****************** LIST ****************************** template class List { public: List(): _first(NULL){} List(const List &t) // copy constructor { copy(t); } List & operator =(const List &L) { if(this != &L) { free(); copy(L); } return *this; } ~List() { free(); } Boolean empty() const { return Boolean(_first == NULL); } E head() const { if(_first == NULL) userERROR("Empty list has no head."); return _first->_value; } List tail() const { if(_first == NULL) userERROR("Empty list has no tail."); List result; if(_first->_next != NULL) result._first = _first->_next->copyAll(); return result; } List consElement(const E& e) const { List result(*this); ListNode *first = new ListNode(e); FAILNULL(first); first->_next = result._first; result._first = first; return result; } private: ListNode* _first; void free() { ListNode *temp; while(_first != NULL) { temp = _first->_next; delete _first; _first = temp; } } void copy(const List& t) { if(t._first != NULL) _first = t._first->copyAll(); else _first = NULL; } friend ostream & operator<<(ostream & os, const List& L); }; template List operator +(const E& e, const List& t) { return t.consElement(e); } template List cons(const E& e, const List& t) { return t.consElement(e); } template ostream & operator<<(ostream & os, const List& L) { ListNode *n = L._first; while(n != NULL) { os << n->_value<<' '; n = n->_next; } return os; } #endif Appendix B. A non-updatable array class implemented in Java. // © Copyright 1996. Joseph Bergin. All rights reserved. package cs1; import java.util.Enumeration; import java.util.NoSuchElementException; // This class represents non-updatable lisp like lists. The constructor // returns an empty list. Use cons to build up other lists. Elements in // a list may not be changed or removed, though functions may return new // lists with changed/removed contents relative to their inputs. // Lists hold Objects of any kind. Enumerations over the list return // references to the actual objects so they may be acted on. Hence their // contents may be modified while they are in the list. This is not the // same thing as saying that the list contents change. // One consequence of non-updatability of lists is that two different lists // may share contents and even share nodes out of which the lists are built. // Non-updatability is what makes this safe. public class LinkList { ListNode first = null; // Note: initialization is possible. public LinkList() // An empty list { first = null; } public Object head() // First element in this list, or null. { if(first == null) return null; return first.element; } public LinkList tail() // Tail (list) of this list, or null. { if(first == null) return null; return new LinkList(first.next); } public static LinkList cons(Object h, LinkList t) // Construct a list. { ListNode n = new ListNode(h,t.first); return new LinkList(n); } public int size() // Length of the list. { if(first==null) return 0; return first.length(); } public Object clone() // returns a LinkList like this one. { if(first == null) return new LinkList(null); return new LinkList((ListNode)first.clone()); } public boolean contains(Object o) // Does this contain o? { if(first == null) return false; return first.hasElement(o); } public void copyInto(Object[] A) // Copy into a preexisting array. { int i = 0; for(Enumeration e = elements(); e.hasMoreElements();) { A[i++] = e.nextElement(); } } public Enumeration elements() // Return an enumeration over this. { return new LinkListEnum(first); } public boolean isEmpty() // Is this empty? { return first == null; } public String toString() // Copy contents to a new String. { String result = new String(); for(Enumeration e = elements(); e.hasMoreElements();) { result = result + ' ' + e.nextElement().toString(); } return result; } LinkList(ListNode first) { this.first = first; } } // Enumerations are called Iterators in other languages. See // LinkList.toString for a typical example of usage. // Note: Enumeration itself is an interface, not a class. class LinkListEnum implements Enumeration { ListNode here; protected LinkListEnum(ListNode n) { here = n; } public boolean hasMoreElements() { return here != null; } public Object nextElement() { if(here == null) throw new NoSuchElementException("Fail in LinkList."); Object result = here.element; here = here.next; return result; } } class ListNode { protected ListNode next = null; protected Object element = null; protected ListNode(Object o, ListNode n) { element = o; next = n; } protected ListNode(Object o) { element = o; next = null; } protected int length() { if(next == null) return 1; return 1 + next.length(); } protected Object clone() // recursivly clone all nodes { if(next == null) return new ListNode(element); return new ListNode (element, (ListNode)next.clone()); } protected boolean hasElement(Object o) // recursive { if(o.equals(element)) return true; if(next == null) return false; return next.hasElement(o); } }