user3366706
user3366706

Reputation: 1599

Is there a way to make the following code more generic?

I'm using gson to serialize/deserialize and java 8 stream. Following is the code snippet,

private long sumofTime() {
    Line[] lines = gson.fromJson(jsonString, Line[].class);
    return Arrays.stream(lines).filter(x -> x.hasTime())
                .mapToInt(x -> x.getTime).sum();
}

Line class looks like,

public class Line {
    String name;
    String stamp;
    Integer time;
    Integer xx;
    Integer yy;
    Integer zz;
    ...
    ...

    boolean hasTotalTime() {
        return totalTime != null;
    }
    ...
    getters...setters...}

Stream is used to check if specific variable(eg.time in the above example) is not null for each array element (i.e., Line), and then get the sum of all times.

Question: There are like 30 variables in the Line objects whose sum are needed, so how to make the solution more generic, instead of writing a sum method for each variable. Note that there is more than 1000 Line objects to process, thats why I thought Stream would be better.

Upvotes: 2

Views: 203

Answers (3)

Alex - GlassEditor.com
Alex - GlassEditor.com

Reputation: 15537

You could pass a function into the sum method to get the value from each line that you want to sum:

public int sumLines(Function<Line, Integer> extractor){
    Line[] lines = ...
    return Arrays.stream(lines).map(extractor)
        .filter(Objects::nonNull).mapToInt(i -> i).sum();
}
....
int time = sumLines(Line::getTime);

This assumes that the function will return null if the attribute is not present, but you could also pass in a Predicate to filter with if that is not the case.

Upvotes: 4

gobernador
gobernador

Reputation: 5739

What you're trying to do is partly a dynamic property extraction and partly a partially applied function, neither of which is particularly straightforward in Java. You'd be better off defining your Line class in a different way. I would use a Map to store the properties like so:

public class Line {
    Map<String, Integer> props;
    public Line() {
        // Initialize props
    }

    public boolean has(String prop) {
        return props.containsKey(prop);
    }

    public Integer get(String prop) {
        return props.get(prop);
    }

    public void set(String prop, Object value) {
        return props.put(prop, value);
    }
}

Now, when you're looking for the sum of a bunch of things, you can call

public int sumOf(Line[] lines, String prop) {
    return Arrays.stream(lines)
               .filter(l -> l.has(prop))
               .reduce(0, Integer::sum);
}

Upvotes: 2

yshavit
yshavit

Reputation: 43391

Rather than having a field per attribute, you could define an enum for the attributes, and then have a mapping from enum to int:

public enum LineAttribute {
    XX,
    YY,
    ZZ,
    ...
}

private final EnumMap<LineAttribute, Integer> attributes;

public Line() {
    attributes = new EnumMap<>(LineAttribute.class);
    // init all attributes to 0
    for (LineAttribute attr : LineAttribute.values()) {
        attributes.put(attr, 0);
    }
}

Then you can loop over each attribute, getting its values for the lines and summing those values.

private long sumOf(LineAttribute attr) {
    Line[] lines = gson.fromJson(jsonString, Line[].class);
    return Arrays.stream(lines)
            .filter(x -> x.has(attr))
            .mapToInt(x -> x.get(attr))
            .sum();
}

Upvotes: 1

Related Questions