Factory Design Pattern

What is a Factory Design Pattern

Factory design pattern is used when we have a super class with multiple sub-classes and based on input, we need to return one of the sub-classes.

This pattern takes out the responsibility of instantiation of a class from client program to the factory class. We can apply Singleton pattern on Factory class or make the factory method static.

Super class in factory pattern can be an interface or a normal java class.

Explanation

The Factory design pattern is a way of creating objects in an object-oriented programming language.

Imagine you have a car factory. The factory makes cars. When you order a car, you specify the type of car you want (e.g. sedan, SUV, sports car, etc.). The factory then builds the car for you and delivers it to you.

Similarly, in the Factory design pattern, you have a factory class that creates objects of different types. When you ask the factory to create an object, you specify the type of object you want. The factory then creates the object for you and returns it to you.

This allows you to separate the process of creating objects from the rest of your code, making it easier to change the way objects are created if needed.

Think of the factory as a kind of “object-making machine.” Instead of writing code to create objects, you tell the factory what you want, and it creates the objects for you. This makes your code easier to read and maintain, and makes it easier to change how objects are created if needed.


The Factory design pattern is often used in situations where client code cannot anticipate the type of objects it needs to create.

The Factory design pattern provides several benefits:

Abstraction: It separates the implementation details of object creation from the client code, allowing the client code to focus on the task at hand and not the details of object creation.

Flexibility: The Factory design pattern allows you to add new types of objects to your application without having to modify the client code. This makes it easier to maintain and extend your application.

Reusability: By encapsulating the details of object creation in a factory class, you can reuse the factory in multiple parts of your application, making your code more modular and easier to maintain. Overall, the Factory

Benefits

  • Factory pattern provides approach to code for interface rather than implementation.
  • Factory pattern removes the instantiation of actual implementation classes from client
    code
    , making it more robust, less coupled and easy to extend.
  • Factory pattern provides abstraction between implementation and client classes through inheritance.

Examples

  • java.util.Calendar, ResourceBundle, java.text.DateFormat and java.text.NumberFormat getInstance () methods uses Factory pattern.
  • valueOf () method in wrapper classes like Boolean, Integer etc.

the Calendar class utilizes the Factory Method getInstance() to create instances of the Calendar class based on the user’s default locale and timezone. The getInstance() method is static, and it internally determines which specific implementation of Calendar to return based on the locale and timezone settings.

// Get an instance of the default Gregorian calendar
Calendar gregorianCalendar = Calendar.getInstance();
System.out.println("Default Calendar: " + gregorianCalendar.getClass().getName());

// Get an instance of a different calendar system (e.g., Buddhist)
Calendar buddhistCalendar = Calendar.getInstance(java.util.Locale.forLanguageTag("th-TH"));
System.out.println("Buddhist Calendar: " + buddhistCalendar.getClass().getName());

Implementation

It allows the client code to create objects by delegating the responsibility of object instantiation to a factory class.

package com.rndayala.designpatterns.factory;

// interface that defines common functionality to be 
// implemented by all related types
public interface Shape {
	void draw();
}

// Concrete class that implements the functionality provided by interface
public class Circle implements Shape {
	@Override
	public void draw() {
		System.out.println("Inside Circle::draw() method."); 

	}
}

// Concrete Product classes implementing the Shape interface
public class Rectangle implements Shape {
	@Override
	public void draw() {
		System.out.println("Inside Rectangle::draw() method."); 
	}
}

// Concrete Product classes implementing the Shape interface
public class Square implements Shape {
	@Override
	public void draw() {
		System.out.println("Inside Square::draw() method."); 

	}
}

Then you define a Factory class that does the instantiation of object based on the type.

package com.rndayala.designpatterns.factory;

// Simple Factory class responsible for creating Shape objects
public class FactoryClass {
	
	// static Factory method which instantiates the object and returns to client 
	public static Shape getShape(String shapeType) {
		if (shapeType == null) {
			return null;
		}

		if (shapeType.equalsIgnoreCase("CIRCLE")) {
			return new Circle();
		} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
			return new Rectangle();
		} else if (shapeType.equalsIgnoreCase("SQUARE")) {
			return new Square();
		} else if (shapeType.equalsIgnoreCase("TRIANGLE")) {
			// TODO : Add Triangle class which implements Shape interface
		}
		return null;
	}
}

Client code that uses the Factory class :

package com.rndayala.designpatterns.factory;

public class FactoryTest {

	public static void main(String[] args) {
		// create objects of  the Shape interface by calling the getShape method 
		// and passing the appropriate String argument. 
		// Type of object to create is determined at runtime by user.
		// NOTE - We code against interface. Higher level modules doesn't depend on lower level classes. 
		Shape shape = FactoryClass.getShape("Circle");
		shape.draw();
		
		shape = FactoryClass.getShape("Square");
		shape.draw();
		
		shape = FactoryClass.getShape("Rectangle");
		shape.draw();
	}
}

Implementations of the Factory Design Pattern in Java provide a way to encapsulate object creation, allowing the client code to focus on using the objects rather than being concerned with how they are created.


Simple Factory Method

In the simple factory method, a separate factory class is responsible for creating instances of various concrete classes that share a common superclass or interface.

// Interface for the Product objects
interface Product {
    void doSomething();
}

// Concrete Product classes implementing the Product interface
class ConcreteProductA implements Product {
    public void doSomething() {
        System.out.println("Doing something in ConcreteProductA.");
    }
}

class ConcreteProductB implements Product {
    public void doSomething() {
        System.out.println("Doing something in ConcreteProductB.");
    }
}

// Simple Factory class responsible for creating Product objects
class ProductFactory {

    // static factory method
    public static Product createProduct(String type) {
        switch (type) {
            case "A":
                return new ConcreteProductA();
            case "B":
                return new ConcreteProductB();
            default:
                throw new IllegalArgumentException("Invalid product type: " + type);
        }
    }
}

// Client code
public class Main {
    public static void main(String[] args) {
        Product productA = ProductFactory.createProduct("A");
        productA.doSomething(); // Output: Doing something in ConcreteProductA.

        Product productB = ProductFactory.createProduct("B");
        productB.doSomething(); // Output: Doing something in ConcreteProductB.
    }
}

Implementations of the Factory Design Pattern in Java provide a way to encapsulate object creation, allowing the client code to focus on using the objects rather than being concerned with how they are created.


Use cases

Some common use cases of the Factory Pattern include:

  • When a class cannot anticipate the type of objects it needs to create
  • When a class wants its subclasses to specify the objects it creates
  • When classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate.

Examples of situations where the Factory Pattern can be used include:

  • when creating objects for UI elements, such as buttons or panels, based on user input or configuration data
  • when implementing a plugin architecture where objects of different types can be created based on user-selected options
  • when managing the creation of objects that are part of a larger system, such as creating database connections based on configuration data.

Singleton Design Pattern

What is a Singleton Design Pattern

Sometimes it’s important for some classes to have exactly one instance. There are many objects we only need one instance of them and if we, instantiate more than one, we’ll run into all sorts of problems like incorrect program behavior, overuse of resources, or inconsistent results.

There are only two points in the definition of a singleton design pattern,

  • There should be only one instance allowed for a class and
  • We should allow global point of access to that single instance.

From the definition, it seems to be a very simple design pattern but when it comes to
implementation, it comes with a lot of implementation concerns.

Explanation of the pattern

With the Singleton pattern, you define a private constructor in the class, which ensures that no one can create a new instance of the class from outside. You also define a public method called “getInstance” that returns the single instance of the class.

The first time the method is called, it creates a new instance of the class. Any subsequent calls to the method return the same instance that was created the first time. In this way, you ensure that there is only one instance of the class.

The Singleton pattern can be implemented in various ways, but it is essential to ensure that only one instance of the class is created, and that it is accessible from anywhere in the code. To achieve this, it is common to use lazy initialization, where the instance is created only when it is first needed.

In summary, the Singleton pattern can be useful for creating shared resources in a system where it is important to maintain a single instance and ensure that it is accessible from anywhere in the code. It can also be used to control the instantiation of a class, ensuring that it is only created once, and to provide a single point of access to the instance.

Why Lazy Initialization

Lazy initialization will be beneficial when we want to delay the initialization until it is not
needed, because if we use eager initialization and if initialization fails there is no chance
to get the instance further. While in lazy initialization we may get it in second chance. In Lazy initialization we will not get instance until we call getInstance () method while in
eager initialization it creates instance at the time of class loading.


How to implement Singleton pattern

package com.rndayala.designpatterns.singleton;

// Author : Raghunath Dayala

/* Singleton is a design pattern that restricts a class to have ONLY one instance, 
 * with a global point of access to it.
 * Useful when you want to limit the number of instances of a class that can exist in the system.
 * This can be useful in situations  where you want to maintain a single instance of a class 
 * to represent a shared resource, such as a  logging service, database connection or a configuration manager.
 * Ref : Check my Kindle library
 */

public class Singleton {
	// private static variable
	private static Singleton instance = null;
	
	// a private constructor that ensures that it cannot be instantiated directly from outside the class.
	private Singleton() {
		System.out.println("Creating Singleton class object..");
	}
	
	// public static method called getInstance is provided, 
	// which returns the  single instance of the class.  
	// The first time the getInstance is invoked, it creates and returns the object.
	// Any subsequent calls returns the same instance created the first time.
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

/* Problems :
 * 1. the above implementation is not thread safe.
 * 2. We can still be able to create new objects using Reflection.
 * 3. We can be able to create new objects using Cloning.
 * 4. When we do serialization/de-serialization, we get new objects.
*/

In above example, the Singleton class has a private constructor that ensures that it cannot be instantiated directly. Instead, a public static method called getInstance is provided, which returns the single instance of the class.

The first time the getInstance method is called, it creates a new instance of the Singleton class by calling the private constructor. Subsequent calls to getInstance return the same instance that was created the first time.

Multi-threaded Singleton implementation

Singleton will work properly in multithreaded environment only if eager instantiation has been done because in this case instance creation will happen at the time of class loading only. But for Lazy instantiation we will have to take care of multiple things. If we want to delay the instantiation because of cost, we use to go with lazy.

Simple Implementation :

package com.rndayala.designpatterns.singleton;

/**
 * Singleton in multi-threaded environments.
 * the behavior of Singleton instance when two threads are 
 * getting executed by comparing their hash code values.
 * 
 * The following code works only in Java 8.
 * @author rndayala
 */

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingletonT {
	
	private static SingletonT instance = null; // lazy initialization
	
	private SingletonT() {
		System.out.println("Creating..");
	}
	
	// When you run the above program many times you will notice that in multithreaded environment,
	// sometimes Singleton principle works, but sometimes it violates.

	public static SingletonT getInstance() {
		if (instance == null) {
			instance = new SingletonT();
		}
		return instance;
	}



	static void useSingleton() {
		SingletonT singleton = SingletonT.getInstance();
		print("Singleton", singleton);
	}
	
	static void print(String name, SingletonT obj) {
		System.out.println(String.format("Object : %s, hashcode : %d", name, obj.hashCode()));
	}
	
	public static void main(String[] args) {
		ExecutorService service = Executors.newFixedThreadPool(2);
		service.submit(SingletonT::useSingleton); // Object : Singleton, hashcode : 918401706
		service.submit(SingletonT::useSingleton); // Object : Singleton, hashcode : 918401706
		service.shutdown();
	}

}

/* observations :
 * When you run the above program many times you will notice that in multithreaded environment,
 * sometimes Singleton principle works but sometimes it violates.
 * Fix : After applying synchronized keyword in the getInstance () method, the program will execute 
 * properly without any issue but in Java.
*/

When you run the above program many times you will notice that in multithreaded environment, sometimes Singleton principle works, but sometimes it violates. Therefore we need to synchronize the getInstance () method as shown below :

	// When you run the above program many times you will notice that in multithreaded environment,
	// sometimes Singleton principle works, but sometimes it violates.
	// Fix : add synchronized keyword to the getInstance() method

	public static synchronized SingletonT getInstance() {
		if (instance == null) {
			instance = new SingletonT();
		}
		return instance;
	}

After applying synchronized keyword in the getInstance () method the program will execute properly without any issue.


Double Checked Locking

Instead of synchronizing whole method we can synchronize only the block of code which is affected while creating instance to escape the extra overhead as below :

	// Don't synchronize getInstance() method completely.
	// Synchronize only the block of code which is affected while creating instance.
	public static SingletonT getInstance() {
		if (instance == null) {
			synchronized (SingletonT.class) {
				instance = new SingletonT();
			}
		}
		return instance;
	}

From the above code we have narrowed down the scope of synchronization for performance reasons. But the above code can cause issues due to thread switching.

So to make sure no other thread has already acquired the lock we will apply one more check after acquiring the lock as shown below. This method is called Double Checked Locking.

	// Don't synchronize getInstance() method completely.
	// Synchronize only the block of code which is affected while creating instance.
	public static SingletonT getInstance() {
		if (instance == null) {
			synchronized (SingletonT.class) {
				// double checked locking
				if (instance == null) {
					instance = new SingletonT();
				}
			}
		}
		return instance;
	}

Sometimes double checked locking also breaks the Principle of Singleton. It may return an instance in half-initialized state.

To address this situation use volatile keyword at the time of instance declaration. Value of volatile variable will be published only when the change completes. Change to write
operation happens before read operation in volatile variable. In short all threads will see the same value of variable.

private static volatile SingletonT instance = null; // lazy initialization

Reflection – Singleton implementation violation ? How to Fix ?

In Java, you can violate the Singleton pattern’s intended behavior using reflection.

Reflection allows you to access and modify the private constructors and fields of a class, which can lead to the creation of multiple instances of the Singleton class, thus violating the pattern.

Here’s an example of how the Singleton pattern can be violated using reflection in Java:

import java.lang.reflect.Constructor;

public class Singleton {

    private static Singleton instance;

    private Singleton() {
        // Private constructor
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    // Other methods and fields...
}

public class Main {

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = null;

        try {
            // Using reflection to access the private constructor
            Constructor<Singleton> constructor 
                              = Singleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            singleton2 = constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(singleton1); // Output: Singleton@hashcode1
        System.out.println(singleton2); // Output: Singleton@hashcode2
    }
}

In the example above, we try to access the private constructor of the Singleton class using reflection and create a new instance. As a result, singleton2 is not the same instance as singleton1, and we have violated the Singleton pattern’s intent.

To protect against this kind of reflection-based Singleton pattern violation, you can modify the Singleton class to throw an exception if someone tries to create a new instance using reflection:

public class Singleton {

    private static Singleton instance;

    private Singleton() {
        if (instance != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance.");
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    // Other methods and fields...
}

By adding the check inside the private constructor, any attempt to create a new instance through reflection will result in an exception, preserving the Singleton pattern’s integrity. However, it’s essential to be cautious when using reflection and design patterns together, as it can lead to unexpected behavior and undermine the patterns’ intended benefits.

Clone – Singleton implementation violation ? How to Fix ?

If we try to make instance by cloning it, the generated hash code of cloned
copy doesn’t match with the actual object so it also violates the Singleton principle of having a single instance.

Here’s an example to illustrate the issue:

public class Singleton implements Cloneable {

    private static Singleton instance;

    private Singleton() {
        // Private constructor
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // Other methods and fields...
}

public class Main {

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = null;

        try {
            // Cloning the singleton object
            singleton2 = (Singleton) singleton1.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        System.out.println(singleton1); // Obj: singleton1, hashcode: 366712642
        System.out.println(singleton2); // Obj: clone, hashcode: 1442407170
    }
}

In this example, we implement the Cloneable interface in the Singleton class, and we override the clone() method to call the super.clone() method. The generated hash code of cloned copy doesn’t match with the actual object so it also violates the Singleton principle.

To address this issue, you may consider throwing an exception in the clone() method to prevent cloning altogether:

public class Singleton implements Cloneable {

    // Singleton implementation...

    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Cloning of Singleton objects is not allowed.");
    }

    // Other methods and fields...
}

By throwing a CloneNotSupportedException, you explicitly prohibit cloning of the Singleton objects and maintain the integrity of the Singleton pattern. However, it’s important to note that the use of Cloneable and clone() method can be controversial in Java, and it is generally recommended to avoid using them in favor of other approaches like copy constructors or factory methods for object duplication.

How to fix: Throw CloneNotSupportedException from the clone () method if someone
tries to make other instance of it.


Bill Pugh method – Singleton Implementation

The Bill Pugh Singleton pattern, also known as the Initialization-on-demand Holder Idiom, is an improvement over the traditional Singleton pattern.

It provides a simpler and more thread-safe way to implement a Singleton in Java without the need for explicit synchronization. This pattern takes advantage of the Java class-loading mechanism to ensure that the Singleton instance is created lazily and safely when the class is loaded.

We use inner static class approach in this implementation. The inner static class encapsulates the Singleton instance, and its instantiation logic is taken care of by the JVM, reducing the need for explicit synchronization or volatile variables.

Bill Pugh implementation – thread safe, no need of explicit synchronization or volatile variable. The inner static class shields the Singleton instance from being created through reflection, as the constructor remains private.

Here’s the implementation of the Bill Pugh Singleton pattern:

public class Singleton {

    // Private constructor to prevent instantiation from other classes
    private Singleton() {
        // Initialization code (if any) goes here
    }

    // Inner static helper class responsible for holding the Singleton instance
    private static class SingletonHolder {
        // The Singleton instance is created when the class is loaded
        private static final Singleton INSTANCE = new Singleton();
    }

    // Public static method to get the Singleton instance
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // Other methods and fields...
}

In this implementation, the Singleton class has a private constructor to prevent direct instantiation. The Singleton instance is stored as a static field within a nested static class called SingletonHolder. The INSTANCE field is initialized during the class-loading phase, which is guaranteed to be thread-safe by the Java Virtual Machine.

When getInstance() is called, it returns the Singleton instance held by the SingletonHolder, ensuring that only one instance is created throughout the application’s lifecycle.

Here’s how you can use the Bill Pugh Singleton pattern:

public class Main {

    public static void main(String[] args) {
        // Get the Singleton instance
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();

        // Both instances are the same
        System.out.println(singleton1 == singleton2); // Output: true
    }
}

This approach provides better performance and avoids unnecessary synchronization overhead because the Singleton is initialized lazily and only when needed.

Nowadays, this Bill Pugh method is widely used and considered a best practice for creating Singleton instances.


enum implementation of Singleton pattern

In Java, you can implement the Singleton pattern using an enum.

Enums in Java are implicitly singleton by design, as they only allow a fixed set of predefined instances, and there can be no more than one instance of each enum constant. This property makes enums a natural fit for implementing a singleton.

Joshua Bloch suggests the use of Enum to implement Singleton design pattern as Java ensures that any enum value is instantiated only once in a Java program. Since Java Enum values are globally accessible, so is the singleton.

The drawback is that the enum type is somewhat inflexible; for example, it does not allow lazy initialization.

Here’s how you can implement the Singleton pattern using an enum :

public enum SingletonEnum {
    INSTANCE;

    // Any additional fields or methods for the Singleton can be added here
    // ...

    // Example method
    public void doSomething() {
        // Implement functionality here
    }
}

In this implementation, SingletonEnum is an enum that contains a single instance called INSTANCE. When the SingletonEnum class is loaded, the INSTANCE constant is initialized, and it remains the only instance throughout the application’s lifecycle.

In the context of implementing the Singleton pattern using an enum, the INSTANCE is a single constant instance of the enum type. In Java, enum constants are implicitly static and final, which means they can only be created once during the class loading and cannot be modified afterward. As a result, an enum with a single constant effectively serves as a singleton.

INSTANCE represents the sole instance of the SingletonEnum class. The enum constant name (INSTANCE in this case) can be any valid Java identifier, but by convention, INSTANCE is commonly used to signify that it represents the single instance of the singleton.

You can use the SingletonEnum instance like this:

public class Main {

    public static void main(String[] args) {
        SingletonEnum singleton1 = SingletonEnum.INSTANCE;
        SingletonEnum singleton2 = SingletonEnum.INSTANCE;

        // Both instances are the same
        System.out.println(singleton1 == singleton2); // Output: true

        // Call methods on the Singleton instance
        singleton1.doSomething();
    }
}

As enum constants are inherently thread-safe and guaranteed to be initialized only once, using an enum for the Singleton pattern eliminates the need for explicit synchronization and ensures a simple, efficient, and safe singleton implementation in Java.

As with any enum, the SingletonEnum’s instance is implicitly thread-safe and immune to issues related to reflection or serialization, making this approach one of the simplest and most effective ways to implement a thread-safe Singleton pattern in Java.

Enum Singleton doesn’t violate principle of Singleton in any case described above.

Design Patterns Introduction

What are Design Patterns

A design pattern is a reusable solution to a commonly occurring problem in software design. It represents a general, proven approach that can be applied to various situations to address specific design challenges.

The design patterns are language independent strategies for solving common object-oriented design problems.

Design patterns are an essential tool for software development that can help programmers write more organized, efficient, and reusable code.

They are like pre-made templates for solving common problems that arise in software development, offering a standardized and proven solution for each problem. By using design patterns, programmers can save time, reduce the risk of bugs, and improve the overall quality of their code.


They are usually divided into categories such as creational, structural, and behavioral patterns, each with its own unique set of solutions.

By learning design patterns in Java, developers can deepen their understanding of object-oriented concepts and improve their ability to design and implement complex software.


Why learn Design Patterns

When you make a design, you should know the names of some common solutions. Learning design patterns is good for people to communicate each other effectively.

SUN suggests GOF (Gang of Four—four pioneer guys who wrote a book named
“Design Patterns”- Elements of Reusable Object-Oriented Software), so we use that book as our guide to describe solutions.

Design patterns are an essential tool for software development that can help programmers write more organized, efficient, and reusable code.

They are like pre-made templates for solving common problems that arise in software development, offering a standardized and proven solution for each problem. By using design patterns, programmers can save time, reduce the risk of bugs, and improve the overall quality of their code.


Categories of Design Patterns

Design patterns are generally categorized into three main types:

1. Creational Patterns: These patterns are used to create objects and classes in a way that is suitable for a particular situation. Creational design patterns provide solution to instantiate an object in the best possible way for specific situations.

  • Singleton Pattern
  • Factory Pattern
  • Abstract Factory Pattern
  • Builder Pattern
  • Prototype Pattern

2. Structural Patterns: These patterns are used to arrange classes and objects to form larger structures. Structural patterns provide different ways to create a class
structure, for example using inheritance and composition to create a large object from
small objects.

  • Adapter Pattern
  • Composite Pattern
  • Proxy Pattern
  • Flyweight Pattern
  • Facade Pattern
  • Bridge Pattern
  • Decorator Pattern

3. Behavioral Patterns: These patterns are used to describe the ways in which objects interact and communicate with each other. Behavioral patterns provide solution for the better interaction between objects and how to provide lose coupling and flexibility to extend easily.

  • Template Method Pattern
  • Mediator Pattern
  • Chain of Responsibility Pattern
  • Observer Pattern
  • Strategy Pattern
  • Command Pattern
  • State Pattern
  • Visitor Pattern
  • Iterator Pattern
  • Interpreter Pattern
  • Memento Pattern

These categorizations are a useful way to understand the different types of design patterns and their intended uses.

JUnit

What is JUnit ?

JUnit is a widely used open-source testing framework for Java programming language. It provides a set of annotations and assertions to write and execute unit tests for Java applications.

  • Unit testing is a software testing method where individual units of code, such as methods or classes, are tested to ensure they function correctly in isolation.

JUnit facilitates the creation and execution of automated tests by providing a framework that simplifies test case creation and test result verification. It follows the principles of test-driven development (TDD) and encourages developers to write tests before implementing the corresponding functionality. This approach helps improve code quality, maintainability, and reliability.

Some key features of JUnit include:

  1. Annotations: JUnit uses annotations, such as @Test, to mark test methods within test classes. These annotations provide instructions to JUnit on how to execute the tests.
  2. Assertions: JUnit provides a wide range of assertion methods to verify expected outcomes. These assertions help compare actual values with the expected values to determine if the test passes or fails.
  3. Test Runners: JUnit utilizes test runners to discover and execute tests. Test runners are responsible for managing the execution of test cases and reporting the results.
  4. Test Fixtures: JUnit allows the setup and teardown of test fixtures using annotations like @Before, @After, @BeforeClass, and @AfterClass. These annotations enable the execution of specific methods before and after each test or before and after the entire test class.

JUnit has become the de facto standard for unit testing in Java. It integrates well with various development environments, build tools, and continuous integration systems. With JUnit, developers can easily write and execute tests to validate the behavior of their code, ensuring its correctness and stability.

JUnit Maven Dependency

To use JUnit in a Maven project, you need to add the JUnit dependency to your project’s pom.xml file. Here’s an example of how to include the JUnit dependency in your Maven project:

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
  </dependency>
</dependencies>

In the above example, the dependency element specifies the details of the JUnit dependency. The groupId is set to “junit,” the artifactId is set to “junit,” and the version is set to “4.13.2,” which is the latest version at the time of writing. The <scope>test</scope> ensures that JUnit is only used for testing purposes and is not included in the runtime classpath.

Once you’ve added the dependency to your pom.xml file, Maven will automatically download the JUnit JAR file and its dependencies from the Maven Central Repository when you build your project. You can then use JUnit in your tests by importing the necessary classes and annotations in your test classes.

How to write a simple test case using JUnit ?

To write a simple test case in JUnit, you can follow these steps:

  1. Create a new Java class for your test case. This class should be separate from your application code and typically resides in a test source directory (src\test\java\ – java source code for tests).
  2. Import the necessary JUnit classes and annotations. The commonly used ones are org.junit.Test for marking test methods and org.junit.Assert for assertion methods.
  3. Create a test method and annotate it with @Test. This annotation tells JUnit that this method should be executed as a test case.
  4. Write the code to set up any necessary test data or objects.
  5. Use the assertion methods from Assert class to verify the expected results. JUnit provides various assertion methods like assertEquals(), assertTrue(), assertFalse(), etc.
  6. Optionally, you can use other JUnit annotations like @Before and @After to define setup and teardown methods that run before and after each test method.

Here’s an example of a simple JUnit test case:

import org.junit.Test;
import static org.junit.Assert.*;

public class MyTestCase {

    @Test
    public void testAddition() {
        int result = add(2, 3);
        assertEquals(5, result);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testDivideByZero() {
        divide(10, 0);
    }

    private int add(int a, int b) {
        return a + b;
    }

    private int divide(int dividend, int divisor) {
        if (divisor == 0) {
            throw new IllegalArgumentException("Divisor cannot be zero");
        }
        return dividend / divisor;
    }
}

The @Test annotation is a key annotation provided by JUnit. It is used to mark a method as a test case that should be executed by the test runner. When JUnit encounters a method annotated with @Test, it considers it as a test case and executes it during the testing process.

Here are some important aspects of the @Test annotation:

  1. Execution: Methods annotated with @Test are executed by the JUnit test runner. Each method annotated with @Test is treated as an independent test case and is executed in isolation.
  2. Signature: The test method should be public, return void, and not take any parameters. It is generally named descriptively to indicate the functionality being tested.
  3. Assertions: Test methods typically contain assertions to verify the expected behavior of the code being tested. These assertions are used to compare the actual results with the expected results.
  4. Expected exceptions: You can specify an expected exception using the expected attribute of the @Test annotation. If the specified exception is thrown during the execution of the test method, the test will pass. If the exception is not thrown or a different exception is thrown, the test will fail.

In the above example, the testAddition() method is marked with @Test and verifies the addition of two numbers.

The testDivideByZero() method is also marked with @Test and specifies that it expects an IllegalArgumentException to be thrown when dividing by zero.

By using the @Test annotation, you can easily identify and execute specific methods as test cases during the test execution.

Naming conventions for test methods in JUnit

When writing test methods in JUnit, it is beneficial to follow naming conventions that provide clarity and consistency. Although JUnit does not enforce any specific naming conventions for test methods, using a standard naming approach can enhance the readability and understandability of your test suite.

Here are some common naming conventions for writing test methods in JUnit:

  1. Method Name Format: It is typical to prefix the name of each test method with the word “test” to indicate that it is a test case. For example, testMethodName().
  2. Descriptive Names: Use descriptive names that convey the purpose or behavior being tested. A good test method name should describe the scenario being tested and the expected outcome. This helps others understand the intent of the test without needing to examine the test code in detail.
  3. Clarity and Readability: Make the test method names concise, clear, and easy to read. Avoid ambiguous or overly complex names that can lead to confusion.
  4. CamelCase Convention: Follow the standard Java naming convention of using camel case for method names. Begin each word after the first with an uppercase letter, for example, testAddition() or testCalculateDiscount().
  5. Use Action-Outcome Style: Structure the test method names in an action-outcome style, where the name reflects the action being performed and the expected outcome. For example, testSaveUserSuccessfully() or testInvalidInputValidation().
  6. Use Underscores for Clarity: If needed, you can use underscores to improve the readability of long test method names. For example, test_calculate_discount_for_large_order().

Remember, the primary goal is to make the test method names self-explanatory and understandable without having to inspect the implementation details. Adopting a consistent naming convention throughout your test suite can enhance maintainability and collaboration among team members.

Ultimately, choose a naming convention that aligns with your project’s coding standards and promotes clarity and consistency in your tests.

 

Understanding @Before and @After annotations

The @Before and @After annotations are provided by JUnit to perform setup and teardown operations before and after each test method execution. These annotations allow you to define methods that will be executed automatically by the test runner, helping you set up the necessary environment for your tests and clean up any resources afterward.

Here’s an explanation of the @Before and @After annotations:

  1. @Before:
    • Annotating a method with @Before indicates that it should be executed before each test method.
    • The purpose of @Before is to set up the preconditions or initialize any necessary objects or resources that are common across multiple test methods.
    • Methods annotated with @Before are commonly used to create test objects, set up test data, establish database connections, or initialize other dependencies required for the test.
    • If multiple methods are annotated with @Before, they will be executed in the order they are declared.
  1. @After:
    • Annotating a method with @After indicates that it should be executed after each test method.
    • The purpose of @After is to perform any cleanup tasks or release resources that were used during the test.
    • Methods annotated with @After are commonly used to release database connections, delete temporary files, or reset the state of the system to ensure the next test starts with a clean environment.
    • If multiple methods are annotated with @After, they will be executed in the reverse order they are declared.

Here’s an example that demonstrates the usage of @Before and @After annotations:

import org.junit.Before;
import org.junit.After;
import org.junit.Test;
import static org.junit.Assert.*;

public class MyTestCase {
    private Calculator calculator;

    @Before
    public void setUp() {
        // This method will be executed before each test method
        calculator = new Calculator();
    }

    @After
    public void tearDown() {
        // This method will be executed after each test method
        calculator = null;
    }

    @Test
    public void testAddition() {
        int result = calculator.add(2, 3);
        assertEquals(5, result);
    }

    @Test
    public void testSubtraction() {
        int result = calculator.subtract(5, 3);
        assertEquals(2, result);
    }
}

In the above example, the setUp() method is annotated with @Before and is executed before each test method. It creates a new instance of the Calculator class, which will be used in the test methods.

The tearDown() method is annotated with @After and is executed after each test method. It sets the calculator object to null, releasing any resources used by it.

By using @Before and @After annotations, you can ensure that each test method starts with a clean and consistent state, and any resources used during the test are properly cleaned up after the test execution.

Understanding @BeforeClass and @AfterClass annotations

The @BeforeClass and @AfterClass annotations in JUnit are used to define methods that are executed once before and after all the test methods in a test class, respectively. These annotations allow you to perform setup and teardown operations at the class level, rather than before and after each individual test method.

  • Annotating a method with @BeforeClass indicates that it should be executed once before any of the test methods in the test class.
  • The purpose of @BeforeClass is to set up static fixtures or perform any expensive one-time initialization tasks that are common to all the test methods in the class.
  • Methods annotated with @BeforeClass should be declared as public static void and can be used, for example, to establish a database connection, load configuration files, or initialize heavy resources.
  • @BeforeClass methods are executed before any @Before or @Test methods in the class.
  • Annotating a method with @AfterClass indicates that it should be executed once after all the test methods in the test class have completed.
  • The purpose of @AfterClass is to perform cleanup or release resources that were set up in the @BeforeClass method.
  • Methods annotated with @AfterClass should be declared as public static void and can be used, for example, to close database connections, delete temporary files, or perform any necessary finalization tasks.
  • @AfterClass methods are executed after all the @After or @Test methods in the class.

Here’s an example that demonstrates the usage of @BeforeClass and @AfterClass annotations:

import org.junit.BeforeClass;
import org.junit.AfterClass;
import org.junit.Test;
import static org.junit.Assert.*;

public class MyTestCase {
    private static DatabaseConnection connection;

    @BeforeClass
    public static void setUpClass() {
        // This method will be executed once before any test method in the class
        connection = new DatabaseConnection();
        connection.connect();
    }

    @AfterClass
    public static void tearDownClass() {
        // This method will be executed once after all test methods in the class
        connection.disconnect();
        connection = null;
    }

    @Test
    public void testMethod1() {
        // Test method 1
    }

    @Test
    public void testMethod2() {
        // Test method 2
    }
}

In the above example, the setUpClass() method is annotated with @BeforeClass and is executed once before any test method in the class. It creates a DatabaseConnection instance and establishes a connection.

The tearDownClass() method is annotated with @AfterClass and is executed once after all the test methods in the class. It disconnects from the database and releases any resources.

By using @BeforeClass and @AfterClass annotations, you can perform setup and teardown operations that are shared among all the test methods in the class, saving time and resources by executing these operations only once for the entire test class.

Performance testing

In JUnit, you can use the @Test annotation with the timeout parameter to specify a maximum time limit for the execution of a test method. This is useful when you want to ensure that a test case completes within a specific timeframe, detecting potential performance issues or infinite loops.

Here’s an example of using the @Test annotation with the timeout parameter:

import org.junit.Test;

public class TimeoutTestCase {

    @Test(timeout = 1000) // Timeout set to 1 second (1000 milliseconds)
    public void testMethod() {
        // Code that should complete within the specified timeout
    }
}

In the above example, the testMethod() is annotated with @Test(timeout = 1000), which sets a timeout of 1 second for the test execution. If the test method takes longer than the specified timeout, it will be marked as a failure.

When the test is executed, if the test method takes longer than the specified timeout, a TimeoutException will be thrown, indicating that the test has failed due to exceeding the time limit.

It’s important to note that the timeout value specified is in milliseconds. You can adjust the timeout value according to your specific needs and the expected execution time of the test.

Using the timeout parameter in the @Test annotation allows you to ensure that your tests complete within a reasonable time frame, preventing them from hanging indefinitely and helping to maintain the efficiency of your test suite.

Testing for exceptions using expected attribute

In JUnit, you can use the @Test annotation with the expected parameter to specify that a test method is expected to throw a particular exception. This is useful when you want to verify that your code correctly throws an exception under certain conditions.

Here’s an example of using the @Test annotation with the expected parameter:

import org.junit.Test;

public class ExceptionTestCase {

    @Test(expected = ArithmeticException.class)
    public void testDivideByZero() {
        int result = 5 / 0; // This division will throw an ArithmeticException
    }
}

In the above example, the testDivideByZero() method is annotated with @Test(expected = ArithmeticException.class). This annotation indicates that the test expects an ArithmeticException to be thrown during the execution of the test method.

When the test is executed, if the specified exception (ArithmeticException in this case) is thrown during the execution of the test method, the test will pass. If the exception is not thrown or a different exception is thrown, the test will fail.

You can specify any exception type that you expect to be thrown by the test method using the expected parameter of the @Test annotation.

Using the expected parameter in the @Test annotation allows you to explicitly state the expected exception and verify that the code under test behaves as expected by throwing the correct exception under specific circumstances.

assertEquals() method

The assertEquals() method in JUnit is used to assert that two values are equal. It compares the expected value with the actual value, allowing you to verify that the two values are the same.

Here’s an explanation of the assertEquals() method:

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class AssertionTestCase {

    @Test
    public void testStringEquality() {
        String expected = "Hello";
        String actual = "Hello";
        assertEquals(expected, actual);
    }

    @Test
    public void testNumericEquality() {
        int expected = 42;
        int actual = 42;
        assertEquals(expected, actual);
    }
}

In the above example, the assertEquals() method is used to compare the expected and actual values. If the two values are equal, the test passes. If they are not equal, the test fails, and an assertion error is thrown.

The assertEquals() method is overloaded to handle different data types, including numeric types, strings, booleans, and objects. It performs an equality check based on the appropriate equals() method for the corresponding data type.

Additionally, you can provide an optional message as the last argument to the assertEquals() method. This message will be displayed when the assertion fails, helping to identify the reason for the failure.

The assertEquals() method is widely used in test cases to verify that a value matches the expected result. It is helpful in ensuring the correctness of calculations, method return values, and other scenarios where equality between values needs to be asserted.

assertTrue() and assertFalse() methods

The assertTrue() and assertFalse() methods in JUnit are assertion methods used to verify that a given condition is true or false, respectively. These methods are commonly used in test cases to check the expected behavior of certain conditions or boolean expressions.

Here’s an explanation of the assertTrue() and assertFalse() methods:

  • The assertTrue() method verifies that a given condition or expression is true.
  • If the condition is true, the test passes. Otherwise, if the condition is false, the test fails, and an assertion error is thrown.
  • The assertFalse() method verifies that a given condition or expression is false.
  • If the condition is false, the test passes. If the condition is true, the test fails, and an assertion error is thrown.
import org.junit.Test;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;

public class AssertionTestCase {

    @Test
    public void testPositiveNumber() {
        int number = 10;
        assertTrue(number > 0); // Asserts that number is greater than 0
    }

    @Test
    public void testNegativeNumber() {
        int number = -5;
        assertFalse(number > 0); // Asserts that number is not greater than 0
    }
}

In the above example, the testPositiveNumber() method uses assertTrue() to assert that the number variable is greater than 0. If the condition is true, the test passes.

The testNegativeNumber() method uses assertFalse() to assert that the number variable is not greater than 0. If the condition is false, the test passes.

If the conditions specified in assertTrue() or assertFalse() are not met during test execution, the respective assertion will fail, and an assertion error will be thrown, indicating the failure of the test.

These assertion methods provide a convenient way to validate specific conditions in your tests, making it easy to verify the expected behavior of your code based on boolean expressions or conditions.

 

assertArrayEquals() method

The assertArrayEquals() method in JUnit is used to assert that two arrays are equal. It compares the elements of the arrays to determine if they have the same length and contain the same elements in the same order. This assertion is useful when you want to verify the equality of array objects in your test cases.

Here’s an example of using the assertArrayEquals() method:

import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;

public class ArrayTestCase {

    @Test
    public void testArrayEquality() {
        int[] expected = {1, 2, 3};
        int[] actual = {1, 2, 3};
        assertArrayEquals(expected, actual);
    }
}

In the above example, the testArrayEquality() method compares two arrays: expected and actual. The assertArrayEquals() method is used to assert that the two arrays are equal.

If the arrays have the same length and contain the same elements in the same order, the test will pass. Otherwise, if the arrays are not equal, the test will fail and an assertion error will be thrown, indicating the mismatch between the expected and actual arrays.

The assertArrayEquals() method is overloaded to support different types of arrays, including arrays of primitive types and arrays of objects. It performs deep comparison, taking into account the elements within the arrays.

It’s important to note that the order of elements in the arrays matters. If the order is significant, the elements must be in the same order in both arrays for the assertion to pass.

The assertArrayEquals() assertion is commonly used to verify the correctness of array-based calculations, data transformations, or operations that return array results. It ensures that the expected and actual arrays match exactly, helping you identify any discrepancies in the array contents.

 

ArrayList

What and Why ArrayList

ArrayList is most commonly used data structure from Collections framework.

ArrayList is an implementation of the List interface in the Java Collections Framework. It provides a resizable array-based implementation of a dynamic list, allowing you to store and manipulate elements in a flexible and efficient manner.

ArrayList allows storing duplicates and also null values. It’s important to note that ArrayList treats each occurrence of a duplicate or null value as a separate entry.

Here are some key features and reasons why you might choose to use ArrayList:

  1. Dynamic Size: ArrayList automatically adjusts its size as elements are added or removed, making it convenient for situations where the number of elements may vary over time. You do not need to worry about manually managing the underlying array’s size.
  2. Random Access: ArrayList allows for constant-time random access to elements based on their index. This means you can quickly retrieve elements from the list using the get(int index) method.
  3. Fast Iteration: ArrayList provides efficient iteration over its elements using enhanced for loops or the Iterator interface. It is particularly useful when you need to iterate through the entire list or access elements sequentially.
  4. Dynamic Modification: ArrayList supports various methods for adding, removing, and modifying elements. You can easily append elements to the end of the list (add(E element)), insert elements at specific positions (add(int index, E element)), remove elements (remove(int index) or remove(Object o)), and modify existing elements (set(int index, E element)).
  5. Backed by an Array: Internally, ArrayList is backed by an array, which provides efficient element storage and retrieval. This array-based implementation offers faster random access compared to linked data structures.
  6. Compatibility with Algorithms: ArrayList is widely used and supported in Java libraries and APIs. It is compatible with various algorithms, utility classes, and methods provided by the Java Collections Framework, making it easy to integrate with existing code.
  7. Simple and Familiar API: ArrayList has a straightforward and intuitive API, allowing for easy adoption and usage. It inherits methods from the List interface, providing a consistent interface for working with other list implementations.

However, it’s important to note that ArrayList might not be the best choice in every situation. If you frequently insert or remove elements in the middle of the list, LinkedList may offer better performance. Additionally, if thread safety is a concern, you might consider using Vector or other concurrent collection classes.

In summary, ArrayList is a commonly used implementation of the List interface, providing dynamic size, random access, fast iteration, and efficient element manipulation. It is a versatile choice for many scenarios where you need to work with ordered collections of elements.

** If appending elements to the list (or) removing the last element is a frequent operation, then you can use ArrayList as both these operations can be performed in constant time O(1) time. NOTE : While appending element, if the ArrayList internal capacity is reached, it would trigger resizing and additional time involved.


ArrayList class syntax in Java API

The syntax of the ArrayList class in the Java API is as follows:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, Serializable {

    // Constructors
    public ArrayList();
    public ArrayList(int initialCapacity);
    public ArrayList(Collection<? extends E> c);

    // Methods
    public boolean add(E e);
    public void add(int index, E element);
    public boolean addAll(Collection<? extends E> c);
    public boolean addAll(int index, Collection<? extends E> c);
    public void clear();
    public boolean contains(Object o);
    public E get(int index);
    public int indexOf(Object o);
    public boolean isEmpty();
    public int lastIndexOf(Object o);
    public E remove(int index);
    public boolean remove(Object o);
    public boolean removeAll(Collection<?> c);
    public boolean retainAll(Collection<?> c);
    public E set(int index, E element);
    public int size();
    public List<E> subList(int fromIndex, int toIndex);
    public Object[] toArray();
    public <T> T[] toArray(T[] a);
    public void trimToSize();
}

In the above syntax, the ArrayList class is defined as a generic class (ArrayList<E>) that can work with elements of any specific type represented by the type parameter E.

The class extends the AbstractList class and implements the List interface, RandomAccess interface (indicating fast random access), Cloneable interface (supporting cloning), and Serializable interface (supporting serialization).


ArrayList – Capacity

Since an array is used internally to implement this data structure, it obviously has some size associated with it and by default it is set to 10. The capacity of an ArrayList refers to the size of this internally array.

The ArrayList class in Java dynamically resizes itself to accommodate the elements added to it. It internally maintains an array that holds the elements. The capacity of an ArrayList refers to the size of this internal array, which may be larger than the number of elements currently stored in the list.

Here are a few key points about the capacity of an ArrayList:

  1. Initial Capacity: When you create an ArrayList using the default constructor (ArrayList()), it starts with an initial capacity of 10. This means the internal array can hold up to 10 elements without needing to resize.
  2. Resizing: If you add more elements to an ArrayList and its internal array reaches its capacity, the ArrayList automatically increases the capacity by creating a new, larger array and copying the elements from the old array to the new one. This resizing process typically doubles the size of the internal array (or) in some cases, it increased by 50%.
  3. Capacity vs. Size: The capacity of an ArrayList is not the same as its size. The size refers to the number of elements currently stored in the list, while the capacity refers to the total number of elements the ArrayList can hold before it needs to resize its internal array.
  4. Controlling Capacity: If you know the expected number of elements in advance, you can specify the initial capacity when creating the ArrayList using the constructor ArrayList(int initialCapacity). This can help avoid unnecessary resizing operations if you have an estimate of the number of elements you will be adding.
  5. TrimToSize: If you want to minimize the internal array’s size to match the current number of elements, you can use the trimToSize() method. This method reduces the capacity of the ArrayList to be the same as its size, potentially saving memory if you no longer expect to add many more elements.

In summary, the capacity of an ArrayList refers to the total number of elements it can hold before needing to resize its internal array. The ArrayList class manages the capacity dynamically, automatically resizing as needed to accommodate additional elements.

** When the capacity is reached, a new array is created with a size that is 50% or double than the old array size. Old array contents are copied into the new array. However, excessive resizing due to adding large numbers of elements can impact performance, so it’s beneficial to estimate the expected size when possible.


ensureCapacity(int) method

The ensureCapacity(int) method is a method in the ArrayList class that allows you to ensure that the ArrayList has a minimum capacity specified by the parameter. It increases the capacity of the internal array, if necessary, to accommodate at least the specified number of elements without automatic resizing.

public void ensureCapacity(int minCapacity)

Here’s how the ensureCapacity(int) method works:

  1. If the current capacity of the ArrayList is less than the minCapacity parameter, the ensureCapacity(int) method increases the capacity of the internal array to be at least minCapacity. This ensures that the ArrayList can accommodate the specified number of elements without resizing.
  2. If the current capacity is already greater than or equal to the minCapacity, no resizing occurs. The ensureCapacity(int) method has no effect in this case.

Using ensureCapacity(int) can be helpful when you have an estimate of the number of elements to be added to the ArrayList. It allows you to allocate sufficient capacity in advance, reducing the number of resizing operations and improving performance.

// Ensure that the ArrayList has a minimum capacity of 20
myList.ensureCapacity(20);


trimToSize() method example

Here is the example :

import java.util.ArrayList;

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

        // Create an ArrayList with an initial capacity of 20
        ArrayList<String> myList = new ArrayList<>(20);

        // Add elements to the ArrayList
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Orange");

        // Print the elements and size of the ArrayList before trimming
        System.out.println("Elements in the ArrayList: " + myList);
        System.out.println("Size of the ArrayList before trimming: " + myList.size());

        // Trim the ArrayList to match its size
        myList.trimToSize();

        // Print the elements and size of the ArrayList after trimming
        System.out.println("Elements in the ArrayList after trimming: " + myList);
        System.out.println("Size of the ArrayList after trimming: " + myList.size());
    }
}

In this example, we create an ArrayList called myList with an initial capacity of 20 using the ArrayList(int initialCapacity) constructor. We add elements to the myList using the add() method.

We use the trimToSize() method to trim the ArrayList to match its size, reducing the internal array’s capacity to match the number of elements.

The trimToSize() method is useful when you want to minimize the memory footprint of the ArrayList by reducing its internal array’s capacity to match the actual number of elements. This can be beneficial if you no longer expect to add many more elements to the list and want to save memory.

Note that calling trimToSize() does not affect the functionality of the ArrayList. If you subsequently add more elements, the ArrayList will automatically resize its internal array to accommodate the additional elements, as needed.


ArrayList – duplicate and null values

It’s important to note that ArrayList treats each occurrence of a duplicate or null value as a separate entry.

Duplicate Values: ArrayList permits storing duplicate values. You can add multiple occurrences of the same element to the list, and they will be stored as separate entries.

Null Values: ArrayList allows storing null values. You can add null as an element to the list without any issues.

ArrayList<String> myList = new ArrayList<>();
myList.add("Apple");
myList.add(null);
myList.add("Banana");
myList.add(null);
myList.add("Apple");

System.out.println(myList.contains(null));       // Output: true
System.out.println(myList.indexOf(null));        // Output: 1
System.out.println(myList.lastIndexOf(null));    // Output: 3

System.out.println(myList);  // Output: [Apple, null, Banana, null, Apple]


ArrayList methods

add(index, element) method

The ArrayList class in Java provides the add(int index, E element) method, which allows you to insert an element at a specific position in the list. Here’s how the add(int index, E element) method works:

public void add(int index, E element)
  • The index parameter represents the position at which you want to insert the element. It should be within the range of 0 to size() (inclusive).
  • The element parameter represents the element to be inserted at the specified index.

When you call add(int index, E element), the method inserts the specified element at the specified index in the ArrayList. The existing elements from the index onwards are shifted to the right (their indices are incremented by 1).

