ixx
ixx

Reputation: 32269

Overloading / generics in Java

I want to run certain tests in Lists. The Lists can contain entirely different classes.

I have one method to check the consistency of the list - not null, not empty, no more than x elements. This is common to all the lists. Then I want to test each of the objects, using overloading.

The idea would be something like:

public static <T> void check(List<T> list) {

    //do general checks

    for (T element : list) {
        check(element);
    }
}

and then

public static void check(SomeType element) {...}

public static void check(SomeOtherType element) {...}

But I also had to add a method like this:

public static void check(T element) {...}

And this was called at runtime - not my other methods with the specific classes. Although the class was exactly the same. I'm evidently missing some generics understanding.

Now if I don't use the general method at all and try to solve it this way:

    public static void check(List<SomeType> list) {...}

    public static void check(List<SomeOtherType> list) {...}

Compiler error - "Method check(List) has the same erasure check(List) as another method..."

So is there any elegant solution for this? I could just use different method names but would like to know how it's possible without that.

Thanks!

Upvotes: 10

Views: 382

Answers (5)

Miserable Variable
Miserable Variable

Reputation: 28761

Since the type of the variable is lost in check(List<T> list) you have two options:

1. Do different things by checking runtime type

check(T element) {
  if (element.getClass().equals(SomeType.class)) {
    check((SomeType) element);
  } elseif (element.getClass().equals(SomeOtherType.class)) {
    check((SomeOtherType) element);
  }

This can be made a little more sophisticated, for example by wrapping each check in a Callable and using a Map<Class, Callable>

This is similar to visitor pattern.

2. Calling a virtual method on the element to be checked itself

If the checking logic can be pushed to the object to be checked itself (this is not necessarily a bad thing) then you don't need to check types:

interface Checkable { void check(); }
class SomeType implements Checkable { .... }
class SomeOtherType implements Checkable { .... }

Then:

public static <T extends Checkable> void check(List<T> list) {
    for (T element : list) {
        element.check();
    }
}

These are the only two options, any implementation has to be a variation on one of these

Upvotes: 1

jacobm
jacobm

Reputation: 14035

You can use instanceof to dispatch:

public static <T> void check(List<T> list) {
  for (T element : list) {
    check(element);
  }
}

public static void check(T t) {
  if (t instanceof SomeType) {
    SomeType someType = (SomeType) t;
    // code for SomeType ...
  } else if (t instanceof OtherType) {
    OtherType otherType = (OtherType) t;
    // code for OtherType ...
  } else {
    // we got a type that we don't have a method for
  }
} 

Upvotes: 4

Cyrille Ka
Cyrille Ka

Reputation: 15533

With generics, the type parameter is actually erased during compilation, and the list object don't know anything about the static type of the object it contains. Since it doesn't know it, it can not use overloading to call methods with different parameters, because Java doesn't support multiple dispatch.

You have then three choices:

  • Make your objects implement a Checked interface with a check method that does the check logic. Downside is that the check logic is now dispersed in several places and it is not practical if you have objects of classes you don't have control of.
  • Use instanceof to call explicitly the check methods according to the dynamic type of the object. Downside is you potentially end up with a big if/else block a bit harder to maintain.
  • Implement the visitor pattern. Downside is that you have to change the object classes too, but the check logic stay in a single place.

Upvotes: 2

JB Nizet
JB Nizet

Reputation: 692121

The problem should be solved by the caller. When it instanciate your class with a concrete type for T, it should also pass an instance of Checker<T> with the same concrete type:

public class SomeClass<T> {
    private List<T> list;
    private Checker<T> checker;

    public SomeClass(Checker<T> checker) {
        this.checker = checker;
    }

    public void check() {
        checker.check(list);
    }
}

public interface Checker<T> {
    public void check(List<T> list);
}

...

SomeClass<Foo> someClass = new SomeClass<Foo>(new Checker<Foo>() {
    @Override 
    public void check(List<Foo> list) {
        // do whatever you want here
    }
});

Upvotes: 4

Mark Peters
Mark Peters

Reputation: 81154

This isn't something about generics that you're missing. Java does not have double dispatch. The call to check must be resolved at compile-time, and check(T) is the only match since the compiler can't tell if T is SomeType or SomeOtherType in a given scenario. It needs to choose one method to call that will work for all possible Ts.

This is sometimes solved using the visitor pattern.

Upvotes: 10

Related Questions