Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

With generics, how can I define a class that allows any object to be passed in and doesn't give warnings?

Tags:

java

generics

I've got an interface defined like this:

public interface AdditionalProcessing<Pre, Post> {
    public void postProcess(Pre pre, Post post);
}

And I've decided I want to make a NoAdditionalProcessing class that extends it. It's a no-op that won't do anything for its implementation. Here's my implementation that compiles:

public class NoAdditionalProcessing implements AdditionalProcessing {

    @Override
    public void postProcess(Object o, Object o2) {

    }
}

The problem is, when I pass this into a method signature like this:

public <I, O> O map(I source, Class<O> destinationClass, AdditionalProcessing<I, O> additionalProcessing) throws MappingException 

It gives me a warning on the method call that say:

Unchecked assignment: ...NoAdditionalProcessing to ...AdditionalProcessing

Those ...'s are the package names which I've excluded. Is there a way to get rid of this warning? I basically want to define NoAdditionalProcessing so it says, "I'll accept anything, it's irrelevant and that's ok".


I've tried this:

public class NoAdditionalProcessing implements AdditionalProcessing<Object, Object>

But that gives me an error because it is requiring that I pass in Object. I've tried

public class NoAdditionalProcessing implements AdditionalProcessing<? extends Object, ? extends Object>

But that doesn't compile.

like image 676
Daniel Kaplan Avatar asked Dec 16 '22 09:12

Daniel Kaplan


2 Answers

Change your class declaration to give values to the type parameters:

public class NoAdditionalProcessing 
    implements AdditionalProcessing<Object, Object>

Then, change your method signature to have a super wildcard so that you can pass in NoAdditionalProcessing for any destination class:

public <I, O> O map(I source, Class<O> destinationClass, 
    AdditionalProcessing<? super I, ? super O> additionalProcessing)
like image 96
Russell Zahniser Avatar answered Dec 17 '22 22:12

Russell Zahniser


This is the long version of why I consider @RussellZahniser solution superior to @assylias solution (but I also add in another twist):

Whenever you use generics, consider whether you want to allow subtypes or supertypes.

While it may seem like the compiler being overly pedantic at first, there is an odd inversion of compability in generics. Roughly said: A bag of apples is not a bag of fruits. In a bag of fruits, you may place bananas, but not in a bag of apples.

So whenever using generics, consider whether you want to allow subtypes (for consumption) or supertypes (for production).

Assuming that your AdditionalProcessing is supposed to be a converter, you may actually want to write it as AdditionalProcessing<IN, OUT>.

So when is an AdditionalProcessing compatible to another?

Assuming you need a converter of type AdditionalProcessing<Fruit, Fruit>. It must accept any kind of fruit, but it also does not matter which kind of fruit it returns. I.e. it may convert apples to oranges. However, it must not be a converter of apples to stones, because then you can't feed oranges, and you don't get fruit out.

So for the converter case, your map function should actually look like this:

public <I, O> O map(I source, Class<O> destinationClass, 
    AdditionalProcessing<? super I, ? extends O> additionalProcessing)

Since:

? super I: "accepts at least fruits (but maybe something else, too!)".

? extends O: "returns some kind of fruit (but maybe only apples)".

I found the fruit example to be really really valueable.

Your NoAdditionalProcessing would then look like this:

public class NoAdditionalProcessing<TYPE>
  implements AdditionalProcessing<TYPE, TYPE> {
    @Override
    public void postProcess(Object o, Object o2) {
         // Actually I would have expected something like "return o1;"
    }
}

Which is essentially a fusion of the two answers I credited in the first line. However, it is hard to tell from your question whether you want super or extends. Which is why I want to highlight that both may be reasonable, and you may want to have both.

Usually, it will be super for input types (i.e. consumed data), and extends for output types (i.e. produced types). (It gets more messy with nested generics.) I.e. for a ComapareTwoTypes<FIRST, SECOND>, you will want to allow a comparator that accepts super types for both.

Some examples as patterns:

  • Validator<? super I, ? super O>

    It is acceptable if it is performing validation on a super type only. It consumes both types, for comparison, and produces nothing.

  • Converter<? super I, ? extends O>

    It may chose to accept super types, and it may restrict its return values to some super type. It consumes the first type, produces the second.

It's much harder to come up with an example where both are extends, because the Java compiler won't be able to infer the type reliably without additional hints. This mostly happens when I and O themselves are generics, e.g. collections of another type, because of contravariance of collections.

Wikipedia has an article on covariance and contravariance of types that may help to shine some light on the situations arising here (unfortunately, the article is a bit theoretical). But collections are the most obvious case. I think it was some C++ that had this "a bag of apples is not a bag of fruit: you are not allowed to put a banana in it" example, although the primary one involves putting submarines in a car parking lot (if a parking-lot-of-cars were a kind-of parking-lot-of-vehicles; but they aren't).

like image 38
Has QUIT--Anony-Mousse Avatar answered Dec 17 '22 22:12

Has QUIT--Anony-Mousse