Skip to content

Module 2 : Java

Divyanshu Gour edited this page Apr 19, 2023 · 2 revisions

Java Fundamentals

Declarations and Access Controls

In Java, access control tells the program how much access a variable, class, or method is given. Access control is important because it affects visibility based on different access control types.

Declare Classes

class className {

//Body of the class

}

Declare Interfaces

interface <interface_name>{  
// declare constant fields  
// declare methods that abstract   
// by default.  
}  

Declare Class Members

Sometimes, you want to have variables that are common to all objects. This is accomplished with the static modifier. Fields that have the static modifier in their declaration are called static fields or class variables. They are associated with the class, rather than with any object. Every instance of the class shares a class variable, which is in one fixed location in memory. Any object can change the value of a class variable, but class variables can also be manipulated without creating an instance of the class.

Constructors, Deconstructors

A constructor is used to initialize a variable that means it allocates memory for the same A constructor is nothing but automatic initialization of the object.

There are two types of constructors; depending upon the type, we can add and remove variables.

Default Constructor Parameterized Constructor

Destructor is used to free that memory allocated during initialization. Generally, in java, we don’t need to call the destructor explicitly. Java has a feature of automatic garbage collection.

 class Employee {
 Employee() { //This is constructor. It has same name as class name.
 System.out.println("This is the default constructor");
 }
 } 

 protected void finalize()  
 {  
 System.out.println("Object is destroyed by the Garbage Collector");  
 }  

Access Modifiers (Public, Private, Protected)

The access modifiers in Java specifies the accessibility or scope of a field, method, constructor, or class. We can change the access level of fields, constructors, methods, and class by applying the access modifier on it.

There are four types of Java access modifiers:

Private: The access level of a private modifier is only within the class. It cannot be accessed from outside the class.

Default: The access level of a default modifier is only within the package. It cannot be accessed from outside the package. If you do not specify any access level, it will be the default.

Protected: The access level of a protected modifier is within the package and outside the package through child class. If you do not make the child class, it cannot be accessed from outside the package.

Public: The access level of a public modifier is everywhere. It can be accessed from within the class, outside the class, within the package and outside the package.

Initialization Blocks

In simpler language whenever we use a static keyword and associate it to a block then that block is referred to as a static block. Unlike C++, Java supports a special block, called a static block (also called static clause) that can be used for static initialization of a class. This code inside the static block is executed only once: the first time the class is loaded into memory.

In order to perform any operations while assigning values to an instance data member, an initializer block is used. In simpler terms, the initializer block is used to declare/initialize the common part of various constructors of a class. It runs every time whenever the object is created.

The initializer block contains the code that is always executed whenever an instance is created and it runs each time when an object of the class is created. There are 3 areas where we can use the initializer blocks:

Constructors

Methods

Blocks

Java constructors or constructors in Java is a terminology used to construct something in our programs. A constructor in Java is a special method that is used to initialize objects. The constructor is called when an object of a class is created. It can be used to set initial values for object attributes.

Arrays, Enums

Java array is an object which contains elements of a similar data type. Additionally, The elements of an array are stored in a contiguous memory location. It is a data structure where we store similar elements. We can store only a fixed set of elements in a Java array.

There are two types of array.

  1. Single Dimensional Array

    class Testarray{
    public static void main(String args[]){
    int a[]=new int[3];//declaration and instantiation a[0]=10;//initialization a[1]=20;
    a[2]=70;
    //traversing array
    for(int i=0;i<a.length;i++)//length is the property of array
    System.out.println(a[i]);
    }}

  2. Multidimensional Array

    int[][] myNumbers = { {1, 2, 3, 4}, {5, 6, 7} };

Java supports the feature of an anonymous array, so you don't need to declare the array while passing an array to the method.

 //Java Program to demonstrate the way of passing an anonymous array  
 //to method.  
 public class TestAnonymousArray{  
 //creating a method which receives an array as a parameter  
 static void printArray(int arr[]){  
 for(int i=0;i<arr.length;i++)  
 System.out.println(arr[i]);  
 }  
   
 public static void main(String args[]){  
 printArray(new int[]{10,22,44,66});//passing anonymous array to method  
 }}  

Naming Conventions

Identifiers Type Naming Rules Examples
Class It should start with the uppercase letter.It should be a noun such as Color, Button, System, Thread, etc.Use appropriate words, instead of acronyms. public class Employee{//code snippet}
Interface It should start with the uppercase letter.It should be an adjective such as Runnable, Remote, ActionListener.Use appropriate words, instead of acronyms. interface Printable{//code snippet}
Method It should start with lowercase letter.It should be a verb such as main(), print(), println().If the name contains multiple words, start it with a lowercase letter followed by an uppercase letter such as actionPerformed(). class Employee{// methodvoid draw(){//code snippet}}
Variable It should start with a lowercase letter such as id, name.It should not start with the special characters like & (ampersand), $ (dollar), _ (underscore).If the name contains multiple words, start it with the lowercase letter followed by an uppercase letter such as firstName, lastName.Avoid using one-character variables such as x, y, z. class Employee{// variableint id;//code snippet}
Package It should be a lowercase letter such as java, lang.If the name contains multiple words, it should be separated by dots (.) such as java.util, java.lang. //packagepackage com.javatpoint;class Employee{//code snippet}
Constant It should be in uppercase letters such as RED, YELLOW.If the name contains multiple words, it should be separated by an underscore(_) such as MAX_PRIORITY.It may contain digits but not as the first letter. class Employee{//constantstatic final int MIN_AGE = 18;//code snippet}

Collections and Generics

HashCode and Equals methods.

In java equals() method is used to compare equality of two Objects.

Some principles of equals() method of Object class : If some other object is equal to a given object, then it follows these rules:

Reflexive : for any reference value a, a.equals(a) should return true.

Symmetric : for any reference values a and b, if a.equals(b) should return true then b.equals(a) must return true.

Transitive : for any reference values a, b, and c, if a.equals(b) returns true and b.equals(c) returns true, then a.equals(c) should return true.

Consistent : for any reference values a and b, multiple invocations of a.equals(b) consistently return true or consistently return false, provided no information used in equals comparisons on the object is modified.

 public boolean equals  (Object obj)

 // This method checks if some other Object
 // passed to it as an argument is equal to 
 // the Object on which it is invoked.

hashCode() method returns the hashcode value as an Integer. Hashcode value is mostly used in hashing based collections like HashMap, HashSet, HashTable….etc. This method must be overridden in every class which overrides equals() method.

The general contract of hashCode is:

  • During the execution of the application, if hashCode() is invoked more than once on the same Object then it must consistently return the same Integer value, provided no information used in equals(Object) comparison on the Object is modified. It is not necessary that this Integer value to be remained same from one execution of the application to another execution of the same application.

  • If two Objects are equal, according to the equals(Object) method, then hashCode() method must produce the same Integer on each of the two Objects.

  • If two Objects are unequal, according to the equals(Object) method, It is not necessary the Integer value produced by hashCode() method on each of the two Objects will be distinct. It can be same but producing the distinct Integer on each of the two Objects is better for improving the performance of hashing based Collections like HashMap, HashTable…etc.

     public int hashCode()
     // This method returns the hash code value 
     // for the object on which this method is invoked.
    

Collections classes.

image image

The table below describes the methods available to use against Java Collections for data Manipulation Jobs.

Method Description
add() Add objects to collection.
isEmpty() Returns true if collection is empty
clear() Removes all elements from the collection
remove() Remove a selected object
size() Find the number of elements
stream() Return Sequential elements
toArray() Returns elements in array format
hashCode() Returns Hashcode of the elements
equals(obj X) Compare an element with the collection
iterator() Return an iterator over collection
max() Return max value in the collection
contains() Returns true is a particular value is present
spliterator() Creates splietarator over the elements in the collection
retainAll() Retains elements in the collection

Comparator and Comparable interfaces.

A comparator interface is used to order the objects of user-defined classes. A comparator object is capable of comparing two objects of the same class. Internally the Sort method does call Compare method of the classes it is sorting. To compare two elements, it asks “Which is greater?” Compare method returns -1, 0, or 1 to say if it is less than, equal, or greater to the other. It uses this result to then determine if they should be swapped for their sort.

 class Sortbyroll implements Comparator<Student> {

     // Method
     // Sorting in ascending order of roll number
     public int compare(Student a, Student b)
     {

         return a.rollno - b.rollno;
     }
 }

The Comparable interface is used to compare an object of the same class with an instance of that class, it provides ordering of data for objects of the user-defined class. The class has to implement the java.lang.Comparable interface to compare its instance, it provides the compareTo method that takes a parameter of the object of that class. In this article, we will see how we can sort an array of pairs of different data types on the different parameters of comparison.

 import java.io.*;
 import java.util.*;

 class Pair implements Comparable<Pair> {
    String firstName;
     String lastName;

     public Pair(String x, String y)
     {
	     this.firstName = x;
	     this.lastName = y;
     } 

     public String toString()
     {
          		return "( " + firstName + " , " + lastName + " )";
     }

     @Override public int compareTo(Pair a)
     {
	     // if the string are not equal
 	     if (this.firstName.compareTo(a.firstName) != 0) {
		     return this.firstName.compareTo(a.firstName);
	     }
	     else {
		     // we compare lastName if firstNames are equal
		     return this.lastName.compareTo(a.lastName);
	     }
     }
 }

 public class GFG {
     public static void main(String[] args)
     {

	     int n = 4;
	     Pair arr[] = new Pair[n];
	     arr[0] = new Pair("raj", "kashup");
	     arr[1] = new Pair("rahul", "singh");
	     arr[2] = new Pair("reshmi", "dubey");
	     arr[3] = new Pair("rahul", "jetli");

	     // Sorting the array
	     Arrays.sort(arr);

	     // printing the
	     // Pair array
	     print(arr);
     }

     public static void print(Pair[] arr)
     {
	     for (int i = 0; i < arr.length; i++) {
		     System.out.println(arr[i]);
	     }
      }
 }

Collections Sorting

java.util.Collections.sort() method is present in java.util.Collections class. It is used to sort the elements present in the specified list of Collection in ascending order. It works similar to java.util.Arrays.sort() method but it is better than as it can sort the elements of Array as well as linked list, queue and many more present in it.

// Java program to demonstrate working of Collections.sort() import java.util.*;

public class Collectionsorting
 {
public static void main(String[] args)
{
	// Create a list of strings
	ArrayList<String> al = new ArrayList<String>();
	al.add("Geeks For Geeks");
	al.add("Friends");
	al.add("Dear");
	al.add("Is");
	al.add("Superb");

	/* Collections.sort method is sorting the
	elements of ArrayList in ascending order. */
	Collections.sort(al);

	// Let us print the sorted list
	System.out.println("List after the use of" +
					" Collection.sort() :\n" + al);
}
}

For sorting in reverse order

Collections.sort(al, Collections.reverseOrder());

 class Sortbyroll implements Comparator<Student>
 {
     // Used for sorting in ascending order of
     // roll number
     public int compare(Student a, Student b)
     {
        return a.rollno - b.rollno;
     }
 }

Using a Comparator and Comparable interface

Collections.sort(ar, new Sortbyroll());

Generics

Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types. An entity such as class, interface, or method that operates on a parameterized type is a generic entity.

Types of Java Generics

Generic Method: Generic Java method takes a parameter and returns some value after performing a task. It is exactly like a normal function, however, a generic method has type parameters that are cited by actual type. This allows the generic method to be used in a more general way. The compiler takes care of the type of safety which enables programmers to code easily since they do not have to perform long, individual type castings.

 static <T> void genericDisplay(T element)
    {
         System.out.println(element.getClass().getName() + " = " + element);
    }

Generic Classes: A generic class is implemented exactly like a non-generic class. The only difference is that it contains a type parameter section. There can be more than one type of parameter, separated by a comma. The classes, which accept one or more parameters, ​are known as parameterized classes or parameterized types.

 class Test<T, U>
 {
     T obj1;  // An object of type T
     U obj2;  // An object of type U

     // constructor
     Test(T obj1, U obj2)
     {
         this.obj1 = obj1;
         this.obj2 = obj2;
     }

     // To print objects of T and U
     public void print()
    {
          System.out.println(obj1);
          System.out.println(obj2);
     }
 }

Control Flow Statements

Java compiler executes the code from top to bottom. The statements in the code are executed according to the order in which they appear. However, Java provides statements that can be used to control the flow of Java code. Such statements are called control flow statements. It is one of the fundamental features of Java, which provides a smooth flow of program.

For Loop

In Java, for loop is similar to C and C++. It enables us to initialize the loop variable, check the condition, and increment/decrement in a single line of code. We use the for loop only when we exactly know the number of times, we want to execute the block of code.

for(initialization, condition, increment/decrement) {    
//block of statements    
}

Enhanced For Loop

Java provides an enhanced for loop to traverse the data structures like array or collection. In the for-each loop, we don't need to update the loop variable. The syntax to use the for-each loop in java is given below.

for(data_type var : array_name/collection_name){    
//statements    
}    

While Loop

The while loop is also used to iterate over the number of statements multiple times. However, if we don't know the number of iterations in advance, it is recommended to use a while loop. Unlike for loop, the initialization and increment/decrement doesn't take place inside the loop statement in while loop.

while(condition){    
//looping statements    
}    

Switch

In Java, Switch statements are similar to if-else-if statements. The switch statement contains multiple blocks of code called cases and a single case is executed based on the variable which is being switched. The switch statement is easier to use instead of if-else-if statements. It also enhances the readability of the program.

switch (expression){  
    case value1:  
        statement1;  
        break;  
    .  
    .  
    .  
    case valueN:  
        statement;  
        break;  
    default:  
        default statement;  
}  

Do Loop

The do-while loop checks the condition at the end of the loop after executing the loop statements. When the number of iteration is not known and we have to execute the loop at least once, we can use do-while loop.

do     
{    
//statements    
} while (condition);    

Continue/Break

As the name suggests, the break statement is used to break the current flow of the program and transfer the control to the next statement outside a loop or switch statement. However, it breaks only the inner loop in the case of the nested loop.

for(int i = 0; i<= 10; i++) {  
    System.out.println(i);  
    if(i==6) {  
        break;  
}  
}  

Unlike break statement, the continue statement doesn't break the loop, whereas, it skips the specific part of the loop and jumps to the next iteration of the loop immediately.

for (int j = i; j<=5; j++) {    
    if(j == 4) {  
        continue;  
    }  
    System.out.println(j);  
}  

Labelled/Unlabelled Statements

Yes. Java supports labeled statements. You can put a label before a for statement and use the break/continue controls to jump to that label.

public class Tester { public static void main(String args[]) {

  first:
     for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++){
           if(i == 1){
              continue first;
           }
           System.out.print(" [i = " + i + ", j = " + j + "] ");
        }
     }
     System.out.println();
 
  second:
     for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++){
           if(i == 1){
              break second;
           }
           System.out.print(" [i = " + i + ", j = " + j + "] ");
        }
     }
}

}

Dates

Working with Dates, TimeZone

Java does not have a built-in Date class, but we can import the java.time package to work with the date and time API. The package includes many date and time classes. For example:

Class Description
LocalDate Represents a date (year, month, day (yyyy-MM-dd))
LocalTime Represents a time (hour, minute, second and nanoseconds (HH-mm-ss-ns))
LocalDateTime Represents both a date and a time (yyyy-MM-dd-HH-mm-ss-ns)
DateTimeFormatter Formatter for displaying and parsing date-time objects

Date Parsing, Conversion

To display the current date, import the java.time.LocalDate class, and use its now() method:

To display the current time (hour, minute, second, and nanoseconds), import the java.time.LocalTime class, and use its now() method:

To display the current date and time, import the java.time.LocalDateTime class, and use its now() method:

You can use the DateTimeFormatter class with the ofPattern() method in the same package to format or parse date-time objects.

LocalDateTime myDateObj = LocalDateTime.now();
System.out.println("Before formatting: " + myDateObj);
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");

String formattedDate = myDateObj.format(myFormatObj);
System.out.println("After formatting: " + formattedDate);
yyyy-MM-dd "1988-09-29" Try it »
dd/MM/yyyy "29/09/1988" Try it »
dd-MMM-yyyy "29-Sep-1988" Try it »
E, MMM dd yyyy "Thu, Sep 29 1988"

Exceptions

Using Try-Catch and Try-with-Resource

The try statement allows you to define a block of code to be tested for errors while it is being executed.

The catch statement allows you to define a block of code to be executed, if an error occurs in the try block.

The try and catch keywords come in pairs:

 try {
   //  Block of code to try
 }
 catch(Exception e) {
   //  Block of code to handle errors
 }

In Java, the Try-with-resources statement is a try statement that declares one or more resources in it. A resource is an object that must be closed once your program is done using it. For example, a File resource or a Socket connection resource. The try-with-resources statement ensures that each resource is closed at the end of the statement execution. If we don’t close the resources, it may constitute a resource leak and also the program could exhaust the resources available to it.

You can pass any object as a resource that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable.

By this, now we don’t need to add an extra finally block for just passing the closing statements of the resources. The resources will be closed as soon as the try-catch block is executed.

 try(declare resources here) {
    // use resources
 }
 catch(FileNotFoundException e) {
     // exception handling
 }

Finally

Java finally block is a block used to execute important code such as closing the connection, etc.

Java finally block is always executed whether an exception is handled or not. Therefore, it contains all the necessary statements that need to be printed regardless of the exception occurs or not.

The finally block follows the try-catch block.

image

try{    
 //below code do not throw any exception  
 int data=25/5;    
 System.out.println(data);    
}    
//catch won't be executed  
catch(NullPointerException e){  
System.out.println(e);  
}    
//executed regardless of exception occurred or not  
finally {  
System.out.println("finally block is always executed");  
}    

Exception Hierarchy

The java.lang.Throwable class is the root class of Java Exception hierarchy inherited by two subclasses: Exception and Error. The hierarchy of Java Exception classes is given below:

image

There are mainly two types of exceptions: checked and unchecked. An error is considered as the unchecked exception. However, according to Oracle, there are three types of exceptions namely:

  1. Checked Exception

  2. Unchecked Exception

  3. Error

1) Checked Exception The classes that directly inherit the Throwable class except RuntimeException and Error are known as checked exceptions. For example, IOException, SQLException, etc. Checked exceptions are checked at compile-time.

