This is not really a question about how the Consumer
- or other interfaces - in Java work, but about Generics
.
Generics aim to simplify the way of writing code and avoid code repetitions. E.g. you need to do a similar task, but for different types you can write it once by using Generics
instead of writing it over and over again, just with concrete types being replaced.
For example one day you have the need to keep track of a list of Strings
. As easy as that, your going ahead and implementing a solution for that, whereby the first implementation can look like the following (note: a very simplified example, but it'll show the purpose):
public class CustomListString {
private String[] elements = new String[10];
public void add(String newElement) {
int nextFreeIndex = findNextFreeIndex();
elements[nextFreeIndex] = newElement;
}
public String get(int index) {
return elements[index];
}
}
So you can use the above implementation of the List
in your code like the following:
public static void main(String[] args) {
CustomListString listOfStrings = new CustomListString();
listOfStrings.add("A");
listOfStrings.add("B");
}
Simple, specific and sufficient!
But the other day, you also have the requirement to keep track of a list of Integers
. What to do now?
A way to solve this is to just repeat your previous approach and to implement another CustomList
only for the Integers
now. Where the corresponding implementation would look like this (the implementation of CustomListString
has been copied and all occurrences of String
have been replaced by Integer
):
public class CustomListInteger {
private Integer[] elements = new Integer[10];
public void add(Integer newElement) {
int nextFreeIndex = findNextFreeIndex();
elements[nextFreeIndex] = newElement;
}
public Integer get(int index) {
return elements[index];
}
}
As you can imagine now already, this is not flexible and can be very cumbersome in the future. This approach will require a new implementation of each type you want to store in the future. So you might end up to also create implementations like CustomListDouble
, CustomListCharacter
, ... and so on, in which only the type of the elements within the array change - nothing else which would be of importance!
This will additionally lead to the situation, that you'll duplicate a lot of similar code (like findNextFreeIndex()
method would have been) and in case of a bugfix need to adjust it in a lot of places instead of in only one.
To solve this issue and remain the type safety in the CustomList.get
method Generics
have been introduced to Java!
With the Generics
approach you'll be able to create a single implementation of the CustomList
to store all of your data types without unnecessarily duplicating any shared, basic code and remain the type safety!
public class CustomList<T> {
private Object[] elements = new Object[10]; // Java doesn't supprort easily support generic arrays, so using Object
// here. But the compiler ensures only elements of the generic type T
// will end up here
public void add(T newElement) {
int nextFreeIndex = findNextFreeIndex();
elements[nextFreeIndex] = newElement;
}
@SuppressWarnings("unchecked")
public T get(int index) {
return (T) elements[index];
}
}
Using the new list following the Generics
approach we can use it like this now:
public static void main(String[] args) {
CustomList<String> genericList = new CustomList<>();
genericList.add("Hello World");
genericList.add(5); // Compile error! Integer and String types cannot be mixed in
// a single instance of the list anymore => Nice, prevents errors!
genericList.get(0).substring(6); // No compile error, also the compiler knows Strings
// are contained in the list
}
The generic CustomList
can now also be reused for any other type and still provide type safety.
What does it mean for your implementation
You can see how we specified the generic type in the CustomList
class as T
- this is similar like you specified it with ?
(probably you'll also want to replace it with T
, since you'll run into other issues later when working with the Consumer
). But when we used the implementation in our other classes, it wouldn't have been possible to specify it as CustomList<T>
or CustomList<?>
anymore. We needed to decide and specifiy which exact type of elements the list should contain. This has been the String
class, so we specified it as CustomList<String>
.
Note: ?
is a generic wildcard and means something like "I don't know the real type of the classes now and I'll also don't know it in the future". That's why it'll be hard for you working with the concrete types later in the Consumer
. You'll be not able to call any conrete methods on your objects therein. Therefore ?
should be avoided as a generic type argument and something like T
should be used instead. T
means something like "I don't know the real type of the classes now, but I'll do later, as soon as you tell me". Therfore you'll be able to call concrete methods on the objects later in the Consumer
, what will simplify your work there a lot.
For your code this means, wherever you want to use your implementation of GenClass<T>
you need to specify with which exact kind of elements the class is going to work with. In case of String
it is GenClass<String>
in case of Character
GenClass<Character>
.
So the place you'll need to replace the occurrences of GenClass<?>
is wherever you refer to it in Otherclass
and Otherclass.testmethod
.
The way you used the Consumer
is fine