Reputation: 752
I'm working on a homework assignment for University which does not allow me to change the properties of the classes. I came up with the following code for a search method, but I can't help but notice the duplicate code. I am required to build two search methods. One that searches an ArrayList<Person>
and one that searches an ArrayList<Module
. Both Person
and Module
have a String name
property.
public static <T extends Person> ArrayList<T> findPerson(List<T> list, String query) {
ArrayList<T> matching = new ArrayList<>();
list.forEach(s -> {
if (s.name.contains(query)) matching.add(s);
});
return matching;
}
public static <T extends Module> ArrayList<T> findModule(List<T> list, String query) {
ArrayList<T> matching = new ArrayList<>();
list.forEach(s -> {
if (s.name.contains(query)) matching.add(s);
});
return matching;
}
Is it possible to combine these methods without changing the class structure? I tried something along these lines but it still doesn't seem to work: (different type argument in method signature)
public static <T extends Person, Module> ArrayList<T> findModule(List<T> list, String query) {
ArrayList<T> matching = new ArrayList<>();
list.forEach(s -> {
if (s.name.contains(query)) matching.add(s);
});
return matching;
}
Edit: Here are the classes:
public abstract class Person extends TableItem {
public String name, famName, street, zip, city;
public String[] data = new String[7];
...
}
public class Module extends TableItem {
private Manager manager;
public String[] data = new String[5];
public String name, nr, profID, semester, part;
public ArrayList<String> participants;
...
}
public abstract class TableItem {
public abstract String getString();
public abstract void updateData();
}
Upvotes: 3
Views: 77
Reputation: 22158
Ideally you make both your classes implement the same interface, like NamedItem
which would have a function getName()
, it would be much simpler, because you make your function take List<? extends NamedItem>
. If this is not possible, you will have to provide a Function
to determine what String
property to match.
public static <T> List<T> find(List<T> list, String query, Function<T, String> keyExtractor) {
return list
.stream()
.filter(s -> keyExtractor.apply(s).contains(query))
.collect(Collectors.toList());
}
Then you can use it like:
List<Person> matchingPersons = find(allPersons, query1, Person::getName);
List<Module> matchingModules = find(allModules, query1, Module::getName);
You can use it with any property, not just name. You just pass the function that extracts the String
you want to match as the 3rd parameter.
Upvotes: 3
Reputation: 159135
The only way to specify more than one base type for a generic type is with the &
syntax:
<T extends Person & Module>
But in that case T
must extend both at the same time, so it is not what you're looking for.
To combine your two methods, the types must share a common interface, e.g.
public interface Named {
String getName();
}
Now, if both Person
and Module
extends/implements the Named
interface, your method can be:
public static <T extends Named> ArrayList<T> findNamed(List<T> list, String query) {
return list.stream().filter(s -> s.getName().contains(query))
.collect(Collectors.toCollection(ArrayList::new));
}
Upvotes: 2