简体   繁体   中英

Create list of objects from another list by using java8 stream

I have a list student list A and I have student list B.

Student objects contains some fields like that: no,age,city,birth_date,salary

My list A contains these objects

A=[1 , 20 , Rome ,    2001, 300 ]
  [2 , 21 , Dublin ,  2005, 250 ]
  [3 , 24 , Istanbul, 2012, 350 ]
  [4 , 27 , Madrid ,  2002, 450 ]
  [5 , 27 , London ,  2005, 650 ]

My list B contains these objects

B=[1,  20 , Rome ,    2014, 700 ]
  [3,  24 , Istanbul, 2012, 350 ]
  [4,  27 , Madrid,   2009, 450 ]

What i want to do is extract student objects that ListA have but listB not and ListA and ListB have(as no,age,city) but salary is different.Also I want to write salary diff.

 Result set = [5 , 27 ,London, 2005, 650 ]
              [2 , 21 ,Dublin ,2005, 250 ]
              [1,  20, Rome ,  2001, -400]

I want to use stream api in java 8.First of all, I want to extract students objects to a list but I can extract common student objects now.I tried tu use noneMatch but it is not working.Do you have any idea?

List<Student> listDiffList = A.stream()
            // We select any elements such that in the stream of elements from the second list
            .filter(two -> B.stream()
            // there is an element that has the same name and school as this element,
                .anyMatch(one -> one.getNo().equals(two.getNo()) 
                        && one.getAge().equals(two.getAge())
                        && one.getCity().equals(two.getCity())))
            // and collect all matching elements from the first list into a new list.
            .collect(Collectors.toList());

I used custom Collector object, looks not very nice but it seems working:

List<Student> listDiffList =
  A.stream()
   .collect(ArrayList::new,
           (a, two) -> {
             Optional<Student> one = B.stream()
                                      .filter(s -> s.getNo().equals(two.getNo())
                                                   && s.getAge().equals(two.getAge())
                                                   && s.getCity().equals(two.getCity()))
                                      .findAny();


             if (one.isPresent()) {
                 if (!one.get().getSalary().equals(two.getSalary())) {
                     two.setSalary(two.getSalary()-one.get().getSalary());
                     a.add(two);
                 }
             }
             else a.add(two);
           },
           ArrayList::addAll);

If you have two lists where you just want a resulting list that is a subtraction of list B from list A, the most obvious way is to use the removeAll() method of the List interface:

import java.util.List;
import java.util.ArrayList;

public class Test {
  public static void main(String[] args){

    List<Integer> listA = List.of(Integer.valueOf(1),
                                  Integer.valueOf(2),
                                  Integer.valueOf(3),
                                  Integer.valueOf(4),
                                  Integer.valueOf(5));

    List<Integer> listB = List.of(Integer.valueOf(1),
                                  Integer.valueOf(3),
                                  Integer.valueOf(4));

    List<Integer> listResult = 
             new ArrayList<>(listA);  // creating an identical list
    listResult.removeAll(listB);      // subtract the elements present in list B

    System.out.println("listA: " + listA);
    System.out.println("listB: " + listB);
    System.out.println("listA \"minus\" listB: " + listResult);
    System.out.println();
  }
}

which will print:

listA: [1, 2, 3, 4, 5]
listB: [1, 3, 4]
listA "minus" listB: [2, 5]

You can do it using the streams interface, but it's not as simple. However both anyMatch() and noneMatch() will work here. They are both "short-circuiting terminal operation[s]" that return a boolean, so they are perfect as predicates. Just stream your list A and use a filter with the predicate to remove the elements you don't want. For anyMatch() you'll want to filter out if the result is true, so that needs to be reversed with a not . noneMatch() is pretty straight forward:

    List<Integer> listResultStream = 
      listA.stream()

..then use either:

           .filter(intA -> 
                     !(listB.stream()
                            .anyMatch(intB -> intB.equals(intA))))

..or:

           .filter(intA -> 
                     listB.stream()
                          .noneMatch(intB -> intB.equals(intA)))

and collect the result:

           .collect(Collectors.toList());

For your particular scenario, I'd probably not go with a streams solution. I've added some later on nonetheless.

I've used this Student class. I decided to comment out the birth_date as it's not relevant to the solutions:

class Student {
  int       no;
  int       age;
  String    city;
//  LocalDate birth_date;
  int       salary;

//  Student(int no, int age, String city, int birth_date, int salary){
  Student(int no, int age, String city, int salary){
    this.no = no;
    this.age = age;
    this.city = city;
//    this.birth_date = LocalDate.of(birth_date, 1, 1); // January 1st.
    this.salary = salary;
  }

  @Override
  public String toString(){
    return "(" +
           no + ", " +
           age + ", " + 
           city + ", " +
//           birth_date + ", " +
           salary +
           ")";
  }

with no birth_date I used these records for the two lists:

    List<Student> listStudentA = List.of(new Student(1 , 20 , "Rome" ,    300 ),
                                         new Student(2 , 21 , "Dublin" ,  250 ),
                                         new Student(3 , 24 , "Istanbul", 350 ),
                                         new Student(4 , 27 , "Madrid" ,  450 ),
                                         new Student(5 , 27 , "London" ,  650 ),
                                         new Student(6 , 27 , "Paris" ,   650 ));
   
    List<Student> listStudentB = List.of(new Student(1 , 20 , "Rome" ,    700 ),
                                         new Student(3 , 24 , "Istanbul", 350 ),
                                         new Student(4 , 27 , "Madrid" ,  450 ),
                                         new Student(6 , 27 , "Paris" ,   650 ));

What you seem to want is to compare records in list A and see if there is a similar record in list B. Only if there isn't even a similar match do you want to add the record to your result list. If there is a similar match, but only almost identical, then you want a new object to be added. And if there is an identical match, none will be added to the resulting list. It's like a List.removeAll() with a twist.

A regular double loop is perfect for that. Go through list A one by one, and see if you can find a similar record in list B. When you find one, check if it's almost or exactly identical. If it's almost then add a new object to the result list. Either way, you can stop the inner loop right there. Only if you have a record where you reach the end of the inner loop without having found a similar record, can the record be added to the result list. A flag can be used to check that using a boolean.

    List<Student> listStudentDiffLoops = new ArrayList<>();

    for (Student studentA : listStudentA) {
      boolean found = false;

      for (Student studentB : listStudentB) {
        if (studentA.no == studentB.no &&
            studentA.age == studentB.age &&
            studentA.city.equals(studentB.city)) {
          found = true; // either add a diffsalary Student or none at all

          // if this studentA is different from the found studentB
          if (studentA.salary != studentB.salary) {
            listStudentDiffLoops
              .add(new Student(studentA.no,
                               studentA.age,
                               studentA.city,
                               -Math.abs(studentA.salary - studentB.salary)));
          }
          break; // We're done comparing this studentA
        }
      }

      // there was no similar studentB
      if (!found) listStudentDiffLoops.add(studentA);
    }
    System.out.println("listStudentDiffLoops = " + listStudentDiffLoops);

this will print:

listStudentDiffLoops = [(1, 20, Rome, -400), (2, 21, Dublin, 250), (5, 27, London, 650)]

I decided to make the difference in salary always be negative using -Math.abs(studentA.salary - studentB.salary) so the object will not get confused with an actual Student . Alternatively one could create another class just for those objects:

class StudentSalaryDiff extends Student {
  StudentSalaryDiff(int no, int age, String city, int salary){
    super(no, age, city, salary);
  }
};

For a streams solution, you can map() each record in list A to either:

  • The same element, if noneMatch() returns true for the entire list B.
  • If there is a similar element in list B, then find it and the attribute of interest (salary), and return:
    • null when the records are identical (salaries match)
    • a new object if they are not identical (the salary is different)

When the objects have been mapped, then just filter out null objects.

Note that at worst this will stream list B twice for every element in list A. It will always stream list B to check for the noneMatch() , and if that is false, it will stream list B again:

    List<Student> listStudentDiff =
      listStudentA
        .stream()
        .map(studentA -> {
               if (listStudentB
                     .stream()
                     .noneMatch(studentB -> // noneMatch returns true or false
                                  (studentA.no == studentB.no &&
                                   studentA.age == studentB.age &&
                                   studentA.city.equals(studentB.city)))) {
                 return studentA;         // The same element
               } else { // there is a similar element
                 int otherSalary =
                       listStudentB
                         .stream()
                         .filter(studentB ->
                                   (studentA.no == studentB.no &&
                                    studentA.age == studentB.age &&
                                    studentA.city.equals(studentB.city)))
                         .findFirst()
                         .get() // should not fail since there IS a similar element
                         .salary;
                 if (otherSalary == studentA.salary) {
                   return (Student) null; // null
                 } else {                 
                   return new Student(    // a new object
                                studentA.no,
                                studentA.age,
                                studentA.city,
                                -Math.abs(studentA.salary - otherSalary));
                 }
               }
            })
        .filter(student -> student != null) // remove null objects.
        .collect(Collectors.toList());

If you prefer to only stream list B once, you can use findFirst() or findAny() which will give you just one result that may or may not be empty. The result is given in an Optional where you can check to see if there was a match or not using isPresent() .

It's a little similar to the accepted Answer by Sergey Afinogenov

This solution still uses map() to return elements as in the previous solution:

