Define yourself a key object that holds and compares the desired properties. In this simple case, you may use a small list whereas each index corresponds to one property. For more complex cases, you may use a Map (using property names as keys) or a dedicated class:
Function<Person,List<Object>> toKey=p -> Arrays.asList(p.getName(), p.getAge());
Having such a mapping function. you may use the simple solution:
list1.stream().map(toKey)
.flatMap(key -> list2.stream().map(toKey).filter(key::equals))
.forEach(key -> System.out.println("{name="+key.get(0)+", age="+key.get(1)+"}"));
which may lead to poor performance when you have rather large lists. When you have large lists (or can’t predict their sizes), you should use an intermediate Set to accelerate the lookup (changing the task’s time complexity from O(n²) to O(n)):
list2.stream().map(toKey)
.filter(list1.stream().map(toKey).collect(Collectors.toSet())::contains)
.forEach(key -> System.out.println("{name="+key.get(0)+", age="+key.get(1)+"}"));
In the examples above, each match gets printed. If you are only interested in whether such a match exists, you may use either:
boolean exists=list1.stream().map(toKey)
.anyMatch(key -> list2.stream().map(toKey).anyMatch(key::equals));
or
boolean exists=list2.stream().map(toKey)
.anyMatch(list1.stream().map(toKey).collect(Collectors.toSet())::contains);
Answer from Holger on Stack OverflowDefine yourself a key object that holds and compares the desired properties. In this simple case, you may use a small list whereas each index corresponds to one property. For more complex cases, you may use a Map (using property names as keys) or a dedicated class:
Function<Person,List<Object>> toKey=p -> Arrays.asList(p.getName(), p.getAge());
Having such a mapping function. you may use the simple solution:
list1.stream().map(toKey)
.flatMap(key -> list2.stream().map(toKey).filter(key::equals))
.forEach(key -> System.out.println("{name="+key.get(0)+", age="+key.get(1)+"}"));
which may lead to poor performance when you have rather large lists. When you have large lists (or can’t predict their sizes), you should use an intermediate Set to accelerate the lookup (changing the task’s time complexity from O(n²) to O(n)):
list2.stream().map(toKey)
.filter(list1.stream().map(toKey).collect(Collectors.toSet())::contains)
.forEach(key -> System.out.println("{name="+key.get(0)+", age="+key.get(1)+"}"));
In the examples above, each match gets printed. If you are only interested in whether such a match exists, you may use either:
boolean exists=list1.stream().map(toKey)
.anyMatch(key -> list2.stream().map(toKey).anyMatch(key::equals));
or
boolean exists=list2.stream().map(toKey)
.anyMatch(list1.stream().map(toKey).collect(Collectors.toSet())::contains);
A simple way to do that is to override equals and hashCode. Since I assume the equality between Person must also consider the id field, you can wrap this instance into a PersonWrapper which will implement the correct equals and hashCode (i.e. only check the name and age fields):
class PersonWrapper {
private Person person;
private PersonWrapper(Person person) {
this.person = person;
}
public static PersonWrapper wrap(Person person) {
return new PersonWrapper(person);
}
public Person unwrap() {
return person;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PersonWrapper other = (PersonWrapper) obj;
return person.name.equals(other.person.name) && person.age.equals(other.person.age);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + person.name.hashCode();
result = prime * result + person.age.hashCode();
return result;
}
}
With such a class, you can then have the following:
Set<PersonWrapper> set2 = list2.stream().map(PersonWrapper::wrap).collect(toSet());
boolean exists =
list1.stream()
.map(PersonWrapper::wrap)
.filter(set2::contains)
.findFirst()
.isPresent();
System.out.println(exists);
This code converts the list2 into a Set of wrapped persons. The goal of having a Set is to have a constant-time contains operation for better performance.
Then, the list1 is filtered. Every element found in set2 is kept and if there is an element left (that is to say, if findFirst() returns a non empty Optional), it means an element was found.
With Collectors.partitioningBy you can get Map<Boolean, List<String>>. From partitioningBy doc:
Returns a Collector which partitions the input elements according to a
Predicate, and organizes them into aMap<Boolean, List<T>>.
In your case:
Map<Boolean, List<String>> map = list1.stream()
.collect(Collectors.partitioningBy(list2::contains));
In case of more complex object that doesn't override equals/hashcode you can do comparison by some specific field. Assuming you have something like:
class ComplexObj {
private String id;
...
}
Map<Boolean, List<ComplexObj>> map = list1.stream()
.collect(Collectors.partitioningBy(obj -> list2.stream()
.map(ComplexObj::getId)
.anyMatch(id -> id.equals(obj.getId()))));
Find my implementation below using Collectors.partitioningBy. I use List.contains() method for predicate and it works fine. Kindly try and share feedback/suggestions. Thanks.
package net.javapedia.streams;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PartitioningByEx1 {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("A", "B", "C", "D");
List<String> list2 = Arrays.asList("A", "D", "E");
list1.stream().collect(Collectors.partitioningBy(item -> list2.contains(item)))
.forEach((key, val) -> System.out.println(key + "-->>" + val));
}
}
Program output:

You can use filter and findFirst:
System.out.println("If any number present in aList is present in bList : "+aList.stream().filter(num -> bList.contains(num)).findFirst().orElse(null));
This will print the matching number if found (it will stop at the first match), or null, if no match is found.
Or, you can collect all the matches into a List:
System.out.println("If any number present in aList is present in bList : "+aList.stream().filter(num -> bList.contains(num)).collect(Collectors.toList());
Try this one
aList.stream().filter(bList::contains).collect(Collectors.toSet());
Let's run through each part of the code. First, createSharedListViaStream:
public static List<SchoolObj> createSharedListViaStream(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
// We create a stream of elements from the first list.
List<SchoolObj> listOneList = listOne.stream()
// We select any elements such that in the stream of elements from the second list
.filter(two -> listTwo.stream()
// there is an element that has the same name and school as this element,
.anyMatch(one -> one.getName().equals(two.getName())
&& two.getSchool().equals(one.getSchool())))
// and collect all matching elements from the first list into a new list.
.collect(Collectors.toList());
// We return the collected list.
return listOneList;
}
After running through the code, it does exactly what you want it to do. Now, let's run through createSharedListViaLoop:
public static List<SchoolObj> createSharedListViaLoop(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
// We build up a result by...
List<SchoolObj> result = new ArrayList<SchoolObj>();
// going through each element in the first list,
for (SchoolObj one : listOne)
{
// going through each element in the second list,
for (SchoolObj two : listTwo)
{
// and collecting the first list's element if it matches the second list's element.
if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
{
result.add(one);
}
}
}
// We return the collected list
return result;
}
So far, so good... right? In fact, your code in createSharedListViaStream is fundamentally correct; instead, it is your createSharedListViaLoop that may be causing discrepancies in output.
Think about the following set of inputs:
List1 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameC","SchoolZ")]
List2 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameA","SchoolX"), SchoolObj("nameB","SchoolY")]
Here, createSharedListViaStream will return the only element of the first list that appears in both lists: SchoolObj("nameA","SchoolX"). However, createSharedListViaLoop will return the following list: [SchoolObj("nameA","SchoolX"),SchoolObj("nameA","SchoolX")]. More precisely, createSharedListViaLoop will collect the correct object, but it will do so twice. I suspect this to be the reason for the output of createSharedListViaStream to be "incorrect" based on comparison to the output of createSharedListViaLoop.
The reason that createSharedListViaLoop does this duplication is based on the lack of termination of its inner for loop. Although we iterate over all elements of the first list to check if they are present in the second, finding a single match will suffice to add the element to the result. We can avoid redundant element addition by changing the inner loop to the following:
for (SchoolObj one : listOne)
{
for (SchoolObj two : listTwo)
{
if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
{
result.add(one);
break;
}
}
}
Additionally, if you don't want duplicate Objects in your list (by location in memory), you can use distinct like so:
List<SchoolObj> result = ...;
result = result.stream().distinct().collect(Collectors.toList());
As a final caution, the above will keep the results distinct in the following scenario:
List<SchoolObj> list = new ArrayList<>();
SchoolObj duplicate = new SchoolObj("nameC", "schoolD");
listOne.add(duplicate);
listOne.add(duplicate);
list.stream().distinct().forEach(System.out::println);
// prints:
// nameC schoolD
However, it will not work in the following scenario, unless you override the equals method for SchoolObj:
List<SchoolObj> list = new ArrayList<>();
listOne.add(new SchoolObj("nameC", "schoolD"));
listOne.add(new SchoolObj("nameC", "schoolD"));
list.stream().distinct().forEach(System.out::println);
// prints (unless Object::equals overridden)
// nameC schoolD
// nameC schoolD
You can filter in one list if contains in another list then collect.
List<SchoolObj> listCommon = listTwo.stream()
.filter(e -> listOne.contains(e))
.collect(Collectors.toList());
You need to override equals() method in SchoolObj class. contains() method you will uses the equals() method to evaluate if two objects are the same.
@Override
public boolean equals(Object o) {
if (!(o instanceof SchoolObj))
return false;
SchoolObj n = (SchoolObj) o;
return n.name.equals(name) && n.school.equals(school);
}
But better solution is to use Set for one list and filter in another list to collect if contains in Set. Set#contains takes O(1) which is faster.
Set<SchoolObj> setOne = new HashSet<>(listOne);
List<SchoolObj> listCommon = listTwo.stream()
.filter(e -> setOne.contains(e))
.collect(Collectors.toList());
You need to override hashCode() method also along with equals() in SchoolObj class for Set#contains.(assuming name and school can't be null)
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + name.hashCode();
result = prime * result + school.hashCode();
return result;
}
Here you will get details how to override equals and hashCode in a better way
Try this out
List<Person> list1 = getPersonList1();
List<Person> list2 = getPersonList2();
list1.replaceAll(l1 -> list2.stream()
.filter(l2 -> l2.getId().equals(l1.getId()))
.findAny()
.orElse(l1));
list1.addAll(list2.stream()
.filter(l2 -> !list1.contains(l2))
.collect(toList()));
getPersonList1
private static List<Person> getPersonList1() {
return Lists.newArrayList(
new Person("1", "test1", "3"),
new Person("2", "test2", "6"),
new Person("3", "test3", "8"),
new Person("10", "test10", "8")
);
}
getPersonList2
private static List<Person> getPersonList2() {
return Lists.newArrayList(
new Person("1", "test1", "33"),
new Person("2", "test22", "16"),
new Person("3", "test3", "18"),
new Person("4", "test4", "15")
);
}
Output
List1
[
Person(id=1, name=test1, age=3),
Person(id=2, name=test2, age=6),
Person(id=3, name=test3, age=8),
Person(id=10, name=test10, age=8)
]
List2
[
Person(id=1, name=test1, age=33),
Person(id=2, name=test22, age=16),
Person(id=3, name=test3, age=18),
Person(id=4, name=test4, age=15)
]
Final List1
[
Person(id=1, name=test1, age=33),
Person(id=2, name=test22, age=16),
Person(id=3, name=test3, age=18),
Person(id=10, name=test10, age=8),
Person(id=4, name=test4, age=15)
]
You can reassign the list by doing:
list1 = existingCartItem.get().getAttributes().stream()
.filter(x -> (cartItem.getAttributes().stream()
.filter(y->y.getCartItemAttributeId()== x.getCartItemAttributeId())
.count()<1
)).collect(Collectors.toList());
What you seem to be after is something known as the cartesian product.

See https://en.wikipedia.org/wiki/Cartesian_product
You can do this with Java 8 streams
All permutations
// Just substitute the types and values for Lecture and Classroom instances
// I'm not going to do this for you
final List<String> first = Arrays.asList("foo","bar","baz");
final List<String> second = Arrays.asList("spam","ham","eggs");
final Set<Map.Entry<String,String>> objects =
first
.stream()
.flatMap(f ->
second
.stream()
.map(s -> new AbstractMap.SimpleEntry<>(f, s)))
.collect(Collectors.toSet());
Your 'objects' set is going to contain Abstract entry maps which hold your combinations.
Set[
Map{foo : spam}
Map{foo : ham}
Map{foo : eggs}
Map{bar : spam}
Map{bar : ham}
Map{bar : eggs}
Map{baz : spam}
Map{baz : ham}
Map{baz : eggs}
]
Groups of combinations
If you actually want 3 items in your set, you can do an intermediate collect on the second stream to collect into a data structure of your choice. Below shows that for a list, as I've already shown use of Collectors.toSet()
final Set<List<AbstractMap.SimpleEntry<String,String>>> objects =
first
.stream()
.map(f ->
second
.stream()
.map(s -> new AbstractMap.SimpleEntry<>(f, s))
.collect(Collectors.toList()))
.collect(Collectors.toSet());
Your 'objects' set is going to contain a list of Abstract entry maps which hold your combinations.
Set[
List(
Map{foo : spam}, Map{foo : ham}, Map{foo : eggs}
),
List(
Map{bar : spam}, Map{bar : ham}, Map{bar : eggs}
),
List(
Map{baz : spam}, Map{baz : ham}, Map{baz : eggs}
)
]
This illustrates a simple cartesian product algorithm using Java 8 in a single functional statement. Should you wish to add any clauses or exclusions, you can use filter or any of the other higher order functions to manipulate the stream.
So i got sucked in to this one and wrote a working solution
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
class ClassMatcher {
//The set of all possible matchings.
static ArrayList<ArrayList<Pair>> set = new ArrayList<ArrayList<Pair>>();
// The current matching being built
static ArrayList<Pair> cur = new ArrayList<Pair>();
public static void main(String[] args) {
Lecture[] l = { new Lecture(50, 1), new Lecture(70, 2), new Lecture(50, 3)};
ArrayList<Classroom> c = new ArrayList<>(Arrays.asList(
new Classroom(50, 1), new Classroom(70, 2),
new Classroom(100, 3)));
for (int i = 0; i < l.length; i++) {
//Fill with dummy values
cur.add(new Pair(new Classroom(-1, -1), new Lecture(-1, -1)));
}
// Sort the arrays to save work in rec()
Arrays.sort(l);
//Sort classrooms in descending order
Collections.sort(c, new Comparator<Classroom>() {
@Override
public int compare(Classroom o1, Classroom o2) {
return o1.compareTo(o2) * -1;
}
});
recursive(l, c, 0);
// Print all the sets
for (int i = 0; i < set.size(); i++) {
System.out.print("{");
for (int j = 0; j < set.get(i).size(); j++) {
System.out.print("Lecture " + set.get(i).get(j).l + ": "
+ "Classroom " + set.get(i).get(j).c);
if (j < set.get(i).size() - 1) {
System.out.print(", ");
} else {
System.out.print("}");
}
}
System.out.println();
}
}
public static void recursive(Lecture[] lectureList,
ArrayList<Classroom> classroomList, int curLecture) {
for (int i = 0; i < classroomList.size(); i++) {
// if the classroom is smaller than the lecture we cna stop as the
// lists are sorted so all other lectures will be to big for the
// current classroom
if (lectureList[curLecture].size > classroomList.get(i).size) {
return;
}
//Match the current classroom to the current lecture and add to the working matching
cur.set(curLecture, new Pair(classroomList.get(i), lectureList[curLecture]));
//If there are more lectures to do then remove the used classroom and recursively call.
if (curLecture < lectureList.length - 1) {
Classroom tmp = classroomList.remove(i);
recursive(lectureList, classroomList, curLecture + 1);
classroomList.add(i, tmp);
}
// If no Lectures left then add this matching to the set of all matchings.
else {
ArrayList<Pair> copy = (ArrayList<Pair>) cur.clone();
set.add(copy);
}
}
}
}
class Classroom implements Comparable<Classroom> {
int size;
int number;
public Classroom(int s, int n) {
size = s;
number = n;
}
@Override
public int compareTo(Classroom o) {
return Integer.compare(this.size, o.size);
}
public String toString() {
return number + " (" + size + ")";
}
}
class Lecture implements Comparable<Lecture> {
int size;
int number;
public Lecture(int s, int n) {
size = s;
number = n;
}
@Override
public int compareTo(Lecture o) {
return Integer.compare(this.size, o.size);
}
public String toString() {
return number + " (" + size + ")";
}
}
class Pair {
Classroom c;
Lecture l;
public Pair(Classroom c, Lecture l) {
this.c = c;
this.l = l;
}
}
This gives the output
{Lecture 1 (50): Classroom 3 (100), Lecture 3 (50): Classroom 1 (50), Lecture 2 (70): Classroom 2 (70)}
{Lecture 1 (50): Classroom 2 (70), Lecture 3 (50): Classroom 1 (50), Lecture 2 (70): Classroom 3 (100)}
{Lecture 1 (50): Classroom 1 (50), Lecture 3 (50): Classroom 3 (100), Lecture 2 (70): Classroom 2 (70)}
{Lecture 1 (50): Classroom 1 (50), Lecture 3 (50): Classroom 2 (70), Lecture 2 (70): Classroom 3 (100)}
EDIT
Here are two versions. One using ArrayList and other using HashSet
Compare them and create your own version from this, until you get what you need.
This should be enough to cover the:
P.S: It is not a school assignment :) So if you just guide me it will be enough
part of your question.
continuing with the original answer:
You may use a java.util.Collection and/or java.util.ArrayList for that.
The retainAll method does the following:
Retains only the elements in this collection that are contained in the specified collection
see this sample:
import java.util.Collection;
import java.util.ArrayList;
import java.util.Arrays;
public class Repeated {
public static void main( String [] args ) {
Collection listOne = new ArrayList(Arrays.asList("milan","dingo", "elpha", "hafil", "meat", "iga", "neeta.peeta"));
Collection listTwo = new ArrayList(Arrays.asList("hafil", "iga", "binga", "mike", "dingo"));
listOne.retainAll( listTwo );
System.out.println( listOne );
}
}
EDIT
For the second part ( similar values ) you may use the removeAll method:
Removes all of this collection's elements that are also contained in the specified collection.
This second version gives you also the similar values and handles repeated ( by discarding them).
This time the Collection could be a Set instead of a List ( the difference is, the Set doesn't allow repeated values )
import java.util.Collection;
import java.util.HashSet;
import java.util.Arrays;
class Repeated {
public static void main( String [] args ) {
Collection<String> listOne = Arrays.asList("milan","iga",
"dingo","iga",
"elpha","iga",
"hafil","iga",
"meat","iga",
"neeta.peeta","iga");
Collection<String> listTwo = Arrays.asList("hafil",
"iga",
"binga",
"mike",
"dingo","dingo","dingo");
Collection<String> similar = new HashSet<String>( listOne );
Collection<String> different = new HashSet<String>();
different.addAll( listOne );
different.addAll( listTwo );
similar.retainAll( listTwo );
different.removeAll( similar );
System.out.printf("One:%s%nTwo:%s%nSimilar:%s%nDifferent:%s%n", listOne, listTwo, similar, different);
}
}
Output:
$ java Repeated
One:[milan, iga, dingo, iga, elpha, iga, hafil, iga, meat, iga, neeta.peeta, iga]
Two:[hafil, iga, binga, mike, dingo, dingo, dingo]
Similar:[dingo, iga, hafil]
Different:[mike, binga, milan, meat, elpha, neeta.peeta]
If it doesn't do exactly what you need, it gives you a good start so you can handle from here.
Question for the reader: How would you include all the repeated values?
You can try intersection() and subtract() methods from CollectionUtils.
intersection() method gives you a collection containing common elements and the subtract() method gives you all the uncommon ones.
They should also take care of similar elements
I'd follow an approach like the following:
List<String> list1 = new ArrayList<>();
list1.add("A,Airplane");
list1.add("B,Boat");
List<String> list2 = new ArrayList<>();
list2.add("A90, Boing Airplane");
list2.add("A70, Boing777");
list2.add("B80, Boing Boat");
Pattern commaPattern = Pattern
.compile("\\s*,\\s*"); // a regex pattern to split by comma and the whitespace around it
Map<String, String> modelToType = list1.stream().map(commaPattern::split)
.collect(Collectors
.toMap(modelAndType -> modelAndType[0],
modelAndType -> modelAndType[1])); // mapping models to types for o(1) lookups
List<Vehicle> vehicles = list2.stream().map(commaPattern::split)
.map(subModelAndSubType -> {
Vehicle vehicle = new Vehicle();
vehicle.submodel = subModelAndSubType[0];
vehicle.subtype = subModelAndSubType[1];
vehicle.model = vehicle.submodel.substring(0, 1);
vehicle.type = modelToType.get(vehicle.model);
return vehicle;
}).collect(Collectors.toList());
The data as provided.
List<String> list1 = new ArrayList<>();
list1.add("A,Airplane");
list1.add("B,Boat");
List<String> list2 = new ArrayList<>();
list2.add("A90, Boeing Airplane");
list2.add("A70, Boeing777");
list2.add("B80, Boeing Boat");
Regardless of how the model type information is obtained, this conversion might be more efficient and certainly easier if instead of using Lists you used Maps to hold the information. If the information was read in from a file it would be best to pre-process them (split if need be) as they are read in and put them in a map. If it was entered by hand then putting the information in a map would negate the need for any pre-processing at all. Here is a simple example.
Map<String,String> map1 = new HashMap<>();
map1.put("A","Airplane");
map1.put("B","Boat");
However, using the information as provided here is how I proceeded.
First, I created a Lambda to assist in the List conversion.
Function<List<String>, Map<String, String>> makeMap =
lst -> lst.stream().map(st -> st.split("\\s*,\\s*")).collect(
Collectors.toMap(a -> a[0], a -> a[1]));
// create the TypeModel map
Map<String, String> mapTM = makeMap.apply(list1);
// create the subTypeSubModel Map;
Map<String, String> mapSTSM = makeMap.apply(list2);
Now just use the keysets of each to go thru and piece it together.
I created a constructor in my example for Vehicle which imho makes
a cleaner object creation.
List<Vehicle> vehicles = mapTM.keySet().stream()
.flatMap(type -> mapSTSM.keySet().stream()
.filter(subType -> subType.startsWith(type))
.map(sbType -> new Vehicle(mapTM.get(type),
mapSTSM.get(sbType), type, sbType)))
.collect(Collectors.toList());
vehicles.forEach(System.out::println);
Prints based on toString (which can be changed).
[Airplane, Boeing Airplane,A,A90]
[Airplane, Boeing777,A,A70]
[Boat, Boeing Boat,B,B80]
Here is the class sans setters and getters.
class Vehicle {
private String model;
private String submodel;
private String type;
private String subtype;
public Vehicle() {
}
public Vehicle(String model, String submodel, String type,
String subtype) {
this.model = model;
this.submodel = submodel;
this.type = type;
this.subtype = subtype;
}
public String toString() {
return "[" + String.join(", ", model, submodel, type, subtype)
+ "]";
}
}
You can make use of noneMatch operation, such as:
List<App1> result = app1List.stream()
.filter(app1 -> app2List.stream()
.noneMatch(app2 -> app2.getDifferentCity().equals(app1.getCity()) &&
app2.getDifferentName().equals(app1.getName())))
.collect(Collectors.toList());
This assumes the combination of both name and city is matched while filtering.
You need to override equals method in App2 class:
public class App2{
private String differentName;
private String differentCity;
private String someProperty1;
private String someProperty2;
// getter setter
// constructors
@Override
public boolean equals(Object obj) {
App2 app2 = (App2) obj;
return this.differentName.equals(app2.getDifferentName()) && this.differentCity.equals(app2.getDifferentCity());
}
}
and then you can use Streams on your list1 like this:
app1List = app1List.stream()
.filter(a-> !app2List.contains(new App2(a.getName(),a.getCity())))
.collect(Collectors.toList());
Output:
[App1{name='test1', city='city1'}, App1{name='test4', city='city4'}]
The trick to make it a one liner would be to create a set in filter cause
String profile = Arrays.stream(args)
.filter(new HashSet(secondList)::contains)
.findFirst()
.orElse("default");
Such set is going to be created only once. The same instance of set will be used for each item in args.
So this code is O(n) to create a set and then O(n) to finding first arg.
Just keep in mind that expanded lambda like
.filter(arg -> new HashSet(secondList).contains(arg))
will create a set for each argument and will blowup complexity to O(n^2).
If the second list is small, then the time complexity is not very relevant. If the second list is large, you can first put it in a HashSet:
Set<String> second = new HashSet<> (secondList); //O(n)
loop //O(n)
if (second.contains(arg)) ... // O(1)
The approach, algorithm
To find if an object exists in a list, you need to perform a linear search, potentially visiting every single element, in \$O(n)\$ time. More efficient data structures exist:
Use an ordered data structure: if the values are sorted, then you can find if an element exists using binary search, in \$O(\log n)\$ time.
Use a hashset: you can find if an element is in the set in constant time, \$O(1)\$
To be to search efficiently, use a hashset instead of a list.
However, to be able to use a hashset efficiently,
it is required that the objects you put in it have appropriate implementation of hashCode and equals methods.
See the official tutorial on the Object class,
especially the sections on the equals and hashCode methods.
Note that IDEs like IntelliJ and Eclipse can generate these methods for you easily (they are boring to write by hand, and usually there's little reason to do so).
With correct implementation of the equals and hashCode methods,
for example as in the other answer by @Teddy,
your main program could be reduced to this:
public static void main(String[] args) {
Set<Person> originalPeople = new HashSet<>();
Set<Person> newPeople = new HashSet<>();
originalPeople.add(new Person("William", "Tyndale"));
originalPeople.add(new Person("Jonathan", "Edwards"));
originalPeople.add(new Person("Martin", "Luther"));
newPeople.add(new Person("Jonathan", "Edwards"));
newPeople.add(new Person("James", "Tyndale"));
newPeople.add(new Person("Roger", "Moore"));
for (Person original : originalPeople) {
if (!newPeople.contains(original)) {
System.out.printf("%s %s is not in the new list!%n",
original.getFirstName(), original.getLastName());
}
}
}
There are certain reasons I would not prefer your approach. These are:
1) There are multiple method calls which reduces the readability.
2) You are using filter twice which decreases the performances. You could do it inside the same filter like I've shown below
3) With this approach you don't have a consolidated List which contains common elements. For that you need another else condition which increases the cyclomatic complexity.
4) A null check which could produce NPE if not handled carefully as you are using findFirst() which returns Optional (Although null can be replaced with default new Person("","") object which is again not recommended).
5) Finally don't use ArrayList<Person> originalPeople = new ArrayList<>();; rather declare as List, to follow programming to interface.
Rather I would use below approach:
private static List<Person> getPersonInList(
final List<Person> newPeople, List<Person> originalPeople) {
List<Person> list = new ArrayList<>();
newPeople.forEach(p ->
originalPeople.stream()
.filter(p1 -> p.getFirstName().equals(p1.getFirstName()) &&
p.getLastName().equals(p1.getLastName()))
.forEach(list::add));
return list;
}
// produce the filter set by streaming the items from list 2
// assume list2 has elements of type MyClass where getStr gets the
// string that might appear in list1
Set<String> unavailableItems = list2.stream()
.map(MyClass::getStr)
.collect(Collectors.toSet());
// stream the list and use the set to filter it
List<String> unavailable = list1.stream()
.filter(e -> unavailableItems.contains(e))
.collect(Collectors.toList());
this can be achieved using below...
List<String> unavailable = list1.stream()
.filter(e -> !list2.contains(e))
.collect(Collectors.toList());