0

Using Java Reflection, I want to add an element to a Collection (Just for learning).

I have created a class, Collectable, which contains a variable of type Collection<?>.

Using field.set I am able set an ArrayList to it, but when I have to just an element to it getting an error. Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.util.Collection field com.company.Collector.result

Though I have achieved the above mentioned problem using

if (data instanceof Collection<?>) {
    collector.getResult().addAll((Collection<?>) data);
} else {
    collector.getResult().add(data);
}

But I want to achieve the same using reflection.

Below is the code for the same:

public class Main {

    public static void main(String[] args) {
        NotMain1 notMain1 = new NotMain1();
        notMain1.setData(Arrays.asList("abc", "bcd", "cde", "def"));
        NotMain2 notMain2 = new NotMain2();
        notMain2.setData("abcdefghi");
        try {
            setResult(notMain1);
            setResult(notMain2);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static void setResult(Object o) throws NoSuchFieldException, IllegalAccessException {
        Collector collector = new Collector();
        Class clazz = o.getClass();
        Field data = clazz.getDeclaredField("data");
        data.setAccessible(Boolean.TRUE);
        Object o1 = data.get(o);
        Class collectorClass = collector.getClass();
        Field result = collectorClass.getDeclaredField("result");
        result.setAccessible(true);
        result.set(collector, o1);
        System.out.println(collector.getResult());
    }
}

class Collector {
    private Collection<?> result;

    public Collection<?> getResult() {
        return result;
    }

    public void setResult(Collection<?> result) {
        this.result = result;
    }
}

class NotMain1 {
    private List<String> data;

    public List<String> getData() {
        return data;
    }

    public void setData(List<String> data) {
        this.data = data;
    }
}

class NotMain2 {
    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}
Thakur Sahab
  • 45
  • 11
  • What you have tried is nowhere near what you want to do. Where is the `instanceof` check? You also did not call `addAll` or `add`. – Sweeper Jun 20 '23 at 07:59
  • Also, it is not typesafe to add anything (except null) into a `Collection>`. – Sweeper Jun 20 '23 at 08:03
  • @Sweeper, with reflection, calling addAll or add is not possible to the field. – Thakur Sahab Jun 20 '23 at 08:03
  • Try declaring _o1_ as a _Collection>_, and cast the assignment also, to _Collection>_. _Collection> o1 = (Collection>) data.get(o);_ – Reilas Jun 20 '23 at 08:03
  • @Reilas. Code is not giving any error when data.get is an ArrayList and is working as expected. When I have to add an single element i.e. data field from NotMain2 class into the result.set then I am getting the error. I am searching a solution for an element adding to the result field. – Thakur Sahab Jun 20 '23 at 08:12
  • @ThakurSahab, because the _result_ field is a _Collection_, which is not compatible with a _String_ object. I believe you're looking to add the value to the _Collection_, and not simply assign the _result_ a value of an object. – Reilas Jun 20 '23 at 08:16
  • @ThakurSahab, so in this case, cast _result_ to a collection, instantiate it with a new instance, and then access the _add_ method. – Reilas Jun 20 '23 at 08:18

1 Answers1

1

First, I should note that it is not typesafe to add anything (except null) into a Collection<?>.

If you still want to do this, you'd either need raw types or call add/addAll with reflection too.

In either case, you need to first get the result field, and then call add/addAll on it with an instanceof check, just like in the non-reflection case. You should also setResult on the collector, so that you won't get a NullPointerException when you call add/addAll.

Collector collector = new Collector();

// !
collector.setResult(new ArrayList<>());

Class<?> clazz = o.getClass();
Field dataField = clazz.getDeclaredField("data");
dataField.setAccessible(Boolean.TRUE);
Object data = dataField.get(o);
Class<?> collectorClass = collector.getClass();
Field resultField = collectorClass.getDeclaredField("result");
resultField.setAccessible(true);

// raw types here...
Collection result = (Collection)resultField.get(collector);
if (data instanceof Collection<?>) {
    result.addAll((Collection) data);
} else {
    result.add(data);
}
System.out.println(collector.getResult());

Or you can call add/addAll with reflection too:

// note that the checked exceptions has changed
public static void setResult(Object o) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    Collector collector = new Collector();
    collector.setResult(new ArrayList<>());
    Class<?> clazz = o.getClass();
    Field dataField = clazz.getDeclaredField("data");
    dataField.setAccessible(Boolean.TRUE);
    Object data = dataField.get(o);
    Class<?> collectorClass = collector.getClass();
    Field resultField = collectorClass.getDeclaredField("result");
    resultField.setAccessible(true);
    Object result = resultField.get(collector);

    if (data instanceof Collection<?>) {
        // call method by reflection too
        result.getClass().getMethod("addAll", Collection.class).invoke(result, data);
    } else {
        // call method by reflection too
        result.getClass().getMethod("add", Object.class).invoke(result, data);
    }
    System.out.println(collector.getResult());
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313