        // Insert an element at index 1
        myList.add(1, "Mango");

It’s important to note that the add(int index, E element) method should be used with caution, as it can potentially affect the performance for large lists. Shifting elements requires additional processing time, especially when inserting at the beginning or middle of the list.

remove(index) method

The ArrayList class in Java provides the remove(int index) method, which allows you to remove an element at a specific position in the list. Here’s how the remove(int index) method works:

public E remove(int index)
  • The index parameter represents the position of the element to be removed. It should be within the range of 0 to size() - 1 (inclusive).

When you call remove(int index), the method removes the element at the specified index from the ArrayList. The elements to the right of the removed element are shifted to the left (their indices are decremented by 1).

** The method returns the element that was removed from the list.

        // Remove the element at index 1
        String removedElement = myList.remove(1);

It’s important to note that when you remove an element using remove(int index), the indices of the subsequent elements change. Therefore, if you plan to iterate over the list using indices, be cautious when removing elements to avoid skipping or accessing the wrong elements.

boolean contains(Object o)

The contains(Object o) method is a convenient way to check for the presence of an element in an ArrayList without explicitly iterating over the list. It provides a simple and efficient way to perform membership checks.

The contains(Object o) method in the ArrayList class is used to check whether the list contains a specific element. It returns a boolean value indicating whether the specified object is present in the list or not. Here’s how the contains(Object o) method works:

public boolean contains(Object o)
  • The o parameter represents the object that you want to check for presence in the list.

When you call contains(Object o), the method checks if the specified object is present in the ArrayList. It iterates over the elements in the list and uses the equals() method to compare the specified object with each element. If a matching element is found, the method returns true. If the specified object is not found in the list or if the list is empty, the method returns false.

