Question

I have two almost identical methods, but I'm trying to avoid code duplication.

Each of them takes a unique object as arguments and finds out the highest value from it.

Here's an example:

public Integer getHighestIndexValue(List<ObjectA> list) {
    int highestValue;
    List<Integer> indexes = new ArrayList<Integer>();
    for (ObjectA a: list) {
        indexes.add(Integer.parseInt(a.getA()));
    }
    highestValue = Collections.max(indexes);

    return highestValue;

} 

Or:

public Integer getHighestIndexValue(List<ObjectB> list) {
    int highestValue;
    List<Integer> indexes = new ArrayList<Integer>();
    for (ObjectB b: list) {
        indexes.add(Integer.parseInt(b.getB()));
    }
    highestValue = Collections.max(indexes);

    return highestValue;

}  

How do I combine these two using generic parameters?

I tried creating a BaseClass that contains these two classes and extended it in the method signature. Still it requires casting.

public <T extends BaseClass> Integer getHighestIndexValue(List<T> objectList) {
    int highestValue;
    List<Integer> indexes = new ArrayList<Integer>();

    for (T objects: objectList) {
        indexes.add(Integer.parseInt(objects.getAorB())); ------ this line needs casting
    }
    highestValue = Collections.max(indexes);

    return highestValue;

}

I have used Generics before but not generics parameters yet.

Is there a way to resolve this?

Was it helpful?

Solution

Can getA and getB be combined into a single interface method?

For example:

interface ProvidesIndex {
  Integer getIndex();
}

Now, you can simply call getIndex, and your method signature will be:

public Integer getHighestIndexValue(List<? extends ProvidesIndex> list) 

As a side note, if you define your interface to extend Comparable<ProvidesIndex> i.e.:

interface ProvidesIndex extends Comparable<ProvidesIndex>

Then you can use Collections.max directly on the initial list:

List<ProvidesIndex> list = new ArrayList<ProvidesIndex>();
list.add(new ObjectA());
list.add(new ObjectB());
Integer max = Collections.max(list).getIndex();

OTHER TIPS

Adding to the answer by Raman :

public Integer getHighestIndexValue(List<? extends CommonInterface> list) {
    int highestValue;
    List<Integer> indexes = new ArrayList<Integer>();

    for (CommonInterface a: list) {
        indexes.add(Integer.parseInt(a. getIndex()));
    }

    highestValue = Collections.max(indexes);

    return highestValue;
}

Guava has an elegant solution to this:

//general purpose function to get A, can be used in transform(), etc
private static final Function<ObjectA, Integer> GET_A = new Function<>() {
     @Override
     public Integer apply(ObjectA a) {
         return a.getA();
     }
}

Integer highestIndex = Collections.max(Collections2.transform(list, GET_A));

Or, if you need the element with the highest index,

ObjectA highest = Ordering.natural().onResultOf(GET_A).max(list);

Then to extend this to ObjectB you just need to implement a GET_B function.

Going back to your helper method (which is basically irrelevant now that you have a one-liner):

public <T> Integer getHighestIndex(List<? extends T> list, Function<? super T, Integer> indexPlucker) {
    return Collections.max(Collections2.transform(list, indexPlucker));
}

You can do this in one Java command. Here is both Java 5 and a much more compact Java 8:

public static class ObjectA {
    public String getA() { return "1"; /* test */ }
}

public static class ObjectB {
    public String getB() { return "1"; /* test */ }
}

// Java 5+ versions

public static void sampleA7(List<ObjectA> list) {
    int maxValue = Integer.parseInt(Collections.max(list, new Comparator<ObjectA>() {
        @Override
        public int compare(ObjectA a, ObjectA b) {
            return Integer.parseInt(a.getA()) - Integer.parseInt(b.getA());
        }
    }).getA());
}

public static void sampleB7(List<ObjectB> list) {
    int maxValue = Integer.parseInt(Collections.max(list, new Comparator<ObjectB>() {
        @Override
        public int compare(ObjectB a, ObjectB b) {
            return Integer.parseInt(a.getB()) - Integer.parseInt(b.getB());
        }
    }).getB());
}

// Java 8+ versions

public static void sampleA8(List<ObjectA> list) {
    Optional<Integer> maxValue = list.stream().map(a -> Integer.parseInt(a.getA())).max((a, b) -> a - b);
}

public static void sampleB8(List<ObjectB> list) {
    Optional<Integer> maxValue = list.stream().map(a -> Integer.parseInt(a.getB())).max((a, b) -> a - b);
}

it's probably not a good idea, if you want to make this code generic, to use an ArrayList of Integers to do the actual comparison. What if one of the objects is a Long, bigger than your max integer? Instead, your objects must be comparable somehow, so you can find the "maximum" (what's the maximum of a set of strings?)

So I'd try an approach like this

import java.util.List;

public abstract class MathMax<E> {
    public abstract boolean isGreaterThan(E x1, E x2);
    public E getHighestIndexValue(List<E> list){
        E highestValue = null;
        for (E a: list) {
            if (isGreaterThan(a,highestValue)){
                highestValue = a;
            }
        }
        return highestValue;
    }
}

and

import java.util.ArrayList;
import java.util.List;

public class MathMaxA extends MathMax<ObjectA> {

    @Override
    public boolean isGreaterThan(ObjectA x1, ObjectA x2) {
        // do your comparison here, careful with null values
        return false;
    }

    public static void main(String[] args) {
        MathMaxA m = new MathMaxA();
        List<ObjectA> list = new ArrayList<ObjectA>();
        System.out.println(m.getHighestIndexValue(list));
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top