ZOFTINO.COM android and web dev tutorials

Java Generics

Generics allows you to create classes or methods in a generic way so that the same code can be used with related types. When a generic method or class is used, type that the method or class handles is specified and compiler will report error if there is a type mismatch in those elements. Generics helps in creating code that works with different types, reducing run time errors and developing clean and readable code.

All the interfaces, classes and methods in Collections API are declared in a generic way.

Table of Contents

Generics

Let’s use collections without generics. You can add any type of objects to a list. While reading an object from the list, you need to cast the object to the type of variable the object is assigned to, otherwise you will get compilation error.

		List lst = new ArrayList();
		lst.add(new Integer(2));
		
		int val = (Integer)lst.get(0);

This way of using the List has two issues. One is that you need to do the casting everywhere an object is obtained from the list and assigned to a variable. The second problem is that you have to use right type casting otherwise run time error will occur, for example, if you cast an object from the above list to Boolean, it will throw class cast exception at runtime.

boolean val = (Boolean)lst.get(0);

This is where the generics come into picture. To prevent casting and runtime exception, you can use generics. The above example can rewritten using generics by specifying the type the list object stores.

		List<Integer> lst = new ArrayList<Integer>();
		lst.add(new Integer(2));
		
		int val = lst.get(0);

You can pass the type as parameter when the list variable is declared. This is possible because List is a generic interface which takes parameter type. When you declare a List variable, you specify type to tell the compiler that it can contain objects of the type. Compiler will report errors, if different type of object than the specified parameter type is added to it.

Another advantage of using generics is that the type or method declared in a generic way can be reused with different types. Like List interface, it implementations and methods can be reused with different types.

Generic Class

Let’s first see an example without using generics, learn the need of generics and then create generic type.

OfferHolder class contains a member variable of type Object. Type object is used because OfferHolder can be passed any type.

public class OfferHolder {
	private Object offer;

	public Object getOffer() {
		return offer;
	}

	public void setOffer(Object offer) {
		this.offer = offer;
	}
}

Let’s set and get an instance of a type.

		Cashback cb =  new Cashback();
		cb.setMaxCashback("200");
		cb.setName("2% cashbck on electronics");
		
		OfferHolder oh = new OfferHolder();
		oh.setOffer(cb);
		Cashback cbTemp = (Cashback) oh.getOffer();

When you get object, you need to cast it. If you do wrong casting there is no way compiler can repot it and will throw runtime exception.

To fix these issues, let’s define generic type by declaring member field type of OfferHolder class as parameter of the class definition.

public class OfferHolder<T> {
	private T offer;

	public T getOffer() {
		return offer;
	}

	public void setOffer(T offer) {
		this.offer = offer;
	}
}

An instance of any type can be assigned to the member field of OfferHolder class and when you get member filed value, casting is not required.

		Coupon c = new Coupon();
		c.setName("20% off on mobiles");
		c.setExpiry(new Date());
		c.setCode("er34");
		
		OfferHolder<Coupon> oh = new OfferHolder<Coupon>();
		oh.setOffer(c);
		Coupon cTemp = oh.getOffer();

Once a generic class is instantiated passing a type, you can use an instance of the type or its sub classes as arguments. For example, OfferHolder is instantiated passing Coupon as type. You can pass instance of Coupon or any sub class (CouponSub) of Coupon to method of OfferHolder which accepts the generic type as argument.

		OfferHolder<Coupon> ohCoupon = new OfferHolder<Coupon>();
		CouponSub cs = new CouponSub();
		ohCoupon.setOffer(cs);

Generic Interface

Similar to generic class, an interface can be declared as generic by declaring type as parameter. The generic type is used as type for parameters of methods. For example, Pricer interface is declared as generic interface and type T is the type of argument that the method accepts.

public interface Pricer<T> {
	double calculateFinalPrice(T offer, double productPrice);
}

When a class implements a generic interface, it needs to specify the type the class will use. For example, CashbackPricer implements generic Pricer interface specifying Cashback type.

public class CashbackPricer implements Pricer<Cashback>{
	@Override
	public double calculateFinalPrice(Cashback offer, double productPrice) {
		return productPrice - (productPrice*offer.getMaxCashback()/100);
	}
}

Generic Method

Similar to generic class, you can declare a method as generic method by defining method parameter type as parameter so that the method can be called passing values of any type. For example, OfferUtil class has generic method getOfferInfo(). Parameter type of the method is declared as a parameter T so that any value of any type can be passed as arguments to the method.

public class OfferUtil {

	public static <T> String getOfferInfo( T offer){
		return offer.toString();
	}
}

