4

Given i want to filter a List of Key-Value objects.

My (Document)-Object from the example below looks like this

{
    "attributeEntityList" : [
        {key: 'key1', value: 'somevalue1'},
        {key: 'key2', value: 'somevalue2'},
        {key: 'key3', value: 'somevalue3'}
    ]
}
  1. When I pass in a list of the following keys ["key1", "key2", "key3"], I expect my function to return the whole given List of attributes.

  2. When I pass in a list of the following keys ["key1", "key2"], I expect my function to return a list of Attributes with the given key-names.

  3. When I pass in a list of the following keys ["key1", "key2", "faultyKey"], I expect my function to return an Empty list.

My imperative-style solution looks like this and it works okay:

private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
    final List<AttributeEntity> documentAttributeList = value.getAttributeEntityList();
    final List<AttributeEntity> resultList = new ArrayList<>();

    for(String configKey: keys){
        boolean keyInAttribute = false;
        for(AttributeEntity documentAttribute : documentAttributeList){
            if(configKey.equals(documentAttribute.getAttribute_key())){
                keyInAttribute = true;
                resultList.add(documentAttribute);
                break;
            }
        }
        if(!keyInAttribute){
            resultList.clear();
            break;
        }
    }

    return resultList;
}

For education and fun (and maybe better scaling) I'd like to know how to convert this piece of Code into a solution using the new Java 8 streaming-api.


This is what I came up with, converting my pre-Java8-code to Java8.

To my eyes it looks much more concise and it's shorter. But it does not, what I expect it to do :/

I'm realy struggling implementing the third bulletpoint of my requirements. It always returns all (found) Attributes, even when i pass in a not existant key.

private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
    final List<AttributeEntity> documentAttributeList = value.getAttributeList();

    return documentAttributeList.stream()
            .filter(attribute ->
                    keys.contains(attribute.getAttribute_key())
            ).collect(Collectors.toList());
}

I'm thinking of implementing my own custom Collector. Since my Collector should only return the List, when the collected results contain each given key at least once.

Any other Idea on how to achieve that?


This solution passes my tests. But it feel's like i'm putting the cart before the horse.

It's neither concise nor short or elegant any more.

private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
    final List<AttributeEntity> documentAttributeList = value.getAttributeList();

    return documentAttributeList.stream()
            .filter(attribute ->
                            keys.contains(attribute.getAttribute_key())
            )
            .collect(Collectors.collectingAndThen(Collectors.toList(), new Function<List<AttributeEntity>, List<AttributeEntity>>() {
                @Override
                public List<AttributeEntity> apply(List<AttributeEntity> o) {
                    System.out.println("in finisher code");
                    if (keys.stream().allMatch(key -> {
                        return o.stream().filter(attrbiute -> attrbiute.getAttribute_key().equals(key)).findAny().isPresent();
                    })) {
                        return o;
                    } else {
                        return new ArrayList<AttributeEntity>();
                    }
                }
            }));
}
crushervx
  • 587
  • 1
  • 7
  • 18
  • I think your code is okay, so why use any Java8-features therefore? This won't make your code easier – msrd0 Oct 22 '14 at 15:46
  • Ok... after the downvotes. I can only guess what i did wrong. Didn't provide examples of what I've tried. And this looks like a `do my homework-question`. I'll fix my question – crushervx Oct 22 '14 at 15:47
  • It really sounds like your teacher told you to write the program for Java8 to learn Lambdas without thinking that the Java7-standart is better fit for your code – msrd0 Oct 22 '14 at 15:48
  • I only see Multithreadding-errors coming up if you use tools from Java8 like the `forEach` method – msrd0 Oct 22 '14 at 15:49
  • "*For education and fun (and maybe better scaling) I'd like to know how to convert this piece of Code into a solution using the new Java 8 streaming-api*" so try to implement with lambdas. What stops you? I don't see what problem you are having (I am assuming that you are not asking us to rewrite your just so you would see how it could be done *before you even try*, which IMO would be off-topic on Stack Overflow). – Pshemo Oct 22 '14 at 15:50
  • @msrd0 You are right. Just thought that exactly this use-case would be a good candidate to learn more about Java8-features. I'm basicly doing transformations on a List. Thought this was one of the use-cases for the streaming-api :) – crushervx Oct 22 '14 at 15:51
  • You're not all wrong, but since you'll get multithreading-errors with your code, I wouldn't recommend it here. Also see http://stackoverflow.com/questions/16635398/java-8-iterable-foreach-vs-foreach-loop – msrd0 Oct 22 '14 at 15:53
  • @Pshemo, Yeah, my fault. Didn't provide the example of what I've already tried. Updated my question. – crushervx Oct 22 '14 at 16:06

3 Answers3

3

First of all I must say that I'm also new at Java 8 features, so I'm not familiar with everything, and not very used to functional programming. I tried a different approach, dividing it all into some methods.

Here it is:

public class Main {

    private static List<AttributeEntity> documentAttributeList;

    static {
        documentAttributeList = new ArrayList<>();
        documentAttributeList.add(new AttributeEntity("key1", "value1"));
        documentAttributeList.add(new AttributeEntity("key2", "value2"));
        documentAttributeList.add(new AttributeEntity("key3", "value3"));
    }

    public static void main(String[] args) {
        Main main = new Main();
        List<AttributeEntity> attributeEntities = main.getAttributeEntities(Arrays.asList("key1", "key2"));
        for (AttributeEntity attributeEntity : attributeEntities) {
            System.out.println(attributeEntity.getKey());
        }
    }

    private List<AttributeEntity> getAttributeEntities(List<String> keys) {
        if(hasInvalidKey(keys)){
            return new ArrayList<>();
        } else {
            return documentAttributeList.stream().filter(attribute -> keys.contains(attribute.getKey())).collect(toList());
        }
    }

    private boolean hasInvalidKey(List<String> keys) {
        List<String> attributeKeys = getAttributeKeys();
        return keys.stream().anyMatch(key -> !attributeKeys.contains(key));
    }

    private List<String> getAttributeKeys() {
        return documentAttributeList.stream().map(attribute -> attribute.getKey()).collect(toList());
    }

}
Rodrigo Sasaki
  • 7,048
  • 4
  • 34
  • 49
  • thank you for your input. Indeed your Solution looks well structured. It's quite easy to see whats going on (especialy how the businesslogic "flows" in your `getAttributeEntities`) – crushervx Oct 22 '14 at 21:03
  • IMO yours is not "fully functional", what I tried to do with my different approaches. But hey, I didn't especialy ask for that. – crushervx Oct 22 '14 at 21:07
  • You just use the functional language-elements where it may make sense for this Problem :) And i think that's what its all about when using Java with functional language-elements. :) – crushervx Oct 22 '14 at 21:10
1

If a document can never have multiple attributes with the same name, I think you can do it like this (don't have a compiler handy to try):

Map<String, AttributeEntity> filteredMap=value.getAttributeEntityList().stream()
    .filter(at->keys.contains(at.getKey()))
    .collect(toMap(at->at.getKey(), at->at));

return filteredMap.keySet().containsAll(keys) 
    ? new ArrayList<>(filteredMap.values()) 
    : new ArrayList<>();

If multiple attributes per name are allowed, you would have to use groupingBy instead of toMap. You can, of course, rewrite this with collectingAndThen but I think it would be less clear.

Misha
  • 27,433
  • 6
  • 62
  • 78
0

I came up with something.

I don't know if it it the most elegant solution but at least it works and i can reason about it.

private List<AttributeEntity> getAttributeEntities(List<String> keys, Document value) {
    final List<AttributeEntity> documentAttributeList = value.getAttributeList();

    boolean allKeysPresentInAnyAttribute = keys.stream()
            .allMatch(key ->
                    documentAttributeList.stream()
                            .filter(attrbiute ->
                                    attrbiute.getAttribute_key().equals(key)
                            )
                            .findAny()
                            .isPresent()
            );
    if (allKeysPresentInAnyAttribute) {
        return documentAttributeList.stream()
                .filter(attribute ->
                    keys.contains(attribute.getAttribute_key())
                )
                .collect(Collectors.toList());
    }
    return new ArrayList<>();
}

Any hints or comments greatly appreciated.

crushervx
  • 587
  • 1
  • 7
  • 18