简体   繁体   中英

Get type of elements in List

I have two different Lists:

List<MyFirstClass> list1;
List<MySecondClass> list2;

Now I need a method taking a List as input. The stuff I do in this method is almost the same for both lists and just slightly different. Since I can't just overload them, I did this:

private String myMethod(List<?> myList) {...}

But now I need to check what type ? is, to make the slighly differences in the method in relation to which list-element-type is given.

How do I do this? Or is there any better way to do this?

Thanks!

Edit The two methods are really the same except one method call list1.method1() and list2.method2() . That is why I don't want to make two different methods.

The literal solution is to use instanceof to differentiate between the types of objects.

private void myMethod(List<?> list) {
    for(Object o : list) {
        if(o instanceof MyFirstClass) {
            MyfirstClass p = (MyFirstClass)o;
            p.myFirstMethod();
        } else if(o instanceof MySecondClass) {
            MySecondClass p = (MyFirstClass)o;
            p.mySecondMethod();
        }
    }
}

And if the design constraints of your project are immutable, this is your best solution.

But a better solution uses Inheritance

This problem does demonstrate that you almost certainly have a flaw in your overall design. If you don't/can't know in advance whether you'll get a List<MyFirstClass> or List<MySecondClass> , then that's pretty strong evidence you have a poorly defined problem domain. It's much better to enforce the necessary constraints by making them implement an interface that instructs how behavior should differ in this situation.

public interface MyInterface {
    void invokeMyMethod();
}

public class MyFirstClass implements MyInterface {
    public void invokeMyMethod() {myFirstMethod();}
    /*...*/
}
public class MySecondClass implements MyInterface {
    public void invokeMyMethod() {mySecondMethod();}
    /*...*/
}

//In some other class...

private void myMethod(List<MyInterface> list) {
    for(MyInterface o : list) {
        o.invokeMyMethod();
    }
}

myMethod could also be rewritten in this scenario where you're not sure whether you'll get List<MyFirstClass> , List<MySecondClass> , or List<MyInterface> :

private <T extends MyInterface> void myMethod(List<T> list) {
    for(MyInterface o : list) {
        o.invokeMyMethod();
    }
}

You might be able to use inheritance to solve your problem. To do so, make both classes inherit from a common base class (or interface). Then make a List to store elements of the sub classes. Store the special logic that is specific to each type within the sub classes. Here is an example:

class BaseClass {
  public void foo() {}
}

class ClassA extends BaseClass {
  public void foo() { /* ClassA logic here */ }
}

class ClassB extends BaseClass {
  public void foo() { /* ClassB logic here */ }
}

List<BaseClass> list = new ArrayList<BaseClass>();
list.add(new ClassA());
list.add(new ClassB());
for(BaseClass element : list) {
  element.foo();
}

You can't adjust your method directly based off of what the type in List<?> is. There are other ways of getting what you want, though. Here's one way (these names are going to be awful because I don't have a lot of context for what you're trying to do):

interface MethodCaller<T> {
  void callMethod(T target);
}

class Class1Caller implements MethodCaller<MyFirstClass> {
  @Override
  public void callMethod(MyFirstClass target) {
    target.method1();
  }
}

class Class1Caller implements MethodCaller<MySecondClass> {
  @Override
  public void callMethod(MySecondClass target) {
    target.method2();
  }
}

Then change your method to take a MethodCaller of the same type as the list, like so:

private <T> String myMethod(List<T> myList, MethodCaller<T> methodCaller) {
   // wherever you'd call your method, instead use methodCaller.callMethod() instead
}

Now to call your method you'd use myMethod(list1, new Class1Caller()) or myMethod(list2, new Class2Caller()) .

You can use a lambda to extract the distinct call and pass it as an argument to your method:

private <T> myMethod(List<? extends T> myList, Consumer<T> method) {
    for (T item : myList) {
        // some stuff
        method.accept(item);
    }
}

Then simply call it like this:

myMethod(list1, MyFirstClass::method1);
myMethod(list2, MySecondClass::method2);

This assumes you're calling 0-arg, void methods. If that's not the case, you can use a lambda instead, and/or use one of the other standard functional interfaces or create your own.

Note that this is essentially a more concise version of @jacobm's solution .

While you could use .getClass() , instanceof , or a Class parameter, those tend to result in ugly, fragile code. Please don't go there if you can help it.

Method #1

The best approach is to use an abstract class or interface to put the required logic into the list items. The list objects would directly perform whatever operation is required.

The original post seems to indicate that this isn't possible, so let's go on to method 2.

Method #2

Use a function parameter.

public <T> String myMethod(List<T> myList, Function<T, String> func)

The function will act on each item in the list, and return something your method can handle.

Method #3

Use wrapper classes. You can wrap each list item inside classes that know how to handle them. The wrapper classes would all inherit from a single base class, allowing your method to look like this:

public String myMethod(List<Wrapper> myList)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM