Java

Java is a strongly typed interpreted programming language created by James Gosling. The first publicly available version of Java (Java 1.0) appeared in 1995, and since then it's been the mainstay of enterprise and web programming workloads. In 2006, Sun Microsystems started to make Java available under the GNU General Public License (GPL) and Oracle (which bought out Sun) continues this project under the name of OpenJDK.

There are a few reasons for Java's success:

  • Platform independence: Java source code is compiled (transformed) into bytecode, which is a target-independent representation of the program. The bytecode consists of instructions that will be interpreted by the Java virtual machine (JVM)JVM is an integral part of the Java platform. This mechanism makes programs very portablepackaged bytecode (JAR) can run unmodified on any platform where there is a supported JVM.
  • Object orientation: Java provides programming constructs to enable object-oriented programming, including classes, inheritance, polymorphism, and generics. There is also support for advanced paradigms such as aspect-oriented programming.
  • Automatic memory management: Unlike the other prevalent languages at that time (such as C++), the Java programmer does not need to manage dynamic memory manually. The JVM tracked memory allocation and freed objects that had no active pointers to them. This part of the JVM, called the garbage collector, has gone through multiple iterations to make dynamic memory allocation robust and minimally invasive.
  • Ecosystem: The Java ecosystem has evolved to have incredible libraries that solve almost any generic problem you might have, including things such as database drivers, data structures, and threading support. There are sophisticated dependency management tools too—the most popular one being Maven. It is essentially a build frameworkbut not only does it describe how the Java program is to be built, it also lists its dependencies on other external modules and components. Besides the names of the dependencies, the versions are also mentioned. Maven then dynamically downloads Java libraries for these dependencies from one or more repositories, such as the Maven Central Repository, and stores them in a local cache. In case you are building a reusable library of your own, the Maven local can also be updated with JARs of such local projects. All this enables a comprehensive ecosystem of reusable components and dependency management.
  • Application frameworks: The Java ecosystem has pioneered the frameworks around Inversion of Control (IoC) and Dependency Injection (DI). These patterns allow the wiring of code dependencies without explicitly mentioning or instantiating specific implementations.

For example, consider the following snippet of code:

public class ContactsController {   
   
    private ICache contactCache;   
   
    public ContactsController() {   
        this.contactCache = new   RedisCache();   
    }   
}   

Here, ContactsController is an API provider for contacts, and to do its job, it uses a cache for the most frequently accessed contacts. The constructor explicitly instances a RedisCache() implementation for the theCache interface. This unfortunately defeats the purpose of the ICache abstraction since now ContactsController is coupled with a specific implementation.

One way to avoid this is for the client that instantiates the ContactsController class to provide the cache implementation:

public class ContactsController {   
   
    private ICache contactCache;   
   
    public ContactsController(ICache   theCache) {   
        this.contactCache = theCache;   
    }   
}   

At first glance, this solves the coupling problem. But, in reality, this transfers the burden to the client. In many cases, the client really does not care what type of cache you use.

The IOC/DI pattern allows the application framework to inject these dependencies, rather than the preceding two options. Spring is a popular example of such a Java application framework, and include, besides the DI, and other features such as transactions support, persistence frameworks, and messaging abstractions. The preceding code can be succinctly represented in Spring as follows:

public class ContactsController {   
   
    @Autowired   
    private ICache contactCache;   
   
    public ContactsController() {   
          
    }   
}   

Such powerful frameworks make writing large-scale code easy. Here, the @Autowired Spring annotation searches for a bean (object instance) that matches the definition and injects its reference into the object of ContactsController (which itself should be maintained by Spring).

  • Performance: The JVM continues to be the subject of multiple optimizations to enable high performance. For example, it contains a Hotspot, just-in-time (JIT) compiler that translates frequently executed/performance-critical bytecode instructions into native code instructions. This avoids the slower interpretation that otherwise would have ensued.
  • Awesome IDE support, including the open source Eclipse project and the freemium IntelliJ.

These types of features have made Java one of the predominant languages not just in backend systems but also in applications (Android).

One question you might have is regarding the name of the IoC pattern. You're probably wondering, what exactly are we inverting?

To answer the history behind the name, consider the early UI applications. They had a main program that initialized a bunch of components that drove individual UI elements. The main program would drive the whole program, plumbing data and control between the components. This was quite cumbersome coding. After a few years, UI frameworks were developed that encapsulated the main function, and you as an application developer provided event handlers for various UI elements.

