Reputation: 425
sorry, but I couldn't find a solution to this:
I got this coalesce method which has been working fine for two single values, but I would like to extend it to iterate through any object and return a new object after applying the coalesce in all fields. I wanted to do it with generics, so it work with any given object type.
This is the coalesce method:
public static <T> T coalesce(T one, T two) {
return one != null ? one : two;
}
The idea is to improve to method like this:
public static <T,Z> T coalesceAll(T one, T two) {
T finalObject;
for (Z field : getFields(one.getClass())) {
finalObject.field = coalesce(one.field, two.field);
}
return finalObject;
}
Any idea on how to implement this in Java? Thanks in advance!
Upvotes: 1
Views: 279
Reputation: 3025
I like more the answer by Smutje. But I also like reflection (a lot!).
EDIT: As @fluffy pointed out, in the comments, my old code failed for inherited fields
; also I observed that it will not work with primitive
value (like int
or double
).
So I decided to recreate it!, now works with primitive
and class inheritance
.
The coalesce
code
private static void doCoalesce(Field field, Object src, Object target) throws ReflectiveOperationException {
// remove "protection" (for private & final fields)
boolean isAccessible = field.isAccessible();
field.setAccessible(true);
// Copy value if null or default primitive value (0 - zero)
if (isNull(field.getType(), field.get(target))) {
field.set(target, field.get(src));
}
// restore "protection"
field.setAccessible(isAccessible);
}
private static void doCoalesceByClass(Object src, Object target, Class cls) throws ReflectiveOperationException {
for (Field field : cls.getDeclaredFields()) {
doCoalesce(field, src, target);
}
}
private static void doCoalesce(Object src, Object target) throws ReflectiveOperationException {
Class cls = target.getClass();
while (cls != Object.class) {
doCoalesceByClass(src, target, cls);
cls = cls.getSuperclass();
}
}
public static Object coalesce(Object... objects) throws ReflectiveOperationException {
switch (objects.length) {
case 0: return null;
case 1: return objects[0];
default: throwExceptionIfDifferentClass(objects); break;
}
Object finalObject = instantiateDefaultClass(objects[0].getClass());
for (Object object : objects) {
doCoalesce(object, finalObject);
}
return finalObject;
}
The auxiliary
code
private static Object instantiateDefaultClass(Class cls) throws ReflectiveOperationException {
final Constructor constructor = cls.getConstructors()[0];
final List<Object> params = new ArrayList<>();
for (Class<?> pType : constructor.getParameterTypes()) {
params.add((pType.isPrimitive()) ? 0 : null);
}
return constructor.newInstance(params.toArray());
}
private static void throwExceptionIfDifferentClass(Object... objects) {
Class cls = objects[0].getClass();
for (Object obj : objects) {
if (obj.getClass() != cls) {
throw new IllegalArgumentException("Objects MUST be of the same class");
}
}
}
private static boolean isNull(Class cls, Object value) {
if (!cls.isPrimitive()) {
return value == null;
}
// Primitive types
if (cls == Boolean.TYPE) return (boolean) value;
if (cls == Byte.TYPE) return (byte) value == 0;
if (cls == Character.TYPE) return (char) value == 0;
if (cls == Short.TYPE) return (short) value == 0;
if (cls == Long.TYPE) return (long) value == 0;
if (cls == Double.TYPE) return (double) value == 0;
if (cls == Float.TYPE) return (float) value == 0;
return false;
}
The test
code
public static class Book
{
public final String title;
public final String author;
public final long isbn;
public Book(String title, String author, long isbn) {
this.title = title;
this.author = author;
this.isbn = isbn;
}
}
public static class AudioBook extends Book
{
public final String url;
public AudioBook(String title, String author, long isbn, String url) {
super(title, author, isbn);
this.url = url;
}
}
public static void main(String[] args) throws ReflectiveOperationException {
AudioBook b1 = new AudioBook(null, "J. R. R. Tolkien", 9780261103306L, null);
AudioBook b2 = new AudioBook("The Hobbit", null, 0, "www.thehobbit.com");
AudioBook finalBook = (AudioBook) coalesce(b1, b2);
System.out.println(finalBook.author + ", " + finalBook.title + ", " + finalBook.isbn + ", " + finalBook.url);
}
Upvotes: 1
Reputation: 18163
I would do this by delegating the coalescing to the actual classes. Making this generic via reflection might be possible but way more complicated:
public interface Coalesce<T> {
T coalesce(T other);
default <V> V coalesce(V one, V two) {
return one != null ? one : two;
}
}
public class Person implements Coalesce<Person> {
private final Integer id;
private final String name;
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Override
public Person coalesce(Person other) {
final int id = coalesce(this.id, other.id);
final String name = coalesce(this.name, other.name);
return new Person(id, name);
}
}
public class Book implements Coalesce<Book> {
private final String title;
private final String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
@Override
public Book coalesce(Book other) {
final String title = coalesce(this.title, other.title);
final String author = coalesce(this.author, other.author);
return new Book(title, author);
}
}
public class Main {
public static void main(String[] args) {
final Person personA = new Person(1, null);
final Person personB = new Person(2, "Bob");
final Person coalescedPerson = personA.coalesce(personB);
System.out.println(coalescedPerson.getId()); // 1
System.out.println(coalescedPerson.getName()); // Bob
final Book bookA = new Book(null, "J. R. R. Tolkien");
final Book bookB = new Book("The Hobbit", null);
final Book coalescedBook = bookA.coalesce(bookB);
System.out.println(coalescedBook.getTitle()); // The Hobbit
System.out.println(coalescedBook.getAuthor()); // J. R. R. Tolkien
}
}
Upvotes: 3