10.3 Writing a Max Function
We've seen that Java provides a nice Collections.max
function for us. Collections
also includes other handy functions like min
, sort
, and reverse
.
In this chapter we'll see how we can write such functions ourselves.
Generic Functions (Warumup)
As a warmup suppose we want a function which picks a random item from an array. Some code is shown below:
public class RandomPicker {
public static <T> T randomItem(T[] items) {
Random random = new Random();
int randomIndex = random.nextInt(x.length);
return x[randomIndex];
}
}
And a sample program that uses this function is given below:
public class RandomPickerDemo {
public static void main(String[] args) {
String[] x = {"hi", "little", "cat"};
System.out.println(RandomPicker.pickRandom(x));
}
}
Next, we'll try to make it so that the pickRandom
function can work on any type of array.
Approach One: Generics
We can try to use Generics, as shown in the code below:
public class RandomPicker<T> {
public static T pickRandom(T[] x) {
Random random = new Random();
int randomIndex = random.nextInt(x.length);
return x[randomIndex];
}
}
However, this code doesn't really make sense as written. The pickRandom
function is static, but we have to instantiate a RandomPicker
object to specify the generic type. That is, we'd need to do something like:
String[] x = {"hi", "little", "cat"};
RandomPicker<String> stringPicker = new RandomPicker<>();
System.out.println(stringPicker.pickRandom(x));
but we can't call static methods using an instance.
A fix for this is to make the pickRandom
function non-static, as shown below:
public class RandomPicker<T> {
public T pickRandom(T[] x) {
Random random = new Random();
int randomIndex = random.nextInt(x.length);
return x[randomIndex];
}
}
If we do this our code will work fine, but it's still very awkward to have to instantate a picker object.
Approach Two: Generic Static Methods
The correct and better approach is to make our static method generic. This is shown below:
public class RandomPicker {
public static <T> T pickRandom(T[] x) {
Random random = new Random();
int randomIndex = random.nextInt(x.length);
return x[randomIndex];
}
}
This syntax is confusing (and it'll get worse later!). The way you can read public static <T> T pickRandom(T[] x)
is: "I am declaring a public static function that works on objects of type , it returns a T, it is called pickRandom, and it takes an array of Ts as its input.
After noting to Java that our static method is generic, we can call it without instantiating a RandomPicker
object as shown below:
public class RandomPickerDemo {
public static void main(String[] args) {
String[] x = {"hi", "little", "cat"};
System.out.println(RandomPicker.pickRandom(x));
}
}
Note that unlike generic classes, we don't need to specify the generic type when calling a generic static method (i.e. there's no <String>
after pickRandom
). The type is automatically inferred.
One minor annoyance is that our pickRnadom function will not work with arrays of primitive types, i.e. we can use int[]
, double[]
, float[]
.
public class RandomPickerDemo {
public static void main(String[] args) {
int[] x = {1, 2, 3, 4, 5, 6, 7, 8};
System.out.println(RandomPicker.pickRandom(x));
}
}
There's no way around this, and in fact real Java libraries often have multiple functions, one for each primitive type. For example, there exists Arrays.sort(int[])
, Arrays.sort(double[])
, Arrays.sort(float[])
, etc. One day maybe Project Valhalla will fix this.
Writing our Max Function, Type Bounds
Using the idea of a generic static function, we can write our max function as follows:
public class Maximizer {
public static <T> T max(T[] items) {
T maxItem = items[0];
for (int i = 0; i < items.length; i += 1) {
int cmp = items[i].compareTo(maxItem);
if (cmp > 0) {
maxItem = items[i];
}
}
return maxItem;
}
}
There's one last problem, Java doesn't know that T
has a compareTo
method. We can fix this by adding a type bound to our generic type, as shown below:
public class Maximizer {
public static <T extends Comparable<T>> T max(T[] items) {
T maxItem = items[0];
for (int i = 0; i < items.length; i += 1) {
int cmp = items[i].compareTo(maxItem);
if (cmp > 0) {
maxItem = items[i];
}
}
return maxItem;
}
}
This tells Java that T
, whatever it is, has to implement Comparable<T>
. If you try to pass an arary of objects into max that does not implement Comparable<T>
, you'll get a compile-time error.
The way to read public static <T extends Comparable<T>> T max(T[] items)
is: "I am declaring a public static function that works on objects of type , it returns a T, it is called max, and it takes an array of Ts as its input. Additionally, T has to implement Comparable."
Bonus: An Even Better Type Bound
In real industrial strength Java code, the correct way to specify our method is:
public class Maximizer {
public static <T extends Comparable<? super T>> T max(T[] items) {
...
}
}
Last updated