-1

I have 2 List<xxx> and need to intersection them by xxx.getId(), so I use Stream and Lambda. These steps are:

List<xxx> list1 = function1();
List<xxx> list2 = function2();
List<String> list1InString = list1.stream().map(input -> input.getId()).collect(toList());
list2 = list2.stream().filter(input -> list1InString.contains(input.getId())).collect(toList());

I want to do it by using double colon like:

list2 = list2.stream().filter(list1InString::contains).collect(toList());

But it's not true because contains reference to xxx object not String getId() method. I know that I waste time to convert list1 to list1InString, but I don't know any better method. Any help is very precious, thanks so much.

Edit: Thanks for many answer, I just wanna make code more simple by using double colon (which is new for me), but in this case may not use. Special thanks to Naman and Holger for clarifying my problem very much.

mmo2112
  • 15
  • 8
  • 1
    The use of method reference would only be possible with `list2` being a `String` or `map` before the `fiiler`, which doesn't seem to be the case. – Naman Dec 11 '19 at 07:36
  • @Naman, Thanks for your reply. So my step are optional answer for my case - can't use double colon for this case? – mmo2112 Dec 11 '19 at 07:39
  • 1
    Well, there is a way suggested by Ravindra in the answer by overriding `equals` but I wouldn't recommend it personally for losing the other information while comparing the objects in general. Another would be abstracting out the code for filtering to a utility method and using that with reference. But that should not be required given the lambda already reads clean and works appropriately here. – Naman Dec 11 '19 at 07:58
  • 1
    I suggests reading [this answer to “Composition of method reference”](https://stackoverflow.com/a/27563647/2711488). It’s about combining two `Function` instances, but the same applies to combining a `Predicate` and a `Function`; you can combine two method references via helper function, but in the end, there is no advantage over just writing a lambda expression. – Holger Dec 11 '19 at 08:33

2 Answers2

1

You can use a little different approach:

List<xxx> list1 = function1();
List<xxx> list2 = function2();
Set<String> seen = new HashSet<>();

To union:

Predicate<xxx> distinctFilter = x -> seen.add(x.getId());
result = Stream.concat(list1.stream(), list2.stream())
.filter(distinctFilter)
.collect(toList());

To intersect:

Predicate<xxx> distinctFilter = x -> !seen.add(x.getId());
result = Stream.concat(list1.stream(), list2.stream())
.filter(distinctFilter)
.collect(toList());
Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
1

You can take an alternative approach like below:

List<XXX> items = list2.stream()
            .map(itemL1->{
                        return list1.stream().
                            filter((itemL2)->itemL2.getId().equals(itemL1.getId()))
                            .collect(Collectors.toList());
                    }
                )
            .flatMap(List::stream)
            .collect(Collectors.toList());
Shrirang Kumbhar
  • 363
  • 4
  • 17
  • I think there is no way to call simple double colonat my case because of difference between String and Object. I will close my question, thanks for you help. – mmo2112 Dec 11 '19 at 09:14
  • Instead of mapping to a `List`, followed by `.flatMap(List::stream)`, you can use `flatMap` in the first place, i.e. `.flatMap(itemL1 -> list1.stream() .filter(itemL2 -> itemL2.getId().equals(itemL1.getId())))`. But anyway, neither is equivalent to the OP’s operation, which is rather `list2.stream() .filter(a -> list1.stream().anyMatch(b -> a.getId().equals(b.getId())) .collect(Collectors.toList());`. But collecting the IDs of `list1` into a collection first is preferable, especially when using a `Set` instead of `List`. – Holger Dec 12 '19 at 08:55