Java Search List by different criteria - java

i have several list classes that need to be searched by different criteria (single values, multiple values, etc...). These classes have, at the moment, different methods depending on the search criteria. Since i hate to write the same code again and again i am looking to avoid this... but i don't want to reinvent the wheel.
So, i am considering creating a custom class that implements a generic search.
The code looks like this:
import java.util.ArrayList;
import java.util.Iterator;
public class ListCustomComparable<T> {
private ArrayList<T> listItems;
public ListCustomComparable() {
}
/**
* #return the listItems
*/
public ArrayList<T> getListItems() {
return listItems;
}
/**
* #param listItems the listItems to set
*/
public void setListItems(ArrayList<T> listItems) {
this.listItems = listItems;
}
public ArrayList<T> searchByComparable(Comparable<T> comparator){
ArrayList<T> listRes= new ArrayList<T>();
for (T item: listItems ){
if(comparator.equals(item))
listRes.add(item);
}
return listRes;
}
}
So, i pretend that every method that wants to do a specific search must implement the comparable interface. This methods, obviously, will need their own code but will rely on the generic class.
Please, what do you think about it? i am reinventing the wheel because it is already done? Or is it OK?
One additional restriction. It should work with 1.6.

i am reinventing the wheel because it is already done? Or is it OK?
Yes, you are re-inventing the wheel.
What you are suggesting pretty much exactly matches Predicates.
Here's an example:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Example {
static class Person{
int age;
String name;
Person(int age, String name){
this.age = age;
this.name = name;
}
}
public static void main(String[] args){
List<Person> persons = new ArrayList<>();
persons.add(new Person(20,"John"));
persons.add(new Person(25,"Alice"));
persons.add(new Person(30,"Peter"));
persons.add(new Person(25,"Stefan"));
List<Person> results = persons.stream()
.filter(p -> p.age <= 25 && p.name.equals("Stefan"))
.collect(Collectors.toList());
for(Person p : results)
System.out.println(p.name);
}
}
Predicate is a generic interface that specifies a method that returns a boolean value if something matches or not given that value.
Examples of how predicates can be defined:
Predicate<String> filter_1 = str -> "value".equals(str);
Predicate<String> filter_2 = "value"::equals;
Predicate<String> filter_3 = new Predicate<String>() {
#Override
public boolean test(String s) {
return "value".equals(s);
}
};
Predicate<String> minLengthFilter = str -> str != null && str.length() > 5;
Predicate<String> maxLengthFilter = str -> str != null && str.length() < 8;
Predicate<String> combined = minLengthFilter.and(maxLengthFilter);
In case you have to work with a Java version before 1.8, you could use for example guava2 which also has it's own Predicate3 system.
You could then for example filter it using the Iterables4 class and then finally collect it back in to a list using5.
You could of course find an alternative or make a own function to combine the filter+collect to list methods.
You can also combine Predicates using their Predicates class6, as well as being able to use them to construct certain simple predicates.
Here's a full example using Guava.
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
public class GuavaExample {
static class Person{
int age;
String name;
boolean isFemale;
Person(int age, String name, boolean isFemale){
this.age = age;
this.name = name;
this.isFemale = isFemale;
}
}
public static <T> List<T> applyFilter(List<? extends T> list, Predicate<? super T> ... filters){
// default case: no filters.
if (filters == null || filters.length == 0)
return new ArrayList<T>(list);
Iterable<? extends T> it = Iterables.filter(list,Predicates.and(filters));
return Lists.newArrayList(it);
}
public static <T> List<T> applyFilter(List<? extends T> list, Predicate<? super T> filter){
Iterable<? extends T> it = Iterables.filter(list,filter);
return Lists.newArrayList(it);
}
public static void main(String[] args) {
List<Person> result;
List<Person> persons = new ArrayList<Person>();
persons.add(new Person(8, "Little John",false));
persons.add(new Person(10, "Alice Jnr.",true));
persons.add(new Person(20,"John",false));
persons.add(new Person(25,"Alice",true));
persons.add(new Person(30,"Sarah",true));
persons.add(new Person(25,"Stefan",false));
Predicate<Person> isAdult = new Predicate<Person>() {
#Override
public boolean apply(Person person) {
return person.age >= 18;
}
};
Predicate<Person> isFemale = new Predicate<Person>() {
#Override
public boolean apply(Person person) {
return person.isFemale;
}
};
result = applyFilter(persons,isAdult);
System.out.println("Adults: ");
for(Person p : result)
System.out.println(p.name);
result = applyFilter(persons,Predicates.not(isAdult));
System.out.println("Children: ");
for(Person p : result)
System.out.println(p.name);
// Generic varargs will yield a warning, unfortionally...
result = applyFilter(persons, isAdult, isFemale);
System.out.println("Adult females: ");
for(Person p : result)
System.out.println(p.name);
}
}
You could perhaps define the Predicates using functions inside the classes you wish to sort and combine them with other classes that can be used to check if an value matches a certain objective.
e.g.
static class Person {
int age;
// ... code ...
public static Predicate<Person> ageFilter(final Range<Integer> range) {
return new Predicate<Person>() {
#Override
public boolean apply(Person person) {
return range.contains(person.age);
}
};
}
}
Which can then be re-used for various filters:
Predicate<Person> isAdult = Person.ageFilter(Range.atLeast(18)); // Yields 18 and older.
Predicate<Person> isToddler = Person.ageFilter(Range.open(1,3)); // Yields ages 1-3, including 1 and 3 exact.
Predicate<Person> isMiddleAge = Person.ageFilter(Range.openClosed(45,65)); // Yields ages 45-65, 45 included, 65 excluded.

