0

Is there a lambdaj way of doing this neatly? I know the title sounds complicated but the code fragment below should make it clear:

private List<String[]> getContractLineItemsForDatatables(List<ContractLineItem> contractLineItems) {

    List<String[]> contractLineItemsForDatatables = Lists.newArrayList();

    for (ContractLineItem contractLineItem : contractLineItems) {

        contractLineItemsForDatatables.add( contractLineItem.getDataTablesRow());
    }

    return contractLineItemsForDatatables;
}

There must be a neat way of doing this with Lambdaj to avoid the for loop above but I can't get my head around it. btw contractLineItem.getDatatablesRow() returns a String[].

So what I want to do is:

run getDataTablesRow() on all elements in contractLineItems list, and add them to contactLineItemsForDatatables list.

Any suggestions?

Ashkan Aryan
  • 3,504
  • 4
  • 30
  • 44
  • 1
    If your code works, why "fix it"? – fge Jan 09 '13 at 11:49
  • Until you have proper support for lambda's in Java 8, it is likely to more complicated and longer to use lambdas in this case. Lambda's will use a loop all the same, just not as efficiently. ;) – Peter Lawrey Jan 09 '13 at 11:51
  • @Peter Lawrey thanks it's not about efficiency (not a performance critical app), it's more curiosity to see if Lambdaj version could be more *readable*. – Ashkan Aryan Jan 09 '13 at 11:57
  • If by readable you mean with less symbols, I doubt it. Let me try to add it to my asnwer all the same (it will take me some time ;) – Peter Lawrey Jan 09 '13 at 11:58
  • @AshkanAryan Guava has a lot of "functional" methods which you can use, but in their documentation, they "weep" when people use it where the "traditional way" of doing it is more readable... Really, you should just wait for Java 8 ;) – fge Jan 09 '13 at 11:59
  • @Peter Lowry no not less symbols, that doesn't matter. the for loop and explicit iteration through collection is the issue. – Ashkan Aryan Jan 09 '13 at 12:03
  • @fge thanks yes that may be the case here, hence the question. I'm just wondering which way is more readable hence posing the question to see if anyone can come up with a good lambdaj solution. – Ashkan Aryan Jan 09 '13 at 12:04

2 Answers2

4

Most of the wordiness comes from the choice of variable names rather than the Java code.

private static List<String[]> extractDataTableRows(List<ContractLineItem> items) {
    List<String[]> ret = new ArrayList<>();
    for (ContractLineItem item : items) ret.add(item.getDataTablesRow());
    return ret;
}

If an exception is thrown it would look like

Exception in thread "main" java.lang.RuntimeException
at Main$ContractLineItem.getDataTablesRow(Main.java:87)
at Main.extractDataTableRows(Main.java:50)
at Main.main(Main.java:27)

Using Guava's lambda

private static List<String[]> extractDataTableRows(List<ContractLineItem> items) {
    return Lists.transform(items, new Function<ContractLineItem, String[]>() {
        @Override
        public String[] apply(ContractLineItem item) {
            return item.getDataTablesRow();
        }
    });
}

if an exception is thrown it would look like

Exception in thread "main" java.lang.RuntimeException
at Main$ContractLineItem.getDataTablesRow(Main.java:76)
at Main$1.apply(Main.java:38)
at Main$1.apply(Main.java:35)
at com.google.common.collect.Lists$TransformingRandomAccessList.get(Lists.java:495)
at java.util.AbstractList$Itr.next(AbstractList.java:358)
at java.util.AbstractCollection.toString(AbstractCollection.java:459)
at java.lang.String.valueOf(String.java:2957)
at java.io.PrintStream.println(PrintStream.java:821)
at Main.main(Main.java:31)

Note: the exception is not triggered until the List is used.

This Guava Caveat is clear enough to me.

As of Java 7, functional programming in Java can only be approximated through awkward and verbose use of anonymous classes. This is expected to change in Java 8, but Guava is currently aimed at users of Java 5 and above.

Excessive use of Guava's functional programming idioms can lead to verbose, confusing, unreadable, and inefficient code. These are by far the most easily (and most commonly) abused parts of Guava, and when you go to preposterous lengths to make your code "a one-liner," the Guava team weeps.

Using Java 8.

private static List<String[]> extractDataTableRows(List<ContractLineItem> items) {
    return items.stream()
            .<String[]>map(ContractLineItem::getDataTablesRow)
            .into(new ArrayList<>());
}

Adding a utility method you can write

public static <E, R> List<R> map(Collection<E> elements, Function<? super E, ? extends R> function) {
    return elements.stream().<R>map(function).into(new ArrayList<R>());
}

// hiding the guff, this is more readable IMHO.
private static List<String[]> extractDataTableRows(List<ContractLineItem> items) {
    return map(items, ContractLineItem::getDataTablesRow);
}

if an exception is thrown it might look like this

 Exception in thread "main" java.lang.RuntimeException
at Main$ContractLineItem.getDataTablesRow(Main.java:77)
at Main$$Lambda$1.apply(Unknown Source)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:87)
at java.util.Arrays$ArraySpliterator.forEach(Arrays.java:4551)
at java.util.stream.AbstractPipeline$PipelineHelperImpl.into(AbstractPipeline.java:197)
at java.util.stream.op.ForEachOp.evaluateSequential(ForEachOp.java:86)
at java.util.stream.op.ForEachOp.evaluateSequential(ForEachOp.java:37)
at java.util.stream.AbstractPipeline.pipeline(AbstractPipeline.java:336)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:142)
at java.util.Collection.addAll(Collection.java:505)
at java.util.ArrayList.addAll(ArrayList.java)
at java.util.stream.ReferencePipeline.into(ReferencePipeline.java:189)
at Main.map(Main.java:34)
at Main.extractDataTableRows(Main.java:39)
at Main.main(Main.java:29)

While @Edwin's answer is shortest, it is the hardest to debug and maintain as there is allot of "magic" going on to implement this. This is fine for unit tests, but you wouldn't want it in production code IMHO.

 Exception in thread "main" ch.lambdaj.function.argument.InvocationException: Failed invocation of public java.lang.String[] Main$ContractLineItem.getDataTablesRow() on object Main$ContractLineItem@1d724f31 caused by: null
at ch.lambdaj.function.argument.Invocation.invokeOn(Invocation.java:70)
at ch.lambdaj.function.argument.InvocationSequence.invokeOn(InvocationSequence.java:91)
at ch.lambdaj.function.argument.InvocationSequence.invokeOn(InvocationSequence.java:85)
at ch.lambdaj.function.argument.Argument.evaluate(Argument.java:35)
at ch.lambdaj.function.convert.ArgumentConverter.convert(ArgumentConverter.java:36)
at ch.lambdaj.function.convert.ConverterIterator.next(ConverterIterator.java:37)
at ch.lambdaj.Lambda.convert(Lambda.java:986)
at ch.lambdaj.Lambda.extract(Lambda.java:1035)
at Main.extractDataTableRows(Main.java:49)
at Main.main(Main.java:27)
 Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:474)
at ch.lambdaj.function.argument.Invocation.invokeOn(Invocation.java:68)
... 14 more
 Caused by: java.lang.RuntimeException
at Main$ContractLineItem.getDataTablesRow(Main.java:85)
... 19 more

This syntax takes some getting used but I imagine as they tighten up the syntax and drop some of the boiler plate code, it might be more readable.

Here is a good comparison Java 8 Lambda vs LambdaJ vs Guava vs Iterative approach Originally in Russian so please excuse Google Translate ;)

Community
  • 1
  • 1
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • I prefer longer more descriptive variable names than short and cryptic ones, and I think the consensus is that sohrt names like "ret" should be avioded. I'm not concerned with the character count or the size of the method, I am more concerned with improving readability of this (and similar) method by avoiding the for loop (or more precisely exploring whether it's possible with lambdas) – Ashkan Aryan Jan 09 '13 at 12:00
  • 1
    @AshkanAryan: "I think the consensus is that sohrt names like "ret" should be avioded." <-- I don't know which consensus, but `ret` is short and to the point: it's your return value. I actually dislike longAsADayWithoutWine variable names ;) – fge Jan 09 '13 at 12:02
  • @AshkanAryan The consensis is that using Lambdas in Java 5.0 to 7 is not about readability as it is about using functional design. Guava's own documentation states as much. See above. – Peter Lawrey Jan 09 '13 at 12:14
  • Pity I cannot +2, I wouldn't have imagined you'd go to the length of actually taking the time to write a Guava example :p – fge Jan 09 '13 at 12:17
  • @fge It's been a while since I have used it and I can't knock it without trying. If I had read the docs first I might have just quoted them. ;) – Peter Lawrey Jan 09 '13 at 12:20
  • In short, I believe the Java 8 syntax can be made more readable if you hide some of the guff. – Peter Lawrey Jan 09 '13 at 12:38
  • thanks for all your contributions, especially useful examples provided by @Peter Lawrey. As you can see from the accepted answer, using lambdaj correctly will simplify the code and remove the need for (explicit) iteration, introduction of local variables, etc. – Ashkan Aryan Jan 09 '13 at 13:56
  • @Peter Lawrey regarding your comment on the reasons for using Lambdaj, I have to disagree with what you say there, this is a classic example of where Lambdaj is useful. From Lamdaj website: "The main purpose of lambdaj is to partially eliminate the burden to write (often nested and poorly readable) loops while iterating over collections. In particular it allows to iterate collections in order to:..." http://code.google.com/p/lambdaj/ can't get more relevant t han that! – Ashkan Aryan Jan 09 '13 at 13:59
  • @AshkanAryan readable yes, but can you explain to me what it is actually doing and how. If this were to break in anyway, would the fix be obvious? Could you debug it for example, I suspect not. ;) – Peter Lawrey Jan 09 '13 at 14:04
2

How about

List<String[]> items = extract(contractLineItems, on(ContractLineItem.class).getDataTablesRow());

LambaJ comes with a method named extract that is a mapper. You can also read the reference about converting objects with lambdaj.

Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205