    List<Student> listStudentDiffOptional =
      listStudentA
        .stream()
        .map(studentA -> {
               Optional<Student> similarStudent = 
                 listStudentB
                   .stream()
                   .filter(studentB ->
                             (studentA.no == studentB.no &&
                              studentA.age == studentB.age &&
                              studentA.city.equals(studentB.city)))
                   .findFirst();
               if (similarStudent.isPresent()){
                 int salarydiff = similarStudent.get().salary;
                 if (salarydiff == studentA.salary) {
                    return (Student) null; // the two objects are identical
                  } else {
                    return new Student(studentA.no,
                                       studentA.age,
                                       studentA.city,
                                       -Math.abs(studentA.salary - salarydiff));
                  }
               } else {  // there wasn't a similar object
                 return studentA;
               }
            })
        .filter(student -> student != null)
        .collect(Collectors.toList());


All the code in one go:

import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.Optional;

public class Test {
  public static void main(String[] args){

    // --------------------------
    // -------- simple Integers -
    List<Integer> listA = List.of(Integer.valueOf(1),
                                  Integer.valueOf(2),
                                  Integer.valueOf(3),
                                  Integer.valueOf(4),
                                  Integer.valueOf(5));

    List<Integer> listB = List.of(Integer.valueOf(1),
                                  Integer.valueOf(3),
                                  Integer.valueOf(4));

    List<Integer> listResult = 
             new ArrayList<>(listA);  // creating an identical list
    listResult.removeAll(listB);      // subtract the elements present in list B

    System.out.println("listA: " + listA);
    System.out.println("listB: " + listB);
    System.out.println("listA \"minus\" listB: " + listResult);
    System.out.println();

    List<Integer> listResultStream = 
      listA.stream()
//           .filter(intA -> 
//                     !(listB.stream()
//                            .anyMatch(intB -> intB.equals(intA))))
           .filter(intA -> 
                     listB.stream()
                          .noneMatch(intB -> intB.equals(intA)))
           .collect(Collectors.toList());


    System.out.println("listA \"minus\" listB using listResultStream: " + listResultStream);
    System.out.println();

    // ---------------------------
    // -------- complex Students -
/*
    List<Student> listStudentA = List.of(new Student(1 , 20 , "Rome" ,    2001, 300 ),
                                         new Student(2 , 21 , "Dublin" ,  2005, 250 ),
                                         new Student(3 , 24 , "Istanbul", 2012, 350 ),
                                         new Student(4 , 27 , "Madrid" ,  2002, 450 ),
                                         new Student(5 , 27 , "London" ,  2005, 650 ),
                                         new Student(6 , 27 , "Paris" ,   2005, 650 ));
   
    List<Student> listStudentB = List.of(new Student(1 , 20 , "Rome" ,    2014, 700 ),
                                         new Student(3 , 24 , "Istanbul", 2012, 350 ),
                                         new Student(4 , 27 , "Madrid" ,  2009, 450 ),
                                         new Student(6 , 27 , "Paris" ,   2005, 650 ));
*/

    List<Student> listStudentA = List.of(new Student(1 , 20 , "Rome" ,    300 ),
                                         new Student(2 , 21 , "Dublin" ,  250 ),
                                         new Student(3 , 24 , "Istanbul", 350 ),
                                         new Student(4 , 27 , "Madrid" ,  450 ),
                                         new Student(5 , 27 , "London" ,  650 ),
                                         new Student(6 , 27 , "Paris" ,   650 ));
   
    List<Student> listStudentB = List.of(new Student(1 , 20 , "Rome" ,    700 ),
                                         new Student(3 , 24 , "Istanbul", 350 ),
                                         new Student(4 , 27 , "Madrid" ,  450 ),
                                         new Student(6 , 27 , "Paris" ,   650 ));

    List<Student> listStudentDiffLoops = new ArrayList<>();

    // --------------------------
    // -------- for loops -------
    for (Student studentA : listStudentA) {
      boolean found = false;

      for (Student studentB : listStudentB) {
        if (studentA.no == studentB.no &&
            studentA.age == studentB.age &&
            studentA.city.equals(studentB.city)) {
          found = true; // either add a diffsalary Student or none at all

          // if this studentA is different from the found studentB
          if (studentA.salary != studentB.salary) {
            listStudentDiffLoops
              .add(new Student(studentA.no,
                               studentA.age,
                               studentA.city,
                               -Math.abs(studentA.salary - studentB.salary)));
          }
          break; // We're done comparing this studentA
        }
      }

      // there was no similar studentB
      if (!found) listStudentDiffLoops.add(studentA);
    }
    System.out.println("listStudentDiffLoops = " + listStudentDiffLoops);

    // --------------------------
    // -------- streaming twice -
    List<Student> listStudentDiff =
      listStudentA
        .stream()
        .map(studentA -> {
               if (listStudentB
                     .stream()
                     .noneMatch(studentB ->
                                  (studentA.no == studentB.no &&
                                   studentA.age == studentB.age &&
                                   studentA.city.equals(studentB.city)))) {
                 return studentA;         // The same element
               } else { // there is a similar element
                 int otherSalary =
                       listStudentB
                         .stream()
                         .filter(studentB -> // noneMatch returns true or false
                                   (studentA.no == studentB.no &&
                                    studentA.age == studentB.age &&
                                    studentA.city.equals(studentB.city)))
                         .findFirst()
                         .get() // should not fail since there IS a similar element
                         .salary;
                 if (otherSalary == studentA.salary) {
                   return (Student) null; // null
                 } else {                 
                   return new Student(    // a new object
                                studentA.no,
                                studentA.age,
                                studentA.city,
                                -Math.abs(studentA.salary - otherSalary));
                 }
               }
            })
        .filter(student -> student != null) // remove null objects.
        .collect(Collectors.toList());
    System.out.println("listStudentDiff: " + listStudentDiff);

    // --------------------------
    // -------- using optional --
    List<Student> listStudentDiffOptional =
      listStudentA
        .stream()
        .map(studentA -> {
               Optional<Student> similarStudent = 
                 listStudentB
                   .stream()
                   .filter(studentB ->
                             (studentA.no == studentB.no &&
                              studentA.age == studentB.age &&
                              studentA.city.equals(studentB.city)))
                   .findFirst();
               if (similarStudent.isPresent()){
                 int salarydiff = similarStudent.get().salary;
                 if (salarydiff == studentA.salary) {
                    return (Student) null; // the two objects are identical
                  } else {
                    return new Student(studentA.no,
                                       studentA.age,
                                       studentA.city,
                                       -Math.abs(studentA.salary - salarydiff));
                  }
               } else {  // there wasn't a similar object
                 return studentA;
               }
            })
        .filter(student -> student != null)
        .collect(Collectors.toList());
    System.out.println("listStudentDiffOptional: " + listStudentDiffOptional);
  }
}


// ------------------------------
// ------------------------------
class Student {
  int       no;
  int       age;
  String    city;
//  LocalDate birth_date;
  int       salary;

//  Student(int no, int age, String city, int birth_date, int salary){
  Student(int no, int age, String city, int salary){
    this.no = no;
    this.age = age;
    this.city = city;
//    this.birth_date = LocalDate.of(birth_date, 1, 1); // January 1st.
    this.salary = salary;
  }

  @Override
  public String toString(){
    return "(" +
           no + ", " +
           age + ", " + 
           city + ", " +
//           birth_date + ", " +
           salary +
           ")";
  }
}

class StudentSalaryDiff extends Student {
  StudentSalaryDiff(int no, int age, String city, int salary){
    super(no, age, city, salary);
  }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM