Home > Software engineering >  Beginner question: Using Java stream to access data on nested data structure with customized class
Beginner question: Using Java stream to access data on nested data structure with customized class

Time:09-29

i am learning the stream today and encountered this problem below: i know this is a beginner question but i hope this can be helpful to ppl who first learn steam like me. so the problem is to do stream() operation on a students array that has many instant variables defined. including two customized variable address and MobileNumber. each student object can instantiate a list of Mobilenumber.

since we have a students array of student objects. how do i use stream to access the mobilenumber array inside each student object and then use filter to find the "3333" number? and 2nd question is how can i Get all student having both mobile number 1233 and 1234, appreciate the help!

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

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

Student student1 = new Student("Jayesh", 20, new Address("1234"),
    Arrays.asList(new MobileNumber("1233"), new MobileNumber("1234")));

Student student2 = new Student("Khyati", 20, new Address("1235"), Arrays
    .asList(new MobileNumber("1111"), new MobileNumber("3333"), new MobileNumber("1233")));

Student student3 = new Student("Jason", 20, new Address("1236"),
    Arrays.asList(new MobileNumber("3333"), new MobileNumber("4444")));

List<Student> students = Arrays.asList(student1, student2, student3);

/*****************************************************
 * Get all student having mobile numbers 3333.
 *****************************************************/
students.stream().filter(student -> student.getMobileNumbers()
        .stream().anyMatch(mobileNumber -> mobileNumber.getNumber().equals("3333")))
        .forEach(student -> student.getName());

     /*****************************************************
 * Get all student having mobile number 1233 and 1234
 *****************************************************/
List<String> studentWith1233And1234 = students.stream().filter(student -> student.getMobileNumbers()
        .stream().anyMatch(mobileNumber -> mobileNumber.getNumber().equals("1233")&& mobileNumber.getNumber().equals("1234")))
        .map(Student::getName)
        .collect(Collectors.toList());

System.out.println(studentWith1233And1234); 

    }
}



import java.util.List;

class TempStudent {
  public String name;
  public int age;
  public Address address;
  public List<MobileNumber> mobileNumbers;

  public TempStudent(String name, int age, Address address, List<MobileNumber> mobileNumbers)         {
    this.name = name;
    this.age = age;
    this.address = address;
    this.mobileNumbers = mobileNumbers;
  }
}


class Student {
  private String name;
  private int age;
  private Address address;
  private List<MobileNumber> mobileNumbers;

  public Student(String name, int age, Address address, List<MobileNumber> mobileNumbers) {
    this.name = name;
    this.age = age;
    this.address = address;
    this.mobileNumbers = mobileNumbers;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  public Address getAddress() {
    return address;
  }

  public List<MobileNumber> getMobileNumbers() {
    return mobileNumbers;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public void setAddress(Address address) {
    this.address = address;
  }

  public void setMobileNumbers(List<MobileNumber> mobileNumbers) {
    this.mobileNumbers = mobileNumbers;
  }

  @Override
  public String toString() {
    return "Student{"   "name='"   name   '\''   ", age="   age   ", address="   address
          ", mobileNumbers="   mobileNumbers   '}';
  }
}



  class Address {
    private String zipcode;

    public Address(String zipcode) {
      this.zipcode = zipcode;
    }

    public String getZipcode() {
      return zipcode;
    }

    public void setZipcode(String zipcode) {
      this.zipcode = zipcode;
    }
  }


  class MobileNumber {
    private String number;

    public MobileNumber(String number) {
      this.number = number;
    }

    public String getNumber() {
      return number;
    }

    public void setNumber(String number) {
      this.number = number;
    }
  }

CodePudding user response:

As said before, but probably won't compile (MobileNumber is not of type String) to apply containsAll() with a List of String:

    List<String> studentWith1233And1234 = students.stream()
            .filter(student -> student.getMobileNumbers().stream().map(MobileNumber::getNumber)
                    .collect(Collectors.toList()).containsAll(List.of("1233", "1234")))
            .map(Student::getName)
            .collect(Collectors.toList());

CodePudding user response:

I simplify your model a bit for better reading (remove Address and TempStudent, use record for removing all getter/setter/toString/equals methods)

    public static void main(String[] args) {
        
        Student student1 = new Student("Jayesh", 20, Arrays.asList(new MobileNumber("1233"), new MobileNumber("1234")));
        
        Student student2 = new Student("Khyati", 20, Arrays.asList(new MobileNumber("1111"), new MobileNumber("3333"), new MobileNumber("1233")));
        
        Student student3 = new Student("Jason", 20, Arrays.asList(new MobileNumber("3333"), new MobileNumber("4444")));
        
        List<Student> students = Arrays.asList(student1, student2, student3);
        
        /*****************************************************
         * Get all student having mobile numbers 3333.
         *****************************************************/
        List<Student> studentsWith3333 =
                students.stream()
                        .filter(student -> student.containsNumber("3333"))
                        .toList();
        System.out.println("Get all students having mobile numbers 3333");
        System.out.println(studentsWith3333);
        
        /*****************************************************
         * Get all student having mobile number 1233 and 1234
         *****************************************************/
        List<Student> studentWith1233And1234 =
                students.stream()
                        .filter(student -> student.containsAllNumbers(List.of("1233","1234")))
                        .toList();
        System.out.println("Get all students having mobile numbers 1233 and 1234");
        System.out.println(studentWith1233And1234);
        
    }
    
    record MobileNumber(String number) {}
    
    record Student(String name, int age, List<MobileNumber> numbers) {
        
        boolean containsNumber(String number){
            return numbers.contains(new MobileNumber(number));
        }
    
        boolean containsAllNumbers(List<String> numberList){
            return numbers.containsAll(numberList.stream().map(MobileNumber::new).toList());
        }
    }
}

Output

Get all students having mobile numbers 3333
[
   Student[
      name=Khyati, 
      age=20, 
      numbers=[
         MobileNumber[number=1111], MobileNumber[number=3333], MobileNumber[number=1233]
      ]
   ],
   Student[
      name=Jason,
      age=20,
      numbers=[
         MobileNumber[number=3333], MobileNumber[number=4444]
      ]
   ]
]
Get all students having mobile numbers 1233 and 1234
[
   Student[
      name=Jayesh, 
      age=20, 
      numbers=[
         MobileNumber[number=1233], MobileNumber[number=1234]
      ]
   ]
]

CodePudding user response:

The predicate below would always be evaluated to false:

.anyMatch(mobileNumber -> mobileNumber.getNumber().equals("1233")
                       && mobileNumber.getNumber().equals("1234"))

because the same string could not be equal to more than one distinct value. For that reason, the solution you've listed is not viable.

Instead, you can use collect all the mobile numbers assosiated with a particular student into a Set and then apply Set.contains() or Set.containsAll() depending on whether you need to check if a single value, or a group of values is present.

List<String> studentWith1233And1234 = students.stream()
    .filter(student -> student.getMobileNumbers().stream()
        .map(MobileNumber::getNumber)
        .collect(Collectors.toSet())
        .containsAll(List.of("1233", "1234")))
    .map(Student::getName)
    .collect(Collectors.toList()); // or .toList for Java 16 

and

List<String> studentsWith3333 = students.stream()
    .filter(student -> student.getMobileNumbers().stream()
        .map(MobileNumber::getNumber)
        .collect(Collectors.toSet())
        .contains("3333"))
    .map(Student::getName)
    .collect(Collectors.toList()); // or .toList for Java 16 

Note: performance of contains() and containsAll() methods, which are defined by the Collection interface, depends on the implementation of a collection on which method is being invoked. If you have only a couple of elements in the collection the difference between an ArrayList and HashSet would be moderate, but if Student and MobileNumber are only dummy analogies and in fact you have lots of elements then the difference in performance would be significant.

CodePudding user response:

Use a Predicate functional to filter. This predicate can also be created from a Set of given numbers using a static (factory) method:

// method to create a filter
static Predicate<Student> hasNumbers(Set<String> searchedNumbers) {
   return (student) -> Set.of(student.getMobileNumbers()).containsAll(searchedNumbers);
   // use a stream map to string for the Set-creation
   // if MobileNumber.getNumber() is the only getter
}


// either one number to search
var byNumber = hasNumbers(Set.of("3333"));
// or many numbers to search
var byNumbers = hasNumbers(Set.of("1233", "1234"));

// apply the filter for your case:
var foundNames = students.stream()
  .filter(byNumber)  // or use the other filter: byNumbers (plural)
  .map(Student::getName)
  .collect(Collectors.toList());

Benefits:

  • the stream is short and readable
  • the filter can be created by any given numbers on the fly
  • Related