I have two methods like the following
private List<Long> getIds(String name, List<Cat> cats) {
List<Long> catIds = new ArrayList<>();
for (Cat cat : cats) {
if (cat.getName().equals(name)) catIds.add(cat.getId());
}
return catIds;
}
private List<Long> getIds(String name, List<Dog> dogs) {
List<Long> dogIds = new ArrayList<>();
for (Dog dog : dogs) {
if (dog.getName().equals(name)) dogIds.add(dog.getId());
}
return dogIds;
}
My cat and dog class are as follows
public class Cat {
String name;
Long id;
// additional variables
// getters and setters
}
public class Dog {
String name;
Long id;
// additional variables
// getters and setters
}
Just to avoid redundancy, I wanted to convert the above two methods to a single generic method.
I tried the following
private List<Long> getIds(String name, List<T> objects) {
List<Long> ids = new ArrayList<>();
for (T object : objects) {
if (object.getName().equals(name)) ids.add(object.getId());
}
return ids;
}
But it does not work out as it complains that the generic T does not have getName
or getId
Here Cat
and Dog
are in-built java classes. As a result I CANNOT perform inheritance and provide a super class Animal
for them with name
and id
as data variables.
Is there any way I could accomplish merging the two above methods without implementing inheritance?
In java, if a method of a class is declared with the “protected” keyword, then it can be accessed by any other class of the same package. A method declared with the protected keyword can't be accessed out of the package directly. However, it can be accessed outside the package with the help of inheritance.
Object. getClass() If an instance of an object is available, then the simplest way to get its Class is to invoke Object. getClass() .
Another possibility: If you are using Java 8, you could pass in the getter methods for name and ID as additional parameters into the method. This is type-safe and does neither need reflection nor modifications to the original classes.
private <T> List<Long> getIds(String name, List<T> objects, Function<T, String> getName, Function<T, Long> getId) {
List<Long> ids = new ArrayList<>();
for (T object : objects) {
if (getName.apply(object).equals(name)) {
ids.add(getId.apply(object));
}
}
return ids;
}
Or shorter, using streams:
private <T> List<Long> getIds(String name, List<T> objects, Function<T, String> getName, Function<T, Long> getId) {
return objects.stream().filter(o -> getName.apply(o).equals(name))
.map(getId).collect(Collectors.toList());
}
Usage:
List<Long> ids = getIds("some name", listOfCats, Cat::getName, Cat::getId);
Of course, while this way the body of the method is the same for all the cases, you have to call it in different ways, depending on the input. For this, you could provide an entry method, calling the other method in the appropriate way or raising an Exception in case the type is wrong.
private List<Long> getIds(String name, List<?> objects) {
if (objects.size() == 0) {
return Collections.emptyList();
}
if (objects.get(0) instanceof Cat) {
return getIds(name, (List<Cat>) objects, Cat::getName, Cat::getId);
}
if (objects.get(0) instanceof Dog) {
...
}
throw new IllegalArgumentException("List containing unsupported type!");
}
Ok, I've got that you cannot use any type of inheritance. Here is a short solution with Reflection API then:
private static List<Long> getIds(String name, List<?> objects) {
List<Long> ids = new ArrayList<>();
for (Object object : objects) {
try {
Method getName = null;
Method getId = null;
for (Method method : object.getClass().getMethods()) {
if (method.getName().equals("getName") && method.getReturnType().equals(String.class) && method.getParameterTypes().length == 0) {
getName = method;
}
if (method.getName().equals("getId") && method.getReturnType().equals(Long.class) && method.getParameterTypes().length == 0) {
getId = method;
}
if (getName != null && getId != null && getName.invoke(object).equals(name)) {
ids.add((Long) getId.invoke(object));
break;
}
}
} catch (Exception e) {
System.out.println(e);
}
}
return ids;
}
You need to define a common interface for Cat
and Dog
, for example Animal
.
Than you can pass a List<Animal>
as parameter.
Can you just make an interface for both Dog and Cat?
interface animal {
...
Long getId();
}
public class Cat implements Animal
public class Dog implements Animal
then your method would become
private List<Long> getIds(String name, List<Animal> animals) {
List<Long> ids = new ArrayList<>();
for (Animal animal : animals) {
ids.add(animal.getId());
}
return ids;
}
Regarding the in-built class you've mentioned, you can wrap them in another class and implements the interface(or superclass) describing the desired behavior.
I once had to provide same interface for log4j
and logback
event Object, they two are quite different definitely.
here the wrapper goes..
public abstract class EventWrapper {
public abstract String getFormattedMessage();
}
And the child class which actually holds the in-built class
public class Log4jEvent extends EventWrapper {
LoggingEvent event;
public Log4jEvent(LoggingEvent event) {
this.event = event;
}
@Override
public String getFormattedMessage() {
if (event.getMessage() != null) {
return event.getMessage().toString();
} else {
return null;
}
}
}
LogbackEvent class is very similar to Log4jEvent class, and in other parts of the code, there are many method which receives argument type of EventWrapper. Maybe this case would help you develop your solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With