You can restrict the valid types used in a generic class by bounding that type in the class definition. Given the following simple type hierarchy:
public abstract class Animal { public abstract String getSound(); } public class Cat extends Animal { public String getSound() { return "Meow"; } } public class Dog extends Animal { public String getSound() { return "Woof"; } }
Without bounded generics, we cannot make a container class that is both generic and knows that each element is an animal:
public class AnimalContainer<T> { private Collection<T> col; public AnimalContainer() { col = new ArrayList<T>(); } public void add(T t) { col.add(t); } public void printAllSounds() { for (T t : col) { // Illegal, type T doesn't have makeSound() // it is used as an java.lang.Object here System.out.println(t.makeSound()); } } }
With generic bound in class definition, this is now possible.
public class BoundedAnimalContainer<T extends Animal> { // Note bound here. private Collection<T> col; public BoundedAnimalContainer() { col = new ArrayList<T>(); } public void add(T t) { col.add(t); } public void printAllSounds() { for (T t : col) { // Now works because T is extending Animal System.out.println(t.makeSound()); } } }
This also restricts the valid instantiations of the generic type:
// Legal AnimalContainer<Cat> a = new AnimalContainer<Cat>(); // Legal AnimalContainer<String> a = new AnimalContainer<String>(); // Legal because Cat extends Animal BoundedAnimalContainer<Cat> b = new BoundedAnimalContainer<Cat>(); // Illegal because String doesn't extends Animal BoundedAnimalContainer<String> b = new BoundedAnimalContainer<String>();
How do you go about using an instance of a (possibly further) inherited generic type within a method declaration in the generic type itself being declared? This is one of the problems you will face when you dig a bit deeper into generics, but still a fairly common one.
Assume we have a DataSeries<T> type (interface here), which defines a generic data series containing values of type T. It is cumbersome to work with this type directly when we want to perform a lot of operations with e.g. double values, so we define DoubleSeries extends DataSeries<Double>. Now assume, the original DataSeries<T> type has a method add(values) which adds another series of the same length and returns a new one. How do we enforce the type of values and the type of the return to be DoubleSeries rather than DataSeries<Double> in our derived class?
The problem can be solved by adding a generic type parameter referring back to and extending the type being declared (applied to an interface here, but the same stands for classes):
public interface DataSeries<T, DS extends DataSeries<T, DS>> { DS add(DS values); List<T> data(); }
Here T represents the data type the series holds, e.g. Double and DS the series itself. An inherited type (or types) can now be easily implemented by substituting the above mentioned parameter by a corresponding derived type, thus, yielding a concrete Double-based definition of the form:
public interface DoubleSeries extends DataSeries<Double, DoubleSeries> { static DoubleSeries instance(Collection<Double> data) { return new DoubleSeriesImpl(data); } }
At this moment even an IDE will implement the above interface with correct types in place, which, after a bit of content filling may look like this:
class DoubleSeriesImpl implements DoubleSeries { private final List<Double> data; DoubleSeriesImpl(Collection<Double> data) { this.data = new ArrayList<>(data); } @Override public DoubleSeries add(DoubleSeries values) { List<Double> incoming = values != null ? values.data() : null; if (incoming == null || incoming.size() != data.size()) { throw new IllegalArgumentException("bad series"); } List<Double> newdata = new ArrayList<>(data.size()); for (int i = 0; i < data.size(); i++) { newdata.add(this.data.get(i) + incoming.get(i)); // beware autoboxing } return DoubleSeries.instance(newdata); } @Override public List<Double> data() { return Collections.unmodifiableList(data); } }
As you can see the add method is declared as DoubleSeries add(DoubleSeries values) and the compiler is happy.
The pattern can be further nested if required.
Generic parameters can also be bound to more than one type using the T extends Type1 & Type2 & ... syntax.
Let's say you want to create a class whose Generic type should implement both Flushable and Closeable, you can write
class ExampleClass<T extends Flushable & Closeable> { }
Now, the ExampleClass only accepts as generic parameters, types which implement both Flushable and Closeable.
ExampleClass<BufferedWriter> arg1; // Works because BufferedWriter implements both Flushable and Closeable ExampleClass<Console> arg4; // Does NOT work because Console only implements Flushable ExampleClass<ZipFile> arg5; // Does NOT work because ZipFile only implements Closeable ExampleClass<Flushable> arg2; // Does NOT work because Closeable bound is not satisfied. ExampleClass<Closeable> arg3; // Does NOT work because Flushable bound is not satisfied.
The class methods can choose to infer generic type arguments as either Closeable or Flushable.
class ExampleClass<T extends Flushable & Closeable> { /* Assign it to a valid type as you want. */ public void test (T param) { Flushable arg1 = param; // Works Closeable arg2 = param; // Works too. } /* You can even invoke the methods of any valid type directly. */ public void test2 (T param) { param.flush(); // Method of Flushable called on T and works fine. param.close(); // Method of Closeable called on T and works fine too. } }
Note:
You cannot bind the generic parameter to either of the type using OR (|) clause. Only the AND (&) clause is supported. Generic type can extends only one class and many interfaces. Class must be placed at the beginning of the list.
Learn All in Tamil © Designed & Developed By Tutor Joes | Privacy Policy | Terms & Conditions