Related

Transform Java Stream and return reduced values both

Assuming I consume a Stream of entities from a source which I do not want to materialize, and I want to both transform the elements, and return some globally reduced value, what is the idiomatic way with java(8)?
This is essentially trying to perform both a reduce() and a collect().
Example:
class Person {
public String firstname,
public String lastname,
public int age;
}
class TeamSummary {
public List<String> fullnames, // firstname and lastname of all
public Person oldest
}
public TeamSummary getSummary(Stream<Person> personStream) {
final TeamSummary summary = new Summary();
summary.fullnames = personStream
.peek(p -> if (summary.getOldest() == null || summary.getOldest.age < p.age) {
summary.oldest = p;
})
.map(p -> p.firstname + ' ' + p.lastname)
.collect(toList());
return summary;
}
It looks ugly to interact with a variable outside the stream inside the peek method, but what good alternatives are there, it seems I need to combine collect() and reduce().
It get's worse if I want to get a reduced value from the whole stream (like average age), and a filtered list (like Persons above 18 years). It also get's worse if TeamSummary is an immutable class, and additional mutable variables are required.
In such cases it is more idiomatic to use a while loop on stream.iterator() to avoid coupling of stream methods and variables? Or is it natural to use reduce to a tuple like (oldest, accumulated).
I am aware this question is a matter of opinion unless there is an obvious way (like a special collector) that solves this elegantly.
So you want to reduce your collection to a single value? That's where Collectors.reducing comes into play (Alternative: You could use Stream.reduce but with other modifications). Furthermore, you want to aggregate your values in some way and also have the perfect accumulator: TeamSummary.
Now, in the below code I made the foollowing adjustments:
Team Summary has the merge/identity function required for reduce, as it serves as the accumulator
I use a Null Object instead of null for a non-existing person, which makes the code much more readable without null checks (NPE during converter being one of the problems). Have you thought about your output if the stream is empty?
I added a Person constructor for my own convenience. But consider using getters and final fields (even if you think getters and the whole fake encapsulation are boilerplate: You can use method references, e.g. to pass to a comparator, but not field references)
Here is the code:
static class Person {
public String firstname;
public String lastname;
public int age;
public Person(String firstname, String lastname, int age) {
this.firstname = firstname;
this.lastname = lastname;
this.age = age;
}
public static Person getNullObjectYoung() {
return new Person("", "", 0);
}
}
static class TeamSummary {
public List<String> fullnames;
public Person oldest;
public static TeamSummary merge(TeamSummary lhs, TeamSummary rhs) {
TeamSummary result = new TeamSummary();
result.fullnames = new ArrayList<>();
result.fullnames.addAll(lhs.fullnames);
result.fullnames.addAll(rhs.fullnames);
result.oldest = Comparator.<Person, Integer>comparing(p -> p.age).reversed()
.compare(lhs.oldest, rhs.oldest) < 0
? lhs.oldest
: rhs.oldest;
return result;
}
public static TeamSummary of(Person person) {
TeamSummary result = new TeamSummary();
result.fullnames = new ArrayList<>();
result.fullnames.add(person.firstname + " " + person.lastname);
result.oldest = person;
return result;
}
public static TeamSummary identity() {
TeamSummary result = new TeamSummary();
result.fullnames = new ArrayList<>();
result.oldest = Person.getNullObjectYoung();
return result;
}
}
public static void main(String[] args) {
Stream<Person> personStream = Arrays.asList(
new Person("Tom", "T", 32),
new Person("Bob", "B", 40))
.stream();
TeamSummary result = personStream.collect(
Collectors.reducing(
TeamSummary.identity(),
TeamSummary::of,
TeamSummary::merge
));
System.out.println(result.fullnames + " " + result.oldest.age);
}
Note: You asked for a java 8 version. Maybe in java 12, you could also use Collectors.teeing, since you basically want to do two different reductions at the same time (for which we can currently leverage the accumulator).
Edit: Also added a solution for Stream.reduce, which requires a BiFunction (summary, person) -> person:
static class TeamSummary {
...
public TeamSummary include(final Person person) {
final TeamSummary result = new TeamSummary();
result.fullnames = new ArrayList<>(fullnames);
result.fullnames.add(person.firstname + " " + person.lastname);
result.oldest = Comparator.<Person, Integer> comparing(p -> p.age).reversed()
.compare(oldest, person) < 0
? oldest
: person;
return result;
}
}
public static void main(final String[] args) {
...
final TeamSummary reduced = personStream.reduce(
TeamSummary.identity(),
TeamSummary::include,
TeamSummary::merge);
}
Based on the requirements such as - Stream as input and inferring the complete list of names in the output of teamSummary. You can perform the operation mapping the person and its name details to an entry and then reduce them further such as :
return personStream
.map(p -> new AbstractMap.SimpleEntry<>(p, Collections.singletonList(p.getFirstname() + ' ' + p.getLastname())))
.reduce((entry1, entry2) -> new AbstractMap.SimpleEntry<>(entry1.getKey().getAge() >= entry2.getKey().getAge() ?
entry1.getKey() : entry2.getKey(), Stream.of(entry1.getValue(), entry2.getValue()).flatMap(List::stream).collect(Collectors.toList())))
.map(entry -> new TeamSummary(entry.getKey(), entry.getValue()))
.orElseThrow(IllegalArgumentException::new);
For a readable and simplified approach though I would rather suggest passing on the collection and working with multiple stream operations here to construct the TeamSummary as :
public TeamSummary getSummary(List<Person> people) {
List<String> fullNames = people.stream()
.map(p -> p.getFirstname() + ' ' + p.getLastname())
.collect(Collectors.toList());
Person oldestPerson = people.stream()
.reduce(BinaryOperator.maxBy(Comparator.comparing(Person::getAge)))
.orElseThrow(IllegalArgumentException::new);
return new TeamSummary(oldestPerson, fullNames);
}
I don't know why you'd use Collectors.reducing() when you can stream.reduce() directly?
BinaryOperator<Player> older = (p1, p2) ->
Comparator.comparing(Player::getAge) > 0
? p1 : p2;
TeamSummary summary = stream.reduce(
TeamSummary::new, // identity
// accumulator
(ts, player) -> {
ts.addFullnames(String.format("%s %s", player.firstName, player.lastName));
ts.setOldest(older.apply(ts.getOldest(), player));
}
// combiner
(ts1, ts2) -> {
// we can safely modify the given summaries, they were all created while reducing
ts1.setOldest(Math.max(ts1.getOldest(), ts2.getOldest()));
ts1.addFullnames(ts2.getFullnames().toArray());
return ts1;
});
TeamSummary would then look like this:
class TeamSummary {
private int oldest;
public Player getOldest() { return oldest; }
public void setOldest(Player newOldest) { oldest = newOldest; }
private List<String> fullnames();
public List<String> getFullnames() { return Collections.unmodifiableList(fullnames); }
public void addFullnames(String... names) {
fullnames.addAll(Arrays.asList(names));
}
}
Alternative
You could also extend TeamSummary with something like addPlayer(Player p) and merge() to allow it to maintain its consistency:
class TeamSummary {
#Getter
private int oldest;
#Getter
private List<String> fullnames = new ArrayList<>();
public void addPlayer(Player p) {
fullnames.add(String.format("%s %s", p.getFirstname(), p.getLastname()));
oldest = olderPlayer(oldest, p);
}
public TeamSummary merge(TeamSummary other) {
older = olderPlayer(oldest, other.oldest)
fullnames.addAll(other.fullnames);
return this;
}
final static Comparator<Player> BY_AGE = Comparator.comparing(Player::getAge);
private static Player olderPlayer(Player p1, Player p2) {
return BY_AGE.compare(p1, p2) > 0 ? p1 : p2;
}
}
which would make the reduction
stream.reduce(
TeamSummary::new,
TeamSummary::addPlayer,
TeamSummary::merge
);

