Generics

CSCI 1913 – Introduction to Algorithms, Data Structures, and Program Development
Adriana Picoral

Polymorphisms in Java

  • Overloading – methods in with the same name (different parameters) in the same class
  • Overriding – methods with the same signature in different (super-sub pair) classes
  • Parametric polymorphism – Generics

“Parametric” – flexible, in this case flexibility for type

The problem to be solved

Imagine we want to write a class called Box that holds one item of any type.

public class BoxString {
    private String item;
    
    public void set(String item) {
        this.item = item;
    }
    
    public String get() {
        return item;
    }
}

The problem to be solved

Imagine we want to write a class called Box that holds one item of any type.

public class BoxPolygon {
    private Polygon item;
    
    public void set(Polygon item) {
        this.item = item;
    }
    
    public Polygon get() {
        return item;
    }
}

The problem to be solved

Imagine we want to write a class called Box that holds one item of any type.

public class BoxCard {
    private Card item;
    
    public void set(Card item) {
        this.item = item;
    }
    
    public Card get() {
        return item;
    }
}

Flawed solution

We can use the Object class as type:

public class Box {
    private Object item;
    
    public void set(Object item) {
        this.item = item;
    }
    
    public Object get() {
        return item;
    }
}

Flawed solution

Box box = new Box();
box.set("Hello");
String str = (String) box.get();  // need to cast - annoying!

box.set(42);
String oops = (String) box.get();  // compiles fine, crashes at runtime!
  • Type casting every time we retrieve something
  • Compiler can’t catch type errors - code crashes at runtime
  • No type safety

Generics

  • Enables code reuse and flexibility
  • Code works independently of the types of values it operates on

Generics allow us to write code that works with multiple types while maintaining type safety.

  • Parametrically polymorphic methods are called generic methods
  • Parametrically polymorphic data types are called generic data types

Box with generics

public class Box<T> {
    private T item;
    
    public void set(T item) {
        this.item = item;
    }
    
    public T get() {
        return item;
    }
}

Box with generics

Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String str = stringBox.get();  // no cast needed!

Box<Integer> intBox = new Box<>();
intBox.set(42);
// intBox.set("Hello");  // compiler error - caught at compile time!

Writing a class with generic type

add <Name> after your class Name with whatever name you want (camel case) , convention is to use Type or T – then use that name when creating instance variables and as return types

public class MyType<Type> {
    private Type data;
    
    public MyType(Type d) {
        data = d;
    }
    
    public Type getData() {
        return data;
    }
}

Using our generic data type

Now, when calling our class we need to specify the actual type inside the <>

MyType<String> name = new MyType<String>("Adriana");
System.out.println(name.getData());

Multiple types

public class MyType<O, T> {
    private O one;
    private T two;
    
    public MyType(O one, T two) {
        this.one = one;
        this.two = two;
    }
    
    public O getOne() {
        return one;
    }
    
    public T getTwo() {
        return two;
    }

}

Multiple types use

MyType<String, Integer> name = new MyType<>("Adriana", 43);
System.out.println(name.getOne());
System.out.println(name.getTwo());

Practice

Goal: Implement a produce class that can have different types of produce (Banana, Avocado, Spinach, Brocolli, etc.) and different types of unit (Double for weight, Integer for count).

Download started code – you will implement and submit to gradescope ProduceQuantity. Here’s how it’s used in the starter code:

ProduceQuantity<Banana, Integer> itemOne = new ProduceQuantity<>(banana, count);
ProduceQuantity<Spinach, Double> itemThree = new ProduceQuantity<>(spinach, weight);

This is what UseShoppingCart.java should print out:

Items in shopping cart:
4 Bananas
1 Avocado
0.5 lbs Spinach
1.0 lbs Brocolli

Practice

Goal: Implement a produce class that can have different types of produce (Banana, Avocado, Spinach, Brocolli, etc.) and different types of unit (Double for weight, Integer for count).

Download started code – you will implement and submit to gradescope ProduceQuantity. Here’s how it’s used in the starter code:

ProduceQuantity<Banana, Integer> itemOne = new ProduceQuantity<>(banana, count);
ProduceQuantity<Spinach, Double> itemThree = new ProduceQuantity<>(spinach, weight);

Public methods in ProduceQuantity: constructor that takes an item and a quantity, getQuantity() and getItem()

Generics Implementation in Java

  • Java does all checking for correctness at compile-time

Type Erasure: During compilation, the generic type information (like T in Box) is “erased” and replaced with its upperbound class (usually Object if no explicit bound is given).

  • At runtime, the JVM does not retain information about the specific type arguments used with a generic class

Java generics limitations

  • Primitive types (int, boolean, double, char) not allowed – Java’s solution “Wraper Types” (Integer, Boolean, Double, Character)
  • We are not allowed to create generic arrays (T[] not allowed) because:
  • We cannot call generic constructors new T() or new T(arg) (no inheritance can enforce constructor parameter lists) error: Type parameter ‘T’ cannot be instantiated directly
    • The type variables are forgotten at runtime
  • We cannot use instanceof on T (for example, T instanceof Double not allowed)

Setting upperbound class

A generic type can extend a class:

public class ProduceQuantity<T extends Produce, U> {

We can then use methods in Produce

return item.isWeighted();

Example – abstract Produce

public abstract class Produce {
    private double price;

    public Produce(double price) {
        this.price = price;
    }

    public abstract boolean isWeighted();
}

Example – generic class with extends

public class ProduceQuantity<T extends Produce, U> {
    private T item;
    private U unit;

    public ProduceQuantity(T item, U unit) {
        this.item = item;
        this.unit = unit;
    }

    public T getItem() {
        return item;
    }
    public U getQuantity() {
        return unit;
    }

    public boolean getInfo() {
        return item.isWeighted();
    }

}