There are two clean ways to make a method communicate multiple return values to the caller.
The first and simplest way is to return an object that contains all the return values. It might be a Collection, like a List, or it might be be an object of your own value class.
List<Person> matches = findMatches(peopleDB,criteria);
MatchReport report = findMatchesAsReport(peopleDB);
...
public List<Person> findMatches(PersonSource source,Criteria criteria) {
List<Person> list = new ArrayList<Person>();
while(source.hasNext()) {
Person person = source.next();
if(person.matches(criteria)) {
list.add(person);
}
}
return list;
}
The second, more complex way is to define a handler which your method can hand items to as it encounters them.
public interface PersonHandler {
public void onPerson(Person p);
}
Then you define your method so a handler gets passed to it:
public void findMatches(PeopleSource source, Criteria criteria, PersonHandler handler) {
while(source.hasNext()) {
Person person = source.next();
if(person.matches(criteria)) {
handler.onPerson(person);
}
}
}
The caller can then define a PersonHandler than meets their own needs:
private static class PrintToWriterPersonHandler implements PersonHandler {
private PrintWriter writer;
public WriteToStreamPersonHandler(PrintWriter writer) {
this.writer = writer;
}
public void onPerson(Person p) {
writer.println(person);
}
}
...
findMatches(source,criteria,new PrintToWriterPersonHandler(System.out));
This is quite involved, and complex for a beginner. But it's worth keeping in mind. It means you can deal with methods that produce huge numbers of responses, without having to wait until the method finishes, and without ending up with a huge List in memory. It also means you can deal with output from a method that may run indefinitely!