Is it possible to group elements without closing the stream?

Is it possible to group elements in a Stream, but then continue streaming instead of having to create a new stream from the EntrySet of the returned map?
For example, I can do this:
public static void main(String[] args) {
// map of access date to list of users
// Person is a POJO with first name, last name, etc.
Map<Date, List<Person>> dateMap = new HashMap<>();
// ...
// output, sorted by access date, then person last name
dateMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e -> {
Date date = e.getKey();
// group persons by last name and sort
// this part seems clunky
e.getValue().stream().collect(Collectors.groupingBy(Person::getLastName, Collectors.toSet()))
.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e2 -> {
// pool agent id is the key
String lastName = e2.getKey();
Set<Person> personSet = e2.getValue();
float avgAge = calculateAverageAge(personSet);
int numPersons = personSet.size();
// write out row with date, lastName, avgAge, numPersons
});
});
}
Which works just fine, but seems a little clunky, especially the streaming into a map, and then immediately streaming on the entry set of that map.
Is there a way to group objects in a stream, but continue streaming?
You can shorten your code by using Map.forEach, downstream collectors, TreeMap, and IntSummaryStatistics.
By grouping into a TreeMap (instead of leaving it up to the groupingBy collector), you get the names sorted automatically. Instead of immediately getting the grouped map, you add a summarizingInt collector that turns the list of persons with the same name into IntSummaryStatistics of their ages.
public static void main(String[] args) {
Map<Date, List<Person>> dateMap = new HashMap<>();
dateMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e -> {
Date date = e.getKey();
e.getValue().stream()
.collect(Collectors.groupingBy(Person::getLastName,
TreeMap::new,
Collectors.summarizingInt(Person::getAge)))
.forEach((name, stats) -> System.out.println(date +" "+
lastName +" "+
stats.getAverage() +" "+
stats.getCount()));
});
}
If you have control over the type of the initial map, you could use TreeMap there as well, and shorten it further:
public static void main(String[] args) {
Map<Date, List<Person>> dateMap = new TreeMap<>();
dateMap.forEach((date, persons -> { ...
There are several different ways to interpret the question, but if we restate the question as, "Is it possible to group elements within a Stream without using a terminal operation and apply stream operations to the resulting groups within the same stream pipeline," then the answer is "Yes." In this restatement of the question, terminal operation is defined in the way that the Java 8 streams API defines it.
Here is an example that demonstrates this.
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
class StreamGrouper {
public static class GroupableObj<K extends Comparable<? super K>, T>
implements Comparable<GroupableObj<K, T>> {
private K key;
private T obj;
private Set<T> setOfObj;
public GroupableObj(K key, T obj) {
if (key == null) {
throw new NullPointerException("Key may not be null");
}
this.key = key;
this.obj = obj;
}
#Override
public int compareTo(GroupableObj<K, T> otherGroupable) {
return key.compareTo(otherGroupable.key);
}
#Override
public boolean equals(Object otherObj) {
if (otherObj == null) {
return false;
}
if (otherObj instanceof GroupableObj) {
GroupableObj<?, ?> otherGroupable =
(GroupableObj<?, ?>)otherObj;
return setOfObj == otherGroupable.setOfObj &&
key.equals(otherGroupable.key);
}
return false;
}
public Set<T> getGroup() {
return setOfObj;
}
public K getKey() {
return key;
}
public T getObject() {
return obj;
}
#Override
public int hashCode() {
return key.hashCode();
}
public void setGroup(Set<T> setOfObj) {
this.setOfObj = setOfObj;
}
}
public static class PeekGrouper<K extends Comparable<? super K>, T>
implements Consumer<GroupableObj<K, T>> {
private Map<K, Set<T>> groupMap;
public PeekGrouper() {
groupMap = new HashMap<>();
}
#Override
public void accept(GroupableObj<K, T> groupable) {
K key = groupable.getKey();
Set<T> group = groupMap.computeIfAbsent(key,
(k) -> new HashSet<T>());
groupable.setGroup(group);
group.add(groupable.getObject());
}
}
public static void main(String[] args) {
Function<Double, Long> myKeyExtractor =
(dblObj) -> Long.valueOf(
(long)(Math.floor(dblObj.doubleValue()*10.0)));
PeekGrouper<Long, Double> myGrouper = new PeekGrouper<>();
Random simpleRand = new Random(20190527L);
simpleRand.doubles(100).boxed().map((dblObj) ->
new GroupableObj<Long, Double>(
myKeyExtractor.apply(dblObj), dblObj)).peek(myGrouper).
distinct().sorted().
map(GroupableObj<Long, Double>::getGroup).
forEachOrdered((grp) -> System.out.println(grp));
}
}
In order to make a program that can be compiled and executed on its own, this example moves away from using the Person objects that are referenced in the question, but the grouping concept is the same, and the code from the question could turn into something like the following.
PeekGrouper<String, Person> myGrouper = new PeekGrouper<>();
e.getValue().stream().map((p) -> new GroupableObj<String, Person>(
p.getLastName(), p)).peek(myGrouper).distinct().sorted().
forEachOrdered(e2 -> {
String lastName = e2.getKey();
Set<Person> personSet = e2.getGroup();
float avgAge = calculateAverageAge(personSet);
int numPersons = personSet.size();
// write out row with date, lastName, avgAge, numPersons
});
Please note that in order for this example to work, it is required that the stream call both the distinct function (which reduces the stream to only a single instance of each group) and the sorted function (which ensures that the entire stream has been processed and the groups have been fully "collected" before processing continues). Also note that as implemented here GroupableObj is not safe to use with parallel streams. If the terminal operation of the stream does not require that the groups be fully "collected" when it processes the objects -- for example, if the terminal operation were something like Collectors.toList() -- then a call to sorted would not be required. The critical point is that any portion of the stream that sees the groups prior to a call to sorted and prior to the end of a terminal operation (including processing during a terminal operation) may see a group that is incomplete.
For the specific example in the question, it may be somewhat less time-efficient to sort the objects before grouping them if many of them are in the same group, but if you are willing to sort the objects before grouping them, you can achieve the same functionality without performing any streaming after doing the grouping. The following is a rewrite of the first example from this answer that demonstrates this.
import java.util.Comparator;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collector;
class PreSortOrderedGrouper {
public static void main(String[] args) {
Function<Double, Long> myKeyExtractor =
(dblObj) -> Long.valueOf(
(long)(Math.floor(dblObj.doubleValue()*10.0)));
Random simpleRand = new Random(20190527L);
Consumer<Set<Double>> groupProcessor =
(grp) -> System.out.println(grp);
simpleRand.doubles(100).boxed().sorted(
Comparator.comparing(myKeyExtractor)).
collect(Collector.of(HashSet<Double>::new,
(set, dblObj) -> {
if (set.isEmpty() || myKeyExtractor.apply(set.iterator().
next()) == myKeyExtractor.apply(dblObj)) {
set.add(dblObj);
} else {
groupProcessor.accept(set);
set.clear();
set.add(dblObj);
}
},
(setOne, setTwo) -> {
throw new UnsupportedOperationException();
},
(finalSet) -> {
groupProcessor.accept(finalSet);
return Integer.valueOf(0);
}));
}
}
I can't be sure that either of these examples will feel less "clunky" to you, but if the example in your question is a pattern you use frequently, you could probably adapt one or both of these examples in ways that will suit your purposes and, aside from a few utility classes, result in no more code than you are currently using.

use Streams to generate a new list taking into account all the first list elements

I would like to map a list to another one. The decision is to be made on all the elements of the first list. Here is how it would look like, in crappy code :
import java.lang.Math;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
public class HelloWorld
{
public static void main(String[] args)
{
MatchingClass myObject = new MatchingClass();
System.out.println(myObject.getMatchingEnums(Arrays.asList("param1", "param3", "param2")));
System.out.print(myObject.getMatchingEnums(Arrays.asList("param1", "param3", "param4")));
}
}
enum TheEnums {
elem1("param1", "param2"),
elem2("param1", "param3");
String[] parameters;
TheEnums (String... parameters){
this.parameters = parameters;
}
String[] getParams (){
return parameters;
}
}
// you can add other public classes to this editor in any order
public class MatchingClass
{
public List<TheEnums> getMatchingEnums (List<String> givenParameters) {
List<TheEnums> result = new ArrayList<>();
for (TheEnums theEnum : TheEnums.values()){
if (givenParameters.containsAll(Arrays.asList(theEnum.getParams()))){
result.add (theEnum);
}
}
return result;
}
}
This could be written better, but what I want to know is if we can use the Java 8 Stream to be able to do that.
Maybe using Collectors ?
Thanks.
For example:
class MatchingClass {
public List<TheEnums> getMatchingEnums(List<String> givenParameters) {
List<TheEnums> enumsList = Arrays.asList(TheEnums.values());
return enumsList.stream()
.filter(e -> givenParameters.containsAll(Arrays.asList(e.getParams())))
.collect(Collectors.toList());
}
}

Need to loop over an array/list/whatever and *return to caller* each element -but the loop only runs once, of course

I'm obviously missing something here, as this sound basic enough but yet...
I have a collection of objects . I need to use each one of them as parameter in constructor for a new object and return each new object to the caller method, one by one.
But -if I loop over the collection obviously the loop only runs once, and only returns the 1st object.
Edit : Returning the whole collection or some new collection will not work because :
The caller method [not mine to change] runs inside a start() method of a Runnable ThingProvider, which returns a single Thing whenever a request is submitted to it. So, returning List is not possible.
Thanks :)
public List<T> loop(Collection<? extends U> coll) {
List<T> a = new ArrayList<T>();
for (U u : coll){
a.add(new T(u));
}
return a;
}
Return a custom Iterator. Assumming your new objects are of class MyObject and the constructor accepts an Object:
public Iterator<MyObject> myObjectsIterator(final Iterator<? extends Object> it) {
return new Iterator<MyObject>() {
public boolean hasNext() {
return it.hasNext();
}
public MyObject next() {
return new MyObject(it.next());
}
public void remove() {
it.remove();
}
};
}
And you would call it like this:
...
Iterator<MyObject> myIt = myObjectsIterator(myListOfObjects.iterator());
// Now you can pass myIt around as a normal object. It will remember
// which one is the next Object with which to construct a MyObject
// and will generate it on the fly
...
while (myIt.hasNext()) { // is there any MyObject remaining?
MyObject myObj = myIt.next(); // gets the next MyObject
// do something with myObj
}
...
This is a poorly worded question and I think as others have noted, just returning a new list of the objects is fine. But if you really want to process them one at a time while you're looping through it, you can use the command pattern.
public interface Command {
void execute(NewType object);
}
Now in your caller method, you can do the following:
public void doSomething() {
processList(myList, new Command() {
void execute(NewType object) {
// Do whatever you want with this object
}
});
}
And, in the method that will actually go through the list:
public void processList(Iterable<OldType> values, Command command) {
for(OldType v : values) {
NewType newType = new NewType(v);
command.execute(newType);
}
}
In java you can return only once. So if you want to get some informations from your methods either you wrap them into a "Big" Object (here a List) or you give to the method the means to put informations in your parameters.
You could have something like this :
public static void main(String... args){
List<Parameter> parameters = methodToGetParameters();
List<Result> results = generateObjectsFromList(parameters);
for(Result result : results){
handleAResult(result);
}
}
public List<Result> generateObjectsFromList(List<Parameter> parameters){
List<Result> results = new ArrayList<Result>();
for(Parameter parameter : parameters){
results.add(new Result(parameter));
}
return results;
}
Or like this :
public static void main(String... args){
List<Parameter> parameters = methodToGetParameters();
List<Result> results = new ArrayList<Result>();
generateObjectsFromList(parameters, results);
for(Result result : results){
handleAResult(result);
}
}
public void generateObjectsFromList(List<Parameter> parameters, List<Result> results){
for(Parameter parameter : parameters){
results.add(new Result(parameter));
}
}
A third way to do this would be to use fields, but it's not really good to have a lot of fields if they're not really used (or only by one method).
On the same topic :
Java Object Oriented Design Question: Returning multiple objects in java(Updated)
Using a java method to return multiple values?
Return a collection from the method and in the collection implement a custom iterator to transform the input collection to the new collection. The following code shows how to do it using the Google Guava library:
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
public class Test {
static class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}
public static Collection<Person> peopleFromNames(Collection<String> names) {
return Collections2.transform(names, new Function<String, Person>() {
public Person apply(String name) {
return new Person(name);
}});
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Brian", "Albert", "Roger");
for (Person person : peopleFromNames(names)) {
System.out.println(person.name);
}
}
}
do you mean using of delegates something like below
public class Test {
private static class Person{
private final String name;
Person(String name){
this.name = name;
}
#Override
public String toString() {
return return name;
}
}
private interface Printer {
void print(Object object);
}
public static void main(String[] args) {
final String[] names = {"one", "two", "three"};
final ArrayList<Person> people = construct(names, new Printer() {
#Override
public void print(Object object) {
System.out.println(object.toString());
}
});
}
private static ArrayList<Person> construct(String[] names, Printer printer) {
ArrayList<Person> people = new ArrayList<Person>();
for (String name : names) {
printer.print(new Person(name));
}
return people;
}
}
It's Possible.
Check these Project for Java-yield , yield4Java, infomancers
If you're using this just once in your entire code, You're better off choosing a method from the other answers.
Return a list of the new objects.

How to most elegantly iterate through parallel collections?

Say I have 2 parallel collections, eg: a list of people's names in a List<String> and a list of their age in a List<Int> in the same order (so that any given index in each collection refers to the same person).
I want to iterate through both collections at the same time and fetch the name and age of each person and do something with it. With arrays this is easily done with:
for (int i = 0; i < names.length; i++) {
do something with names[i] ....
do something with ages[i].....
}
What would be the most elegant way (in terms of readability and speed) of doing this with collections?
it1 = coll1.iterator();
it2 = coll2.iterator();
while(it1.hasNext() && it2.hasNext()) {
value1 = it1.next();
value2 = it2.next();
do something with it1 and it2;
}
This version terminates when the shorter collection is exhausted; alternatively, you could continue until the longer one is exhausted, setting value1 resp. value2 to null.
I would create a new object that encapsulates the two. Throw that in the array and iterate over that.
List<Person>
Where
public class Person {
public string name;
public int age;
}
for (int i = 0; i < names.length; ++i) {
name = names.get(i);
age = ages.get(i);
// do your stuff
}
It doesn't really matter. Your code won't get points for elegance. Just do it so that it works. And please don't bloat.
You could create an interface for it:
public interface ZipIterator<T,U> {
boolean each(T t, U u);
}
public class ZipUtils {
public static <T,U> boolean zip(Collection<T> ct, Collection<U> cu, ZipIterator<T,U> each) {
Iterator<T> it = ct.iterator();
Iterator<U> iu = cu.iterator();
while (it.hasNext() && iu.hasNext()) {
if (!each.each(it.next(), iu.next()) {
return false;
}
}
return !it.hasNext() && !iu.hasNext();
}
}
And then you have:
Collection<String> c1 = ...
Collection<Long> c2 = ...
zip(c1, c2, new ZipIterator<String, Long>() {
public boolean each(String s, Long l) {
...
}
});
I took #cletus comment and Improved it abit, And that's what I use:
public static <T,U> void zip(Collection<T> ct, Collection<U> cu, BiConsumer<T, U> consumer) {
Iterator<T> it = ct.iterator();
Iterator<U> iu = cu.iterator();
while (it.hasNext() && iu.hasNext()) {
consumer.accept(it.next(), iu.next());
}
}
Usage:
zip(list1, list2, (v1, v2) -> {
// Do stuff
});
While the submitted solutions are correct I prefer the following one because it follows the guides from effective java item 57: minimize the scope of local variables:
for (Iterator<String> i = lst1.iterator(), ii = lst2.iterator(); i.hasNext() && ii.hasNext(); ) {
String e1 = i.next();
String e2 = ii.next();
....
}
As suggested by jeef3, modeling the true domain rather than keeping separate, implicitly coupled Lists is the right way to go... when this is an option.
There are various reasons why you might not be able to adopt this approach. If so...
A. You can use a callback approach, as suggested by cletus.
B. You can still choose to expose an Iterator that exposes domain object element for each composite instance. This approach doesn't force you to keep a parallel List structure around.
private List<String> _names = ...;
private List<Integer> _ages = ...;
Iterator<Person> allPeople() {
final Iterator<String> ni = _names.iterator();
final Iterator<Integer> ai = _ages.iterator();
return new Iterator() {
public boolean hasNext() {
return ni.hasNext();
}
public Person next() {
return new Person(ni.next(), ai.next());
}
public void remove() {
ni.remove();
ai.remove();
}
};
}
C. You can use a variation of this and use a RowSet style cursor API. Let's say IPerson is an interface that describes Person. Then we can do:
public interface IPerson {
String getName();
void setName(String name);
...
}
public interface ICursor<T> {
boolean next();
T current();
}
private static class PersonCursor implements IPerson, ICursor<IPerson> {
private final List<String> _names;
...
private int _index = -1;
PersonCursor(List<String> names, List<Integer> ages) {
_names = names;
...
}
public boolean next() {
return ++_index < _names.size();
}
public Person current() {
return this;
}
public String getName() {
return _names.get(_index);
}
public void setName(String name) {
_names.set(0, name);
}
...
}
private List<String> _names = ...;
private List<Integer> _ages = ...;
Cursor<Person> allPeople() {
return new PersonCursor(_names, _ages);
}
Note that the B approach also be made to support updates to list by introducing a Domain interface, and having the Iterator return 'live' objects.
I just posted this function in this similar question (which #Nils von Barth asserts is not a duplicate ;) ), but it's equally applicable here:
public static <L,R,M> List<M> zipLists(
BiFunction<L,R,M> factory, Iterable<L> left, Iterable<R> right) {
Iterator<L> lIter = left.iterator();
Iterator<R> rIter = right.iterator();
ImmutableList.Builder<M> builder = ImmutableList.builder();
while (lIter.hasNext() && rIter.hasNext()) {
builder.add(factory.apply(lIter.next(), rIter.next()));
}
// Most of the existing solutions fail to enforce that the lists are the same
// size. That is a *classic* source of bugs. Always enforce your invariants!
checkArgument(!lIter.hasNext(),
"Unexpected extra left elements: %s", ImmutableList.copyOf(lIter));
checkArgument(!rIter.hasNext(),
"Unexpected extra right elements: %s", ImmutableList.copyOf(rIter));
return builder.build();
}
You can then provide a factory operation for the BiFunction, such as a value-type's constructor:
List<Person> people = zipLists(Person::new, names, ages);
If you really just want to iterate over them and do some operation, rather than construct a new collection, you could swap the BiFunction for a BiConsumer and have the function return void.

Categories