Context
I want to create color maps for my JavaFX application in order to display a grid with different colours according to their value. Two types are defined: DiscreteColorMap which uses integer keys and ContinuousColorMap with double keys. Both must implement the interface ColorMap, so that it can be called like that:
ColorMap palette1 = new DiscreteColorMap();
ColorMap palette2 = new ContinuousColorMap();
Problem
As both classes rely on the same interface, I specify a template (public interface ColorMap<T>) in order to adapt to each of them:
ColorMap<Integer> palette1 = new DiscreteColorMap();
ColorMap<Double> palette2 = new ContinuousColorMap();
I want the simplest syntax for color maps so I need to get rid of the <Integer> and <Double> strings. What is the most elegant way to do that?
Source
The complete code can be found in this GitHub project.
EDIT
My English is not perfect ^^ I used "to get rid of" but this is not clear: when I instantiate my color maps, I want to make <Integer> and <Double> disappear, so I could write ColorMap palette... instead of ColorMap<Integer> palette....
TL/DR:
There are three ways to remove the type parameters from the type of the reference variable:
var. This is simply a syntactic shorthand, andvar palette = new DiscreteColorMap();is identical at both runtime and compile time toDiscreteColorMap palette = new DiscreteColorMap();. This is covered in another answer.ColorMap<?> palette = new DiscreteColorMap();. This tells the compiler to "forget" the type being used as the parametrized type. This means you won't be able to invoke any methods expecting parameters of typeT, because the compiler can't check the type is correct. This is covered in detail below.ColorMap palette = new DiscreteColorMap();. This tells the compiler to ignore the types of the parameters (it effectively treatsTasObject). Any errors because of incompatible types are thrown at runtime and not caught at compile time, and for this reason this approach is strongly not recommended.The rest of this answer describes in detail the second option, using wildcards.
The purpose of Java generics is to allow the flexibility to create classes which can work with any type of object (or a specific "range" of types of object), while preserving the ability of the compiler to perform compile-time type checking. The canonical example of this is the Collections API that is part of the
java.utilpackage.In your case, you've defined a
ColorMapinterface which is generic, and I'm guessing from the name of the interface that you are mapping values of the parametrized typeTto colors. So you probably have something like this:And then some implementations. I'm going to use very basic implementations which are not production-level, just to demonstrate the idea. There is one for values of
Integertype:and one of type
Double:Note that as far as the compiler is concerned,
ColorMap<Integer>andColorMap<Double>are distinct types.As far as I can tell, your question seems to be "Can I create a
DiscreteColorMapandContinuousColorMapand assign them to references of the same type". The answer is "yes", and you can do this in a non-trivial way (i.e. not just assigning them toObjectreferences) using wildcards:The reference type
ColorMap<?>can be thought of as "AColorMapof some specific, but unknown, type". (I think ofColorMap<Integer>as "AColorMapof typeInteger", etc.)You can also use bounded wild cards. Since both
IntegerandDoubleare subclasses ofNumber, you can specify that in the types:The way to think of
ColorMap<? extends Number>is "AColorMapof some specific type that isNumberor a subclass ofNumber". HereNumberis an upper bound for the parametrized type.With the current interface and class definitions you have, this is the most specific way to "unite" the types of the two different color maps: they are both
ColorMaps of some type that is a subclass ofNumber.Whether or not any of this is useful depends on what you want to do with the
ColorMaps. You can certainly doThe problem here is that the only method you have in
ColorMapconsumes values of the parametrized type (i.e. theget(...)method expects a parameter of typeT). Since the actual type for eachColorMapin our list is unknown (we only know it is some specific subclass ofNumber), the compiler cannot infer that we are passing the correct value to any given instance of aColorMap<? extends Number>. One of our instances specifically needs to be passed anInteger, the other specifically needs to be passed aDouble. Since there's no value that can be both of these things, we can't write any code like:This next part is a little artificial in this case, but if
ColorMaphad a method that produced (i.e. returns) values of typeT, then this list might be useful. It's not clear how you would implement this, but if you added a method to the interface:Then you could do:
This will compile. The complier is assured that each
ColorMapin our list has a specific value ofTthat isNumberor a subclass ofNumber. Therefore eachgetValue()method returns some kind ofNumber, and the assignmentNumber value = cm.getValue(c);is guaranteed to succeed.If you change the definition of
ContinuousColorMapa little, then there may be a nice way to use a common type without the artificialgetValue()method:This time, make
ContinuousColorMapaColorMap<Number>:Now we can do
Here
Integeris a lower bound for the parametrized type, and we can interpretColorMap<? super Integer>as "AColorMapof some specific type which is anIntegeror a superclass ofInteger". SinceNumberis a superclass of integer, the assignment forcm2compiles.For every element in the list, the
get(...)method expects some specific type, but we know that specific type must be either anIntegeror a superclass ofInteger. So if we pass in anInteger, that call is guaranteed to succeed. Consequently, we can doThis is probably now way beyond the scope of the question, but if you want you can even write a class that keeps track of
ColorMapinstances according to their type, and given aNumberwill return a color from the color map for the specific type of number provided. To do this, you use theClass<T>class as a "type token":And then you can do fun things like:
This code will use the
DiscreteColorMapto map the integers in the list (0,1, and2), and theContinuousColorMapto map the doubles in the list (0.5and1.0).