This increased the productivity of developer as they were now focused on the business logic rather than coding the plumbing. Essentially, the program control went from the code that the developer wrote to the framework. This came to be known as IoC.

As with all good things in life, there is a flip side (although some points might be controversial). There are a few quirks with the Java ecosystem:

  • Verbosity: Java code tends to be very verbose. Recent inclusions in the language specification, such as lambdas and related functional programming primitives, are targeted to alleviate this. But still Java code tends to be very verbose.
  • Opaqueness: Powerful frameworks sometimes abstract out key aspects. For example, consider the new, shiny parallel stream feature in Java. It is one of the features that were added to reduce verbosity. But one gotcha here is that all parallel streams in the program use the same thread pool (ForkJoinPool.commonPool). This default causes a scalability bottleneck as all the parallel streams in the code contend for threads from the same pool!

There is a trick to solve this by defining a custom thread pool, like so:

final List<Integer>   input = Arrays.asList(1,2,3,4,5);   
   
            ForkJoinPool newForkJoinPool   = new ForkJoinPool(5);   
            Thread t2 = new Thread(() ->   newForkJoinPool.submit(() -> {   
                input.parallelStream().forEach(n   -> {   
                    try {   
                        Thread.sleep(5);   
                          System.out.println("In  : " + Thread.currentThread());   
                    } catch   (InterruptedException e) {   
                    }   
                });   
            }).invoke()); 
  • Complexity/Over-Engineering: Java's powerful constructs, including generics, can sometimes lead to cute code, which is unmanageable in the long run. This includes the definition of tall inheritance hierarchies that, besides making code difficult to read/understand, also make the system brittle due to the fragile base class problem. Frameworks such as Spring have become so complex that now the only way to use them reliably is to have a framework-over-the-framework such as Spring Boot (which gives a set of working libraries with sensible defaults for various tuneables). This creates a new set of threads to run the data computation of the stream, but interestingly the common-thread pool is also used along with the new thread pool.
  • Deployment Challenges: The amount of resources needed by a Java process is not always clear. For example, Java developers define the Java heap size using the -Xmx and -Xms options (where mx is the maximum size of the heap and ms is the initial size). This heap is used for the allocation of objects within your Java code. Besides this, there is another heap that the Java process uses! Called the Native heap, this is the heap used by the JVM for its needs, which includes things such as JIT and NIO. So you need to carefully profile an application in a specific deployment context (basically dampening the platform-independence feature).
  • Developer Productivity: Because of the verbose nature of Java and the need to deploy/test (the compiler rarely catches interesting bugs), a lot of developer time is spent orchestrating/waiting for the dev/test cycle. Frameworks such as Spring Boot have made things better, but still the productivity in no way in the league of Python or Go. You need active IDE support to have a chance of being productive.

The primary reasons people want to switch from Java to Go are resource efficiency, concurrent modeling, and developer productivity. There are many Java purists that still say Java is for web applications and Go is for system software, but in the world of microservices and API-driven development, this distinction is quickly disappearing.

As with Python, the journey from Java to Go will not be without its gotchas:

  • Pointers: Java programmers generally are not used to dealing with pointers. They often make mistakes when using themfor example when deciding to passed a variable by reference or by value (in Java, everything is pass by reference).
  • Error handling: Similar to Python, error-handling in Go and the lack of try-catch-finally can make the program verbose. It may also lead to the program working on the wrong data if errors are not caught for every function in a disciplined way.
  • Lack of generics: Java programmers are so used to generics (at least using them, if not defining them) that it takes time to think in the Go way of doing things. There will be some time for which developers would feel inhibited.
  • Lack of Spring-like IoC frameworks: Developers in Go typically define a main function that initializes and kicks off the computation in the program. You can engineer the distributed initialization using the init() function, but this generally leads to race conditions in big programs, so invariably you fall back to coding the main driver program.
  • new and make: Though Golang touts simplicity/brevity, sometimes there are quirks. For example, the built-in new() function can be used to allocate zeroed storage of a type (new is common keyword in many languages). Go also has the make() function, which is a special built-in allocation function that is used to initialize slices, maps, and channels. A common, and deadly, mistake is to use new instead of make for something, such as a map, which later manifests as a panic when trying to access the uninitialized map.
  • Performance: While Go is native and Java is mostly interpreted, there is still not a sizeable gap in the performance of the programs in both languages yet. This is because of the 20+ years of performance-engineering that has gone into the JVM and techniques such as the JIT compilation.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset