upGrad KnowledgeHut SkillFest Sale!

Java Interview Questions and Answers

Java is a high-level programming language used for creating web applications. It is widely used in a variety of other applications, from mobile applications to desktop software and enterprise applications. It is also commonly used in the development of Android mobile apps. Java's popularity is due to its reliability, scalability, security features, and the large community of developers and extensive libraries available for the language. Our set of Java interview questions and answers will help you prepare your interview from beginner to advanced-level. The questions are from various topics like Java syntax and data types, exception handling, multithreading, Java frameworks and libraries, best practices, and coding conventions. With these Java interview questions, you can appear for the interview more confidently.

  • 4.6 Rating
  • 239 Question(s)
  • 90 Mins of Read
  • 5248 Reader(s)

Beginner

Java supports programming in the Object-Oriented paradigm, but it is not fully object-oriented. Java has a set of primitive data types - byte, short, char, int, long, float, double, boolean. Any variable of this type is not an object. That’s why Java is not purely an object-oriented. 

This is a frequently asked question in Java interview questions and answers.

The wrapper classes wrap the primitive data types to introduce them as objects. The primitive values are not objects, and the developer needs to write many boilerplate codes to convert them to each other and use them in collections. To overcome these problems, Java introduced wrapper classes. These classes provide with polymorphic APIs for data type conversions and the utility methods like hashCode() and equals(). These make the values very useful members in the object-oriented environment. 

Polymorphism is a property of the object-oriented programming paradigm, which denotes that an object or a method can have different forms in different contexts. We can define a method in a class with different implementations based on its arguments in Java. In this way, when the client code calls the method using the same interface with a different set of parameters, internally, it decides which implementation needs to be invoked. Let’s take an example: 

class AreaCalculator { 
  double calculate(Circle c)
     { return 3.14*c.getRadius()*c.getRadius(); 
     } 
double calculate(Square s) 
     {  return s.getLength()*s.getLength();
     } 

//Client code 
AreaCalculator ac = new AreaCalculator(); 
ac.calculate(new Circle(10)); 
ac.calculate(new Square(5)); 

As you see, in the AreaCalculator class, there are separate implementations for calculate(), but from the client’s point of view, the interface is the same. 

There are several ways we can create an object in java. 

1. A new object can be created using the new operator on the class calling one of 

its constructors. MyClass o = new MyClass(); 

2. We can create an object using the Java reflection API - Class.newInstance() if the class has a default constructor. If the class has multiple constructors that take parameters, we can get the corresponding constructor using Class.getConstructor() method and invoke that to create a new object. MyClass o = MyClass.class.newInstance(); MyClass o = MyClass.class 

.getConstructor(int.class) .newInstance(10);  

3. We can invoke clone method on an object to create a duplicate object. 

MyClass o = new MyClass(); MyClass b = (MyClass)o.clone(); 

4. If a state of that object is available in a serialized form, we can deserialize it to 

create a new object having the same state. ObjectInputStream is = new ObjectInputStream(anIStream); MyClass o = (MyClass) is.readObject(); 

Polymorphism or static polymorphism decides which method needs to be invoked at the time of compilation and bind the method invocation with the call. However, there are some situations where the static binding doesn’t work. As we know, a parent class reference can point to a parent object as well as a child object. Now, if there is a method which exists in both the parent class and the child class with the same signature and we invoke the method from parent class reference, the compiler cannot decide which method to bind with the call. This will depend on which type of object the reference is pointing to at the time of running. If the reference is pointing to a parent object, then the method in the parent class will be invoked. If the pointed object is an instance of the Child class, then the child-class implementation is invoked. That’s why this is called dynamic binding or runtime polymorphism and it is said that the child class method has overridden the parent class method. Let’s take an example of this: class Animal { 

public void makeSound() { 
System.out.println(“My sound varies based on my type”); } } class Dog extends Animal { 
@Override public void makeSound() { 
System.out.println(“I bark”); 
} } Animal a = new Animal(); a.makeSound(); a = new Dog(); a.makeSound(); 

If we run the code snippet, we’ll find that a.makeSound() printing different messages based on the object-type pointed by reference. Please note that the Override annotation is required in the child class to notify the compiler that this method is overriding the parent implementation. 

StringBuffer and StringBuilder expose the same kind of APIs to build a String and both are mutable classes. There is a big difference in them, though. StringBuffer is thread-safe which means it can be used as a shared object among multiple threads. On the other hand, StringBuilder is not thread-safe and should not be allowed to be modified by multiple threads without proper synchronization techniques. That’s why StringBuilder is faster than StringBuffer. In the scenario where we need to build a String object local to a method or local to a particular thread, we should prefer StringBuilder over StringBuffer. 

A class can inherit from both an abstract class and an interface, but there are some differences. A class can extend only one abstract class while can implement multiple interfaces. An interface cannot have a constructor whereas the abstract class can have constructors which the child classes need to invoke in their constructors. An abstract class may contain fields which may be accessible by child classes to change its state. Interface, on the other hand, can contain only final variables. So, abstract class lets the child class to inherit the state and behavior while the interface is mainly used for implementing a set of behaviors in a child class. An abstract class should be preferred where there is a direct IS-A relationship between the parent and the child. We use abstract class when the different implementations have most of the behaviors common and defined by the abstract class. An interface is preferred while exposing a public API to the client code and if a class can behave differently in different context. 

Java doesn’t support multiple inheritance completely as a class can extend only one class. This is not supported as this can cause ambiguity in accessing inherited fields or methods if the same member exists in the other parent class. However, the facility is provided partially through the interfaces. 

It's no surprise that this one pops up often in Java interview questions for freshers.

We declare a member as static if it doesn’t depend on any instance of the class, i.e., independent of any objects. These members are bound to the type and are usually accessed using the type name (rather than the object references). Static methods and fields are shared between all the objects of a class and can be accessed from any of them, whereas we cannot access the non-static members from a static method. As static methods are not bound to an object, they cannot be overridden. Static fields are initialized through a static block which is executed when the class loader loads a class. Let’s see an example: 

public class Countable { 
private static int count; private int x; static { 
count = 0; // initialize static member } public Countable(int x) { 
this.x = x; Count++; // non-static can access static member } public int getX() { return x; } public static int getCount() { 
return count; // only static can access static member } } Countable c1 = new Countable(10); 
Countable c2 = new Countable(20); System.out.println("Object count " + Countable.getCount()); 
// should print 2 

ArrayList and LinkedList both represent a list of numbers but differ in their internal implementation. ArrayList uses an array internally to store the elements added to it. When the number of elements is about to exceed the size of the array, it allocates a new array and copies the elements to the new location. It gives constant time access to add (if it doesn’t need to expand) and get an element, but for deletion, it gives linear time complexity as it needs to shift its elements to the left. LinkedList, on the other hand, maintains a sequence of linked nodes internally for storing the elements. So, retrieves an element in linear time whereas addition and deletion take a constant time to execute.

HashTable and HashMap both store key-value pairs and take a constant time to put or get operations. However, Hashtable is synchronized and can be shared to get modified by multiple threads while HashMap is not synchronized and performs better, but not suitable for the multithreaded environment as a shared object. HashTable doesn’t allow Null keys or values, bur a HashMap allows a Null key and more than one Null values.

For retrieval or storing a value, a HashMap uses two methods of its Key class - hashCode() and equals(). HashMap stores its entries in a large collection of buckets which can be randomly accessed using an index. To retrieve a value, first, the hashCode() method of the Key is invoked to get the hash value. This hash value is used to identify the bucket where the value would be retrieved from. While storing an entry, there might be some scenario where the calculated hash value is the same for more than one keys. This results in to enter multiple key-value pairs in the same bucket. 

A bucket keeps its entries as a Linked List. So, while retrieving, after finding out the appropriate bucket, this linked list needs to be traversed to find the actual entry for the key. This time, the equals() method is used to compare the key of each entry in the list. Once it finds a key equal, the value from the entry is returned. There is a contract between these two methods which says if two objects are equal based on equals() method, their hashCode() value must be the same. So, if we plan to use objects of a class as the key of a HashMap, we should override both the methods - hashCode() and equals() so that this contract is maintained. 

The public static void main(String args[]) is the main method of the Java Program. It is the entry point of any Java Program. A Java program cannot be executed without the main method.

The syntax of the main method is as follows:

public static void main(String args[])

Only the args can be changed to a different name but the rest of the method carries the same syntax.

Let us analyze the main method by breaking it up:

  • public: This is the access specifier/access modifier of the main method. It should be public to allow the class to access it. In case of violation of the same, the program will show an error.
  • static: At the start of the runtime, the class is devoid of any object. Thus, the main method needs to be static so that the Java Virtual Machine can set the class to the memory and call the main method. If the main method is not static then the JVM won’t be able to call it due to the absence of an object.
  • void: It is the return type of the main method. Each and every method in Java must have a return type. The main method doesn’t return any value that can be accessed by the other methods as the main method can’t be called in any other method. As soon as the main method finishes its execution the program terminates. Hence it wouldn’t be of any use to return a value.
  • main: It is the name of the main method. This name is already set and can’t be changed. If any changes are made to the main keyword then the compiler tells that the main method couldn’t be found.
  • String args[] :  The main method can accept a single argument which is of the String type. They are also called command line arguments. They can be used and manipulated as normal arguments in the code written in the main method. They are written along with the execution statement.

Wrapper classes are predefined classes in Java whose objects have primitive data types. They convert primitive data types into objects and vice versa. The Wrapper Classes provide a new angle to Java which helps it put a strong foot forward against its contemporaries. Data Structures in a collection framework can store only objects and not primitive data types. They provide synchronization during multithreading. They are defined in the java.lang package.

They are converted to primitive data types and vice versa by the process of boxing and unboxing.

Typically, there are eight wrapper classes. They are linked to the primitive data types as follows:

Primitive Data typesWrapper Classes
 byte Byte
shortShort
intInteger
longLong
charCharacter
boolBoolean
floatFloat
doubleDouble

Now let us discuss about the wrapper classes:

  • Byte: It wraps a primitive byte data type in an object. Its object contains a single field of type byte.
  • Character: It wraps a char primitive data type in an object. An object of type Character contains a single field, whose type is char.
  • Short: The short class wraps a primitive short type value in an object. Its object contains only a single field whose type is short.
  • Integer: The Integer class wraps a primitive int type value in an object. Its object contains only a single field whose type is int.
  • Long:  The Long class generally wraps the primitive data type long into an object. An object of Long class contains a field with the type Long.
  • Float: The Float class wraps a primitive float type value in an object. Its object contains only a single field whose type is float.
  • Double: The Double class wraps a primitive double type value in an object. Its object contains only a single field whose type is double. This class is useful in providing various methods like a method which can be used to convert a double to a String and vice versa.
  • Boolean: It wraps a primitive boolean data type ‘bool’ in an object. Its object contains only a single parameter which is either true or false.

Local Variable in Java is a variable declared in a method body or a constructor or a block (for example for loop) and its scope lies within the method or constructor or the block.

The scope of the local variables starts from its declaration and ends when the block or method body or the constructor ends by the closing curly brace.

Access specifiers like public, private, protected can’t be used for declaring local variables.

They are implemented at the stack level internally.

Instance Variables in Java is a data member of a class and is defined in a class.

Each object can create its own copy of the instance variable and access it separately.

The instance variables are visible for all methods, constructors and block in the class. They can be accessed directly by calling the variable name inside the class. Any changes made to the instance variables in the methods reflect in the state or value of the variable outside the scope of the method for a particular object.

The scope of the instance variable is created and destroyed when the objects are created and destroyed respectively.

Default values are given to instance variables. The default value is 0 for integers, floats, doubles and bytes, it is false for Boolean and null for object references.

They cannot be declared as static otherwise they’ll be classified as static variables.

If instance and local variables have the same name then ‘this’ keyword is used to differentiate among them.

An example program to illustrate the use of Local as well as Instance variables is given as follows.

public class Example
{
   int a=5; // a is an instance variable
   public void add(int n)
   {
       int sum = a + n; // sum is a local variable
       System.out.println(sum);  //accessing local variable
   }
   public static void main(String[] args)
   {
       int k=11;
       Example obj= new Example(); // creating object for Example class
       System.out.println(obj.a); // accessing instance variable
       obj.add(k);
   }
}

The given program produces the following output:

$javac Example.java
$java Example
5
16

Here, a is the instance variable while sum is the local variable.

Abstract class and Interface both are used for abstraction which is hiding the background details and representing only the essential features. But there are major differences between abstract classes and interfaces. These are given as follows:

Parameter
Abstract Classes
 Interfaces
Instance Variables
Can have instance variables
Cannot have instance variables
Visibility
Can have any visibility:public, private or protected
Have either public visibility or no visibility
Nature of methods
Can have both abstract as well as non-abstract methods
Only have abstract methods
Constructors
Can have constructors
Cannot have constructors
Association
Can be extended by using keywords ‘extends’
Can be implemented by using keyword ‘implements’
Nature of Association by Java class
A Java class can extend only one abstract class
A Java class can implement multiple interfaces
Provision of code
Can provide complete code
Just provide the signature/prototype

WAR stands for Web Application Resource or Web Application Archive. It finds its application in distribution of a collection of JAR(Java Archive) Files, Java Server Pages, Java Servlets, Java Classes, XML files, tag libraries, static web pages, and other resources that constitute a web Application.

WAR files have the file extension .war. These are extended from JAR files.

So, a .war is a .jar, but it contains web application components and is laid out according to a specific structure. A .war is designed to be deployed to a web application server such as Tomcat or Jetty or a Java EE server such as JBoss or Glassfish.

The primary advantage of a .war file is it combines all files into a single unit which reduces the transfer time from client to server.

To create a war file, we need to use the jar tool of the Java Development Kit. Go inside the project directory of your project, then write the following command:

jar -cvf yourproject.war *

The -c switch is used to create file, -v is used to generate the verbose output and -f switch is used to specify the archive name of the file.

During Java runtime, verbose options can be used to tell the JVM which kind of information to see. JVM supports three verbose options out of the box. As the name suggests, verbose is for displaying the work done by JVM.

In Method Overloading, the names of the methods in the same class are same but the arguments (type and/or number) are different.
In Method Overriding, the names and arguments of the methods are same but one of the methods is in the super class while the other is in the sub class.

Basis
Method Overloading
Method Overriding
Arguments
The methods in overloading have different arguments.
The methods in overriding have similar arguments.
Polymorphism
Overloading is a part of compile time polymorphism
Overriding is a part of run time polymorphism.
Occurrence
Happens at compile time.
The binding of the overloaded function calls it definition during compile time.
Happens at runtime. The binding of the overridden function calls its definition during runtime.
Static Methods
Static Methods can be overloaded.
Static methods cannot be overridden.
Class
Overloading is done in the same class
Overriding is done in the parent and child class.
Type of binding
Static binding is used for overloaded methods
Dynamic binding is used for overridden methods
Performance
Method Overloading gives better performance as the binding is done during compile time.
Method Overriding gives less performance as the binding is done during runtime.
private methods
private methods can be overloaded.
private methods cannot be overridden.
final methods
final methods can be overloaded
final methods cannot be overridden.
Application
Method overloading is used to increase readability of the program
Method overriding is used to provide specific
implementation of the method
Return type
Return type of the methods does not matter during method overloading.
In method overriding, the method should have a specific return type.


Expect to come across this popular question in Java basic interview questions.

Custom Exceptions are nothing but user-defined exceptions. Java custom exceptions are used to make and modify the exception according to the requirements of the user.

Before creating a custom exception, let us look at its prerequisites

  • All exceptions that are created must be a subclass of the Throwable class
  • Use the extends keyword and extend the Exception class
  • If you want to write a runtime exception, you need to extend the RuntimeException class.

Let us now create a parent custom exception which generates an exception if a number is not divisible by 3:

class CustomException extends Exception
{  
 CustomException(String errormsg)
 {
   super(errormsg);  
 }
}  
public class Example
{  
  static void validate(int num)throws CustomException
 {
    if(num%3!=0)  
     throw new CustomException("Not divisible by 3");  
    else
     System.out.println("Divisible by 3");  
  }
  public static void main(String args[])
  {
     try
     {
     validate(10);  
     }
     catch(Exception e)
     {
      System.out.println("Exception caught: "+e);
     }
     System.out.println("Outside the try-catch block");  
 }
}  

The output for the above program is as follows:

$javac Example.java
$java Example
Exception caught: CustomException: Not divisible by 3
Outside the try-catch block

A switch statement is a decision making statement in Java. It is used to check a variable’s equality against a list of values and their subsequent statements. It is a multiway branch statement.

Each condition is called a case and the variable is checked for each case.

Syntax for switch case:

switch(expression)
{
  case value1 :
     // Statements
     break; // optional
 
  case value2 :
     // Statements
     break; // optional
      .
      .
      .
      .
   default : // This is the default switch case
     // Statements
}

The default statement is optional, and can appear anywhere inside the switch block.

If there is no match with a constant expression, the statement associated with the default keyword is executed. If the default keyword is not used, control passes to the statement following the switch block.

Errors in Java are a part of the java.lang.error class. Exceptions in Java a part of the java.lang.Exception class. Both are a part of the java.lang.Throwable class.

Basis
Errors
Exceptions
Type
Errors in Java are of the unchecked type.
Exceptions in Java are both of the checked and unchecked type.
Occurrence
Errors occur at the run time and are oblivious to knowledge of the compiler.
Exceptions also occur at run time but checked exceptions are known to the compiler.
Class
They’re defined in java.lang.Error class
They’re a part of the java.lang.Exception class
Dependency
They are caused by the environment in which the compiler is run.
The program is solely responsible for generation of exceptions.
Recovery
Recovery from an error is impossible.
Recovery from exceptions is possible through try-catch blocks.

The a=a+b statement has an assignment operator = and a arithmetic operator + while a+=b has a arithmetic assignment operator +=. Apart from this there is only a slight difference between the two.

For similar integer or byte or float data types both the expressions would compile.

But if one value(a) is byte and the other(b) is int, a=a+b will not compile as byte+int is not byte

On the other hand, a+=b will compile as the arithmetic assignment operator will do an implicit type casting.

Take for example,

public class Example
{
   public static void main(String []args)
   {
       byte a=2;
       int b=3;
       a=a+b; // generates an error as byte+int=int
       System.out.println(a);
   }
}

The above program generates an error

$javac Example.java
Example.java:7: error: incompatible types: possible lossy conversion from int to byte
       a=a+b;
          ^
1 error

But if we take a+=b

public class Example
{
   public static void main(String []args)
   {
       byte a=2;
       int b=3;
       a+=b; // compiles
       System.out.println(a);
   }
}

This code is error free and will generate the following output:

$javac Example.java
$java Example
5

The transient keyword in Java is a variable modifier. It finds its application in serializiation. During serialization, if we don’t want to write the state of the particular variable in the byte stream, we use the transient keyword. When the JVM comes up to the transient keyword, it ignores the original state of the variable and stores a default value of that data type i.e. 0 for int, 0 for byte, 0.0 for float,etc.

As static variables are also ignored by the JVM, there is no use of writing transient with them though this won’t generate an error.

As final variables are directly serialized by their values, there is no use of making them transient although this would not give a compile time error.

The transient keyword is useful for security and data hiding. It is a good practice to use the transient keyword with private access specification.

An example program where the effect of the transient keyword during serialization and deserialization is shown below:

import java.io.*;
public class Example implements Serializable
{
   int a = 1, b = 2;      // instance variables
   transient int c = 3;   // transient variable
   // transient is rendered affectless with static and final
   transient final int d = 4;
   transient static int e = 5;
   public static void main(String[] args) throws Exception
   {
       Example input = new Example();
       // serialization
       FileOutputStream fo = new FileOutputStream("example_file.txt");
       ObjectOutputStream obj = new ObjectOutputStream(fo);
       obj.writeObject(input);
       // de-serialization
       FileInputStream fi = new FileInputStream("example_file.txt");
       ObjectInputStream o = new ObjectInputStream(fi);
       Example x = (Example)o.readObject();
       System.out.println("a = " + x.a);
       System.out.println("b = " + x.b);
       System.out.println("c = " + x.c);
       System.out.println("d = " + x.d);   
       System.out.println("e = " + x.e);
   }
}

The output of the program is:

$javac Example.java
$java Example
a = 1
b = 2
c = 0
d = 4
e = 5

We can notice that the transient variable c has been set to its default value i.e. 0 while there is no change in the final, static and the instance variables.

The final keyword is a type of modifier in Java. It is of the non-access type. It can only be applied to a variable, method or a class.

Class that are final cannot be extended. Methods that are final cannot be used for method overriding in child class.

The final variables have constant value which are not changeable throughout the program. This means they need to be initialized along with their declaration, otherwise they would be blank final variables, which can be later initialized only once. If a final variable is used as a reference to an object, it cannot be used as a reference to another object.


1. If we try to change the value of the final variable, it will generate an error:

 public class Example  
{ 
    static final int x=10; 
      
    public static void main(String args[]) 
    { 
         x = 5; // re-assignment of final variable
         System.out.println(x);
    } 
}

This program will generate a compile time error

$javac Example.java
Example.java:9: error: cannot assign a value to final variable x
         x= 5; 
         ^
1 error

2. If we try to inherit a final class in a subclass, the compiler will show an error.

final class Superclass
{
static int x=5;
}
public class Example extends Superclass
{  
   public static void main(String []args)
   {
    System.out.println(x);
   }
}

The error message is as follows:

$javac Example.java
Example.java:5: error: cannot inherit from final Superclass
public class Example extends Superclass
                             ^
1 error

3. If we try to override a final class, it will generate an error:

class Superclass
{
 int x=5;
 final void add()
{
    int j=x+5;
    System.out.println(j);
}
}
public class Example extends Superclass
{  
   void add()
   {
       int a=5,b=10;
       System.out.println(a+b);
   }
   public static void main(String []args)
   {
    Example e=new Example();
    e.add();
   }
}

The error generated is as follows

$javac Example.java
Example.java:12: error: add() in Example cannot override add() in Superclass
   void add()
        ^
 overridden method is final
1 error

The public static void main(String args[]) is the main method of the Java Program. It is the most important Java Method. It is the entry point of any Java Program. A Java Program cannot be executed without the main method.

The static keyword acts as an access modifier. When the Java Virtual Machine calls out to the main method, it has no object to call to. Hence, we use static to permit its call from the class.

If we do not use static in public static void main(String args[]), then the compiler will give an error message.

public class Example
{
   public void main(String args[])
   {
       System.out.println("Hello");
   }
}

The output window is as follows

$javac Example.java
$java Example
Error: Main method is not static in class Example, please define the main method as:
  public static void main(String[] args)

During compile time, the Java Compiler converts the Source Code into the ByteCode.

ByteCode is a highly developed set of instructions given to the Java Virtual Machine to generate the machine code. It is the machine code in the form of the .class file. Analogies can be made to the assembler in C++. It is called ByteCode because each instruction is of 1-2 bytes.

One of the main features in Java which distinguishes it from other Object-Oriented Languages is that it is Platform Independent. This Platform Independence is achieved through the Java ByteCode.

The components required to execute the bytecode are provided by the Java Virtual Machine, which invocates the processor to allot the needed resources. JVMs are based on stacks so they implement stacks to read and process the bytecode.

Some ByteCode instructions are as follows:

1: istore_1
2: iload_1
3: sipush 1000
6: if_icmpge 44
9: iconst_2
10: istore_2

The Code Segment is the memory segment that holds the ByteCode.

The following is the difference:

BasisConstructorMethod
Return typeConstructors in Java have no return type, not even void.Methods in Java always return a value or they are void
NomenclatureConstructors have same name as the classMethods have any other legal name apart from the class name
Relation with ObjectsConstructors are used to initialize the state of the objectMethods are used to show the behavior of the object
OverridingConstructor overriding is not possible in JavaMethod Overriding is possible in Java
StaticConstructors can never be declared as staticMethods can be declared as static
InvocationConstructors are only invoked when an object is created using the ‘new’ keywordMethods are invoked by a class name,their own name or by an object.
ExecutionConstructors will be executed only one time per objectMethods can be executed a number of time per object

An example program to show the use of constructor and method is as follows:

public class Example
{
   Example()
   {
       System.out.println("You are in a constructor");
   }
   public void print()
   {
       System.out.println("You are in a method");
   }
   public static void main(String []args)
   {
       Example e = new Example();
       e.print();
   }
}

The output for the following is as follows:

$javac Example.java
$java Example
You are in a constructor
You are in a method

The following is the comparison:

BasisCompile-time PolymorphismRun-time Polymorphism
Definition

Alternate nameCompile-time Polymorphism is also known as Static PolymorphismRuntime Polymorphism is also known as Dynamic Polymorphism
OccurrenceIt occurs during compile timeIt occurs during runtime
ImplementationIt is implemented through Method OverloadingIt is implemented through Method Overriding
SpeedMethod Execution is quickerMethod Execution is slower
FeatureIt increases the readability of the programIt provides specific implementation to the program

An example for the implementation of compile time polymorphism using method overloading is given below:

class Overload
{
   public void display(char c)
   {
        System.out.println(c);
   }
   public void display(char c, int num)  
   {
        System.out.println(c + " "+num);
   }
}
public class Example
{
  public static void main(String args[])
  {
      Overload obj = new Overload();
      obj.display('s');
      obj.display('s',12);
  }
}

The output of the above program is:

$javac Example.java
$java Example
s
s 12

An example for the implementation of run-time polymorphism using method overriding is given as follows:

class SuperClass
{
   void display() { System.out.println("SuperClass"); }
}
class SubClass extends SuperClass
{
   void display()
   {
       System.out.println("Subclass");
   }
}
public class Example
{
   public static void main(String[] args)
   {
       SuperClass obj1 = new SuperClass();
       obj1.display();
       SuperClass obj2 = new SubClass();
       obj2.display();
   }
}

The output is as follows:

$javac Example.java
$java Example
SuperClass
Subclass

String Pool in Java is a pool or collection of Strings stored in the Java Heap Memory. When initialize a String using double quotes, it first searches for String with same value in the String pool. If it matches, it just returns the reference otherwise, it generates a new String in the String pool and then it returns its reference.

The possibility of the String Pool exists only due to the immutability of the String in Java.

In case of the creation of String using the new operator, the String class is entitled to create a new String in the String Pool.

For example,

public class Example
{
   public static void main(String[] args)
   {
       String s1 = "Hello";
       String s2 = "Hello";
       String s3 = new String("Hello");
       System.out.println("Do s1 and s2 have the same address? "+ (s1==s2));
       System.out.println("Do s1 and s2 have the same address? "+ (s1==s3));
   }
}

The output will show that s1 and s2 have the same address due to implementation of String Pool. String s3 though will have a different address due to the use of the new keyword.

$javac Example.java
$java Example
Do s1 and s2 have the same address? true
Do s1 and s2 have the same address? false

No, the finally block will not execute. The System.exit() method is a predefined method of the java.lang package. It exits the current program by termination of the running of Java Virtual Machine.

The System.exit() method has a status code represented by an integer value. Usually the status code is 0. A non-zero status code means unusual termination of the Java program.

The syntax for the exit method in the java.lang package is as follows is as follows

public static void exit(int status_code);

The finally block in Java usually executes everything irrespective of what is written in the try and catch block. An aberration is made when the System.exit() function is written in the try block.

For example,

public class Example
{
   public static void main(String[] args)
   {
       try
       {
        System.out.println("In try block");
        System.exit(0);
        int a=1/0;
       }
       catch(ArithmeticException e)
       {
           System.out.println("Exception Caught");
       }
       finally
       {
           System.out.println("In finally block");
       }
   }
}

The output is as follows:

$javac Example.java
$java Example
In try block

Expect to come across this popular question in Java interview questions experienced.

The constructor of a class is invoked at the time of object creation. Each time an object is created using the new keyword, the constructor gets invoked. The constructor initializes the data members of the same class.

class Example
{
 Example() // constructor
 {
  }
}
Example obj=new Example(); // invokes above constructor

A constructor is used to initialize the state of the object. It contains a list of statements that are executed at the time of creation of an object.

For example,

public class Example
{
   Example()  // constructor of class Example
   {
       System.out.println("This is a constructor");
   }
   public static void main(String []args)
   {
       Example e=new Example();
       // constructor Example() is invoked by creation of new object e        
   }
}

The output of the above is as follows:

$javac Example.java
$java Example
This is a constructor

Vectors in Java are dynamic data structures which can expand when a new element is added to them. They are synchronized and contain many methods that are not a part of the Collections framework in Java.

Vectors in Java have two methods call size() and capacity() which are in relation with the number of elements of the Vector.

The size() returns the number of elements the vector is currently holding. It increases or decreases whenever elements are inserted or deleted to/from a vector respectively.

The capacity() returns the maximum number a elements a vector can hold. Since the vector is an expandable data structure, its capacity is not fixed. We can set the initial value of the capacity.

The Vector() constructor initializes the initial capacity of the vector to be 10.

For example,

import java.util.*;          // Vector is a class of the java.util package
public class Example
{
 public static void main (String[] args)
 {
   Vector v = new Vector(); // creating new Vector object
   System.out.println("Vector Size: " + v.size());  // prints the current size of the Vector
   v.addElement(10);
   v.addElement(20);
   System.out.println("Vector Size: " + v.size());
   v.addElement(5);
   System.out.println("Vector Size: " + v.size());
   System.out.println("Vector Capacity: " + v.capacity());  //prints the capacity of the vector
 }
}

The output will be the following:

$javac Example.java
$java Example
Vector Size: 0
Vector Size: 2
Vector Size: 3
Vector Capacity: 10

The yield() method of the Thread class is used to temporarily stop the execution of the thread and carry out the execution.

For the java.lang.Thread class, the syntax for the yield method is as follows:

public static void yield()

Execution of a thread is prevented by three ways namely yield(), sleep(), join().

In certain situations where one thread is taking exceedingly more time to complete its execution, we need to find a solution to delay the execution of the thread which completes its execution quickly in between if something important is pending. The yield() provides an answer to this problem.

For example,

import java.lang.*;
class ExampleThread extends Thread
{
   public void run()
   {
       for (int i=0; i<2 ; i++)
           System.out.println(Thread.currentThread().getName() + " in control");
   }
}
public class Example
{
   public static void main(String[]args)
   {
       ExampleThread t = new ExampleThread();
       t.start();  // this calls the run() method
       for (int i=0; i<2; i++)
       {
           Thread.yield();
           System.out.println(Thread.currentThread().getName()  + " in control");
       }
   }
}

The output of the above program is:

$javac Example.java
$java Example
Thread-0 in control
Thread-0 in control
main in control
main in control

The output may differ from system to system but the probability of execution of the yield() thread is more.

A common basic Java program for interviews, don't miss this one.

Yes, try statements can be nested in Java.

A try block within a try block is called nested try block.

The syntax for a nested try block is as follows:

try  // outer try block        
{  
   statements;  
   try  // inner try block
   {
       statements;  
   }
   catch(Exception e)  // inner catch block
   {
   }
}  
catch(Exception x)  // outer catch block
{  
}

The following is an example for nested try block:

public class Example
{
    public static void main(String[] args)
    {
      int[] n={1,2,3,4,5,6};
      int[] d={1,2,0,3};
      for(int i=0;i<n.length;i++)
          {
               try
              {
                  try
                  {
                       System.out.println(n[i]+"/"+d[i]+"="+n[i]/d[i]);
                   }
                   catch(ArithmeticException e)
                   {
                       System.out.println("Division by zero not Possible");
                   }
              }
              catch(ArrayIndexOutOfBoundsException e)
              {
                      System.out.println("Array Index is out of bounds");
              }
          }
     }
}

The output is:

$javac Example.java
$java Example
1/1 = 1
2/2 = 1
Division by zero not Possible
4/3 = 1
Array Index is out of bounds
Array Index is out of bounds

JVM stands for Java Virtual Machine. It is the driving force that provides the run time environment. It converts the Java ByteCode into machine code. It is an abstract machine that provides the specification for execution of a Java program during runtime.

JVM loads the byte code and verifies it. It also runs the code and provides runtime environment.

During compile time, the Java Compiler converts the Source Code into the ByteCode. The components required to execute the bytecode are provided by the Java Virtual Machine, which invocates the processor to allot the needed resources. JVMs are based on stacks so they implement stacks to read and process the bytecode.

In other words when we compile a .java file, files with nomenclature same to that of the class are created with the extension .class by the Java Compiler. These files contain the byte code. There are various steps involved when a .class file is executed. These steps provide a detailed account of the Java Virtual Machine.

JVM Language Classes

There are quite a few differences between final, finalize and finally.

  • final is a keyword which is used to curtail the class, method or variable on which it is applied. A final class cannot be inherited by a subclass, a method declared as final cannot be overridden or a variable declared as final cannot be updated.
  • finalize is a method which is used to clean up the processing before the garbage collection in java. It is called by the garbage collector of an object when there are no further references to that object.
  • finally is a block which is a part of the try-catch-finally flow block and is used to execute code irrespective of the handling of exceptions in the try and catch blocks.

The finalize, unlike final and finally is not a reserved keyword in Java. The finalize method can be invoked explicitly which leads to it being executed as a normal method call and wouldn’t lead to destruction of the object.

Let us see a program which violates the use of the final keyword:

public class Example
{  
   public static void main(String[] args)
   {
           final int x=1;  
           x++;
    }
}

The program will generate a compile time error as a final variable can’t be updated:

$javac Example.java
Example.java:6: error: cannot assign a value to final variable x
           x++;//Compile Time Error  
           ^
1 error

 Now let us look at the use of finally keyword in Java:

public class Example
{
   public static void main(String[] args)
   {
       try
       {
        System.out.println("In try block");
        int a=1/0;
       }
       catch(ArithmeticException e)
       {
           System.out.println("Exception Caught");
       }
       finally
       {
           System.out.println("In finally block");
       }
   }
}

The program will give the following output:

$javac Example.java
$java Example
In try block
Exception Caught
In finally block

Now let us look at the overriding of finalize method in Java:

public class Example
{
   public static void main(String[] args)
   {
       String str = new String("Example");
       str = null;
       System.gc(); // prompts the JVM to call the Garbage Collector
       System.out.println("End of main method");
   }
   public void finalize()
   {
       System.out.println("This is the finalize method");
   }
}

The output is as follows:

$javac Example.java
$java Example
End of main method

Yes, we can have an empty catch block in Java. This is a bad practice though and shouldn’t be implemented.

Generally, the try block has the code which is capable of producing exceptions. Whenever something out of the blue or malicious is written in the try block, it generates an exception which is caught by the catch block. The catch block identifies(catches), handles the exceptions and usually prompts the user on what is wrong.

If the catch block is empty then you will have no idea what went wrong with your code.

Take for example, division by zero when handled by an empty catch block

public class Example
{
   public static void main(String[] args)
   {
       try
       {
           int a=4 ,b=0;
           int c=a/b;
       }
       catch(ArithmeticException e)
       {
       }
   }
}

The catch block catches the exception but doesn’t print anything. This makes the user think that there is no exception in the code.

$javac Example.java

$java Example

But when the catch block is not empty, it gives a sense of awareness to the user about the exception.

public class Example
{
   public static void main(String[] args)
   {
       try
       {
           int a=4, b=0;
           int c=a/b;
       }
       catch(ArithmeticException e)
       {
           System.out.println("Division by zero is illegal");
       }
   }
}

The output for the following is as follows

$javac Example.java
$java Example
Division by zero is illegal

A Java access specifier shows the capability to specific classes to access a given class and its components. They are also called access modifiers. They help limit the scope of the class, constructor, variables and methods.

Basically, there are four access specifiers in Java

  • Default (No keyword required)
  • Public
  • Private
  • Protected

The following table illustrates the visibility of the access specifiers

Access Specifiersdefaultprivatepublicprotected
Accessible within same classYesYesYesYes
Accessible to other classes in same packageYesNoYesYes
Accessible within the subclass inside the same packageYesNoYesYes
Accessible within the subclass outside the packageNoNoYesYes
Accessible to other non subclasses outside the packageNoNoYesNo

Let us see an example:

class Scope
{
   private int x=5;         // a private variable can only be accessed in the same class
   protected int k=10;  // a protected variable can be accessed in the subclass
   public void print()    // a public method which can be accessed anywhere
   {
       System.out.println(x);
   }
}
public class Example extends Scope
{
   public static void main(String[] args)
   {
       Scope s=new Scope();
       s.print();
       System.out.println(s.k);
   }
}

The program gives the following output:

$javac Example.java
$java Example
5
10

To check whether a string is empty or not is an easy task since Java comes with a function isEmpty () for this.

Let us see an example:

public class Example {
 public static void main(String[] args) {
   String s = "";  
   System.out.println("Is the string empty? "+s.isEmpty());  
 }
}

The output:

Is the string empty? true

Java uses the instanceOf operator to check the type of object during the time of execution of a Java program.

The instanceOf operator is used to check whether a particular object is an instance of a parent class, child class or an interface.

The instanceOf operator returns a boolean value, either true or false. It is used as a type comparison operator because it compares the object with its type. If applied to a variable having a null value, the operator will always return a false value.

For example,

public class Example
{
 public static void main(String args[])
  {
    Example obj=new Example();
    System.out.println(obj instanceof Example);
  }
}

The output is as follows:

$javac Example.java
$java Example
True

Here is another example when instanceOf operator is used against a instance having null value

public class Example
{  
public static void main(String args[])
{  
 Example obj=null;  
 System.out.println(obj instanceof Example);
}  
}  

The output is as follows

$javac Example.java
$java Example
False

The instanceOf operator is also useful in downcasting. Downcasting is the process when the child class type refers to the object of the parent class. If Downcasting is performed directly, a ClassCastException is generated. The instanceOf operator provides the means for downcasting. This can be done via typecasting:

class Parent
{
// empty class
}    
public class Example extends Parent
{  
 static void downcast(Parent p)
 {
   if(p instanceof Example)
   {
      Example d = (Example)p;
      System.out.println("Downcasting successful");  
   }
 }
 public static void main (String [] args)
 {
   Parent obj=new Example();  
   Example.downcast(obj);  
 }    
}

The output is as follows:

$javac Example.java
$java Example
Downcasting successful

The java.util.ArrayList class extends the AbstractList class. It is a part of the collection framework. The ArrayList is initialized a size which is capable of increasing or decreasing its size when objects are separated from the collection.

The java.util.Vector class represents dynamic data structures which can expand when a new element is added to them. Vectors are synchronized and contain many methods that are not a part of the Collections framework in Java.

Here are a few key differences between Vectors and ArrayList:

Basis
Vector
ArrayList
Threads
Vector thread is thread safe as only one thread is allowed to work at a time
ArrayList is not thread safe as multiple threads are allowed to work at a time
Synchronization
Every method available in Vector is Synchronized
Every method available in ArrayList is not Synchronized
Performance
Threads are required to hold-up on Vector object and hence their performance is low as compared to ArrayList.
Threads are not required to hold-up on ArrayList object and hence their performance is high as compared to Vector
Growth
Doubles its size when it grows
Grows by half of its size
Application
Multi user application
Single user application
Legacy class
Vector is a legacy class, introduced in JDK 1.0
ArrayList is a non legacy class, introduced in JDK 1.2
Traversal
Uses enumeration for traversal.
Uses Iterator/ListIterator for traversal.

Java imparts us with three ways to generate random numbers. The three ways are:

  • Using the java.util.Random class
  • Using the java.util.Math.random() method
  • Using the java.util.concurrent.ThreadLocalRandom class

The java.util.Random class

We create an object of this class and call predefined methods such as nextInt() and nextDouble() using this object.

Random numbers of several data types can be produced by this method.

Suppose If we pass an argument x to nextInt() then we would get any random integer from 0 to x-1.

For example,

import java.util.Random;
public class Example
{
     public static void main(String args[])
   {
       Random r= new Random();
       int r1 = r.nextInt(20);   // generates random integers from 0 to 19
       int r2 = r.nextInt(100); // generates random integers from 0 to 99
       System.out.println("The first random number generated is: "+r1);
       System.out.println("The second random number generated is "+r2);
   }
}

The subsequent output is as follows:

$javac Example.java
$java Example
The first random number generated is: 2
The second random number generated is 23

The Math.random() method

The Math.random() is a method of the java.util.Math class. It returns a positive double value between 0.0 (inclusive) and 1.0 (exclusive).

import java.util.*;
public class Example
{   
   public static void main(String args[])
   {
       double x=Math.random();
       System.out.println("Random number between 0.0 and 1.0 is "+x);
   }
}

The random output for the following is as follows:

$javac Example.java
$java Example
Random number between 0.0 and 1.0 is 0.7534013549366972

The ThreadLocalRandom Class

This is a relatively new feature introduced in JDK 1.7.

The ThreadLocalRandom gives random values for integers, doubles, floats and booleans.

For example,

import java.util.concurrent.ThreadLocalRandom;
public class Example
{
   public static void main(String args[])
   {
       int intVal = ThreadLocalRandom.current().nextInt();
       System.out.println("A random integer : " + intVal);
       double doubVal = ThreadLocalRandom.current().nextDouble();
       System.out.println("A random double number : "+doubVal);
   }
}

The output is as follows:

$javac Example.java
$java Example
A random integer : 1700060375
A random double number : 0.24593329857940383

Jagged arrays in Java are multidimensional arrays in which each element itself is an array of varying size as well as dimensions. Thus, they’re often referred to as an array of arrays.

Syntax for a two-dimensional jagged array for fixed rows and variable columns,

data_type array_name[][]= new data_type [array_size][];
   (OR)
data_type[][] array_name=new data_type[array_size][];

Take for example a Jagger integer array of size 4:

int arr[][] = new int[4][];

This row has 4 rows and each row has varying amount of columns which might later be specified.

We can either declare the array and initialize the elements directly:

arr[0] = new int[] {13, 42};
arr[1] = new int[] {34, 43, 95};
arr[2] = new int[] {69, 71, 83, 29};
arr[3] = new int[] {10,11};

or, we can just declare the array elements without initializing them.

arr[0] = new int[2];
arr[1] = new int[3];
arr[2] = new int[4];
arr [3] = new int[2];

For example,

public class Example
{
   public static void main(String[] args)
   {
       int arr[][] = new int[4][]; // creating a Jagged Array of 4 rows
       arr[0] = new int[]{1,2,3,4};  // the first row has 4 columns
       arr[1] = new int[]{5,6,7};  // the second row has 3 columns
       arr[2] = new int[]{10,20,30}; // the third row has 3 columns
       arr[3] = new int[]{2,4,2,4}; //  the fourth row has 4 columns
       System.out.println("The elements of the Jagged Array are...");
       for (int i=0; i<arr.length; i++)
       {
           for (int j=0; j<arr[i].length; j++)
           {
               System.out.print(arr[i][j] + " ");
           }
           System.out.println();
       }
   }
}

The output is the following:

$javac Example.java
$java Example
The elements of the Jagged Array are...
1 2 3 4
5 6 7
10 20 30
2 4 2 4

Both Iterator and Enumeration are interfaces belonging to the java.util package. They are used for traversal of collection objects.

Although Iterator and Enumeration have the same functionality of traversal, there are quite a few differences between them. Some of the differences are given below:

Basis
Iterator
Enumeration
Introduction
Iterator interface was introduced in JDK 1.2
Enumeration interface existed since JDK 1.0
Functions
Iterator can be used for traversal as well as to remove an element from a given Collection object.
Enumeration can only perform traversal through the collection object.
Nature
Iterator is fail-fast is nature. It throws a ConcurrentModificationException if any modification other than the remove() method  is done to the collection while iteration.
Enumeration is fail-safe in nature. It does not throw any exceptions during modification of the Collection Object during an iteration.
Legacy
Classes like ArrayList, HashSet and HashMap in the collection framework are traversed by Iterator
Traversal of legacy classes like Vector and Stack is done through Enumeration
Safety
Iterator is safer and more robust than Enumeration
Enumeration is more vulnerable due to its fail-safe nature.
Methods
hasNext()  
next()
remove()
hasMoreElements()
nextElement()
-

A class created in a block( method body or a loop or conditional clauses) is called as a local inner class. They are not a member of the class they are enclosed in. They rather, associate themselves to the block they are defined in. Local inner class can be declared as final or abstract. They cannot be instantiated out of their scope i.e.  the block they are created in.

We can say that they are a form of non-static nested classes.

For example,

public class Example
{
  final int y=12;
  void disp()
 {
    class Inner
    {
      void print()
       {
         System.out.println(y);
       }
    }
    Inner i=new Inner();
    i.print();
  }
 public static void main(String args[])
{
  Example obj=new Example();
  obj.disp();
 }
}

The output is as follows

$javac Example.java
$java Example
12

There are basically two types of parameter passing:

  • Pass by value
  • Pass by reference

Java supports Pass by value.

Passing by value means that when a method is called, a copy of the parameters is sent to the memory. When we are using the parameters inside the method or the block, the actual arguments aren’t used but the copy of those arguments (formal parameters) are worked with. There is no change in the actual parameters.

Passing by reference means that the changes in the parameters will be reflected in the original value . This occurs because the method receives the memory address of the parameters i.e. the address of the parameters.

What Java does is that it passes the reference of the object by value.

In Java, arguments are always passed by value irrespective of their original variable type. Each time a method is called, a copy is created in the stack memory and its version is passed to the method.

There are two situations which arise:

  • The original variable type is primitive: If the original variable type is primitive, then a copy is created in the stack memory and is further passed to the method.
  • The original variable type is non-primitive: If the original variable type is non-primitive, then a new reference is formed in the stack memory which points to the actual parameters and this new reference is sent to the method.

The HashSet class implements the Set interface, supported by a Hashtable. Due to the implementation of the Set Interface, duplicate values are not allowed. The underlying data structure for a HashSet is a Hashtable.

The TreeSet class implements the SortedSet interface, which in turn extends the Set interface. Objects are stored in a sorted ascending order. The underlying data structure for TreeSet is a tree.

Here are a few notable differences between a HashSet and a TreeSet:

Basis
HashSet
TreeSet
Performance
HashSet in Java offers faster performance than TreeSet
TreeSet in Java is slower than HashSet for most of the generic operations like add, remove and search.
Underlying data structure
HashSet has hashtable as its underlying data structure.
TreeSet has a red black tree as its underlying data structure.
Null element
HashSet does allow one null element
TreeSet doesn’t allow any null element to be present
Implementation
It is implemented by HashMap
It is implemented by TreeMap
Interface
HashSet implements the Set interface.
TreeSet implements the SortedSet interface. SortedSet interface extends the Set interface.
Sorting
HashSet is not in sorted order
TreeSet is sorted in ascending order
Comparison
HashSet applies the equals() method for comparison
TreeSet applies the compareTo() method for comparison.

JVM stands for Java Virtual Machine. It is the driving force that provides the run time environment. It converts the Java ByteCode into machine code. It is an abstract machine that provides the specification for execution of a Java program during runtime.

When we compile a .java file, files with nomenclature same to that of the class are created with the extension .class by the Java Compiler. These files contain the byte code. There are various steps involved when a .class file is executed. These steps provide a detailed account of the Java Virtual Machine.

There are typically 6 types of memory areas allocated in JVM.

  • Classloader: Classloader is a subsystem of JVM and a part of the Java Runtime Environment which dynamically loads class in the JVM.
  • Method Area: Method Area is common across all threads. It consists of per-class elements such as constant pool, fields, method data and code and constructor codes. This area gets created during the JVM start-up. If the memory isn’t sufficient then it gives an OutOfMemoryError.
  • Heap: Variables assigned with this memory structure can be administered during runtime. This leads to dynamic memory allocation.
  • Stack: If the amount of memory required is known beforehand, memory is allocated using stacks.
  • Program Counter Register: It contains the address of the Java virtual machine instruction currently being executed. Programs are a set of instructions given to a computer for performing few specific tasks. The Program Counter Register carries the address of the forthcoming instructions.
  • Native Method Stack: It is a collection of all the native methods used in the application.

The finalize() is a method which is used to clean up the processing before the garbage collection in java. It is called by the garbage collector of an object when there are no further references to that object.

finalize() is overridden by a child class to get rid of the system resources or to conduct other cleanup. The exception raised by this method is called the Throwable exception.

The java.lang.Object.finalize() method does not take any parameters and does not return a value.

Now let us look at the overriding of finalize() method in Java,

public class Example
{
   public static void main(String[] args)
   {
       String str = new String("Example");
       str = null;
       System.gc(); // prompts the JVM to call the Garbage Collector
       System.out.println("End of main method");
   }
   public void finalize()
   {
       System.out.println("This is the finalize method");
   }
}

The output is as follows

$javac Example.java
$java Example
End of main method

When a Java program begins, the public static void main(String []args) method is called.

When the Java program needs to be executed, the system looks for the main entry point in the program. This is provided through the main method,

The public static void main(String args[]) is the main method of the Java Program. It is the most important Java Method. It is the entry point of any Java Program. A Java Program cannot be executed without the main method.

Only the args can be changed to a different name but the rest of the method carries the same syntax.

Let us analyze the main method by breaking it up:

  • public : This is the access specifier/access modifier of the main method. It should be public to allow the class to access it. In case of violation of the same, the program will show an error.
  • static : At the start of the runtime, the class is devoid of any object. Thus, the main method needs to be static so that the Java Virtual Machine can set the class to the memory and call the main method. If the main method is not static then the JVM won’t be able to call it due to the absence of an object.
  • void : It is the return type of the main method. Each and every method in Java must have a return type.The main method doesn’t return any value that can be accessed by the other methods as the main method can’t be called in any other method. As soon as the main method finishes its execution the program terminates. Hence it wouldn’t be of any use to return a value. If any value is returned then the compiler will show an unexpected return value error.
  • main: It is the name of the main method. This name is already set and can’t be changed. If any changes are made to the main keyword then the compiler tells that the main method couldn’t be found.
  • String args[]:  The main method can accept a single argument which is of the String type. They are also called command line arguments. They can be used and manipulated as normal arguments in the code written in the main method. They are written along with the execution statement.

The following program will illustrate that the main method is run at first:

public class Example
{
   void display()
   {
       System.out.println("This is the display method");
   }
  public static void main(String[] args) // the main method
   {
       System.out.println("This is the main method");
       Example obj = new Example();
       obj.display(); // invokes display method
   }
}

The output is as follows:

$javac Example.java
$java Example
This is the main method
This is the display method

JIT stands for Just In Time. JIT compiler is a program which converts the Java ByteCode into processor level instructions.

The JIT compiler runs after the program has begun and compiles the bytecode while the program is running into a quicker and more local processor level directive set.

After you've completed writing a Java program, the source code are compiled by the Java compiler into bytecode. Then the bytecode is converted into processor level instructions by the JIT compiler. Thus the JIT compiler acts as a second compiler.

The JIT compiler runs simultaneously with the execution of the program. It compiles the bytecode into platform-specific executable code that is instantly executed.

Once the code is re-compiled by the JIT compiler, it runs relatively quickly on the system.

  • HashMap in Java is a part of the collection class in the java.util package. It is based on Maps and is used for storage of data in Key and Value pairs.

HashMap extends the AbstractMap class and also implements the Cloneable and Serializable interface.

  • Hashtable in Java was an original part of the java.util package and was implementation of a Dictionary. It was updated by JDK 1.2 and implemented the Map Interface. Currently, it is a part of the collections framework. It also stores data in Key and Value pairs.

Despite of the similarities, HashMap and HashTable have few differences. Let us have a look at them.

BasisHashMapHashtable
Null keys/valuesNull key/values are allowed in HashMap. It allows one null key and multiple null values.Null key/values are not allowed in Hashtable
SynchronizationHashMap is not synchronized and is not thread safe. It can’t be shared amongst multiple threads without appropriate synchronization.Hashtable is synchronized and thread safe.
Nature of iteratorHashMap iterator is fail fast in nature.Hashtable iterator is fail safe in nature.
IntroductionHashMap was introduced in JDK 1.2Hashtable is a legacy class.
PerformanceHashMap is fast.Hashtable is slower than HashMap
Parent ClassAbstractMap is the parent class for HashMapDictionary is the parent class for Hashtable

A NumberFormatException in Java is an exception of the java.lang package which is thrown when we try to convert a String into a numeric data type such as int, float, double, long and short. This happens when the String does not have an appropriate format.

Take for example, we try to parse a String which is not of numeric type into an integer:

public class Example
{
   public static void main(String[] args)
   {
           String str = "Number";
           int intVal = Integer.parseInt(str);
           System.out.println(intVal);
   }
}

The above code will throw a NumberFormatException:

$javac Example.java
$java -Xmx128M -Xms16M Example
Exception in thread "main" java.lang.NumberFormatException: For input string: "Number"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at Example.main(Example.java:8)

But if the String is of numeric type, it will successfully parse it to an integer

public class Example
{
   public static void main(String[] args)
   {
           String str = "12";
           int intVal = Integer.parseInt(str);
           System.out.println(intVal);
   }
}

The output is the following:

$javac Example.java
$java -Xmx128M -Xms16M Example
12

StringBuffer class which is related to class String that gives most of the usage of strings. StringBuffer is a mutable, expandable and writable sequence of characters.

StringBuilder is a class whose objects are similar to String objects barring that they can be changed. The size and matter of the sequence of characters can be modified through method calling statements.

Basis
StringBuffer
StringBuilder
Introduction
StringBuffer in Java was introduced in JDK 1.0.
StringBuilder in Java was introduced in JDK 1.5.
Synchronization
Every method present in StringBuffer class is synchronized.
Every method present in StringBuilder class is not synchronized.
Performance
Performance is low
Performance is relatively high
Thread Safe
StringBuffer Object is thread safe.
StringBuilder Object is not thread safe.

ClassNotFoundException and NoClassDefFoundError both occur when a class is not found during runtime.

ClassNotFoundException is an exception that is thrown when we try to load a class while execution of a Java program.

NoClassDefFoundError is an error which is thrown when a class marks it presence during compile time but isn’t available during runtime.

Despite their similarity of a missing class during runtime, there are a quite a few differences between ClassNotFoundException and NoClassDefFoundError.

Basis
ClassNotFoundException
NoClassDefFoundError
Parent class
ClassNotFoundException is a child class of java.lang.Exception
NoClassDefFoundError is a child class of java.lang.Error
Occurrence
It occurs when an application tries to load a class during runtime which is not updated in the classpath
It occurs when the system doesn’t find the class definition during runtime which was present during compile time.
Thrown by
It is thrown by the application (program). Some methods like Class.forName(), loadClass() and findSystemClass() cause this exception.
It is thrown by the Java Runtime System.
Condition
It occurs when the classpath is not updated in accordance to the Java Archive files
It occurs when class definition is missing during runtime.

A stack trace is a characterization of a call stack at a particular instant, with each element depicting a method call statement. The stack trace contains all the call statements from the start of a thread until the point of generation of exception.

When the stack trace is printed, the point of generation is exception is printed first, followed by method call statements, which help us identify the root cause of failure.

Here is an example of printing the stack trace in Java:

public class Example
{
  public static void main (String args[])
  {
      int arr[] = {1,2,3,4};
      int num1=10, num2=0;
      int ans;
      try
      {
         System.out.println("The output is...");
         ans = num1/num2;
         System.out.println("The result is " +ans);
       }
      catch (ArithmeticException ex)
      {
        ex.printStackTrace();
      }
  }
}

The output is as follows:

$javac Example.java
$java Example
The output is...
java.lang.ArithmeticException: / by zero
at Example.main(Example.java:11)

The Arrays.sort() method is provided in Java to sort Short array.

Let us see an example:

import java.util.*;
public class Example {
    public static void main(String []args){
    short[] shortArr = new short[] { 35, 25, 18, 45, 77, 21, 3 };
    System.out.println("Unsorted:");
    for (short a : shortArr) {
     System.out.print(a+" ");
    }
    System.out.println("\nSorted Array = ");
    // sort array
    Arrays.sort(shortArr);
    for (short a : shortArr) {
     System.out.print(a+" ");
    }
    System.out.println();
    }
}

The output:

Unsorted:
35 25 18 45 77 21 3
Sorted Array =
3 18 21 25 35 45 77

The parseInt() method converts hexadecimal to decimal. It can even convert octal to decimal. You just need to set the radix. For hexadecimal, the radix is 16.

The following is an example:

public class Demo
{
  public static void main( String args[] )
  {
      // hexadecimal string
      String str = "298";
      // hex to decimal
      System.out.println("Decimal = "+Integer.parseInt(str, 16));
  }
}

The output:

Decimal = 664

To subtract hours, you need to use the HOUR_OF_DAY constant. Within that, include the number with the negative sign. This would be the hours you want to reduce. All this is done under the Calendar add() method.

The following is an example:

import java.util.Calendar;
public class Example {
 public static void main(String[] args) {
   Calendar c = Calendar.getInstance();
   System.out.println("Date : " + c.getTime());
   // 2 hours subtracted
   c.add(Calendar.HOUR_OF_DAY, -2);
   System.out.println("After subtracting 2 hrs : " + c.getTime());
 }
}

Here is the output:

Date : Sun Dec 16 16:28:53 UTC 2018
After subtracting 2 hrs : Sun Dec 16 14:28:53 UTC 2018

To replace every occurrence of a character, use the replace() method in Java.

Let’s say the following is the string:

Tables and Chairs

And for every occurrence of “a”, you need to replace it with “z”.

The following is the example that would replace every occurrence of “a” with “z”:

public class Example {
 public static void main(String[] args) {
    // Initial string
    String str = "Tables and Chairs";
    System.out.println("Initial String = "+str);
    // replacing character
    String res = str.replace('a', 'z');
    // displaying the new string
    System.out.println("New string = "+res);
 }
}

The output:

Initial String = Tables and Chairs
New string = Tzbles znd Chzirs

We can add leading zeros to a number in Java. Let us see how:

The number we are taking as an example:

15

We will add 6 leading zeros to the above number using the following code. Here, we are working on the String.format() method to achieve the same:

import java.util.Formatter;
public class Example {
   public static void main(String args[]) {
     int a = 15;
     System.out.println("Value = "+a);
     // adding leading zeros
     String res = String.format("%08d", a);
     System.out.println("Updated = " + res);
   }
}

The output:

Value = 15
Updated = 00000015

To display the digits from a string, use Regular Expression.

The string (with digits):

12345example

To get the digits use the \\D flag and the replaceAll():

replaceAll("\\D", ""))

The example:

public class Example {
 public static void main(String[] args) {
   // string with digits
   String s = "12345example";
   System.out.println("Our String = "+s);
   // display digits
   System.out.println("Get digits from the string = "+s.replaceAll("\\D", ""));
 }
}

The output displays only the digits:

Our String = 12345example
Get digits from the string = 12345

The split() method is used to split a string on the basis of regular expression. The first parameter is the same regular expression, whereas if the second parameter is zero, it returns all the strings matching the Regular Expression.

Our sample string:

The TV, and the remote

To split the string with comma, the following is the example:

public class Example {
 public static void main(String[] args) {
     String s = "The TV, and the remote";
     System.out.println("Initial String = "+s);
     String[] str = s.split("[,]", 0);
     System.out.println("\nSplitted string: ");
     for(String val: str){
       System.out.println(val);
     }
  }
}

The output:

Initial String = The TV, and the remote
Splitted string:
The TV
and the remote

No, sizeof operator doesn’t exist in Java.

All primitive data types in Java such as int, char, float, double, long, short have a predefined size in Java. Hence there is no specific requirement of the sizeof operator.

Also, in Java, size of the primitive data types is independent of the platform i.e. Windows, Linux.

An int variable will take 4 bytes in both 32 and 64 bit and in both Windows and Linux operating systems.

The size of boolean is not fixed and depends on the JVM. Different JVMs might have different boolean size. Mostly the size of boolean is 1 bit.

Here are a few primitive data types with their fixed sizes

Data typeDefault size
char2 bytes
byte1 byte
short2 byte
int4 bytes
long8 bytes
float4 bytes
double8 bytes
boolean1 bit

Since Java 8, all primitive wrapper classes provide a SIZE constant in bits. Since 1 byte= 8 bits, we divide the constant by 8 to obtain the size of the wrapper class in bytes.

public class Example
{
 public static void main (String[] args)
 {
   System.out.println(" char: " + (Character.SIZE/8) + " bytes");
   System.out.println(" byte: " + (Byte.SIZE/8) + " bytes");
   System.out.println(" short: " + (Short.SIZE/8) + " bytes");
   System.out.println(" int: " + (Integer.SIZE/8) + " bytes");
   System.out.println(" long: " + (Long.SIZE/8) + " bytes");
   System.out.println(" float: " + (Float.SIZE/8) + " bytes");
   System.out.println(" double: " + (Double.SIZE/8) + " bytes");
 }
}

The output of the program is as follows:

$javac Example.java
$java Example
char: 2 bytes
byte: 1 bytes
short: 2 bytes
int: 4 bytes
long: 8 bytes
float: 4 bytes
double: 8 bytes

The exceptions which are checked during compile time are called checked exceptions. When method throws checked exceptions, they must either be handled by the try-catch block or must declare the exception using the throws keyword. In case of violation, it will show a compile time error.

Unchecked exceptions are the exceptions that are not checked during compile time. If the code throws an unchecked exception and even if it is not handled, it will not generate a compile time error. This is dangerous as unchecked exceptions generally generate runtime errors. All unchecked exceptions are a child class of RuntimeException Class:

Checked ExceptionUnchecked Exception
Checked Exceptions are checked by the compiler.Unchecked exceptions are not checked by the compiler.
It is a subclass of Exception classIt is the subclass of RuntimeException class
They are required to be handled by the try catch block or be rethrown by a method using the throws keywordThey are not restricted
Checked Exceptions are generated for errors that cannot be directly prevented  from occuringUnchecked Exceptions are generated for errors that can be directly prevented from occuring

Example of a Checked Exception by reading a file which is not created:

import java.io.File;
import java.io.FileReader;
public class Example
{
  public static void main(String args[])
  {
     File f = new File("D://abc.txt");


     // file abc is not created, it generates a FileNotFoundException
     FileReader obj= new FileReader(f);
  }
}

The exception generated is as follows

$javac Example.java
Example.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
     FileReader obj= new FileReader(f);
                     ^
1 error

Now, let us see an example of an unchecked exception:

public class Example
{
  public static void main(String args[])
  {
     int val1=10;
     int val2=0;
     int ans=val1/val2;
     System.out.println(ans);
  }
}

The output is as follows:

$javac Example.java
$java  Example
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Example.main(Example.java:7)

To check for uppercase, you need to test the value from ‘A’ to ‘Z’.
Let us see an example:

public class Example {
    public static void main(String []args) {
       char C = 'B';
       if (C >= 'A' && C <= 'Z') {
         System.out.println("Uppercase!");
       } else {
         System.out.println("Lowecase!");
       }
    }
}

The output:

Uppercase!

Unsupported Operation Exception is an exception thrown by Java during the time of execution of the Java program. It is included in the java.lang package.

Unsupported Operation Exception is a part of the Collections framework in Java.

It is a subclass of the Runtime Exception class which is a subclass of the Exception class which, in turn extends the Throwable class.

The class definition is as follows:

public class Unsupported Operation Exception extends Runtime Exception

Here is a sample program for Unsupported Operation Exception:

import java.util.*;
public class Example
{
 public static void main(String[] args)
 {
   List aList = new ArrayList();
   aList.add('a');
   aList.add('b');
   List newList = Collections.unmodifiableList(aList);
   newList.add('c');
 }
}

The output displays an error:

$javac Example.java.
$java Example
Exception in thread "main" java.lang.Unsupported Operation Exception
at java.util.Collections$UnmodifiableCollection.add(Collections.java:1055)
at Example.main(Example.java:10)

A Locale Class is used to perform locale operations and supply locale information to the client or user.

Locale is defined as a set of parameters that represents a geographical location or place where some operation occurs.

The locale class is declared as follows:

public final class Locale
  extends Object
  implements Cloneable, Serializable

The Locale class uses the following constructors:

  • Locale(String L) : Initializes locale from the language code passed as an argument .   
  • Locale(String L, String C) : Initializes locale from the  language, country code passed as arguments.                
  • Locale(String L, String C, String V) : Initializes locale from the  language, country, variant passed as arguments.

The following program is an example for the implementation of the locale class:

import java.text.SimpleDateFormat;
import java.util.Locale;
public class Example
{
   public static void main(String[] args)
   {
       Locale arr[] = SimpleDateFormat.getAvailableLocales();
       for (int i = 1; i <=15; i++)
       {
        System.out.printf("\n%s (%s) " ,arr[i].getDisplayName(), arr[i].toString());
       }
   }
}

The output of the following is as follows

javac Example.java
$java Example
Arabic (United Arab Emirates) (ar_AE)
Arabic (Jordan) (ar_JO)
Arabic (Syria) (ar_SY)
Croatian (Croatia) (hr_HR)
French (Belgium) (fr_BE)
Spanish (Panama) (es_PA)
Maltese (Malta) (mt_MT)
Spanish (Venezuela) (es_VE)
Bulgarian (bg)
Chinese (Taiwan) (zh_TW)
Italian (it)
Korean (ko)
Ukrainian (uk)
Latvian (lv)
Danish (Denmark) (da_DK)

Singleton class is a class which has only a single object. This means you can instantiate the class only once.When we declare the constructor of the class as private, it will limit the scope of the creation of the object.If we return an instance of the object to a static method, we can handle the object creation inside the class itself.

We create a static block for the creation of an object.

For example,

public class Example
{
   private static Example obj;
   static
   {
       obj = new Example(); // creation of object in a static block
   }
   private Example() { }   // declaring the constructor as private
   public static Example getObject()
   {
       return obj;     
   }
   public void print()
   {
       System.out.println("Just for checking");
   }
   public static void main(String[] args)
   {
       Example e = getObject();
       e.print();
   }
}

Exceptions in Java are a part of the java.lang.Exception class. It is an issue that arises during the execution of a program.

All Exception classes in Java are subordinates of the java.lang.Exception class. The java.lang.Exception class is a child class of the Throwable class.

Another subclass of the Throwable class is the java.lang.Error class. Errors are anomalous conditions that occur in a Java program due to many failures. They cannot be handled by the Java program. Usually, programs can’t recuperate from errors.

Exceptions in Java Java Exception Classes

A staple in Java programming interview questions, be prepared to answer this one.

In Java, we use the getCause() method, which returns the cause of the exception or returns null if the cause of the exception is not known.

The declaration of the java.lang.Throwable.getCause() is

public Throwable getCause()

The getCause() method doesn’t accept any arguments and doesn’t throw an exception. It returns the cause that was provided by one of its constructors or that was determined by formation of the initCause(Throwable) method.

Let us see an example on the working of the getCause() method as we try to compile a program with ArrayIndexOutOfBoundsException:

public class Example
{
  public static void main(String[] args) throws Exception
  {
      try
       {
         myException();
       }
      catch(Exception e)
      {
        System.err.println("Cause = " + e.getCause());
      }
  }
  public static void myException() throws Exception
  {
       int arr[]={1,3,5};
       try
       {
         System.out.println(arr[8]);  // generates an ArrayIndexOutOfBoundsException
       }
      catch(ArrayIndexOutOfBoundsException aie)
      {
       Exception e = new Exception(); // creating Exception class object
       throw (Exception)  // throwing the exception to be caught by catch block in main()
           e.initCause(aie); // supplies the cause to getCause()
      }
  }
}   

The output is as follows:

$javac Example.java
$java Example
Cause = java.lang.ArrayIndexOutOfBoundsException: 8

The printStackTrace() method assists the programmer to find the trace of the actual error. It prints the throwable and its backtrace in the standard error stream.

It is a method of the java.lang.Throwable Class. The printStackTrace() method  doesn’t have any parameter and doesn’t return any value.

The java.lang.Throwable.printStackTrace() method is declared as follows

public void printStackTrace()

The printStackTrace() method is a very useful tool in diagnosing exceptions.

Let us see an example when a program prints the stack trace information when we divide a number by zero to generate an ArithmeticException:

public class Example
{
  public static void main(String[] args)
  {
      try
       {
        int x=1/0; // generates ArithmeticException
       }
       catch(ArithmeticException e)
       {
           e.printStackTrace(); //prints the stack trace information
           System.out.println("Caught you dividing by zero!");
       }
  }
}

The program generates the following output wherein we are printing the stack trace:

$javac Example.java
$java Example
Caught dividing by zero!
java.lang.ArithmeticException: / by zero
at Example.main(Example.java:7)

Ordered in Java Collection

An ordered collection in Java mean that the elements of the collection have a definite order. The order of the elements is unconstrained by their value. In other words, the order of the elements in the ordered collection does not depend on their value. An example for an ordered Java collection is a List.

List in Java are interface that extend the Collection interface. Java List allows us to exercise control over the index for insertion of elements. List allows searching and access of elements by their index. Duplicate elements can be stored in a List.

Instances of the list can be created by the new keyword along with the ArrayList, LinkedList, Vector and Stack classes.

List arrayListObj = new ArrayList();
List stackObj = new Stack();
List linkedListObj = new LinkedList();
List vectorObj = new Vector();

After JDK 1.5, we can limit the type of object we want in the List,

List<Obj> list = new List<Obj>();

Let us see an example of a List in Java:

import java.util.*;
public class Example
{
  public static void main(String[] args)
        {
       List<Integer> list = new ArrayList<Integer>();
       list.add(0,6); // adds element 6 at index 0
       list.add(1,3); // adds element 3 at index 1
       list.add(2,9); // adds element 9 at index 2
       System.out.println(list);
       list.remove(1); // removes element at index = 1
       System.out.println(list);
   }

The output is as follows

$javac Example.java
$java Example
[6, 3, 9]
[6, 9]

Sorted in Java Collection

A sorted collection in Java is a collection whose elements have a definite order and the order is constrained by the values of the elements. In other words, the order of the elements is dependent on the values of the elements.

An example for a sorted collection in Java is a SortedSet.

SortedSet is an interface which extends the Set interface in Java. All the components in SortedSet are bound to implement the Comparator interface in Java.

Let us see an example of a SortedSet:

import java.util.*;
public class Example
{
  public static void main(String[] args)
  {
     SortedSet set = new TreeSet(); // creating a SortedSet object set
     set.add(10);  // adding 10 to the SortedSet
     set.add(1);   // adding 1 to the SortedSet
     set.add(4);   // adding 4 to the SortedSet
     Iterator i = set.iterator(); // creating an iterator
     // iterating through the SortedSet by checking for the presence of next element
     while (i.hasNext())
     {
        Object obj = i.next(); // acquiring the element
        System.out.println(obj);
     }
  }
}

The output is as follows:

$javac Example.java
$java Example
1
4
10

Java is a platform independent programming language. It can operate on any platform i.e. operating system with little or no change.

The Java Virtual Machine plays a pivotal role in making Java a platform independent language. When the Java program is compiled, a .class file is created by the Java compiler. This .class file contains nothing but Java ByteCode. The Java ByteCode is a highly developed set of instructions given to the Java Virtual Machine to generate the machine code. It is the machine code in the form of the .class file . ByteCode is independent on the version of JVM.

JVM is dependent on the system. So when the .class file is ported to the other system, that system’s JVM runs the bytecode and converts it into a machine level code which the system can understand. Thus Java achieves platform independence.

An important point to be taken in consideration is that the Java Virtual Machine does not have any knowledge in regard to the programming language. It only is aware of the binary format of the ByteCode and confirm that the .class file is as per Java ByteCode specification thus eliminating errors of running bad Java ByteCodes:

Java achieves Platform

We have the ability to define a Thread in two ways:

  1. By extending the Thread class
  2. By implementing the Interface

By the first technique, the Thread class is extended. Since Java doesn’t support multiple inheritance, we can’t extend any other class. This is a shortcoming as the benefits of inheritance can’t be completely exploited.

By the second technique, the Runnable interface is implemented. We can extend any other class and use the advantages of inheritance completely.

Let us see a program where we extend the Thread class:

public class Example extends Thread  // any other class can’t be extended
{
   public void run()
   {
       System.out.println("Thread running");
   }
   public static void main(String[] args)
   {
       Example obj = new Example();
       obj.start(); // it calls the run() method
       System.out.println("Thread starting");
   }
}

The output is as follows:

$javac Example.java
$java Example
Thread starting
Thread running

Let us now see the implementation of the Runnable interface

class Parent
{
   public static void method()
   {
       System.out.println("This is an extended class");
   }
}
// since Runnable is a interface, class Example can extend class Parent
public class Example extends Parent implements Runnable
{
   public void run()
   {
       System.out.println("Thread running");
   }
   public static void main(String[] args)
   {
       Example obj = new Example();
       obj.method();
       Thread t = new Thread(obj); // creating a new thread t
       t.start(); // calls the run() method
       System.out.println("Thread starting");
   }
}

The output is as follows:

$javac Example.java
$java  Example
This is an extended class
Thread starting
Thread running

The InputStream is an abstract class of the java.io package. It is the parent class of all the subclasses related to input in the form of sequence of bytes.

Hierarchy of InputStream and OutputStream in Java

  • FileInputStream consists of input bytes from a file.
  • ByteArrayInputStream reads the next byte of data from the input stream.
  • FilterInputStream returns the approximate number of bytes that can be read from the input stream. It consists of the DataInputStream, BufferedInputStream and PushBackInputStream classes.
  • PipedInputStream can be used to read data and is piped with PipedOutputStream. A pipe is said to be a link between two threads running in JVM simultaneously.
  • ObjectInputStream is used to deserialize primitive data types and objects previously written by the ObjectOutputStream.

The OutputStream is an abstract class of the java.io package. It is the parent all the subclasses related to input in the form of sequence of bytes.

Hierarchy of InputStream and OutputStream in Java

  • FileOutputStream is a output stream used for writing data into a file.
  • ByteArrayOutputStream allows us to seize data written to a stream in an array.
  • FilterOutputStream is the parent class of all those classes which filter output streams. It is extended by many subclasses including DataOutputStream, BufferedOutputStream and PrintStream.
  • PipedOutputStream is piped with the PipedInputStream.
  • ObjectOutputStream writes primitive objects, data types and graphs to the OutputStream.

Details about JDK, JRE and JVM in Java are given as follows:

Java Development Kit (JDK)

The Java SE, Jakarta EE or the Java Me are platforms whose implementation is done by the Java Development Kit. The basic contents of the JDK are resources for a Java application and the JVM. A list of some of the JDK programming tools is given as follows:

  1. pack200 - This is a JAR compression tool.
  2. appletviewer - This runs and debugs the Java applets without using a web browser.
  3. javah - This is used to write the native methods and is a stub generator as well as C header.
  4. JConsole - This is a graphic management tool.
  5. jstack -This prints the stack traces of java threads

Java Runtime Environment (JRE)

The Java Runtime Environment consists of the Java Virtual Machine, supporting files and core classes. This is basically the minimum requirements to execute a Java application. The JRE is actually a component of the JDK but it can be downloaded separately from the rest.

Some of the components of the JRE are given as follows:

  1. Java Virtual Machine and Server Virtual Machines.
  2. User Interface Toolkits such as Java 2D, AWT, Sound, Swing etc.
  3. Base libraries such as I/O, Beans, JNI, Networking etc.
  4. Lang and Util base libraries such as Zip, Collections, Versioning etc.
  5. Deployment techniques

Java Virtual Machine (JVM)

The Java Virtual Machine allows a computer to run Java programs. It can also run programs in other languages that are beforehand compiled to Java bytecode. The JVM is a virtual machine.

Some of the operations performed by the JVM are given as follows:

  1. The code can be loaded and executed by the Java Virtual Machine.
  2. The JVM also provides the runtime environment.
  3. The code is verified by using the JVM.

Any variable is a class in Java can either be static or non-static. Some of the differences between static and non-static variables in Java is given as following:

Static Variables
Non-static variables
The static keywords are defined using the keyword static.
The non-static keywords do not contain the keyword static.
Static variables can be accessed using a class reference as there is only a single copy of them.
Non-static variables can be accessed using only an object reference.
The memory for static variables is allocated when the class is loaded.
The memory for non-static variables is allocated when an object of the class is created.
Every object of the class shares the static variable i.e. it is common for every object.
All objects of the class have their own copy of a non-static variable i.e the non-static variables are specific for objects.
The memory for static variables is allocated only one time.
The memory for non-static variables is allocated whenever a new object of the class is created.
Static variables can also be called class variables as memory for static variables is allocated when the class is loaded.
Non-static variables can also be called instance variables as memory for non-static variables is allocated whenever a new instance of the class is created.

The primitive data types in Java are boolean, byte, char, short, int, long, float and double. These data types have no special capabilities and are only single values.

A table that demonstrates all the primitive types in Java is as follows:

TypeDefault ValueSize (in bits)Description
booleanfalse1This signifies true or false
byte08Two’s complement integer
char\u000016Unicode Character
short016Two’s complement integer
int032Two’s complement integer
long064Two’s complement integer
float0.032IEEE 754 floating point
double0.064IEEE 754 floating point

A program that demonstrates all the primitive types in Java is given as follows:

public class Demo
{
   public static void main(String []args)
   {
       boolean var1 = true;
       byte var2 = 127;
       char var3 = 'A';
       short var4 = -2000;
       int var5 = 5000;
       long var6 = 10000;
       float var7 = 75.8756f;
       double var8 = 28.84642387;
       System.out.println("boolean: " + var1);  
       System.out.println("byte: " + var2);  
       System.out.println("char: " + var3);  
       System.out.println("short: " + var4);  
       System.out.println("int: " + var5);  
       System.out.println("long: " + var6);  
       System.out.println("float: " + var7);  
       System.out.println("double: " + var8);  
    }
}

The output of the above program is as follows:

boolean: true
byte: 127
char: A
short: -2000
int: 5000
long: 10000
float: 75.8756
double: 28.84642387

A key part of object-oriented programming is multiple inheritance. This means that a class inherits the properties of multiple classes.

However, multiple inheritance may lead to many problems. Some of these are:

  1. The diamond problem occurs when 2 classes (B and C) inherit from a single class(A) and then a class(D) inherits these 2 classes(B and C) using multiple inheritance. This leads to multiple copies of class members of class A in class D which can create problems.
  2. The ambiguity problem occurs if a class inherits from 2 classes that contain the same name for a class member. Then there is ambiguity in the inherited class when that class member is required.

The above problems are one of the reasons that Java doesn't support multiple inheritance.

A program that demonstrates the diamond problem in Java is given as follows:

public class A
{
   void display()
   {
       System.out.println("This is class A");
   }
}
public class B extends A
{
   void display()  
   {
       System.out.println("This is class B");
   }
}
public class C extends A
{
   void display()
   {
       System.out.println("This is class C");
   }
}
public class D extends B, C
{
  public static void main(String args[])
  {
      D obj = new D();
      D.display();
  }
}

The above program leads to an error as multiple inheritance is not allowed Java.

The this keyword in Java refers to the current object and so is a reference variable. Some of the uses of the this keyword is given as follows:

  1. The current class object can be pointed to by using the this keyword.
  2. The this keyword can be used in a constructor call as an argument.
  3. The current class constructor can be invoked using this().
  4. The current class method can be invoked implicitly using this keyword.
  5. The this keyword can be used in a method call as an argument.

A program that demonstrates this keyword in Java is given as follows:

class Employee
{  
   int empNo;  
   String name;  
   int salary;  
   Employee(int empNo, String name, int salary)
   {
       this.empNo = empNo;  
       this.name = name;  
       this.salary = salary;  
   }
   void print()
   {
       System.out.println("Employee Number = " + empNo);
       System.out.println("Name = " + name);
       System.out.println("Salary = " + salary);
   }
}  
public class Demo
{  
   public static void main(String args[])
   {
       Employee emp1 = new Employee(1, "Amy", 20000);  
       Employee emp2 = new Employee(2, "Harry", 15000);
       Employee emp3 = new Employee(3, "Peter", 50000);
       emp1.print();  
       emp2.print();  
       emp3.print();  
   }
}

The output of the above program is as follows:

Employee Number = 1
Name = Amy
Salary = 20000
Employee Number = 2
Name = Harry
Salary = 15000
Employee Number = 3
Name = Peter
Salary = 50000

A string is said to be palindrome if it is read the same forwards and backwards. An example of this is given as follows:

String = madam
The above string is palindrome.

A program that checks if a string is palindrome in Java is given as follows:

public class Example
{
   public static void main(String args[])
   {
       String str = "madam";
       String strrev = "";
       int n = str.length();
       System.out.println("String: " + str);
       for(int i = n - 1; i >= 0; i--)
       {
           strrev = strrev + str.charAt(i);
       }
       if(str.equalsIgnoreCase(strrev))
       {
           System.out.println("The above string is palindrome.");
       }
       else
       {
           System.out.println("The above string is not palindrome.");
       }
   }
}

The output of the above program is as follows:

String: madam
The above string is palindrome.

The Heap Space in Java is allocated the memory to the Objects and the JRE classes by the Java runtime. All the objects in the application are created in the heap space. The objects in the heap space are globally accessible from any place in the application and so they have a lifetime for the whole application execution.

The Memory Model of the heap space is divided into parts known as generations. Details about these are given as follows:

1. Young Generation

All the new objects are allocated in the young generation and they age here. When this place fills up, then minor garbage collection occurs.

2. Old Generation

All the longer existing objects are stored in the old generation. When objects in the young generation reach a certain age threshold, they are moved to the old generation.

3. Permanent Generation

The Java metadata for runtime classes is stored in the permanent generation,.

Some of the important features of Heap space in Java are given as follows:

  1. The heap space contains the young generation, old generation and the permanent generation.
  2. The stack memory is faster than the heap space in Java.
  3. The error java.lang.OutOfMemoryError is thrown if the heap space is full.
  4. The heap space is not thread safe. It needs synchronization methods for safety purposes

Some of the differences between stack memory and heap memory are given as follows:

  1. Stack memory is faster than heap memory as its memory allocation procedure is much more simple.
  2. The stack memory is only used by a thread whereas the heap memory is required by all the application parts.
  3. The stack memory is only accessible by the individual thread but the heap memory objects can be accessed globally.
  4. The stack memory is comparatively short lived while the heap memory exists for the whole execution of the application.
  5. The stack memory management is done using LIFO but it is more complicated for heap memory as that is used by the whole application.

The main usage of the copy constructor is to create an object of a class by initializing it with another object of the same class. A copy constructor in Java is not available by default like in C++, but needs to be explicitly created.

A program that demonstrates a copy constructor in Java is given as follows:

class Employee
{  
   int empNo;  
   String name;  
   int salary;  
   Employee(int eNo, String n, int sal)
   {
       empNo = eNo;  
       name = n;  
       salary = sal;  
   }
   Employee( Employee e)
   {
       empNo = e.empNo;  
       name = e.name;  
       salary = e.salary;  
   }
   void print()
   {
       System.out.println("Employee Number = " + empNo);
       System.out.println("Name = " + name);
       System.out.println("Salary = " + salary);
   }
}  
public class Demo
{  
   public static void main(String args[])
   {
       Employee emp1 = new Employee(1, "John", 25000);  
       Employee emp2 = new Employee(emp1);
       emp1.print();  
       emp2.print();  
   }
}

The output of the above program is as follows:

Employee Number = 1
Name = John
Salary = 25000
Employee Number = 1
Name = John
Salary = 25000

The terms process and thread are often used in multithreading in Java. A major difference between a process and a thread is that a process is a program that is currently executing while a thread is a subpart of the process.

The differences between a process and a thread in detail are given as follows:

Process
Thread
A process is a program that is currently executing.
A thread is a subpart of the process.
A process is also known as a heavyweight task.
A thread is also known as a lightweight task.
Communication between two processes is quite complicated and expensive.
Communication between two threads is comparatively less expensive.
All the processes have a separate address space as they are individual entities.
The threads of a single process share the address space of the process as they are a part of the process.
Context switching between two processes is quite expensive.
Context switching between two threads is less expensive than that between processes.
An individual process has its own global variables, address space, files, accounting information etc.
A thread has its own stack, register and program counter. The rest of the components are shared by all the threads of a process.
A computer system can run multiple processes concurrently in process multitasking.
A computer system can run multiple threads of a program concurrently in thread multitasking.
Java does not control process multitasking.
Java does controls thread multitasking.

The Garbage collection in Java destroys all those objects that are not in use any more. So basically, garbage collection helps free the heap memory by removing the unreachable objects i.e. the objects that don’t have any reference to them.

The different ways that an object is made eligible for garbage collection by the programmer is given as follows:

  1. The reference variable to the object is reassigned.
  2. The reference variable to the object is set is null.
  3. An island of isolation
  4. An object is created inside a method.

After an object is eligible for garbage collection, the garbage collector can be run by requesting the Java Virtual Machine. This can be done using the following methods:

  1. The Runtime.getRuntime().gc() method can be called to run the garbage collector by requesting the Java Virtual Machine.
  2. The System.gc() method can be called to run the garbage collector by requesting the Java Virtual Machine.

A program that demonstrates running the garbage collector by requesting the Java Virtual Machine is given as follows:

public class Demo
{
   public static void main(String[] args) throws InterruptedException
   {
       Demo obj = new Demo();
       obj = null;
       System.gc();
   }
   @Override
   protected void finalize() throws Throwable
   {
       System.out.println("The garbage collector is called...");
       System.out.println("The object that is garbage collected is: " + this);
   }
}

The output of the above program is as follows:

The garbage collector is called...
The object that is garbage collected is: Demo@7978f9b4

The equals() method and the == operator in Java are both used to find if two objects are equal. However, while equals() is a method, == is an operator. Also, equals() compares the object values while == checks if the objects point to the same memory location.

A program that demonstrates the == operator is given as follows:

public class Demo
{
   public static void main(String[] args)
   {      
       System.out.println(67 == 67);
       System.out.println('u' == 'v');
       System.out.println('A' == 65.0);
       System.out.println(false == true);   
   }
}

The output of the above program is as follows:

true
false
true
false

A program that demonstrates the equals() method is given as follows:

public class Demo
{
   public static void main(String[] args)
   {
       String str1 = new String("apple");
       String str2 = new String("apple");
       String str3 = new String("mango");
       System.out.println(str1.equals(str2));
       System.out.println(str1.equals(str3));
   }
}

The output of the above program is as follows:

true
false

The Thread Life Cycle in Java contains 5 states. This means that the thread can be in any of these 5 states. A diagram to understand the states in the thread life cycle is given below:

Thread Life Cycle in Java

The 5 states in a thread life cycle are:

  • New

When an instance of the thread class has been created but the start() method is not invoked, then the thread is in the new state.

  • Runnable

When the start() method has been invoked but the thread scheduler has not selected the thread for execution, then the thread is in the runnable state.

  • Running

If the thread scheduler has selected a thread and it is currently running, then it is in the running state.

  • Blocked (Non-runnable)

When a thread is not eligible to run but still alive, then it is in the blocked state.

  • Terminated

When the run() method of a thread exits, then it is in the terminated state.

To get the JRE version in Java, use the predefined System.property() and include the key as “java.version” as shown below:

public class Example {
  public static void main(String[] args) {
    System.out.println(System.getProperty("java.version"));
  }
}

The output displays the version: 

1.8.0_141

Both the throw and throws keywords in Java are related to exception handling. Differences between these two keywords are given as follows:

ThrowThrows
An exception is explicitly thrown using the throw keyword.An exception is declared using the throws keyword.
The throw keyword is followed by an instance.The throws keyword is followed by a class.
The throw keyword only cannot propagate a checked exception.The throws keyword can propagate a checked exception.
It is not possible to throw multiple exceptions.Multiple exceptions can be declared using throws keyword.
The throw keyword is used within a method.The throws keyword is used with the method signature.

A program that demonstrates throw keyword in Java is given as follows:

public class Demo
{  
  static void checkMarks(int marks)
  {
    if(marks < 40)  
     throw new ArithmeticException("You failed");  
    else
     System.out.println("Congratulations! You passed");  
  }
  public static void main(String args[])
  {
     System.out.println("Test Report");
     checkMarks(29);  
 }
}

The output of the above program is as follows:

Test Report
Exception in thread "main" java.lang.ArithmeticException: You failed
at Demo.checkMarks(Demo.java:6)
at Demo.main(Demo.java:16)

A program that demonstrates throws keyword in Java is given as follows:

public class Demo
{
   public static void main(String[] args) throws InterruptedException
   {
       Thread.sleep(1000);
       System.out.println("Demonstration of throws keyword");
   }
}

The output of the above program is as follows:

Demonstration of throws keyword

Some of the new features of Java 11 are given as follows:

1. Local-Variable Syntax for Lambda Parameters

The Local-Variable Type Inference was introduced by JDK 10 that simplified the code as the type of the local variable did not need to be explicitly stated. This syntax is extended by JEP 32 for use to the parameters of Lambda expressions.

2. Single-File Source-Code Programs

Java is criticized as quite a complex language. However, JEP 330 simplifies this a little bit by eliminating the need to compile a single file application.

3. HTTP Client (Standard)

The Java SE 11 standard contains the HTTP client API as its part. A new module and package is introduced i.e. java.net.http. Some of the main types defined in this are as follows:

  • HttpRequest
  • HttpResponse
  • HttpClient
  • WebSocket

4. Remove the Java EE and CORBA Modules

There are 6 modules included in the java.se.ee meta-module that are not a part of the Java SE 11 standard. The modules that are affected are:

  • transaction
  • activation
  • corba
  • xml.ws
  • xml.ws.annotation
  • xml.bind

5. New APIs

There are a lot of APA’s that are included in the JDK 11 result. There are available as the HTTP is a part of the standard and also because the Flight Recorder is included.

Intermediate

No, Java doesn’t exactly use pointers. Pointers are variables which store the exact address of another variable in their memory. Due to its feature of security and robustness, Java shimmies away from this concept of pointers which forms a major part of C and C++ in memory addressing.

If we know the address of a variable, we can access and modify it from anywhere even if it is private which is self-contradictory and hence Java doesn’t use pointers. Instead of using a pointer, Java sticks to a safer option called references.

A reference is an address that shows the location of storage of the object’s variables and methods. We never actually use objects or copies of objects when we assign objects to variables or methods. Instead we use references to those objects.

References though,

  • Cannot perform arithmetic operations
  • Can’t be assigned an address
  • Can’t be set to point to a variable without an object

To display the last two digits of the current year, use the date character y as shown in the below example:

import java.util.Date;
public class Example {
 public static void main(String[] args) throws Exception {
    Date date = new Date();
    System.out.printf("Two-digit Year = %ty\n",date);
 }
}

The output display 18 for the current year 2018:

Two-digit Year = 18

The startsWith() method is used to check whether a string begins with a specific string or not.

Here, we are checking whether the given string begins with the substring “one”:

public class Example {
 public static void main(String[] args) {
    String s = "OneAndOnly";
    if(s.startsWith("one")) {
      System.out.println("Begins with the specified word!");
    }
    else {
      System.out.println("Does not begin with the specified word!");   
    }
 }
}

The output:

Begins with the specified word!

The endsWith() method is used to check whether a string ends with a specific string or not.

Here, we are checking whether the given string ends with the substring “one”:

public class Example {
 public static void main(String[] args) {
    String s = "Mobile Phone";
    if(s.endsWith("one")) {
      System.out.println("Ends with the specified word!");
    }
    else {
      System.out.println("Does not end with the specified word!");   
    }
 }
}

The output:

Ends with the specified word!

C++ and Java are programming languages with many differences and similarities. C++ supports both procedural as well as object-oriented programming language while Java is purely an object oriented programming language.

The major differences between C++ and Java are given as follows:

C++
Java
The main usage of C++ is for system programming.
The main usage of Java is for application programming.
Structures and Unions are supported by C++.
Structures and Unions are not supported by Java.
There is no build-in support for threads in C++.
There is build-in support for threads in Java.
The goto statement is supported by C++.
The goto statement is not supported by Java.
The >>> operator is not supported by C++.
The >>> operator is supported by Java.
Operator overloading is supported by C++.
Operator overloading is not supported by Java.
Call by value and call by reference are both supported by C++.
Only call by value is supported by C++.
C++ is platform dependent.
Java is platform independent.
Single root hierarchy is not possible in C++.
Single root hierarchy is possible in Java as everything is derived from java.lang.Object.
There is no support for documentation comments in C++.
There is support for documentation comments in Java to provide documentation for Java code.
Multiple inheritance is supported in C++.
Multiple inheritance is not supported in Java.
There is a virtual keyword in C++. It is used to override a function.
There is no virtual keyword in Java. Override of all non-static functions is possible by default.
C++ is quite interactive with hardware.
Java is not so interactive with hardware.

A package in Java is a group of different sub-packages, interfaces, classes etc. that are of a similar type. The two types of packages are build-in packages and user defined packages. Some of the build-in packages are awt, swing, lang, util. javax, sql etc.

Some of the advantages of packages in Java are given as follows:

  1. Packages are quite useful as they group together similar sub-packages, interfaces, classes etc. This helps the programmers to understand which of the enumerations, sub-packages, annotations, interfaces, classes etc. are related.
  2. Related classes in Java can be easily located using Packages.
  3. Access Control can be provided easily using packages.
  4. Individual packages can be defined by programmers to group together enumerations, sub-packages, annotations, interfaces, classes etc.
  5. There are no name conflicts with names in other packages as a package creates a new namespace.

A program that demonstrates a package in Java is given as follows:

package packageExample;
public class Demo
{  
public static void main(String args[])
{  
   System.out.println("This is a package example");  
}  
}

The output of the above program is as follows:

This is a package example

An important part of object-oriented programming is multiple inheritance. This means that a class inherits the properties of multiple classes.

However, multiple inheritance may lead to many problems. Some of these are:

  • The diamond problem occurs when 2 classes (B and C) inherit from a single class(A) and then a class(D) inherits these 2 classes (B and C) using multiple inheritance. This leads to multiple copies of class members of class A in class D which can create problems.
  • The ambiguity problem occurs if a class inherits from 2 classes that contain the same name for a class member. Then there is ambiguity in the inherited class when that class member is required.

The above problems are one of the reasons that Java doesn't support multiple inheritance.

A program that demonstrates the diamond problem in Java is given as follows:

public class A
{
   void display()
   {
       System.out.println("This is class A");
   }
}
public class B extends A
{
   void display()  
   {
       System.out.println("This is class B");
   }
}
public class C extends A
{
   void display()
   {
       System.out.println("This is class C");
   }
}
public class D extends B, C
{
  public static void main(String args[])
  {
      D obj = new D();
      D.display();
  }
}

The above program leads to an error as multiple inheritance is not allowed Java.

Local variables in Java are those declared locally in methods, code blocks, constructors etc. When the program control enters the methods, code blocks, constructors etc. then the local variables are created and when the program control leaves the methods, code blocks, constructors etc. then the local variables are destroyed.

The local variables do not have any default values in Java. This means that they should be declared and assigned a value before the variables are used for the first time. Other the compiler throws an error.

A program that demonstrates local variables in Java is given as follows:

public class Demo
{
  public void func()
  {
     int num;
     System.out.println("The number is : " + num);
  }
  public static void main(String args[])
  {
     Demo obj = new Demo();
     obj.func();
  }
}

The above program contains a local variable num. It leads to an error “variable num might not have been initialized”

The correct version of the above program is given as follows:

public class Demo
{
  public void func()
  {
     int num = 50;
     System.out.println("The number is : " + num);
  }
  public static void main(String args[])
  {
     Demo obj = new Demo();
     obj.func();
  }
}

The output of the above program is as follows:

The number is : 50

The different types of variables in Java are local variables, instance variables and class variables. Details about these are given as follows:

Local Variables

Local variables in Java are those declared locally in methods, code blocks, constructors etc. When the program control enters the methods, code blocks, constructors etc. then the local variables are created and when the program control leaves the methods, code blocks, constructors etc. then the local variables are destroyed.

A program that demonstrates local variables in Java is given as follows:

public class Demo
{
  public void func()
  {
     int num = 50;
     System.out.println("The number is : " + num);
  }
  public static void main(String args[])
  {
     Demo obj = new Demo();
     obj.func();
  }
}

The output of the above program is as follows:

The number is : 50

Instance Variables

Instance variables in Java are those variables that are declared outside a block, method, constructor etc. but inside a class. These variables are created when the class object is created and similarly, they are destroyed when the class object is destroyed.

A program that demonstrates instance variables in Java is given as follows:

public class Demo
{
  int num;
  Demo(int n)
  {
     num = n;
  }
  public void display()
  {
     System.out.println("The number is: " + num);
  }
  public static void main(String args[])
  {
     Demo obj = new Demo(20);
     obj.display();
  }
}

The output of the above program is as follows:

The number is: 20

Class Variables

Class variables or Static variables are defined using the static keyword. These variables are declared inside a class but outside a method, code block etc. Class variables last for the program lifetime i.e. they are created at the start of the program and destroyed at the end of the program.

A program that demonstrates class variables in Java is given as follows:

public class Demo
{
  int num;
  static int count;
  Demo(int n)
  {
     num = n;
     count ++;
  }
  public void display()
  {
     System.out.println("The number is: " + num);
  }
  public static void main(String args[])
  {
     Demo obj1 = new Demo(20);
     obj1.display();
     Demo obj2 = new Demo(50);
     obj2.display();
     System.out.println("The total objects of class Demo are: " + count);
  }
}

The output of the above program is as follows:

The number is: 20
The number is: 50
The total objects of class Demo are: 2

Use the Big Decimal negate() method to negate a BigDecimal value in Java.
Let us see a simple example:

import java.math.*;
public class Example {
  public static void main(String[] args) {
     BigDecimal b1, b2;
     b1 = new BigDecimal("879879");
     b2 = b1.negate();
     System.out.println("Negated = " +b2);
  }
}

The output:

Negated = -879879

A relationship between two classes that can be established using their objects is known as association in Java. The two forms of association are Composition and Aggregation. The types of association can be one-to-one, many-to-one, one-to-many, many-to-many etc.

A program that demonstrates association in Java is given as follows:

class Teacher
{
   private String name;
   private int age;
   private String subject;
   Teacher(String name, int age, String subject)
   {
       this.name = name;
       this.age = age;
       this.subject = subject;
   }
   public String retTeacherName()
   {
       return this.name;
   }
   public String retTeacherSubject()
   {
       return this.subject;
   }
}  
class Student
{
   private int rno;
   private String name;
   Student(int rno, String name)  
   {
       this.rno = rno;
       this.name = name;
   }
   public String retStudentName()
   {
       return this.name;
   }
}
public class Association  
{
   public static void main (String[] args)  
   {
       Teacher t = new Teacher("Amy", 25, "Maths");
       Student s1 = new Student(12, "John");
       Student s2 = new Student(15, "Susan");
       System.out.println(t.retTeacherName() + " teaches " + t.retTeacherSubject() + " to students " + s1.retStudentName() + " and " + s2.retStudentName()) ;
   }
}

The output of the above program is as follows:

Amy teaches Maths to students John and Susan

In the above program, the two classes Teacher and Student are associated as a teacher may teach multiple students. This is a one-to-many relationship.

Aggregation is a type of weak association that specifies a HAS-A relationship. Both the objects of a class in an aggregation can exist individually. Also, aggregation is a one-way relationship. For example - A class in a school has students but the opposite is not true.

A program that demonstrates aggregation in Java is given as follows:

class School
{
   private String name;
   School(String name)
   {
       this.name = name;
   }
   public String retClassName()
   {
       return this.name;
   }
}  
class Student
{
   private int rno;
   private String name;
   Student(int rno, String name)  
   {
       this.rno = rno;
       this.name = name;
   }
   public String retStudentName()
   {
       return this.name;
   }
}
public class AssociationDemo
{
   public static void main (String[] args)  
   {
       School c = new School("XII B");
       Student s1 = new Student(1, "John");
       Student s2 = new Student(2, "Susan");
       Student s3 = new Student(3, "Mary");
       Student s4 = new Student(4, "Adam");
       Student s5 = new Student(5, "Lucy");
       System.out.println("The students in school class " + c.retClassName() + " are:") ;
       System.out.println(s1.retStudentName());
       System.out.println(s2.retStudentName());
       System.out.println(s3.retStudentName());
       System.out.println(s4.retStudentName());
       System.out.println(s5.retStudentName());
   }
}

The output of the above program is as follows:

The students in school class XII B are:
John
Susan
Mary
Adam
Lucy

In the above program, there is a HAS-A relationship between School class and student as a class in a school has multiple students.

The operator precedence in Java determines the order of evaluation for the operators in an expression. The Java operator that has the highest precedence can be seen from the table given below:

Operator
Sign
Associativity
Postfix
++ --
Left to Right
Unary
++ -- + - ! (type)
Right to Left
Multiplicative
* / %
Left to Right
Additive
+ -
Left to Right
Shift
>> << >>>
Left to Right
Relational
< > <= >=
Left to Right
Equality
== !=
Left to Right
Bitwise AND
&
Left to Right
Bitwise XOR
^
Left to Right
Bitwise OR
|
Left to Right
Logical AND
&&
Left to Right
Logical OR
||
Left to Right
Conditional
?:
Right to Left
Assignment
= += -= *= /= %= ^= |= >>= <<=  
Right to Left

As is obvious from the above table, the postfix operators (++ --) have the highest precedence in Java.

The Stack memory in Java is used for thread execution. Specific values are stored in the stack memory that are available for a short time. Also, stack memory may contain data references to objects getting referred from the method that are in the heap memory.

The order in Stack memory is Last In First Out (LIFO). A block is created in the stack memory for all the primitive values and references to other objects in a method when that method is invoked. After the end of the method, the memory block in the stack memory is free and can be used by another method.

In general, the size of the stack memory is quite less as compared to the heap memory.

Some of the important features of Java Stack Memory are given as follows:

  1. The stack memory can grow if more methods are called and it also shrinks if more methods are returned.
  2. The error java.lang.StackOverFlowError is thrown if the stack memory is full.
  3. As long is the method with the variables is running, those variables exist inside the stack. After method is returned, the variables are deleted.
  4. The memory access time of stack memory is less than that of heap memory.
  5. The variables are automatically allocated and delocated in stack memory according to the method.
  6. Each thread has its own stack and so the stack memory is thread safe.

Some of the differences between stack memory and heap memory are given as follows:

  1. The stack memory is only used by a thread whereas the heap memory is required by all the application parts.
  2. The stack memory management is done using LIFO but it is more complicated for heap memory as that is used by the whole application.
  3. The stack memory is comparatively short lived while the heap memory exists for the whole execution of the application.
  4. Stack memory is faster than heap memory as its memory allocation procedure is much more simple.
  5. The stack memory is only accessible by the individual thread but the heap memory objects can be accessed globally.

The thread life cycle in Java contains 5 states. This means that the thread can be in any of these 5 states. The 5 states in a thread life cycle are demonstrated with the help of a diagram as follows:

Life cycle of a thread

1. New

When an instance of the thread class has been created but the start() method is not invoked, then the thread is in the new state.

2. Runnable

When the start() method has been invoked but the thread scheduler has not selected the thread for execution, then the thread is in the runnable state.

3. Running

If the thread scheduler has selected a thread and it is currently running, then it is in the running state.

4. Blocked (Non-runnable)

When a thread is not eligible to run but still alive, then it is in the blocked state.

5. Terminated

When the run() method of a thread exits, then it is in the terminated state.

Use the BigInteger negate() method to nagate a BigInteger value in Java.

Let us see a simple example:

import java.math.*;
public class Example {
  public static void main(String[] args) {
     BigInteger b1, b2;
     b1 = new BigInteger("35");
     b2 = b1.negate();
     System.out.println("Negated = " +b2);
  }
}

The output:

Negated = -35

Both the throw and throws keywords in Java are related to exception handling. Details about these are given as follows:

The throw keyword

An exception can be thrown explicitly from a method or a code block using the throw keyword. Mainly custom exceptions are thrown using this keyword. Also, throw can be used to throw checked or unchecked exceptions.

A program that demonstrates throw keyword in Java is given as follows:

public class Demo
{  
  static void checkMarks(int marks)
  {
    if(marks < 40)  
     throw new ArithmeticException("You failed");  
    else
     System.out.println("Congratulations! You passed");  
  }
  public static void main(String args[])
  {
     System.out.println("Test Report");
     checkMarks(29);        
 }
}

The output of the above program is as follows:

Test Report
Exception in thread "main" java.lang.ArithmeticException: You failed
at Demo.checkMarks(Demo.java:6)
at Demo.main(Demo.java:16)

The throws keyword

The signature of a method can contain the throws keyword to signify that the specified method can throw any one of the listed type exceptions. The exception can be handled using a try catch block by the caller of the method.

A program that demonstrates throws keyword in Java is given as follows:

public class Demo
{
   public static void main(String[] args)throws InterruptedException
   {
       Thread.sleep(1000);
       System.out.println("Demonstration of throws keyword");
   }
}

The output of the above program is as follows:

Demonstration of throws keyword

A default constructor is a constructor without parameters that is automatically created if there is no constructor written by the programmer. All the data members in a class are initialized by the default constructor to their default values.

The default values of numeric data types is 0, of references is null, of floating point numbers is 0.0 and of booleans is false.

A program that demonstrates a default constructor in Java is given as follows:

class Student
{
  int rollNumber;
  String name;
  float marks;
}
public class Demo
{
   public static void main(String args[])
   {
     Student s = new Student();
     System.out.println(s.rollNumber);
     System.out.println(s.name);
     System.out.println(s.marks);
   }
}

The output of the above program is as follows:

0
null
0.0

In the above program, the default constructor is called when an object s of class Student is created. Then the default values of the data members of class Student are printed.

The access specifiers in Java specify the scope of the data members, constructors, methods etc. of the class. Details about the default and protected access specifiers are given as follows:

Default Access Specifier

The default access specifier is used when no access specifier is clearly provided by the programmer. The data members, constructors, methods etc. that have the default access specifier are only accessible in the same package.

A program that demonstrates the default access specifier in Java is given as follows:

package pack1;  
class C1
{  
   void print()
   { System.out.println("This is in package p1");
   }
}  
package pack2;  
import pack1.*;  
class C2
{  
  public static void main(String args[])
  {
  C1 obj = new C1();
  obj.print();
  }
}  

Execution of the above program results in compile time error as the access specifier of class C1 and print() is default and so they cannot be accessed from outside the package pack1.

Protected Access Specifier

The protected access specifier can be used on the class data members or methods with the protected keyword. This means that the data members are accessible inside the package and in other packages using inheritance. The protected access specifier cannot be directly applied on a class but it can be applied on a data member, constructor or method.

A program that demonstrates the protected access specifier in Java is given as follows:

package pack1;  
public class C1
{  
   protected void print()
   { System.out.println("This is in package p1");
   }
}  

Another package:

package pack2;  
import pack1.*;  
class C2 extends C1
{  
  public static void main(String args[])
  {
  C1 obj = new C1();
  obj.print();
  }
}

The output of the above program is as follows:

This is in package p1

The Java Runtime Environment consists of the Java Virtual Machine, supporting files and core classes. This is basically the minimum requirements to execute a Java application. The JRE is actually a component of the JDK but it can be downloaded separately from the rest.

Some of the components of the JRE are given as follows:

  1. User Interface Toolkits such as Java 2D, AWT, Sound, Swing etc.
  2. Java Virtual Machine and Server Virtual Machines.
  3. Lang and Util base libraries such as Zip, Collections, Versioning etc.
  4. Deployment techniques
  5. Base libraries such as I/O, Beans, JNI, Networking etc.

JRE working with the JVM

The Java programs are executed by the Java Virtual Machine which is a running software system. The Java Runtime Environment on the other hand, is a on-disk system that starts the JVM to execute the Java code by combining it with the required libraries.

In short, the software and libraries that are necessary to run the Java programs are contained in the Java Runtime Environment.

A diagram that demonstrates that the JRE contains the JVM is given as follows:

Java Runtime Environment

Installing the JRE

The Java Runtime Environment is essentially a software that is installed on the computer that runs the Java programs. Normally, Java is installed on the computer and the JRE is included. However, if anytime it is required to install or upgrade the JRE manually, it can be done by downloading the current JRE version from Oracle.

The Java Sockets are used for communication between two computers using the TCP protocol. The communication between the client and server occurs when they read from and write to the sockets as required. The class java.net.Socket provides a representation for sockets in Java and the class java.net.ServerSocket allows the server programs to listen for clients and create connections with them.

The steps to create a TCP connection between two computers using sockets is given as follows:

  1. A ServerSocket object is instantiated by the server and this denotes the port number for communication.
  2. Then the accept() method of the ServerSocket class is invoked by the server. This method waits until the till the time the client can connect to the server on the specified port.
  3. The client instantiates a Socket object while a server is waiting. This object specifies the server name as well as the port number.
  4. The Socket class constructor tries to connect the client and the server on the required port number. After communication establishment, the client can communicate with the server using a Socket object.
  5. The accept() method on the server side returns a reference to the socket that is connected to the client’s socket.

The communication can occur using the Output stream and Input stream after the connections are established.

ArrayList and LinkedList are very important lists in Java. While the ArrayList implements a dynamic array, the LinkedList implements a doubly linked list.

Some of the differences between Arraylist and LinkedList are given as follows:

  1. It is easier and much faster to insert elements in LinkedList as compared to ArrayList. The time complexity for insertion in ArrayList is O(n) in worst case while it is O(1) in LinkedList.
  2. Deletion of elements is also easier in LinkedList as compared to ArrayList. However, the time complexity for deletion in ArrayList and LinkedList is O(n) in worst case.
  3. The time required to find if an element is available in both ArrayList and LinkedList is O(n) in the worst case. But binary search can be used on a sorted ArrayList to reduce this time to O(log n).
  4. There is more memory overhead in LinkedList as compared to ArrayList. This is because in LinkedList each node contains the data and address of the previous and next nodes while in ArrayList each index contains the data only.

A program that demonstrates ArrayList in Java is given as follows:

import java.util.ArrayList;
public class ArrayListDemo
{
   public static void main(String[] args)
   {
       ArrayList<String> al= new ArrayList<String>();
       al.add("Apple");
       al.add("Orange");
       al.add("Guava");
       al.add("Mango");
       al.add("Peach");
       System.out.println(al);
       al.remove(2);
       System.out.println(al);
       if (al.contains("Apple"))
           System.out.println("Apple found in ArrayList");
       else
           System.out.println("Apple not found in ArrayList");
   }
}

The output of the above program is as follows:

[Apple, Orange, Guava, Mango, Peach]
[Apple, Orange, Mango, Peach]
Apple found in ArrayList

A program that demonstrates LinkedList in Java is given as follows:

import java.util.LinkedList;
public class LinkedListDemo
{
   public static void main(String[] args)
   {
       LinkedList l = new LinkedList();
       l.add("Apple");
       l.add("Orange");
       l.add("Guava");
       l.add("Mango");
       l.add("Peach");
       System.out.println(l);
       l.remove(0);
       System.out.println(l);
       if (l.contains("Apple"))
           System.out.println("Apple found in LinkedList");
       else
           System.out.println("Apple not found in LinkedList");
   }
}

The output of the above program is as follows:

[Apple, Orange, Guava, Mango, Peach]
[Orange, Guava, Mango, Peach]
Apple not found in LinkedList

A group of objects can be stored and manipulated using an architecture that is provided by the Collections API in Java. All the possible operations in Java such as searching, sorting, deletion, insertion etc. can be performed using the Java Collections.

A hierarchy of collections in Java is given as follows:

Collections API in Java

Some of the characteristics of the Collections API in Java are given as follows:

  1. There are some Collections that don’t allow duplicate elements.
  2. Some Collections may use linked lists to store the elements. All the elements in a linked list have a reference to the previous and the next elements in the linked list. It is easier and much faster to insert elements in LinkedList as compared to ArrayList.
  3. Some Collections may use arrays to store the elements. The array can be resized dynamically to store more and more elements as required.
  4. Some Collections may use hash tables to store the elements. These tables contain specific keys for all the elements that they store.
  5. Some Collections may use trees to store the elements. Tree based storage is quite useful for storing elements.

An empty interface that contains no fields or methods is known as a marker interface. Realistic examples of a marker interface are Serializable interface, Cloneable interface and Remote interface. Details about these interfaces are given as follows:

Serializable Interface

The serializable interface makes an object eligible to save its state in a file. This interface is stored in the java.io package. The classes in which the serializable interface is not implemented do not have their states serialized or deserialized.

Cloneable Interface

The cloneable interface is implemented by a class that indicates it is allowed for a clone() method in the Object class to make field-to field copy of the class instances. The java.lang package contains the cloneable interface.

The exception CloneNotSupportedException is thrown if the clone() method is invoked for a class that has no implementation of the cloneable interface. The classes that have an implementation of this interface usually override the Object.clone() method by convention.

Remote Interface

An object that is stored on a machine and can be accessed from another machine is known as a remote object. The remote interface is required to flag an object and convert it into a remote object. The java.rmi package contains the remote interface.

The interfaces whose methods can be invoked using a non-local virtual machine can also be identified by the remote interface. Also, the Remote Method Invocation (RMI) has some convenience class that can be extended using the remote object implementations and these facilitate the creation of the remote objects.

Exception handling is the process that deals with the occurrence of exceptions during computation. Some of the best practices of exception handling in Java are given as follows:

  • Put clean up code in a finally block

There are many resources that are used in a try block that need to be closed afterwards. However, these resources should not be closed in the end of the try block as it may never be reached if any exception is thrown. So, all the cleanup code should be in the finally block for better results.

  • Do not catch throwable

The superclass of all the exceptions and errors is throwable. It should never be used in a catch clause as it will catch all exceptions and errors, some of which may be outside the control of the application and so unable to be handled.

  • Use descriptive messages with exceptions

Descriptive messages should be provided with exceptions that help to understand why the exception was reported to the monitoring tool or the log file. The descriptive messages should be precise and describe the exceptional event problem as clearly as possible.

  • Document the specified exceptions

All the exceptions that are specified in the method signature should also be documented in the Javadoc. This is quite useful as it provides the caller with more information that helps to handle or avoid the exception as required.

  • Never ignore exceptions

Never ignore an exception under the assumption that it will never occur. This is faulty as the code may change in unforeseen ways in the future and the exceptions thought not required as a particular event would never occur might just occur.

  • Catch the most specific exception first

The most specific exception class should be catched first and the less specific catch blocks should be provided later as the first catch block that matches an exception gets executed. So, if the less specific exception catch block is given first, then the control may never reach the more specific exception catch block.

  • Use more specific exceptions

It is better to have as specific exceptions as possible as they make the API easier to understand. This means that the class which is the best fit for the exceptional event should be used and unspecified exceptions should be avoided.

  • Do not log and throw exceptions

Do not log and rethrow exceptions as it leads to multiple error messages for the same exception. These additional error messages are quite useless as they do not provide any extra information. If any additional information is required, the exception should be caught and wrapped in a custom one.

The compare() method is used in Java to compare two doubles.
Let us see an example:

import java.lang.*;
public class Example {
   public static void main(String args[]) {
     Double val1 = new Double("488.11");
     Double val2 = new Double("445.78");
     int a =  Double.compare(val1, val2);
     if(a > 0) {
        System.out.println("Value 1 is greater than Value 2");
     } else if(a < 0) {
        System.out.println("Value 1 is less than Value 2");
     } else {
        System.out.println("Equal!");
     }
   }
}

The output:

Value 1 is greater than Value 2

There are two interfaces to sort the objects using the class data members. These are comparable interface and comparator interface. Details about these are given as follows:

Comparable Interface

The comparable interface provides a single sorting sequence. This means that a single element of the collection can be used as the basis for the sorting. The compareTo() method is used by the comparable interface to sort the elements. The java.lang package contains the comparable interface.

A program that demonstrates the comparable interface is given as follows:

import java.io.*;
import java.util.*;
class Student implements Comparable<Student>
{
   private int rollNumber;;
   private String name;
   private double marks;
   public int compareTo(Student s)
   {
       return this.rollNumber - s.rollNumber;
   }
   public Student(int rno, String n, double m)
   {
       this.rollNumber = rno;
       this.name = n;
       this.marks = m;
   }
   public int getRNo()
   {
       return rollNumber;
   }
   public String getName()   
   {
       return name;
   }
   public double getMarks()      
   {
       return marks;  
   }
}
public class Demo
{
   public static void main(String[] args)
   {
       ArrayList<Student> list = new ArrayList<Student>();
       list.add(new Student(23, "Harry", 87.5));
       list.add(new Student(1, "Amy", 85.5));
       list.add(new Student(15, "John", 55.0));
       list.add(new Student(45, "James", 95.0));
       list.add(new Student(7, "Sally", 78.0));
       System.out.println("Original Student List: ");
       for (Student s: list)
       {
           System.out.println(s.getRNo() + " " +
                              s.getName() + " " +
                              s.getMarks());
       }
       Collections.sort(list);
       System.out.println("\nStudent List after sorting according to their Roll Number: ");
       for (Student s: list)
       {
           System.out.println(s.getRNo() + " " +
                              s.getName() + " " +
                              s.getMarks());
       }
   }
}

The output of the above program is as follows:

Original Student List:
23 Harry 87.5
1 Amy 85.5
15 John 55.0
45 James 95.0
7 Sally 78.0
Student List after sorting according to their Roll Number:
1 Amy 85.5
7 Sally 78.0
15 John 55.0
23 Harry 87.5
45 James 95.0

Comparator Interface

The comparator interface provides multiple sorting sequences. This means that multiple elements of the collection can be used as the basis for the sorting. The compare() method is used by the comparator interface to sort the elements. The java.util package contains the comparator interface.

A program that demonstrates the comparator interface is given as follows:

import java.io.*;
import java.util.*;
class Student implements Comparable<Student>
{
   private int rollNumber;;
   private String name;
   private double marks;
   public int compareTo(Student s)
   {
       return this.rollNumber - s.rollNumber;
   }
   public Student(int rno, String n, double m)
   {
       this.rollNumber = rno;
       this.name = n;
       this.marks = m;
   }
   public int getRNo()
   {
       return rollNumber;
   }
   public String getName()   
   {
       return name;
   }
   public double getMarks()      
   {
       return marks;  
   }
}
class MarksCompare implements Comparator<Student>
{
   public int compare(Student s1, Student s2)
   {
       if (s1.getMarks() < s2.getMarks())
       return -1;
       if (s1.getMarks() > s2.getMarks())
       return 1;
       else
       return 0;
   }
}
class NameCompare implements Comparator<Student>
{
   public int compare(Student s1, Student s2)
   {
       return s1.getName().compareTo(s2.getName());
   }
}
public class Main
{
   public static void main(String[] args)
   {
       ArrayList<Student> list = new ArrayList<Student>();
       list.add(new Student(23, "Harry", 87.5));
       list.add(new Student(1, "Amy", 85.5));
       list.add(new Student(15, "John", 55.0));
       list.add(new Student(45, "James", 95.0));
       list.add(new Student(7, "Sally", 78.0));
       System.out.println("Original Student List: ");
       for (Student s: list)
       {
           System.out.println(s.getRNo() + " " +
                              s.getName() + " " +
                              s.getMarks());
       }
       Collections.sort(list);
       System.out.println("\nStudent List sorted by Roll Number: ");
       for (Student s: list)
       {
           System.out.println(s.getRNo() + " " +
                              s.getName() + " " +
                              s.getMarks());
       }
       System.out.println("\nStudent List sorted by Marks: ");
       MarksCompare mc = new MarksCompare();
       Collections.sort(list, mc);
       for (Student s: list)
       {
           System.out.println(s.getRNo() + " " +
                              s.getName() + " " +
                              s.getMarks());
       }
       System.out.println("\nStudent List sorted by Name: ");
       NameCompare nc = new NameCompare();
       Collections.sort(list, nc);
       for (Student s: list)
       {
           System.out.println(s.getRNo() + " " +
                              s.getName() + " " +
                              s.getMarks());
       }
   }
}

The output of the above program is as follows:

Original Student List:
23 Harry 87.5
1 Amy 85.5
15 John 55.0
45 James 95.0
7 Sally 78.0
Student List sorted by Roll Number:
1 Amy 85.5
7 Sally 78.0
15 John 55.0
23 Harry 87.5
45 James 95.0
Student List sorted by Marks:
15 John 55.0
7 Sally 78.0
1 Amy 85.5
23 Harry 87.5
45 James 95.0
Student List sorted by Name:
1 Amy 85.5
23 Harry 87.5
45 James 95.0
15 John 55.0
7 Sally 78.0

An anonymous class is an inner class that does not have any name in Java. Only a single object of this class is created. An instance of an object can be created with more features such as overloading methods of a class by using the anonymous inner class. This can be done without having to subclass a class.

A program that demonstrates the anonymous inner class in Java is given as follows:

interface Num
{
   int num = 50;
   void getNum();
}
public class Demo
{
   public static void main(String[] args)
   {
       Num obj = new Num()
       {
           @Override
           public void getNum()
           {
               System.out.print("The number is: " + num);
           }
       };
       obj.getNum();
   }
}

In the above example an anonymous inner class is used and the object of that is created as well as copied in the class code.

The priority queue is a data structure in which elements are handled based on their priority. The priority heap is the basis of the priority queue. Also, priority queues are unbound queues and they do not allow NULL pointers.

A program that demonstrates priority queue in Java is given as follows:

import java.util.*;
public class Demo
{
   public static void main(String args[])
   {
       PriorityQueue<String> p = new PriorityQueue<String>();
       p.add("Apple");
       p.add("Mango");
       p.add("Orange");
       p.add("Peach");
       p.add("Guava");
       System.out.println("The priority queue elements are:");
       Iterator i1 = p.iterator();
       while (i1.hasNext())
       {
           System.out.println(i1.next());
       }
       System.out.println("\nThe element with highest priority is: " + p.peek());
       p.poll();
       System.out.println("\nThe priority queue elements after removing an element with poll function are:");
       Iterator<String> i2 = p.iterator();
       while (i2.hasNext())
       {
           System.out.println(i2.next());
       }
   }
}

The output of the above program is as follows:

The priority queue elements are:
Apple
Guava
Orange
Peach
Mango
The element with highest priority is: Apple
The priority queue elements after removing an element with poll function are:
Guava
Mango
Orange
Peach

The two entities in composition are quite dependent on each other i.e. one entity cannot exist without the other. Basically, composition is a restricted form of aggregation.

A program that demonstrates composition in Java is given as follows:

import java.io.*;
import java.util.*;
class Department
{
   public String name;
   Department(String name)
   {
       this.name = name;
   }
}
class College
{
   private final List<Department> departments;
   College (List<Department> departments)
   {
       this.departments = departments;  
   }
   public List<Department> getDepartments()
   {
      return departments;   
   }
}
public class Demo
{
   public static void main (String[] args)  
   {
       Department d1 = new Department("Computer Science");
       Department d2 = new Department("Electrical");
       Department d3 = new Department("Mechanical");
       Department d4 = new Department("Information Technology");
       Department d5 = new Department("Civil");
       List<Department> departments = new ArrayList<Department>();
       departments.add(d1);
       departments.add(d2);
       departments.add(d3);
       departments.add(d4);
       departments.add(d5);
       College c = new College(departments);
       List<Department> dpt = c.getDepartments();
       System.out.println("The different departments in college are: ");
       for(Department d : dpt)
       {
           System.out.println(d.name);
       }
   }
}

The output of the above program is as follows:

The different departments in college are:
Computer Science
Electrical
Mechanical
Information Technology
Civil

The above program is an example of composition as the departments and college are dependent on each other. There would be no departments without a college.

Threads in Java help to achieve parallelism in a program. This means that multiple operations can be performed at the same time using multithreading.

The most important usage of threads can be achieved using multithreading. This means that multiple tasks can be done simultaneously using multithreading. Some of the major uses of multithreading in Java are given as follows:

  • Reduction of response time

The response time for a particular problem can be reduced by dividing it into smaller chunks and assigning each of these chunks to a thread. This means that multiple threads can be used to solve the problem in a relatively lesser time.

  • Multiple tasks can run in parallel

Multiple tasks can run in parallel using multithreading. An example of this is event handling or drawing which can be performed at the same time using multiple threads.

Also, multiple threads are required in a Graphical User Interface as a thread is performing a particular function, other threads are required for more user tasks as the GUI cannot be frozen.

  • Multiple clients can be served at the same time

In a client server application, many clients can connect to the server using multiple threads. This means that a client does not have to wait until the request of the previous client has been serviced by the server.

  • The CPU power can be used to its full advantage

Threads can be used to utilize the full CPU power and increase the throughput of the system. If there are multiple cores to the CPU, then multiple threads are required to run in parallel across these cores to optimize the system performance.

In multithreading, each thread is assigned a priority. The processor is assigned to the thread by the scheduler based on its priority i.e. the highest priority thread is assigned the processor first and so on.

The three static values defined in the Thread class for the priority of a thread are given as follows:

  • MAX_PRIORITY

This is the maximum thread priority with value 10.

  • NORM_PRIORITY

This is the default thread priority with value 5.

  • MIN_PRIORITY

This is the minimum thread priority with value 1.

A program that demonstrates thread priority in Java is given as follows:

import java.lang.*;
public class ThreadPriorityDemo extends Thread
{
   public static void main(String[]args)
   {
       ThreadPriorityDemo thread1 = new ThreadPriorityDemo();
       ThreadPriorityDemo thread2 = new ThreadPriorityDemo();
       ThreadPriorityDemo thread3 = new ThreadPriorityDemo();
       System.out.println("Default thread priority of thread1: " + thread1.getPriority());
       System.out.println("Default thread priority of thread2: " + thread2.getPriority());
       System.out.println("Default thread priority of thread3: " + thread3.getPriority());
       thread1.setPriority(8);
       thread2.setPriority(3);
       thread3.setPriority(6);
       System.out.println("New thread priority of thread1: " + thread1.getPriority());
       System.out.println("New thread priority of thread2: " + thread2.getPriority());
       System.out.println("New thread priority of thread3: " + thread3.getPriority());
   }
}

The output of the above program is as follows:

Default thread priority of thread1: 5
Default thread priority of thread2: 5
Default thread priority of thread3: 5
New thread priority of thread1: 8
New thread priority of thread2: 3
New thread priority of thread3: 6

Memory Management is a vital part of Java and it is very important to understand it. The memory in Java is divided into two main parts, the stack and the heap. A diagram that demonstrates this is given as follows:

Memory Management in Java

Details about the stack and the heap memory in Java is given as follows:

Stack Memory in Java

The Stack memory in Java is used for thread execution. Specific values are stored in the thread stack that are available for a short time. Also, stack memory may contain data references to objects getting referred from the method that are in the heap memory.

The order in Thread Stack memory is Last In First Out (LIFO). A block is created in the stack memory for all the primitive values and references to other objects in a method when that method is invoked. After the end of the method, the memory block in the stack memory is free and can be used by another method.

Heap Memory in Java

The heap memory in Java is memory allocated  to the Objects and the JRE classes by the Java runtime. All the objects in the application are created in the heap memory. The objects in the heap memory are globally accessible from any place in the application and so they have a lifetime for the whole application execution.

The list interface in Java can be maintained using both ArrayList and Vector and both of them use dynamically resizable arrays as their internal data structure. So, the major differences between these are given as follows:

ArrayList
Vector
The ArrayList is not synchronized i.e. many threads can work on the ArrayList at the same time.
Vector is synchronized i.e. only one thread can access the Vector at a time.
The ArrayList is fast as compared to vector since it is not synchronized.
The Vector is slow as compared to ArrayList since it is synchronized.
If the number of elements exceed the capacity of the ArrayList, then it increases its current array size by 50%.
If the number of elements exceed the capacity of the Vector, then it doubles its current array size.
The traversal of elements in ArrayList is done using Iterator interface only.
The traversal of elements in Vector is done using Enumeration and Iterator interface.
The ArrayList is not a legacy class.
The Vector is a legacy class.

Both super and this are keywords in Java. Details about these are given as follows:

The super keyword

The super keyword is a reserved keyword in Java that is used to refer to the immediate parent class. The super keyword can also invoke the method and constructor of the immediate parent class.

A program that demonstrates the super keyword is given as follows:

class A
{
   int x = 26;
   static int y = 15;
}
public class B extends A
{
   void display()
   {
       System.out.println(super.x);
       System.out.println(super.y);
   }
   public static void main(String[] args)
   {
       B obj = new B();
       obj.display();
   }
}

The output of the above program is as follows:

26
15

The this keyword

The this keyword is a reserved keyword in Java that is used to refer to the current class instance variable. The this keyword can also invoke the method and constructor of the current class. It can also be passed as an argument in the method or constructor call.

A program that demonstrates the super keyword is given as follows:

public class Demo
{
   int x = 25;
   static int y = 12;
   void display()
   {
       this.x = 250;
       System.out.println(x);
       this.y = 120;
       System.out.println(y);
   }
   public static void main(String[] args)
   {
       Demo obj = new Demo();
       obj.display();
   }
}

The output of the above program is as follows:

250
120

Serialization in Java involves writing the object state into a byte stream so that it can be sent to a database or a disk. Deserialization is the reverse process wherein the stream is converted into the object.

The java.io.Serializable interface is implemented by default by the String class as well as all the wrapper classes. The functionality to serialize the objects is provided by the writeObject() method of ObjectOutputStream class. Deserialization of objects and primitive data is done using ObjectInputStream.

A program that demonstrates serialization and deserialization in Java is given as follows:

import java.io.*;
class Employee implements Serializable
{
   int empID;
   String name;
   Employee(int e, String n)
   {
       this.empID = e;
       this.name = n;
   }
}
public class Demo
{
   public static void main(String[] args)
   {
       Employee emp1 = new Employee(251,"Jason Scott");  
       FileOutputStream f = new FileOutputStream("file.txt");  
       ObjectOutputStream o = new ObjectOutputStream(f);  
       o.writeObject(emp1);  
       o.flush();  
       ObjectInputStream i = new ObjectInputStream(new FileInputStream("file.txt"));  
       Employee emp2 = (Employee)i.readObject();  
       System.out.println(emp2.empID + " " + emp2.name);  
       i.close();  
   }
}

The output of the above program is as follows:

251 Jason Scott

Stopping a thread in Java can be a little complicated as there is no working stop method. This is quite different than starting a thread in Java as there is a start() method available. When Java was first released, there was a stop() method in Thread class, but that has since been deprecated.

A program that demonstrates how to stop a thread in Java using a personal stop() method is given as follows:

import static java.lang.Thread.currentThread;
import java.util.concurrent.TimeUnit;
public class Demo
{
   public static void main(String args[]) throws InterruptedException
   {
       Server ser = new Server();
       Thread thread = new Thread(ser, "T1");
       thread.start();
       System.out.println(currentThread().getName() + " is stopping Server thread");
       ser.stop();
       TimeUnit.MILLISECONDS.sleep(200);
       System.out.println(currentThread().getName() + " is finished now");
   }
}
class Server implements Runnable
{
   private volatile boolean exit = false;
   public void run()
   {
       while(!exit)
     {
         System.out.println("The Server is running");
     }
     System.out.println("The Server is now stopped");
   }
   public void stop()
   {
       exit = true;  
   }
}

The output of the above program is as follows:

main is stopping Server thread
The Server is running
The Server is now stopped
main is finished now

A nested class in Java is declared inside a class or interface. There are two types of nested classes i.e. static and non-static. The non-static nested classes are known as inner class in Java whereas the static nested classes are merely known as nested class.

A program that demonstrates an inner class in Java is given as follows:

class Outer
{
  class Inner
  {
     public void display()
     {
          System.out.println("Inside the inner class method");
     }
  }
}
public class Main
{
  public static void main(String[] args)
  {
      Outer.Inner obj = new Outer().new Inner();
      obj.display();
  }
}

The output of the above program is as follows:

Inside the inner class method

A program that demonstrates the nested class in Java is given as follows:

class Outer
{  
  static int num = 67;  
  static class Inner
  {
     public void display()
     {
          System.out.println("The number is: " + num);
     }
  }
}
public class Main
{
  public static void main(String[] args)
  {
      Outer.Inner obj = new Outer.Inner();
      obj.display();
  }
}

The output of the above program is as follows:

The number is: 67

Declaring a variable implies giving a data type to the variable such as int, float, char etc. An example of this is given as follows:

int val;

Defining a variable implies assigning a value to the declared variable. This value is stored in the variable. An example of this is given as follows:

val = 5;

A program that demonstrates declaring a variable and defining a variable in Java is given as follows:

public class Demo
{
  public static void main(String[] args)
  {
      int val;    //declaring a variable
      val = 5;   //defining a variable
      System.out.println("val = " + val);
  }
}

The output of the above program is as follows:

val = 5

Expect to come across this popular question in Java technical interview questions.

Strings in Java are immutable. This means that they are unchangeable or unmodifiable. There are several reasons for this. Some of these are given below:

  • Synchronization and Concurrency

Synchronization issues are solved by making strings in Java as immutable. This is  because they automatically become thread safe if they are immutable.

  • Class Loading

Class Loading has string arguments. If Strings were mutable, then wrong classes could be loaded as mutable objects change their state.

  • Security

Network connections, urls, database connections, usernames/passwords etc. have parameters that are represented as String. These parameters could be easily changed if the Strings were mutable and that would be a security breach.

A program that demonstrates Strings is given as follows:

public class Demo
{  
public static void main(String args[])
{  
  String str = "Snow";  
  str.concat("White");  
  System.out.println(str);
}  
}  

The output of the above program is as follows:

Snow

In the above program, only Snow is printed as Strings are immutable objects.

Both Java and Python are high level languages. Some of the differences between these languages are given as follows:

JavaPython

Java has longer lines of code that are more complex as compared to Python. For example:

public class Demo
{  
public static void main(String args[])
{  
  System.out.println(“Hello World”);
}  
}

Python has shorter and easier lines of code as compared to Java. For example:

print(““Hello World”)

The type of the variable that is used should be declared in Java.

There is no need to declare the type of variable in Python as it is dynamically typed. This is known as duck typing.

JVM is available almost everywhere because of the high portability of Java.

Python is also portable but it is not as popular as Java

Each statement in Java requires a semicolon at the end.

statements in Python do not require a semicolon at the end.

Java database connectivity is quite popular (JDBC) and so Java is frequently used with databases.

Python is not used with databases frequently as its database access layers are weaker than JDBC.

Java is more complicated to use than Python as there is no dynamic programming and the codes are also larger.

Python is easier to use than Java as dynamic programming is used and the codes are also shorter.

Java is faster than Python.

Python is slower than Java.

Curly braces are mandatory in Java to define a block.

Curly braces are not required in Python but indentation is mandatory.

Inter-thread communication involves the communication of Java threads with each other. The three methods are Java that are used to implement inter-thread communication are given as follows:

  • wait()

This method causes the current thread to release the lock. This is done until a specific amount of time has passed or another thread calls the notify() or notifyAll() method for this object.

  • notify()

This method wakes a single thread out of multiple threads on the current object’s monitor. The choice of thread is arbitrary.

  • notifyAll()

This method wakes up all the threads that are on the current object’s monitor.

A program that demonstrates inter-thread communication in Java is given as follows:

class BankCustomer
{  
   int balAmount = 10000;  
   synchronized void withdrawMoney(int amount)
   {
       System.out.println("Withdrawing money");  
       balAmount -= amount;  
       System.out.println("The balance amount is: " + balAmount);  
   }
   synchronized void depositMoney(int amount)
   {
       System.out.println("Depositing money");  
       balAmount += amount;  
       System.out.println("The balance amount is: " + balAmount);  
       notify();  
   }
}  
public class Demo
{  
   public static void main(String args[])
   {
       final BankCustomer cust = new BankCustomer();  
       new Thread()
       {
           public void run()
           {
               cust.withdrawMoney(5000);
           }
       }.start();  
       new Thread()
       {
           public void run()
           {
               cust.depositMoney(2000);
           }
       }.start();  
   }
}

The output of the above program is as follows:

Withdrawing money
The balance amount is: 5000
Depositing money
The balance amount is: 7000

A deadlock is a situation that usually occurs in multi-threading or multi-tasking. It means that two or more threads are waiting indefinitely on each other to release the resources they require to complete their execution.

Deadlocks can be avoided in Java by trying to avoid the possibilities that give rise to them. These possibilities cannot be completely erased but they can definitely be lessened.

Some of the ways to avoid deadlocks in Java are given as follows:

  • Avoid unnecessary locks

Using unnecessary locks can lead to deadlock so only those members should be locked that are actually required.

  • Avoid nested locks

Nested locks to multiple threads are the main reason for deadlocks. So, locks to multiple threads should be avoided if one thread has already been locked.

  • Use Thread.join

If one thread is waiting for another then deadlock occurs. So, Thread.join can be used if the deadlock condition appears for the maximum time required for the execution.

Serialization is the process of changing the state of an object into the byte stream so that the byte stream can return back into a copy of the object

In Java, an object is said to be serializable if its class or parent classes implement either the Serializable interface or the Externalizable interface.

Deserialization is converting the serialized object back into a copy of the object.

During serialization, if we don’t want to write the state of the particular variable in the byte stream, we use the transient keyword. When the JVM comes up to the transient keyword, it ignores the original state of the variable and stores a default value of that data type i.e. 0 for int, 0 for byte, 0.0 for float,etc.

Serialization of an object is done through the FileOutputStream and ObjectOutputStream.

Suppose we create an object obj of class Example:

Example obj = new Example();

It is serialized as follows:

FileOutputStream fos = new FileOutputStream("file_name.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);

The serialized object is deserialized using FileInputStream and ObjectInputStream. The byte stream reverts back to the copy of the object i.e. x

FileInputStream fis = new FileInputStream("file_name.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Example x = (Example)ois.readObject();

Let us see an program for serialization along with deserialization of an object named obj :

import java.io.*;
public class Example implements Serializable
{
   int a = 1, b = 2;      // instance variable
   transient int c = 3;   // transient variable
   public static void main(String[] args) throws Exception
   {
       Example obj = new Example();
       // serialization
       FileOutputStream fos = new FileOutputStream("example_file.txt");
       ObjectOutputStream oos = new ObjectOutputStream(fos);
       oos.writeObject(obj);
       // de-serialization
       FileInputStream fis = new FileInputStream("example_file.txt");
       ObjectInputStream ois = new ObjectInputStream(fis);
       Example x = (Example)ois.readObject();
       System.out.println("a = " + x.a);
       System.out.println("b = " + x.b);
       System.out.println("c = " + x.c);         
   }
}

The output is as follows:

$javac Example.java
$java Example
a = 1
b = 2
c = 0

No, private methods cannot be overridden in Java. The private keyword limits the scope of the method, variable or class with which it is declared.

Private methods in Java are not visible to any other class which limits their scope to the class in which they are declared.

Let us see what happens when we try to override a private method:

class Parent
{
  private void display()
 {
    System.out.println("Super class");      
 }
}
public class Example extends Parent
{
 void display()   // trying to override display()
 {
    System.out.println("Sub class");   
 }
 public static void main(String[] args)
 {
     Parent obj = new Example();  
     obj.display();
 }  
}

The output is as follows:

$javac Example.java
Example.java:17: error: display() has private access in Parent
     obj.method();
        ^
1 error

The program gives a compile time error showing that display() has private access in Parent class and hence cannot be overridden in the subclass Example.

Before Java 7 when we needed to handle more than one exception, we required multiple catch blocks to handle those exceptions.

Let us see an example:

import java.util.*;
public class Example
{
   public static void main(String args[])
   {
       Scanner sc = new Scanner(System.in);
       try
       {
           int n=Integer.parseInt(sc.next());
           System.out.println(n/0);
       }
       catch (ArithmeticException ex)
       {
           System.out.println("Exception caught " + ex);
       }
       catch (NumberFormatException ex)
       {
           System.out.println("Exception caught " + ex);
       }
   }
}

The output here depends on the input.

When we input an integer it will generate an arithmetic exception.

For the following input:

3

The output would be as follows:

$javac Example.java
$java Example
Exception caught java.lang.ArithmeticException: / by zero

For a String or character input, the output would be different.

For the following input:

Hello

The output would is as follows:

$javac Example.java
$java Example
Exception caught java.lang.NumberFormatException: For input string: "Hello"

From Java 7, the multi-catch block was introduced in Java

A single catch block could catch multiple exceptions which are separated symbol.

Let us see an example for the multi-catch block:

import java.util.*;
public class Example
{
   public static void main(String args[])
   {
       Scanner sc = new Scanner(System.in);
       try
       {
           int n=Integer.parseInt(sc.next());
           System.out.println(n/0);
       }
       catch (NumberFormatException | ArithmeticException ex)
       {
           System.out.println("Exception caught " + ex);
       }
   }
}

When we input an integer it will generate an arithmetic exception.

For the following input:

3

The output would be as follows:

$javac Example.java
$java Example
Exception caught java.lang.ArithmeticException: / by zero

For a String or character input, the output would be different.

For the following input:

Hello

The output would is as follows:

$javac Example.java
$java Example
Exception caught java.lang.NumberFormatException: For input string: "Hello"

Annotations in Java are a form syntactic metadata which are used to convey additional information about a program’s elements like constructors, methods, instance variables and classes.

They always start with symbol. Annotations have no direct effect in the compilation of the program.

On the other side, annotations do not completely act like comments as they can transform the compiler’s perspective about the program.

Let us see an example where annotations effect the output of a program. We are trying to override a private method . This will generate an error.

class Parent
{
  private void display()
 {
    System.out.println("Super class");      
 }
}
public class Example extends Parent
{   
   //using the Override annotation
   @Override
 void display()   // trying to override display()
 {
    System.out.println("Sub class");   
 }
 public static void main(String[] args)
 {
     Parent obj = new Example();  
     obj.display();
 }  
}

The output is as follows:

$javac Example.java
Example.java:11: error: method does not override or implement a method from a supertype
   @Override
   ^
Example.java:19: error: display() has private access in Parent
     obj.display();
        ^
2 errors

Basically, Annotations are divided into three categories

  1. Marker Annotations
  2. Full Annotations
  3. Single value Annotations

Marker Annotations: They are used to point out at declaration. These annotations do not have any parameters.

For example:

@ExampleAnnotation()

@Override is an example of a marker annotation.

Full Annotations: These annotations consist of multiple variables’ name,value, pairs.

For example:

@ExampleAnnotation(name=”Program”, value=”Java”)

Single value Annotations: These annotations consist only a single member with a shorthand value.

For example:

@ExampleAnnotation(“running”)

There are a few built-in/ primitive annotations in Java.

3 of the built-in annotations are included in the java.lang package. They are as follows

  • @Deprecated
  •  @Override
  • @SuppressWarnings

4 of them are imported the java.lang.annotation class, namely:

  • @Retention
  • @Documented
  • @Inherited
  • @Target
Annotation
Description
@Deprecated
A marker annotation that indicates that the declaration is outdated and has been updated
@Override
A marker annotation used when a method overrides another method from a parent class
@SuppressWarnings
It is used to generate compiler warnings
@Documented
It is a marker annotation that  tells a tool that an annotation is to be documented
@Inherited
It is used only on declaration of an annotation
@Target
It acts as an annotation to another annotation

Lambda expressions in Java are expressions which implement functional interfaces. Functional interfaces are those interfaces that have only one abstract method.

These expressions were created to reduce the unwieldy overhead code of an anonymous class. An anonymous class is a local class devoid of a name and is declared and instantiated at the same time.

Until Java 8, even for the simplest of operations, additional syntactical code was written with anonymous classes . Lambda expressions were introduced to remove this shortcoming.

The syntax:

lambda operator->body

lambda operator is an parameter list can have 0, 1 or multiple parameters.

Zero parameter:

()->System.out.println(“I have no argument”);

One parameter:

(arg)->System.out.println(“I have one argument”+arg)

Multiple parameters:

(arg1,arg2)->System.out.println(“I have many arguments”+arg1+“ ”+arg2)

Let us use lambda expression to print even integers from a list:

import java.util.*;
public class Example
{
   public static void main(String args[])
   {
       List<Integer> list = new ArrayList<Integer>();
       for(int i=1;i<=10;i++)  // adding 1 to 10 in the integer ArrayList
       {
           list.add(i);
       }
      // printing even elements in list using lambda expression
       list.forEach(arg -> { if (arg%2 == 0) System.out.println(arg); });
   }
}

The output is as follows:

$javac Example.java
$java Example
2
4
6
8
10

The size of a primitive array cannot be increased in Java as it is fixed. If we try to increase the size of the primitive array, it would lead to an ArrayIndexOutOfBoundsException.

import java.util.*;
public class Example
{
   public static void main(String args[])
   {
       int size=10;
      int arr[]=new int[size];
      for(int i=0;i<10;i++)   // initializing the array with values from 0 to 9
      {
          arr[i]=i;
      }
       size++;
       // trying to increase the size and then initialize the 11th element present at 10th index
       arr[size-1]=2;
   }
}

The output is as follows:

$javac Example.java
$java  Example
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at Example.main(Example.java:14)

The code generates an java.lang.ArrayIndexOutOfBoundsException exception.

To increase the size of an array, we need to copy it and increase its size dynamically. The java.util.Arrays class provides Arrays.copyOf() method which helps us to create a new array with an increased size and copy the values of the elements of the original array simultaneously.

The syntax:

Arrays.copyOf(original_array,new_size);

Let us see an example where we copy an array and increase its size:

import java.util.Arrays;
public class Example
{
   public static void main(String[] args)
   {
       int[] arr = {1,2,3,4,5,6,7,8}; // array arr has 8 elements
       System.out.println("Inital array size: "+arr.length);
       // copying the array arr and increasing its size to 10
        int[] crr = Arrays.copyOf(arr, 10);
       System.out.println("Final array size: "+crr.length);
   }
}

The output is as follows:

$javac Example.java
$java Example
Inital array size: 8
Final array size: 10

We can use the ArrayList class to create a dynamic array list but that would occupy a lot more memory. Thus we use the Arrays.copyOf() method.

Using compare methods

Compare two strings in Java using the compareTo() and compareToIgnoreCase() methods. The compareTo() method is used to compare two strings lexicographically or in dictionary order. Each character is translated into a Unicode value for comparison. If both the strings are equal, it returns 0 otherwise it returns positive or negative values.

The compareTo() method returns a positive value if the first string is lexicographically greater than the second string and it returns a negative value if the first string is lexicographically lesser than the second string. The compareToIgnoreCase() method compares two strings lexicographically irrespective of their cases.

Let us see an example where compareTo() and compareToIgnoreCase() methods are used to compare two strings:

public class Example
{
  public static void main(String args[])
  {
     String s1 = "Same string";
     String s2 = "same string";
     int a1= s1.compareTo(s2);
     int a2=s1.compareToIgnoreCase(s2);
     if(a1<0)
       System.out.println("String s2 is greater");
     else if(a1>0)
       System.out.println("String s1 is greater");
     else
       System.out.println("String s1 is equal to String s2");
    if(a2==0)
       System.out.println("After Ignoring the case, s1 and s2 are equal");
     else
       System.out.println("Even after ignoring the case, s1 and s2 are not equal");
  }
}

The output is as follows:

$javac Example.java
$java Example
String s2 is greater
After Ignoring the case, s1 and s2 are equal

Using equals() and equalsIgnoreCase()

The equals() method compares the string to a specific object. It returns a boolean value either true or false. It returns true when argument is not null and the string matches the characters of the object. The equalsIgnoreCase() does the same operation but does not take into account the case of the string and the sequence of characters of the object.

Let us see an example where equals() and equalsIgnoreCase() are used to compare two strings.

public class Example
{
  public static void main(String []args)
  {
     String s1 = "Same String";
     String s2 = "same string";
     System.out.println(s1.equals(s2));
     System.out.println(s1.equalsIgnoreCase(s2));
  }
}

The output is as follows:

$javac Example.java
$java Example
false
true

Using the == operator

We can use the == operator to compare two strings. This operator acts like the equals() method and returns a boolean value as the result.

Let us see the application of == operator for comparison of two strings:

public class Example
{
  public static void main(String []args)
  {
     String s1 = "Same String";
     String s2 = "same string";
     String s3 = "Same String";
     System.out.println(s1==s2);
     System.out.println(s1==s3);
  }
}

The output is as follows:

$javac Example.java
$java Example
false
true

It is quite possible to overload the main() method in Java a