2) Unchecked Exception The classes that inherit the RuntimeException are known as unchecked exceptions. For example, ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException, etc. Unchecked exceptions are not checked at compile-time, but they are checked at runtime.

3) Error Error is irrecoverable. Some example of errors are OutOfMemoryError, VirtualMachineError, AssertionError etc.

Keyword Description
try The "try" keyword is used to specify a block where we should place an exception code. It means we can't use try block alone. The try block must be followed by either catch or finally.
catch The "catch" block is used to handle the exception. It must be preceded by try block which means we can't use catch block alone. It can be followed by finally block later.
finally The "finally" block is used to execute the necessary code of the program. It is executed whether an exception is handled or not.
throw The "throw" keyword is used to throw an exception.
throws The "throws" keyword is used to declare exceptions. It specifies that there may occur an exception in the method. It doesn't throw an exception. It is always used with method signature.

Creating a custom Exception

In Java, we can create our own exceptions that are derived classes of the Exception class. Creating our own Exception is known as custom exception or user-defined exception. Basically, Java custom exceptions are used to customize the exception according to user need.

 public class WrongFileNameException extends Exception {  
     public WrongFileNameException(String errorMessage) {  
     super(errorMessage);  
     }  
 }  

File IO

Understanding of various File related classes

Read/Write Files

FileWriter: FileWriter is useful to create a file writing characters into it.

  • This class inherits from the OutputStream class.

  • The constructors of this class assume that the default character encoding and the default byte-buffer size are acceptable. To specify these values yourself, construct an OutputStreamWriter on a FileOutputStream.

  • FileWriter is meant for writing streams of characters. For writing streams of raw bytes, consider using a FileOutputStream.

  • FileWriter creates the output file if it is not present already.

      // attach a file to FileWriter
      FileWriter fw=new FileWriter("output.txt");
    
      // read character wise from string and write
      // into FileWriter
      for (int i = 0; i < str.length(); i++)
          fw.write(str.charAt(i));
    
      System.out.println("Writing successful");
      //close the file
      fw.close();
    

FileReader is useful to read data in the form of characters from a ‘text’ file.

  • This class inherited from the InputStreamReader Class.

  • The constructors of this class assume that the default character encoding and the default byte-buffer size are appropriate. To specify these values yourself, construct an InputStreamReader on a FileInputStream.

  • FileReader is meant for reading streams of characters. For reading streams of raw bytes, consider using a FileInputStream.

      FileReader fr=null;
      try
      {
          fr = new FileReader("text");
      }
      catch (FileNotFoundException fe)
      {
          System.out.println("File not found");
      }
    
      // read from FileReader till the end of file
      while ((ch=fr.read())!=-1)
          System.out.print((char)ch);
    
      // close the file
      fr.close();
    

Various IO Stream Classes and their usages.

image

Methods of Java IO OutputStreams

flush() – The flush() method is used for flushing the outputStream This method forces the buffered output bytes to be written out.

close() – The close() method is used to close the outputStream and to release the system resources affiliated with the stream.

write(int b) – The write(int b) method is used to write the specified byte to the outputStream.

write(byte [] b) – The write(byte [] b) method is used to write bytes of length b.length from the specified byte array to the outputStream.

image

Methods of Java IO InputStreams

read() – The read() method is used to read the next byte of data from the Input Stream. The value byte is passed on a scale of 0 to 255. If no byte is free because the end of the stream has arrived, the value -1 is passed.

mark(int arg) – The mark(int arg) method is used to mark the current position of the input stream. It sets read to limit, i.e., the maximum number of bytes that can be read before the mark position becomes invalid.

reset() – The reset() method is invoked by mark() method. It changes the position of the input stream back to the marked position.

close() – The close() method is used to close the input stream and releases system resources associated with this stream to Garbage Collector.

read(byte [] arg) – The read(byte [] arg) method is used to read the number of bytes of arg.length from the input stream to the buffer array arg. The bytes read by read() method are returned as an int. If the length is zero, then no bytes are read, and 0 is returned unless there is an effort to read at least one byte.

skip(long arg) – The skip(long arg) method is used to skip and discard arg bytes in the input stream.

markSupported() – The markSupported() method tests if the inputStream supports the mark and reset methods. The markSupported method of Java IO InputStream yields false by default.

Class Hierarchy of various File IO classes.

image

Garbage Collection

Java Memory Management Overview

In Java, the programmer need not care for all those objects which are no longer in use. Garbage collector destroys these objects. The main objective of Garbage Collector is to free heap memory by destroying unreachable objects. The garbage collector is the best example of the Daemon thread as it is always running in the background.

How Does Garbage Collection in Java works? Java garbage collection is an automatic process. Automatic garbage collection is the process of looking at heap memory, identifying which objects are in use and which are not, and deleting the unused objects. An in-use object, or a referenced object, means that some part of your program still maintains a pointer to that object. An unused or unreferenced object is no longer referenced by any part of your program. So the memory used by an unreferenced object can be reclaimed. The programmer does not need to mark objects to be deleted explicitly. The garbage collection implementation lives in the JVM.

Types of Activities in Java Garbage Collection Two types of garbage collection activity usually happen in Java. These are:

Minor or incremental Garbage Collection: It is said to have occurred when unreachable objects in the young generation heap memory are removed. Major or Full Garbage Collection: It is said to have occurred when the objects that survived the minor garbage collection are copied into the old generation or permanent generation heap memory are removed. When compared to the young generation, garbage collection happens less frequently in the old generation.

Important Concepts Related to Garbage Collection in Java

1. Unreachable objects: An object is said to be unreachable if it doesn’t contain any reference to it. Also, note that objects which are part of the island of isolation are also unreachable.

Integer i = new Integer(4);
// the new Integer object is reachable  via the reference in 'i' 
i = null;
// the Integer object is no longer reachable. 
garbage collection

2. Eligibility for garbage collection: An object is said to be eligible for GC(garbage collection) if it is unreachable. After i = null, integer object 4 in the heap area is suitable for garbage collection in the above image.

Ways to make an object eligible for Garbage Collector

Even though the programmer is not responsible for destroying useless objects but it is highly recommended to make an object unreachable(thus eligible for GC) if it is no longer required.

There are generally four ways to make an object eligible for garbage collection.

Nullifying the reference variable

Re-assigning the reference variable

An object created inside the method

Island of Isolation

Ways for requesting JVM to run Garbage Collector

Once we make an object eligible for garbage collection, it may not destroy immediately by the garbage collector. Whenever JVM runs the Garbage Collector program, then only the object will be destroyed. But when JVM runs Garbage Collector, we can not expect.

We can also request JVM to run Garbage Collector. There are two ways to do it :

Using System.gc() method: System class contain static method gc() for requesting JVM to run Garbage Collector.

Using Runtime.getRuntime().gc() method: Runtime class allows the application to interface with the JVM in which the application is running. Hence by using its gc() method, we can request JVM to run Garbage Collector. There is no guarantee that any of the above two methods will run Garbage Collector. The call System.gc() is effectively equivalent to the call : Runtime.getRuntime().gc()

Finalization Just before destroying an object, Garbage Collector calls finalize() method on the object to perform cleanup activities. Once finalize() method completes, Garbage Collector destroys that object. finalize() method is present in Object class with the following prototype.

 protected void finalize() throws Throwable

Based on our requirement, we can override finalize() method for performing our cleanup activities like closing connection from the database.

The finalize() method is called by Garbage Collector, not JVM. However, Garbage Collector is one of the modules of JVM. Object class finalize() method has an empty implementation. Thus, it is recommended to override the finalize() method to dispose of system resources or perform other cleanups.

The finalize() method is never invoked more than once for any object.

If an uncaught exception is thrown by the finalize() method, the exception is ignored, and the finalization of that object terminates.

Inner Classes

Regular Inner Classes

It can access any private instance variable of the outer class. Like any other instance variable, we can have access modifier private, protected, public, and default modifier. Like class, an interface can also be nested and can have access specifiers.

class OuterClass {
   int x = 10;

   class InnerClass {
      public int myInnerMethod() {
        return x;
      }
   }
 }

 public class Main {
     public static void main(String[] args) {
       OuterClass myOuter = new OuterClass();
       OuterClass.InnerClass myInner = myOuter.new InnerClass();
       System.out.println(myInner.myInnerMethod());
     }
  } 

Method-Local Inner Class

Inner class can be declared within a method of an outer class

// Java program to illustrate // working of local inner classes

 public class Outer
 {
private void getValue()
{
	// Note that local variable(sum) must be final till JDK 7
	// hence this code will work only in JDK 8
	int sum = 20;
	
	// Local inner Class inside method
	class Inner
	{
		public int divisor;
		public int remainder;
		
		public Inner()
		{
			divisor = 4;
			remainder = sum%divisor;
		}
		private int getDivisor()
		{
			return divisor;
		}
		private int getRemainder()
		{
			return sum%divisor;
		}
		private int getQuotient()
		{
			System.out.println("Inside inner class");
			return sum / divisor;
		}
	}
	
	Inner inner = new Inner();
	System.out.println("Divisor = "+ inner.getDivisor());
	System.out.println("Remainder = " + inner.getRemainder());
	System.out.println("Quotient = " + inner.getQuotient());
}

public static void main(String[] args)
{
	Outer outer = new Outer();
	outer.getValue();
}
 }

Anonymous Inner Class

Anonymous inner classes are declared without any name at all. They are created in two ways.

  1. As a subclass of the specified type
  2. As an implementer of the specified interface

Based on declaration and behavior, there are 3 types of anonymous Inner classes:

  • Anonymous Inner class that extends a class

      // Using Anonymous Inner class that extends a class
      // Here a Thread class
      Thread t = new Thread() {
         
          // run() method for the thread
          public void run()
          {
              // Print statement for child thread
              // execution
              System.out.println("Child Thread");
          }
      };
    
  • Anonymous Inner class that implements an interface

      // Here we are using Anonymous Inner class
      // that implements a interface i.e. Here Runnable
      // interface
      Runnable r = new Runnable() {
         
          // run() method for the thread
          public void run()
          {
              // Print statement when run() is invoked
              System.out.println("Child Thread");
          }
      };
    
  • Anonymous Inner class that defines inside method/constructor argument

      // Using Anonymous Inner class that define inside
      // argument
      // Here constructor argument
      Thread t = new Thread(new Runnable() {
         
          public void run()
          {
              System.out.println("Child Thread");
          }
      });
    

