Generic Constructors in Java

1. Overview

We previously discussed the basics of Java Generics. In this tutorial, we’ll have a look at Generic Constructors in Java.

A generic constructor is a constructor that has at least one parameter of a generic type.

We’ll see that generic constructors don’t have to be in a generic class, and not all constructors in a generic class have to be generic.

2. Non-Generic Class

First, we have a simple class Entry, which is not a generic class:

public class Entry {
    private String data;
    private int rank;
}

In this class, we’ll add two constructors: a basic constructor with two parameters, and a generic constructor.

2.1. Basic Constructor

The first Entry constructor is a simple constructor with two parameters:

public Entry(String data, int rank) {
    this.data = data;
    this.rank = rank;
}

Now, let’s use this basic constructor to create an Entry object:

@Test
public void givenNonGenericConstructor_whenCreateNonGenericEntry_thenOK() {
    Entry entry = new Entry("sample", 1);
    
    assertEquals("sample", entry.getData());
    assertEquals(1, entry.getRank());
}

2.2. Generic Constructor

Next, our second constructor is a generic constructor:

public <E extends Rankable & Serializable> Entry(E element) {
    this.data = element.toString();
    this.rank = element.getRank();
}

Although the Entry class isn’t generic, it has a generic constructor, as it has a parameter element of type E.

The generic type E is bounded and should implement both Rankable and Serializable interfaces.

Now, let’s have a look at the Rankable interface, which has one method:

public interface Rankable {
    public int getRank();
}

And, suppose we have a class Product that implements the Rankable interface:

public class Product implements Rankable, Serializable {
    private String name;
    private double price;
    private int sales;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public int getRank() {
        return sales;
    }
}

We can then use the generic constructor to create Entry objects using a Product:

@Test
public void givenGenericConstructor_whenCreateNonGenericEntry_thenOK() {
    Product product = new Product("milk", 2.5);
    product.setSales(30);
 
    Entry entry = new Entry(product);
    
    assertEquals(product.toString(), entry.getData());
    assertEquals(30, entry.getRank());
}

3. Generic Class

Next, we’ll have a look at a generic class called GenericEntry:

public class GenericEntry<T> {
    private T data;
    private int rank;
}

We’ll add the same two types of constructors as the previous section in this class as well.

3.1. Basic Constructor

First, let’s write a simple, non-generic constructor for our GenericEntry class:

public GenericEntry(int rank) {
    this.rank = rank;
}

Even though GenericEntry is a generic class, this is a simple constructor that doesn’t have a parameter of a generic type.

Now, we can use this constructor to create a GenericEntry<String>:

@Test
public void givenNonGenericConstructor_whenCreateGenericEntry_thenOK() {
    GenericEntry<String> entry = new GenericEntry<String>(1);
    
    assertNull(entry.getData());
    assertEquals(1, entry.getRank());
}

3.2. Generic Constructor

Next, let’s add the second constructor to our class:

public GenericEntry(T data, int rank) {
    this.data = data;
    this.rank = rank;
}

This is a generic constructor, as it has a data parameter of the generic type TNote that we don’t need to add <T> in the constructor declaration, as it’s implicitly there.

Now, let’s test our generic constructor:

@Test
public void givenGenericConstructor_whenCreateGenericEntry_thenOK() {
    GenericEntry<String> entry = new GenericEntry<String>("sample", 1);
    
    assertEquals("sample", entry.getData());
    assertEquals(1, entry.getRank());        
}

4. Generic Constructor with Different Type

In our generic class, we can also have a constructor with a generic type that’s different from the class’ generic type:

public <E extends Rankable & Serializable> GenericEntry(E element) {
    this.data = (T) element;
    this.rank = element.getRank();
}

This GenericEntry constructor has a parameter element with type E, which is different from the T type. Let’s see it in action:

@Test
public void givenGenericConstructorWithDifferentType_whenCreateGenericEntry_thenOK() {
    Product product = new Product("milk", 2.5);
    product.setSales(30);
 
    GenericEntry<Serializable> entry = new GenericEntry<Serializable>(product);

    assertEquals(product, entry.getData());
    assertEquals(30, entry.getRank());
}

Note that:

  • In our example, we used Product (E) to create a GenericEntry of type Serializable (T)
  • We can only use this constructor when the parameter of type E can be cast to T

5. Multiple Generic Types

Next, we have the generic class MapEntry with two generic types:

public class MapEntry<K, V> {
    private K key;
    private V value;

    public MapEntry(K key, V value) {
        this.key = key;
        this.value = value;
    }
}

MapEntry has one generic constructor with two parameters, each of a different type. Let’s use it in a simple unit test:

@Test
public void givenGenericConstructor_whenCreateGenericEntryWithTwoTypes_thenOK() {
    MapEntry<String,Integer> entry = new MapEntry<String,Integer>("sample", 1);
    
    assertEquals("sample", entry.getKey());
    assertEquals(1, entry.getValue().intValue());        
}

6. Wildcards

Finally, we can use wildcards in a generic constructor:

public GenericEntry(Optional<? extends Rankable> optional) {
    if (optional.isPresent()) {
        this.data = (T) optional.get();
        this.rank = optional.get().getRank();
    }
}

Here, we used wildcards in this GenericEntry constructor to bound the Optional type:

@Test
public void givenGenericConstructorWithWildCard_whenCreateGenericEntry_thenOK() {
    Product product = new Product("milk", 2.5);
    product.setSales(30);
    Optional<Product> optional = Optional.of(product);
 
    GenericEntry<Serializable> entry = new GenericEntry<Serializable>(optional);
    
    assertEquals(product, entry.getData());
    assertEquals(30, entry.getRank());
}

Note that we should be able to cast the optional parameter type (in our case, Product) to the GenericEntry type (in our case, Serializable).

7. Conclusion

In this article, we learned how to define and use generic constructors in both generic and non-generic classes.

The full source code can be found over on GitHub.

Related posts:

Overflow and Underflow in Java
Java Program to Find Hamiltonian Cycle in an UnWeighted Graph
Java Switch Statement
String Initialization in Java
Java Program to Find Number of Spanning Trees in a Complete Bipartite Graph
Java Program to Generate All Possible Combinations of a Given List of Numbers
Spring WebClient Requests with Parameters
Collect a Java Stream to an Immutable Collection
Spring Boot Tutorial – Bootstrap a Simple Application
Java Program to Check whether Directed Graph is Connected using DFS
Java Program to Implement Dijkstra’s Algorithm using Priority Queue
Lập trình hướng đối tượng (OOPs) trong java
Checking for Empty or Blank Strings in Java
Java Program to Implement Uniform-Cost Search
Spring Cloud Series – The Gateway Pattern
Java Program to Check if a Directed Graph is a Tree or Not Using DFS
Java Program to Print the Kind of Rotation the AVL Tree is Undergoing
Java Program to Implement Nth Root Algorithm
Hướng dẫn tạo và sử dụng ThreadPool trong Java
Java Program to Find the Nearest Neighbor Using K-D Tree Search
Concrete Class in Java
JUnit 5 @Test Annotation
Java Program to Find the Peak Element of an Array O(n) time (Naive Method)
Quick Guide to Spring MVC with Velocity
Java Program to Create a Minimal Set of All Edges Whose Addition will Convert it to a Strongly Conne...
Runnable vs. Callable in Java
Control the Session with Spring Security
Adding Shutdown Hooks for JVM Applications
Java Program to Implement Cartesian Tree
Java Program to Implement Radix Sort
Java InputStream to Byte Array and ByteBuffer
How to Add a Single Element to a Stream