        // Create an ArrayList
        ArrayList<String> myList = new ArrayList<>();

        // Add elements to the ArrayList
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Orange");

        // Check if "Banana" is present in the ArrayList
        boolean containsBanana = myList.contains("Banana");
        System.out.println("ArrayList contains 'Banana': " + containsBanana);
 // true

We then use the contains("Banana") method to check if the ArrayList contains the element “Banana”. The method returns true because “Banana” is present in the list.

contains(Object o) relation with equals() method

The contains(Object o) method in the ArrayList class uses the equals() method to determine whether the specified object is present in the list.

When contains(Object o) is called, it iterates over the elements in the ArrayList and calls the equals() method on each element to compare it with the specified object o. By using equals(), the method can perform customized equality checks if the element class overrides the equals() method.

By default, the equals() method in the Object class compares the references of objects, which means two different instances with the same content are considered unequal. However, many classes in Java, including String, Integer, and custom classes, override the equals() method to provide meaningful equality comparisons based on their internal state.

Therefore, when using contains(Object o) with an ArrayList, it’s important to consider how the equals() method is defined for the objects you are working with.

** If you’re working with custom classes, you may need to override the equals() method to ensure proper equality comparisons according to your specific requirements.

By relying on the equals() method, contains(Object o) provides flexibility to handle various types of objects and supports customized equality comparisons, making it suitable for general use cases.

NOTE – The methods contains(), indexOf(), lastIndexOf() – all these methods use equals() method to compare the object with each of the element in the list.


boolean removeAll(Collection<?> c) method

The removeAll(Collection<?> c) method in the ArrayList class is used to remove all elements from the list that are contained in the specified collection.

Here’s how the removeAll(Collection<?> c) method works internally:

  1. Iteration: The removeAll(Collection<?> c) method iterates over each element in the specified collection (c).
  2. Element Comparison: For each element in the specified collection, the removeAll method internally calls the remove(Object o) method to remove all occurrences of that element from the ArrayList. It uses the equals() method to determine the equality between the elements.
  3. Removal: When remove(Object o) is called, it iterates over the elements in the ArrayList and removes all occurrences of the specified object o. This is achieved by shifting the subsequent elements to the left and updating the size of the ArrayList accordingly.
  4. Iteration Completion: After iterating through all the elements in the specified collection, the removeAll method completes the removal process.

The removeAll(Collection<?> c) method returns true if at least one element was removed from the ArrayList. If no elements were removed, it returns false.

Here’s an example that demonstrates the usage of the removeAll(Collection<?> c) method:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ArrayListExample {
    public static void main(String[] args) {
        // Create an ArrayList
        ArrayList<String> myList = new ArrayList<>(Arrays.asList("Apple", "Banana", "Orange", "Apple"));

        System.out.println("ArrayList before removeAll: " + myList);

        // Create a collection with elements to remove
        List<String> toRemove = Arrays.asList("Apple", "Orange");

        // Remove elements from the ArrayList
        boolean removed = myList.removeAll(toRemove);

        System.out.println("Elements removed: " + removed);
        System.out.println("ArrayList after removeAll: " + myList);
    }
}

Output :

ArrayList before removeAll: [Apple, Banana, Orange, Apple]
Elements removed: true
ArrayList after removeAll: [Banana]

We use the removeAll(toRemove) method to remove all occurrences of the elements in the toRemove collection from the ArrayList. In this case, “Apple” and “Orange” are removed.

Internally, the removeAll(Collection<?> c) method iterates over the elements in the specified collection, calls contains() method and if element is present, removes the matching element from the ArrayList, using the remove(Object o) method. It repeats this process for each element in the collection until all occurrences are removed.


ArrayList example

Here’s an example that demonstrates the usage of various methods in the ArrayList class :

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        // Create an ArrayList
        ArrayList<String> myList = new ArrayList<>();

        // Add elements to the ArrayList
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Orange");

        // Accessing elements using get() method
        System.out.println("Element at index 0: " + myList.get(0));
        System.out.println("Element at index 1: " + myList.get(1));
        System.out.println("Element at index 2: " + myList.get(2));

        // Modifying elements using set() method
        myList.set(1, "Grapes");

        // Printing the modified ArrayList
        System.out.println("Modified ArrayList: " + myList);

        // Checking the size of the ArrayList
        System.out.println("Size of ArrayList: " + myList.size());

        // Checking if the ArrayList is empty
        System.out.println("Is ArrayList empty? " + myList.isEmpty());
    }
}

Output :

Element at index 0: Apple
Element at index 1: Banana
Element at index 2: Orange
Modified ArrayList: [Apple, Grapes, Orange]
Size of ArrayList: 3
Is ArrayList empty? false

In the above example:

  1. We create an ArrayList called myList.
  2. We use the add() method to add three elements (“Apple”, “Banana”, and “Orange”) to the ArrayList.
  3. We use the get() method to access and print the elements at specific indices (0, 1, and 2).
  4. We use the set() method to modify the element at index 1, replacing “Banana” with “Grapes”.
  5. We print the modified ArrayList using System.out.println().
  6. We use the size() method to check the size of the ArrayList and print it.
  7. We use the isEmpty() method to check if the ArrayList is empty (contains no elements) and print the result.

Another example :

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        // Create an ArrayList
        ArrayList<String> myList = new ArrayList<>();

        // Add elements to the ArrayList
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Orange");

        // Accessing elements using get() method
        System.out.println("Element at index 0: " + myList.get(0));
        System.out.println("Element at index 1: " + myList.get(1));
        System.out.println("Element at index 2: " + myList.get(2));

        // Modifying elements using set() method
        myList.set(1, "Grapes");

        // Printing the modified ArrayList
        System.out.println("Modified ArrayList: " + myList);

        // Checking the size of the ArrayList
        System.out.println("Size of ArrayList: " + myList.size());

        // Checking if the ArrayList is empty
        System.out.println("Is ArrayList empty? " + myList.isEmpty());

        // Removing elements using remove() method
        myList.remove("Apple");

        // Printing the ArrayList after removing an element
        System.out.println("ArrayList after removing an element: " + myList);

        // Removing all elements using removeAll() method
        myList.removeAll(myList);

        // Printing the ArrayList after removing all elements
        System.out.println("ArrayList after removing all elements: " + myList);

        // Adding elements to the ArrayList again
        myList.add("Mango");
        myList.add("Pineapple");

        // Iterating over elements using an enhanced for loop
        System.out.println("Iterating over elements:");
        for (String element : myList) {
            System.out.println(element);
        }
    }
}

Output :

Element at index 0: Apple
Element at index 1: Banana
Element at index 2: Orange
Modified ArrayList: [Apple, Grapes, Orange]
Size of ArrayList: 3
Is ArrayList empty? false
ArrayList after removing an element: [Grapes, Orange]
ArrayList after removing all elements: []
Iterating over elements:
Mango
Pineapple

In the above example:

  1. We create an ArrayList called myList.
  2. We use the add() method to add three elements (“Apple”, “Banana”, and “Orange”) to the ArrayList.
  3. We use the get() method to access and print the elements at specific indices (0, 1, and 2).
  4. We use the set() method to modify the element at index 1, replacing “Banana” with “Grapes”.
  5. We print the modified ArrayList using System.out.println().
  6. We use the size() method to check the size of the ArrayList and print it.
  7. We use the isEmpty() method to check if the ArrayList is empty (contains no elements) and print the result.
  8. We use the remove() method to remove the element “Apple” from the ArrayList.
  9. We print the ArrayList after removing an element.
  10. We use the removeAll() method to remove all elements from the ArrayList.
  11. We print the ArrayList after removing all elements.
  12. We add elements “Mango” and “Pineapple” to the ArrayList again.
  13. We use an enhanced for loop to iterate over the elements in the ArrayList and print each element.

List Interface

What and Why List interface

The List interface in Java is used when you need to store a collection of elements in a specific order that allows duplicate elements. It is part of the Java Collections Framework and provides a flexible and powerful way to manage and manipulate ordered collections.

Here are some reasons why you would use the List interface in Java:

  1. Order Preservation: The List interface maintains the order of elements as they are inserted. This allows you to access elements by their index and perform operations like appending, inserting, and removing elements at specific positions.
  2. Indexed Access: List provides direct access to elements based on their index using methods like get(int index). This makes it convenient to retrieve and modify elements at specific positions within the list.
  3. Duplicate Elements: Unlike other collection types like Set, List allows duplicate elements. You can add multiple occurrences of the same element to the list, and they will be stored as separate entries.
  4. Dynamic Size: List implementations, such as ArrayList or LinkedList, automatically handle resizing as elements are added or removed. This allows you to dynamically change the size of the list without needing to manage the underlying data structure manually.
  5. Iteration and Manipulation: The List interface provides several methods to iterate over elements, such as enhanced for loop, Iterator, or ListIterator. It also offers numerous methods to add, remove, or modify elements, allowing you to perform various operations on the collection.
  6. Compatibility with Algorithms: The List interface is widely used in Java libraries and APIs. It is compatible with numerous algorithms, sorting methods, and utility classes provided by the Java Collections Framework. It allows you to leverage the rich functionality available for working with ordered collections.
  7. Flexibility: The List interface has various implementations to choose from, depending on your specific requirements. For example, ArrayList provides fast element access and manipulation, while LinkedList offers efficient insertion and removal at both ends of the list.

Overall, the List interface is suitable when you need to manage ordered collections that allow duplicates and require frequent access to elements based on their position. It provides a rich set of methods and compatibility with existing Java libraries, making it a versatile choice for many use cases.


List interface syntax as in Java API

The syntax of the List interface in the Java API is as follows :

public interface List<E> extends Collection<E> {
    // Positional Access
    E get(int index);
    E set(int index, E element); // inserts and returns previous element at index.
    void add(int index, E element);
    void add(E element); // adds to the end of list
    E remove(int index);
    boolean addAll(int index, Collection<? extends E> c); // add all from Collection

    // Search Operations
    int indexOf(Object o); // returns -1 if object not found
    int lastIndexOf(Object o);

    // List Iterators
    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);

    // View Operations
    List<E> subList(int fromIndex, int toIndex);
}

In the above syntax, the List interface is defined as a generic interface (List<E>) that can work with elements of any specific type represented by the type parameter E.

The interface extends the Collection interface, inheriting its methods and adding additional methods specific to lists.

The List interface includes methods categorized into positional access, search operations, list iterators, and view operations.

The positional access methods include get(int index), set(int index, E element), add(int index, E element), and remove(int index). These methods allow you to retrieve an element at a specific index, modify the element at a given index, insert an element at a particular index, and remove an element at a specific index, respectively.

The search operations methods are indexOf(Object o) and lastIndexOf(Object o). These methods return the index of the first occurrence and the last occurrence of the specified object in the list, respectively. If the object is not found, these methods return -1.

The list iterator methods are listIterator() and listIterator(int index). These methods return a ListIterator that allows bidirectional iteration over the elements of the list. The second variant allows you to specify the starting position for the iterator.

  • ListIterator is a sub interface of Iterator interface.

The range view operation method is subList(int fromIndex, int toIndex). This method returns a view of the list between the specified fromIndex (inclusive) and toIndex (exclusive).

  • Returned list is backed by the original list, meaning any change made in the returned list will be reflected in original list and vice versa. Changes to the sublist will be reflected in the original list and vice versa.
  • ** To the returned list, we can do both data related changes(meaning updating data) or structural changes like adding / removing elements etc. The changes will get reflected in original list. However, to the original list, if we do any structural changes like adding / removing elements, then the returned list which is the view, will be invalidated and trying to access elements using returned list will throw ConcurrentModificationException.

Note that the List interface inherits other methods from the Collection interface, such as add(E e), remove(Object o), contains(Object o), size(), isEmpty(), addAll(Collection<? extends E> c), removeAll(Collection<?> c), retainAll(Collection<?> c), containsAll(Collection<?> c), toArray(), and toArray(T[] a).

The List interface serves as a contract for list implementations and provides a wide range of methods to manage ordered collections.

Collection interface

What is Collection Interface

The Collection interface in Java is the foundation of the Java Collections Framework. It represents a group of objects, known as elements, and provides a set of methods to manipulate and operate on these elements.

The Collection interface is a root interface that extends the Iterable interface, allowing collections to be iterated over using the enhanced for loop or Iterator interface. Since, it is root interface, polymorphically it provides maximum generality.

The AbstractCollection class in Java is an abstract base class that provides a skeletal implementation of the Collection interface. It serves as a convenient starting point for creating custom collection classes by handling common functionality and reducing the implementation effort required for concrete collection classes.

Collection interface extends another interface called Iterable interface which would enable any collection object to be used in for each loops.


Collection interface syntax as in Java API

public interface Collection<E> extends Iterable<E> {
    // Basic Operations
    boolean add(E e);
    boolean remove(Object o);
    boolean contains(Object o);
    int size();
    boolean isEmpty();
    void clear();

    // Bulk Operations
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    boolean containsAll(Collection<?> c);
    void clear(); // optional, removes all elements from collection.
  
    // Array Conversion
    Object[] toArray();
    <T> T[] toArray(T[] a);

    // Iteration
    Iterator<E> iterator();

    // Stream Support (Java 8+)
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

In the above syntax, the Collection interface is defined as a generic interface (Collection<E>) that can work with elements of any specific type represented by the type parameter E.

The interface extends the Iterable interface, allowing collections to be iterated over using the enhanced for loop or the Iterator interface.


Collection interface methods

The Collection interface defines a set of methods categorized as basic operations, bulk operations, array conversion, iteration, and stream support (added in Java 8).

The basic operations include methods like add(), remove(), contains(), size(), isEmpty(), and clear(). These methods provide common functionality for adding, removing, checking the presence of elements, and managing the size and emptiness of the collection.

The bulk operations include methods like addAll(), removeAll(), retainAll(), and containsAll(). These methods allow collections to be modified based on other collections, such as adding all elements from another collection, removing elements common to another collection, retaining only elements from another collection, and checking if the collection contains all elements of another collection.

The array conversion methods toArray() and toArray(T[] a) allow the collection to be converted into an array. The first method returns an Object array, while the second method allows specifying the type of the array.

The iterator() method returns an Iterator over the elements of the collection, allowing for iteration and sequential access to the elements.

Starting from Java 8, the stream() and parallelStream() methods are provided for supporting streaming operations using the Stream API.

Concrete implementations of the Collection interface, such as ArrayList, LinkedList, and HashSet, provide additional methods and functionalities specific to their implementations.


Examples

<T> T[] toArray(T[] a) method

import java.util.ArrayList;
import java.util.List;

public class ToArrayExample {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        myList.add("Hello");
        myList.add("World");

        // Create an array of String with the same size as the list
        String[] array = new String[myList.size()];

        // Convert the list to an array using the toArray() method
        String[] resultArray = myList.toArray(array);

        // Print the elements of the resulting array
        for (String element : resultArray) {
            System.out.println(element);
        }
    }
}

In this example, we have a List of strings called myList. We want to convert this list to an array of strings using the toArray(T[] a) method.

First, we create a String array called array with the same size as the list using myList.size(). Then, we call the toArray() method on the myList object and pass array as the argument.

** The toArray(T[] a) method takes the provided array as an argument and populates it with the elements of the list. If the provided array is large enough to accommodate all the elements, it will be used. Otherwise, a new array of the same runtime type and size will be created and returned.

This usage of toArray(T[] a) allows you to convert a List or any other Collection into an array of the desired type. It provides a way to obtain an array representation of the collection’s elements, which can be useful in scenarios where you specifically need an array or want to interface with code that requires an array.

Object[] toArray()

import java.util.ArrayList;
import java.util.List;

public class ToArrayExample {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        myList.add("Hello");
        myList.add("World");

        // Convert the list to an array using the toArray() method
        Object[] resultArray = myList.toArray();

        // Print the elements of the resulting array
        for (Object element : resultArray) {
            System.out.println(element);
        }
    }
}

In this example, we have a List of strings called myList. We want to convert this list to an array of Object using the toArray() method.

We simply call the toArray() method on the myList object without passing any argument. The toArray() method returns an array of Object, where each element of the list is copied into the corresponding position of the array.

Finally, we iterate over the resulting array (resultArray) and print its elements. Since the resultArray is of type Object[], we need to use the Object type to iterate over the elements.

The toArray() method is useful when you need to obtain a generic array representation of a List or any other Collection. However, keep in mind that the resulting array will have the runtime type of Object[].

** If you require a specific type of array, you can use the overloaded toArray(T[] a) method and pass an array of the desired type.


How to enable your class objects to be iterable