Static Inner Class

Static nested classes are not technically inner classes. They are like a static member of outer class.

 class OuterClass {
   int x = 10;

   static class InnerClass {
     int y = 5;
   }
 }

 public class Main {
   public static void main(String[] args) {
     OuterClass.InnerClass myInner = new OuterClass.InnerClass();
     System.out.println(myInner.y);
   }
 } 

Java Stream API

Introduced in Java 8, the Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result.

The features of Java stream are –

  • A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.

  • Streams don’t change the original data structure, they only provide the result as per the pipelined methods.

  • Each intermediate operation is lazily executed and returns a stream as a result, hence various intermediate operations can be pipelined.

  • Terminal operations mark the end of the stream and return the result.

Different Operations On Streams-

Intermediate Operations:

map: The map method is used to returns a stream consisting of the results of applying the given function to the elements of this stream.

 List number = Arrays.asList(2,3,4,5);
 List square = number.stream().map(x->x*x).collect(Collectors.toList());

filter: The filter method is used to select elements as per the Predicate passed as argument.

 List names = Arrays.asList("Reflection","Collection","Stream");
 List result = names.stream().filter(s->s.startsWith("S")).collect(Collectors.toList());

sorted: The sorted method is used to sort the stream.

 List names = Arrays.asList("Reflection","Collection","Stream");
 List result = names.stream().sorted().collect(Collectors.toList());

Terminal Operations:

collect: The collect method is used to return the result of the intermediate operations performed on the stream.

 List number = Arrays.asList(2,3,4,5,3);
 Set square = number.stream().map(x->x*x).collect(Collectors.toSet());

forEach: The forEach method is used to iterate through every element of the stream.

 List number = Arrays.asList(2,3,4,5);
 number.stream().map(x->x*x).forEach(y->System.out.println(y));

reduce: The reduce method is used to reduce the elements of a stream to a single value.The reduce method takes a BinaryOperator as a parameter.

 List number = Arrays.asList(2,3,4,5);
 int even = number.stream().filter(x->x%2==0).reduce(0,(ans,i)-> ans+i);

Lambda Expressions

A lambda expression is a short block of code that takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.

(parameter1, parameter2) -> { code block }

Functional Interfaces

A functional interface is an interface that contains only one abstract method. They can have only one functionality to exhibit. From Java 8 onwards, lambda expressions can be used to represent the instance of a functional interface. A functional interface can have any number of default methods. Runnable, ActionListener, Comparable are some of the examples of functional interfaces.

 @FunctionalInterface

 interface Square {
   int calculate(int x);
 }

Supplier, Consumer and BiConsumer

Supplier is a functional interface, in Java 8 under package java.util.function, that represents the structure and does not take any input but returns an output. This functional interface can be used as the assignment target for a lambda expression or method reference.

 @FunctionalInterface
 public interface Supplier<T> {

    T get();

 }

Consumer functional interface in Java 8 under package java.util.function represents the structure and takes input but does not return any output. This functional interface can be used as the assignment target for a lambda expression or method reference.

 @FunctionalInterface
 public interface Consumer<T> {

   void accept(T t);

   default Consumer<T> andThen(Consumer<? super T> after);

 }

BiConsumer is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. It represents an operation that accepts two input arguments and returns no result. This is the two-arity specialization of Consumer. Unlike most other functional interfaces, BiConsumer is expected to operate via side-effects.

 @FunctionalInterface
 public interface BiConsumer<T, U> {

   void accept(T t, U u);

   default BiConsumer<T,U>	andThen(BiConsumer<? super T,? super U> after)

 }

Predicate and BiPredicate

The Functional Interface PREDICATE is defined in the java.util.function package. It improves manageability of code, helps in unit-testing them separately, and contain some methods like:

  • isEqual(Object targetRef) : Returns a predicate that tests if two arguments are equal according to Objects.equals(Object, Object).

  • and(Predicate other) : Returns a composed predicate that represents a short-circuiting logical AND of this predicate and another.

  • negate() : Returns a predicate that represents the logical negation of this predicate.

  • or(Predicate other) : Returns a composed predicate that represents a short-circuiting logical OR of this predicate and another.

  • test(T t) : Evaluates this predicate on the given argument.boolean test(T t)

Function and BiFunction

The Function interface is a pre-defined functional interface that can be used as an assignment target for a lambda expression or method reference. It takes a single parameter and returns result by calling the apply() method. While the BiFunction interface is also a pre-defined functional interface that takes two parameters and returns a result. It is similar to the Function interface except it takes two parameters.

@FunctionalInterface
public interface Function<T, R>

@FunctionalInterface
public interface BiFunction<T, U, R>

EXAMPLE:

import java.util.function.BiFunction;
import java.util.function.Function;

public class SampleFunctionBiFunctionTest {
public static void main(String[] args) {
  Function<Integer, Integer> printNumber = a -> a*10;
  System.out.println("The number is: "+ printNumber.apply(10));

  BiFunction<Integer, Integer, Integer> add = (a, b) -> a+b;
  System.out.println("The addition of two numbers are: "+ add.apply(3,2));
}
}

UnaryOperator and BinaryOperator

java.util.function.UnaryOperator is a java 8 functional interface that extends java.util.function.Function. UnaryOperator is used to work on a single operand. It returns the same type as an operand. UnaryOperator can be used as lambda expression to pass as an argument. While defining UnaryOperator, we need to define Function.apply(Object) where Function will be the instance of UnaryOperator.

 UnaryOperator<Integer> uOp = (Integer i) -> i * 10;

java.util.function.BinaryOperator is a functional interface that can be assigned as lambda expression. BinaryOperator extends java.util.function.BiFunction. It accepts two operands of the same type and process it and then returns results of the same type as operands.

 BinaryOperator<Integer> bOp = (Integer i1, Integer i2) -> i1 * i2 * 10;

Using Java Streams with Collections.

Filtering Collection by using Stream

  List<Float> productPriceList2 =productsList.stream()  
                                 .filter(p -> p.price > 30000)// filtering data  
                                 .map(p->p.price)        // fetching price  
                                 .collect(Collectors.toList()); //

Java Stream Iterating

  Stream.iterate(1, element->element+1)  
    .filter(element->element%5==0)  
    .limit(5)  
    .forEach(System.out::println);

Filtering and Iterating Collection

     productsList.stream()  
                         .filter(product -> product.price == 30000)  
                         .forEach(product -> System.out.println(product.name));

reduce() Method in Collection

  Float totalPrice = productsList.stream()  
                .map(product->product.price)  
                .reduce(0.0f,(sum, price)->sum+price);  

Sum by using Collectors Methods

   double totalPrice3 = productsList.stream()  
                    .collect(Collectors.summingDouble(product->product.price)); 

Find Max and Min Product Price

  Product productA = productsList.stream().max((product1, product2)->product1.price > product2.price ? 1: -1).get();    

  Product productB = productsList.stream().min((product1, product2)->product1.price > product2.price ? 1: -1).get();    

count() Method in Collection

  long count = productsList.stream()  
                .filter(product->product.price<30000)  
                .count();  