You can pass any type of object as parameter to the method.

		Coupon c = new Coupon();
		c.setName("upto 50% off on fashion");
		c.setExpiry(new Date());
		c.setCode("er43");
		
		String offerDetails = OfferUtil.getOfferInfo(c);
		System.out.println(offerDetails);
		
		Cashback cb =  new Cashback();
		cb.setMaxCashback("500");
		cb.setName("5% cashbck on fashion");
		
		offerDetails = OfferUtil.getOfferInfo(cb);
		System.out.println(offerDetails);

Bounded Type Parameters

So far we have seen generic types which allow any type to be passed as argument. Bounded type parameter allows you to restrict type parameter argument to sub classes of a class specified in the generic type. You can specify bounded type using extends keyword.

For example, OfferHolder can be used with any type which extends OfferProvider. If you try to use it wither other classes, you will get compiler error, Bound mismatch: The type Coupon is not a valid substitute for the bounded parameter of the type OfferHolder.

public class OfferHolder<T extends OfferProvider> {
	private T offer;

	public T getOffer() {
		return offer;
	}

	public void setOffer(T offer) {
		this.offer = offer;
	}
}
public class CouponProvider extends OfferProvider{
	@Override
	public String getOffer() {
		return super.getOffer()+" "+"flat 50% off";
	}
}
		OfferHolder<CouponProvider> oh = new OfferHolder<CouponProvider>();
		CouponProvider cp = new CouponProvider();
		oh.setOffer(cp);

Similarly, you can specify bounded type parameter for methods so that the method parameter type can be restricted to sub classes of the specified type. For example, below method getOfferInfo can accept OfferProvider or any of its subclasses as its parameter type.

public class OfferUtil {

	public static <T extends OfferProvider> String getOfferInfo( T offer){
		return offer.toString();
	}
}

Type Inference

When a generic method is invoked, you don’t need to specify type arguments, compiler infers the type and reports if the method invocation is not valid. For example, getDiscountPercent() is a generic method in OfferUtil class.

public class OfferUtil {	
	public static <T> int getDiscountPercent( T offer){
		return offer.getDiscount();
	}	
}

Here is how generic method is invoked with parameter type in angle brackets.

	OfferUtil.<DealProvider>getDiscountPercent(cp);

Without specifying the type, you can invoke generic method because compiler can infer the type.

	OfferUtil.getDiscountPercent(cp);

Generics Wildcards

In generics, ? is a wildcard character used to specify unknown type.

For example, OfferUtil class has getTopOffer method which accepts list of objects of a type that is either Offer or its sub class type. Here ? specififies that List can contain objects of a type which is a subclass of Offer or Offer itself.

public class OfferUtil {
	
	public Offer getTopOffer(List<? extends Offer> offerList) {
		Offer topOffer = null;
		 for (Offer offer :  offerList) {			 
			 if (topOffer == null ) {
				 topOffer = offer; 
			 }else if(topOffer.getDiscPercent() < offer.getDiscPercent()) {
				 topOffer = offer;
			 }
		 }
		 return topOffer;
	}
	
}

Here is Offer class.

public class Offer {
	String storeName;
	int discPercent;
	public String getStoreName() {
		return storeName;
	}
	public void setStoreName(String storeName) {
		this.storeName = storeName;
	}
	public int getDiscPercent() {
		return discPercent;
	}
	public void setDiscPercent(int discPercent) {
		this.discPercent = discPercent;
	}
}

Here is a subclass of Offer.

public class Coupon extends Offer{
	private String code;
	public String getCode() {
		return code;
	}
	public void setCode(String code) {
		this.code = code;
	}
}

Here is how the list of coupon objects is passed to the method

		List<Coupon> offLst =  new ArrayList<>();
		
		Coupon c = new Coupon();
		c.setStoreName("amazon");
		c.setCode("am50");
		c.setDiscPercent(50);
		
		offLst.add(c);
		
		Coupon ctwo = new Coupon();
		ctwo.setStoreName("bestbuy");
		ctwo.setCode("bb20");
		ctwo.setDiscPercent(20);
		
		offLst.add(ctwo);
		
		OfferUtil ou  = new OfferUtil();
		Offer off = ou.getTopOffer(offLst);
		System.out.println("top offer "+ off.getStoreName());

And also, using ?, you can declare a method which can accept list of any type of object.

	public static String allItems(List<?> items){
		StringBuilder sb = new StringBuilder();
		for(Object obj : items) {
			sb.append(obj.toString());
			sb.append("\n");
		}
		return sb.toString();
	}

Similarly, you can create a method which accepts list of objects of a class and its super class. Below method can accept list of objects of Coupon type or its super class type.

	public String getAllOfferItems(List<? super Coupon> offerList) {
		StringBuilder sb = new StringBuilder();
		for(Object obj : offerList) {
			sb.append(obj.toString());
			sb.append("\n");
		}
		return sb.toString();
	}