To enable your class objects to be iterable, you need to implement the Iterable interface and provide an implementation of the iterator() method. This allows instances of your class to be used in enhanced for loops or with the Iterator interface for iteration.

Here’s an example of how to enable your class objects to be iterable:

import java.util.Iterator;

public class MyIterableClass<T> implements Iterable<T> {
    private T[] elements;

    public MyIterableClass(T[] elements) {
        this.elements = elements;
    }

    // provide an implementation for iterator() method
    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    // private class implementing Iterator interface
    private class MyIterator implements Iterator<T> {
        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < elements.length;
        }

        @Override
        public T next() {
            return elements[currentIndex++];
        }
    }

    // Other methods and code specific to your class
}

In the example above, the MyIterableClass implements the Iterable interface by specifying the type parameter <T>. It provides an implementation of the iterator() method that returns an instance of the custom MyIterator class.

The MyIterator class implements the Iterator interface and defines the hasNext() and next() methods required for iteration. It keeps track of the current index to provide the next element from the array of elements.

With this implementation, you can use instances of MyIterableClass in enhanced for loops or with the Iterator interface as follows:

MyIterableClass<String> iterable = new MyIterableClass<>(new String[]{"Hello", "World"});

// Using enhanced for loop
for (String element : iterable) {
    System.out.println(element);
}

// Using Iterator
Iterator<String> iterator = iterable.iterator();
while (iterator.hasNext()) {
    String element = iterator.next();
    System.out.println(element);
}

By implementing the Iterable interface and providing an iterator implementation, you enable your class objects to be

  • iterated over using enhanced for loops or
  • by obtaining an iterator explicitly.

This allows for a more intuitive and convenient way of working with instances of your class in iterative scenarios.


Collections Framework – Introduction

What is Collections Framework

The Collections Framework in Java is a unified architecture that provides a set of interfaces, classes, and algorithms to manipulate and store collections of objects. It was introduced in Java 1.2 and is part of the java.util package.

The Collections Framework offers a wide range of data structures, such as lists, sets, queues, and maps, along with various utility classes for working with collections. These structures and utilities are designed to be efficient, flexible, and reusable.

These are ready to use, highly efficient data structures which we can use right out of the box without having to implement ourselves.

Key components of the Collections Framework

Here are some key components of the Collections Framework:

  1. Interfaces: The framework defines several core interfaces, including List, Set, Queue, Map, and their sub-interfaces. These interfaces provide common methods and contracts for working with collections.
  2. Implementations: The framework provides various classes that implement the collection interfaces. For example, ArrayList and LinkedList are implementations of the List interface, HashSet and TreeSet implement the Set interface, and HashMap and TreeMap implement the Map interface. These implementations offer different performance characteristics and behaviors to suit different needs.
  3. Algorithms: The Collections class provides a set of algorithms, such as sorting, searching, shuffling, and reversing, which can be applied to collections. These algorithms are implemented as static methods and can be used with any collection that satisfies the requirements.
  4. Utility classes: The Collections Framework includes utility classes like Collections and Arrays that provide additional functionality for manipulating and working with collections. These classes offer methods for operations like collection conversion, synchronization, and filling collections with default values.

The Collections Framework simplifies the process of working with collections by providing a consistent and standardized way to handle them. It promotes code reusability and improves code quality by encouraging the use of interfaces and providing efficient implementations for common collection types.

General Requirements

General requirements which need to be fulfilled by data structures for real world use cases :

  1. No knowledge about size
  2. Automatically extendable
  3. Fast random access
  4. Fast lookups
  5. Ordered vs Unordered
  6. null vs non-null data
  7. Duplicate vs Unique
  8. Automatic sorting
  9. <key, value> mapping

Collections Framework includes different kinds of data structures that would meet one or more of these requirements.

Core Interfaces in Collections Framework

source – https://techvidvan.com/tutorials/java-collection-framework/

  • Collection – would represent a collection of objects.
  • Map – would represent <key, value> pairs.

Most of the implementation permit storing null values; the implementations are not synchronized.

SortedSet, SortedMap – as the name implies they ensure that the data they store is automatically sorted, meaning elements are placed at appropriate positions.

NOTE – Set implementations internally use Map implementations.


Legacy Implementations

In the Java Collections Framework, there are certain legacy implementations that have been retained for compatibility reasons but are generally considered less preferred than their modern alternatives.

The legacy implementations are considered less efficient due to factors such as synchronization overhead, lack of type safety, and limited functionality. The modern alternatives provide improved performance, type safety, and additional features.

Legacy implementations – these are synchronized data structures. Recommended, not to use these classes any more.

  • Vector – replace with ArrayList, LinkedList
  • HashTable – replace with HashMap, LinkedHashMap
  • Stack – replace with ArrayDeque


Set implementation uses Map

In Java, the implementation of the Set interface in the Collections Framework often internally uses a Map implementation to store its elements.

The Set interface is designed to store a collection of unique elements, where duplicates are not allowed. To ensure uniqueness, Set implementations rely on the keys of a Map to store the elements. The elements themselves are stored as keys, leveraging the fact that a Map cannot contain duplicate keys.

When adding elements to a Set, the implementation typically adds the elements as keys into an underlying Map implementation and uses a dummy value (e.g., Boolean.TRUE) as the associated value.

  • The map’s key set represents the elements of the Set, and the uniqueness property is automatically enforced by the map’s behavior.

By utilizing a Map internally, Set implementations can take advantage of the efficient key-based operations provided by the map, such as constant-time contains, add, and remove operations, which are crucial for maintaining the integrity of a Set.

It’s important to note that the specific implementation details may vary across different Set implementations in Java, such as HashSet, TreeSet, or LinkedHashSet. However, the underlying usage of a Map data structure to enforce uniqueness remains a common approach.


Java Collection Framework classes – Serializable

Most of the implementations in Collection Framework are serializable.

In Java, when a class implements the Serializable interface, it means that objects of that class can be converted into a stream of bytes and then saved to a file or transferred over a network. The process of converting an object into a stream of bytes is known as serialization.

The Serializable interface acts as a marker interface, meaning it does not define any methods that need to be implemented. Its purpose is to indicate that an object of the implementing class can be serialized.

For the Java Collections Framework, many of the classes and interfaces, including List, Set, Map, and their implementations like ArrayList, HashSet, HashMap, etc., are designed to be serializable. This allows you to serialize instances of these classes and store them persistently or transmit them across a network.

When a collection class is serializable, it means you can write the collection object to an ObjectOutputStream and read it back from an ObjectInputStream, preserving its state and contents.