Convert List into Set

 Set<Float> productPriceList =   
        productsList.stream()  
        .filter(product->product.price < 30000)   // filter product on the base of price  
        .map(product->product.price)  
        .collect(Collectors.toSet());

Convert List into Map

 Map<Integer,String> productPriceMap =   
        productsList.stream()  
                    .collect(Collectors.toMap(p->p.id, p->p.name));  

Method Reference in stream

 List<Float> productPriceList =   
              productsList.stream()  
                        .filter(p -> p.price > 30000) // filtering data  
                        .map(Product::getPrice)         // fetching price by referring getPrice method  
                        .collect(Collectors.toList());  // collecting as list  

OOPS

Encapsulation

Encapsulation is defined as the wrapping up of data under a single unit. It is the mechanism that binds together code and the data it manipulates. Another way to think about encapsulation is, that it is a protective shield that prevents the data from being accessed by the code outside this shield.

 // fields to calculate area
 class Area {

 int length;
 int breadth;

 // constructor to initialize values
 Area(int length, int breadth) {
     this.length = length;
     this.breadth = breadth;
 }

 // method to calculate area
 public void getArea() {
     int area = length * breadth;
     System.out.println("Area: " + area);
 }   
 }

 class Main {
 public static void main(String[] args) {
     Area rectangle = new Area(2, 16);
     rectangle.getArea();
 }
 }

Inheritance

Inheritance in Java is a mechanism in which one object acquires all the properties and behaviors of a parent object. The idea behind inheritance in Java is that you can create new classes that are built upon existing classes. When you inherit from an existing class, you can reuse methods and fields of the parent class. Moreover, you can add new methods and fields in your current class also.

Terms used in Inheritance:

Class: A class is a group of objects which have common properties. It is a template or blueprint from which objects are created.

Sub Class/Child Class: Subclass is a class which inherits the other class. It is also called a derived class, extended class, or child class.

Super Class/Parent Class: Superclass is the class from where a subclass inherits the features. It is also called a base class or a parent class.

Reusability: As the name specifies, reusability is a mechanism which facilitates you to reuse the fields and methods of the existing class when you create a new class. You can use the same fields and methods already defined in the previous class.

 class Employee{  
       float salary=40000;  
 }  
 class Programmer extends Employee{  
       int bonus=10000;  
  public static void main(String args[]){  
         Programmer p=new Programmer();  
         System.out.println("Programmer salary is:"+p.salary);  
         System.out.println("Bonus of Programmer is:"+p.bonus);  
 }  
 }  

Types of inheritance in java:

Single-level inheritance Multi-level Inheritance Hierarchical Inheritance Multiple Inheritance(only achievable by interface) Hybrid Inheritance

Polymorphism

The word polymorphism means having many forms. In simple words, we can define polymorphism as the ability of a message to be displayed in more than one form.

In Java polymorphism is mainly divided into two types:

Compile-time Polymorphism It is also known as static polymorphism. This type of polymorphism is achieved by function overloading or operator overloading.

// Java Program for Method overloading // By using Different Types of Arguments
// Class 1 // Helper class class Helper {

 // Method with 2 integer parameters
 static int Multiply(int a, int b)
{

     // Returns product of integer numbers
     return a * b;
 }

 // Method 2
// With same name but with 2 double parameters
 static double Multiply(double a, double b)
 {

     // Returns product of double numbers
     return a * b;
}

}

// Class 2 // Main class class GFG {

 // Main driver method
 public static void main(String[] args)
 {

     // Calling method by passing
     // input as in arguments
     System.out.println(Helper.Multiply(2, 4));
     System.out.println(Helper.Multiply(5.5, 6.3));
 }

}

Runtime Polymorphism* It is also known as Dynamic Method Dispatch. It is a process in which a function call to the overridden method is resolved at Runtime. This type of polymorphism is achieved by Method Overriding. Overriding, on the other hand, occurs when a derived class has a definition for one of the member functions of the base class. That base function is said to be overridden.

Overriding/Overloading

Overloading occurs when two or more methods in one class have the same method name but different parameters.

Overriding occurs when two methods have the same method name and parameters. One of the methods is in the parent class, and the other is in the child class. Overriding allows a child class to provide the specific implementation of a method that is already present in its parent class.​

Operators

Instanceof

The java instanceof operator is used to test whether the object is an instance of the specified type (class or subclass or interface).

The instanceof in java is also known as type comparison operator because it compares the instance with type. It returns either true or false. If we apply the instanceof operator with any variable that has null value, it returns false.

 class Simple1{  
  public static void main(String args[]){  
  Simple1 s=new Simple1();  
 System.out.println(s instanceof Simple1);//true  
  }  
 }  

Conditional

Operators constitute the basic building block to any programming language. Java too provides many types of operators which can be used according to the need to perform various calculations and functions, be it logical, arithmetic, relational, etc. They are classified based on the functionality they provide.

Types of Operators:

Arithmetic Operators

Unary Operators

Assignment Operator

Relational Operators

Logical Operators

Ternary Operator

Bitwise Operators

Shift Operators

Logical

In Java, conditional operators check the condition and decides the desired result on the basis of both conditions.

Operator Symbol
Conditional or Logical AND &&
Conditional or Logical OR ||
Ternary Operator ?:

Arithmetic

image

Serialization

ObjectOutputStream and ObjectInputStream.

This Java File IO tutorial helps you understand and use the object streams classes ObjectInputStream and ObjectOutputStream in the Java File I/O API.

You use object streams to read and write Java objects in binary format. ObjectInputStream and ObjectOutputStream are the main object stream classes provided by the Java File I/O API.

The ObjectOutputStream class implements the ObjectOutput interface that defines a method for writing an object to an output stream: writeObject(Object): writes an object to the underlying storage or stream. This method throws IOException if an I/O error occurs. The process of writing an object to an output stream is called Serialization.

The ObjectOutput interface extends from the DataOutput interface, which means an ObjectOutputStream inherits all behaviors of writing primitive types and Strings like a DataOutputStream.

Likewise, the ObjectInputStream class implements the ObjectInput interface that defines a method for reading an object from an input stream: readObject(): reads and returns an object. This method throws ClassNotFoundException if the class of the serialized object cannot be found, and throws IOException if an I/O error occurs.

The process of reconstructing an object from an input stream is called deserialization.

The ObjectInput interface extends from the DataInput interface, which means an ObjectInputStream also has behaviors of reading primitive types and Strings like a DataInputStream.

The following class diagram depicts the API hierarchy of object stream classes and interfaces:

image

Note that only objects of classes that implement the java.io.Serializable interface can be written to and read from an output/input stream. Serializable is a marker interface, which doesn’t define any methods. Only objects that are marked ‘serializable’ can be used with ObjectOutputStream and ObjectInputStream.

Object Graphs.

An Object Graph is the set of objects which will be serialized automatically, if the object which contains reference to them is serialized.

In other words, we can say that when we serialize any object and if it contains any other object reference then JVM serializes the object and as well as its object references.

In the below example when we serialize the Student class Object, the Address class object will be automatically seriliazed.

class Address implements Serializable{
public String city;
public String state;
public String picode;
public String country;

public Address(String city, String state, String picode, String country) {
    this.city = city;
    this.state = state;
    this.picode = picode;
    this.country = country;
}
}


