Lucas Pedroso Santos
Lucas Pedroso Santos

Reputation: 425

Java - From two equal objects, return new object coalescing all fields

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

Answers (2)

D.Kastier
D.Kastier

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

Smutje
Smutje

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:

Coalesce.java

public interface Coalesce<T> {

    T coalesce(T other);

    default <V> V coalesce(V one, V two) {
        return one != null ? one : two;
    }
}

Person.java

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);
    }
}

Book.java

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);
    }
}

Main.java

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

Related Questions