0

I have a list which contains objects of different classes say A, B, C, D, E, F, G. Each of the objects(A, B, C, D, E, F, G) have an instance variable "name" in it. I want to iterate over the list and retrieve values stored in each of those object's name variable.

I tried below code

List<String> str = new ArrayList<String>();
for(Object obj: list_containing_different_objects) {
      str.add(obj.name) or  str.add(obj.getName());
}

But obj.name will give error as I don't know the exact index(as it can be different each time) in the list_containing_different_objects where A,B,C,D,E,F are stored. Also If I know the index, I couldn't fathom any solution for it.Can someone help me out of it?

user2755407
  • 348
  • 1
  • 3
  • 15

3 Answers3

2

It depends on whether the classes A, B, C, D, E, F and G have a common base class or interface.

Common base type

If not, you could, for example, create an interface like this:

interface Nameable {
    String getName();
}

and then make each of the classes implement this interface. Then you can easily just call the getName() method on each of the objects:

for (Nameable nameable : listContainingDifferentObjects) {
    str.add(nameable.getName());
}

instanceof

Otherwise, if the classes will not or cannot implement a common interface or extend a base class, then the classes are unrelated, despite having a field with the same name.

In that case, you end up having to check each type, cast it and use the field1:

for (Nameable nameable : listContainingDifferentObjects) {
    if (obj instanceof A) {
        str.add(((A) obj).name);
    }
    else if (obj instanceof B) {
        str.add(((B) obj).name);
    }
    // et cetera
}

If your classes don't implement a common type, but still have a field with the same name, I would rethink the design.

Reflection

vsfDawg came up with using reflection to dynamically invoke the getName() method or access the name field. While one could indeed make it work, you should avoid to use it if that's possible, and only use it if there's no other way. ➤ More details


1 Since version 16, Java supports pattern matching, which simplifies the ceremony of checking type, casting and using the value. For instance:

for (Object obj : listContainingDifferentObjects) {
    if (obj instanceof A a) {
        str.add(a.name());
    }
    else if (obj instanceof B b) {
        str.add(b.name());
    }
    // et cetera
}

See this article for more details.

MC Emperor
  • 22,334
  • 15
  • 80
  • 130
1

Use Reflection

Assuming the classes do not share a common base class or interface which contains the method you may use Reflection to invoke the method. There is a lot of error handling to make it work properly but the following example waves that away and forces the caller to deal with it.

public String getName(Object named)
throws NoSuchMethodException, 
       SecurityException,
       IllegalAccessException,
       IllegalArgumentException,
       InvocationTargetException
{
  Method method = named.getClass().getMethod("getName");
  return (String) method.invoke(named);
}

The following iterates a List of objects and invokes the above method. Since the Exceptions are percolating up, this method has to handle them. You could just catch Exception here but I left the full list for completeness.

public void printNames(List<Object> namedObjects) {
  for (Object obj : namedObjects) {
    try  {
      System.out.println(getName(obj));
    } catch (NoSuchMethodException |
             SecurityException |
             IllegalAccessException |
             IllegalArgumentException |
             InvocationTargetException e) {
      System.err.println("Failed to invoke getName on object - skipping");
    }
  }
}
vsfDawg
  • 1,425
  • 1
  • 9
  • 12
0

I like 'MC Emperor's answer about common base class, it's the way to go. However, if you do not have that common class and cannot change the code. Reflection can be used to achieve what is needed.

The code below does not even need a getter (getName()), it just needs a 'name' field.

class A {
    public A(String aName) {
        name = aName;
    }

    public String name;
}

class B {
    public B(String aName) {
        name = aName;
    }

    public String name;
}

class C {
    public C(String aName) {
        name = aName;
    }

    public String name;
}

@Test
public void testClasses() {
    List<String> str = new ArrayList<>();
    List<? extends Object> list_containing_different_objects = Lists.list(new A("a"), new B("b"),
            new C("c"), new A("aa"));
    
    for (Object commonObj : list_containing_different_objects) {
        try {
            Field field = commonObj.getClass().getField("name");
            String nameValue = (String) field.get(commonObj);

            str.add(nameValue);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    // print the results
    str.forEach(System.out::println);
}

The result of this test execution looks the following:

 a
 b
 c
 aa

I hope this was helpful.

Igor Kanshyn
  • 867
  • 6
  • 13