Reputation: 10561
class Attribute<T>{
private T attr;
public Attribute(T attr){
this.attr = attr;
}
}
Class Matrix<T>{
private String name;
List<Attribute<T>> list;
public Matrix(String name, T t){
this.name = name;
list = new ArrayList<>();
list.add(new Attribute<T>(t));
}
}
interface Extractor<T> {
public List<Matrix<T>> extract();
}
InfoExtractor implements Extractor<String>{
public List<Matrix<String>> extract(){
List<Matrix<String>> matrixList = new ArrayList<>();
// The problem is here!!!!
matrixList.add(new Matrix<String>("abc"));
}
}
Document<T>{
Map<String, List<Matrix<T>>> matrixMap;
public void process(){
...
Extractor<T> extractor = (Extractor<T>) new StringExtractor(sent);
List<Matrix<T>> matrix = extractor.extract(...);
}
My question is, is there a way to avoid defining Matrix as a generic type ? The reason I want to avoid is that the "List<Attribute<T>>
" is used in multiple other classes, either as private member variables, or method return types. Due to Attribute, it seems I have to define some other related classes as generic types too, which causes my problem.
In this case, is there a way to not define Matrix as generic, but keep the "list" variable as a generic type?
Upvotes: 3
Views: 130
Reputation: 11483
Your problem lies not in the generic implementation, but in its usage:
class InfoExtractor implements Extractor{
// The problem is actually here
public <T> List<Matrix<T>> extract(){
List<Matrix<T>> matrixList = new ArrayList<>(); //and here
// "The problem is here!!!!"
matrixList.add(new Matrix<String>("abc"));
}
}
The <T>
indicates that you are binding a new generic type relative to the method invocation. In short, a new T
type just for this method's execution. You also make a List<Matrix<T>>
but then attempt to add a new Matrix<String>
back. If you know the list is going to be of the type Matrix<String>
, then you can specify that in InfoExtractor
:
//If you cannot generify the interface for some reason
interface Extractor {
public List<? extends Matrix<?>> extract(); //explained at bottom
}
//IDEALLY, then implement Extractor<String> instead
interface Extractor<T> {
public List<Matrix<T>> extract();
}
class InfoExtractor implements Extractor { //or Extractor<String>
public List<Matrix<String>> extract() {
List<Matrix<String>> matrixList = new ArrayList<>();
matrixList.add(new Matrix<>("abc"));
return matrixList;
}
}
Of course, you can see the method signature for Extractor
changed. Due to using a nested generic on the return type, things will get a bit messy for type matching at compile-time. The <?>
for the Matrix is fairly self-explanatory, we're returning multiple and possibly unknown types inside of the Matrix.
By specifying ? extends Matrix
on Extractor
, we're specifying the proper variance. Concrete generics are invariant, thus a List<Toyota>
isn't a List<Car>
, even if Toyota
is a subclass of Car
. If we want the Matrix
to be covariant, then we need the bounding on Matrix as well unfortunately. This means in effect, ignoring Liskov's substitution principal, we'd essentially be referencing the concrete subclasses for the extractors (InfoExtractor ex
vs Extractor ex
), especially from a usability standpoint (as the concrete classes can return proper type safety whereas the interface cannot).
This of course, is handled much more sanely/cleanly when you specify the <T>
for the matrices in the list as a class-level generic type.
Upvotes: 2
Reputation: 2605
It depends on what you would like to do.
If you intend to use Matrix to hold a single type of attribute, that is T then the best approach seems to be the one you posted as you get to have compile time type checking.
If you remove the generic type T from Matrix then you cannot know the type of each attribute. You can completely remove T from Matrix declaration and use the wildcard for the Attribute variable. While this would allow you to store attributes of different T types in the same Matrix object, it will remove the compile type checking. In other words, the classes using the Matrix would have to know what class to expect and perhaps cast each attribute to the class expected.
Class Matrix{
private String name;
List<Attribute<?>> list;
public Matrix(String name, T t){
this.name = name;
list = new ArrayList<>();
list.add(new Attribute<>(t));
}
}
Adding a Class variable and saving the Class object inside the attribute, could help you retain some information in runtime to identify the class of the objects inside the attribute.
class Attribute<T>{
private T attr;
private final Class<T> clazz;
public Attribute(T attr, Class<T> clazz){
this.attr = attr;
this.clazz=clazz;
}
public Attribute(T attr){
this.attr = attr;
Objects.requireNonNull(attr);
this.clazz= (Class<T>) attr.getClass();
}
}
Using this approach the original constructor can be used only for non null values, otherwise the class object should be provided by the programmer.
Upvotes: 0