The new features and upgrades included into Java changed the face of programming environment and gave a new definition to Object Oriented Programming (OOP in short). But unlike its predecessors, Java needed to be bundled with standard functionality and be independent of the host platform.
The primary goals in the creation of the Java language:
- It is simple.
- It is object-oriented.
- It is independent of the host platform.
- It contains language facilities and libraries for networking.
- It is designed to execute code from remote sources securely.
The Java language introduces some new features that didn't exist in other languages like C and C++.
Object orientation ("OO") refers to a method of programming and language technique. The main idea of OO is to design software around the "things" (i.e. objects) it manipulates, rather than the actions it performs.
As the hardware of the computer advanced, it brought about the need to create better software techniques to be able to create ever increasing complex applications. The intent is to make large software projects easier to manage, thus improving quality and reducing the number of failed projects. Object oriented solution is the latest software technique.
- Assembly languages
- Software techniques started with the assembly languages, that were close to machine instruction and were easy to convert into executable code. Each hardware had its own assembly language. Assembly language contains low level instructions like move data from memory to hardware registers, do arithmetic operations, and move data back to memory. Programmers had to know the detailed architecture of the computer in order to write programs.
- Procedural languages
- After the assembly languages, high level languages were developed. Here the language compiler is used to convert the high level program to machine instructions, freeing the programmers from the burden of knowing the computer hardware architecture. To promote the re-use of code and to minimize the use of GOTO instructions, "procedural" techniques were introduced. This simplified the creation and maintenance of software control flow, but left out the organization of data. It became a nightmare to debug and maintain programs having many global variables (i.e. variables that contain data that can be modified anywhere in the application).
- Object oriented languages
- In OO languages, data is taken seriously with information hiding. Data that is specific to an object can only be accessed by procedures in that object. As a result, objects contain data as well as control flow and a program becomes a series of interactions between objects.
In this situation, the machine code file and its execution are specific to the platform (Windows, Linux, macOS, ...) it was compiled for, that is to say to the targeted platform:
... because the compiled file is a machine code file designed to work on a specific platform and hardware. It would have produced different results/output for another platform. So if you want your program to run on several platforms, you have to compile your program several times:
It poses greater vulnerability risks. Note here that when a certain code is compiled into an executable format, the executable cannot be changed dynamically. It would need to be recompiled from the changed code for the changes to be reflected in the finished executable. Modularity (dividing code into modules) is not present in Java's predecessors. If instead of a single executable, the output application was in the form of modules, one could easily change a single module and review changes in the application. In C/C++ on the other hand, a slight change in code required the whole application to be recompiled.
The idea of Java is to compile the source code into an intermediate language that will be interpreted.
|The source code||The intermediate file||The interpreter|
The intermediate language is the byte code. The interpreter is the Java Virtual Machine (JVM). The byte code file is universal and the JVM is platform specific:
So a JVM should be coded for each platform. And that's the case. So you just have to generate a unique byte code file (a
The first implementations of the language used an interpreted virtual machine to achieve portability, and many implementations still do. These implementations produce programs that run more slowly than the fully-compiled programs created by the typical C++ compiler, so the language suffered a reputation for producing slow programs. Since Java 1.2, Java VM produces programs that run much faster, using multiple techniques.
The first of these is to simply compile directly into native code like a more traditional compiler, skipping bytecode entirely. This achieves great performance, but at the expense of portability. This is not really used any more.
Another technique, the just-in-time (JIT) compiler, compiles the Java bytecode into native code at the time the program is run, and keep the compiled code to be used again and again. More sophisticated VMs even use dynamic recompilation, in which the VM can analyze the behavior of the running program and selectively recompile and optimize critical parts of the program. Both of these techniques allow the program to take advantage of the speed of native code without losing portability.
Portability is a technically difficult goal to achieve, and Java's success at that goal is a matter of some controversy. Although it is indeed possible to write programs for the Java platform that behave consistently across many host platforms, the large number of available platforms with small errors or inconsistencies led some to parody Sun's "Write once, run anywhere" slogan as "Write once, debug everywhere".
C++ was built atop the C language and as a result divergent ways of doing the same thing manifested around the language. For instance, creating an object could be done in three different ways in C++. Furthermore, C++ did not come with a standard library bundled with its compilers. Instead, it relied on resources created by other programmers; code which rarely fit together.
In Java, standardized libraries are provided to allow access to features of the host machines (such as graphics and networking) in unified ways. The Java language also includes support for multi-threaded programs—a necessity for many networking applications.
Platform independent Java is, however, very successful with server side applications, such as web services, servlets, or Enterprise JavaBeans.
Java also made progress on the client side: first it had Abstract Window Toolkit (AWT), then Swing, and the most recent client side library is the Standard Widget Toolkit (SWT). It is interesting to see how they tried to handle the two opposing consuming forces. Those are :
- Efficient, fast code; port to most popular hardware (write once, test anywhere)
- Use the underlying native subroutine to create a GUI component. This approach was taken by AWT, and SWT.
- Portability to any hardware where JVM ported (write once, run anywhere)
- To achieve this to the latter, the Java toolkit should not rely on the underlying native user interface. Swing took this approach.
It is interesting to see how the approach was switched back and forth. AWT → Swing → SWT.
With the high-level of control built into the language to manipulate hardware, a C/C++ programmer could access almost any resource, either hardware or software on the system. This was intended to be one of the languages' strong points, but this very flexibility led to confusion and complex programming practices.
The old way of error handling was to let each function return an error code then let the caller check what was returned. The problem with this method was that if the return code was full of error-checking codes, this got in the way of the original one that was doing the actual work, which in turn did not make it very readable.
In the new way of error handling, functions/methods do not return error codes. Instead, when there is an error, an exception is thrown. The exceptions can be handled by the
catch keyword at the end of a
try block. This way, the code that is calling the function does not need to be mangled with error checking codes, thus making the code more readable. This new way of error handling is called Exception handling.
Exception handling was also added to C++. However, there are two differences between Java and C++ Exception handling:
- In Java, the exception that is thrown is a Java object like any other object in Java. It only has to implement
- In Java, the compiler checks whether an exception may be caught or not. The compiler gives an error if there is no catch block for a thrown exception.
The optional exception handling in the Java predecessors leads the developers not to care about the error handling. As a consequence, unexpected errors often occur. Java forces the developers to handle exceptions. The programmer must handle exception or declare that the user must handle it. Someone must handle it.
However powerful, the predecessors of Java lacked a standard feature to network with other computers, and usually relied on the platforms' intricate networking capabilities. With almost all network protocols being standardized, the creators of Java technology wanted this to be a flagship feature of the language while keeping true to the spirit of earlier advances made towards standardizing Remote Procedure Call. Another feature that the Java team focused on was its integration in the World Wide Web and the Internet.
The Java platform was one of the first systems to provide wide support for the execution of code from remote sources. The Java language was designed with network computing in mind.
An applet could run within a user's browser, executing code downloaded from a remote HTTP server. The remote code runs in a highly restricted "sandbox", which protects the user from misbehaving or malicious code; publishers could apply for a certificate that they could use to digitally sign applets as "safe", giving them permission to break out of the sandbox and access the local file system and network, presumably under user control.
Dynamic class loading
In conventional languages like C and C++, all code had to be compiled and linked to one executable program, before execution. In Java, classes are compiled as needed. If a class is not needed during an execution phase, that class is not even compiled into byte code.
This feature comes in handy especially in network programming when we do not know, beforehand, what code will be executed. A running program could load classes from the file system or from a remote server.
Also this feature makes it theoretically possible for a Java program to alter its own code during execution, in order to do some self-learning behavior. It would be more realistic to imagine, however, that a Java program would generate Java code before execution, and then, that code would be executed. With some feedback mechanism, the generated code could improve over time.
Automatic memory garbage collection
In conventional languages like C and C++, the programmer has to make sure that all memory that was allocated is freed. Memory leaks became a regular nuisance in instances where the programmers had to manually allocate the system's memory resources.
Memory resources or buffers have specific modes of operation for optimal performance. Once a buffer is filled with data, it needs to be cleaned up after there is no further use for its content. If a programmer forgets to clean it in their code, the memory is easily overloaded. Programming in C/C++ languages became tedious and unsafe because of these very quirks, and programs built in these languages were prone to memory leakages and sudden system crashes — sometimes even harming the hardware itself. Freeing memory is particularly important in servers, since it has to run without stopping for days. If a piece of memory is not freed after use and the server just keeps allocating memory, that memory leak can take down the server.
In Java, freeing up memory is taken out of the programmers hands; the Java Virtual Machine keeps track of all used memory. When memory is not used any more it is automatically freed up. A separate task is running in the background by the JVM, freeing up unreferenced, unused memory. That task is called the Garbage Collector.
The Garbage Collector is always running. This automatic memory garbage collection feature makes it easy to write robust server side programs in Java. The only thing the programmer has to watch for is the speed of object creation. If the application is creating objects faster than the Garbage Collector can free them, it can cause memory problems. Depending on how the JVM is configured, the application either can run out of memory by throwing the
NotEnoughMemoryException, or can halt to give time for the Garbage Collector to do its job.
The Java creators created the concept of the applet. A Java program can be run in a client browser program. Java was released in 1995; the time when the Internet was becoming more available and familiar to the general public. The promise of Java was in the client browser-side in that code would be downloaded and executed as a Java applet in the client browser program.
- See also Java Programming/Applets.
Forbidden bad practices
Over the years, some features in C/C++ programming became abused by the programmers. Although the language allows it, it was known as bad practices. So the creators of Java have excluded them from the language:
- Operator overloading
- Multiple inheritance
- Friend classes (access another object's private members)
- Restrictions of explicit type casting (related to memory management)
In most people's opinions, Java technology delivers reasonably well on all these goals. The language is not, however, without drawbacks. Java tends to be more high-level than similar languages (such as C++), which means that the Java language lacks features such as hardware-specific data types, low-level pointers to arbitrary memory addresses, or programming methods like operator overloading. Although these features are frequently abused or misused by programmers, they are also powerful tools. However, Java technology includes Java Native Interface (JNI), a way to call native code from Java language code. With JNI, it is still possible to use some of these features.
Some programmers also complain about Java's lack of multiple inheritance, a powerful feature of several other object-oriented languages, such as C++. The Java language separates inheritance of type and implementation, allowing inheritance of multiple type definitions through interfaces, but only single inheritance of type implementation via class hierarchies. This allows most of the benefits of multiple inheritance while avoiding many of its dangers. In addition, through the use of concrete classes, abstract classes, as well as interfaces, a Java language programmer has the option of choosing full, partial, or zero implementation for the object type they define, thus ensuring maximum flexibility in application design.
There are some who believe that for certain projects, object orientation makes work harder instead of easier. This particular complaint is not unique to the Java language but applies to other object-oriented languages as well.