Here’s an example of serializing a List implementation (ArrayList) to a file:

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class SerializationExample {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        myList.add("Hello");
        myList.add("World");

        // Serialize the list to a file
        try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("list.ser"))) {
            outputStream.writeObject(myList);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize the list from the file
        try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("list.ser"))) {
            List<String> deserializedList = (List<String>) inputStream.readObject();
            System.out.println(deserializedList);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

In this example, the ArrayList object myList is serialized to a file named list.ser. Later, the list is deserialized and printed, restoring its original state.

** It’s worth noting that not all classes in the Java Collections Framework are serializable. For example, concurrent collections like ConcurrentHashMap and specialized collections like PriorityQueue may not implement the Serializable interface due to their complex internal state or synchronization requirements.

When using serialization, it’s important to be aware of the potential issues related to versioning, security, and compatibility between different Java versions or platforms. It’s recommended to carefully design and test the serialization and deserialization process to ensure data integrity and compatibility.


Java Collection Framework classes – Clone

In the Java Collections Framework, some classes provide a clone() method to create a shallow copy of the object. The clone() method creates a new instance of the same class and copies the values of the internal data structures and fields into the new instance. However, it’s important to note that the clone() method is considered to be somewhat problematic and is not widely used in modern Java programming.

  • By default, the clone() method creates a shallow copy of the object. This means that the internal data structures are not cloned deeply, and both the original and cloned objects share references to the same objects. Changes made to the shared objects may affect both the original and cloned instances.
  • Required Casting: The clone() method returns a copy of the object as an Object type. To use the cloned object as the specific type, you need to cast it appropriately. For example:
ArrayList<String> originalList = new ArrayList<>();
originalList.add("Hello");
originalList.add("World");

ArrayList<String> clonedList = (ArrayList<String>) originalList.clone();

Alternatives: In modern Java programming, it is often recommended to use alternative approaches for creating copies of objects, such as copy constructors, factory methods, or the copyOf() methods provided by some collection classes (List.copyOf(), Set.copyOf(), etc.). These approaches provide more control and clarity over the copying process.

To summarize, while the clone() method exists in some classes of the Java Collections Framework, it is generally not recommended to use clone() method due to its limitations and potential issues. It is advisable to consider alternative approaches for creating copies of objects that better suit the requirements of your specific use case.


SQL commands

These SQL commands are run on MySQL :

To connect to MySQL

mysql -u root -p
<-- it will prompt for password -->

To list available databases

SHOW DATABASES;

Create Database

The general command for creating a database:

CREATE DATABASE <database_name>;
CREATE DATABASE soap_store;

Drop database

DROP DATABASE <database-name>;

To use a database

USE <database-name>;

Create table

syntax :

    CREATE TABLE cats (
        name VARCHAR(50),
        age INT
    );
     
    CREATE TABLE dogs (
        name VARCHAR(50),
        breed VARCHAR(50),
        age INT
    );

Display all tables in database / Table Structure

SHOW TABLES;
SHOW COLUMNS FROM <tablename>;    <-- this command to see the table structure
(or)
DESC <tablename>;

Dropping tables / Deleting table

-- To drop a table
DROP TABLE <tablename>;

Create the table:

    CREATE TABLE pastries
      (
        name VARCHAR(50),
        quantity INT
      );
-- table creation
CREATE TABLE IF NOT EXISTS APP_LOGS
(
    LOG_ID              INT AUTO_INCREMENT NOT NULL UNIQUE KEY,
    DB_SESSION_ID       INT                NOT NULL, 
    MODULE              VARCHAR(255)           NULL,
    TYPE                VARCHAR(16)            NULL,
    MESSAGE             TEXT                   NULL,     
    LOG_DATE            TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View tables:

SHOW TABLES;

View details of pastries table:

DESC pastries;

Delete the whole pastries table:

DROP TABLE pastries;

INSERT: The Basics

-- while inserting data - the order matters : column names and the data values
INSERT INTO cats (name, age) VALUES ('Blue Steele', 5);

-- switching order of age and name
INSERT INTO cats (age, name) VALUES (3, 'Scottish Beth');

INSERT INTO cats (name, age) 
       VALUES ('Jenkins', 7);


-- after inserting data, if you want to know data exists or not
-- To view all rows in our table
SELECT * FROM cats;

Multiple Inserts

-- inserting multiple rows with a single INSERT statement
INSERT INTO cats (name, age) 
       VALUES 
          ('Meatball', 5), 
          ('Turkey', 1), 
          ('Potato Face', 15);

Exercise :

    CREATE TABLE people
      (
        first_name VARCHAR(20),
        last_name VARCHAR(20),
        age INT
      );

    INSERT INTO people(first_name, last_name, age)
    VALUES ('Tina', 'Belcher', 13);

    INSERT INTO people(age, last_name, first_name)
    VALUES (42, 'Belcher', 'Bob');

    --  inserting multiple rows with single INSERT statement
    INSERT INTO people(first_name, last_name, age)
           VALUES
                ('Linda', 'Belcher', 45),
                ('Phillip', 'Frond', 38),
                ('Calvin', 'Fischoeder', 70);

Using NOT NULL

    CREATE TABLE cats2 (
        name VARCHAR(100) NOT NULL,
        age INT NOT NULL
    );

Quotation marks

It is a good practice to wrap up text related data in single quotes. If data contains single quotes, u can use escape sequences.

INSERT INTO shops (name) VALUES ('shoe emporium');

-- use escape sequence to include single quote in text value
INSERT INTO shops (name) VALUES ('mario\'s pizza');

INSERT INTO shops (name) VALUES ('she said "haha"');

DEFAULT values

CREATE TABLE cats3  (    
        name VARCHAR(20) DEFAULT 'no name provided',    
        age INT DEFAULT 99  
);

INSERT INTO cats3(age) VALUES(2);

INSERT INTO cats3() VALUES();

Having DEFAULT value for a column doesn’t guarantee that it can’t have NULL values. We can manually set NULL value for that column.

Combine NOT NULL and DEFAULT

CREATE TABLE cats4  (    
        name VARCHAR(20) NOT NULL DEFAULT 'unnamed',    
        age INT NOT NULL DEFAULT 99 
);

Primary Key

-- creating primary key for the table
-- this method is useful if primary key is based on single column
CREATE TABLE unique_cats (
    	cat_id INT PRIMARY KEY,
        name VARCHAR(100) NOT NULL,
        age INT
);

another option of specifying primary key :

-- creating primary key after all columns are specified
-- this option is useful if we have primary key of multiple columns
CREATE TABLE unique_cats2 (
    	cat_id INT,
        name VARCHAR(100),
        age INT,
        PRIMARY KEY (cat_id, name) 
);

Primary keys cannot be NULL. So it is redundant to specify NOT NULL for the columns that are part of primary key.

Primary key constraints are NOT NULL.

AUTO_INCREMENT

automatically increment for each row inserted into the table, starting with value 1 (by default).

CREATE TABLE unique_cats3 (
      cat_id INT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(100) NOT NULL,
      age INT NOT NULL
);

To change default value for AUTO_INCREMENT :

ALTER TABLE <tablename> AUTO_INCREMENT = 100;

This statement alters the table’s AUTO_INCREMENT value to start from 100. Note that this statement will only affect future inserts into the table. If there are existing rows in the table with lower primary key values, they will not be modified. The next insert operation will use 100 as the starting value for the AUTO_INCREMENT column.


Exercise – Creating EMPLOYEES table

CREATE TABLE employees (
        id INT AUTO_INCREMENT,
        first_name VARCHAR(255) NOT NULL,
        last_name VARCHAR(255) NOT NULL,
        middle_name VARCHAR(255),
        age INT NOT NULL,
        current_status VARCHAR(255) NOT NULL DEFAULT 'employed',
        PRIMARY KEY(id)
);

-- inserting a row into the table
INSERT INTO employees(first_name, last_name, age) 
       VALUES ('Dora', 'Smith', 58);


Creating new Table and populating with Data

    DROP TABLE cats;

    -- creating a table
    CREATE TABLE cats (
        cat_id INT AUTO_INCREMENT,
        name VARCHAR(100),
        breed VARCHAR(100),
        age INT,
        PRIMARY KEY (cat_id)
    ); 

    -- populating data ino table
    INSERT INTO cats(name, breed, age) 
      VALUES ('Ringo', 'Tabby', 4),
             ('Cindy', 'Maine Coon', 10),
             ('Dumbledore', 'Maine Coon', 11),
             ('Egg', 'Persian', 4),
             ('Misty', 'Tabby', 13),
             ('George Michael', 'Ragdoll', 9),
             ('Jackson', 'Sphynx', 7);

    -- reading data from table
    -- To get all the columns
    SELECT * FROM cats;

    -- To only get the age column
    SELECT age FROM cats;

    -- To select multiple specific columns
    SELECT name, breed FROM cats;

WHERE Clause

-- Use where to specify a condition
SELECT * FROM cats WHERE age = 4;

SELECT * FROM cats WHERE name ='Egg';

By default, MySQL’s SELECT queries are case-insensitive for string comparisons. By default, MySQL uses a case-insensitive collation, such as utf8_general_ci (CI stands for case-insensitive) or utf8mb4_general_ci. In this case, string comparisons in SELECT queries are case-insensitive.

SELECT * FROM table_name WHERE column_name = 'apple';

This query would match rows with values ‘apple’, ‘Apple’, or ‘APPLE’.

If you need case-sensitive comparisons in your SELECT query, you can explicitly specify a case-sensitive collation, such as utf8_bin (BIN stands for binary).

SELECT * FROM table_name WHERE column_name COLLATE utf8_bin = 'apple';

This query would only match rows with the exact value ‘apple’, considering the case.

The case sensitivity behavior can also be defined at the column level. By specifying a case-sensitive collation for a specific column, you can override the default behavior for that column.

CREATE TABLE table_name (
    column_name VARCHAR(50) COLLATE utf8_bin
);

In this case, the column_name column would have a case-sensitive collation.

Aliases

Use ‘AS’ to alias a column in your results. It is used to rename column so that it is easier to understand.

SELECT cat_id AS id, name FROM cats;

UPDATE statement

Good Thumb rule – Test your WHERE clause with SELECT query before trying out with UPDATE / DELETE statements.

UPDATE cats SET breed='Shorthair' WHERE breed='Tabby';

UPDATE cats SET age=14 WHERE name='Misty';

-- More exercises

SELECT * FROM cats WHERE name='Jackson'; 
 
UPDATE cats SET name='Jack' WHERE name='Jackson'; 
 
SELECT * FROM cats WHERE name='Jackson'; 
 
SELECT * FROM cats WHERE name='Jack'; 
 
SELECT * FROM cats WHERE name='Ringo'; 
 
UPDATE cats SET breed='British Shorthair' WHERE name='Ringo'; 
 
SELECT * FROM cats WHERE name='Ringo'; 
 
SELECT * FROM cats; 

SELECT * FROM cats WHERE breed='Maine Coon'; 
 
UPDATE cats SET age=12 WHERE breed='Maine Coon'; 
 
SELECT * FROM cats WHERE breed='Maine Coon';

DELETE Statement

-- Delete all cats with name of 'Egg'
DELETE FROM cats WHERE name='Egg';

-- Delete all rows in the cats table
DELETE FROM cats;

In SQL, “TRUNCATE” and “DELETE” are two different commands used to remove data from database tables, but they function in distinct ways.

The TRUNCATE command is a Data Definition Language (DDL) operation used to quickly and efficiently remove all rows from a table. When you execute the TRUNCATE command, it removes all data from the specified table, but it retains the table structure and any associated indexes, triggers, or constraints.

TRUNCATE cannot be used on tables with foreign key constraints unless you disable or drop the constraints first.

Once executed, TRUNCATE cannot be undone, and the data cannot be recovered.

TRUNCATE TABLE table_name;

The DELETE command is a Data Manipulation Language (DML) operation used to remove specific rows from a table based on specified conditions. It allows you to selectively delete rows based on criteria such as a WHERE clause.

DELETE FROM table_name WHERE condition;

DELETE is slower than TRUNCATE since it logs each individual row deletion.

DELETE is a transactional operation that can be rolled back if executed within a transaction.

DELETE can be used with tables having foreign key constraints, and it can automatically handle cascading deletes if configured.

TRUNCATE is non-transactional, irreversible, and does not log individual deletions, while DELETE is transactional, reversible, and logs each row deletion.

Eclipse – for Java Development

What is Eclipse IDE

Eclipse is an Integrated Development Environment (IDE) that is widely used by developers for Java development, although it supports various other programming languages as well.

It offers a range of features and tools that make Java programming more efficient and productive. Here are some key features of Eclipse IDE for Java development:

  1. Code Editing: Eclipse provides a powerful code editor with features like syntax highlighting, code completion, and code templates. It also supports automatic formatting and refactoring to improve code readability and maintainability.
  2. Integrated Debugger: Eclipse includes a debugger that allows you to step through your code, set breakpoints, inspect variables, and analyze the flow of your program during runtime, helping you identify and fix bugs.
  3. Build and Compilation Tools: Eclipse integrates with build tools like Apache Maven and Ant, allowing you to manage dependencies, build projects, and run unit tests within the IDE. It also provides quick access to compile and run Java applications.
  4. Integrated Development Environment: Eclipse offers a comprehensive development environment with a project management system that organizes your Java projects, files, and resources. It supports version control systems like Git, enabling collaborative development.
  5. Plug-in Ecosystem: Eclipse is highly extensible through its plug-in architecture. It has a vast ecosystem of plugins, including those for additional language support, tools for testing, performance analysis, and integration with other frameworks and libraries.
  6. User Interface Design: Eclipse includes tools like WindowBuilder that allow you to design graphical user interfaces (GUIs) using drag-and-drop components. It supports various UI frameworks such as Swing, SWT, and JavaFX.
  7. Documentation and Help: Eclipse provides comprehensive documentation and online resources, including tutorials, user guides, and a vibrant community of developers who actively contribute to forums and provide support.

Eclipse IDE is known for its flexibility and scalability, making it suitable for both small projects and large enterprise applications. It offers a rich set of features specifically tailored to Java development, making it a popular choice among Java developers.

Installing Eclipse

Google for eclipse download and go to the link and download the installer.

https://www.eclipse.org/downloads/ – official link for downloading Eclipse software.

Instead of downloading installer file, go to packages section and download from there. Basically you will get a zip file which you can extract and gets the eclipse software.

Java 8 documentation –

https://docs.oracle.com/javase/8/docs/

–> ** Eclipse requires a compatible version of the Java Development Kit (JDK) to run. Make sure you have the JDK installed on your system. Set the PATH environment correctly to point to bin directory of the jdk installation.

ex : C:\Program Files\Java\jdk-1.8\bin

Check Eclipse binary and its pre-requisite Java versions.

Writing a Java Program in Eclipse

Here are the steps to write a Java program in Eclipse IDE:

  1. Launch Eclipse: Open the Eclipse IDE on your computer.
  2. Create a New Java Project: Go to “File” -> “New” -> “Java Project”. Enter a project name and click “Finish”.
  3. Create a New Java Class: Right-click on the project in the Package Explorer pane and select “New” -> “Class”. Enter a class name and click “Finish”.
  4. Write Java Code: In the newly created class file, you can start writing your Java code. For example, you can define the main method, which is the entry point of your program, as follows:
public class MyClass {
    public static void main(String[] args) {
        // Your code here
    }
}
  1. Write Your Java Code: Within the main method, you can write your Java code. This is where you define the logic of your program. You can use Eclipse’s code editor features, such as code completion, to assist you in writing code faster and with fewer errors.
  2. Save Your Java File: After writing your code, make sure to save the Java file by clicking “Ctrl + S” or going to “File” -> “Save”.
  3. Build and Run Your Program: To build and run your Java program, right-click within the editor area of the Java file and select “Run As” -> “Java Application” or click the “Run” button in the toolbar. Eclipse will compile your code, and the program will run in the console view or a separate console window, depending on your configuration.

That’s it! You have successfully written and executed a Java program in Eclipse IDE. You can continue to edit, build, and run your program as needed, and explore the various features and functionalities provided by Eclipse for Java development.