Saturday 9 May 2015

Features of the Java Language

Having seen that Java is equally suited as a language for development both on and off the Internet, it's time to look more closely at the Java language itself. The creators of Java at Sun Microsystems have defined the Java language as "a simple, object-oriented, distributed, interpreted, robust, secure, architecture-neutral, portable, high-performance, multithreaded, and dynamic language." Well, they managed to fit all of the important 1990s buzzwords into one sentence, but we need to look more closely at Java to see if they managed to fit all of these concepts into one language.

Simple

If you have experience with any object-oriented language, especially C++, you probably will find Java to be easier than your high school prom date. Because Java started out as C++ but has had certain features removed, it is certainly a simpler language than C++.

The simplicity of Java is enhanced by its similarities to C and C++. Because many of today's current programmers, especially those likely to consider using Java, are experienced in at least C and probably C++, Java is instantly familiar to these programmers.

Java has simplified C++ programming by both adding features beyond those found in C++ and by removing some of the features that make C++ a complicated and difficult language to master. Java is simple because it consists of only three primitive data types-numbers, Boolean types, and arrays. Everything else in Java is a class. For example, strings are true objects, not just arrays of characters. Similarly, arrays in the Java language are first-class objects, not just memory allocations and runtime representations.

Java offers additional simplifications over C++. The ubiquitous goto statement has been removed. Operator overloading, a frequent source of confusion during the maintenance of C++ programs, is not allowed in Java. Unlike C and C++, Java has no preprocessor. This means that the concepts behind #define and typedefare not necessary in Java. Java reduces the redundancy of C++ by removing structures and unions from the language. These are both just poor cousins of a full-fledged class and are superfluous in a cohesively designed language. Of course, they were necessary in C++ because it was important for early C++ translators and then compilers to be able to correctly interpret the existing C code that relied on these features.

The most important C++ feature left out of Java is the capability to directly manipulate memory addresses through the use of pointers. Pointers are one of the cornerstones of the C and C++ languages, and it would be difficult to write many programs in these languages without using pointers. However, as any C or C++ programmer will admit, pointers are also a significant source of problems and debugging time in C and C++ programs. Pointers can accidentally be set to point to the wrong thing, causing unexpected behavior including crashes. Pointers also can be used to store allocated memory. If the allocated memory isn't freed, or released back to the operating system, then the program will gradually leak memory, until it eventually runs out. An entire set of commercial products, such as the Bounds Checker products, has come into existence to help programmers identify these types of pointer-related problems. Java simplifies this by completely removing pointers from the language and using a handle-based solution instead.

Of course, if all Java did was remove syntax from C++, it would be a poor compiler instead of an exciting new language. Java goes well beyond C++ by adding some important features. One of the most important is automatic memory management, usually known as garbage collection. Garbage collection is really just a blue-collar term that means that you don't need to free memory that you allocate-the Java Virtual Machine takes care of doing this for you. If you're a C or C++ programmer, or have ever had to track down memory leaks in another language, just imagine how nice your life could be if you never have to do it again. You would have time for walks on the beach, barbecued turkey burgers on holiday weekends, and romantic evenings with your spouse.

Java goes beyond C++ in a variety of other ways, as well. For example, Java includes language-level support for writing multithreadedprograms. A multithreaded program is one that is written such that it performs more than one task at a time. For example, consider the stock price Web page shown earlier in Figure 1.2. One thread in the program to create this page may be constantly retrieving quotes from the stock exchange while another thread searches various news databases for breaking stories about the stocks being monitored. Although you can definitely write this program in a traditional single-threaded manner, the ability to use multiple threads can make it simpler to write and maintain.

Object-Oriented

Of course, Java is object-oriented. In fact, in the mid-1990s, it's hard to imagine someone developing a new language and declaring it the greatest new thing without it being object-oriented. In its approach to object-orientation, Java follows more closely along the lines of languages such as SmallTalk than C++. Except for its primitive data types, everything in Java is an object. In contrast, C++ is much more lax in that you are entirely free to mix and match object-oriented code (classes) and procedural code (functions). In Java, this is not the case. There are no global functions in Java: all functions are invoked through an object.

Java's support for object-orientation does not include multiple inheritance. The designers of the language felt that the complexity introduced by multiple inheritance was not justified by its benefits.

Java classes are comprised of methods and variables. Class methods are the functions that an object of the class can respond to. Class variables are the data that define the state of an object. In Java, methods and variables can be declared as private, protected, or public. Private methods and variables are not accessible outside of the class. Protected members are accessible to subclasses of the class, but not to other classes. Finally, public methods and variables are accessible to any class.

Classes in Java can be defined as abstract. An abstract class is a class that collects generic state and behavioral information. More specific classes are defined as subclasses of the abstract class and are used to define actual, specific entities. For example, software in use at a pet store may have an abstract class named Pet. This class would store information that is common to all pets-birthdate, cost, sale price, date received, and so on. Derived from the abstract Petclass could be classes such as Dog, Cat, Bird, and Fish. Each of these classes can augment the abstract class as necessary. For example, a member variable called WaterType(salt or fresh) would be necessary in Fish. Because WaterType would be meaningless for Dogs, Cats, and Birds, it is not part of the abstract implementation of Pet.

Distributed

Java facilitates the building of distributed applications by a collection of classes for use in networked applications. By using Java's URL (Uniform Resource Locator) class, an application can easily access a remote server. Classes also are provided for establishing socket-level connections.

Interpreted

Because Java is interpreted, once the Java interpreter has been ported to a specific machine, that machine can instantly run the growing body of Java applications. As an example of the usefulness of this, imagine a hypothetical chip manufacturer, Outtel, that has just finished its newest CPU chip. This new chip, named the Zentium, serves as the foundation of a new line of computers being marketed toward Zen Buddhist monasteries. Once Outtel ports the Java interpreter to work on the Zentium, the new machine will be able to run all of the Java development utilities-the compiler, the debugger, and so on. Contrast this with a traditional language. If Outtel wants to release a C++ compiler with its new computer it must port, or create from scratch, the compiler, the debugger, the runtime library, and so on.

Also, when using an interpreter, programmers are freed from some of the concerns of intermodule dependencies. You no longer have to maintain a "make" file that is sometimes as complicated as the hardest part of your program.

Another advantage is that the time-consuming edit-compile-link-test cycle is broken. Without the compile and link steps, working in an interpreted environment is a much simpler edit-test cycle. Even with today's quick C++ compilers, it is not uncommon for a complete recompile and relink of a large program to be measured in hours and take the better part of a day. Without having to wait for lengthy compiles and links, Java promotes prototyping and easier debugging.

Robust

The designers of Java anticipated that it would be used to solve some very complex programming problems. Writing a distributed, multithreaded program that can run on a variety of operating systems with a variety of processors is not a simple task. To do it successfully, you need all the help your programming language can offer you. With this in mind, Java was created as a strongly typed language. Data type issues and problems are resolved at compile-time, and implicit casts of a variable from one type to another are not allowed.

Memory management has been simplified in Java in two ways. First, Java does not support direct pointer manipulation or arithmetic. This makes it impossible for a Java program to overwrite memory or corrupt data. Second, Java uses runtime garbage collection instead of explicit freeing of memory. In languages like C++, it is necessary to delete or free memory once the program has finished with it. Java follows the lead of languages such as LISP and SmallTalk by providing automatic support for freeing memory that has been allocated but is no longer used.

Secure

Closely related to Java's robustness is its focus on security. Because Java does not use pointers to directly reference memory locations, as is prevalent in C and C++, Java has a great deal of control over the code that exists within the Java environment.

It was anticipated that Java applications would run on the Internet and that they could dynamically incorporate or execute code found at remote locations on the Internet. Because of this, the developers of Java hypothesized the existence of a hostile Java compiler that would generate Java byte codes with the intent of bypassing Java's runtime security. This led to the concept of a byte-code verifier. The byte-code verifier examines all incoming code to ensure that the code plays by the rules and is safe to execute. In addition to other properties, the byte code verifier ensures the following:
  • No pointers are forged.
  • No illegal object casts are performed.
  • There will be no operand stack overflows or underflows.
  • All parameters passed to functions are of the proper types.
  • Rules regarding private, protected, and public class membership are followed.

Architecture-Neutral

Back in the dark ages of the early 1980s, there was tremendous variety in desktop personal computers. You could buy computers from Apple, Commodore, Radio Shack, Atari, and eventually even from IBM. Additionally, every machine came with its own very different operating system. Because developing software is such a time-consuming task, very little of the software developed for use on one machine was ever ported and then released for use on a different machine.

In many regards, this situation has improved with the acceptance of Windows, the Apple Macintosh, and UNIX variations as the only valid personal computer options. However, it is still not easy to write an application that can be used on Windows NT, UNIX, and a Macintosh. And it's getting more complicated with the move of Windows NT to non-Intel CPU architectures.

A number of commercially available source code libraries (for example, Zinc, ZApp, and XVT) attempt to achieve application portability. These libraries attempt this by focusing on either a lowest common denominator among the operating systems or by creating a common core API (Application Programming Interface).

Java takes a different approach. Because the Java compiler creates byte code instructions that are subsequently interpreted by the Java interpreter, architecture neutrality is achieved in the implementation of the Java interpreter for each new architecture.

Portable

In addition to being architecture-neutral, Java code is also portable. It was an important design goal of Java that it be portable so that as new architectures (due to hardware, operating system, or both) are developed, the Java environment could be ported to them.

In Java, all primitive types (integers, longs, floats, doubles, and so on) are of defined sizes, regardless of the machine or operating system on which the program is run. This is in direct contrast to languages like C and C++ that leave the sizes of primitive types up to the compiler and developer.

Additionally, Java is portable because the compiler itself is written in Java and the runtime environment is written in POSIX-compliant C.

High-Performance

For all but the simplest or most infrequently used applications, performance is always a consideration. It is no surprise, then, to discover that achieving high performance was one of the initial design goals of the Java developers. A Java application will not achieve the performance of a fully compiled language such as C or C++. However, for most applications, including graphics-intensive ones such as are commonly found on the World Wide Web, the performance of Java is more than adequate. For some applications, there may be no discernible difference in performance between C++ and Java.

Many of the early adopters of C++ were concerned about the possibility of performance degradation as they converted their programs from C to C++. However, many C++ early adopters discovered that, although a C program will outperform a C++ program in many cases, the additional development time and effort don't justify the minimal performance gains. Of course, because we're not all programming in assembly language, there must be some amount of performance we're willing to trade for faster development.

It is very likely that early experiences with Java will follow these same lines. Although a Java application may not be able to keep up with a C++ application, it will normally be fast enough, and Java may enable you to do things you couldn't do with C++.

Multithreaded

Writing a computer program that only does a single thing at a time is an artificial constraint that we've lived with in most programming languages. With Java, we no longer have to live with this limitation. Support for multiple, synchronized threads is built directly into the Java language and runtime environment.

Synchronized threads are extremely useful in creating distributed, network-aware applications. Such an application may be communicating with a remote server in one thread while interacting with a user in a different thread.

Dynamic

Because it is interpreted, Java is an extremely dynamic language. At runtime, the Java environment can extend itself by linking in classes that may be located on remote servers on a network (for example, the Internet). This is a tremendous advantage over a language like C++ that links classes in prior to runtime.

In C++, every time member variables or functions are added to a class, it is necessary to recompile that class and then all additional code that references that class. Of course, the problem is exacerbated by the fact that you need to remember to recompile the files that reference the changed class. Using make files reduces the problem, but for large, complex systems, it doesn't eliminate it.


Java addresses this problem by deferring it to runtime. At runtime, the Java interpreter performs name resolution while linking in the necessary classes. The Java interpreter is also responsible for determining the placement of objects in memory. These two features of the Java interpreter solve the problem of changing the definition of a class used by other classes. Because name lookup and resolution are performed only the first time a name is encountered, only minimal performance overhead is added. 

No comments:

Post a Comment