week12
week12
GENERICS cont’d
Generic methods
The previous week, we discussed that we can define generic classes and interfaces. Generics
types can be also utilized to define generic methods. Generic (instance or static) methods can
be defined even in a non-generic class. This allows you to declare type parameters specific to
the method, even if the class itself does not use generics.
access_modifier <T> type methodName(ArgsIncludingTheGenericType)
A good example for a generic static method could be printing some arrays. Printing out the
contents of the arrays is a frequent practice. Instead of accepting an argument relying on a
specific type, we can have a generic type array in the argument list. Below is an example.
public class MyGenericMethodDemo{
To invoke a generic method, you should use the type as a prefix, this acts as defining the type
you are going to use in your method. If you omit it the compiler can discover it for you. As you
will see, we omit the type in the second call, Java replaces it with the one commented.
Integer[] integers = {10, 20, 40, 100};
String[] strings = {"Ali", "Mehmet", "Can"};
Bounded types: You can limit the types to either subclasses. The extends keyword defines the
upper bound
1/17
Some examples
1. <T extends GeometricShape> : Restricts the types to any class that is
GeometricShapes.
2. <T extends Iterable> : remember Iterable was an interface and type definition restricts
the types to only classes that implement Iterable.
3. <T extends GeometricShape & Iterable<T>>: type type must be a subtype of the
GeometricShape and implement Iterable.
You can use super keyword to define a lower bound, for example the below definition allows
only supertypes (this is not possible with bounded types)
<? super Rectangle>: this only allows supertypes of Rectangle. In our example, these are
GeometricShape and Object.
More on wildcards?*
This example is adopted from here,
https://angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html, which explains the
generics in detail.
Consider the following method code segment in our MyGenericMethodDemo class (I skipped
the code for the class to save some space): It copies the contents of one array list to another.
// assume we have method to copy a source arraylist to a destination arraylist
// we use T for a generic type because we do not know what types will be inside
2/17
// each array list
public static <T> void copyTo (ArrayList<T> dest, ArrayList<T> src){
// make sure we clear the destination arraylist
if(dest!=null)
dest.clear();
for (int i = 0; i < src.size(); i++) {
dest.add(src.get(i));
}
}
The following call to the method generates a compile-time error because types do not match.
Review the code snippet to understand that we require the same type.
ArrayList <Integer> src = new ArrayList<>();
src.add(10);
src.add(20);
ArrayList <Object> dest = new ArrayList<>();
MyGenericMethodDemo.<Integer>copyTo(dest, src);
→ an error here because types do not match. dest is Object
We use a generic type T because we do not know what types will be inside the ArrayList. This
approach requires that both the source and destination ArrayList be of the same type, but what
if we want the source and destination lists to hold different types? At this point, we can remove T
that restricts us to supply the same type of arguments and replace it with a wildcard, ?.
See the following code. Be aware that we have removed <T> type declaration after static, too,
because we do not need to define a generic type this type, we want to work with any type.
When we replace T with ?, we tell the method to accept an ArrayList of any type.
public static void copyTo (ArrayList<?> dest, ArrayList<?> src){
if(dest!=null)
dest.clear();
for (int i = 0; i < src.size(); i++) {
dest.add(src.get(i)); → compile time error again
}
}
3/17
We should define the unknown type in terms of the type we define in this method. For example,
we require the destination to be any type that is the super class of T and source to be any class
that extends T. When T is Integer and Integer is supplied as a source, for example, any
destination which is an instance of any superclass of Integer is valid. Why? Because T is Integer
because it can be safely cast to any superclass (one of them Number for example)
when used with the dest.add() method.
public static <T> void copyTo (ArrayList<? super T> dest, ArrayList<? extends T> src){
// make sure we clear the destination arraylist
if(dest!=null)
dest.clear();
for (int i = 0; i < src.size(); i++) {
dest.add(src.get(i));
}
}
4/17
You can now tell why the last commented code segment produces a compile-time error. It is
because Double does not satisfy <? super T> constraint where T is actually Integer in this
example.
The sort method sorts the specified list into ascending order, according to the natural ordering of
its elements. Let’s review these method signatures in terms of what we know about generics.
Analysis of public static <T extends Comparable<? super T>> void sort(List<T> list)
The method parameter “List<T> list” wants us to supply a List. List is an interface
(https://docs.oracle.com/javase/8/docs/api/java/util/List.html). ArrayList
(https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html) is a subclass of the generic
AbstractList class (https://docs.oracle.com/javase/8/docs/api/java/util/AbstractList.html), which
implements the List interface. So, we can pass an ArrayList into the sort method. The <T> in the
List<T> denotes a generic list. As you know, we can define our own type in our ArrayList.
Assume I want to add instances of GeometricShapes (this could be a Rectangle, Triangle,
Circle, etc.).
// do not forget → import java.util.Collections;
ArrayList<GeometricShape> elements = new ArrayList<>();
elements.add(new Triangle("blue", false, 3, 4, 5));
elements.add(new Rectangle("blue", false, 3, 5));
elements.add(new Square("blue", false, 5));
elements.add(new Circle("blue", false, 2));
Collections.sort(elements); → error
for (GeometricShape geometricShape : elements) {
System.out.println(geometricShape);
}
Why? To understand this, we have to have a full understanding of the method signature. The
sort method wants the T to be a class that implements Comparable interface. T is defined as
GeometricShape (keep this in mind). How do I know this? I understand it through bounded type
definition <T extends Comparable<? super T>>.
5/17
The Comparable interface
(https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html) has one abstract method
compareTo as you can see in the documentation. We need to provide an implementation to it.
int compareTo(T o)
According to the documentation, the method should return a negative integer, zero, or a positive
integer as this object is less than, equal to, or greater than the specified object.
Should ArrayList or GeometricShape class hierarchy implement it? Of course, the elements we
are sorting are instances of GeometricShape, we need to implement it at the GeometricShape
level (we can also say here that T is a GeometricShape to explain this). Collections’s sort
method will use this compareTo method to compare if the current object is less than (or equal to
or greater than) the object passed. We do not have to know the implementation details but we
know that we should return a negative integer if the current object is less than, 0 if the current
object is equal to, and a positive integer if the current object is greater than. Let’s do it.
@Override
public int compareTo(GeometricShape o) {
double diff = this.calculateArea() - o.calculateArea();
if (diff > 0)
return 1;
else if (diff < 0)
return -1;
return 0;
}
}
6/17
the current object is greater than the passed object in my implementation), a negative value
when it is less, and 0 when they are equal.
Analysis of public static <T> void sort(List<T> list, Comparator<? super T> c)
The method parameter “List<T> list” wants us to supply a List. What I have just discussed above
is true for here. So, I am skipping to the type of the second parameter, Comparator<? super
T>. Go to this link, https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html, and see
the abstract methods to implement this interface. By implementing this interface we can define
how our ordering should be in the sort method of the Collections. The interface has two
methods to be implemented, which are int compare(T o1, T o2) and boolean equals(Object
obj). The first abstract method wants us to compare two arguments for order. The return logic is
the same with our compareTo implementation we did with the Comparable interface. The
second method is the equals method. Although it is an abstract method, we do not need to
provide an implementation to it because the Object class has already done so (this does not
mean you should not, you can provide an implementation). So, let’s define two Comparator
implementations. Since the T will be GeometricShape, we must specify a type that fits the ?
super T. So, I am going to implement Comparator<GeometricShape>.
// GeometricShapeAreaComparator.java
import java.util.Comparator;
@Override
public int compare(GeometricShape o1, GeometricShape o2) {
return o1.compareTo(o2); // we have already have an implementation of comparing
}
}
//GeometricShapePerimeterComparator.java
import java.util.Comparator;
7/17
@Override
public int compare(GeometricShape o1, GeometricShape o2) {
return (int)(o1.calculatePerimeter() - o2.calculatePerimeter());
}
}
You should supply your Comparator instances to the sort method.
Nested classes are divided into two types: static and non-static. Non-static nested classes, also
known as inner classes (many people prefer this term), can directly access and use all
variables and methods of their outer class, similar to other non-static members of the outer class
like methods. Static nested classes do not have access to the instance variables and methods
of the outer class. It acts like a top-level class but is scoped within the outer class.
8/17
Why do we need nested classes
(from: https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html) ?
● It is a way of logically grouping classes that are only used in one place: If a class is
useful to only one other class, then it is logical to embed it in that class and keep the two
together. Nesting such "helper classes" makes their package more streamlined.
● It increases encapsulation: Consider two top-level classes, A and B, where B needs
access to members of A that would otherwise be declared private. By hiding class B
within class A, A’s members can be declared private and B can access them. In addition,
B itself can be hidden from the outside world.
● It can lead to more readable and maintainable code: Nesting small classes within
top-level classes places the code closer to where it is used.
// Constructor
public CustomLinkedList() {
this.head = null;
this.size = 0;
}
9/17
setHead(newNode);
} else {
Node current = getHead();
while (current.getNext() != null) {
current = current.getNext();
}
current.setNext(newNode);
}
size++;
}
if (getHead().getData().equals(data)) {
setHead(getHead().getNext());
size--;
return true;
}
if (current.getNext() == null) {
return false;
}
current.setNext(current.getNext().getNext());
size--;
return true;
}
10/17
public void printList() {
Node current = getHead();
while (current != null) {
System.out.print(current.getData() + " -> ");
current = current.getNext();
}
System.out.println("null");
}
@Override
public Iterator<T> iterator() {
return new CustomLinkeListIterator(this.getHead());
}
// Constructor
public Node(T data) {
this.data = data;
this.next = null;
}
11/17
*/
// Getter for next
public Node getNext() {
return next;
}
Here, check our earlier implementation where the Node was a separate (top-level) class. There,
Node was generic. Here, the outer class CustomLinkedList specifies T; therefore, we do not
specify it with the Node class.
// Constructor
public CustomLinkedList() {
this.head = null;
this.size = 0;
}
12/17
if (isEmpty()) {
setHead(newNode);
} else {
Node current = getHead();
while (current.getNext() != null) {
current = current.getNext();
}
current.setNext(newNode);
}
size++;
}
if (getHead().getData().equals(data)) {
setHead(getHead().getNext());
size--;
return true;
}
if (current.getNext() == null) {
return false;
}
current.setNext(current.getNext().getNext());
size--;
return true;
}
13/17
// Print the elements of the list
public void printList() {
Node current = getHead();
while (current != null) {
System.out.print(current.getData() + " -> ");
current = current.getNext();
}
System.out.println("null");
}
@Override
public Iterator<T> iterator() {
return new CustomLinkeListIterator(this.getHead());
}
// Constructor
public Node(T data) {
this.data = data;
this.next = null;
}
14/17
}
*/
// Getter for next
public Node getNext() {
return next;
}
// constructor
// assign head
public CustomLinkeListIterator(Node head) {
this.currentNode = head;
}
@Override
public boolean hasNext() {
return currentNode != null;
}
@Override
public T next() {
// if there is not next.
// so calling next before hasNext will cause an exception
// if there is no next
if(!hasNext())
throw new java.util.NoSuchElementException();
T temp = currentNode.getData(); // get the current
currentNode = currentNode.getNext(); // make the current next anymore, move to next
return temp; // return the current
}
}
}
Again, compare our implementation with the one we did in week 11. You will see that we
removed the generic definition from CustomLinkeListIterator because it has been already
specified in the outer class.
15/17
First, be aware that only nested classes can be static. You cannot define a top-level (separate)
class static in Java. Your class definition might not require an instance creation, this does not
mean that your top-level class is static. For example, when working with Math class, we do not
need its instance not because it is static, but because its members are static1. Notice the static
modifier applies to nested classes, methods, or fields, but not to top-level classes.
Suppose you need to have a set of utility methods to validate different user inputs, such as
checking if a string is an email or a phone number. A static nested class can group related
functionality together while keeping the implementation encapsulated.
public class Utility {
// Static nested class for validation methods
public static class Validator {
// Validates if a string is a valid email
public static boolean isValidEmail(String email) {
if (email == null || email.isEmpty())
return false;
// do not mind the implementation. it is a regex.
return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
}
As you can see, we have provided a nested Validator class inside Utility class to provide some
checks regarding some Strings (In this example, Utility is just a class to illustrate the idea. It
does have no purpose other than having a static class member). You can have instances of the
Utility class; however, the class has only static member(s), we do not need it because static
members can be access via the class, it does not require an instance. To invoke the methods
(isValidEmail, isValidPhoneNumber) of the static nested class, you should access the static
nested class like you access any static member, Utility.Validator.
Utility.Validator.isValidEmail("[email protected]"); → invoke the method through Validator
Utility.Validator.isValidPhoneNumber("1111111"); → invoke the method through Validator
1
The Math class (https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html) is indeed final and its
constructor is private. Being final means that you cannot further extend it for customization or
modification. As you know, final avoids misuse or unintended behavior through subclassing.Having a
private constructor means no code outside the class can create an instance of it. All its methods are
static, so creating an object of the class serves no purpose.You should know that the Math class is NOT
static and such a top-level class cannot be static. The reason we cannot create an instance is because its
constructor is private.
16/17
It may sound awkward to you but the static nested classes can be instantiated. Why? The
answer is obvious, they are not abstract classes. Can you tell how you can instantiate them?
Utility.Validator uv = new Utility.Validator();
Do you really need to instantiate a static nested class? If you really need the instance of the
static nested class, you can do so. For example, you can change our Node class to a static
nested one because it does not need any instance members of the enclosing
(CustomLinkedList) class (it is fine to define it as a static nested class). While traversing in a for
loop, you would have an instance of the static Node class for clarity in your code.You can try it
yourself this time (do not forget to use proper access modifier, in our implementation it is
private).
You can define such a class, which is temporary and its functionality is not needed elsewhere.
17/17