//Student class is serializable we will be able to write it to a file
public class Student implements Serializable{
public String name;
// public String dob;
// public Date dob;
public Object dob;
public Address address; 
// This is used to identify the version of the class. If the version of the class changes, 
// then the serialVersionUID also changes. 
// This is used to check if the class is compatible with the serialized object or not.
public static final long serialVersionUID = 1L;

public Student(String name, String dob, Address address) {
    this.name = name;
    this.dob = dob;
    // this.dob = new Date(dob);
    // this.dob = new Date(dob);
    this.address = address;
}

public static void main(String[] args){
    
}
}

Using writeObject and readObject.

Serializing the object in a file

        // FileOutputStream provide the file output stream to write the object to the file.
        FileOutputStream fileOut = new FileOutputStream("Serialization/src/output1.ser");

        // ObjectOutputStream is used to write the object to the file.
        ObjectOutputStream out = new ObjectOutputStream(fileOut);

        // Write the object to the file.
        out.writeObject(students);
        out.close();
        fileOut.close();

Deserializig the object stored in the file

        // FileInputStream provides the functionality to read the data from a file.
        FileInputStream fileIn = new FileInputStream("Serialization/src/output1.ser");

        // ObjectInputStream is used to read the object from the file.
        ObjectInputStream in = new ObjectInputStream(fileIn);
        ArrayList<Student> students = (ArrayList<Student>) in.readObject();

        // ArrayList students = (ArrayList) object.readObject();
        in.close();
        fileIn.close();

Strings

String, StringBuilder and StringBuffer.

class GFG {

// Method 1
// Concatenates to String
public static void concat1(String s1)
{
    s1 = s1 + "forgeeks";
}

// Method 2
// Concatenates to StringBuilder
public static void concat2(StringBuilder s2)
{
    s2.append("forgeeks");
}

// Method 3
// Concatenates to StringBuffer
public static void concat3(StringBuffer s3)
{
    s3.append("forgeeks");
}

// Method 4
// Main driver method
public static void main(String[] args)
{
    // Custom input string
    // String 1
    String s1 = "Geeks";

    // Calling above defined method
    concat1(s1);

    // s1 is not changed
    System.out.println("String: " + s1);

    // String 1
    StringBuilder s2 = new StringBuilder("Geeks");

    // Calling above defined method
    concat2(s2);

    // s2 is changed
    System.out.println("StringBuilder: " + s2);

    // String 3
    StringBuffer s3 = new StringBuffer("Geeks");

    // Calling above defined method
    concat3(s3);

    // s3 is changed
    System.out.println("StringBuffer: " + s3);
}

}

Concat1: In this method, we pass a string “Geeks” and perform “s1 = s1 + ”forgeeks”. The string passed from main() is not changed, this is due to the fact that String is immutable. Altering the value of string creates another object and s1 in concat1() stores reference of the new string. References s1 in main() and cocat1() refer to different strings.

Concat2: In this method, we pass a string “Geeks” and perform “s2.append(“forgeeks”)” which changes the actual value of the string (in main) to “Geeksforgeeks”. This is due to the simple fact that StringBuilder is mutable and hence changes its value.

Concat3: StringBuilder is similar and can be compatible at all places to StringBuffer except for the key difference of thread safety. StringBuffer is thread-safe while StringBuilder does not guarantee thread safety which means synchronized methods are present in StringBuffer making control of one thread access at a time while it is not seen in StringBuilder, hence thread-unsafe.

String manipulation and various methods on String class.

Method Description Return Type
charAt() Returns the character at the specified index (position) char
codePointAt() Returns the Unicode of the character at the specified index int
codePointBefore() Returns the Unicode of the character before the specified index int
codePointCount() Returns the number of Unicode values found in a string. int
compareTo() Compares two strings lexicographically int
compareToIgnoreCase() Compares two strings lexicographically, ignoring case differences int
concat() Appends a string to the end of another string String
contains() Checks whether a string contains a sequence of characters boolean
contentEquals() Checks whether a string contains the exact same sequence of characters of the specified CharSequence or StringBuffer boolean
copyValueOf() Returns a String that represents the characters of the character array String
endsWith() Checks whether a string ends with the specified character(s) boolean
equals() Compares two strings. Returns true if the strings are equal, and false if not boolean
equalsIgnoreCase() Compares two strings, ignoring case considerations boolean
format() Returns a formatted string using the specified locale, format string, and arguments String
getBytes() Encodes this String into a sequence of bytes using the named charset, storing the result into a new byte array byte[]
getChars() Copies characters from a string to an array of chars void
hashCode() Returns the hash code of a string int
indexOf() Returns the position of the first found occurrence of specified characters in a string int
intern() Returns the canonical representation for the string object String
isEmpty() Checks whether a string is empty or not boolean
lastIndexOf() Returns the position of the last found occurrence of specified characters in a string int
length() Returns the length of a specified string int
matches() Searches a string for a match against a regular expression, and returns the matches boolean
offsetByCodePoints() Returns the index within this String that is offset from the given index by codePointOffset code points int
regionMatches() Tests if two string regions are equal boolean
replace() Searches a string for a specified value, and returns a new string where the specified values are replaced String
replaceFirst() Replaces the first occurrence of a substring that matches the given regular expression with the given replacement String
replaceAll() Replaces each substring of this string that matches the given regular expression with the given replacement String
split() Splits a string into an array of substrings String[]
startsWith() Checks whether a string starts with specified characters boolean
subSequence() Returns a new character sequence that is a subsequence of this sequence CharSequence
substring() Returns a new string which is the substring of a specified string String
toCharArray() Converts this string to a new character array char[]
toLowerCase() Converts a string to lower case letters String
toString() Returns the value of a String object String
toUpperCase() Converts a string to upper case letters String
trim() Removes whitespace from both ends of a string String
valueOf() Returns the string representation of the specified value String

String and Memory

As we know both Stack and Heap space are part of Java Virtual Machine (JVM). But these memory spaces are used for different purposes. Stack space contains specific values that are short-lived whereas Heap space used by Java Runtime to allocate memory to objects and JRE classes. In Java, strings are stored in the heap area.

Why Java strings stored in Heap, not in Stack?

Well, String is a class and strings in java treated as an object, hence the object of String class will be stored in Heap, not in the stack area. Let’s go deep into the topic. As we all know we can create string object in two ways i.e

  1. By string literal

  2. By using ‘new’ keyword

String Literal is created by using a double quote. For Example:

 String s=”Welcome”; 

Here the JVM checks the String Constant Pool. If the string does not exist then a new string instance is created and placed in the pool if the string exists then it will not create a new object rather it will return the reference to the same instance. The cache which stores these string instances is known as String Constant pool or String Pool. In earlier versions of Java up to JDK 6 String pool was located inside PermGen(Permanent Generation) space. But in JDK 7 it is moved to the main heap area.

Why did the String pool move from PermGen to normal heap area?

PermGen space is limited space, the default size is just 64 MB. And it was a problem of creating and storing too many string objects in PermGen space. That’s why the String pool is moved to a larger heap area. To make the java more memory efficient the concept of string literal is used.

By the use of ‘new’ keyword, the JVM will create a new string object in the normal heap area even if the same string object present in the string pool.

For example:

 String a=new String(“Trident”);

Threads

Creating Threads and starting.

There are two ways to create a thread:

  1. By extending Thread class

       class Multi extends Thread{  
           public void run(){  
              System.out.println("thread is running...");  
           }  
           public static void main(String args[]){  
              Multi t1=new Multi();  
              t1.start();  
           }  
       }  
    
  2. By implementing Runnable interface.

       class Multi3 implements Runnable{  
        public void run(){  
            System.out.println("thread is running...");  
        }  
    
        public  static void main(String args[]){  
           Multi3 m1=new Multi3();  
           Thread t1 =new Thread(m1);   // Using the constructor Thread(Runnable r)  
           t1.start();  
        }  
       }  
    

Thread States.

To work with threads in a program, it is important to identify thread state. The following figure shows thread states in Java thread life cycle.

image

A thread is a path of execution in a program that goes through the following states of a thread. The five states are as follows:

1. New

When an instance of the Thread class is created a new thread is born and is known to be in New-born state. That is, when a thread is born, it enters into new state but its execution phase has not been started yet on the instance.

In simpler terms, Thread object is created but it cannot execute any program statement because it is not in an execution state of the thread. Only start() method can be called on a new thread; otherwise, an IllegalThreadStateException will be thrown.

2. Runnable

The second phase of a new-born thread is the execution phase. When the start() method is called on a the new instance of a thread, it enters into a runnable state. In the runnable state, thread is ready for execution and is waiting for availability of the processor (CPU time). There are many threads that are ready for execution, they all are waiting in a queue (line).

If all threads have equal priority, a time slot is assigned for each thread execution on the basis of first-come, first-serve manner by CPU. The process of allocating time to threads is known as time slicing. A thread can come into runnable state from running, waiting, or new states.

3. Running

Running means Processor (CPU) has allocated time slot to thread for its execution. When thread scheduler selects a thread from the runnable state for execution, it goes into running state.

A running thread may give up its control in any one of the following situations and can enter into the blocked state.

  • When sleep() method is invoked on a thread to sleep for specified time period, the thread is out of queue during this time period. The thread again reenters into the runnable state as soon as this time period is elapsed.

  • When a thread is suspended using suspend() method for some time in order to satisfy some conditions. A suspended thread can be revived by using resume() method.

  • When wait() method is called on a thread to wait for some time. The thread in wait state can be run again using notify() or notifyAll() method.

4. Blocked (Non-runnable state)

A thread is considered to be in the blocked state when it is suspended, sleeping, or waiting for some time in order to satisfy some condition.

5. Dead

A thread dies or moves into dead state automatically when its run() method completes the execution of statements. That is, a thread is terminated or dead when a thread comes out of run() method. A thread can also be dead when the stop() method is called.

Sleep, notify, notifyAll, yield, join methods.

The yield() basically means that the thread is not doing anything particularly important and if any other threads or processes need to be run, they should run. Otherwise, the current thread will continue to run.

 public static void yield()

sleep() Method causes the currently executing thread to sleep for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

 public static void sleep(long mls) throws InterruptedException   
 public static void sleep(long mls, int n) throws InterruptedException    

The join() method of thread class waits for a thread to die. It is used when you want one thread to wait for completion of another. This process is like a relay race where the second runner waits until the first runner comes and hand over the flag to him.

 public final void join()throws InterruptedException  
 public void join(long millis)throwsInterruptedException  
 public final void join(long millis, int nanos)throws InterruptedException  

The notifyAll() method of thread class is used to wake up all threads. This method gives the notification to all waiting threads of a particular object.

 public final void notifyAll()  

The notify() method of thread class is used to wake up a single thread. This method gives the notification for only one thread which is waiting for a particular object.

 public final void notify()  

Synchronization.

Synchronization in Java is the capability to control the access of multiple threads to any shared resource. Java Synchronization is better option where we want to allow only one thread to access the shared resource.

There are two types of thread synchronization mutual exclusive and inter-thread communication.

  • Mutual Exclusive
  • Cooperation (Inter-thread communication in java)

Mutual Exclusive helps keep threads from interfering with one another while sharing data. It can be achieved by using the following three ways:

  • By Using Synchronized Method
  • By Using Synchronized Block
  • By Using Static Synchronization

Locks.

Synchronization is built around an internal entity known as the lock or monitor. Every object has a lock associated with it. By convention, a thread that needs consistent access to an object's fields has to acquire the object's lock before accessing them, and then release the lock when it's done with them.

From Java 5 the package java.util.concurrent.locks contains several lock implementations.

In Java, Lock is an interface available in the Java.util.concurrent.locks package. Java lock acts as thread synchronization mechanisms that are similar to the synchronized blocks. After some time, a new locking mechanism was introduced. It is very flexible and provides more options in comparison to the Synchronized block.

Thread Interaction.

wait(), notify(), and notifyAll() must be called from within a synchronized context. A thread can't invoke a wait or notify method on an object unless it owns that object's lock.

Method Signature Description
final void wait( ) throws InterruptedException Calling this method will male calling thread to give up the monitor and go to sleep until some other thread enters the same monitor and calls notify( ) method.
final void notify( ) This method is similar to notify(), but it wakes up all the threads which are on waiting for state for object lock but only one thread will get access to object lock.

wait(), notify(), and notifyAll() must be called from within a synchronized context. A thread can't invoke a wait or notify method on an object unless it owns that object's lock.

Wrapper Classes

The eight classes of the java.lang package are known as wrapper classes in Java. The list of eight wrapper classes are given below:

Primitive Type Wrapper class
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double

Using Wrapper Classes.

Java is an object-oriented programming language, so we need to deal with objects many times like in Collections, Serialization, Synchronization, etc. Let us see the different scenarios, where we need to use the wrapper classes.

Change the value in Method: Java supports only call by value. So, if we pass a primitive value, it will not change the original value. But, if we convert the primitive value in an object, it will change the original value.

Serialization: We need to convert the objects into streams to perform the serialization. If we have a primitive value, we can convert it in objects through the wrapper classes.

Synchronization: Java synchronization works with objects in Multithreading.

java.util package: The java.util package provides the utility classes to deal with objects.

Collection Framework: Java collection framework works with objects only. All classes of the collection framework (ArrayList, LinkedList, Vector, HashSet, LinkedHashSet, TreeSet, PriorityQueue, ArrayDeque, etc.) deal with objects only.

Creating Wrapper Objects.

Integer a=new Integer(3);    

Autoboxing

The automatic conversion of primitive data type into its corresponding wrapper class is known as autoboxing, for example, byte to Byte, char to Character, int to Integer, long to Long, float to Float, boolean to Boolean, double to Double, and short to Short.

 //Java program to convert primitive into objects  
 //Autoboxing example of int to Integer  
 public class WrapperExample1{  
 public static void main(String args[]){  
 //Converting int into Integer  
 int a=20;  
 Integer i=Integer.valueOf(a);//converting int into Integer explicitly  
 Integer j=a;//autoboxing, now compiler will write Integer.valueOf(a) internally  

System.out.println(a+" "+i+" "+j);
}}

Wrapper Objects Conversion

// converts into primitive types
int var1 = obj1.intValue();
double var2 = obj2.doubleValue();
boolean var3 = obj3.booleanValue();
Clone this wiki locally