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:
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
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(); }}